test: recognize valid review evidence and bind legacy diff requests

This commit is contained in:
Garry Tan committed 2026-09-11 05:23:28 +00:00
1 parent 5ab30bfbda
commit d4b1675be9
18 files changed
+861 -65

No files matched your search

+32
View File
@@ -165,6 +165,38 @@ describe('owned Autoplan artifact edit permission', () => {
expect(pick(r)).toBeNull(); // Existing context is not part of the requested deletion.
});
// AZ's public line 116 wraps at column five, not the old fixed column four.
// These small panes exercise the same renderer rule without a transcript corpus.
for (const [line, numbered, continuation] of [
[7, ' 7 ', ' '], [17, ' 17 ', ' '],
[116, ' 116 ', ' '], [1024, ' 1024 ', ' '],
] as const) test(`wrapped line ${line} binds its own marker column and exact requested bytes`, () => {
const r = replay(), old = 'Old first portion kept together', replacement = 'New first portion kept together';
const before = Array.from({ length: line - 1 }, (_, n) => `Context ${n}`).concat(old, 'Context tail').join('\n');
fs.writeFileSync(r.file, before);
r.context.publicTools.filter(event => event.name === 'Write').at(-1)!.input!.content = before;
const edit = r.context.publicTools.at(-1)!;
edit.input!.old_string = old; edit.input!.new_string = replacement;
const menu = r.viewport.slice(r.viewport.indexOf(' Do you want'));
const rows = `${numbered}-Old first portion\n${continuation}- kept together\n` +
`${numbered}+New first portion\n${continuation}+ kept together\n`;
const pane = rows + '╌'.repeat(20) + '\n' + menu;
r.viewport = pane;
expect(pick(r)).toEqual({ input: '1\r', signature: `${edit.sessionId}:${edit.toolUseId}`, file: r.file });
expect(pick(r, new Set([pick(r)!.signature]))).toBeNull();
for (const invalid of [
pane.replaceAll(`\n${continuation}`, `\n${continuation.slice(1)}`), // left-shifted continuation
pane.replaceAll(`\n${continuation}`, `\n ${continuation}`), // right-shifted continuation
pane.replace(`${continuation}- kept`, `${continuation}+ kept`), // different kind
pane.replace(`${numbered}+New`, ` ${numbered}+New`), // mixed complete-row columns
`${continuation}- kept together\n` + pane, // no owning numbered row
pane.replace('New first portion', 'Foreign replacement'),
pane.replace(numbered, ' 0 '),
pane.replace(numbered, ' 01 '),
pane.replace(numbered, ' 9007199254740992 '),
]) { r.viewport = invalid; expect(pick(r), invalid).toBeNull(); }
});
test('an earlier unresolved mutation cannot make the latest completed Edit current', () => {
const r = replay(); const events = r.context.publicTools; const edit = events.at(-1)!;
events.splice(-1, 0, { ...structuredClone(edit), toolUseId: 'earlier-unresolved-edit',
+3
View File
@@ -46,6 +46,9 @@ test('exact suffix, corresponding line, next line and complete crop content are
(s:string)=>s.replace(/^ \+t\./,' +x.'), (s:string)=>s.replace(/^ \+t\./,' +t!'),
(s:string)=>s.replace(/^ \+t\./,' +t.'),(s:string)=>s.replace(/^ \+t\./,' +t.'),
(s:string)=>s.replace(/^ \+t\./,' -t.'),(s:string)=>s.replace(/^ \+t\./,' Source: t.'),
// A forged deletion marker cannot make rejected digest rows use legacy authority.
(s:string)=>s.replace(/^ \+t\./,' -t.').replace(/^ 139 /m,' 140 '),
(s:string)=>s.replace(/^ \+t\./,' -t.').replace('Snapshot consistency','Foreign consistency'),
(s:string)=>s.replace(/^ 139 /m,' 140 '),(s:string)=>s.replace('Snapshot consistency','Foreign consistency'),
(s:string)=>s.replace('authoritative gate','unrequested gate'),(s:string)=>'> source\n'+s,
(s:string)=>s.replace('3. No','3. Maybe'),(s:string)=>s.replace('❯ 1. Yes','❯ 2. Yes'),
+12
View File
@@ -137,5 +137,17 @@ test.each([
r.context.pending!.editDigest={...digest,beforeSHA256:'malformed'};expect(pick(r)).toBeNull();
r.context.pending!.editDigest=digest;
const viewport=r.viewport;r.viewport=r.viewport.replace(/^((?: {0,3}\d+ | {4})-).*$/gm,'$1Foreign unowned deletion');expect(pick(r)).toBeNull();r.viewport=viewport;
// The digest's request ownership remains binding through the legacy crop path.
r.context.pending!.editDigest={...digest,oldLineHashes:[autoplanEditLineHash('Context')]};expect(pick(r)).toBeNull();
r.context.pending!.editDigest=digest;
if(c.rows.some(row=>/^[ ]*\d+ \+/.test(row))){
r.viewport=viewport.replace(/^([ ]*\d+ \+).*$/gm,'$1Context');expect(pick(r)).toBeNull();r.viewport=viewport;
}
if(c.name==='leading partial deletion'){
r.viewport=viewport.replace(' -full line',' -Context');expect(pick(r)).toBeNull();r.viewport=viewport;
}
if(c.name==='leading partial context'){
r.viewport=viewport.replace(' full line',' +full line');expect(pick(r)).toBeNull();r.viewport=viewport;
}
fs.unlinkSync(r.file);expect(pick(r)).toBeNull();
});
+21 -2
View File
@@ -35,7 +35,7 @@ test('the exact retained prior diff output does not hide the current published o
});
test('completed diff rows are ignored only before one complete current native panel', () => {
for (const prefix of [' 1 +Previous completed output\n\n', ' +cropped prior row\n 2 +next prior row\n +wrapped row\n\n', ' 1 -Old value\n 1 +New value\n\n']) {
for (const prefix of [' 1 +Previous completed output\n\n', ' +cropped prior row\n 12 +next prior row\n +wrapped row\n\n', ' 1 -Old value\n 1 +New value\n\n']) {
const r = replay(); r.viewport = prefix + r.panel; expect(pick(r)?.input).toBe('1\r');
}
});
@@ -51,11 +51,30 @@ test('competing headers, previous panels, misleading prose and quotes remain rej
});
test('malformed completed-output gutters cannot become a panel delimiter', () => {
for (const prefix of [' 1 +wrong indent\n', ' 0 +zero line\n', ' 9007199254740992 +unsafe line\n', ' 1 +row\n +short wrap\n', ' 1 +row\n -wrong kind\n', ' +only a cropped fragment\n']) {
for (const prefix of [' 1 +wrong indent\n', ' 0 +zero line\n', ' 9007199254740992 +unsafe line\n', ' 11 +row\n +short wrap\n', ' 11 +row\n -wrong kind\n', ' +only a cropped fragment\n']) {
const r = replay(); r.viewport = prefix + r.panel; expect(pick(r)).toBeNull();
}
});
for (const [numbered, continuation] of [
[' 7 ', ' '], [' 17 ', ' '],
[' 116 ', ' '], [' 1024 ', ' '],
] as const) test(`completed prefix ${numbered.trim()} infers one column before checking cropped and wrapped rows`, () => {
const r = replay();
const prefix = `${continuation}+leading cropped fragment\n${numbered}+Previous completed\n${continuation}+ output\n\n`;
r.viewport = prefix + r.panel;
expect(pick(r)?.input).toBe('1\r');
for (const invalid of [
prefix.replaceAll(continuation + '+', continuation.slice(1) + '+'),
prefix.replaceAll(continuation + '+', ' ' + continuation + '+'),
prefix.replace(continuation + '+ output', continuation + '- output'),
prefix + numbered.replace(/(\d+) /, '$10 ') + '+mixed column\n',
prefix.replace(numbered + '+', ' ' + numbered.trim() + ' +'),
prefix.replace(numbered + '+Previous completed\n', ''),
'Example:\n' + prefix,
]) { r.viewport = invalid + r.panel; expect(pick(r), invalid).toBeNull(); }
});
test('the complete current header, exact target, menu and requested replacement remain binding', () => {
for (const change of [
(r: ReturnType<typeof replay>) => { r.viewport = r.viewport.replace('● Update(~/.gstack/', '● Update(/foreign/'); },
+28
View File
@@ -116,6 +116,34 @@ test('queued unrelated public tools do not confer permission or block the curren
r.context.publicTools.at(-1)!.name='Write';expect(pick(r)).toBeNull();
});
for (const [line, numbered, next, continuation] of [
[7, ' 7 ', ' 8 ', ' '], [17, ' 17 ', ' 18 ', ' '],
[116, ' 116 ', ' 117 ', ' '], [1024, ' 1024 ', ' 1025 ', ' '],
] as const) test(`legacy pending deletion line ${line} binds leading and wrapped fragments to its numbered column`, () => {
const r = replay();
expect(r.context.pending?.editDigest).toBeUndefined();
const before = Array.from({ length: line - 2 }, (_, n) => `Context ${n}`)
.concat('Head before crop tail', 'Old complete row', 'Context').join('\n');
fs.writeFileSync(r.file, before);
const at = new Date(Date.parse(r.context.pending!.timestamp) - 1); fs.utimesSync(r.file, at, at);
const menu = r.screen.slice(r.screen.indexOf(' Do you want'));
const rows = `${continuation}-tail\n${numbered}-Old complete\n${continuation}- row\n` +
`${numbered}+New complete\n${continuation}+ row\n${next} Context\n`;
const pane = rows + '╌'.repeat(20) + '\n' + menu;
r.screen = pane;
expect(pick(r)?.input).toBe('1\r');
expect(pick(r, new Set([pick(r)!.signature]))).toBeNull();
for (const invalid of [
pane.replaceAll(continuation + '-', continuation.slice(1) + '-'),
pane.replaceAll(continuation + '-', ' ' + continuation + '-'),
pane.replace(continuation + '- row', continuation + '+ row'),
pane.replace(next + ' Context', ' ' + next + ' Context'),
pane.replace('Old complete', 'Unrelated deleted'),
pane.replace(continuation + '-tail', continuation + '-foreign suffix'),
pane.replaceAll(numbered, ' 0 '),
]) { r.screen = invalid; expect(pick(r), invalid).toBeNull(); }
});
test.skipIf(process.platform==='win32')('real launcher installs only opt-in owned hooks and removes records on close or early exit',async()=>{
const root=fs.mkdtempSync(path.join(os.tmpdir(),'gstack-artifact-launch-'));roots.push(root);
const fake=path.join(root,'fake-claude');fs.writeFileSync(fake,`#!${process.execPath}\n`+String.raw`
+113
View File
@@ -41,6 +41,119 @@ function replay(input: NativePlanQuestionCall[], first = isDesignCountFirstRevie
return { ...counts, started, phases };
}
describe('a declared primary-action issue owns its native amendment and open gap', () => {
// Minimal AZ public question and choices; the full transcript stays local.
const current = (): NativePlanQuestionCall => {
const question = 'D2 — Issue 1 (G1): make Save the visible primary action\n' +
'Project/branch/task: main branch, account-settings header action group.\n' +
'ELI10: Four header buttons currently share one style. A user who just edited their email has to read all four labels to find the one that stores the change.';
const options = [
{label: '1A Apply DESIGN.md token (recommended)', description: 'Save filled #1d4ed8 white; Reset, Cancel, Export neutral ghost. Verify ghost text and border contrast.'},
{label: '1B Spacing-only separation', description: 'Keep four equal buttons, add a gap before Save. Violates DESIGN.md.'},
{label: '1C Defer', description: 'Leave G1 open and record it as unresolved.'},
];
return {sessionId: 'az-design', toolUseId: 'primary', answered: true, failed: false,
unansweredQuestionIndices: [], answeredAt: '2026-09-11T04:04:10.000Z',
questions: [{header: 'Issue 1', question, options, multiSelect: false}],
answers: {[question]: options[0]!.label}};
};
const changed = (change: (q: NativePlanQuestionCall['questions'][number]) => void) => {
const c = current(), q = c.questions[0]!; change(q);
c.answers = {[q.question]: q.options[0]!.label}; return fingerprint(c);
};
test('the declared gap starts review for every offered answer, with optional question punctuation', () => {
const c = current(), q = c.questions[0]!;
for (const option of q.options) {
c.answers = {[q.question]: option.label};
expect(isDesignCountFirstReview(fingerprint(c))).toBe(true);
}
expect(isDesignCountFirstReview(changed(q => {q.question = q.question.replace('action\n', 'action?\n');}))).toBe(true);
expect(isDesignCountFirstReview(changed(q => {
q.question = q.question.replaceAll('Save', 'Publish').replace('(G1)', '(G7)').replace('Issue 1', 'Issue 3')
.replace('Four', '4').replace('share one style', 'look identical').replace('\nELI10:', '\n[P1]\nELI10:');
q.header = 'Issue 3';
q.options = q.options.map(o => ({label: o.label.replace(/^1/, '3'), description: o.description.replaceAll('Save', 'Publish')
.replace('#1d4ed8 white', '#ffee22 with black text').replace('Reset, Cancel, Export', 'Export/Reset/Cancel').replace('G1', 'G7')}));
}))).toBe(true);
expect(isDesignCountFirstReview(changed(q => {
q.question = q.question.replace(' (G1)', ''); q.options[2]!.description = 'Leave Issue 1 open and record it as unresolved.';
}))).toBe(true);
expect(isDesignCountFirstReview(changed(q => {
q.question = q.question.replace(' (G1)', '').replace('action\n', 'action?\n');
q.options[2]!.description = 'Leave Issue 1 open and record it as unresolved.';
}))).toBe(true);
});
test('current gap, distinct primary/peers, native authority and owned deferral are required together', () => {
const changes: Array<(q: NativePlanQuestionCall['questions'][number]) => void> = [
q => {q.question = q.question.replace('currently share', 'used to share');},
q => {q.question = q.question.replace('Four', 'Three');},
q => {q.question = q.question.replace('ELI10:', '> ELI10:');},
q => {q.question = q.question.replace('Four header', 'If approved, four header');},
q => {q.question += '\nELI10: Four header buttons currently share one style.';},
q => {q.question = 'Historical example:\n' + q.question;},
q => {q.question = q.question.replace('\nELI10:', '\nSource example:\nELI10:');},
q => {q.header = 'Issue 2';},
q => {q.options[0]!.description = q.options[0]!.description.replace('Save filled', 'Publish filled');},
q => {q.options[0]!.description = q.options[0]!.description.replace('Reset, Cancel, Export', 'Save, Cancel, Export');},
q => {q.options[0]!.description = q.options[0]!.description.replace('Reset, Cancel, Export', 'Reset, Cancel, Cancel');},
q => {q.options[0]!.description = q.options[0]!.description.replace('#1d4ed8', 'blue');},
q => {q.options[0]!.description = q.options[0]!.description.replace('neutral ghost', 'filled primary');},
q => {q.options[0]!.label = '2A Apply DESIGN.md token (recommended)';},
q => {q.options[0]!.label = '1A Prepare the review';},
q => {q.options[1]!.description = q.options[0]!.description; q.options[0]!.description = 'Prepare the review.';},
q => {q.options[2]!.label = '2C Defer';},
q => {q.options[2]!.description = 'Leave G2 open and record it as unresolved.';},
q => {q.options[2]!.description = 'Leave G1 closed and record it as resolved.';},
q => {q.options[2]!.description = 'Prepare the next review.';},
q => {q.question = q.question.replaceAll('Save', 'Fix'); q.options[0]!.description = 'This applies the next review step.';},
q => {q.options[0]!.description += ' This amendment keeps all four header buttons identical.';},
q => {q.options[2]!.description += ' Correction: do not leave G1 open.';},
q => {q.options[2]!.description += ' Correction: never defer Issue 1.';},
q => {q.question += ' G1 is historical.';},
q => {q.options[0]!.description += ' This amendment applies only to another project.';},
];
for (const change of changes) expect(isDesignCountFirstReview(changed(change)), change.toString()).toBe(false);
});
test('owned withdrawals and approval conditions cannot hide in any evidence body', () => {
for (const suffix of [
' This finding is withdrawn.', ' Issue 1 is "closed".', ' G1 is ‘resolved’.',
' Assuming approval, proceed with this option.', ' G1 applies only if approved.',
' This finding requires approval.', ' This token contract is withdrawn.',
' G1 is "historical".',
]) for (const owner of [-1, 0, 2]) {
expect(isDesignCountFirstReview(changed(q => {
if (owner === -1) q.question += suffix;
else q.options[owner]!.description += suffix;
})), owner + suffix).toBe(false);
}
for (const owner of [-1, 0, 2]) expect(isDesignCountFirstReview(changed(q => {
if (owner === -1) q.question += '\n"G1 is closed." G2 is closed.';
else q.options[owner]!.description += ' "G1 is closed." G2 is closed.';
}))).toBe(true);
expect(isDesignCountFirstReview(changed(q => {
q.options[0]!.description += ' "This amendment keeps all four header buttons identical."';
q.options[2]!.description += ' "Correction: do not leave G1 open." Do not leave G2 open.';
}))).toBe(true);
expect(isDesignCountFirstReview(changed(q => {
q.question += ' "G1 is historical." G2 is historical.';
q.options[0]!.description += ' "This amendment applies only to another project."';
}))).toBe(true);
});
test('the new declaration preserves native completion, answer and signature checks', () => {
for (const change of [
(c: NativePlanQuestionCall) => {c.answered = false;},
(c: NativePlanQuestionCall) => {c.failed = true;},
(c: NativePlanQuestionCall) => {delete c.answeredAt;},
(c: NativePlanQuestionCall) => {c.answers = {};},
(c: NativePlanQuestionCall) => {c.answers = {foreign: '1A Apply DESIGN.md token (recommended)'};},
(c: NativePlanQuestionCall) => {c.unansweredQuestionIndices = [0];},
(c: NativePlanQuestionCall) => {c.questions.push(structuredClone(c.questions[0]!));},
(c: NativePlanQuestionCall) => {c.questions[0]!.multiSelect = true;},
]) {const c = current(); change(c); expect(isDesignCountFirstReview(fingerprint(c))).toBe(false);}
expect(isDesignCountFirstReview({...fingerprint(current()), signature: 'foreign'})).toBe(false);
});
});
describe('A descriptive hierarchy header owns its primary and peer controls', () => {
// Minimal public excerpt of AY D3: retain its question, current gap and native
// options, without copying the full review or its repeated option prose.
+116
View File
@@ -13,6 +13,122 @@ function extra(id: string, sessionId: string): NativePlanQuestionCall {
return {sessionId,toolUseId:id,questions:[{header:'Extra',question,multiSelect:false,options:[{label:'Add command',description:'Add the command after the beta.'},{label:'Defer',description:'Defer the command.'}]}],answered:true,failed:false,answers:{[question]:'Defer'},unansweredQuestionIndices:[],answeredAt:'2026-09-09T20:23:00Z'};
}
// Minimal public AZ D6 evidence and offered correction. Keep the exact full
// failed attempt for replay; recognizing this decision grants no paid pass.
function evidenceTranscript(): PlanCountTranscript {
const t = transcript(), c = t.calls[2]!, q = c.questions[0]!;
q.question = [
'D6 — Journey stage REAL USAGE: the two public evaluation functions take the same two arguments in opposite positional order',
'Project/branch/task: EvalKit beta DX review, branch main.',
'Evidence: docs/api.md lines 5-9: `run_eval(dataset, evaluator)` and `run_batch(evaluator, dataset)`. Both arguments describe the same concepts; the reversed order is described as intentional; neither requires keywords.',
'ELI10: Your ML engineer learns `run_eval(dataset, evaluator)` from the demo, then scales up to `run_batch` and writes the arguments in the same order.',
].join('\n');
q.options = [
{ label: 'A) Align to (dataset, evaluator) (recommended)', description: 'Same order in both functions, keywords accepted, swap detected with a clear error during beta.' },
{ label: 'B) Make both keyword-only', description: 'Force run_eval(dataset=..., evaluator=...) and same for run_batch.' },
{ label: 'C) Keep order, distinct types', description: 'Leave positional order; rely on type annotations to flag swaps.' },
{ label: 'D) Acceptable friction, skip', description: 'Keep the reversed order as documented.' },
];
c.answers = { [q.question]: q.options[0]!.label };
return t;
}
describe('DX signature evidence within the current decision', () => {
test('the observed correction binds one distinct seed, including genuine alternate answers', () => {
const t = evidenceTranscript(), c = t.calls[2]!, q = c.questions[0]!;
for (const option of q.options) {
c.answers = { [q.question]: option.label };
expect(devexSeedCoverage(t).complete).toBe(true);
expect(devexSeedCoverage(t).decisions['reversed-arguments']).toEqual([`${c.sessionId}:${c.toolUseId}`]);
}
t.calls.splice(2, 1);
expect(devexSeedCoverage(t).missing).toEqual(['reversed-arguments']);
});
test('citation location, formatting and repair prose can vary without changing the evidence', () => {
for (const edit of [
(s: string) => s.replace('opposite positional order\n', 'reversed positional order.\n'),
(s: string) => s.replace('public evaluation functions', 'public functions').replace('docs/api.md lines 5-9', 'docs/public-api.md:12–16'),
(s: string) => s.replaceAll('`', '').replaceAll('(dataset, evaluator)', '( dataset , evaluator )'),
(s: string) => s.replace('ELI10: Your', 'Impact: Your').replace('Evidence: docs', 'ELI10: docs'),
]) { const t = evidenceTranscript(); changeDeclaration(t, 2, edit); expect(devexSeedCoverage(t).complete).toBe(true); }
const t = evidenceTranscript(), c = t.calls[2]!, q = c.questions[0]!;
q.options[0] = { label: 'Unify call order to (dataset, evaluator)', description: 'Both functions use the same positional order; keywords supported; swaps are rejected with an actionable message.' };
c.answers = { [q.question]: q.options[0]!.label };
expect(devexSeedCoverage(t).complete).toBe(true);
});
test('the asserted pair cannot come from healthy, foreign, borrowed or quoted evidence', () => {
for (const edit of [
(s: string) => s.replace('opposite positional order', 'the same positional order'),
(s: string) => s.replace('Journey stage REAL USAGE: ', 'Journey stage REAL USAGE: If approved, '),
(s: string) => '> ' + s,
(s: string) => s.replace('`run_batch(evaluator, dataset)`', '`run_batch(dataset, evaluator)`'),
(s: string) => s.replace('`run_batch(evaluator, dataset)`', '`other_batch(evaluator, dataset)`'),
(s: string) => s.replace('`run_eval(dataset, evaluator)`', '`other_eval(dataset, evaluator)`'),
(s: string) => s.replace(' and `run_batch(evaluator, dataset)`', ''),
(s: string) => s.replace(' and `run_batch(evaluator, dataset)`', '\nEvidence: docs/api.md: `run_batch(evaluator, dataset)`'),
(s: string) => s.replace('Evidence: ', 'Evidence: Another issue is worth discussing. '),
(s: string) => s + '\nELI10: Another explanation.',
...['> ', 'Source excerpt: ', 'Historical example: ', 'If approved: ', '"', '`'].map(prefix => (s: string) => s.replace('Evidence: ', 'Evidence: ' + prefix)),
(s: string) => s.replace(/^(Evidence:.*)$/m, '```\n$1\n```'),
(s: string) => s.replace(/^(Evidence:.*)\n(ELI10:.*)$/m, '$2\n$1'),
]) {
const t = evidenceTranscript(); changeDeclaration(t, 2, edit);
expect(devexSeedCoverage(t).missing, edit(t.calls[2]!.questions[0]!.question)).toContain('reversed-arguments');
}
});
test('current withdrawals defeat the evidence while literal historical quotations do not', () => {
for (const status of [
'These functions are now aligned.', 'These signatures are historical.',
'This evidence is withdrawn.', 'This evidence is no longer current.', 'This evidence is cancelled.', 'This evidence is hypothetical.',
'This finding applies only if approved.', 'D6 is cancelled.',
]) for (const quoted of [false, true]) {
const t = evidenceTranscript();
changeDeclaration(t, 2, s => s.replace(/^(Evidence:.*)$/m, '$1 ' + (quoted ? JSON.stringify(status) : status)));
expect(devexSeedCoverage(t).complete, `${quoted}: ${status}`).toBe(quoted);
}
const t = evidenceTranscript(); changeDeclaration(t, 2, s => s + '\nThis evidence is "withdrawn".');
expect(devexSeedCoverage(t).missing).toContain('reversed-arguments');
const scalar = evidenceTranscript(); changeDeclaration(scalar, 2, s => s + "\nThis evidence is 'withdrawn'.");
expect(devexSeedCoverage(scalar).missing).toContain('reversed-arguments');
});
test('one current offered action must align this pair and retain the swap correction', () => {
for (const edit of [
(s: string) => s.replace('Same order', 'Opposite order'),
(s: string) => s.replace('both functions', 'other functions'),
(s: string) => s.replace('both functions', 'both functions run_score and run_many'),
(s: string) => s.replace('keywords accepted, ', ''),
(s: string) => s.replace('swap detected', 'swap ignored'),
(s: string) => s.replace('clear error', 'generic failure'),
(s: string) => 'If approved, ' + s,
(s: string) => JSON.stringify(s),
(s: string) => s + ' Correction: this option is withdrawn.',
(s: string) => s + ' This option is historical.',
(s: string) => s + ' This correction applies to another project.',
(s: string) => s + ' Do not align these functions.',
]) {
const t = evidenceTranscript(); t.calls[2]!.questions[0]!.options[0]!.description = edit(t.calls[2]!.questions[0]!.options[0]!.description!);
expect(devexSeedCoverage(t).missing, edit.name).toContain('reversed-arguments');
}
const t = evidenceTranscript(), c = t.calls[2]!, q = c.questions[0]!;
q.options[0]!.label = 'Align to (evaluator, dataset)'; c.answers = { [q.question]: q.options[0]!.label };
expect(devexSeedCoverage(t).missing).toContain('reversed-arguments');
q.options = q.options.slice(1); c.answers = { [q.question]: q.options[0]!.label };
expect(devexSeedCoverage(t).missing).toContain('reversed-arguments');
});
test('native completion, session ownership and batching gates still govern the new evidence', () => {
for (const mutate of [
(c: NativePlanQuestionCall) => { c.answered = false; },
(c: NativePlanQuestionCall) => { c.failed = true; },
(c: NativePlanQuestionCall) => { c.answeredAt = 'invalid'; },
(c: NativePlanQuestionCall) => { c.sessionId = 'foreign'; },
(c: NativePlanQuestionCall) => { c.unansweredQuestionIndices = [0]; },
(c: NativePlanQuestionCall) => { c.answers = { 'Other question': c.questions[0]!.options[0]!.label }; },
(c: NativePlanQuestionCall) => { c.questions[0]!.multiSelect = true; },
(c: NativePlanQuestionCall) => { c.questions.push(structuredClone(c.questions[0]!)); },
]) { const t = evidenceTranscript(); mutate(t.calls[2]!); expect(devexSeedCoverage(t).complete).toBe(false); }
});
});
// Exact public AY headings and signature trace, applied to the existing native
// completion fixture. Full public replay remains separate from paid-run credit.
const tracedAyTitles = [
+11 -4
View File
@@ -4,18 +4,25 @@ import type { NativePublicToolEvent, PlanCountTranscript } from './helpers/plan-
import { E2E_TOUCHFILES, selectTests } from './helpers/touchfiles';
import fixture from './fixtures/eng-option-b-scope-al.json';
const input = (attempt = 1) => structuredClone(fixture.attempts[attempt]!.projection);
const actualInput = (attempt = 1) => structuredClone(fixture.attempts[attempt]!.projection);
const input = () => {
const p = actualInput();
// Mutate the named declaration alone; the earlier spoken introduction is
// independently valid and remains present in the exact replays below.
p.transcript.assistantMessages = p.transcript.assistantMessages.filter(m => m.text !== "I'll run the eng review skill on this draft plan.");
return p;
};
type Input = ReturnType<typeof input>;
const verdict = (p = input()) => nativeSeededPlanSelection(p.transcript as PlanCountTranscript, p.tools as NativePublicToolEvent[], p.opts);
const declaration = (p: Input) => p.transcript.assistantMessages.find(m => m.text.startsWith("I've selected option B,"))!;
test('both named retry and fresh unique-draft first introduction bind; original outcomes stay intact', () => {
expect(fixture.attempts.map(a => a.rawScopeGateAutoSelectObserved)).toEqual([false, false]);
expect(verdict(input(0))).toBe(true);
expect(verdict(input(1))).toBe(true);
expect(verdict(actualInput(0))).toBe(true);
expect(verdict(actualInput(1))).toBe(true);
});
test('the named target is necessary; an unnamed option-B notice supplies no selection', () => {
test('an unnamed option-B notice supplies no selection without a draft introduction', () => {
const p = input(); p.transcript.assistantMessages = p.transcript.assistantMessages.filter(m => m !== declaration(p));
expect(verdict(p)).toBe(false);
for (const replacement of ['option A,', 'option C,', 'option B if approved,', 'option B, possibly']) {
+83 -2
View File
@@ -82,7 +82,7 @@ describe('Eng current decomposition and post-rewrite error choices', () => {
});
test.each([0,1])('owned scalar statuses, including Markdown, close family %i',i=>{
for(const status of ['withdrawn','no longer current','hypothetical','resolved'])for(const [open,close]of [['',''],['"','"'],["'","'"],['‘','’'],['`','`']])for(const bold of ['', '**']) {
for(const owner of ['This finding',`D${i===0?1:5}`])rejected(i,c=>question(c,s=>`${s}\n${bold}${owner}${bold} is ${open}${status}${close}.`));
for(const owner of ['This finding','This decision',`D${i===0?1:5}`])rejected(i,c=>question(c,s=>`${s}\n${bold}${owner}${bold} is ${open}${status}${close}.`));
rejected(i,c=>options(c,o=>{o.description+=`\n${bold}This option${bold} is ${open}${status}${close}.`;}));
}
});
@@ -91,7 +91,7 @@ describe('Eng current decomposition and post-rewrite error choices', () => {
for(const status of ['This option proceeds once approved.','This remedy applies only if the user agrees.','Do not '+(i===0?'cut AuthCache and TokenStore.':'split or flatten the function.')])rejected(i,c=>options(c,o=>{o.description+='\n'+status;}));
});
test.each([0,1])('foreign and quoted historical withdrawals do not close family %i',i=>{
const c=fresh(i);question(c,s=>s+'\nD99 is withdrawn.\nEarlier reviewer said "This finding is withdrawn."');
const c=fresh(i);question(c,s=>s+'\nD99 is withdrawn.\nEarlier reviewer said "This finding is withdrawn." Earlier reviewer said "This decision is withdrawn."');
options(c,o=>{o.description+='\nEarlier reviewer said "This option is withdrawn."';});
expect(evaluate([c]).decisions[seeds[i]!]).toBeDefined();
});
@@ -136,3 +136,84 @@ test('current named-object resolutions supersede the owned defect',()=>{
const scope=fresh(0);question(scope,s=>s+'\nvalidateAndDispatch() now rethrows every error.');expect(evaluate([scope]).decisions.complexity).toBeDefined();
const errors=fresh(1);question(errors,s=>s+'\nAuthCache now has independent policy rules.');expect(evaluate([errors]).decisions['swallowed-errors']).toBeDefined();
});
// Exact public AZ question; synthetic identity/time only, without recrediting the failed run.
const step0InventoryQuestion = {
"question": "D3 — Step 0 complexity check: reduce the plan's moving parts, or proceed as-is?\nProject/branch/task: main, eng-reviewing PLAN.md (Multi-tenant Auth Refactor).\nELI10: PLAN.md:35-36 says this touches 12 files and adds 4 new classes (TokenStore, SessionMint, AuthCache, RequestPolicy) plus the AuthBroker service. That trips the complexity smell (8+ files or 2+ new classes). PLAN.md:7-13 also says an existing cache adapter already keys tokens by tenant, evicts, and invalidates on logout/revocation/suspension, and AuthCache is just a facade over it. So TokenStore looks like a second token store next to the one you already have, and RequestPolicy is a class for logic that currently has one consumer. Fewer new nouns means fewer places a tenant-isolation bug can hide and a smaller diff to review.\nStakes if we pick wrong: over-reduce and you re-add a class mid-implementation; under-reduce and you maintain two token stores with two invalidation stories, which is exactly how cross-tenant cache leaks start.\nRecommendation: A because the existing adapter already does what TokenStore describes, and RequestPolicy can start as a plain function and become a class when a second caller appears (engineered enough, not over-engineered).\nNote: options differ in kind, not coverage — no completeness score. Caveat: I cannot read the source here, so if TokenStore holds something the adapter does not (refresh tokens, mint receipts), say so and keep it.\nNet: 3 new classes with one backing store vs. 4 classes and a duplicate store vs. no facade at all.",
"header": "Scope",
"multiSelect": false,
"options": [
{
"label": "A) Reduce: cut TokenStore, demote RequestPolicy (recommended)",
"description": "✅ One token store, one invalidation story: the existing adapter behind the AuthCache facade. (human: ~1 day less / CC: ~10 min less)\n✅ AuthCache facade stays as the single seam where the shared-state fix lands in Section 1.\n❌ If TokenStore was meant to hold data the adapter cannot key, you add it back later. ~8 files, 3 new classes."
},
{
"label": "B) Proceed as-is: 4 classes, 12 files",
"description": "✅ No re-planning; every component named in the plan ships in this PR. (human: ~1 week / CC: ~1 hr)\n✅ RequestPolicy as a class is ready for a second consumer on day one.\n❌ Two token-holding components (TokenStore + adapter) means two invalidation paths to keep consistent under tenant suspension."
},
{
"label": "C) Reduce harder: no AuthCache facade, inject adapter directly",
"description": "✅ Smallest diff: 2 new services, ~6 files, zero new cache classes. (human: ~3 days / CC: ~30 min)\n✅ Both services depend on the adapter interface the existing tests already cover.\n❌ Loses the one place to serialize mutations and add tenant-scoped guards; both services must re-implement that themselves."
}
]
};
function step0InventoryCall(): NativePlanQuestionCall {
const q=structuredClone(step0InventoryQuestion);
return {sessionId:'step0-inventory',toolUseId:'owned-decision',questions:[q],answered:true,failed:false,
answers:{[q.question]:q.options[0]!.label},unansweredQuestionIndices:[],answeredAt:new Date(start+1000).toISOString()};
}
const inventorySeed=(c:NativePlanQuestionCall)=>evaluate([c]).decisions.complexity;
test('a current Step 0 decision owns its inventory and reduction in the explanation',()=>{
expect(inventorySeed(step0InventoryCall())).toBe('step0-inventory:owned-decision');
for(const option of step0InventoryQuestion.options){const c=step0InventoryCall();c.answers={[c.questions[0]!.question]:option.label};expect(inventorySeed(c)).toBeDefined();}
const c=step0InventoryCall();question(c,s=>s.replace('touches 12 files','touches 13 files'));options(c,o=>{o.label=o.label.replace('12 files','13 files');});expect(inventorySeed(c)).toBeDefined();
question(c,s=>s+'\nD99 is withdrawn.\nEarlier reviewer said "This finding is withdrawn." Earlier reviewer said "This decision is withdrawn."');expect(inventorySeed(c)).toBeDefined();
});
test('Step 0 inventory, present overlap, and a current single-option reduction are required',()=>{
for(const [before,after] of [
['says this touches','might touch'],['adds 4 new classes','adds 5 new classes'],
['(TokenStore, SessionMint, AuthCache, RequestPolicy)','(TokenStore, SessionMint, AuthCache, TokenStore)'],
['plus the AuthBroker service','plus another service'],['already keys tokens by tenant','might someday key tokens by tenant'],
['AuthCache is just a facade over it','AuthCache has independent policy rules'],
['looks like a second token store next to the one you already have','stores different data from the adapter'],
['currently has one consumer','already has two consumers'],['ELI10: ','ELI10: Source: '],
['ELI10: ','ELI10: Historical assessment. '],['ELI10: ','ELI10: If approved, '],['ELI10: ','ELI10: If the user approves, '],
]){const c=step0InventoryCall();question(c,s=>s.replace(before!,after!));expect(inventorySeed(c)).toBeUndefined();}
for(const transform of [(s:string)=>s.replace(/^ELI10: (.*)$/m,'ELI10: "$1"'),(s:string)=>s.replace(/^ELI10:.*$/m,'ELI10: General naming discussion.'),
(s:string)=>s+'\nThis decision applies only if the user agrees.',(s:string)=>s+'\nTokenStore now has a documented independent purpose.',(s:string)=>s+'\nRequestPolicy now has a second consumer.']){
const c=step0InventoryCall();question(c,transform);expect(inventorySeed(c)).toBeUndefined();
}
for(const change of [
(c:NativePlanQuestionCall)=>{c.questions[0]!.options[0]!.label='A) Keep TokenStore (recommended)';},
(c:NativePlanQuestionCall)=>{c.questions[0]!.options[0]!.description='Choose later.';},
(c:NativePlanQuestionCall)=>{const q=c.questions[0]!;q.options[1]!.description=q.options[0]!.description;q.options[0]!.description='Choose later.';},
(c:NativePlanQuestionCall)=>{c.questions[0]!.options[1]!.label='B) Proceed as-is: 5 classes, 12 files';},
(c:NativePlanQuestionCall)=>{c.questions[0]!.options[1]!.description='There is one storage layer already.';},
(c:NativePlanQuestionCall)=>{c.questions[0]!.options[0]!.description='Source:\n'+c.questions[0]!.options[0]!.description;},
(c:NativePlanQuestionCall)=>{c.questions[0]!.options[0]!.description+='\nDo not cut TokenStore.';},
(c:NativePlanQuestionCall)=>{c.questions[0]!.options[0]!.description+='\nThis remedy proceeds once approved.';},
]){const c=step0InventoryCall();change(c);c.answers={[c.questions[0]!.question]:c.questions[0]!.options[0]!.label};expect(inventorySeed(c)).toBeUndefined();}
});
test('current scalar statuses and completed native ownership still gate the Step 0 decision',()=>{
for(const scalar of ['withdrawn',"'no longer current'",'`no longer current`','conditional on approval'])for(const owner of ['finding','decision','repair','opposed']){
const c=step0InventoryCall();if(owner==='finding'||owner==='decision')question(c,s=>s+`\nThis ${owner} is ${scalar}.`);
else c.questions[0]!.options[owner==='repair'?0:1]!.description+=`\nThis option is ${scalar}.`;
expect(inventorySeed(c)).toBeUndefined();
}
for(const change of [(c:NativePlanQuestionCall)=>{c.answered=false;},(c:NativePlanQuestionCall)=>{c.failed=true;},(c:NativePlanQuestionCall)=>{c.answers={};},(c:NativePlanQuestionCall)=>{c.answeredAt=new Date(end+1).toISOString();}]){
const c=step0InventoryCall();change(c);expect(inventorySeed(c)).toBeUndefined();
}
});
// Meaning-preserving wording keeps the same inventory, overlap and opposed repairs.
test('the Step 0 relations do not depend on the original paragraph or option prose',()=>{
const c=step0InventoryCall();question(c,s=>s.replace('complexity check: reduce','complexity decision: simplify')
.replace('says this touches 12 files and adds 4 new classes','changes 12 files and introduces 4 new classes')
.replace('an existing cache adapter already keys','the current adapter keys').replace('AuthCache is just a facade over it','AuthCache remains a facade for that adapter')
.replace('TokenStore looks like a second token store next to the one you already have','TokenStore duplicates the current adapter token storage')
.replace('RequestPolicy is a class for logic that currently has one consumer','RequestPolicy serves a single consumer'));
const q=c.questions[0]!;q.options[0]!.label='A) Remove TokenStore; make RequestPolicy a plain function';q.options[0]!.description='Keep the current adapter as the single backing store behind AuthCache.';
q.options[1]!.label='B) Keep the plan';q.options[1]!.description='Retain 4 classes across 12 files, with two invalidation paths.';c.answers={[q.question]:q.options[0]!.label};
expect(inventorySeed(c)).toBeDefined();
});
+39
View File
@@ -73,3 +73,42 @@ test.each(controls)('%s cannot provide mandatory regression coverage', (_, chang
test('the regression evidence test selects its existing Eng workflow', () => {
expect(Object.entries(E2E_TOUCHFILES).filter(([, paths]) => paths.includes('test/eng-scheduled-regression.test.ts')).map(([name]) => name)).toEqual(['plan-eng-finding-count']);
});
// Verbatim owned rule, T1 and ordered verification from the failed AZ report.
const orderedRuleReport = "# Current reviewed plan\n\n### REGRESSION RULE — CRITICAL, no approval needed (skill iron rule)\n\nPLAN.md:27-28 rewrites `legacyAuthFlow()` with no regression test;\nPLAN.md:14-16 excluded it from coverage. That is modified existing behavior\nwith no covering test. **Before** the rewrite, add\n`legacyAuthFlow.characterization.test.ts` capturing current outputs for:\nvalid token, expired token, wrong tenant, wrong audience, revoked token,\nIDP unavailable. The rewrite must pass the same suite unchanged.\n\n## Implementation Tasks\n- [ ] **T1 (P1, human: ~half day / CC: ~15min)** — legacyAuthFlow — Write characterization suite for 6 prior behaviors BEFORE rewrite\n - Surfaced by: Test review — REGRESSION RULE, PLAN.md:27-28 and 14-16\n - Files: `legacyAuthFlow.characterization.test.ts`\n - Verify: suite green on current code; green again after rewrite\n\n## Verification (end to end)\n1. Run T1's characterization suite on unmodified code: green.\n2. Implement T2-T6; run unit suites: green, no shared-state ordering flakes (run with shuffled order).\n3. Run T7 E2E: A/B isolation, suspend-mid-mint denial, double-submit consistency all green.\n4. Re-run T1 after the rewrite: green, unchanged.\n";
test('an iron-rule declaration and ordered task verification establish the mandatory baseline',()=>{
expect(regression(orderedRuleReport)).toBe('plan');
for(const change of [(s:string)=>s.replaceAll('T1','T17'),(s:string)=>s.replaceAll('legacyAuthFlow.characterization.test.ts','spec/legacy-golden.test.ts'),(s:string)=>s.replace(/[`*]/g,''),
(s:string)=>s+'\n## History\nT1 is withdrawn.\n',(s:string)=>s+'\n## Current assessment\n"T1 is withdrawn."\n',(s:string)=>s+'\n## Payment regression suite\nThe regression suite is withdrawn.\n'])expect(regression(change(orderedRuleReport))).toBe('plan');
});
test('the ordered baseline stays owned, required, and unchanged across the rewrite',()=>{
for(const [before,after] of [
['no approval needed','optional if approved'],['skill iron rule','hypothetical example'],['legacyAuthFlow','otherAuthFlow'],
['**Before** the rewrite','After the rewrite'],['capturing current outputs','capturing expected outputs'],
['The rewrite must pass the same suite unchanged.','The rewrite may update the expectations.'],
['suite green on current code; green again after rewrite','suite green on changed code; green again after rewrite'],
['suite green on current code','suite is not green on current code'],['green again after rewrite','not green again after rewrite'],
['on unmodified code: green.','on unmodified code: not green.'],['after the rewrite: green, unchanged.','after the rewrite: failing, unchanged.'],
['1. Run T1','1. Run T9'],['on unmodified code: green','on changed code: green'],['1. Run ','1. If approved, Run '],
['4. Re-run T1','4. Re-run T9'],['green, unchanged.','green, with updated expectations.'],
['## Implementation Tasks\n','## Implementation Tasks\nSource:\n'],['## Implementation Tasks\n','## Implementation Tasks\nOnce approved,\n'],
['PLAN.md:27-28','Source:\nPLAN.md:27-28'],['Current reviewed plan','Historical reviewed plan'],
]){const changed=orderedRuleReport.replaceAll(before!,after!);expect(changed).not.toBe(orderedRuleReport);expect(regression(changed)).toBeUndefined();}
for(const change of [(s:string)=>s.replace(/^ - Files: .*$/m,' - Files: different.test.ts'),(s:string)=>s.replace(/^1\. .*$/m,''),
(s:string)=>s.replace(/^1\. .*$/m,'1. Rewrite legacyAuthFlow() before recording T1.'),(s:string)=>s.replace(/^(1\. )(.*)$/m,'$1"$2"'),
(s:string)=>s.replace(/^4\. .*$/m,''),(s:string)=>s.split('\n').map(l=>'> '+l).join('\n'),(s:string)=>'```\n'+s+'\n```',
(s:string)=>s+'\n## Current assessment\nT1 is withdrawn.\n',(s:string)=>s+'\n## Current assessment\nT1 verification is "optional".\n',
(s:string)=>s+'\n## Current assessment\nDo not run T1.\n',(s:string)=>s+'\n## Current assessment\nlegacyAuthFlow() is rewritten before T1.\n',
(s:string)=>s+'\n## Current assessment\nUpdate T1 assertions.\n']){const changed=change(orderedRuleReport);expect(changed).not.toBe(orderedRuleReport);expect(regression(changed)).toBeUndefined();}
});
test('required regression relations survive heading, task and verification paraphrases',()=>{
const changed=orderedRuleReport.replace('REGRESSION RULE — CRITICAL, no approval needed (skill iron rule)','Required characterization baseline')
.replace('The rewrite must pass the same suite unchanged.','The same suite must remain green unchanged after the rewrite.')
.replace('Write characterization suite for 6 prior behaviors BEFORE rewrite','Add characterization tests for existing outputs')
.replace('suite green on current code; green again after rewrite','current implementation passes; after the rewrite the suite passes again')
.replace("1. Run T1's characterization suite on unmodified code: green.",'1) Execute characterization task T1 against untouched code; it must pass.')
.replace('4. Re-run T1 after the rewrite: green, unchanged.','4) Execute the same T1 tests unchanged after the refactor; they must pass.');
expect(regression(changed)).toBe('plan');
});
+46 -27
View File
@@ -2,7 +2,7 @@
import * as fs from 'node:fs';
import * as path from 'node:path';
import { createHash } from 'node:crypto';
import { validAutoplanEditDigest, readAutoplanDigestFile, matchesAutoplanDigestRows, createAutoplanEditDigest } from './autoplan-artifact-digest';
import { validAutoplanEditDigest, readAutoplanDigestFile, matchesAutoplanDigestRows, createAutoplanEditDigest, autoplanEditLineHash } from './autoplan-artifact-digest';
import type { PendingAutoplanArtifact } from './autoplan-artifact-recorder';
import type { NativePublicToolEvent } from './plan-count-transcript';
@@ -49,21 +49,24 @@ function ownedEditDiffRows(rows: string[], file: string, ownedStateRoot?: string
// A completed native tool's diff may remain above the active edit panel.
// Only its indented diff output is ignored; competing panels or prose are
// not evidence for the current request and cannot be used as a prefix.
const prefix = rows.slice(0, header), fullRow = /^ {6}([1-9]\d*) ([+ -])/;
const first = prefix.map(row => fullRow.exec(row)).find(Boolean);
if (!first) return null;
const markerColumn = 6 + first[1]!.length + 1;
let kind: string | undefined;
let numbered = 0;
for (const row of rows.slice(0, header)) {
for (const row of prefix) {
if (!row.trim()) continue;
const full = /^ {6}(\d+) ([+ -])/.exec(row);
const full = fullRow.exec(row);
if (full) {
if (!Number.isSafeInteger(Number(full[1])) || Number(full[1]) < 1) return null;
numbered++; kind = full[2];
if (!Number.isSafeInteger(Number(full[1])) || 6 + full[1]!.length + 1 !== markerColumn) return null;
kind = full[2];
} else {
const wrap = /^ {9}([+ -])/.exec(row);
if (!wrap || (kind !== undefined && wrap[1] !== kind)) return null;
kind = wrap[1];
const wrappedKind = row[markerColumn];
if (!row.startsWith(' '.repeat(markerColumn)) || !['+', ' ', '-'].includes(wrappedKind ?? '') ||
(kind !== undefined && wrappedKind !== kind)) return null;
kind = wrappedKind;
}
}
if (!numbered) return null;
rows = rows.slice(header);
}
// A redraw can repeat the same native tool title above one current panel.
@@ -146,17 +149,21 @@ function matchesCroppedEdit(viewport: string, file: string, before: string, remo
const diffRows = ownedEditDiffRows(rows,file,ownedStateRoot);
if (!diffRows) return false;
const chunks: Array<{ kind: string; text: string }> = [];
let markerColumn: number | undefined;
for (const row of diffRows) {
const numbered = /^ {0,3}(\d+) ([+ -])(.*)$/.exec(row);
const numbered = /^( {0,3})([1-9]\d*) ([+ -])(.*)$/.exec(row);
if (numbered) {
const line = Number(numbered[1]);
if (!Number.isSafeInteger(line) || line < 1) return false;
chunks.push({ kind: numbered[2]!, text: numbered[3]! });
const line = Number(numbered[2]), column = numbered[1]!.length + numbered[2]!.length + 1;
// Match the digest parser: every row owns one marker column, regardless
// of line-number width; continuations retain that column and diff kind.
if (!Number.isSafeInteger(line) || (markerColumn !== undefined && markerColumn !== column)) return false;
markerColumn = column;
chunks.push({ kind: numbered[3]!, text: numbered[4]! });
} else {
const wrapped = /^ {4}([+ -])(.*)$/.exec(row);
const last = chunks.at(-1);
if (!wrapped || !last || wrapped[1] !== last.kind) return false;
last.text += wrapped[2]!;
if (markerColumn === undefined || !row.startsWith(' '.repeat(markerColumn)) ||
!last || row[markerColumn] !== last.kind) return false;
last.text += row.slice(markerColumn + 1);
}
}
// The crop itself cannot contain an example introduction, quote, unrelated
@@ -455,25 +462,28 @@ export function pendingAutoplanArtifactPermissionInput(viewport: string,
if (!before || createHash('sha256').update(before).digest('hex') !== p.editDigest.beforeSHA256) return null;
if (matchesAutoplanDigestRows(diffRows,before,p.editDigest,currentViewport !== viewport)) return {input:'1\r', signature, file:p.file};
if (currentViewport !== viewport) return null; // The new prefix path requires the exact digest, including additions.
// Digest authority adds insertion-only crops; existing anchored deletion
// authority remains available after the current-file binding succeeds.
// Legacy deletion crops below must still honor the recorded request digest.
}
const originals = fs.readFileSync(p.file, 'utf8').split('\n').map(compact);
const chunks: Array<{kind:string; text:string; partial?:boolean}> = [];
const fullRow = /^( {0,3})([1-9]\d*) ([+ -])(.*)$/;
const first = diffRows.map(row => fullRow.exec(row)).find(Boolean);
if (!first) return null;
const markerColumn = first[1]!.length + first[2]!.length + 1;
let numbered = 0;
for (const row of diffRows) {
const full = /^ {0,3}(\d+) ([+ -])(.*)$/.exec(row);
const full = fullRow.exec(row);
if (full) {
if (!Number.isSafeInteger(Number(full[1])) || Number(full[1]) < 1) return null;
numbered++; chunks.push({kind:full[2]!, text:full[3]!});
if (!Number.isSafeInteger(Number(full[2])) || full[1]!.length + full[2]!.length + 1 !== markerColumn) return null;
numbered++; chunks.push({kind:full[3]!, text:full[4]!});
} else {
const wrap = /^ {4}([+ -])(.*)$/.exec(row);
if (!wrap) return null;
if (!chunks.length) chunks.push({kind:wrap[1]!, text:wrap[2]!, partial:true});
const kind = row[markerColumn], text = row.slice(markerColumn + 1);
if (!row.startsWith(' '.repeat(markerColumn)) || !['+', ' ', '-'].includes(kind ?? '')) return null;
if (!chunks.length) chunks.push({kind:kind!, text, partial:true});
else {
const previous = chunks.at(-1)!;
if (previous.kind !== wrap[1]) return null;
previous.text += wrap[2]!;
if (previous.kind !== kind) return null;
previous.text += text;
}
}
}
@@ -482,6 +492,15 @@ export function pendingAutoplanArtifactPermissionInput(viewport: string,
if (numbered < 2 || !chunks.some(c => c.kind === '-' && compact(c.text)) ||
chunks.some(c => c.kind !== '+' && !originals.some(line => c.partial
? line.endsWith(compact(c.text)) : line === compact(c.text)))) return null;
const digest = p.editDigest;
if (digest && chunks.some(chunk => {
const hash = autoplanEditLineHash(chunk.text);
if (chunk.kind === '+') return chunk.partial || !digest.newLineHashes.includes(hash);
if (chunk.kind !== '-') return false; // Current-file context was checked above.
return chunk.partial
? !originals.some(line => line.endsWith(compact(chunk.text)) && digest.oldLineHashes.includes(autoplanEditLineHash(line)))
: !digest.oldLineHashes.includes(hash);
})) return null;
return {input:'1\r', signature, file:p.file};
} catch { return null; }
}
+54 -1
View File
@@ -1807,8 +1807,25 @@ export function hasNativePlanCompletion(
return hasCompletePlanReport(expectedPlanPath, Math.max(startedAt, ...answerTimes), finishedAt);
}
// Shared only by the opt-in Design report binding and its native completion
// route. A quoted example is inert; an owned quoted status remains evidence.
const DESIGN_CLOSURE_PROVISIONAL = /\b(?:example|sample|template|historical|previous|earlier|quoted|hypothetical|if|unless|until|once|assuming|provided|pending|would|will|could|might|may)\b/i;
function designClosureText(text: string, expectedPlanPath: string): string {
const state = /^(?:(?:still|now) )?(?:pending|failed|incomplete|unfinished|unresolved|open|withdrawn|superseded|cancelled|canceled|historical|not complete|not passed|not current)$/i;
return text.replace(/\*\*/g, '')
.replace(/`([^`\n]+)`/g, (_, value: string) =>
value === expectedPlanPath || state.test(value) ? value : '')
.replace(/"[^"\n]*"|“[^”\n]*”|(?<!\w)'[^'\n]*'(?!\w)|‘[^’\n]*’/g, value =>
state.test(value.slice(1, -1)) ? value.slice(1, -1) : '');
}
function conflictingDesignClosure(text: string): boolean {
const owner = '(?:(?:the )?Design review(?: of PLAN\\.md)?|DESIGN CLEARED|(?:the )?(?:Design review )?exit gate|(?:the |this )?(?:review|report|gate|verdict|reviewed plan)|(?:(?:this|the|one|a|[1-9]\\d*) )?(?:design )?(?:decision|issue|finding)s?)';
return new RegExp(`(?:^|[.!?;]\\s+|\\n)(?:Correction:\\s*)?${owner} (?:(?:is|are|remains?|was|were|has been|have been) (?:(?:still|now) )?(?:not (?:the )?(?:complete|passed|current)|incomplete|unfinished|pending|failed|unresolved|open|withdrawn|superseded|cancelled|canceled|historical)|failed|did not pass|has not passed)\\b|${owner} (?:requires approval|applies only if approved)\\b|${owner}[^.!?\\n]*\\b(?:only if|conditional on|subject to)\\b|${owner} (?:belongs to|applies only to) (?:an? )?(?:another|different) (?:plan|project|review)\\b`, 'i').test(text) ||
new RegExp(`(?:^|[.!?;]\\s+|\\n)(?:If|When|Once|Unless|Assuming|Provided)\\b[^.!?\\n]*\\b${owner}\\b`, 'i').test(text);
}
function hasCompletePlanReport(expectedPlanPath: string, minimumMtime: number, maximumMtime: number,
allowRunHeaderForFailure = false): boolean {
allowRunHeaderForFailure = false, requiredReview?: 'Design'): boolean {
if (!path.isAbsolute(expectedPlanPath)) return false;
try {
const stat = fs.lstatSync(expectedPlanPath);
@@ -1853,6 +1870,20 @@ function hasCompletePlanReport(expectedPlanPath: string, minimumMtime: number, m
const trailing = rows.slice(decisions + 1).filter(line => line.trim());
const closed = rows.filter(line => line.trim()).at(-1) === 'NO UNRESOLVED DECISIONS' ||
(decisions >= 0 && trailing.length > 0 && trailing.every(line => /^[-*] \S|^\+ \d+ unresolved from prior reviews$/.test(line)));
if (requiredReview === 'Design') {
const cells = (row: string) => row.split('|').slice(1, -1).map(cell => cell.trim());
const header = cells(rows[table] ?? '');
const design = rows.slice(table + 2).filter(row => row.startsWith('|'))
.map(cells).filter(row => row[header.indexOf('Review')] === 'Design Review');
const verdicts = rows.filter(row => /^(?:[-*] )?(?:\*\*)?VERDICT:/i.test(row));
const verdict = designClosureText(verdicts[0] ?? '', expectedPlanPath).replace(/^(?:[-*] )?VERDICT:\s*/i, '');
if (design.length !== 1 || !/^(?:clean|clear(?: \(full\))?|complete[d]?)$/i.test(design[0]![header.indexOf('Status')] ?? '') ||
verdicts.length !== 1 || !/^DESIGN CLEARED\b/i.test(verdict) ||
/\?/.test(verdict) || /^DESIGN CLEARED\s+(?:is|was|were|has been|had been|not|never|no longer)\b/i.test(verdict) ||
DESIGN_CLOSURE_PROVISIONAL.test(verdict) || conflictingDesignClosure(verdict) ||
conflictingDesignClosure(designClosureText(report, expectedPlanPath)) ||
rows.filter(row => row.trim()).at(-1) !== 'NO UNRESOLVED DECISIONS') return false;
}
return completeTable && /^(?:[-*] )?(?:\*\*)?VERDICT:(?:\*\*)?[ \t]*[A-Za-z]/m.test(report) && closed;
} catch { return false; }
}
@@ -1946,6 +1977,28 @@ export function hasNativePlanTerminal(
if (!fence && !/^(?: {4}|\t| {0,3}>)/.test(line)) lines.push(line);
}
if (fence) return false;
// A Design review may finish at the user's manual handoff without invoking
// ExitPlanMode. Bind its affirmative review and passed gate to this report;
// the common native ownership, answered-call and fresh-file checks above
// still apply. Other skills retain their existing completion routes below.
const designText = designClosureText(lines.join('\n'), expectedPlanPath);
const designParagraphs = designText.split(/\n\s*\n/).map(p => p.trim());
const designComplete = /(?:^|[.!]\s+)(?:The )?Design review(?: of PLAN\.md)? (?:is complete|has been completed)[.!](?:\s|$)/i;
const designGate = /(?:^|[.!]\s+)(?:The )?(?:Design review )?exit gate (?:has )?passed[.:!](?:\s|$)/i;
const escapedPlanPath = expectedPlanPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const designReport = new RegExp(`(?:^|[.!]\\s+)(?:The )?(?:reviewed plan|review report) (?:is at|was written to|is saved at) ${escapedPlanPath}[.!](?:\\s|$)`, 'i');
const designCompleted = designParagraphs.filter(p => designComplete.test(p) && designReport.test(p));
const designPassed = designParagraphs.filter(p => designGate.test(p));
const sourcedDesign = (paragraph: string) => DESIGN_CLOSURE_PROVISIONAL.test(paragraph) ||
/\b(?:example|sample|template|historical|previous|earlier|quote|source|emit|print)\b.*[::]\s*$/i
.test(designParagraphs[designParagraphs.indexOf(paragraph) - 1] ?? '');
if (designCompleted.length === 1 && designPassed.length === 1 &&
(designText.match(/\b(?:reviewed plan|review report) (?:is at|was written to|is saved at)\b/gi)?.length ?? 0) === 1 &&
![...designCompleted, ...designPassed].some(sourcedDesign) &&
!conflictingDesignClosure(designText) &&
Date.parse(final.timestamp) <= Date.now() &&
hasCompletePlanReport(expectedPlanPath, Math.max(startedAt, ...modifyingAnswers),
Date.parse(final.timestamp), false, 'Design')) return true;
const summary = lines.findIndex(line => /^(?:#{1,6}\s*)?(?:\*\*)?Completion\s+summary(?:\*\*)?\s*:?[ \t]*$/i.test(line.trim()));
const preceding = lines.slice(0, summary).filter(line => line.trim()).at(-1) ?? '';
if (summary < 0 || /\b(?:example|sample|template|quote|emit|print)\b.*[::]\s*$/i.test(preceding)) return false;
+39 -13
View File
@@ -68,7 +68,13 @@ function ordinaryDesignIssue(fp: AskUserQuestionFingerprint): boolean {
const headerActionIssue = /^(?:D[1-9]\d*\s*[—–:-]\s*)?Issue ([1-9]\d*)(?: \(F[1-9]\d*\))?: (How should the header action group establish the primary action)\?$/i.exec(title);
const signaledPrimaryIssue = /^(?:D[1-9]\d*\s*[—–:-]\s*)?Issue ([1-9]\d*) \(G[1-9]\d*\): (How should the header action group signal that [A-Za-z][A-Za-z0-9 _-]{0,39} is the primary action)\?$/i.exec(title);
const distinguishedPrimaryIssue = /^D[1-9]\d*\s*[—–:-]\s*Issue ([1-9]\d*): How should ([A-Za-z][A-Za-z0-9 _-]{0,39}) (?:be distinguished|stand out) from ([A-Za-z][A-Za-z0-9 ,_-]{0,119}?)(?: in the header)?\?$/i.exec(title);
const issue = /^(?:D[1-9]\d*\s*[—–:-]\s*)?Issue ([1-9]\d*)(?: \((?:(?:G[1-9]\d*|Pass [1-7]), )?(?:Visual Hierarchy|Spacing|Color|Typography|Motion)\))?: ([^?]+)\?$/i.exec(title) ?? headerActionIssue ?? signaledPrimaryIssue;
const questionIssue = /^(?:D[1-9]\d*\s*[—–:-]\s*)?Issue ([1-9]\d*)(?: \((?:(?:G[1-9]\d*|Pass [1-7]), )?(?:Visual Hierarchy|Spacing|Color|Typography|Motion)\))?: ([^?]+)\?$/i.exec(title) ?? headerActionIssue ?? signaledPrimaryIssue;
// A declaration can own the same primary-action decision. Its body and
// native choices below must prove the gap, complete styling and deferral.
const declaredPrimaryIssue = (!questionIssue || /\nELI10: (?:two|three|four|five|six|seven|eight|nine|ten|[1-9]\d*) header buttons currently /i.test(q.question)) &&
/^(?:D[1-9]\d*\s*[—–:-]\s*)?Issue ([1-9]\d*)(?: \(G[1-9]\d*\))?: ([^?\n]+)\??$/i.exec(title);
const issue = questionIssue || declaredPrimaryIssue;
const declaredGap = declaredPrimaryIssue && /\(G([1-9]\d*)\)/.exec(title)?.[1];
const descriptivePrimaryHeader = (signaledPrimaryIssue && /^(?!(?:focus|scope|setup|routing|learnings|outside voices|next steps?)$)[A-Za-z][A-Za-z _-]{0,39}$/i.test(q.header.trim())) ||
(distinguishedPrimaryIssue && /^(?:Visual )?Hierarchy$/i.test(q.header.trim()));
if (!issue || !(new RegExp(`^Issue ${issue[1]}(?:: [A-Za-z][A-Za-z0-9 _-]{0,39})?$`, 'i').test(q.header.trim()) || descriptivePrimaryHeader) ||
@@ -81,7 +87,7 @@ function ordinaryDesignIssue(fp: AskUserQuestionFingerprint): boolean {
if (!distinguishedPrimaryIssue && !/\b(?:buttons?|primary(?: header)? actions?|primary emphasis|hierarchy|spacing|contrast|colou?rs?|labels?|typography|fonts?|loading|spinner|skeleton|motion)\b/i.test(issue[2]!)) return false;
const opposed = q.options.filter(o => /^(?:[1-9]\d*[A-Z](?:[).:]\s*|\s+))?(?:Defer|Decline|Leave|Keep|Accept the gap)\b/i.test(o.label) ||
(distinguishedPrimaryIssue && /^(?:Keep|Leave)\b/i.test(o.description?.trim() ?? '')));
const repair = !headerActionIssue && !distinguishedPrimaryIssue && /\b(?:fix|resolve|address)\b/i.test(title) &&
const repair = !headerActionIssue && !distinguishedPrimaryIssue && !declaredPrimaryIssue && /\b(?:fix|resolve|address)\b/i.test(title) &&
q.options.some(o => /\b(?:closing|closes|fixes|resolves?|applies?)\b/i.test(o.description ?? ''));
// A source citation alone can describe a report or the next reviewer.
// Bind the alternate wording to a named control's concrete style amendment
@@ -92,7 +98,8 @@ function ordinaryDesignIssue(fp: AskUserQuestionFingerprint): boolean {
/^How should (?:the )?(?:header )?actions establish that ([A-Za-z][A-Za-z0-9 _-]{0,39}) is the primary action$/i.exec(issue[2]!) ??
(distinguishedPrimaryIssue && /^How should ([A-Za-z][A-Za-z0-9 _-]{0,39}) (?:be distinguished|stand out) from /i.exec(issue[2]!)) ??
(headerActionIssue && new RegExp(`^Issue ${issue[1]}: ([A-Za-z][A-Za-z0-9 _-]{0,39})$`, 'i').exec(q.header.trim()));
const primaryEmphasisIssue = !!signaledPrimaryIssue || !!distinguishedPrimaryIssue || /^Give [A-Za-z][A-Za-z0-9 _-]{0,39} primary emphasis in the header action group$/i.test(issue[2]!);
if (declaredPrimaryIssue && (!primary || q.options.length > 4 || Object.keys(call.answers ?? {}).length !== 1)) return false;
const primaryEmphasisIssue = !!signaledPrimaryIssue || !!distinguishedPrimaryIssue || !!declaredPrimaryIssue || /^Give [A-Za-z][A-Za-z0-9 _-]{0,39} primary emphasis in the header action group$/i.test(issue[2]!);
const scopedPrimaryStatus = !!headerActionIssue || primaryEmphasisIssue;
const explicitStyle = primary && `${primary[1]} filled (?:primary )?#[0-9a-f]{6}(?:/| with )(?:white|black)(?: text)?; ` +
'[A-Za-z][A-Za-z0-9 ,/_-]{0,99} neutral ghost(?: buttons)?\\.';
@@ -112,8 +119,8 @@ function ordinaryDesignIssue(fp: AskUserQuestionFingerprint): boolean {
];
const primaryHeader = !q.header.includes(':') || q.header.split(':')[1]!.trim().toLowerCase() === primary?.[1]?.toLowerCase();
const ownedStatus = (value: string, index: number, source: string) =>
/^(?:withdrawn|superseded|resolved|closed|hypothetical|rejected|cancelled|canceled|not current|no longer current)$/i.test(value) &&
/(?:^|[.!?;]\s+|\n)(?:Correction:\s*)?(?:(?:This (?:issue|finding|question|amendment|deferral|style|fix|remedy|choice|option|(?:DESIGN\.md |token )?(?:requirement|contract))|Issue [1-9]\d*) (?:is|was|has been)|(?:these|the|this) (?:tokens?|styles?|primary treatment) (?:are|is|were|was|have been|has been)) $/i.test(source.slice(0, index));
/^(?:withdrawn|superseded|resolved|closed|historical|hypothetical|rejected|cancelled|canceled|not current|no longer current)$/i.test(value) &&
/(?:^|[.!?;]\s+|\n)(?:Correction:\s*)?(?:(?:This (?:issue|finding|question|amendment|deferral|style|fix|remedy|choice|option|(?:DESIGN\.md |token )?(?:requirement|contract))|(?:Issue |G)[1-9]\d*) (?:is|was|has been)|(?:these|the|this) (?:tokens?|styles?|primary treatment) (?:are|is|were|was|have been|has been)) $/i.test(source.slice(0, index));
// The style wordings share one owned decision: a current equal-weight gap,
// a named control's DESIGN.md amendment, and a different choice retaining it.
// A following status assertion remains current after a parenthesized effort
@@ -136,15 +143,16 @@ function ordinaryDesignIssue(fp: AskUserQuestionFingerprint): boolean {
const assessments = [...questionText.matchAll(/^ELI10: (.+)$/gm)];
const prefix = questionText.slice(0, assessments[0]?.index ?? 0)
.split('\n').filter(line => line.trim()).slice(1);
const sourceAssessment = /\b(?:historical|hypothetical|quoted|source|earlier review)\s+(?:example|excerpt|assessment|material|text)\b|\bnot\s+(?:the\s+)?current\s+(?:UI|assessment|finding|amendment|deferral|remedy|choice|option)\b/i;
const sourceAssessment = /\b(?:historical|hypothetical|quoted|source|earlier review)\s+(?:example|excerpt|assessment|material|text)\b|\bnot\s+(?:the\s+)?current\s+(?:UI|assessment|finding|amendment|deferral|remedy|choice|option)\b|\bthis (?:finding|amendment|deferral|remedy|choice|option) (?:applies only to|belongs to) (?:an? )?(?:another|different) (?:project|plan|review)\b/i;
const assessment = assessments.length === 1 &&
prefix.every(line => /^(?:Project\/branch\/task:|\[P[0-3]\])/.test(line)) &&
!/^(?:Project\/branch\/task:|\[P[0-3]\])\s*(?:If|When|Unless|Provided|Assuming)\b/im.test(prefix.join('\n')) &&
!sourceAssessment.test(prefix.join(' ')) && !sourceAssessment.test(assessments[0]![1]!)
? assessments[0]![1]! : '';
const headerPeers = primary && headerActionIssue && new RegExp(`^${primary[1]}, ([A-Za-z][A-Za-z0-9 _-]{0,39}(?:, [A-Za-z][A-Za-z0-9 _-]{0,39})*(?:,? and [A-Za-z][A-Za-z0-9 _-]{0,39})?) currently look (?:the same|identical)\\.`, 'i').exec(assessment);
const countedHeader = distinguishedPrimaryIssue && /^(?:Right now|Today) (?:all|the) (two|three|four|five|six|seven|eight|nine|ten|[1-9]\d*) header buttons look (?:the same|identical)\./i.exec(assessment);
const primaryAssessment = distinguishedPrimaryIssue ? countedHeader?.[0] : headerActionIssue ? headerPeers?.[0] :
const countedHeader = distinguishedPrimaryIssue && /^(?:Right now|Today) (?:all|the) (two|three|four|five|six|seven|eight|nine|ten|[1-9]\d*) header buttons look (?:the same|identical)\./i.exec(assessment) ||
declaredPrimaryIssue && /^(two|three|four|five|six|seven|eight|nine|ten|[1-9]\d*) header buttons currently (?:share one style|look identical)\./i.exec(assessment);
const primaryAssessment = distinguishedPrimaryIssue || declaredPrimaryIssue ? countedHeader?.[0] : headerActionIssue ? headerPeers?.[0] :
primary && new RegExp(`^(?:Right now|Today) ${primary[1]}(?:, [A-Za-z][A-Za-z0-9 _-]{0,39})+(?:,? and [A-Za-z][A-Za-z0-9 _-]{0,39})? (?:(?:all )?look (?:the same|identical)|are (?:all )?(?:(?:two|three|four|five|six|seven|eight|nine|ten|[1-9]\\d*) )?identical buttons)\\b`, 'i').exec(assessment)?.[0];
const premiseSentence = assessment.split(/[.!?](?:\s|$)/)[0] ?? '';
const currentPrimary = !!primaryAssessment && !/\b(?:not|never|no longer)\b/i.test(primaryAssessment) &&
@@ -169,8 +177,10 @@ function ordinaryDesignIssue(fp: AskUserQuestionFingerprint): boolean {
const conditionalHeader = (text: string) => /(?:^|[.!?;]\s+|\n)(?:[✅❌]\s*)?(?:Correction:\s*)?(?:If|When|Unless|Assuming|Provided)\b/i.test(text);
// Approval conditions suspend this offered decision; explanatory conditions
// about user behavior do not make an otherwise current amendment optional.
const pendingPrimaryApproval = (text: string) => primaryEmphasisIssue && /(?:^|[.!?;]\s+|\n)(?:[✅❌]\s*)?(?:Correction:\s*)?(?:If|When|Once|Provided|Assuming|Pending)\s+(?:approval|approved|acceptance|accepted|(?:we|you)\s+(?:approve|accept))\b/i.test(text);
const currentHeaderContract = distinguishedPrimaryIssue ? (countedHeader && headerControls.length > 0 &&
const pendingPrimaryApproval = (text: string) => primaryEmphasisIssue && (
/(?:^|[.!?;]\s+|\n)(?:[✅❌]\s*)?(?:Correction:\s*)?(?:If|When|Once|Provided|Assuming|Pending)\s+(?:approval|approved|acceptance|accepted|(?:we|you)\s+(?:approve|accept))\b/i.test(text) ||
(declaredPrimaryIssue && new RegExp(`(?:^|[.!?;]\\s+|\\n)(?:Correction:\\s*)?(?:This (?:issue|finding|amendment|deferral|option)|Issue ${issue[1]}${declaredGap ? `|G${declaredGap}` : ''}) (?:requires approval|applies only if approved)\\b`, 'i').test(text)));
const currentHeaderContract = declaredPrimaryIssue ? countedHeader && !conditionalHeader(questionText) : distinguishedPrimaryIssue ? (countedHeader && headerControls.length > 0 &&
new Set(headerControls).size === headerControls.length && !headerControls.includes(primary![1]!.toLowerCase()) &&
numberValue(countedHeader[1]!) === headerControls.length + 1 && !conditionalHeader(questionText)) : !headerActionIssue || (headerContract && headerControls.length > 0 &&
new Set(headerControls).size === headerControls.length && !headerControls.includes(primary![1]!.toLowerCase()) &&
@@ -179,7 +189,7 @@ function ordinaryDesignIssue(fp: AskUserQuestionFingerprint): boolean {
numberValue((variantContract || namedContract)![1]!) === otherControls &&
!/\b(?:proposed|hypothetical|quoted|historical|source)\s+(?:example|contract|requirement)\b/i.test(assessment.slice(0, (variantContract || namedContract)!.index)) &&
!invalidContract.test(questionText);
const withdrawn = new RegExp(`(?:^|${statusBoundary}\\s+|\\n)(?:Correction:\\s*)?(?:(?:This (?:issue|finding|question|amendment|deferral|style|fix|remedy|choice|option)|Issue ${issue[1]}) (?:is|was|has been) (?:withdrawn|superseded|resolved|closed|hypothetical|rejected|cancelled|canceled|not current|no longer current)|We have (?:resolved|closed|withdrawn) this (?:issue|finding)|No current (?:issue|finding|gap|violation) (?:remains|exists))\\b`, 'i');
const withdrawn = new RegExp(`(?:^|${statusBoundary}\\s+|\\n)(?:Correction:\\s*)?(?:(?:This (?:issue|finding|question|amendment|deferral|style|fix|remedy|choice|option)|Issue ${issue[1]}${declaredGap ? `|G${declaredGap}` : ''}) (?:is|was|has been) (?:withdrawn|superseded|resolved|closed|historical|hypothetical|rejected|cancelled|canceled|not current|no longer current)|We have (?:resolved|closed|withdrawn) this (?:issue|finding)|No current (?:issue|finding|gap|violation) (?:remains|exists))\\b`, 'i');
const closedGap = /(?:^|[.!?;]\s+|\n)(?:Correction:\s*)?(?:this|the|that) (?:gap|violation) (?:is|was|has been) (?:already\s+|now\s+)?(?:resolved|fixed|closed)\b/i;
const cancelledStyle = /(?:^|[.!?;]\s+|\n)(?:Correction:\s*)?(?:do not|don't|never|skip|cancel|withdraw)\s+(?:apply|use|add|keep)\s+(?:(?:these|the|this)\s+)?(?:tokens?|styles?|primary treatment)\b/i;
const withdrawnStyles = /(?:^|[.!?;]\s+|\n)(?:Correction:\s*)?(?:these|the|this) (?:tokens?|styles?|primary treatment) (?:are|is|were|was|have been|has been) (?:withdrawn|rejected|cancelled|canceled|not current|no longer current)\b/i;
@@ -194,6 +204,15 @@ function ordinaryDesignIssue(fp: AskUserQuestionFingerprint): boolean {
q.options.some(amendment => {
const body = currentText(amendment.description ?? '');
if (pendingPrimaryApproval(body)) return false;
const declaredStyle = primary && declaredPrimaryIssue &&
new RegExp(`^(?:✅\\s*)?${primary[1]} filled #[0-9a-f]{6}(?: with)? (?:white|black)(?: text)?; ([A-Za-z][A-Za-z0-9 ,/_-]{0,119}) neutral ghost(?: buttons)?\\.`, 'i').exec(body);
if (declaredPrimaryIssue) {
const peers = declaredStyle && controlNames(declaredStyle[1]!.replaceAll('/', ','));
if (!peers || peers.length !== numberValue(countedHeader![1]!) - 1 ||
new Set(peers).size !== peers.length || peers.includes(primary![1]!.toLowerCase()) ||
!new RegExp(`^${issue[1]}[A-Z][).:]?\\s+Apply DESIGN\\.md tokens?(?: \\(recommended\\))?$`, 'i').test(amendment.label) ||
conditionalHeader(body) || invalidContract.test(body)) return false;
}
const headerStyle = primary && headerActionIssue && new RegExp(`^(?:✅\\s*)?${primary[1]} becomes the only filled #[0-9a-f]{6} button with (?:white|black) text; ([A-Za-z][A-Za-z0-9 ,_-]{0,119}) become neutral ghost buttons, exactly as DESIGN\\.md states\\.`, 'i').exec(body);
// A descriptive header still owns a concrete primary and every peer.
// The native label supplies the filled role; its own body supplies tokens.
@@ -201,9 +220,10 @@ function ordinaryDesignIssue(fp: AskUserQuestionFingerprint): boolean {
new RegExp(`^(?:✅\\s*)?${primary[1]} is #[0-9a-f]{6} with (?:white|black) text; ([A-Za-z][A-Za-z0-9 ,_-]{0,119}) are neutral ghost buttons per DESIGN\\.md\\.`, 'i').exec(body) ??
new RegExp(`^(?:✅\\s*)?${primary[1]} becomes the only filled button \\(#[0-9a-f]{6}, (?:white|black) text\\); ([A-Za-z][A-Za-z0-9 ,/_-]{0,119}) use the existing neutral ghost variant` +
'(?: \\(human: ~?[0-9]+(?:\\.[0-9]+)?(?:h|min) / CC: ~?[0-9]+(?:\\.[0-9]+)?(?:h|min)\\))?\\. (?:✅\\s*)?Matches DESIGN\\.md exactly\\b', 'i').exec(body));
const style = distinguishedPrimaryIssue ? distinguishedStyle?.[0] : headerActionIssue ? headerStyle?.[0] : amendments.map(pattern => pattern.exec(body)).find(Boolean)?.[0];
const style = declaredPrimaryIssue ? declaredStyle?.[0] : distinguishedPrimaryIssue ? distinguishedStyle?.[0] : headerActionIssue ? headerStyle?.[0] : amendments.map(pattern => pattern.exec(body)).find(Boolean)?.[0];
if ((declaredPrimaryIssue || distinguishedPrimaryIssue) &&
/(?:^|[.!?;]\s+|\n)(?:Correction:\s*)?(?:the|this) (?:current )?(?:amendment|fix) keeps (?:all )?(?:two|three|four|five|six|seven|eight|nine|ten|[1-9]\d*) (?:header )?buttons identical\b/i.test(body)) return false;
if (distinguishedPrimaryIssue && (!distinguishedStyle || conditionalHeader(body) || invalidContract.test(body) ||
/(?:^|[.!?;]\s+|\n)(?:Correction:\s*)?(?:the|this) (?:current )?(?:amendment|fix) keeps (?:all )?(?:two|three|four|five|six|seven|eight|nine|ten|[1-9]\d*) (?:header )?buttons identical\b/i.test(body) ||
!new RegExp(`^[1-9]\\d*[A-Z][).:]?\\s+Filled primary (?:(?:\\+|and|with) ghosts|${primary![1]})(?: \\(recommended\\))?$`, 'i').test(amendment.label) ||
JSON.stringify(controlNames(distinguishedStyle[1]!.replaceAll('/', ','))) !== JSON.stringify(headerControls))) return false;
if (headerActionIssue && (!headerStyle || conditionalHeader(body) || invalidContract.test(body) ||
@@ -227,6 +247,12 @@ function ordinaryDesignIssue(fp: AskUserQuestionFingerprint): boolean {
return opposed.some(defer => {
const declined = currentText(defer.description ?? '');
if (pendingPrimaryApproval(declined)) return false;
if (declaredPrimaryIssue) return defer !== amendment &&
new RegExp(`^${issue[1]}[A-Z][).:]?\\s+Defer(?: \\(recommended\\))?$`, 'i').test(defer.label) &&
new RegExp(`^Leave ${declaredGap ? `G${declaredGap}` : `Issue ${issue[1]}`} open and record it as unresolved\\.`, 'i').test(declined) &&
!new RegExp(`(?:^|[.!?;]\\s+|\\n)(?:Correction:\\s*)?(?:do not|don't|never|skip|cancel|withdraw) (?:leave|keep|defer) (?:${declaredGap ? `G${declaredGap}|` : ''}Issue ${issue[1]})\\b`, 'i').test(declined) &&
!conditionalHeader(declined) && !sourceAssessment.test(declined) && !withdrawn.test(declined) &&
!closedGap.test(declined) && !invalidContract.test(declined) && !cancelledStyle.test(declined) && !withdrawnStyles.test(declined);
// Native menus can list current benefits before the gap retained by
// declining. Only consume a complete affirmative pro/con prefix; prose
// framing a source example or a future condition cannot expose an icon.
+36 -7
View File
@@ -9,7 +9,14 @@ export type DevexSeededGap = typeof DEVEX_SEEDED_GAPS[number];
function explainedReversedSignatures(q: NativePlanQuestion, title: string): boolean {
const question = /^(?:Journey stage [A-Z ]+: )?the two public functions take the same two arguments in (?:opposite|reversed) positional order\. How should (?:the plan|we) (?:fix|align|unify) the signatures\?$/i.test(title);
const traced = /^Journey stage: REAL USAGE\. Two sibling functions take the same two arguments in (?:opposite|reversed) order\.$/i.test(title);
const declaration = traced || /^Journey stage(?: REAL USAGE:|: REAL USAGE\.) The two public evaluation functions take the same two arguments in (?:opposite|reversed) order\.$/i.test(title);
const declared = traced || /^Journey stage(?: REAL USAGE:|: REAL USAGE\.) The two public evaluation functions take the same two arguments in (?:opposite|reversed) order\.$/i.test(title);
// A dedicated assertion can put its named signatures in its own Evidence
// field. Bind subject, source identities and repair instead of menu wording.
const subject = title.replace(/^Journey stage(?: REAL USAGE:|: REAL USAGE\.)\s*/i, '');
const evidenced = !question && !declared &&
/^(?:the )?(?:two|both) public (?:evaluation )?functions take\b/i.test(subject) &&
/\bthe same two arguments\b/i.test(subject) && /\b(?:opposite|reversed) (?:positional )?order\.?$/i.test(subject);
const declaration = declared || evidenced;
if (!question && !declaration) return false;
const lines = q.question.split('\n');
if (lines[0]!.trim().replace(/^D\s*\d+\s*[—–:-]\s*/i, '') !== title) return false;
@@ -17,7 +24,7 @@ function explainedReversedSignatures(q: NativePlanQuestion, title: string): bool
const context = lines.slice(1, explanation).filter(line => line.trim());
const project = traced ? /^Project\/branch\/task: [^;\n]+; ([\w./-]+):\d+(?:[-–]\d+)?\.$/.exec(context[0] ?? '') : declaration && context.length === 1
? /^Project\/branch\/task: [^;\n]+; ([\w./-]+) lines? \d+(?: to |[-–])\d+\.$/.exec(context[0]!) : null;
if (explanation < 1 || (declaration && !project) || (!traced && context.some(line =>
if (explanation < 1 || (declared && !project) || (!traced && !evidenced && context.some(line =>
(!declaration && !/^Project\/branch\/task: [^;\n]+; reviewing the public function signatures in [\w./-]+\.$/.test(line)) ||
/\b(?:quoted|source excerpt|source example|hypothetical|historical|not (?:a )?current|if approved)\b/i.test(line)))) return false;
// Inline code may name each signature; a quoted/fenced explanation, earlier
@@ -25,25 +32,47 @@ function explainedReversedSignatures(q: NativePlanQuestion, title: string): bool
const declaredSignatures = traced
? /^I traced the first real integration after the demo\. ([\w./-]+) lists the two evaluation functions: (`?)run_eval\(\s*dataset\s*,\s*evaluator\s*\)\2 and (`?)run_batch\(\s*evaluator\s*,\s*dataset\s*\)\3\./.exec(context[1] ?? '')
: declaration && /^ELI10: ([\w./-]+) documents (`?)run_eval\(\s*dataset\s*,\s*evaluator\s*\)\2 and (`?)run_batch\(\s*evaluator\s*,\s*dataset\s*\)\3\. Same two concepts, reversed positional order, and neither function requires keywords\./.exec(lines[explanation]!);
if (declaration ? !declaredSignatures || declaredSignatures[1] !== project?.[1]
: !/^ELI10: [\w./-]+(?: lines? \d+(?:\s*[-–]\s*\d+)?)? define (`?)run_eval\(\s*dataset\s*,\s*evaluator\s*\)\1 and (`?)run_batch\(\s*evaluator\s*,\s*dataset\s*\)\2\./.test(lines[explanation]!)) return false;
if (!evidenced && (declaration ? !declaredSignatures || declaredSignatures[1] !== project?.[1]
: !/^ELI10: [\w./-]+(?: lines? \d+(?:\s*[-–]\s*\d+)?)? define (`?)run_eval\(\s*dataset\s*,\s*evaluator\s*\)\1 and (`?)run_batch\(\s*evaluator\s*,\s*dataset\s*\)\2\./.test(lines[explanation]!))) return false;
const currentProse = (text: string) => {
let fence = false;
return text.split('\n').filter(line => {
if (/^\s*(?:```|~~~)/.test(line)) { fence = !fence; return false; }
return !fence && !/^\s*>/.test(line);
}).join('\n').replace(/`[^`\n]*`|"[^"\n]*"|“[^”\n]*”/g, '');
}).join('\n')
.replace(/(^|[.!?\n]\s*)((?:Correction:\s*)?(?:this|that|the) (?:evidence|trace) (?:is|was|has been) )["“'‘`](withdrawn|rejected|cancelled|canceled|superseded|historical|hypothetical|(?:not|no longer) current)["”'’`]/gi, '$1$2$3')
.replace(/`[^`\n]*`|"[^"\n]*"|“[^”\n]*”/g, '');
};
// The traced declaration owns its named signatures before ELI10, so its
// currentness must include that same source paragraph.
const current = currentProse(lines.slice(traced ? 1 : explanation).join('\n'));
const current = currentProse(lines.slice(traced || evidenced ? 1 : explanation).join('\n'));
if ((current.match(/^ELI10:/gm)?.length ?? 0) !== 1) return false;
if (declaration && /\b(?:if|once|when|unless) (?:approved|accepted)|\b(?:after|pending) approval\b/i.test(current)) return false;
if (declaration && /(?:^|[.!?\n]\s*)(?:Correction:\s*)?(?:these|the) (?:functions|signatures) (?:are (?:now|already)|have been) (?:aligned|consistent)\b/i.test(current)) return false;
if (traced && /(?:^|[.!?\n]\s*)(?:Correction:\s*)?(?:this|that|the) trace (?:is|was|has been) (?:withdrawn|rejected|historical|(?:not|no longer) current)\b/i.test(current)) return false;
if ((traced || evidenced) && /(?:^|[.!?\n]\s*)(?:Correction:\s*)?(?:this|that|the) (?:trace|evidence) (?:is|was|has been) (?:withdrawn|rejected|cancelled|canceled|superseded|historical|hypothetical|(?:not|no longer) current)\b/i.test(current)) return false;
if (/(?:^|[.!?\n]\s*)(?:Correction:\s*)?(?:(?:this|that|the) (?:finding|explanation)|(?:(?:this|that|the) )?argument[- ]order (?:issue|defect)|these signatures)\b[^.\n]*\b(?:withdrawn|rejected|(?:already )?(?:fixed|resolved)|historical|(?:not|no longer) current)\b/i.test(current) ||
/(?:^|[.!?\n]\s*)(?:Correction:\s*)?(?:there is|there's) no argument[- ]order (?:issue|defect)\b/i.test(current) ||
/(?:^|[.!?\n]\s*)(?:Correction:\s*)?run_eval and run_batch now (?:use|take) the same positional order\b/i.test(current)) return false;
if (evidenced) {
// Only an asserted citation at the start of this decision's field owns
// the pair; quoted examples, later borrowed prose and split fields do not.
const fields = lines.slice(1, explanation + 1).filter(line => /^(?:Evidence|ELI10):/.test(line));
const pair = /^(?:Evidence|ELI10):\s*[\w./-]+(?: lines? \d+(?:[-–]\d+)?|:\d+(?:[-–]\d+)?)?:\s*(`?)run_eval\(\s*dataset\s*,\s*evaluator\s*\)\1 and (`?)run_batch\(\s*evaluator\s*,\s*dataset\s*\)\2(?:[.;]|$)/;
if (!fields.some(line => pair.test(line)) || fields.some(line =>
/^(?:Evidence|ELI10):\s*(?:>|`|"|“|Source\b|Quoted\b|Historical\b|Earlier\b|Example\b|Hypothetical\b|If\b|Assuming\b|Provided\b)/i.test(line))) return false;
return q.options.some(option => {
const label = currentProse(option.label.replace(/`(\(\s*dataset\s*,\s*evaluator\s*\))`/g, '$1'));
const remedy = currentProse(option.description ?? '');
const first = remedy.split(/[.!?\n]/)[0] ?? '';
return /^(?:[A-D]\)\s*)?(?:Align|Unify|Standardize)\b/i.test(label) && /\(\s*dataset\s*,\s*evaluator\s*\)/.test(label) &&
/\bsame (?:positional )?order\b/i.test(first) && /\bboth functions\b/i.test(first) &&
/\bkeywords? (?:accepted|supported)\b|\baccept keywords\b/i.test(remedy) &&
/\bswaps? (?:is |are )?(?:detected|caught|rejected)\b/i.test(remedy) && /\b(?:clear|actionable) (?:error|message)\b/i.test(remedy) &&
!/\b(?:if|unless|when|once|after|pending)\b|\b(?:no|not|never|without|do not|don't)\b|\b(?:other|another|foreign|different) (?:functions?|API|pair|project|issue)\b/i.test(`${label}\n${remedy}`) &&
!/(?:^|[.!?\n]\s*)(?:Correction:\s*)?(?:this|that|the) (?:option|action|correction) (?:is|was|has been) (?:historical|withdrawn|rejected|cancelled|canceled|superseded|(?:not|no longer) current)\b/i.test(remedy) &&
!/\brun_(?!eval\b|batch\b)\w+\b/.test(remedy);
});
}
// A declared reversal may offer a keyword-only repair instead of a swap
// guard. It must bind both arguments to both functions in the same option.
if (declaration) return q.options.some(option =>
+57 -5
View File
@@ -41,7 +41,7 @@ function seedSubjects(q: NativePlanQuestionCall['questions'][number]): Seed[] {
if (explainedSeed) {
const ordinal = /^D([1-9]\d*)\s*[—–:-]/.exec(title)?.[1];
const status = '(?:withdrawn|rejected|cancelled|canceled|superseded|resolved|fixed|optional|hypothetical|unproven|not current|no longer current)';
const owner = `(?:(?:this|the|that) (?:finding|issue|gap|defect|assessment|explanation)${ordinal ? `|D${ordinal}` : ''})`;
const owner = `(?:(?:this|the|that) (?:finding|issue|decision|gap|defect|assessment|explanation)${ordinal ? `|D${ordinal}` : ''})`;
const current = (value: string, option = false) => {
const subject = option ? `(?:${owner}|(?:this|the|that) (?:option|action|remedy|correction))` : owner;
const scalar = new RegExp(`((?:^|[.!?;]\\s+|\\n)[\\t ]*(?:Correction:\\s*)?${subject} (?:is|was|has been) )["“'‘\x60](${status})["”'’\x60]`, 'gim');
@@ -162,7 +162,7 @@ function seedSubjects(q: NativePlanQuestionCall['questions'][number]): Seed[] {
function explainedSeedSubjects(q: NativePlanQuestionCall['questions'][number]): Seed[] {
const rawTitle = q.question.split('\n').find(line => line.trim())?.trim() ?? '';
const ordinal = /^D([1-9]\d*)\s*[—–:-]/.exec(rawTitle)?.[1];
const owner = `(?:(?:this|the|that) (?:finding|issue|gap|defect|assessment|explanation|option|action|remedy)${ordinal ? `|D${ordinal}` : ''})`;
const owner = `(?:(?:this|the|that) (?:finding|issue|decision|gap|defect|assessment|explanation|option|action|remedy)${ordinal ? `|D${ordinal}` : ''})`;
const inactive = '(?:withdrawn|retracted|rejected|cancelled|canceled|resolved|fixed|superseded|optional|hypothetical|unproven|not current|no longer current)';
const current = (value: string) => prose(value.replace(/\*\*/g, '').replace(
new RegExp(`(${owner} (?:is|was|has been) )["“'‘\x60](${inactive})["”'’\x60]`, 'gi'), '$1$2'), true)
@@ -187,6 +187,33 @@ function explainedSeedSubjects(q: NativePlanQuestionCall['questions'][number]):
const explanation = explanations[0]!, subject = metadata[0] + ' ' + explanation;
const options = q.options.map(o => current(`${o.label}\n${o.description ?? ''}`)).filter(active);
const ids: Seed[] = [];
// Step 0 may name the complexity decision in its title and put the concrete
// inventory in its own explanation. The reduction and retained backing
// store must belong to one current option, opposed by that same inventory.
if (/^(?:Step 0 )?complexity (?:check|decision):\s*(?:reduce|simplify|trim)\b/i.test(title)) {
const fileCounts = [...explanation.matchAll(/\b(?:touches|changes|modifies|spans) ([1-9]\d*) files\b/gi)];
const inventories = [...explanation.matchAll(/\b(?:adds|introduces) ([1-9]\d*) (?:new )?(?:classes|types) \(([^)]+)\)/gi)];
const names = inventories[0]?.[2]?.split(/,\s*(?:and )?| and /) ?? [];
const conditional = /\b(?:this|the|that) (?:finding|issue|decision|option|action|remedy) (?:(?:applies|proceeds|will proceed) (?:only )?(?:if|once|when)|(?:is|was|has been) conditional on (?:user )?approval)\b|(?:^|\n|[.!?;]\s+)(?:(?:ELI10|Project\/branch\/task):\s*)?(?:if|once|when|assuming|provided) (?:the )?(?:user|owner|reviewer) (?:approves|agrees|accepts)\b/i;
const currentOptions = options.filter(o => !conditional.test(o));
if (fileCounts.length === 1 && inventories.length === 1 && names.length === Number(inventories[0]![1])
&& new Set(names).size === names.length && (Number(fileCounts[0]![1]) >= 8 || names.length >= 2)
&& ['TokenStore', 'SessionMint', 'AuthCache', 'RequestPolicy'].every(name => names.includes(name)) && /\bAuthBroker\b/.test(explanation)
&& /\b(?:existing|current) (?:cache )?adapter (?:already )?keys tokens by tenant\b[^.!?]{0,40}\bevicts?\b[^.!?]{0,40}\binvalidates?\b/i.test(explanation)
&& /\bAuthCache (?:is|remains) (?:just |only )?a facade (?:over (?:it|the (?:existing |current )?(?:cache )?adapter)|for (?:that|the) adapter)\b/i.test(explanation)
&& /\bTokenStore (?:looks like|is|adds) (?:a |another )?(?:second|duplicate) (?:token )?store\b|\bTokenStore duplicates (?:the )?(?:existing |current )?adapter(?:'s)? token storage\b/i.test(explanation)
&& /\bRequestPolicy\b[^.!?]{0,90}\b(?:currently )?(?:has|serves) (?:only )?(?:one|a single) consumer\b/i.test(explanation)
&& !conditional.test(text)
&& !/\bTokenStore (?:now |already )?has (?:a documented )?independent purpose|\bRequestPolicy (?:now |already )?has (?:two|multiple|a second) consumers?\b/i.test(text)
&& currentOptions.some(o => /^(?:[A-D][):.]\s*)?(?:Reduce:\s*)?(?:cut|remove|drop) TokenStore\b/i.test(o)
&& /\b(?:demote|inline|flatten) RequestPolicy\b|\b(?:make|turn) RequestPolicy (?:into )?(?:a )?(?:plain|pure) function\b/i.test(o)
&& /\b(?:one|single|only) (?:(?:token|backing) )?(?:store|storage layer|cache)\b[^.!?\n]{0,80}\b(?:existing|current) (?:cache )?adapter\b|\b(?:existing|current) (?:cache )?adapter (?:as|is|remains|provides) (?:the )?(?:one|single|only) (?:(?:token|backing) )?(?:store|storage layer|cache)\b/i.test(o)
&& /\bAuthCache\b/.test(o))
&& currentOptions.some(o => /^(?:[A-D][):.]\s*)?(?:Proceed as-is|keep|retain)\b/i.test(o)
&& new RegExp(`\\b${inventories[0]![1]} (?:new )?(?:classes|types)\\b`, 'i').test(o)
&& new RegExp(`\\b${fileCounts[0]![1]} files\\b`, 'i').test(o)
&& /\b(?:two|2|separate) invalidation (?:paths|stories)\b/i.test(o))) ids.push('complexity');
}
// The overlapping stores, reduced component count and single backing store
// must be this question's finding and one option's complete repair.
if (/\b(?:scope|components?|pieces|classes|decomposition)\b/i.test(title)
@@ -1034,8 +1061,9 @@ function hasScheduledLegacyRegression(current: ReadonlyArray<{ title: string; bo
match: /^- (?:\[[ xX]\] )?(T[1-9]\d*)(?: \([^\n)]*\))? [—–:-] (.+)(?:\n|$)/.exec(body) }));
});
for (const declaration of current) {
if (!/\b(?:regression|characterization)\b/i.test(declaration.title) || !/\bmandatory\b/i.test(declaration.title)
|| /\b(?:not|never|no longer) mandatory\b/i.test(declaration.title) || approval.test(unquoted(declaration.title)) || /\b(?:if|when|once|unless) accepted\b/i.test(unquoted(declaration.title))) continue;
const requiredRule = /\b(?:mandatory|critical|required)\b/i.test(unquoted(declaration.title));
if (!/\b(?:regression|characterization)\b/i.test(declaration.title) || !requiredRule
|| /\b(?:not|never|no longer) (?:mandatory|critical|required)\b/i.test(declaration.title) || approval.test(unquoted(declaration.title)) || /\b(?:if|when|once|unless) accepted\b/i.test(unquoted(declaration.title))) continue;
const body = declaration.body.join('\n').trim(), text = flat(unquoted(body));
const files = [...text.matchAll(/\b([A-Za-z][\w/.-]*\.test(?:\.[jt]s)?)\b/g)].map(m => m[1]!);
const beforeAndAfter = /\bmust (?:pass|be green) before and after (?:this|the) (?:refactor|rewrite|change)\b/i.test(text);
@@ -1083,7 +1111,31 @@ function hasScheduledLegacyRegression(current: ReadonlyArray<{ title: string; bo
const committedBaseline = pinnedParity && scheduledVerification
&& tasks.filter(t => new RegExp(`^ - Files: ${files[0]!.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm').test(t.body)).length === 1
&& /\b(?:passes?|green) (?:on|against) (?:main|master|(?:the )?(?:unmodified|untouched) (?:code|legacy path)) before (?:any|the) (?:refactor|rewrite|change) commit\b/i.test(verify);
const scopedBaseline = linkedBaseline || committedBaseline;
// The rule, uniquely named file/task and ordered verification together
// establish the unchanged-code baseline without repeating CRITICAL and
// "before" on every line of that same requirement.
const green = (value: string) => /\b(?:pass(?:es)?|green)\b/i.test(value)
&& !/\b(?:not|never|no longer|fail(?:s|ed|ing)?|red)\b/i.test(value);
const orderedBaseline = requiredRule && scheduled && taskFiles[0]![1] === files[0]
&& /\b(?:write|add|create|implement|record|capture|pin)\b/i.test(title)
&& runs.some(run => green(run) && /\b(?:current|unmodified|untouched) (?:code|implementation|legacy path)\b/i.test(run))
&& runs.some(run => green(run) && /\bafter (?:the )?(?:rewrite|refactor|change)\b/i.test(run))
&& /\b(?:captures?|capturing|records?|recording|pins?|pinning) (?:the )?(?:current|prior|existing) (?:outputs|outcomes|behavior)\b/i.test(text)
&& /\bmust (?:pass|satisfy) (?:the )?same (?:suite|tests|assertions) unchanged\b|\b(?:the )?same (?:suite|tests|assertions) must (?:pass|remain green) unchanged (?:after|across) (?:the )?(?:rewrite|refactor|change)\b/i.test(text)
&& tasks.filter(t => new RegExp(`^ - Files: ${files[0]!.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm').test(t.body)).length === 1
&& current.filter(s => /^Verification(?: \([^)]*\))?$/i.test(s.title)).some(s => {
const steps = s.body.join('\n').trim().split(/\n(?=\d+[.)] )/);
const first = unquoted(steps[0] ?? '').trim();
return steps.length >= 2 && owned(steps[0]!) && /^1[.)] (?:run|execute|test|verify)\b/i.test(first)
&& new RegExp(`\\b${id}\\b`).test(first) && /\b(?:unmodified|untouched) (?:code|implementation|legacy path)\b/i.test(first) && green(first)
&& steps.slice(1).some(step => {
const statement = unquoted(step).trim();
return owned(step) && /^(?:[2-9]|[1-9]\\d+)[.)] (?:re-run|rerun|run|execute)\b/i.test(statement)
&& new RegExp(`\\b${id}\\b`).test(statement) && /\bafter (?:the )?(?:rewrite|refactor|change)\b/i.test(statement)
&& green(statement) && /\bunchanged\b/i.test(statement);
});
});
const scopedBaseline = linkedBaseline || committedBaseline || orderedBaseline;
if (!scopedBaseline && !scheduledVerification) continue;
// An explicit ordered step provides the old-code oracle; matching a task label alone cannot.
const baseline = scopedBaseline || current.filter(s => /^Verification(?: \([^)]*\))?$/i.test(s.title)).some(s => {
+5 -2
View File
@@ -1,7 +1,7 @@
import type { NativePublicToolEvent, PlanCountTranscript } from './plan-count-transcript';
function deicticPlanSelection(text: string): RegExpExecArray | null {
return /^(?:I'll|I will) (?:review|(?:run|invoke) (?:the )?\/?([\w:-]+) skill (?:to review|on|against)) (?:this|your|the) (?:draft[ \t]+)?([\p{L}\p{N}]+(?:[ \t\u2010-\u2015-]+[\p{L}\p{N}]+)*[ \t]+)?plan\.$/iu.exec(text);
return /^(?:I'll|I will) (?:review|(?:run|invoke) (?:the )?\/?([\w:-]+(?:[ \t]+[\w:-]+)*) skill (?:to review|on|against)) (?:this|your|the) (?:draft[ \t]+)?([\p{L}\p{N}]+(?:[ \t\u2010-\u2015-]+[\p{L}\p{N}]+)*[ \t]+)?plan\.$/iu.exec(text);
}
function describesTitle(descriptor: string | undefined, title: string): boolean {
@@ -102,7 +102,10 @@ export function nativeSeededPlanSelection(
// occur as contiguous whole words in its title, never merely in its body.
const draft = deicticPlanSelection(text);
const names = [opts.skillName, `gstack:${opts.skillName}`, opts.skillName.replace(/^plan-/, '')];
if (draft && describesTitle(draft[2], title) && (!draft[1] || names.includes(draft[1].toLowerCase())) && remainsSelected(message.timestamp)) return true;
// Human role names remain tied to this successfully loaded skill.
names.push(opts.skillName.replace(/-/g, ' '), opts.skillName.replace(/^plan-/, '').replace(/-/g, ' '));
if (opts.skillName === 'plan-eng-review') names.push('eng-manager plan review');
if (draft && describesTitle(draft[2], title) && (!draft[1] || names.includes(draft[1].toLowerCase().replace(/[ \t]+/g, ' '))) && remainsSelected(message.timestamp)) return true;
const automatic = /^(?:I\'ll|I will) auto[- ]select option B and review\s+(?:the\s+)?(.+?)\s+(?:draft(?:\s+plan)?|plan)\s+(?:you shared|you pasted|pasted here)(.*)$/i.exec(text);
const automaticTarget = automatic?.[1]?.replace(/^(?:"([^"\n]+)"|“([^”\n]+)”|`([^`\n]+)`)$/, (_, straight, curly, code) => straight ?? curly ?? code);
const selectedNow = /^(?:I've|I have) selected (?:option B, )?(?:reviewing|to review)\s+(?:the\s+)?pasted\s+(?:"([^"\n]+)"|“([^”\n]+)”|`([^`\n]+)`)\s+(?:draft(?:\s+plan)?|plan)(.*)$/i.exec(text);
+127
View File
@@ -62,6 +62,133 @@ function fixture() {
return { dir, file, startedAt, transcript, cleanup: () => fs.rmSync(dir, { recursive: true, force: true }) };
}
describe('Design manual completion binds its current exit gate to the fresh report', () => {
const designFixture = () => {
const f = fixture();
fs.writeFileSync(f.file, REPORT.replaceAll('DX', 'Design').replace('Design CLEARED', 'DESIGN CLEARED'));
const time = Date.parse('2026-09-08T17:48:30Z') / 1000; fs.utimesSync(f.file, time, time);
return f;
};
// AZ's final public declaration; detailed score/decision tables are omitted.
const done = (file: string) => 'Exit gate passed: every issue has its own recorded decision, the plan file\'s last heading is the review report, it has the table and VERDICT, and the final line is the unbolded sentinel.\n\n' +
' **STATUS: DONE.** Design review of PLAN.md is complete. The reviewed plan is at `' + file + '`.';
test('recognizes the bound Design declaration independently of terminal layout or ExitPlanMode', () => {
const f = designFixture();
try {
for (const text of [
done(f.file), done(f.file).replace('**STATUS: DONE.** ', '').replace('Design review of PLAN.md is complete', 'The Design review has been completed')
.replace('The reviewed plan is at', 'The review report was written to'),
done(f.file) + '\n\nEng review is pending. Implementation should not start until you leave plan mode.',
done(f.file) + '\n\n"Design review is pending."',
done(f.file) + '\n\n"This report is historical."',
]) {
f.transcript.assistantMessages[0]!.text = text;
expect(hasNativePlanTerminal(f.transcript, f.file, f.startedAt, 'completion_summary'), text).toBe(true);
expect(hasNativePlanTerminal(f.transcript, f.file, f.startedAt, 'plan_ready')).toBe(false);
}
} finally {f.cleanup();}
});
test('rejects unbound, quoted, historical, provisional and conflicting current completion', () => {
const f = designFixture();
try {
const complete = done(f.file);
for (const text of [
complete.replace('Design review of PLAN.md is complete.', 'Everything is done.'),
complete.replaceAll('Design review', 'Eng review'),
complete.replace('PLAN.md', 'OTHER.md'), complete.replace(f.file, f.file + '.other'),
complete.replace('Exit gate passed:', 'Exit gate pending:'), complete.replace('Exit gate passed:', 'Exit gate did not pass:'),
complete.replace('is complete.', 'is not complete.'), complete.replace('is complete.', 'will be complete.'),
complete.replace('is complete.', 'is complete if approved.'),
complete.replace('Exit gate passed:', 'If approved, exit gate passed:'),
complete.replace('**STATUS:', 'Historical example:\n**STATUS:'),
complete.replace('The reviewed plan', '\n\nThe reviewed plan'),
'> ' + complete.replaceAll('\n', '\n> '), '```text\n' + complete + '\n```',
'An example follows:\n\n"' + complete.replaceAll('\n', ' ') + '"',
'Source example:\n\n' + complete,
complete.replace('Design review of PLAN.md is complete.', '"Design review of PLAN.md is complete."'),
complete + '\n\nThe reviewed plan is at `' + f.file + '.other`.',
complete + '\n\nDesign review is pending.',
complete + '\n\nDesign review is "still pending".',
complete + '\n\nDesign review is complete only if approved.',
complete + '\n\nThis review is subject to approval.',
complete + '\n\nThe exit gate failed.',
complete + '\n\nThe Design review is cancelled.',
complete + '\n\nThis report is historical.',
complete.replace('Exit gate passed:', 'This gate belongs to a different plan. Exit gate passed:'),
complete + '\n\nThis gate belongs to a different plan.',
complete + '\n\nThe exit gate has been "failed".',
complete + '\n\nThe exit gate did not pass.',
complete + '\n\nThe exit gate has not passed.',
complete + '\n\nThis report is ‘withdrawn’.',
complete + '\n\nOne design decision remains unresolved.',
complete + '\n\nThis review requires approval.',
complete + '\n\nIf approved, this review is complete.',
complete + '\n\nWaiting for your decision.',
complete + '\n\nPlease confirm?',
]) {
f.transcript.assistantMessages[0]!.text = text;
expect(hasNativePlanTerminal(f.transcript, f.file, f.startedAt, 'completion_summary'), text).toBe(false);
}
} finally {f.cleanup();}
});
test('the Design route preserves native ownership, answer order and artifact readiness', () => {
const f = designFixture();
try {
f.transcript.assistantMessages[0]!.text = done(f.file);
for (const change of [
(t: any) => {t.status = 'missing';}, (t: any) => {t.calls = [];},
(t: any) => {t.calls[0].answered = false;}, (t: any) => {t.calls[0].failed = true;},
(t: any) => {t.calls[0].sessionId = 'foreign';}, (t: any) => {t.calls[0].answeredAt = '2026-09-08T17:50:00Z';},
(t: any) => {t.calls[0].answeredAt = undefined;},
(t: any) => {t.assistantMessages[0].timestamp = '2999-01-01T00:00:00Z';},
(t: any) => {t.planReadyRequests = [{sessionId: CAPTURED_CALL.sessionId, toolUseId: 'exit', timestamp: '2026-09-08T17:49:40Z', failed: true}];},
(t: any) => {t.assistantMessages.push({...t.assistantMessages[0], timestamp: '2026-09-08T17:50:00Z', text: 'Still reviewing.'});},
]) {
const t = structuredClone(f.transcript); change(t);
expect(hasNativePlanTerminal(t, f.file, f.startedAt, 'completion_summary'), change.toString()).toBe(false);
}
for (const timestamp of ['2026-09-08T17:44:00Z', '2026-09-08T17:50:00Z']) {
const time = Date.parse(timestamp) / 1000; fs.utimesSync(f.file, time, time);
expect(hasNativePlanTerminal(f.transcript, f.file, f.startedAt, 'completion_summary')).toBe(false);
}
for (const report of ['# Draft\n', REPORT + '\n## Still editing\n', REPORT,
REPORT.replaceAll('DX', 'Design').replace('Design CLEARED', 'DESIGN CLEARED').replace('clean', 'pending'),
REPORT.replaceAll('DX', 'Design').replace('Design CLEARED', 'DESIGN CLEARED')
.replace('NO UNRESOLVED DECISIONS', 'VERDICT: NOT CLEARED\n\nNO UNRESOLVED DECISIONS'),
]) {
fs.writeFileSync(f.file, report);
const time = Date.parse('2026-09-08T17:48:30Z') / 1000; fs.utimesSync(f.file, time, time);
expect(hasNativePlanTerminal(f.transcript, f.file, f.startedAt, 'completion_summary')).toBe(false);
}
fs.rmSync(f.file);
expect(hasNativePlanTerminal(f.transcript, f.file, f.startedAt, 'completion_summary')).toBe(false);
} finally {f.cleanup();}
});
test('a cleared prefix cannot hide a conditional, historical, cancelled or foreign report verdict', () => {
const f = designFixture();
try {
f.transcript.assistantMessages[0]!.text = done(f.file);
const report = fs.readFileSync(f.file, 'utf8');
for (const verdict of [
'DESIGN CLEARED only if approval is granted',
'DESIGN CLEARED is not the current verdict; review remains incomplete',
'DESIGN CLEARED is historical; this report has been cancelled',
'DESIGN CLEARED. This report is "historical".',
'DESIGN CLEARED. This report belongs to a different plan.',
'DESIGN CLEARED?', 'DESIGN CLEARED is a placeholder verdict',
]) {
fs.writeFileSync(f.file, report.replace('DESIGN CLEARED — eng review required', verdict));
const time = Date.parse('2026-09-08T17:48:30Z') / 1000; fs.utimesSync(f.file, time, time);
expect(hasNativePlanTerminal(f.transcript, f.file, f.startedAt, 'completion_summary'), verdict).toBe(false);
}
fs.writeFileSync(f.file, report.replace('DESIGN CLEARED — eng review required',
'DESIGN CLEARED (2026-09-11, commit 1a3f728) — eng review required. "This report is historical."'));
const time = Date.parse('2026-09-08T17:48:30Z') / 1000; fs.utimesSync(f.file, time, time);
expect(hasNativePlanTerminal(f.transcript, f.file, f.startedAt, 'completion_summary')).toBe(true);
} finally {f.cleanup();}
});
});
describe('native plan completion and final report', () => {
test('recognizes captured completed native DevEx prose despite a nonmatching terminal heading', () => {
const f = fixture();
+39 -2
View File
@@ -205,9 +205,10 @@ console.log(JSON.stringify(obs));
}, 40_000);
test('AG actual first declaration absence stays negative while the explicit retry selection binds', () => {
test('AG spoken first declaration and explicit retry selection both bind their requested skill', () => {
const [first, retry] = agFixture.observations;
expect(nativeSeededPlanSelection(first!.transcript as PlanCountTranscript, first!.tools as NativePublicToolEvent[], first!.opts)).toBe(false);
// The recorded failed outcome is unchanged; the first public intro names the design review skill.
expect(nativeSeededPlanSelection(first!.transcript as PlanCountTranscript, first!.tools as NativePublicToolEvent[], first!.opts)).toBe(true);
expect(nativeSeededPlanSelection(retry!.transcript as PlanCountTranscript, retry!.tools as NativePublicToolEvent[], retry!.opts)).toBe(true);
});
@@ -388,3 +389,39 @@ test('the AW public fixture selects the existing scope helper owners without a n
expect(selectTests(['test/fixtures/plan-scope-target-aw.json'], E2E_TOUCHFILES, []).selected)
.toEqual(selectTests(['test/helpers/plan-scope-selection.ts'], E2E_TOUCHFILES, []).selected);
});
// Exact public AZ intros; identities and timestamps use the existing synthetic fixture.
const spokenEngIntros = [
"I'll run the eng review skill against your draft plan.",
"I'll run the eng-manager plan review skill against this draft plan.",
];
const spokenEngFixture = (text: string) => {
const f = fixture(text); f.tools[0]!.input!.skill = 'plan-eng-review';
f.transcript.assistantMessages[0]!.timestamp = timestamp(2);
f.tools[0]!.timestamp = timestamp(4); f.tools[1]!.timestamp = timestamp(5);
return f;
};
const engScope = { ...opts, skillName: 'plan-eng-review' };
test('human-readable role names bind only the successfully requested skill', () => {
for (const text of [...spokenEngIntros, "I'll run the plan eng review skill against this draft plan.", "I'll run the eng review skill against this draft plan.", "I'll run the gstack:plan-eng-review skill against this draft plan."]) {
expect(verdict(spokenEngFixture(text), engScope), text).toBe(true);
}
expect(verdict(fixture("I'll run the design review skill against this draft plan."))).toBe(true);
});
test('spoken skill names retain scope ownership, currentness and target boundaries', () => {
for (const text of spokenEngIntros) {
for (const invalid of [text.replace(/(?:eng review|eng-manager plan review)/, 'design review'), text.replace(/(?:eng review|eng-manager plan review)/, 'eng review and design review'), text.replace(/(?:eng review|eng-manager plan review)/, 'Claude reviewer'), `"${text}"`, `Source:\n${text}`, `If approved, ${text}`])
expect(verdict(spokenEngFixture(invalid), engScope), invalid).toBe(false);
for (const mutate of [
(f: ReturnType<typeof fixture>) => { f.tools[0]!.input!.skill = 'plan-design-review'; },
(f: ReturnType<typeof fixture>) => { f.tools[1]!.isError = true; },
(f: ReturnType<typeof fixture>) => { f.transcript.assistantMessages[0]!.sessionId = 'foreign'; },
(f: ReturnType<typeof fixture>) => { f.transcript.assistantMessages[0]!.timestamp = timestamp(-1); },
(f: ReturnType<typeof fixture>) => { f.transcript.assistantMessages[0]!.timestamp = timestamp(0); },
(f: ReturnType<typeof fixture>) => { f.tools.push({kind:'use',name:'Read',sessionId:'owned',toolUseId:'work',timestamp:timestamp(1)}); },
(f: ReturnType<typeof fixture>) => { f.transcript.assistantMessages.push({sessionId:'owned',timestamp:timestamp(4),text:'This selection is "withdrawn".'}); },
(f: ReturnType<typeof fixture>) => { f.transcript.assistantMessages.push({sessionId:'owned',timestamp:timestamp(4),text:'I will review your checkout plan.'}); },
]) { const f=spokenEngFixture(text); mutate(f); expect(verdict(f,engScope)).toBe(false); }
expect(verdict(spokenEngFixture(text),{...engScope,seed:opts.seed+'\n# Another plan'})).toBe(false);
}
});