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).
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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")),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user