From 3fc5c0f1ca821297ba15cf738777e4b55671f3e2 Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Thu, 14 May 2026 21:05:06 +0200 Subject: [PATCH] feat(work): auto-tick epic story bullets when a story finishes - Add tickStoryBulletInEpic(workRoot, epic, story) helper that finds the bullet in the parent epic's `## Stories` section linking to the given story folder and flips `- [ ]` to `- [x]`. Idempotent. - applyApprovedState now ticks the parent epic bullet whenever a story flips to status: done (alongside the existing per-task tick and epic-status flip). Epic file gets staged on either ticked-or- flipped, not just flipped. - Backfill all 3 existing epics: 21 bullets ticked to match their already-done story statuses (binder-wrap-helper x3, library- evaluation-policy x9, ci-security-and-supply-chain x9). --- .../2026-05-13-binder-wrap-helper/_epic.md | 6 +-- .../_epic.md | 18 ++++----- .../_epic.md | 18 ++++----- docs/work/_state.json | 2 +- scripts/work/dispatch.mjs | 38 ++++++++++++++++++- 5 files changed, 59 insertions(+), 23 deletions(-) diff --git a/docs/work/2026-05-13-binder-wrap-helper/_epic.md b/docs/work/2026-05-13-binder-wrap-helper/_epic.md index 1d4dc7e..f5e30c0 100644 --- a/docs/work/2026-05-13-binder-wrap-helper/_epic.md +++ b/docs/work/2026-05-13-binder-wrap-helper/_epic.md @@ -18,6 +18,6 @@ Five of `pnpm fallow`'s top-ten clone groups come from binder pairs across featu ## Stories -- [ ] [01 — Introduce `wireUseCase` helper in core-shared](01-wire-use-case-helper/_story.md) -- [ ] [02 — Migrate all five feature binders to `wireUseCase`](02-migrate-feature-binders/_story.md) -- [ ] [03 — Update generator templates to emit `wireUseCase` call shape](03-update-generator-templates/_story.md) +- [x] [01 — Introduce `wireUseCase` helper in core-shared](01-wire-use-case-helper/_story.md) +- [x] [02 — Migrate all five feature binders to `wireUseCase`](02-migrate-feature-binders/_story.md) +- [x] [03 — Update generator templates to emit `wireUseCase` call shape](03-update-generator-templates/_story.md) diff --git a/docs/work/2026-05-14-ci-security-and-supply-chain/_epic.md b/docs/work/2026-05-14-ci-security-and-supply-chain/_epic.md index 795f302..c164364 100644 --- a/docs/work/2026-05-14-ci-security-and-supply-chain/_epic.md +++ b/docs/work/2026-05-14-ci-security-and-supply-chain/_epic.md @@ -18,12 +18,12 @@ The repo's security posture has zero security tooling. ADR-022 + the library-eva ## Stories -- [ ] [01 — Trace schema extensions (socketRisk + lastRevalidated)](01-trace-schema-extensions/_story.md) -- [ ] [02 — Socket integration (skill + CI)](02-socket-integration/_story.md) -- [ ] [03 — Renovate adoption](03-renovate-adoption/_story.md) -- [ ] [04 — Major-bump re-evaluation flow](04-major-bump-reevaluation/_story.md) -- [ ] [05 — Trace revalidation workflow](05-trace-revalidation-workflow/_story.md) -- [ ] [06 — CodeQL workflow + pnpm audit signatures](06-codeql-and-audit-signatures/_story.md) -- [ ] [07 — Gitleaks pre-commit hook](07-gitleaks-precommit/_story.md) -- [ ] [08 — Sandcastle reviewer prompt update](08-reviewer-prompt-update/_story.md) -- [ ] [09 — CI security guide + CLAUDE.md](09-ci-security-guide-and-docs/_story.md) +- [x] [01 — Trace schema extensions (socketRisk + lastRevalidated)](01-trace-schema-extensions/_story.md) +- [x] [02 — Socket integration (skill + CI)](02-socket-integration/_story.md) +- [x] [03 — Renovate adoption](03-renovate-adoption/_story.md) +- [x] [04 — Major-bump re-evaluation flow](04-major-bump-reevaluation/_story.md) +- [x] [05 — Trace revalidation workflow](05-trace-revalidation-workflow/_story.md) +- [x] [06 — CodeQL workflow + pnpm audit signatures](06-codeql-and-audit-signatures/_story.md) +- [x] [07 — Gitleaks pre-commit hook](07-gitleaks-precommit/_story.md) +- [x] [08 — Sandcastle reviewer prompt update](08-reviewer-prompt-update/_story.md) +- [x] [09 — CI security guide + CLAUDE.md](09-ci-security-guide-and-docs/_story.md) diff --git a/docs/work/2026-05-14-library-evaluation-policy/_epic.md b/docs/work/2026-05-14-library-evaluation-policy/_epic.md index f18ede8..b326fa7 100644 --- a/docs/work/2026-05-14-library-evaluation-policy/_epic.md +++ b/docs/work/2026-05-14-library-evaluation-policy/_epic.md @@ -18,12 +18,12 @@ The repo's narrow third-party surface is uncodified. New dependencies enter via ## Stories -- [ ] [01 — Trace schema module + docs/library-decisions/ foundation](01-trace-schema-foundation/_story.md) -- [ ] [02 — Pre-commit check script](02-pre-commit-check-script/_story.md) -- [ ] [03 — Claude PreToolUse / PostToolUse hooks](03-claude-hooks/_story.md) -- [ ] [04 — evaluate-library skill](04-evaluate-library-skill/_story.md) -- [ ] [05 — Human guide: docs/guides/adding-a-library.md](05-human-guide/_story.md) -- [ ] [06 — Sandcastle reviewer prompt update](06-sandcastle-reviewer-prompt/_story.md) -- [ ] [07 — Generator pre-shipped traces for optional cores](07-generator-pre-shipped-traces/_story.md) -- [ ] [08 — Backfill traces for existing runtime deps](08-backfill-traces/_story.md) -- [ ] [09 — CLAUDE.md Key Conventions bullet](09-claude-md-update/_story.md) +- [x] [01 — Trace schema module + docs/library-decisions/ foundation](01-trace-schema-foundation/_story.md) +- [x] [02 — Pre-commit check script](02-pre-commit-check-script/_story.md) +- [x] [03 — Claude PreToolUse / PostToolUse hooks](03-claude-hooks/_story.md) +- [x] [04 — evaluate-library skill](04-evaluate-library-skill/_story.md) +- [x] [05 — Human guide: docs/guides/adding-a-library.md](05-human-guide/_story.md) +- [x] [06 — Sandcastle reviewer prompt update](06-sandcastle-reviewer-prompt/_story.md) +- [x] [07 — Generator pre-shipped traces for optional cores](07-generator-pre-shipped-traces/_story.md) +- [x] [08 — Backfill traces for existing runtime deps](08-backfill-traces/_story.md) +- [x] [09 — CLAUDE.md Key Conventions bullet](09-claude-md-update/_story.md) diff --git a/docs/work/_state.json b/docs/work/_state.json index c668078..a6fc546 100644 --- a/docs/work/_state.json +++ b/docs/work/_state.json @@ -1,5 +1,5 @@ { - "updated_at": "2026-05-14T18:17:30.697Z", + "updated_at": "2026-05-14T19:05:08.479Z", "epics": { "2026-05-13-binder-wrap-helper": { "status": "done", diff --git a/scripts/work/dispatch.mjs b/scripts/work/dispatch.mjs index d5fe647..8fd24db 100644 --- a/scripts/work/dispatch.mjs +++ b/scripts/work/dispatch.mjs @@ -191,6 +191,40 @@ export function readFrontmatterStatus(content) { return m ? m[1].trim() : null; } +/** + * Tick the bullet in an epic's `## Stories` section that links to the given + * story folder. Idempotent: returns false if the bullet is already ticked or + * not present. Mirrors the per-task tick that already happens inside story + * files, applied at the parent-epic granularity. + */ +export function tickStoryBulletInEpic(workRoot, epicId, storyId) { + const epicFile = path.join(workRoot, epicId, "_epic.md"); + if (!fs.existsSync(epicFile)) return false; + const content = fs.readFileSync(epicFile, "utf8"); + const lines = content.split("\n"); + let inStories = false; + let changed = false; + for (let i = 0; i < lines.length; i++) { + if (lines[i].startsWith("## ")) { + inStories = /^##\s+Stories\b/i.test(lines[i]); + continue; + } + if (!inStories) continue; + if ( + lines[i].includes(`(${storyId}/_story.md)`) || + lines[i].includes(`(./${storyId}/_story.md)`) + ) { + if (/\[\s\]/.test(lines[i])) { + lines[i] = lines[i].replace(/\[\s\]/, "[x]"); + changed = true; + } + break; + } + } + if (changed) fs.writeFileSync(epicFile, lines.join("\n")); + return changed; +} + /** * If all stories in an epic are `status: done`, flip the epic's own * frontmatter to `status: done`. Returns true if it flipped, false otherwise. @@ -457,12 +491,14 @@ function applyApprovedState(next) { fs.writeFileSync(next.storyPath, content); let epicFlipped = false; + let epicBulletTicked = false; if (storyFlipped) { + epicBulletTicked = tickStoryBulletInEpic(WORK_ROOT, next.epic, next.story); epicFlipped = flipEpicDoneIfAllStoriesDone(WORK_ROOT, next.epic); } const filesToStage = [path.relative(REPO_ROOT, next.storyPath)]; - if (epicFlipped) { + if (epicFlipped || epicBulletTicked) { filesToStage.push( path.relative(REPO_ROOT, path.join(WORK_ROOT, next.epic, "_epic.md")), );