From e8533b875d953c9b495849706426cf36fa2ba4b0 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sun, 12 Jul 2026 16:06:38 -0400 Subject: [PATCH] fix(#2177): match the body Progress: line, preserve the descriptive suffix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cmdStateUpdateProgress ran its Progress: patterns against the raw STATE.md content (frontmatter included). With /i+/m the first match was the YAML frontmatter `progress:` key, not the body line β€” so the frontmatter block was mangled (\s* crossed the newline) while the body line stayed stale, and the next STATE.md write re-derived percent from that stale line, silently reverting the update. \2: the .* also discarded any agent-authored descriptive suffix. - Match against the frontmatter-stripped body only (reusing stripFrontmatter); reconstruct content as fmPrefix + modified body so the frontmatter is untouched. - Swap only the machine segment ([bar] NN% or bare NN%), preserving any suffix. - updated stays false when the body has no Progress: line (no false success from a frontmatter progress: key). Closes #2177 --- src/state.cts | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/src/state.cts b/src/state.cts index f7d7b4149..5e59b7370 100644 --- a/src/state.cts +++ b/src/state.cts @@ -613,17 +613,33 @@ function cmdStateUpdateProgress(cwd: string, raw: boolean): void { const _totalSummaries = totalSummaries; readModifyWriteStateMd(statePath, (content) => { - // Try **Progress:** bold format first, then plain Progress: format - const boldProgressPattern = /(\*\*Progress:\*\*\s*).*/i; - const plainProgressPattern = /^(Progress:\s*).*/im; - if (boldProgressPattern.test(content)) { - updated = true; - return content.replace(boldProgressPattern, (_match, prefix: string) => `${prefix}${progressStr}`); - } else if (plainProgressPattern.test(content)) { - updated = true; - return content.replace(plainProgressPattern, (_match, prefix: string) => `${prefix}${progressStr}`); - } - return content; + // #2177: match against the BODY only. With /i the patterns below would + // otherwise hit the YAML frontmatter `progress:` key first (and `\s*` would + // eat its newline, mangling the nested block), while the body Progress: line + // β€” which frontmatter `percent` is re-derived from on every write β€” stays + // stale and silently reverts the update. + const body = stripFrontmatter(content); + const fmPrefix = content.slice(0, content.length - body.length); + + // Swap only the machine segment ("[bar] NN%" or bare "NN%"), preserving any + // descriptive suffix an agent authored, e.g. "(2/4 plans done; blocked on…)". + const machineSegment = /(?:\[[^\]\r\n]*\][ \t]*)?\d{1,3}%/; + const replaceValue = (value: string) => machineSegment.test(value) + ? value.replace(machineSegment, progressStr) + : progressStr; + + // Try **Progress:** bold format first, then plain Progress: format. + const boldProgressPattern = /(\*\*Progress:\*\*[ \t]*)([^\r\n]*)/i; + const plainProgressPattern = /^(Progress:[ \t]*)([^\r\n]*)/im; + const pattern = boldProgressPattern.test(body) + ? boldProgressPattern + : plainProgressPattern.test(body) + ? plainProgressPattern + : null; + if (!pattern) return content; + + updated = true; + return fmPrefix + body.replace(pattern, (_match, prefix: string, value: string) => `${prefix}${replaceValue(value)}`); }, cwd); if (updated) {