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
|
## Stories
|
||||||
|
|
||||||
- [ ] [01 — Introduce `wireUseCase` helper in core-shared](01-wire-use-case-helper/_story.md)
|
- [x] [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)
|
- [x] [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] [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
|
## Stories
|
||||||
|
|
||||||
- [ ] [01 — Trace schema extensions (socketRisk + lastRevalidated)](01-trace-schema-extensions/_story.md)
|
- [x] [01 — Trace schema extensions (socketRisk + lastRevalidated)](01-trace-schema-extensions/_story.md)
|
||||||
- [ ] [02 — Socket integration (skill + CI)](02-socket-integration/_story.md)
|
- [x] [02 — Socket integration (skill + CI)](02-socket-integration/_story.md)
|
||||||
- [ ] [03 — Renovate adoption](03-renovate-adoption/_story.md)
|
- [x] [03 — Renovate adoption](03-renovate-adoption/_story.md)
|
||||||
- [ ] [04 — Major-bump re-evaluation flow](04-major-bump-reevaluation/_story.md)
|
- [x] [04 — Major-bump re-evaluation flow](04-major-bump-reevaluation/_story.md)
|
||||||
- [ ] [05 — Trace revalidation workflow](05-trace-revalidation-workflow/_story.md)
|
- [x] [05 — Trace revalidation workflow](05-trace-revalidation-workflow/_story.md)
|
||||||
- [ ] [06 — CodeQL workflow + pnpm audit signatures](06-codeql-and-audit-signatures/_story.md)
|
- [x] [06 — CodeQL workflow + pnpm audit signatures](06-codeql-and-audit-signatures/_story.md)
|
||||||
- [ ] [07 — Gitleaks pre-commit hook](07-gitleaks-precommit/_story.md)
|
- [x] [07 — Gitleaks pre-commit hook](07-gitleaks-precommit/_story.md)
|
||||||
- [ ] [08 — Sandcastle reviewer prompt update](08-reviewer-prompt-update/_story.md)
|
- [x] [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] [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
|
## Stories
|
||||||
|
|
||||||
- [ ] [01 — Trace schema module + docs/library-decisions/ foundation](01-trace-schema-foundation/_story.md)
|
- [x] [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)
|
- [x] [02 — Pre-commit check script](02-pre-commit-check-script/_story.md)
|
||||||
- [ ] [03 — Claude PreToolUse / PostToolUse hooks](03-claude-hooks/_story.md)
|
- [x] [03 — Claude PreToolUse / PostToolUse hooks](03-claude-hooks/_story.md)
|
||||||
- [ ] [04 — evaluate-library skill](04-evaluate-library-skill/_story.md)
|
- [x] [04 — evaluate-library skill](04-evaluate-library-skill/_story.md)
|
||||||
- [ ] [05 — Human guide: docs/guides/adding-a-library.md](05-human-guide/_story.md)
|
- [x] [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)
|
- [x] [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)
|
- [x] [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)
|
- [x] [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] [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": {
|
"epics": {
|
||||||
"2026-05-13-binder-wrap-helper": {
|
"2026-05-13-binder-wrap-helper": {
|
||||||
"status": "done",
|
"status": "done",
|
||||||
|
|||||||
@@ -191,6 +191,40 @@ export function readFrontmatterStatus(content) {
|
|||||||
return m ? m[1].trim() : null;
|
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
|
* 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.
|
* frontmatter to `status: done`. Returns true if it flipped, false otherwise.
|
||||||
@@ -457,12 +491,14 @@ function applyApprovedState(next) {
|
|||||||
fs.writeFileSync(next.storyPath, content);
|
fs.writeFileSync(next.storyPath, content);
|
||||||
|
|
||||||
let epicFlipped = false;
|
let epicFlipped = false;
|
||||||
|
let epicBulletTicked = false;
|
||||||
if (storyFlipped) {
|
if (storyFlipped) {
|
||||||
|
epicBulletTicked = tickStoryBulletInEpic(WORK_ROOT, next.epic, next.story);
|
||||||
epicFlipped = flipEpicDoneIfAllStoriesDone(WORK_ROOT, next.epic);
|
epicFlipped = flipEpicDoneIfAllStoriesDone(WORK_ROOT, next.epic);
|
||||||
}
|
}
|
||||||
|
|
||||||
const filesToStage = [path.relative(REPO_ROOT, next.storyPath)];
|
const filesToStage = [path.relative(REPO_ROOT, next.storyPath)];
|
||||||
if (epicFlipped) {
|
if (epicFlipped || epicBulletTicked) {
|
||||||
filesToStage.push(
|
filesToStage.push(
|
||||||
path.relative(REPO_ROOT, path.join(WORK_ROOT, next.epic, "_epic.md")),
|
path.relative(REPO_ROOT, path.join(WORK_ROOT, next.epic, "_epic.md")),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user