mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-11 07:29:00 +02:00
feat(plan-eng/design-review): auto-select B in plan mode at the scope gate
In plan mode the scope gate's "What should I review? A/B/C" question is pure
friction: there is no branch diff and the target is the plan being drafted.
Both gates gain an ordered exceptions block, checked BEFORE asking:
1. Plan mode → auto-select B: review the active plan (in context or pasted),
announce it in one line ("Scope gate: plan mode — auto-selected B
(reviewing <target>)") so the user can interrupt; an explicitly different
user-named target still wins; no plan drafted yet → ask as normal.
2. User-named target (outside plan mode): explicit-only — a path, a pasted
doc, or the literal words "branch diff". A passing mention is not naming;
when in doubt, ask.
Outside plan mode with no explicitly-named target, nothing changes. Plan-mode
is checked FIRST because the PTY harness seeds drafts as pasted user messages
(claude-pty-runner.ts:1600) — ordering makes the seeded smokes deterministic.
Pinning: seeded plan-mode smokes assert no gate render + announcement rendered
(eng test 2; new design seeded test); plan-mode-no-op extends to eng/design
(bypass must not misfire outside plan mode; first question must be the gate)
plus a named-target case proving the pasted target is consumed; a drift-guard
asserts the two hand-duplicated exceptions blocks stay identical modulo the
two variant slots and carry the announcement string the detectors pin.
Skeleton ceilings ratcheted with comments (eng 68k, design 89k; eng union
ratio 1.08→1.09) — measured 67,006 B / 88,226 B after regen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
44cd3037f0
commit
2009f89283
@@ -3244,6 +3244,54 @@ describe('EXIT PLAN MODE GATE placement', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('scope-gate exceptions drift-guard', () => {
|
||||
// The plan-mode auto-select-B exceptions block is hand-duplicated in the
|
||||
// plan-eng-review and plan-design-review templates (matching the gate
|
||||
// around it, which predates this block). The two copies must stay
|
||||
// byte-identical modulo exactly two known variant slots:
|
||||
// 1. the plan-mode bullet's action tail (Design Doc Check vs pre-review
|
||||
// audit + mockups),
|
||||
// 2. the named-target vocabulary ("a path, a doc" vs "a path, a page, a doc").
|
||||
// A future edit to one copy that silently misses the other fails here
|
||||
// instead of drifting. The real fix (shared {{SCOPE_GATE}} resolver) is a
|
||||
// filed TODO — this guard is the stopgap that makes the duplication safe.
|
||||
const START_MARKER = '**Exceptions — check in this order, BEFORE asking:**';
|
||||
const END_MARKER = 'nothing changes: this gate is a hard STOP.';
|
||||
|
||||
function extractExceptionsBlock(skill: string): string {
|
||||
const md = fs.readFileSync(path.join(ROOT, skill, 'SKILL.md'), 'utf-8');
|
||||
const start = md.indexOf(START_MARKER);
|
||||
expect(start, `${skill}/SKILL.md: exceptions block start marker present`).toBeGreaterThan(-1);
|
||||
const end = md.indexOf(END_MARKER, start);
|
||||
expect(end, `${skill}/SKILL.md: exceptions block end marker present`).toBeGreaterThan(start);
|
||||
return md.slice(start, end + END_MARKER.length);
|
||||
}
|
||||
|
||||
const normalizeVariantSlots = (block: string) =>
|
||||
block
|
||||
.replace('Then run the Design Doc Check and Step 0 against that plan.', '<ACTION_TAIL>')
|
||||
.replace('Then run the pre-review audit, mockups, and Step 0 against that plan.', '<ACTION_TAIL>')
|
||||
.replace('a path, a page, a doc they pasted,', 'a path, a doc they pasted,');
|
||||
|
||||
test('eng and design exceptions blocks are identical modulo the two variant slots', () => {
|
||||
const eng = normalizeVariantSlots(extractExceptionsBlock('plan-eng-review'));
|
||||
const design = normalizeVariantSlots(extractExceptionsBlock('plan-design-review'));
|
||||
expect(eng).toBe(design);
|
||||
// The action tail must actually have been normalized in both (guards
|
||||
// against a rewording that bypasses the normalizer and vacuously passes).
|
||||
expect(eng).toContain('<ACTION_TAIL>');
|
||||
});
|
||||
|
||||
test('exceptions block carries the announcement string the PTY detectors pin', () => {
|
||||
for (const skill of ['plan-eng-review', 'plan-design-review']) {
|
||||
const block = extractExceptionsBlock(skill);
|
||||
expect(block, `${skill}: verbatim announcement`).toContain(
|
||||
'Scope gate: plan mode — auto-selected B (reviewing <target>).',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('GSTACK REVIEW REPORT mandatory unresolved-decisions status', () => {
|
||||
// Report text rides in PLAN_FILE_REVIEW_REPORT → every report consumer gets it.
|
||||
// devex-review is a report consumer but NOT a gate consumer, so the two target
|
||||
|
||||
Reference in New Issue
Block a user