fix(start-work): add CHECKED_CHECKBOX_PATTERN for plan progress tracking (#3066)

Plan progress now correctly counts checked checkboxes when determining
current task vs completed tasks.

TDD verified. tsc clean.

Closes #3066
This commit is contained in:
YeonGyu-Kim
2026-04-07 15:11:50 +09:00
parent 3e8fd5ff18
commit 0ab2370d4e
3 changed files with 194 additions and 47 deletions
+57 -6
View File
@@ -196,8 +196,25 @@ export function findPrometheusPlans(directory: string): string[] {
}
}
const TODO_HEADING_PATTERN = /^##\s+TODOs\b/i
const FINAL_VERIFICATION_HEADING_PATTERN = /^##\s+Final Verification Wave\b/i
const SECOND_LEVEL_HEADING_PATTERN = /^##\s+/
const UNCHECKED_CHECKBOX_PATTERN = /^(\s*)[-*]\s*\[\s*\]\s*(.+)$/
const CHECKED_CHECKBOX_PATTERN = /^(\s*)[-*]\s*\[[xX]\]\s*(.+)$/
const TODO_TASK_PATTERN = /^\d+\.\s+/
const FINAL_WAVE_TASK_PATTERN = /^F\d+\.\s+/i
type ProgressSection = "todo" | "final-wave" | "other"
/**
* Parse a plan file and count checkbox progress.
*
* Only top-level (zero-indent) checkboxes under `## TODOs` and
* `## Final Verification Wave` sections are counted. The checkbox
* body must carry a valid task label (`N.` for TODOs, `FN.` for
* Final Verification Wave). Nested acceptance-criteria checkboxes
* and checkboxes in other sections are intentionally ignored so
* that progress tracking stays aligned with `readCurrentTopLevelTask`.
*/
export function getPlanProgress(planPath: string): PlanProgress {
if (!existsSync(planPath)) {
@@ -206,13 +223,47 @@ export function getPlanProgress(planPath: string): PlanProgress {
try {
const content = readFileSync(planPath, "utf-8")
// Match markdown checkboxes: - [ ] or - [x] or - [X]
const uncheckedMatches = content.match(/^\s*[-*]\s*\[\s*\]/gm) || []
const checkedMatches = content.match(/^\s*[-*]\s*\[[xX]\]/gm) || []
const lines = content.split(/\r?\n/)
let section: ProgressSection = "other"
let total = 0
let completed = 0
const total = uncheckedMatches.length + checkedMatches.length
const completed = checkedMatches.length
for (const line of lines) {
if (SECOND_LEVEL_HEADING_PATTERN.test(line)) {
section = TODO_HEADING_PATTERN.test(line)
? "todo"
: FINAL_VERIFICATION_HEADING_PATTERN.test(line)
? "final-wave"
: "other"
continue
}
if (section !== "todo" && section !== "final-wave") {
continue
}
const checkedMatch = line.match(CHECKED_CHECKBOX_PATTERN)
const uncheckedMatch = checkedMatch ? null : line.match(UNCHECKED_CHECKBOX_PATTERN)
const match = checkedMatch ?? uncheckedMatch
if (!match) {
continue
}
if (match[1].length > 0) {
continue
}
const taskBody = match[2].trim()
const labelPattern = section === "todo" ? TODO_TASK_PATTERN : FINAL_WAVE_TASK_PATTERN
if (!labelPattern.test(taskBody)) {
continue
}
total++
if (checkedMatch) {
completed++
}
}
return {
total,