From 0cb938e3aca9800d8c658b2751e5491fb9a910c0 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 8 Apr 2026 17:18:29 +0900 Subject: [PATCH] fix(boulder): count only top-level checkboxes in simple-mode plan progress Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src/features/boulder-state/storage.test.ts | 59 ++++++++++++++++++++++ src/features/boulder-state/storage.ts | 8 +-- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/features/boulder-state/storage.test.ts b/src/features/boulder-state/storage.test.ts index 65f6ad87e..4326b42e0 100644 --- a/src/features/boulder-state/storage.test.ts +++ b/src/features/boulder-state/storage.test.ts @@ -650,6 +650,65 @@ describe("boulder-state", () => { expect(progress.completed).toBe(1) expect(progress.isComplete).toBe(false) }) + + test("should count only top-level checkboxes for simple plans with nested tasks", () => { + // given + const planPath = join(TEST_DIR, "simple-nested-plan.md") + writeFileSync(planPath, `# Plan + +- [ ] Top-level task 1 + - [x] Nested task ignored +- [x] Top-level task 2 + * [ ] Another nested task ignored +`) + + // when + const progress = getPlanProgress(planPath) + + // then + expect(progress.total).toBe(2) + expect(progress.completed).toBe(1) + expect(progress.isComplete).toBe(false) + }) + + test("should treat final-wave-only plans as structured mode", () => { + // given + const planPath = join(TEST_DIR, "final-wave-only-plan.md") + writeFileSync(planPath, `# Plan + +## Final Verification Wave +- [ ] F1. Top-level final review + - [x] Nested verification detail ignored +`) + + // when + const progress = getPlanProgress(planPath) + + // then + expect(progress.total).toBe(1) + expect(progress.completed).toBe(0) + expect(progress.isComplete).toBe(false) + }) + + test("should ignore mixed indentation levels in simple plans", () => { + // given + const planPath = join(TEST_DIR, "simple-mixed-indentation-plan.md") + writeFileSync(planPath, `# Plan + +* [x] Top-level star task + - [ ] Indented task ignored + - [x] Tab-indented task ignored +- [ ] Top-level dash task +`) + + // when + const progress = getPlanProgress(planPath) + + // then + expect(progress.total).toBe(2) + expect(progress.completed).toBe(1) + expect(progress.isComplete).toBe(false) + }) }) describe("getPlanName", () => { diff --git a/src/features/boulder-state/storage.ts b/src/features/boulder-state/storage.ts index 1d5dc2a59..d570ce525 100644 --- a/src/features/boulder-state/storage.ts +++ b/src/features/boulder-state/storage.ts @@ -226,7 +226,9 @@ export function getPlanProgress(planPath: string): PlanProgress { const lines = content.split(/\r?\n/) // Check if the plan has structured sections (## TODOs / ## Final Verification Wave) - const hasStructuredSections = lines.some((line) => TODO_HEADING_PATTERN.test(line)) + const hasStructuredSections = lines.some( + (line) => TODO_HEADING_PATTERN.test(line) || FINAL_VERIFICATION_HEADING_PATTERN.test(line), + ) if (hasStructuredSections) { // Structured plan: only count top-level checkboxes with numbered labels @@ -291,8 +293,8 @@ function getStructuredPlanProgress(lines: string[]): PlanProgress { } function getSimplePlanProgress(content: string): PlanProgress { - const uncheckedMatches = content.match(/^\s*[-*]\s*\[\s*\]/gm) || [] - const checkedMatches = content.match(/^\s*[-*]\s*\[[xX]\]/gm) || [] + const uncheckedMatches = content.match(/^[-*]\s*\[\s*\]/gm) || [] + const checkedMatches = content.match(/^[-*]\s*\[[xX]\]/gm) || [] const total = uncheckedMatches.length + checkedMatches.length const completed = checkedMatches.length