mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-10 02:30:39 +01:00
Count an autoplan phase report that starts after a preface sentence (GST-1916) (#4171)
* Count an autoplan phase report that starts after a preface sentence (GST-1916) Claude Code can journal the prose between tool calls as a paraphrased narration summary. A Phase 1 report that opened with a check sentence came back as "I checked the packet... Phase 1 is complete with 9 native issues and no outside review, so the work moves on to Phase 2.", and the publication guard denied the next phase with publication_missing because it counted a declaration only at the start of a line (#4155). - A declaration that starts a sentence inside a plain prose line now counts. Sentences inside double, curly or single quotes or inline code, and list, heading, table and quote lines, still don't. - A "with" result that mentions "no" after its counts is affirmative; a result that opens with "no" is still rejected. - New test replays the captured summary as a narration-journaled report through the guard, with quoted, negated and other-phase controls. Co-authored-by: Marius Storm-Olsen <1500+mstormo@users.noreply.github.com> Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Reject an autoplan phase report that follows a same-line example introduction (GST-1916) Review-Finding: review-p1/specialist-batched#1 reader=red-team Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Mention same-line example introductions in the autoplan changelog entry (GST-1916) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: Marius Storm-Olsen <1500+mstormo@users.noreply.github.com> Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
3 files changed
+115
-3
No files matched your search
@@ -5,6 +5,8 @@ export interface AutoplanPhaseHit {
|
||||
ts: number;
|
||||
}
|
||||
|
||||
const exampleIntroduction = /\b(?:example|sample|quote(?:d)?|source|template|instruction|marker|expected\s+(?:output|announcement))\b/i;
|
||||
|
||||
function phaseDeclaration(text: string): RegExpExecArray | null {
|
||||
const declaration = String.raw`Phase[ \t]+(1|2(?:\.5)?|3)(?:[ \t]+\(([^()]*)\))?[ \t]+(?:is[ \t]+)?(?:complete(?:d)?|done|finished|wrapped[ \t]+up)`;
|
||||
const plain = text.replace(new RegExp(String.raw`^\*\*(${declaration}[.:]?)\*\*`, 'i'), '$1');
|
||||
@@ -16,7 +18,7 @@ function phaseDeclaration(text: string): RegExpExecArray | null {
|
||||
// supply the missing phase declaration.
|
||||
const recap = new RegExp(String.raw`^${declaration}(?:[ \t]*[—–][ \t]*|[ \t]+(?<withResult>with)[ \t]+)(.+)$`, 'i').exec(plain);
|
||||
const tail = recap?.[4]?.trim();
|
||||
if (tail && !/^["“'‘>]|\?|\b(?:if|unless|when|once|pending|maybe|perhaps|would|could|will|source|example|sample|quote(?:d)?|historical|earlier|previous(?:ly)?|template|not|no|never|superseded|provided|rejected|incomplete|unfinished|withdrawn|retracted|cancelled|canceled)\b/i.test(tail)) match = recap;
|
||||
if (tail && !/^(?:["“'‘>]|no\b)|\?|\b(?:if|unless|when|once|pending|maybe|perhaps|would|could|will|source|example|sample|quote(?:d)?|historical|earlier|previous(?:ly)?|template|not|never|superseded|provided|rejected|incomplete|unfinished|withdrawn|retracted|cancelled|canceled)\b/i.test(tail)) match = recap;
|
||||
if (match?.groups?.withResult && /\bhypothetic(?:al|ally)\b/i.test(tail!)) match = null;
|
||||
}
|
||||
// Optional phase names are metadata, and must agree with the phase number.
|
||||
@@ -30,6 +32,25 @@ function phaseDeclaration(text: string): RegExpExecArray | null {
|
||||
return match;
|
||||
}
|
||||
|
||||
/**
|
||||
* A narration summary can fold a preface sentence into the report, so the
|
||||
* declaration starts a sentence inside the line (#4155). Only plain prose
|
||||
* lines qualify, and a sentence inside quotes or inline code is quoted text.
|
||||
*/
|
||||
function sentenceDeclaration(line: string): RegExpExecArray | null {
|
||||
if (/^(?:[#>|"“'‘`[+-]|\*(?!\*)|\d+[.)][ \t])/.test(line) || /\bEmit\s+phase-transition\s+summary\s*:/i.test(line)) return null;
|
||||
for (const boundary of line.matchAll(/[.!?]["”’)]?[ \t]+(?=(?:\*\*)?Phase[ \t])/g)) {
|
||||
const before = line.slice(0, boundary.index! + boundary[0].length);
|
||||
if (exampleIntroduction.test(before)) return null;
|
||||
const count = (pattern: RegExp) => before.match(pattern)?.length ?? 0;
|
||||
if (count(/"/g) % 2 || count(/`/g) % 2 || count(/“/g) > count(/”/g) ||
|
||||
count(/(?:^|[\s(])['‘]/g) > count(/['’](?=[\s.,;:!?)]|$)/g)) continue;
|
||||
const match = phaseDeclaration(line.slice(before.length));
|
||||
if (match) return match;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** An explicit current withdrawal in the same announcement cancels a new with-result claim. */
|
||||
function withResultWithdrawn(lines: string[], index: number, phase: string): boolean {
|
||||
let source = false;
|
||||
@@ -93,7 +114,7 @@ export function autoplanPhaseCompletions(
|
||||
if (fence) continue;
|
||||
// Accept a plain/bold declaration, never a heading, quoted source,
|
||||
// table cell, checklist, or a sentence promising future completion.
|
||||
let match = phaseDeclaration(text);
|
||||
let match = phaseDeclaration(text) ?? sentenceDeclaration(text);
|
||||
if (/^>\s/.test(text)) {
|
||||
// The skill's transition summary itself is a blockquote. Accept a
|
||||
// filled-in single-phase summary with concrete consensus counts;
|
||||
@@ -107,7 +128,7 @@ export function autoplanPhaseCompletions(
|
||||
!/\[[^\]]*\]|\{\{/.test(block.join('\n'));
|
||||
if (concreteSummary) match = phaseDeclaration(text.replace(/^>\s?/, ''));
|
||||
}
|
||||
const introducedExample = /\b(?:example|sample|quote(?:d)?|source|template|instruction|marker|expected\s+(?:output|announcement))\b.*[::]\s*$/i.test(previousLine) ||
|
||||
const introducedExample = new RegExp(String.raw`${exampleIntroduction.source}.*[::]\s*$`, 'i').test(previousLine) ||
|
||||
(!!match?.groups?.withResult && /\b(?:example|sample|quote(?:d)?|source|historical|earlier|previous|archived|hypothetical|template)\b.*[::]\s*$/i.test(previousLine.replace(/\*\*/g, '')));
|
||||
// Keep an example introduction across all its marker/quoted lines,
|
||||
// rather than allowing its second marker to look like real completion.
|
||||
|
||||
Reference in new issue
Block a user