mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-03 01:46:55 +02:00
test(eng-batching): read the report target as a field, not a spelling
The next targeted rerun (Claude Code 2.1.284) again asked eleven separate native questions and again counted zero: its briefs named no plan and its report declared '- **Review target (fixed):** `/abs/PLAN.md`' under '# Eng Review — PLAN.md: <plan>'. An unsourced brief now inherits the one current target field that names a PLAN.md file, whatever its list or emphasis markup; its ledger record still supplies the cited finding and must reproduce the brief exactly. A brief that names its plan must still match the report title. Replays of all three captures count 9, 9 and 3; controls reject a foreign, duplicate or missing target and an archived title.
This commit is contained in:
1 parent
e758fbe96d
commit
475667ff5d
3 files changed
+405
-11
No files matched your search
@@ -117,8 +117,8 @@ export function isEngBatchingIssueAUQ(fp: AskUserQuestionFingerprint, priorCalls
|
||||
return !priorCalls.some(prior => batchingIssueNumber(prior) === issue);
|
||||
}
|
||||
|
||||
// The report's one target declaration, in the skill's own spellings.
|
||||
const TARGET_FIELD = /^(?:Reviewed |Review )?[Tt]arget(?: \(fixed\))?:/;
|
||||
// The report's target declaration field (Target / Review target / Reviewed target, optionally qualified).
|
||||
const TARGET_FIELD = /^(?:Reviewed |Review )?target(?: \([^)\n]*\))?:/i;
|
||||
|
||||
/** A native brief can use its D number and topic while its stable R identity
|
||||
* lives in the required saved ledger. Count that owned choice, not a title
|
||||
@@ -168,23 +168,26 @@ function recordedBatchingIssue(call: NativePlanQuestionCall, savedPlan: string):
|
||||
const named = [...(lines[1] ?? '').matchAll(/"(Plan:\s*[^"\n]+)"|“(Plan:\s*[^”\n]+)”|\b[Pp]lan\s+"([^"\n]+)"|\b[Pp]lan\s+“([^”\n]+)”/g)]
|
||||
.map(match => targetName(match[1] ?? match[2] ?? match[3] ?? match[4]!));
|
||||
const titles = tokens.slice(0, start).filter(token => token.type === 'heading' && token.depth === 1);
|
||||
// Target declarations are fields, whatever their list or emphasis markup.
|
||||
const targetFields = tokens.slice(0, start).flatMap((token, at) => {
|
||||
if (token.type !== 'paragraph' || !currentHeading(at)) return [];
|
||||
if ((token.type !== 'paragraph' && token.type !== 'list') || !currentHeading(at)) return [];
|
||||
const previous = tokens.slice(0, at).filter(t => t.type !== 'space').at(-1);
|
||||
const quotedContext = /\b(?:quoted|copied|historical|example|hypothetical|archived)\b[^\n]*:\s*$/i;
|
||||
if (previous?.type === 'paragraph' && quotedContext.test(previous.raw)) return [];
|
||||
const parts = token.raw.split('\n');
|
||||
const parts = token.raw.split('\n').map(line => line.replace(/^\s*(?:[-*+]|\d+[.)])\s+/, '').replace(/[*_]/g, '').trim());
|
||||
return parts.filter((line, i) => TARGET_FIELD.test(line) &&
|
||||
!parts.slice(0, i).some(part => quotedContext.test(part)));
|
||||
});
|
||||
// The report title owns the target; an unfenced copy of the reviewed plan
|
||||
// may add its own H1 only when it names that same plan.
|
||||
const namedSource = !rawSourceNames.length && named.length === 1 && titles.length >= 1 &&
|
||||
const targetFiles = targetFields.length === 1 ? [...targetFields[0]!.matchAll(/[\w./-]*[\w-]+\.md\b/g)].map(match => match[0]) : [];
|
||||
// An unsourced brief inherits the report's one current PLAN.md target; its
|
||||
// ledger record still supplies the cited finding. A brief that names its plan
|
||||
// must name the report title's plan, and an unfenced copy of that plan may
|
||||
// add its own H1 only when it names that same plan.
|
||||
const namedSource = !rawSourceNames.length && named.length <= 1 && titles.length >= 1 &&
|
||||
titles[0]!.type === 'heading' && currentHeading(tokens.indexOf(titles[0]!)) &&
|
||||
/^Eng(?:ineering)? review\s*[:—–-]\s*\S/i.test(clean(titles[0]!.text)) &&
|
||||
titles.every(title => title.type === 'heading' && targetName(title.text) === named[0]) && targetFields.length === 1 &&
|
||||
new RegExp(`${TARGET_FIELD.source}\\s*\`?PLAN\\.md\`?(?:\\s|$)`).test(targetFields[0]!) &&
|
||||
[...targetFields[0]!.matchAll(/\b[\w./-]+\.md\b/g)].length === 1;
|
||||
targetFiles.length === 1 && targetFiles[0]!.split('/').at(-1) === 'PLAN.md' &&
|
||||
(named.length === 0 || /^Eng(?:ineering)? review\s*[:—–-]\s*\S/i.test(clean(titles[0]!.text)) &&
|
||||
titles.every(title => title.type === 'heading' && targetName(title.text) === named[0]));
|
||||
if (!directSource && !namedSource) return;
|
||||
const withdrawn = (value: string, owners: string) => new RegExp(
|
||||
`(?:^|[.!?;]\\s+|\\n)(?:Correction:\\s*)?(?:${owners}) (?:is|was|has been) ["“'‘]?(?:withdrawn|cancelled|canceled|rejected|superseded|resolved|closed|hypothetical|not current|no longer current)\\b`, 'i').test(prose(value, true));
|
||||
|
||||
Reference in new issue
Block a user