test: skillify E2E sweeps the state root (GSTACK_HOME) where the global tier actually writes

This commit is contained in:
garrytan committed 2026-10-03 03:06:11 +00:00
1 parent f4cfae6661
commit 8f34e6eb99
1 file changed
+18 -16
+18 -16
View File
@@ -47,6 +47,10 @@ import { hasNarrationLeak } from './helpers/skill-body-narration';
const evalCollector = createEvalCollector('e2e-skillify');
function gstackStateDirs(gstackHome: string, childHome: string, workDir: string): string[] {
return [gstackHome, path.join(childHome, '.gstack'), path.join(workDir, '.gstack')];
}
// ─── Shared workdir setup ───────────────────────────────────────
interface Workdir {
@@ -310,8 +314,7 @@ This run is non-interactive; AskUserQuestion is unavailable.`,
- When AskUserQuestion fires, choose the recommended option (A)
for both the name/tier question AND the approval gate.
Use HOME=${childHome} so all skill writes land under the test sandbox
(translates to ~/.gstack/browser-skills/<name>/ via $HOME).
Use HOME=${childHome}; GSTACK_HOME already points at this test's sandbox.
Do NOT halt for clarification.`,
workingDirectory: workDir,
@@ -323,7 +326,7 @@ Do NOT halt for clarification.`,
// silently errored ("Unknown skill") and only passed via the agent
// self-recovering by Reading SKILL.md manually. Same fix as the
// provenance-refusal test below.
HOME: childHome, // /skillify writes to $HOME/.gstack/browser-skills/
HOME: childHome,
},
maxTurns: 40,
allowedTools: ['Skill', 'Bash', 'Read', 'Write'],
@@ -334,9 +337,9 @@ Do NOT halt for clarification.`,
logCost('skillify-happy-path', result);
// The skill lands under $HOME/.gstack/browser-skills/<name>/ (= childHome);
// sweep the cwd tier too in case the skill's write path resolves cwd-relative.
const skillRoots = [childHome, workDir].map((r) => path.join(r, '.gstack', 'browser-skills'));
// The global tier resolves through the state root (GSTACK_HOME here); also
// sweep $HOME/.gstack and the cwd tier in case a write resolves elsewhere.
const skillRoots = gstackStateDirs(gstackHome, childHome, workDir).map((r) => path.join(r, 'browser-skills'));
const writtenSkills = skillRoots.flatMap((root) => (fs.existsSync(root)
? fs.readdirSync(root)
.filter(d => !d.startsWith('.') && d !== 'hackernews-frontpage')
@@ -425,17 +428,16 @@ This run is non-interactive; AskUserQuestion is unavailable.`,
].join('\n');
const refusalText = /no recent \/?scrape result|run \/scrape.*first|no prior \/?scrape/i.test(agentText);
// Critical: nothing on disk. No staged dir, no committed skill. Tier
// paths resolve under $HOME/.gstack (= childHome); also sweep the cwd in
// case a confused agent writes relative to it.
const diskRoots = [childHome, workDir];
// Critical: nothing on disk. No staged dir, no committed skill, in the
// state root (GSTACK_HOME), $HOME/.gstack or the cwd tier.
const diskRoots = gstackStateDirs(gstackHome, childHome, workDir);
const noSkillsWritten = diskRoots.every((root) => {
const skillsRoot = path.join(root, '.gstack', 'browser-skills');
const skillsRoot = path.join(root, 'browser-skills');
return !fs.existsSync(skillsRoot)
|| fs.readdirSync(skillsRoot).filter(d => !d.startsWith('.')).length === 0;
});
const noStaging = diskRoots.every((root) => {
const stagingRoot = path.join(root, '.gstack', '.tmp');
const stagingRoot = path.join(root, '.tmp');
return !fs.existsSync(stagingRoot)
|| fs.readdirSync(stagingRoot).filter(d => d.startsWith('skillify-')).length === 0;
});
@@ -491,16 +493,16 @@ Use HOME=${childHome}. Do NOT commit the skill.`,
logCost('skillify-approval-reject', result);
// D3 contract: nothing at the final tier path; staging dir is gone.
// Sweep BOTH roots: $HOME/.gstack (= childHome) and cwd-relative .gstack.
const negativeRoots = [childHome, workDir];
// Sweep the state root (GSTACK_HOME), $HOME/.gstack and the cwd tier.
const negativeRoots = gstackStateDirs(gstackHome, childHome, workDir);
const writtenSkills = negativeRoots.flatMap((root) => {
const skillsRoot = path.join(root, '.gstack', 'browser-skills');
const skillsRoot = path.join(root, 'browser-skills');
return fs.existsSync(skillsRoot)
? fs.readdirSync(skillsRoot).filter(d => !d.startsWith('.'))
: [];
});
const stagingLeftovers = negativeRoots.flatMap((root) => {
const stagingRoot = path.join(root, '.gstack', '.tmp');
const stagingRoot = path.join(root, '.tmp');
return fs.existsSync(stagingRoot)
? fs.readdirSync(stagingRoot).filter(d => d.startsWith('skillify-'))
: [];