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:
garrytan committed 2026-09-29 20:12:02 +00:00
1 parent e758fbe96d
commit 475667ff5d
3 files changed
+405 -11

No files matched your search

+14 -11
View File
@@ -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));