From 5fe1640f2a9ff4d23e8003e3affd4af181301ec2 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Thu, 26 Feb 2026 20:20:28 +0900 Subject: [PATCH] fix(boulder): count indented checkboxes in plan progress Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus --- src/features/boulder-state/storage.test.ts | 65 ++++++++++++++++++++++ src/features/boulder-state/storage.ts | 4 +- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/features/boulder-state/storage.test.ts b/src/features/boulder-state/storage.test.ts index 967c090cf..e52174cef 100644 --- a/src/features/boulder-state/storage.test.ts +++ b/src/features/boulder-state/storage.test.ts @@ -269,6 +269,71 @@ describe("boulder-state", () => { expect(progress.isComplete).toBe(false) }) + test("should count space-indented unchecked checkbox", () => { + // given - plan file with a two-space indented checkbox + const planPath = join(TEST_DIR, "space-indented-plan.md") + writeFileSync(planPath, `# Plan + - [ ] indented task +`) + + // when + const progress = getPlanProgress(planPath) + + // then + expect(progress.total).toBe(1) + expect(progress.completed).toBe(0) + expect(progress.isComplete).toBe(false) + }) + + test("should count tab-indented unchecked checkbox", () => { + // given - plan file with a tab-indented checkbox + const planPath = join(TEST_DIR, "tab-indented-plan.md") + writeFileSync(planPath, `# Plan + - [ ] tab-indented task +`) + + // when + const progress = getPlanProgress(planPath) + + // then + expect(progress.total).toBe(1) + expect(progress.completed).toBe(0) + expect(progress.isComplete).toBe(false) + }) + + test("should count mixed top-level checked and indented unchecked checkboxes", () => { + // given - plan file with checked top-level and unchecked indented task + const planPath = join(TEST_DIR, "mixed-indented-plan.md") + writeFileSync(planPath, `# Plan +- [x] top-level completed task + - [ ] nested unchecked task +`) + + // when + const progress = getPlanProgress(planPath) + + // then + expect(progress.total).toBe(2) + expect(progress.completed).toBe(1) + expect(progress.isComplete).toBe(false) + }) + + test("should count space-indented completed checkbox", () => { + // given - plan file with a two-space indented completed checkbox + const planPath = join(TEST_DIR, "indented-completed-plan.md") + writeFileSync(planPath, `# Plan + - [x] indented completed task +`) + + // when + const progress = getPlanProgress(planPath) + + // then + expect(progress.total).toBe(1) + expect(progress.completed).toBe(1) + expect(progress.isComplete).toBe(true) + }) + test("should return isComplete true when all checked", () => { // given - all tasks completed const planPath = join(TEST_DIR, "complete-plan.md") diff --git a/src/features/boulder-state/storage.ts b/src/features/boulder-state/storage.ts index f1dccf5b3..ab84368b7 100644 --- a/src/features/boulder-state/storage.ts +++ b/src/features/boulder-state/storage.ts @@ -121,8 +121,8 @@ export function getPlanProgress(planPath: string): PlanProgress { const content = readFileSync(planPath, "utf-8") // Match markdown checkboxes: - [ ] or - [x] or - [X] - const uncheckedMatches = content.match(/^[-*]\s*\[\s*\]/gm) || [] - const checkedMatches = content.match(/^[-*]\s*\[[xX]\]/gm) || [] + const uncheckedMatches = content.match(/^\s*[-*]\s*\[\s*\]/gm) || [] + const checkedMatches = content.match(/^\s*[-*]\s*\[[xX]\]/gm) || [] const total = uncheckedMatches.length + checkedMatches.length const completed = checkedMatches.length