From 8c18d032a5a06d1ad29d99dd28185d68640e6107 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Wed, 8 Jul 2026 10:02:27 -0400 Subject: [PATCH] fix: restrict phase-complete checkbox regex gap (#2067) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The checkbox regex in cmdPhaseComplete used a greedy .* between ] and 'Phase N', so completing an already-checked phase (idempotent re-run) matched a LATER phase whose description merely mentioned the target phase number — checking the wrong phase's box. Restrict the gap to whitespace / optional markdown bold emphasis, mirroring the tight pattern already used by phase-insert. --- src/phase.cts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/phase.cts b/src/phase.cts index 51d18284b..deb41fe4b 100644 --- a/src/phase.cts +++ b/src/phase.cts @@ -1467,8 +1467,14 @@ function cmdPhaseComplete(cwd: string, phaseNum: string, raw: boolean): void { roadmapContent = originalRoadmapContent; const phaseEscaped = phaseMarkdownRegexSource(phaseNum); + // #2067: the gap between `]` and `Phase N` must allow only whitespace / + // markdown bold emphasis — NOT greedy `.*`. A greedy gap matched a later + // phase whose description merely mentioned the completed phase number, + // so completing an already-checked phase (idempotent re-run) checked the + // wrong phase's box. Mirrors the tight pattern used by phase-insert + // (`]\\s*(?:\\*\\*)?Phase`). const checkboxPattern = new RegExp( - `(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phaseEscaped}${OPTIONAL_PHASE_TAG_SOURCE}[:\\s][^\\n]*)`, + `(-\\s*\\[)[ ](\\]\\s*(?:\\*\\*)?\\s*Phase\\s+${phaseEscaped}${OPTIONAL_PHASE_TAG_SOURCE}[:\\s][^\\n]*)`, 'i', ); roadmapContent = roadmapContent.replace(