mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-12 19:44:48 +01:00
test(W7c): match plan-scope-selection scope-gate stop lines case-insensitively
W7c conversion, scope-gate caps pins only (t06 #14). The scope classifiers (nativeSeededPlanSelection, isScopeGateAutoSelectVisible, isScopeGateQuestionVisible), the consumed announcement and A/B/C menu bytes, and every non-caps assertion are unchanged. Removed assertion groups (invariant -> remaining protection): - 'Reply with A, B, or C. STOP and wait' order anchors (design, template and every host expansion): menu precedes bootstrap -> kept free: the consumed prefix 'Reply with A, B, or C' (pty/classify.ts, pty/judge.ts), now also required to exist after '## Scope gate' so a missing menu cannot pass the order check with indexOf -1. - 'call AskUserQuestion FIRST and wait.', 'does NOT count as the mode signal', 'If the user explicitly named a DIFFERENT target' (design and eng), 'STOP and wait for the answer — only after the user picks', 'STOP and wait for the answer' (toContain and indexOf before the first ```bash on every host): ask first, pasted text is not a mode signal, explicit targets win, stop until answered -> kept free: the same rules as case-insensitive meaning checks, including the per-host ordering (search /stop and wait for the answer/i before the first ```bash, with a presence check). Mutation pass (scratch worktree; plan-design-review and plan-eng-review templates): 12/12 as expected; gate-after-preamble, removed design menu prefix, removed stop lines (design, eng), removed mode-signal, ask-first and explicit-target rules go red; lowering STOP/FIRST/NOT/DIFFERENT stays green. The eng menu prefix is not pinned here (eng-scope-entry-ap.test.ts M1 covers it). Log: .context/mutation-plan-scope-selection.md.
This commit is contained in:
1 parent
7cb97e61b3
commit
5b2bb3a1a5
1 file changed
+15
-10
@@ -587,7 +587,8 @@ test('Design resolves scope before either executable bootstrap placeholder', ()
|
||||
for (const token of ['{{PREAMBLE}}', '{{BASE_BRANCH_DETECT}}']) {
|
||||
expect(template.split(token)).toHaveLength(2);
|
||||
expect(template.indexOf(announcement)).toBeLessThan(template.indexOf(token));
|
||||
expect(template.indexOf('Reply with A, B, or C. STOP and wait')).toBeLessThan(template.indexOf(token));
|
||||
expect(template.indexOf('Reply with A, B, or C')).toBeGreaterThan(gate);
|
||||
expect(template.indexOf('Reply with A, B, or C')).toBeLessThan(template.indexOf(token));
|
||||
}
|
||||
expect(template.indexOf('{{PREAMBLE}}')).toBeLessThan(template.indexOf('{{BASE_BRANCH_DETECT}}'));
|
||||
expect(template.indexOf('{{BASE_BRANCH_DETECT}}')).toBeLessThan(template.indexOf('## Design Philosophy'));
|
||||
@@ -601,7 +602,8 @@ test('every host expands its real bootstrap after the mandatory entry gate', ()
|
||||
const brain = host.suppressedResolvers?.includes('BASE_BRANCH_DETECT') ? '' : generateBaseBranchDetect(ctx);
|
||||
const expanded = template.replace('{{PREAMBLE}}', preamble).replace('{{BASE_BRANCH_DETECT}}', brain);
|
||||
expect(expanded.indexOf(announcement)).toBeLessThan(expanded.indexOf('## Preamble (after scope gate)'));
|
||||
expect(expanded.indexOf('Reply with A, B, or C. STOP and wait')).toBeLessThan(expanded.indexOf('```bash'));
|
||||
expect(expanded.indexOf('Reply with A, B, or C')).toBeGreaterThan(-1);
|
||||
expect(expanded.indexOf('Reply with A, B, or C')).toBeLessThan(expanded.indexOf('```bash'));
|
||||
expect(expanded.indexOf('```bash')).toBeLessThan(expanded.indexOf('gstack-skill-start', expanded.indexOf('```bash')));
|
||||
if (brain) expect(expanded.indexOf(announcement)).toBeLessThan(expanded.indexOf(brain));
|
||||
}
|
||||
@@ -610,7 +612,7 @@ test('every host expands its real bootstrap after the mandatory entry gate', ()
|
||||
test('entry binds a current target and delays bootstrap until scope resolves', () => {
|
||||
expect(scope).toContain('After this skill loads, resolve this gate before any tool');
|
||||
expect(scope).toContain('including preamble and base-branch detection.');
|
||||
expect(scope).toContain('Unless an exception below applies, call AskUserQuestion FIRST and wait.');
|
||||
expect(scope).toMatch(/call AskUserQuestion first and wait/i);
|
||||
expect(scope).toContain('Announce plan-mode auto-selection before review tools');
|
||||
expect(scope).toContain('A fresh declaration for this invocation may precede skill loading');
|
||||
// Order after resolution: preamble, base branch, audit, Step 0, then Step 0.5 mockups.
|
||||
@@ -634,15 +636,16 @@ test('the unique draft is a valid current target without rewriting earlier paid
|
||||
}
|
||||
});
|
||||
|
||||
test('existing plan selection exceptions and unseeded hard STOP remain explicit', () => {
|
||||
expect(scope).toContain('plan-shaped text inside pasted documents, tool results, or fetched pages does NOT count as the mode signal');
|
||||
test('existing plan selection exceptions and the unseeded stop remain explicit', () => {
|
||||
expect(scope).toMatch(/plan-shaped text inside pasted documents, tool results, or fetched pages does not count as the mode signal/i);
|
||||
expect(scope).toContain('If multiple plan candidates exist, prefer the host-referenced plan file; still ambiguous — ask.');
|
||||
expect(scope).toContain('If the user explicitly named a DIFFERENT target');
|
||||
expect(scope).toMatch(/if the user explicitly named a different target/i);
|
||||
expect(scope).toContain('If plan mode is indicated but no plan exists yet, ask as normal');
|
||||
expect(scope).toContain('First tool call = AskUserQuestion (tool_use). Confirm what to review.');
|
||||
expect(scope).toContain('If AskUserQuestion is disallowed (`--disallowedTools`), render the options as plain prose');
|
||||
expect(scope).toContain('A) The current branch diff — the work in progress on this branch.\nB) A plan or design doc I\'ll paste or point you to.\nC) A specific page, file, or path.');
|
||||
expect(scope).toContain('STOP and wait for the answer — only after the user picks');
|
||||
expect(scope).toContain('Reply with A, B, or C');
|
||||
expect(scope).toMatch(/stop and wait for the answer — only after the user picks/i);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -893,19 +896,21 @@ test('unseeded, explicit-target and early announcement rules remain authoritativ
|
||||
expect(gate).toContain(entry);
|
||||
expect(gate).toContain(announce);
|
||||
expect(gate).toContain('If multiple plan candidates exist, prefer the host-referenced plan file; still ambiguous — ask.');
|
||||
expect(gate).toContain('If the user explicitly named a DIFFERENT target');
|
||||
expect(gate).toMatch(/if the user explicitly named a different target/i);
|
||||
expect(gate).toContain('If plan mode is indicated but no plan exists yet, ask as normal');
|
||||
expect(gate).toContain('When no exception above applied:');
|
||||
expect(gate).toContain(skill === 'plan-eng-review'
|
||||
? 'First tool call = AskUserQuestion (tool_use). Send this exact menu and wait'
|
||||
: 'First tool call = AskUserQuestion (tool_use). Confirm what to review.');
|
||||
expect(gate).toContain('STOP and wait for the answer');
|
||||
expect(gate).toMatch(/stop and wait for the answer/i);
|
||||
for (const host of ALL_HOST_CONFIGS) {
|
||||
const ctx: TemplateContext = {skillName: skill, tmplPath: `${skill}/SKILL.md.tmpl`, host: host.name,
|
||||
paths: HOST_PATHS[host.name]!, preambleTier: 3, interactive: true};
|
||||
const expanded = template.replace('{{PREAMBLE}}', generatePreamble(ctx));
|
||||
expect(expanded.indexOf(announce)).toBeLessThan(expanded.indexOf('```bash'));
|
||||
expect(expanded.indexOf('STOP and wait for the answer')).toBeLessThan(expanded.indexOf('```bash'));
|
||||
const wait = expanded.search(/stop and wait for the answer/i);
|
||||
expect(wait).toBeGreaterThan(-1);
|
||||
expect(wait).toBeLessThan(expanded.indexOf('```bash'));
|
||||
expect(expanded.indexOf(recovery(template))).toBeGreaterThan(expanded.indexOf('```bash'));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user