From 1136a59e0c6c9646d4b5c283afe56cc6f84845a7 Mon Sep 17 00:00:00 2001 From: pbean Date: Thu, 30 Apr 2026 16:02:43 -0700 Subject: [PATCH] anthropic critique of skill adjustments --- .../bmad-create-epics-and-stories/SKILL.md | 20 +-- .../prompts/discovery.md | 6 +- .../prompts/epic-authoring.md | 4 +- .../prompts/epic-design.md | 6 +- .../prompts/intent.md | 2 - .../prompts/validate.md | 4 +- .../resources/story-frontmatter-schema.md | 2 +- .../resources/validation-error-codes.md | 147 ++++++++++++++++++ .../scripts/init_story.py | 2 +- .../scripts/move_story.py | 2 +- .../scripts/parse_v6_epics.py | 2 +- .../scripts/rename_epic.py | 2 +- .../scripts/tests/test_init_story.py | 3 +- .../scripts/tests/test_move_story.py | 2 +- .../scripts/tests/test_rename_epic.py | 2 +- .../scripts/tests/test_validate_initiative.py | 27 ++++ .../scripts/validate_initiative.py | 2 +- 17 files changed, 196 insertions(+), 39 deletions(-) create mode 100644 src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/resources/validation-error-codes.md diff --git a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md index 3190d397b..61d30e362 100644 --- a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md +++ b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md @@ -11,16 +11,14 @@ This skill produces and maintains the **v7 epic-first folder tree** for an initi **Acts as:** a product strategist and technical specifications writer collaborating with the user as a peer. The user owns product vision and priorities; this skill brings requirements decomposition, sizing judgment, and the v7 schema. Conversational throughout — soft gates ("ready to move on?") rather than rigid menus. -**One skill, three modes × three interaction styles:** +**One skill, three modes:** - **Create** — no `epics/` tree yet. Walks intent → discovery → epic design → per-epic authoring → validate → finalize. - **Edit** — the tree exists. Routes by user phrasing or flag to add-epic, split-epic, merge-epics, rename-epic, refine-story, re-derive-deps, or re-validate. Never re-walks intent or discovery. - **Migrate** — a v6 monolithic `epics.md` (or sharded directory) exists but no v7 tree. Offers leave-alone, run-canonical-helper, or walk-through-manually. -Interaction style is orthogonal: +Plus a **deterministic surface** for pipelines: -- **Guided** (default) — conversational dialog with soft gates and per-epic checkpoints. Right for first-timers and complex initiatives. -- **YOLO** (`--yolo`) — same flow, but discovery summary, the soft gate dialog at each stage, and the per-epic checkpoint are skipped. The skill proposes the full epic list in one shot, authors every epic end-to-end, then surfaces a single batched recap before validation. Right for experts on their third initiative this quarter. - **From-spec** (`--from-spec `) — Stages 1–3 skipped entirely. A structured spec drives Stages 4 and 5 deterministically. Right for pipelines and pre-drafted plans. **Headless surfaces:** @@ -28,7 +26,7 @@ Interaction style is orthogonal: - `--re-validate` (alias `--headless` / `-H`) runs strict validation only and emits JSON. Pair with `--coverage-strict` to fail CI on uncovered requirements. - `--from-spec ` runs end-to-end authoring + validation deterministically and emits JSON. Implicitly headless; pass `--coverage-strict` to fail on uncovered requirements. -All other modes are interactive (Guided and YOLO). +Create, edit, and migrate are interactive. **Owns:** front-matter schemas (`resources/`), bootstrap and validation scripts (`scripts/`), the inventory cache at `{initiative_store}/.bmad-cache/inventory.json`, and the only writers of the epic tree. **Does not own:** `governance.md` or `initiative-context.md` authoring, `initiative_store` config plumbing, downstream status transitions beyond `draft`. @@ -90,11 +88,7 @@ If the user passed `--re-validate`, `--headless`, or `-H` (or said "re-validate" If the user passed `--from-spec `, set `{mode}=from-spec` and `{spec_path}=`. Set `{headless_mode}=true` by default (override only if the user is interactive). Skip Stages 1–3, route directly to `prompts/from-spec.md`. -### 3. YOLO interaction style - -If the user passed `--yolo` (or said "yolo this", "go fast", "don't ask me", or similar in their opening message), set `{yolo}=true`. Otherwise `{yolo}=false`. YOLO is orthogonal to the mode — both create and migrate flows respect it. Edit-mode sub-flows ignore `{yolo}` because they are inherently interactive graph reasoning. - -### 4. Mode by filesystem state +### 3. Mode by filesystem state - If `{initiative_store}/epics/` does not exist OR exists but contains no epic folders → `{mode}=create`. - If `{initiative_store}/epics/` contains v7 epic folders (any folder matching `NN-*` with an `epic.md` inside) → `{mode}=edit`. @@ -102,7 +96,7 @@ If the user passed `--yolo` (or said "yolo this", "go fast", "don't ask me", or If both v7 folders and a v6 file exist, prefer `edit` and surface the v6 file in Stage 1 as a one-line note. -### 5. Edit sub-mode dispatch (only when `{mode}=edit`) +### 4. Edit sub-mode dispatch (only when `{mode}=edit`) Detect from the user's opening message: @@ -120,7 +114,7 @@ Detect from the user's opening message: Set `{edit_submode}` to the matched value before routing. -### 6. Route +### 5. Route - `create` → `prompts/intent.md` - `migrate` → `prompts/intent.md` (it offers the migrate three-options branch when `{mode}=migrate`) @@ -128,7 +122,7 @@ Set `{edit_submode}` to the matched value before routing. - `headless` → `prompts/validate.md` - `from-spec` → `prompts/from-spec.md` -Carry `{mode}`, `{yolo}`, `{spec_path}` (when set), and `{edit_submode}` (when set) into the routed prompt. +Carry `{mode}`, `{spec_path}` (when set), and `{edit_submode}` (when set) into the routed prompt. ## Stages diff --git a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/prompts/discovery.md b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/prompts/discovery.md index 5bc5bec48..417b616c5 100644 --- a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/prompts/discovery.md +++ b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/prompts/discovery.md @@ -8,7 +8,7 @@ ## Pre-flight -Before launching the artifact-analyzer, tell the user (in 3–5 lines) what you're about to scan: the resolved `{planning_artifacts}` path, the resolved `{project_knowledge}` path, and any user-pointed paths from Stage 1. This lets a misconfigured path surface immediately rather than as an empty result. Skip the pre-flight in `{yolo}=true` and `{mode}=headless`. +Before launching the artifact-analyzer, tell the user (in 3–5 lines) what you're about to scan: the resolved `{planning_artifacts}` path, the resolved `{project_knowledge}` path, and any user-pointed paths from Stage 1. This lets a misconfigured path surface immediately rather than as an empty result. Skip the pre-flight in `{mode}=headless`. ## Subagent fan-out @@ -59,12 +59,10 @@ Lists may be empty. Each requirement entry must have a unique `code`. Tell the user in 4–8 lines: counts (FRs, NFRs, UX-DRs, debt items), the starter-template note if any, governance constraints if any, and any gaps. Do not dump the full inventory — they have the source documents. Mention the cache path so they know where the inventory lives. -In `{yolo}=true` collapse to a single line: "Inventory: N FRs, M NFRs, K UX-DRs (cached at `.bmad-cache/inventory.json`)." - In `{mode}=headless` skip the summary entirely. Soft gate (interactive only): "Anything missing or wrong here, or shall we move on to designing the epic list?" ## Stage Complete -When the user confirms (or `{yolo}=true` auto-confirms), route to `prompts/epic-design.md`. The inventory remains on disk; later stages re-read it rather than relying on working memory. +When the user confirms, route to `prompts/epic-design.md`. The inventory remains on disk; later stages re-read it rather than relying on working memory. diff --git a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/prompts/epic-authoring.md b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/prompts/epic-authoring.md index 485190685..7312e6930 100644 --- a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/prompts/epic-authoring.md +++ b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/prompts/epic-authoring.md @@ -85,8 +85,6 @@ python3 scripts/validate_initiative.py --initiative-store {initiative_store} --l Before starting the next epic, confirm with the user that this epic is complete. The next epic does not begin until the current is approved. -In `{yolo}=true`, **skip the per-epic checkpoint** entirely — author the full epic list end-to-end, then surface a single batched recap before routing to validation. - ## After all epics are authored Route to `prompts/validate.md` for full-tree strict validation. @@ -106,4 +104,4 @@ After any edit-mode flow finishes, route to `prompts/validate.md` strict. ## Stage Complete -Stage 4 ends when every approved epic has its `epic.md` and all its story files written, the per-epic non-strict validation passes for each, and the user has confirmed completion (or `{yolo}=true` auto-confirmed after the batch recap). +Stage 4 ends when every approved epic has its `epic.md` and all its story files written, the per-epic non-strict validation passes for each, and the user has confirmed completion. diff --git a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/prompts/epic-design.md b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/prompts/epic-design.md index 9918c7972..c0916cb24 100644 --- a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/prompts/epic-design.md +++ b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/prompts/epic-design.md @@ -35,13 +35,9 @@ Mentally compute the cross-epic dependency graph. If you find any cycle (Epic A If the user wants to pressure-test the epic shape, they may invoke `bmad-advanced-elicitation` (deeper critique methods) or `bmad-party-mode` (multi-agent perspectives) explicitly. **Do not present these as a menu** — only invoke when the user asks. -## YOLO mode - -When `{yolo}=true`, propose the entire epic list in one message — title, intent, `depends_on`, theme, and FR/UX-DR allocations for every epic — and ask the user once whether to lock it in or revise. Skip the per-step dialog; rely on the cycle check and Stage 5 to catch problems. - ## Soft gate -"Does this epic list capture the initiative? Anything missing, anything overlapping that should be consolidated?" When the user is satisfied, the list is approved and Stage 3 is complete. Skip in `{yolo}=true` after the one-shot proposal is approved. +"Does this epic list capture the initiative? Anything missing, anything overlapping that should be consolidated?" When the user is satisfied, the list is approved and Stage 3 is complete. ## Edit-mode flows diff --git a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/prompts/intent.md b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/prompts/intent.md index 57f6ae712..c79f33c82 100644 --- a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/prompts/intent.md +++ b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/prompts/intent.md @@ -52,8 +52,6 @@ After the user picks: This stage is conversational. Confirm with a soft prompt rather than a menu — "Anything else to add about the initiative, or should we move on to scanning the project?" Users almost always remember one more thing when given a graceful exit ramp. -In `{yolo}=true` skip the soft gate entirely once the three items are settled. - ## Stage Complete Stage 1 ends when the chosen mode's exit conditions above are met. Carry the initiative title, primary intent, story-type mix, and any volunteered details into the next stage in working memory — none of this is written to disk yet (Stage 2 writes the inventory to `.bmad-cache/inventory.json`). diff --git a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/prompts/validate.md b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/prompts/validate.md index 6e81cb262..ed43ad601 100644 --- a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/prompts/validate.md +++ b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/prompts/validate.md @@ -37,7 +37,7 @@ If `{mode}=headless`: ### 1. Surface failures conversationally -For each error in `findings`, explain it in one sentence and offer to fix. Group by file when several errors land on the same path. Common patterns and the right next step: +For each error in `findings`, explain it in one sentence and offer to fix. Group by file when several errors land on the same path. The full code → meaning → fix lookup lives at `resources/validation-error-codes.md` — load it when surfacing failures so the explanation and fix are accurate. Common shortcuts to the right next step: - **Schema errors** (`*-extra-keys`, `*-missing-keys`, `*-bad-status`, `*-bad-type`) → loop back to `prompts/epic-authoring.md` for that one file, edit the front matter, re-validate. - **`epic-nn-mismatch` / `story-epic-mismatch`** → likely a hand-edit of the front matter; the folder name is canonical, so update the front matter to match. @@ -45,8 +45,6 @@ For each error in `findings`, explain it in one sentence and offer to fix. Group - **`epic-dep-cycle`** → cross-epic graph has a loop. Loop back to `prompts/epic-design.md` (re-derive-deps flow) to fix it. - **`story-numbering-gaps`** → use `scripts/rename_story.py --to-nn` to fill the gap or renumber the survivors. -If the failure-pattern set ever grows past ~10 entries, extract this list to `resources/validation-error-codes.md` to keep this prompt tight. - ### 2. Coverage check When `--inventory` was passed, the validator already produced `coverage_missing` deterministically — surface those codes conversationally and route into the **coverage-fix** edit-mode entry point in `prompts/epic-authoring.md`. diff --git a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/resources/story-frontmatter-schema.md b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/resources/story-frontmatter-schema.md index 193504105..5d0dda53a 100644 --- a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/resources/story-frontmatter-schema.md +++ b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/resources/story-frontmatter-schema.md @@ -21,7 +21,7 @@ metadata: # OPTIONAL — free-form table; BMad - **title** — Always emitted with double quotes by `init_story.py`. Inner double quotes escaped with `\`. - **type** — Drives body-skeleton generation: `task` omits the As-a/I-want/So-that stanza by default; `bug` and `spike` make it optional; `feature` requires it. - **status** — `init_story.py` always writes `draft`. Promotion to any other value is owned by downstream skills (`bmad-dev-story` etc.). This skill never auto-promotes. -- **epic** — The enclosing folder name (e.g. `01-billing-stripe`), not just the NN. The folder name wins on conflict; the validator flags drift. +- **epic** — The enclosing folder name (e.g. `01-billing-stripe`), not just the NN. Emitted unquoted (the dash makes it unambiguously a string in YAML). The folder name wins on conflict; the validator flags drift. - **depends_on** — Inline YAML list. Two reference forms: - **Within-epic:** the sibling story's basename without `.md` — e.g. `04-define-schema`. - **Cross-epic:** `/` — e.g. `02-auth-migration/04-session-management`. diff --git a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/resources/validation-error-codes.md b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/resources/validation-error-codes.md new file mode 100644 index 000000000..4fafd2e72 --- /dev/null +++ b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/resources/validation-error-codes.md @@ -0,0 +1,147 @@ +# Validation Error Codes + +The lookup table for every error and warning `scripts/validate_initiative.py` emits — what each code means, how it arises, and how to fix it. Loaded by `prompts/validate.md` when surfacing failures conversationally. + +The output shape is always: + +```json +{"level": "error|warning", "code": "", "message": "", "path": ""} +``` + +`level` controls the exit code: any `error` causes the validator to return 1; warnings don't. `coverage-missing` is the one code whose level depends on a flag (`--coverage-strict` escalates it). + +## Schema errors + +These fire when a file's front matter doesn't match the locked schema in `resources/epic-frontmatter-schema.md` and `resources/story-frontmatter-schema.md`. + +| Code | Meaning | Fix | +| --- | --- | --- | +| `epic-frontmatter-parse` | The validator's loose YAML parser couldn't read the front matter. Usually a missing `:`, an unindented continuation, or unclosed `---`. | Open the `epic.md` and check the front-matter block. The parser tolerates inline lists and inline strings; reject indented multi-line strings. | +| `epic-extra-keys` | The epic's front matter has a top-level key that isn't in `{title, epic, status, depends_on, metadata}`. | Move the key under `metadata:` (which is free-form), or remove it. | +| `epic-missing-keys` | A required top-level key is absent: `title`, `epic`, `status`, or `depends_on`. | Add it. `init_epic.py` always emits all four; this fires when a hand-edit dropped one. | +| `epic-bad-status` | `status` value isn't one of `draft / ready / in-progress / review / done / blocked`. | Set to a valid enum value. New epics should be `draft`. | +| `epic-nn-mismatch` | `epic:` field doesn't match the folder's NN prefix. The folder name is canonical. | Edit the front matter to match the folder. If the folder name itself is wrong, use `rename_epic.py --to-nn`. | +| `epic-deps-not-list` | `depends_on:` is not a YAML list. | Make it `[]` (empty) or `["01", "02"]`. Inline lists only. | +| `story-frontmatter-parse` | Same as above, for a story file. | Check the front-matter block. | +| `story-extra-keys` | Story has a top-level key not in `{title, type, status, epic, depends_on, metadata}`. | Move under `metadata:` or remove. | +| `story-missing-keys` | A required key is absent. | Add it. | +| `story-bad-type` | `type` isn't one of `feature / bug / task / spike`. | Pick one. Story type drives body skeleton choices (the user-story stanza is required for `feature`, optional for `bug`/`spike`, absent for `task`). | +| `story-bad-status` | Same as `epic-bad-status` for a story. | Set a valid status. | +| `story-bad-prefix` | Story filename doesn't start with `NN-`. | Rename to match the convention. The validator expects `^\d+-`. | +| `story-epic-mismatch` | Story's `epic:` field doesn't match its enclosing folder name. The folder name is canonical. | Edit the front matter to match the folder. Or move the file with `move_story.py`. | +| `story-deps-not-list` | `depends_on:` is not a YAML list. | Make it `[]` or an inline list. | + +## Dependency errors + +These fire when references don't resolve. + +| Code | Meaning | Fix | +| --- | --- | --- | +| `epic-dep-unresolved` | An epic's `depends_on` references an NN that has no corresponding folder in the tree. | Either fix the typo, or remove the dep, or create the missing epic. | +| `epic-dep-cycle` | The cross-epic depends_on graph has a cycle (Epic A → B → A, directly or transitively). The message lists the cycle. | Break the cycle. If the cycle reflects a real bidirectional dependency, the epics should probably be merged or the seam reconsidered. | +| `story-dep-unresolved` | A story's `depends_on` entry doesn't resolve — either as a within-epic basename or as a cross-epic `/` ref. | Fix the typo, or use `move_story.py` / `rename_story.py` going forward (they update refs atomically). For after-the-fact fixes, edit `depends_on:` directly and re-validate. | + +## Structural errors + +| Code | Meaning | Fix | +| --- | --- | --- | +| `no-epics-dir` | `{initiative_store}/epics/` doesn't exist. | Either you have the wrong `--initiative-store`, or no tree has been generated yet. Run the skill in create mode. | +| `missing-epic-md` | A folder matches `NN-*` but has no `epic.md` inside. | Either the folder is bogus (delete it) or the `epic.md` was lost (regenerate with `init_epic.py` and re-fill). | +| `story-numbering-gaps` | Story NNs in an epic are not sequential `01..N`. The message lists what was found vs expected. | Use `rename_story.py --to-nn` to fill the gap or renumber the survivors. | + +## Coverage findings + +| Code | Meaning | Fix | +| --- | --- | --- | +| `coverage-missing` | An inventory code from `--inventory ` does not appear textually in any story body. **Warning** by default; **error** under `--coverage-strict`. | Use the `coverage-fix` edit-mode entry point in `prompts/edit-mode.md` — extend an existing story's `## Coverage` section, or add a new story for the missing code. | + +## Sizing warnings + +| Code | Meaning | Fix | +| --- | --- | --- | +| `story-oversized` (warning) | A story's body is more than 3× the epic mean (computed when the epic has ≥ 3 stories). | Often a real signal that the story should be split. Sometimes the threshold is too tight for legitimately-large foundational stories — judgment call. The warning never fails CI. Pass `--lax` to suppress mid-flow. | + +## Validator runtime errors + +These exit `2` and aren't structured findings. + +| Symptom | Cause | Fix | +| --- | --- | --- | +| `template missing: ...` | `resources/epic-md-template.md` or `resources/story-md-template.md` not found. | Reinstall the skill; resources are required. | +| `inventory file not found: ...` | `--inventory ` was passed but the file doesn't exist. | Either the path is wrong, or the inventory cache was deleted. The cache lives at `{initiative_store}/.bmad-cache/inventory.json` and is auto-deleted at finalize — that's expected. | +| `could not parse : ...` | The inventory file isn't valid JSON. | Fix the JSON. | + +## from-spec errors + +`scripts/from_spec.py` validates the spec before doing any work; on failure it returns: + +```json +{"error": "invalid spec", "details": ["epic[0] missing `title`", "..."]} +``` + +Common shapes: + +| Detail | Cause | Fix | +| --- | --- | --- | +| `spec must contain a non-empty epics list` | Top-level `epics` is missing or empty. | Add at least one epic. | +| `epic[N] missing nn` / `missing title` | A required epic field is absent. | Add it. | +| `epic[N].stories[M] missing nn / title / type` | A required story field is absent. | Add it. | +| `epic[N].stories[M].type invalid: '...'` | `type` is not `feature` / `task` / `bug` / `spike`. | Pick a valid type. | + +If the spec is valid but `init_epic.py` or `init_story.py` fails (e.g. a folder collision), the envelope returns: + +```json +{"error": "init_epic.py failed", "details": "epic folder already exists: ...", "epic": "..."} +``` + +Usually means the target initiative store isn't empty. Either clean it (`rm -rf /epics`) or generate into a fresh path. + +## parse_v6_epics.py warnings + +The parser emits warnings in its `warnings[]` array rather than failing. The LLM driving the migrate flow surfaces these to the user for confirmation. + +| Warning shape | Meaning | +| --- | --- | +| `no '## Requirements Inventory' section found` | The v6 file doesn't have the canonical inventory heading. The migrate flow proceeds without populating the requirements block. | +| `no '## Epic N:' headings found; file may not be canonical v6` | The parser couldn't find epic headings. Likely a hand-edited v6 file. Pick option 3 from the migrate menu (walk through manually). | +| `epic N (Title): no stories parsed` | An epic heading was found but no `### Story N.M:` blocks under it. Either the v6 file has stories elsewhere, or the epic is genuinely empty. | +| `epic N story M: no acceptance criteria parsed` | The `**Acceptance Criteria:**` block is missing or formatted differently. The v7 story will have empty ACs; fix in the LLM-driven migrate confirmation step. | +| `input is a directory; sharded v6 input — flatten first` | Sharded v6 detected. The migrate flow offers to flatten before re-parsing. | + +## Common operator scenarios + +### "The skill won't let me edit because it thinks it's create mode" + +Mode detection is filesystem-driven. Either `{initiative_store}/epics/` has no folders matching `NN-*`, or your `{initiative_store}` is pointing at the wrong path. Check `_bmad/config.yaml` and the resolution chain documented in `SKILL.md` Step 4 ("Load Config"). + +### "Validation passes but I know there's a coverage gap" + +You forgot to pass `--inventory`. Without it, the validator only emits `mentioned_requirements` (the textual extraction) and won't compare to anything. The coverage gate requires the inventory file to compare against. + +### "I want to delete an epic and the skill won't let me" + +By design — see `prompts/edit-mode.md` under "Boundaries". Run `rm -rf {initiative_store}/epics/`, then `re-validate` to surface every dep ref that pointed at the deleted epic. + +### "rename_epic.py refuses to renumber" + +Probably the new NN collides with another existing epic. The message says which one. Renumber that epic out of the way first. + +### "I keep getting story-numbering-gaps after a manual delete" + +`rm`'ing a story file leaves a gap (e.g. 01, 02, 04). Fix with `rename_story.py --epic --from --to-nn N` to renumber the survivors into a contiguous sequence. + +### "Validator output is huge — I just want to see one thing" + +For a quick view of the tree shape: `--summary-only` (JSON) or `--tree` (plain text). Both skip the findings list. + +For a single epic's findings: `--epic `. Cross-epic refs still resolve against the whole tree, so a story whose dep references another epic gets reported correctly. + +### "validate_initiative.py exits 0 but the JSON has errors" + +The exit code only counts `error`-level findings. Schema and dep checks are always errors; coverage findings default to warnings unless `--coverage-strict`. If you want exit 1 on warnings too, post-process the JSON: + +```bash +out=$(python3 scripts/validate_initiative.py --initiative-store $S) +warns=$(echo "$out" | jq '.summary.warnings') +[ "$warns" -gt 0 ] && exit 1 || exit 0 +``` diff --git a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/init_story.py b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/init_story.py index bfc847088..1be8b49f1 100644 --- a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/init_story.py +++ b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/init_story.py @@ -90,7 +90,7 @@ def main() -> int: f"title: {yaml_quote(args.title)}\n" f"type: {args.type}\n" "status: draft\n" - f"epic: {yaml_quote(args.epic)}\n" + f"epic: {args.epic}\n" f"depends_on: {deps_yaml}\n" "---\n\n" ) diff --git a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/move_story.py b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/move_story.py index 32fdeb30b..3245abe30 100644 --- a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/move_story.py +++ b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/move_story.py @@ -68,7 +68,7 @@ def main() -> int: return 1 text = src_path.read_text(encoding="utf-8") - text = re.sub(r"^epic:.*$", f"epic: {yaml_quote(args.to_epic)}", text, count=1, flags=re.MULTILINE) + text = re.sub(r"^epic:.*$", f"epic: {args.to_epic}", text, count=1, flags=re.MULTILINE) # The moved story's own depends_on may carry bare basenames that referenced # within-epic siblings in src_epic; those refs now need cross-epic form. new_text_lines: list[str] = [] diff --git a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/parse_v6_epics.py b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/parse_v6_epics.py index 97219cd4d..e3fa19574 100644 --- a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/parse_v6_epics.py +++ b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/parse_v6_epics.py @@ -60,7 +60,7 @@ USER_STORY_RE = re.compile( ) AC_RE = re.compile(r"\*\*Acceptance Criteria\*\*:?\s*\n+", re.IGNORECASE) GIVEN_WHEN_THEN_RE = re.compile(r"^[-*]\s+(?:Given|When|Then|And|But)\b.*$", re.MULTILINE | re.IGNORECASE) -REQUIREMENT_CODE_RE = re.compile(r"\b(?:UX-DR|NFR|FR)\d+(?:\.\d+)?\b") +REQUIREMENT_CODE_RE = re.compile(r"\b(?:UX-DR|NFR|FR|D|R)\d+(?:\.\d+)?\b") REQUIREMENT_LINE_RE = re.compile(r"^[-*]\s+(?:\*\*)?(FR\d+(?:\.\d+)?|NFR\d+(?:\.\d+)?|UX-DR\d+(?:\.\d+)?)(?:\*\*)?:?\s*(.*)$", re.MULTILINE) diff --git a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/rename_epic.py b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/rename_epic.py index bee13b846..25a533a9a 100644 --- a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/rename_epic.py +++ b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/rename_epic.py @@ -102,7 +102,7 @@ def main() -> int: if sf.name == "epic.md": continue t = sf.read_text(encoding="utf-8") - new = re.sub(r"^epic:.*$", f"epic: {yaml_quote(new_folder)}", t, count=1, flags=re.MULTILINE) + new = re.sub(r"^epic:.*$", f"epic: {new_folder}", t, count=1, flags=re.MULTILINE) if new != t: sf.write_text(new, encoding="utf-8") refs_updated += 1 diff --git a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/tests/test_init_story.py b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/tests/test_init_story.py index d3df5da5e..ce75aae7a 100644 --- a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/tests/test_init_story.py +++ b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/tests/test_init_story.py @@ -36,7 +36,8 @@ class TestInitStory(unittest.TestCase): content = Path(json.loads(r.stdout)["path"]).read_text(encoding="utf-8") self.assertIn("As a {{user_type}}", content) self.assertIn("type: feature", content) - self.assertIn(f'epic: "{epic}"', content) + self.assertIn(f"epic: {epic}", content) + self.assertNotIn(f'epic: "{epic}"', content) self.assertIn("status: draft", content) # The user-story marker comments stay so the LLM can locate the block. self.assertIn("USER_STORY_START", content) diff --git a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/tests/test_move_story.py b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/tests/test_move_story.py index c83c33799..41c326dd9 100644 --- a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/tests/test_move_story.py +++ b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/tests/test_move_story.py @@ -35,7 +35,7 @@ class TestMoveStory(unittest.TestCase): data = json.loads(r.stdout) self.assertEqual(data["new"], "02-mig/01-register") moved = (store / "epics" / "02-mig" / "01-register.md").read_text(encoding="utf-8") - self.assertIn('epic: "02-mig"', moved) + self.assertIn("epic: 02-mig", moved) self.assertIn('"01-auth/01-schema"', moved) self.assertNotIn('depends_on: ["01-schema"]', moved) self.assertFalse((store / "epics" / "01-auth" / "02-register.md").exists()) diff --git a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/tests/test_rename_epic.py b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/tests/test_rename_epic.py index fa45f3f35..6416ae666 100644 --- a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/tests/test_rename_epic.py +++ b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/tests/test_rename_epic.py @@ -36,7 +36,7 @@ class TestRenameEpic(unittest.TestCase): self.assertEqual(data["new"], "01-user-authentication") self.assertTrue((store / "epics" / "01-user-authentication" / "epic.md").is_file()) schema = (store / "epics" / "01-user-authentication" / "01-schema.md").read_text(encoding="utf-8") - self.assertIn('epic: "01-user-authentication"', schema) + self.assertIn("epic: 01-user-authentication", schema) mailer = (store / "epics" / "02-migration" / "01-mailer.md").read_text(encoding="utf-8") self.assertIn('"01-user-authentication/01-schema"', mailer) v = _run(VALIDATE, "--initiative-store", str(store)) diff --git a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/tests/test_validate_initiative.py b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/tests/test_validate_initiative.py index c61d2e6aa..1e9d2f24d 100644 --- a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/tests/test_validate_initiative.py +++ b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/tests/test_validate_initiative.py @@ -120,6 +120,33 @@ class TestValidateInitiative(unittest.TestCase): errors = [f for f in data["findings"] if f["code"] == "coverage-missing"] self.assertEqual(errors[0]["level"], "error") + def test_inventory_coverage_recognizes_debt_and_research_codes(self) -> None: + # The validator's mentioned-codes regex must include D (debt) and R (research) + # in addition to FR/NFR/UX-DR. Otherwise tech-debt and research initiatives + # always report spurious coverage-missing on inventory codes the stories + # actually reference. + with tempfile.TemporaryDirectory() as tmp: + store = Path(tmp) + _build_clean_tree(store) + inv = store / "inventory.json" + inv.write_text(json.dumps({ + "requirements": { + "debt": [{"code": "D1", "text": "Remove legacy /charge endpoint"}], + "research": [{"code": "R1", "text": "Investigate webhook ordering"}], + } + }), encoding="utf-8") + schema = store / "epics" / "01-auth" / "01-schema.md" + schema.write_text( + schema.read_text(encoding="utf-8") + "\n## Coverage\n- AC1: D1, R1\n", + encoding="utf-8", + ) + r = _run(VALIDATE, "--initiative-store", str(store), "--inventory", str(inv)) + self.assertEqual(r.returncode, 0, r.stdout + r.stderr) + data = json.loads(r.stdout) + self.assertEqual(data["summary"]["coverage_missing"], []) + self.assertIn("D1", data["summary"]["mentioned_requirements"]) + self.assertIn("R1", data["summary"]["mentioned_requirements"]) + def test_summary_only_emits_per_story_metadata(self) -> None: with tempfile.TemporaryDirectory() as tmp: store = Path(tmp) diff --git a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/validate_initiative.py b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/validate_initiative.py index ea3ba81ed..75f411220 100644 --- a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/validate_initiative.py +++ b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/scripts/validate_initiative.py @@ -48,7 +48,7 @@ STORY_REQUIRED = {"title", "type", "status", "epic", "depends_on"} EPIC_KEYS = {"title", "epic", "status", "depends_on", "metadata"} EPIC_REQUIRED = {"title", "epic", "status", "depends_on"} -REQUIREMENT_CODE_RE = re.compile(r"\b(?:UX-DR|NFR|FR)\d+(?:\.\d+)?\b") +REQUIREMENT_CODE_RE = re.compile(r"\b(?:UX-DR|NFR|FR|D|R)\d+(?:\.\d+)?\b") def parse_frontmatter(text: str) -> tuple[dict | None, str | None]: