A phase directory can hold more than one `*-VERIFICATION.md` — an ad-hoc `03-CORRECTION-VERIFICATION.md` worksheet beside the real `03-VERIFICATION.md`. Discovery took the alphabetically-first match, so the worksheet won and the phase could report `missing` while a passing report sat next to it. The issue named two copies. There were seven, in four grammars: two `.sort()[0]` sites in the verification module, three `.find()` over UNSORTED readdir order (phase status, and `verification_path` twice — filesystem-dependent, so two machines on one commit could disagree), and two in shell. All seven now route through one exported `resolveVerificationFile`; the shell copies via a new `verification resolve-file` verb rather than hand-rolling the rule an eighth time. Fixing five of seven would have been worse than fixing none: the verify-work workflow is a WRITER that stamps `status: passed` onto the file it picks, so canonical-aware readers plus an alphabetical writer means the human_needed→passed canonicalization silently no-ops forever while the worksheet gets stamped. That divergence did not exist on next. The first resolver was itself a regression — it preferred ANY canonically-shaped name over the phase's own report, so a stray cross-phase or sentinel-numbered file outranked it. The global-canonical preference was removed rather than narrowed; the rule is pinned to the phase token via `PHASE_NUMBER_TOKEN_SOURCE`, its existing owner. Fixed in passing: the transition workflow's awk guarded on `NR==1` instead of `FNR==1`, so across a multi-file glob it armed only on the first file — a leading worksheet with no frontmatter blocked transition even when the canonical report passed. Also removed a U+00AD soft hyphen introduced earlier on this branch. Five broader-grammar AGGREGATE scans are deliberately out of scope — a different defect class (phase-unscoped scanning), tracked as #3511. Closes #3357 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
32
src/init.cts
32
src/init.cts
@@ -89,7 +89,7 @@ const {
|
||||
extractCurrentMilestone,
|
||||
} = roadmapParser;
|
||||
const { pathExistsInternal, generateSlugInternal, toPosixPath } = coreUtils;
|
||||
const { normalizePhaseName, matchPhaseDirs, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE, isForeignPrefixedPhaseQuery, isSentinelPhaseId } = phaseId;
|
||||
const { normalizePhaseName, matchPhaseDirs, stripProjectCodePrefix, PHASE_NUMBER_TOKEN_SOURCE, isForeignPrefixedPhaseQuery, isSentinelPhaseId, extractPhaseToken } = phaseId;
|
||||
const { pruneOrphanedWorktrees } = worktreeSafety;
|
||||
|
||||
const {
|
||||
@@ -103,7 +103,7 @@ const {
|
||||
|
||||
const { determinePhaseStatus } = commandsMod;
|
||||
const { extractFrontmatter } = frontmatterMod;
|
||||
const { isPhaseComplete } = verificationMod;
|
||||
const { isPhaseComplete, resolveVerificationFile } = verificationMod;
|
||||
const { evaluateUatPassed } = uatPredicateMod;
|
||||
const { resolveLoopHooks } = loopResolverMod;
|
||||
const { loadRegistry } = capabilityLoaderMod;
|
||||
@@ -1144,9 +1144,17 @@ function cmdInitPlanPhase(
|
||||
if (researchFile) {
|
||||
result['research_path'] = toPosixPath(path.join(phaseDirFull, researchFile));
|
||||
}
|
||||
const verificationFile = files.find(
|
||||
(f) => f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md',
|
||||
);
|
||||
// #3473 F2: routed through the shared resolver — readdir order is
|
||||
// filesystem-dependent, so the prior hand-rolled `.find()` could pick
|
||||
// either file when a phase held both a canonical report and an ad-hoc
|
||||
// `-CORRECTION-VERIFICATION.md` worksheet (#3357).
|
||||
// #3492: pin selection to THIS phase's own token so a stray cross-phase
|
||||
// or sentinel-numbered canonically-shaped file cannot outrank this
|
||||
// phase's own (possibly non-canonical) report.
|
||||
const verificationFile = resolveVerificationFile(files, {
|
||||
allowBare: true,
|
||||
phaseToken: extractPhaseToken(path.basename(phaseDirFull)),
|
||||
});
|
||||
if (verificationFile) {
|
||||
result['verification_path'] = toPosixPath(path.join(phaseDirFull, verificationFile));
|
||||
}
|
||||
@@ -1969,9 +1977,17 @@ function cmdInitPhaseOp(cwd: string, phase: string, raw: boolean): void {
|
||||
if (researchFile) {
|
||||
result['research_path'] = toPosixPath(path.join(phaseDirFull, researchFile));
|
||||
}
|
||||
const verificationFile = files.find(
|
||||
(f) => f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md',
|
||||
);
|
||||
// #3473 F2: routed through the shared resolver — readdir order is
|
||||
// filesystem-dependent, so the prior hand-rolled `.find()` could pick
|
||||
// either file when a phase held both a canonical report and an ad-hoc
|
||||
// `-CORRECTION-VERIFICATION.md` worksheet (#3357).
|
||||
// #3492: pin selection to THIS phase's own token so a stray cross-phase
|
||||
// or sentinel-numbered canonically-shaped file cannot outrank this
|
||||
// phase's own (possibly non-canonical) report.
|
||||
const verificationFile = resolveVerificationFile(files, {
|
||||
allowBare: true,
|
||||
phaseToken: extractPhaseToken(path.basename(phaseDirFull)),
|
||||
});
|
||||
if (verificationFile) {
|
||||
result['verification_path'] = toPosixPath(path.join(phaseDirFull, verificationFile));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user