From fb28c032fbd1276cebca8b79b47fecc5331ed2fb Mon Sep 17 00:00:00 2001 From: Brian Madison Date: Sun, 11 Oct 2026 22:00:01 -0500 Subject: [PATCH] feat(skills): readers, agents, migration, help and docs name Sofia and the ticket file bmad-build and bmad-build-auto load the active node's ticket file as the capability contract; bmad-architecture adopts the spine into the ticket's References through Sofia; the planning skills' closing offers name her SPEC capability; John's and Amelia's menus drop the items that invoked the retired skills and Sofia joins the roster. bmad migrate renders a v6 spec into the epic file, folds a pre-release spec-/ into its ticket (memlog entries in order, companions beside the ticket, CAP ids mapped to R), turns a loose spec folder into a backlog story, and drops the retired customization keys with a note. Help and docs describe one path: the initiative, epic or story file is the spec at its level, Sofia fills its requirements from what the user has and slices from it. --- docs-site/src/diagrams/planning-skills.svg | 21 +- docs/_STYLE_GUIDE.md | 2 +- docs/build/autonomous-development-loops.md | 6 +- docs/build/build-a-change.md | 12 +- docs/build/finish-an-epic.md | 2 +- docs/existing-codebases/getting-deeper.md | 14 +- .../start-in-an-existing-codebase.md | 2 +- docs/index.md | 6 +- .../break-work-into-stories-and-track-it.md | 14 +- docs/plan/choose-a-planning-path.md | 58 +- ...define-requirements-and-a-specification.md | 107 ++-- docs/plan/design-ux-and-architecture.md | 11 +- docs/plan/explore-and-validate-an-idea.md | 10 +- docs/plan/plan-inside-an-organization.md | 61 +- docs/plan/set-up-the-ticket-tree.md | 33 +- docs/reference/skills-and-agents.md | 24 +- docs/start/install-bmad.md | 2 +- skills/bmad-agent-architect/customize.toml | 5 - skills/bmad-agent-dev/customize.toml | 5 - skills/bmad-agent-pm/customize.toml | 10 - skills/bmad-architecture/SKILL.md | 8 +- .../assets/spine-template.md | 3 +- .../bmad-architecture/references/headless.md | 2 +- .../bmad-brainstorming/references/finalize.md | 2 +- .../step-01-clarify-and-route.md | 2 +- .../bmad-build/step-01-clarify-and-route.md | 2 +- skills/bmad-eval/evals/triggers.json | 2 +- skills/bmad-forge-idea/SKILL.md | 2 +- skills/bmad-prd/SKILL.md | 2 +- skills/bmad-retrospective/workflow.md | 2 +- skills/bmad-ux/SKILL.md | 4 +- skills/bmad/references/initiative.md | 2 +- skills/bmad/references/setup.md | 2 +- skills/bmad/scripts/migrate_v6.py | 588 +++++++++++++++++- .../bmad/scripts/tests/test_render_skill.py | 14 +- .../bmad/scripts/tests/test_stamp_release.py | 2 +- .../scripts/tests/test_validate_manifests.py | 40 +- skills/bmod-method/help/analysis-skills.md | 4 +- skills/bmod-method/help/artifact-lifetime.md | 2 +- skills/bmod-method/help/existing-codebase.md | 2 +- skills/bmod-method/help/help.md | 59 +- .../bmod-method/help/implementation-skills.md | 9 +- skills/bmod-method/help/planning-skills.md | 31 +- skills/bmod-method/help/prototyping.md | 6 +- .../bmod-method/help/ticketing-and-epics.md | 2 +- skills/bmod-method/help/ticketing-setup.md | 16 +- skills/bmod-method/help/unattended-builds.md | 2 +- .../help/working-in-an-organization.md | 19 +- skills/bmod-method/migration-1.toml | 49 +- skills/bmod-method/roster.toml | 8 + tools/tests/test_bmad_setup.py | 44 +- tools/tests/test_migrate_v6.py | 437 ++++++++++++- 52 files changed, 1391 insertions(+), 383 deletions(-) diff --git a/docs-site/src/diagrams/planning-skills.svg b/docs-site/src/diagrams/planning-skills.svg index 9f87abbbd..d83a8bc86 100644 --- a/docs-site/src/diagrams/planning-skills.svg +++ b/docs-site/src/diagrams/planning-skills.svg @@ -1,6 +1,6 @@ Planning skills and what they produce - Three columns of skills and their artifacts. Analysis: bmad-brainstorming, bmad-forge-idea, bmad-deep-recon, bmad-product-brief, bmad-prfaq. Planning: bmad-prd, bmad-ux, bmad-spec. Solutioning: bmad-architecture and bmad-ticket. The columns flow left to right and hand off to bmad-build, one session per unit. Build finishes at built; the user marks done and retains the plan. + Three columns of skills and their artifacts. Analysis: bmad-brainstorming, bmad-forge-idea, bmad-deep-recon, bmad-product-brief, bmad-prfaq. Planning: bmad-prd, bmad-ux. Solutioning: bmad-architecture and bmad-agent-po, Sofia the Product Owner. The columns flow left to right and hand off to bmad-build, one session per unit. Build finishes at built; the user marks done and retains the plan. @@ -9,7 +9,7 @@ PLANNING SKILLS AND WHAT THEY PRODUCE - Pick the ones the gap calls for, in any order. Each lands in its own <type>-<slug>/ folder in the active initiative. Hand the results to bmad-spec. + Pick the ones the gap calls for, in any order. Each lands in its own <type>-<slug>/ folder in the active initiative. Hand the results to Sofia. @@ -61,15 +61,9 @@ bmad-ux ux-<slug>.md, DESIGN.md, EXPERIENCE.md - - - bmad-spec - spec-<slug>.md + companions - bmad-ticket handoff - - Use a spec when needed. + Use a PRD when needed. One session of work goes - straight from the spec to Build. + straight to Build. @@ -84,9 +78,10 @@ architecture-<slug>.md - - bmad-ticket - ordered tickets.toml entries + + bmad-agent-po + Sofia, the Product Owner + Requirements + tickets.toml diff --git a/docs/_STYLE_GUIDE.md b/docs/_STYLE_GUIDE.md index 9b069ef47..a050be9ab 100644 --- a/docs/_STYLE_GUIDE.md +++ b/docs/_STYLE_GUIDE.md @@ -388,7 +388,7 @@ Only for work that benefits from architecture. Clear work can enter implementati ### Can I change my plan later? -Yes. The `bmad-correct-course` workflow handles scope changes mid-implementation. +Yes. Sofia, the Product Owner (`bmad-agent-po`), replans scope changes mid-implementation. **Have a question not answered here?** [Open an issue](...) or ask in [Discord](...). ``` diff --git a/docs/build/autonomous-development-loops.md b/docs/build/autonomous-development-loops.md index 95b52bcad..e6d43c504 100644 --- a/docs/build/autonomous-development-loops.md +++ b/docs/build/autonomous-development-loops.md @@ -56,10 +56,10 @@ Supported intent shapes include: ### Tickets from the Tree Build Auto reads the tree through `{project-root}/_bmad/method/scripts/tickets.py`, -the script [`bmad-ticket`](../plan/break-work-into-stories-and-track-it.md) installs. +the script `bmad setup` installs from Sofia's skill, [`bmad-agent-po`](../plan/break-work-into-stories-and-track-it.md). This page describes the repo store, where a ticket's status lives in its plan. On a tracker store, `tickets.py mark` refuses to run, so move the ticket on -the tracker through `bmad-ticket`. +the tracker through Sofia (`bmad-agent-po`). - A named ticket goes through `tickets.py find`. When `find` fails, for example on a reference that matches no ticket or more than one, the run halts with `ticket not resolved`. - It builds from the entry in `tickets.toml`, its epic file and what that file's References name, and the entry's story file when it has one. It never writes a ticket file and never runs `tickets.py pull`. @@ -145,7 +145,7 @@ The plan frontmatter `status` is the main machine-readable state for orchestrati | `built` | The run finished; nobody has called the ticket done yet | `review` | | `done` | The user or an orchestrator called the ticket done | `done` | | `blocked` | The run cannot safely continue unattended | `in-progress` | -| `dropped` | `bmad-ticket` dropped the ticket, on the user's word | `dropped` | +| `dropped` | Sofia dropped the ticket, on the user's word | `dropped` | Build Auto never moves a ticket to `done`. The user or an orchestrator marks a ticket done with `tickets.py mark done`. A follow-up pass on a diff --git a/docs/build/build-a-change.md b/docs/build/build-a-change.md index 59f151575..c9fe013ae 100644 --- a/docs/build/build-a-change.md +++ b/docs/build/build-a-change.md @@ -195,15 +195,15 @@ decisions may set patterns for later work. Once those patterns are stable, | `bmad-build` | Implement and review one direct intent or ticket with human checkpoints (this page) | Plan + code | | `bmad-build-auto` | Implement and review one ticket unattended for a caller or orchestrator ([Autonomous Development Loops](./autonomous-development-loops.md)) | Plan + code + terminal status | | `bmad-code-review` | Review any code change with several independent reviewers ([Review a Change](./review-a-change.md)) | Findings + applied patches | -| `bmad-correct-course` | Assess the impact of a significant mid-sprint change ([Break Work into Stories and Track It](../plan/break-work-into-stories-and-track-it.md#correct-course)) | Updated plan or re-routing | +| `bmad-agent-po` | Replan after a significant mid-flight change: what it changes, what goes stale, what is re-sliced or dropped ([Break Work into Stories and Track It](../plan/break-work-into-stories-and-track-it.md)) | Updated requirements and tickets | | `bmad-retrospective` | Review a completed epic against the evidence it left behind ([Finish an Epic](./finish-an-epic.md)) | Retro document, action items, acceptance verdict | Clear one-session work enters `bmad-build` directly. Larger work is sliced -into a ticket tree with `bmad-ticket`, from a spec or any other -intent, and each build takes one ticket. `bmad-build-auto` does not orchestrate -those tickets: an AI coding session or another orchestrator, such as bmad-loop, -dispatches one worker per ticket. See -[Autonomous Development Loops](./autonomous-development-loops.md) for the +into a ticket tree by Sofia, the Product Owner (`bmad-agent-po`), from its +requirements or any other intent, and each build takes one ticket. +`bmad-build-auto` does not orchestrate those tickets: an AI coding session or +another orchestrator, such as bmad-loop, dispatches one worker per ticket. +See [Autonomous Development Loops](./autonomous-development-loops.md) for the worker and orchestration contracts. ## Why Does This Take So Long? I Could Plan Mode and Code It in Ten Minutes diff --git a/docs/build/finish-an-epic.md b/docs/build/finish-an-epic.md index d72bdbcbb..2f852e206 100644 --- a/docs/build/finish-an-epic.md +++ b/docs/build/finish-an-epic.md @@ -49,7 +49,7 @@ show. It won't invent a root cause or a pattern the code doesn't back up. ## What It Reads The retrospective works on one epic folder in the ticket tree that -`bmad-ticket` keeps (see +Sofia, the Product Owner (`bmad-agent-po`), keeps (see [Break Work into Stories](../plan/break-work-into-stories-and-track-it.md)): - **`tickets.toml`**: the epic's tickets, in build order. diff --git a/docs/existing-codebases/getting-deeper.md b/docs/existing-codebases/getting-deeper.md index ec43158e1..97e773d0d 100644 --- a/docs/existing-codebases/getting-deeper.md +++ b/docs/existing-codebases/getting-deeper.md @@ -1,13 +1,13 @@ --- title: 'Getting Deeper' -description: Use Build and BMad Spec to extend a command in a specific Django version +description: Use Build and Sofia, the Product Owner, to extend a command in a specific Django version sidebar: order: 3 --- You already know Build from small projects. Here, you will use it in a specific version of Django: first for one bounded command change, then for three related -stories defined by one BMad Spec. The two exercises demonstrate the +stories under one epic. The two exercises demonstrate the [one-session and epic-sized planning paths](../plan/choose-a-planning-path.md). :::note[Prerequisites] @@ -140,14 +140,14 @@ codebase. The next change needs three Build runs. `/bmad-forge-idea` can help you decide what to build. `/bmad-advanced-elicitation` can help you improve a draft. You do not need either here because the requirements are already clear. Send them -straight to BMad Spec: +straight to Sofia, the Product Owner: ```text -/bmad-spec Create a spec named diffsettings-audit and break it into +/bmad-agent-po Create an epic named diffsettings-audit and break it into exactly three stories in this order: filters, redaction, then CI status. Read the current diffsettings implementation, focused tests, and command -documentation before writing the spec. Keep every existing output format +documentation before writing the requirements. Keep every existing output format and the JSON design already approved. Add repeatable --include and --exclude shell-glob filters. Include patterns are OR, and exclusions always win. Add repeatable --redact shell-glob masks that replace current and default values @@ -155,10 +155,10 @@ with [REDACTED] without changing whether a difference exists. Add --fail-on-difference, which exits 1 when differences remain after filtering and 0 otherwise. Each story adds focused tests and updates the existing command documentation. Do not add another Django documentation file or an external -service. Use diffsettings-audit as the spec folder slug. +service. Use diffsettings-audit as the epic's slug. ``` -BMad Spec first has you pick or create an initiative, then writes `_bmad-output/initiative-/spec-diffsettings-audit/spec-diffsettings-audit.md`. Hand it to `bmad-ticket` to create `epic-diffsettings-audit` and its three ordered entries in `tickets.toml`. Read the spec and entries and answer any questions. Continue when they match the requirements above; note the epic folder the skill creates. +Sofia first has you pick or create an initiative, then writes `_bmad-output/initiative-/epic-diffsettings-audit/epic-diffsettings-audit.md` with its Requirements, and its three ordered entries in `tickets.toml` beside it. Read the epic file and entries and answer any questions. Continue when they match the requirements above; note the epic folder she creates. ## 9. Build the Three Stories diff --git a/docs/existing-codebases/start-in-an-existing-codebase.md b/docs/existing-codebases/start-in-an-existing-codebase.md index f8657832a..6abd31086 100644 --- a/docs/existing-codebases/start-in-an-existing-codebase.md +++ b/docs/existing-codebases/start-in-an-existing-codebase.md @@ -17,7 +17,7 @@ ordinary change — an agent doing a small request should not even be able to find it by accident. For a small change, use `[bmad-build](../build/build-a-change.md)`. -For one that needs several coding sessions, run `bmad-spec`, plan its entries with `bmad-ticket`, and Build each entry directly. Then run `bmad-retrospective` on the epic. Keep its joined plans as live status and evidence. If it is bigger than that, treat it as a project and follow [Choose a Planning Path](../plan/choose-a-planning-path.md). +For one that needs several coding sessions, have Sofia, the Product Owner (`bmad-agent-po`), write the epic's requirements and slice its entries, and Build each entry directly. Then run `bmad-retrospective` on the epic. Keep its joined plans as live status and evidence. If it is bigger than that, treat it as a project and follow [Choose a Planning Path](../plan/choose-a-planning-path.md). Too little planning costs one Build run: Build looks at the code first, and stops to ask when it cannot settle the intent. Too much diff --git a/docs/index.md b/docs/index.md index e021ee8c7..db8540bfb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -46,9 +46,9 @@ Consider running `bmad-project-context`, then build as usual. See [Set and Maintain Project Context](./existing-codebases/set-and-maintain-project-context.md). **You are building a larger feature or a whole product.** -If you can give `bmad-spec` a complete intent, start there. If you need to -go through the ideation/planning paces first, choose a path in -[Choose a Planning Path](./plan/choose-a-planning-path.md). +If you can give Sofia, the Product Owner (`bmad-agent-po`), a complete intent, +start there. If you need to go through the ideation/planning paces first, +choose a path in [Choose a Planning Path](./plan/choose-a-planning-path.md). **Your idea is still vague, or you are not sure it is a good one.** Generate options or [pressure-test the idea](./plan/explore-and-validate-an-idea.md), diff --git a/docs/plan/break-work-into-stories-and-track-it.md b/docs/plan/break-work-into-stories-and-track-it.md index 08478aae4..ec7f6c506 100644 --- a/docs/plan/break-work-into-stories-and-track-it.md +++ b/docs/plan/break-work-into-stories-and-track-it.md @@ -1,15 +1,15 @@ --- title: 'Break Work into Stories and Track It' -description: Turn intent, a spec, or a PRD into ticket entries, build them directly, and track progress in joined plans. +description: Turn intent or a PRD into ticket entries, build them directly, and track progress in joined plans. sidebar: order: 7 --- -Use `bmad-ticket` to split and track work. It accepts described intent, a spec, or a PRD. One small story or bug can go straight to [Build](../build/build-a-change.md) without ticketing. +Ask Sofia, the Product Owner (`bmad-agent-po`), to split and track work. She accepts described intent or a PRD, writes the requirements into the initiative or epic file, and slices from them. One small story or bug can go straight to [Build](../build/build-a-change.md) without ticketing. ## Plan the Work -For several epics, create an initiative and ask the skill to slice it. Each epic gets an envelope with requirements and done-when checks. Incept one epic to propose its stories and bugs in build order, with requirement coverage, dependencies, and verification. Review the breakdown before accepting it. +For several epics, create an initiative and ask Sofia to slice it. Each epic gets an envelope with requirements and done-when checks. Incept one epic to propose its stories and bugs in build order, with requirement coverage, dependencies, and verification. Review the breakdown before accepting it. The initiative's `tickets.toml` lists epics. Each epic's `tickets.toml` lists entries with stable ids. A planned entry needs no story file. Standalone tracked stories and bugs have files in `backlog/`; they do not need an invented epic. @@ -25,14 +25,14 @@ For unattended work, explicitly dispatch a ticket to `bmad-build-auto`, one invo ## Track Progress -Ask `bmad-ticket` “what's next?” or “show status.” Plans carry build progress. A build finishes at `built`, shown in the review column; the user or orchestrator decides when to mark it `done`. A tracker card's status remains separate from build status. +Ask Sofia “what's next?” or “show status.” Plans carry build progress. A build finishes at `built`, shown in the review column; the user or orchestrator decides when to mark it `done`. A tracker card's status remains separate from build status. Keep completed plans. Deleting one removes the state and evidence later builds, review, and retrospective read. ## Review and Close -`bmad-code-review` reads a ticket's plan and baseline and appends a dated `Code Review` block. It never changes ticket status. When the epic is finished, run [Retrospective](../build/finish-an-epic.md) with its folder, id, or slug. Retrospective writes its evidence and verdict directly in the epic folder; `bmad-ticket` handles confirmed closure. +`bmad-code-review` reads a ticket's plan and baseline and appends a dated `Code Review` block. It never changes ticket status. When the epic is finished, run [Retrospective](../build/finish-an-epic.md) with its folder, id, or slug. Retrospective writes its evidence and verdict directly in the epic folder; Sofia handles confirmed closure. -## Correct Course +## Replan -Run `bmad-correct-course` when a requirement, architecture choice, or dependency changes significantly. It requires a PRD and your description of the affected work and dependencies. For standalone spec work without a PRD, update the spec with `bmad-spec` instead. Correct-course assesses the available planning documents and writes its proposal as `change-/change-.md` in the active initiative's folder, or in the output folder when none is active, with the edits and a `bmad-ticket` handoff. It does not read or edit the ticket tree. Apply the approved changes through the owning skills, then use `bmad-ticket` to revise the remaining breakdown. +Tell Sofia when a requirement, architecture choice, or dependency changes significantly: “this changed, what now.” She names the change and the ticket it lands on, reads that ticket, its memlog, its breakdown, and the finished children, and says whether what is required changed or only how it is delivered. When what is required changed, she updates the ticket's requirements first, keeping ids stable and naming the entries that went stale. Then she re-slices the affected epics, drops what no longer belongs, and records the reason as a dated `Decision:` line in each container's Notes. There is no proposal file, and nothing already built is rewritten. diff --git a/docs/plan/choose-a-planning-path.md b/docs/plan/choose-a-planning-path.md index 4f63a49c7..7a6995155 100644 --- a/docs/plan/choose-a-planning-path.md +++ b/docs/plan/choose-a-planning-path.md @@ -6,8 +6,9 @@ sidebar: --- Use this page to decide how much planning a change needs. The answer turns on -one question: is the intent already well defined? If it is, feed it to -`bmad-spec`, which shapes it to the size of the work, and build. If it is not, +one question: is the intent already well defined? If it is, give it to Sofia, +the Product Owner (`bmad-agent-po`), who writes the ticket's requirements at +the size of the work, and build. If it is not, the other pages in this chapter are how you get a defined intent. If the work belongs to an organization, with a PRD other people must approve and several engineers building in parallel, read @@ -22,18 +23,18 @@ could build it without guessing, and no longer than that. Where it came from does not matter: a sentence, an issue, a forged idea, a research report, a PRD. -Keep the input short. `bmad-spec` reads everything you give it in one pass, +Keep the input short. Sofia reads everything you give her in one pass, and the practical ceiling is a few tens of thousands of tokens, roughly a -40-page document. Hand it a pile of raw documents several times that size and -it silently loses the parts that mattered; condense them first. If the spec +40-page document. Hand her a pile of raw documents several times that size and +she silently loses the parts that mattered; condense them first. If Sofia says the input is too thin, you are not done on this chapter yet. -- **Well-defined intent**: run `bmad-spec` with it. A spec that fits one Build - session goes straight to `bmad-build`; an epic-sized one goes to - `bmad-ticket` for stories, then a Build per story. See +- **Well-defined intent**: give it to Sofia. Work that fits one Build + session goes straight to `bmad-build`; for epic-sized work she fills the + epic's requirements, then slices it into stories, then a Build per story. See [Define Requirements and a Specification](./define-requirements-and-a-specification.md). - **Anything else**: the intent is not ready yet. Use the pages below until it - is, then run `bmad-spec`. The spec skill writes the contract; it does not + is, then give it to Sofia. She writes the requirements; she does not help you figure out what you want. If the change fits one implementation session, you are on the Build page's @@ -49,7 +50,7 @@ for an obvious, low-risk edit. These are independent tools, not stages. Pick the ones the gap calls for, in any order. None of them build anything. Condense what they produce and hand -`bmad-spec` the result, not the raw pile. +Sofia the result, not the raw pile. | The intent is missing | Do this | | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | @@ -61,8 +62,9 @@ any order. None of them build anything. Condense what they produce and hand A short list of decisions is often enough on its own. You need a PRD when more than one person must agree on what the product is, or more than one epic must -not diverge; otherwise skip it. A multi-epic product runs `bmad-spec` once per -epic with those documents as sources. +not diverge; otherwise skip it. A multi-epic product gives those documents to +Sofia once, at the initiative; each epic's requirements are cut from the +initiative's lines. ## Planning Skills and What They Produce @@ -83,15 +85,14 @@ is active. | `bmad-prfaq` | Stress-test a product concept customer-first, working backwards from the press release | `prfaq-.md` + a distillate | | `bmad-prd` | Create, update, or validate a PRD | Create/update: `prd-.md`, `addendum.md`, `.memlog.md`; validate: HTML + `.md` report | | `bmad-ux` | Record how the product looks and behaves ([Design UX and Architecture](./design-ux-and-architecture.md)) | `DESIGN.md`, `EXPERIENCE.md`, `ux-.md`, `.memlog.md` | -| `bmad-spec` | Condense any intent into a short contract; hand it to `bmad-ticket` for stories on request | `spec-.md` + companions | | `bmad-architecture` | Make the technical decisions that keep separately built parts consistent | `architecture-.md` by default | -| `bmad-ticket` | [Plan and track entries](./break-work-into-stories-and-track-it.md) | Epic envelopes, ordered `tickets.toml`, and optional leaf files | +| `bmad-agent-po` | Sofia, the Product Owner: write a ticket's requirements from any input, then [plan and track entries](./break-work-into-stories-and-track-it.md) | Requirements and Constraints in the initiative, epic, or story file, `.memlog.md` beside it, ordered `tickets.toml`, and optional leaf files | `bmad-prd` has three intents, create, update, and validate; say which one you want when you invoke it, or it will ask. `bmad-product-brief` feeds `bmad-prd`, which reads the brief during discovery, but neither requires the other. -![Three columns of planning skills and the files each writes: analysis (brainstorming, forge idea, deep recon, product brief, PRFAQ), planning (PRD, UX, spec), and solutioning (architecture and ticket), all handing off to bmad-build, one session per unit](/diagrams/planning-skills.svg) +![Three columns of planning skills and the files each writes: analysis (brainstorming, forge idea, deep recon, product brief, PRFAQ), planning (PRD, UX), and solutioning (architecture and Sofia, the Product Owner), all handing off to bmad-build, one session per unit](/diagrams/planning-skills.svg) ## Size Follows the Intent @@ -117,11 +118,12 @@ coherent outcome. **Define and divide the epic** -1. Run `bmad-spec` with the epic intent. See +1. Give Sofia the epic intent. She fills the epic's requirements from what + you have, then slices. See [Define Requirements and a Specification](./define-requirements-and-a-specification.md) - for what a spec contains and when it is enough on its own. -2. Run `bmad-ticket` with the spec folder. It plans the epic with - you and records the stories in build order in the epic's `tickets.toml`. + for what the requirements hold and when they are enough on their own. +2. She plans the epic with you and records the stories in build order in the + epic's `tickets.toml`. 3. Review the proposed order and decide which stories need a checkpoint. 4. Build each story from its entry when you are ready; no story file is needed. Refine a story first only when its entry says `refine = true`, the @@ -129,8 +131,9 @@ coherent outcome. build. The breakdown is an execution plan, not a promise that nothing will change. -Update the spec and re-slice the remaining stories when earlier work reveals a -missing constraint, a better division, or a conflict between stories. +Tell Sofia what changed, and she updates the requirements and re-slices the +remaining stories, when earlier work reveals a missing constraint, a better +division, or a conflict between stories. **Establish the implementation pattern** @@ -141,7 +144,7 @@ before automating repetitions of them. Run Build once per story, naming the story. Build writes its plan beside the epic's `tickets.toml` and leaves the plan at `built` until you mark it done -through `bmad-ticket`. To run stories unattended instead, give +through Sofia. To run stories unattended instead, give `bmad-build-auto` the story as its intent, one run per story; see [Autonomous Development Loops](../build/autonomous-development-loops.md). @@ -157,7 +160,8 @@ work likely to need roughly 20 or more implementation sessions. Prepare only the planning the project actually needs from the table above ([Plan Inside an Organization](./plan-inside-an-organization.md) covers who -owns which document and where sign-off happens). Then run `bmad-spec` per epic, +owns which document and where sign-off happens). Then give the documents to +Sofia, who fills the initiative's requirements and slices it into epics, track the stories with [Break Work into Stories and Track It](./break-work-into-stories-and-track-it.md), and close each epic with [Finish an Epic](../build/finish-an-epic.md). @@ -171,7 +175,7 @@ boundary. Dividing work can lose information: a requirement weakens, a constraint disappears, or two correct stories fail when combined. The PRD, architecture, -and specs exist so later sessions can still see the whole. +and each ticket's requirements exist so later sessions can still see the whole. ## After Decisions Stabilize @@ -182,6 +186,6 @@ implementation decisions are stable. For the worker contract, see ## What You Get -A path sized to the work: a spec and stories for an epic, or shared product -documents plus one spec per epic for a project — each still implemented one -Build session at a time. +A path sized to the work: an epic with its requirements and stories, or shared +product documents plus an initiative sliced into epics for a project — each +still implemented one Build session at a time. diff --git a/docs/plan/define-requirements-and-a-specification.md b/docs/plan/define-requirements-and-a-specification.md index 75a860653..ca7456c34 100644 --- a/docs/plan/define-requirements-and-a-specification.md +++ b/docs/plan/define-requirements-and-a-specification.md @@ -1,25 +1,27 @@ --- title: 'Define Requirements and a Specification' -description: Choose between a succinct spec and the full product-planning path — product brief or PRFAQ, then a PRD — and know what each produces. +description: Choose between writing a ticket's requirements with Sofia and the full product-planning path — product brief or PRFAQ, then a PRD — and know what each produces. sidebar: order: 5 --- -Use this page to pick the requirements skill for the work in front of you: a -product brief or a PRFAQ, then a PRD, for a product; or `bmad-spec` for the -contract implementation reads. +Use this page to pick the requirements path for the work in front of you: a +product brief or a PRFAQ, then a PRD, for a product; or Sofia, the Product +Owner (`bmad-agent-po`), to write the ticket's requirements that +implementation reads. ## Which Path Applies [Choose a Planning Path](./choose-a-planning-path.md) decides the size. Most -work goes straight to `bmad-spec` from whatever defined the intent: a forged -idea, a PRFAQ summary, a brainstorm intent, an issue. A PRD becomes necessary -when several people must agree on what the product is or several epics must -stay aligned; [Plan Inside an Organization](./plan-inside-an-organization.md) +work goes straight to Sofia from whatever defined the intent: a forged idea, +a PRFAQ summary, a brainstorm intent, an issue. A PRD becomes necessary when +several people must agree on what the product is or several epics must stay +aligned; [Plan Inside an Organization](./plan-inside-an-organization.md) covers that setting. This page covers what each document is for and which -skill writes it. None of them replaces the spec: every epic still ends up as a -`spec-.md` that Build reads, and when there is a PRD, that is where the spec's -answers come from. +skill writes it. None of them replaces the ticket's requirements: the +initiative, epic, or story file is the specification at its level, its +Requirements section is what Build reads, and when there is a PRD, that is +where those lines come from. The brief, PRD, and UX skills steer the same way. Each opens with a brain dump: tell it everything and point it at any files you have. Then you choose a @@ -60,11 +62,11 @@ brainstorming or Forge Idea instead of forcing it. Use it when you want the concept stress-tested before committing resources. If you cannot write a compelling press release, the product is not ready. It -produces the PRFAQ document plus a short summary a PRD or spec can read, and +produces the PRFAQ document plus a short summary a PRD or Sofia can read, and it accepts `-H` for an unattended first draft when you supply the customer, problem, stakes, and concept up front. -Brief and PRFAQ both feed a PRD, and the PRFAQ summary can feed `bmad-spec` +Brief and PRFAQ both feed a PRD, and the PRFAQ summary can feed Sofia directly when no PRD is needed. Choose by how much challenge you want: the brief is collaborative discovery, the PRFAQ is the harder path. Neither is required; `bmad-prd` starts from a brain dump on its own. @@ -94,49 +96,63 @@ divide work into stories; that is If you open it with a one-pager in mind or an idea to vet, it points you at the brief or the PRFAQ instead. -## Spec: The Contract Implementation Reads +## Requirements: The Contract Implementation Reads -Run `bmad-spec` to turn an intent into a short contract that Build reads. -`spec-.md` has five fields: Why, Capabilities (each with an intent and a -success condition), Constraints, Non-goals, and Success signal. Tables, -diagrams, glossaries, and documents other skills already wrote sit beside it; -the spec points at them rather than copying them. +Point Sofia at what you have: "distill this PRD", "write the requirements +from these notes", or `/bmad-agent-po `. She writes the Requirements +and Constraints sections of the ticket's own file, the initiative, epic, or +story that is active or that you name. Each Requirements line says what is +possible and what shows it was met, under a stable `R` id that names its +source: `R1 (FR-2, FR-5)` on an initiative, `R1 (parent R3)` on an epic. The +input's non-goals go to Boundaries and its open questions to Notes. Tables, +diagrams, glossaries, and documents other skills already wrote sit beside +the ticket or stay where they are; the ticket names them in References +rather than copying them. -The spec writes the contract; it does not help you figure out what you want. -Rich input is extracted with no questions. Sparse input gets a choice: a -best-effort draft where every gap becomes an open question, or a guided walk -through the five fields. Input too thin to use ("an app for hikers") is sent -to `bmad-prd`. Input too large is the other failure: a few tens of thousands -of tokens is the practical ceiling, so condense a pile of material first. +Sofia writes the requirements; she does not help you figure out what you +want. Rich input is extracted with no questions. Sparse input gets a choice: +a best-effort draft where every gap becomes an open question, or a guided +walk through the sections. Input too thin to use ("an app for hikers") is +sent to `bmad-prd`. Input too large is the other failure: a few tens of +thousands of tokens is the practical ceiling, so condense a pile of material +first. -`bmad-spec` is the only writer of the spec. Do not hand-edit it; run the -skill again with the change and it updates the spec in place, keeping -capability IDs stable. The PRD, UX, and architecture skills can run in any -order and feed the same spec. After every run it reports assumptions it made -and open questions it could not answer, for you to resolve. +After every run she reports the ticket's path, the requirement count, the +companions, and the verdict of her own check, then lists the `Assumption:` +and `Open question:` lines for you to walk through. Resolving one can update +the source input, the ticket, or both; say which. Read the `R` lines +yourself: a line you would not defend to a stakeholder is one to change now, +before anything is cut from it. -`bmad-spec` does not split a spec into stories. When a spec it writes reads as -several slices, it offers once to hand off to `bmad-ticket`. To -split a spec at any time, run `bmad-ticket` with the spec folder. -It plans one epic with you in build order and records the stories in the -epic's `tickets.toml`. Each story cites the spec's capability IDs. A constraint or design decision that comes up while slicing -goes back into the spec as an update. See +Sofia is the only writer of the Requirements and Constraints sections. Do not +hand-edit them; she derives them from `.memlog.md` beside the ticket, so a +hand edit is overwritten on the next run. When the input changes, run her +again with the change and she updates the sections in place, keeps every `R` +id stable, and names the entries whose `covers` cite a changed id. The PRD, +UX, and architecture skills can run in any order and feed the same ticket. + +When the ticket's lines read as several slices, she offers once to slice. To +split at any time, say "slice this initiative", which cuts it into epics, or +"incept this epic", which plans one epic with you in build order and records +the stories in the epic's `tickets.toml`, each citing the epic's `R` ids. A +constraint or design decision that comes up while slicing goes back into the +ticket's requirements as an update. See [Choose a Planning Path](./choose-a-planning-path.md#1-start-epic-sized-work) for how the epic then runs. -`bmad-spec` delegates story breakdown to `bmad-ticket`. To run -the planned stories unattended, explicitly dispatch each ticket to +To run the planned stories unattended, explicitly dispatch each ticket to [`bmad-build-auto`](../build/autonomous-development-loops.md) as `ticket `, one run per ticket. No leaf file needs to be pulled first. :::note[What each skill produces] `bmad-product-brief`: `brief-.md` and `addendum.md`. `bmad-prfaq`: a -PRFAQ document with a short summary for the PRD or spec. `bmad-prd`: -`prd-.md` and `addendum.md`, or a validation report. `bmad-spec`: -`spec-.md` plus supporting files. Each lands in its own `-/` -folder in the active initiative's folder, or in the output folder when no -initiative is active. Exact paths and options belong to each -skill; see +PRFAQ document with a short summary for the PRD or for Sofia. `bmad-prd`: +`prd-.md` and `addendum.md`, or a validation report. Each document +lands in its own `-/` folder in the active initiative's folder, +or in the output folder when no initiative is active. Sofia writes the +Requirements and Constraints sections of the initiative, epic, or story file, +with `.memlog.md` and any companions beside it. Exact paths and options +belong to each skill; see [Planning Skills and What They Produce](./choose-a-planning-path.md#planning-skills-and-what-they-produce). ::: @@ -144,6 +160,5 @@ skill; see With a PRD in hand for multi-epic work, decide whether the work needs shared design decisions: [Design UX and Architecture](./design-ux-and-architecture.md). -With a spec in hand for one epic, run `bmad-ticket` with the spec -folder and go to +With an epic's requirements written, ask Sofia to slice it and go to [Break Work into Stories and Track It](./break-work-into-stories-and-track-it.md). diff --git a/docs/plan/design-ux-and-architecture.md b/docs/plan/design-ux-and-architecture.md index acd7911f6..bf4dfdc4e 100644 --- a/docs/plan/design-ux-and-architecture.md +++ b/docs/plan/design-ux-and-architecture.md @@ -48,10 +48,10 @@ shape are starting points; the code owns them once they exist. One test decides what belongs. If two units built this independently, could they choose incompatibly? A decision goes in the spine only when the answer is yes, the call is non-obvious, and it is a real trade-off. Everything else -is left to the code. Each decision gets a stable ID so specs and stories can +is left to the code. Each decision gets a stable ID so tickets and stories can cite it. -The skill works from whatever you have: a spec, a raw idea, a long +The skill works from whatever you have: a ticket's requirements, a raw idea, a long architecture document to shorten, or an existing codebase, where it reads the real code and records the conventions already there. Coaching is the default: the important calls are shown with the alternatives weighed, then you choose. @@ -61,7 +61,8 @@ already decides a lot of the architecture. Point it at the whole system or at one epic; an epic spine inherits the parent's decisions and records only what the parent left open. When it -finishes, it offers to attach itself to the spec, which is how Build and the +finishes, it offers to add the spine to the ticket's References through +Sofia, the Product Owner (`bmad-agent-po`), which is how Build and the readiness gate find it. Seed [project context](../existing-codebases/set-and-maintain-project-context.md) from it so every later skill reads the same rules. @@ -70,7 +71,7 @@ reads the same rules. Deciding the API style "as we go", documenting every minor choice, and a spine written once and never updated are the three ways this step fails. Document decisions that cross epic boundaries, keep the spine current as you learn, and -run `bmad-correct-course` for a significant mid-implementation change. +tell Sofia when a significant mid-implementation change arrives. ::: ## UX Design @@ -93,7 +94,7 @@ changes to an existing UI that already has established patterns. ## What Comes Next -The spine and the UX documents become input to the spec and to +The spine and the UX documents become References of the ticket and input to [Break Work into Stories and Track It](./break-work-into-stories-and-track-it.md), where the readiness gate checks that stories do not depend on decisions nothing records. diff --git a/docs/plan/explore-and-validate-an-idea.md b/docs/plan/explore-and-validate-an-idea.md index 33ba5e4e1..583a5edf1 100644 --- a/docs/plan/explore-and-validate-an-idea.md +++ b/docs/plan/explore-and-validate-an-idea.md @@ -48,8 +48,8 @@ wrap. Say when you want to narrow and it switches to prioritizing and deciding. Sessions can be paused and resumed. You get an HTML record of the session, and a short `brainstorm-.md` -holding only the chosen discoveries, shaped to feed `bmad-spec`, -`bmad-product-brief`, or `bmad-prd`. +holding only the chosen discoveries, shaped to feed Sofia, the Product Owner +(`bmad-agent-po`), `bmad-product-brief`, or `bmad-prd`. ## Pressure-Test an Idea with Forge Idea @@ -98,7 +98,7 @@ and each is a valid result: | **Killed** | The idea does not hold up. The report records why. | | **Clearer** | You understand the idea better, but there is nothing to hand off. The session log is the record. | -`forge-.md` should be short enough to feed `bmad-spec`, `bmad-prd`, or +`forge-.md` should be short enough to feed Sofia, `bmad-prd`, or `bmad-prfaq`. If it reads like a document, it is too long. :::note[Example] @@ -121,8 +121,8 @@ bet you could test with a plain email and no model at all. Point `bmad-advanced-elicitation` at a recent piece of output (a section, a plan, a draft, a decision) and it offers a short menu of critique methods, such as pre-mortem, first principles, or red team, runs the ones you pick, and -shows proposed changes for you to apply or reject. The brief, PRD, UX, and -spec skills offer it at their own pauses. See +shows proposed changes for you to apply or reject. The brief, PRD, and UX +skills offer it at their own pauses. See [`bmad-advanced-elicitation`](../reference/skills-and-agents.md#bmad-advanced-elicitation). ## What Comes Next diff --git a/docs/plan/plan-inside-an-organization.md b/docs/plan/plan-inside-an-organization.md index e08a83832..39c0a1339 100644 --- a/docs/plan/plan-inside-an-organization.md +++ b/docs/plan/plan-inside-an-organization.md @@ -15,9 +15,10 @@ the skills second. A single builder, or a small team that already agrees, does not need most of what follows. A forged idea, a PRFAQ summary, a brainstorm intent, or a -well-written issue goes straight to `bmad-spec`, and the rest is one spec per -epic. [Choose a Planning Path](./choose-a-planning-path.md) covers that route. -`bmad-spec` will tell you if the input is too thin; until it does, no PRD is +well-written issue goes straight to Sofia, the Product Owner +(`bmad-agent-po`), who writes the requirements into the ticket and slices it. +[Choose a Planning Path](./choose-a-planning-path.md) covers that route. +Sofia will tell you if the input is too thin; until she does, no PRD is required. Reach for the full path when one of these is true: @@ -39,12 +40,16 @@ is derived from it: - `bmad-ux` writes `DESIGN.md` and `EXPERIENCE.md` in its own `ux-/` folder, as input beside the PRD. - `bmad-architecture` writes a short architecture document (the spine): the decisions that keep independently built epics compatible. -- `bmad-spec` writes one spec per epic from the PRD, pointing at the spine and - the UX documents rather than copying them. -- `bmad-ticket` turns the specs into ordered entries and tracks their joined plans. +- Sofia writes the initiative's requirements from the PRD and each epic's + from the initiative's lines, pointing at the spine and the UX documents + rather than copying them. +- Sofia slices them into ordered entries and tracks their joined plans. -Nothing downstream reinterprets the PRD. If a spec needs an answer the PRD -does not give, the answer goes into the PRD first; the spec is re-run after. +Nothing downstream reinterprets the PRD. If a ticket's requirements need an +answer the PRD does not give, the answer goes into the PRD first; Sofia is +re-run after. When the PRD and a ticket's Requirements disagree, the ticket's +Requirements win and the PRD is a source; Sofia surfaces the contradiction +and you decide. ## Bring the Documents You Have @@ -67,15 +72,15 @@ Linear, and a review cadence, and all of it stays. skill reads the code and records the conventions already there rather than proposing new ones. - **Your tracker stays your tracker.** Jira remains where the organization - plans, reports, and reviews. `bmad-ticket` publishes leaf files when needed; build status lives in local joined plans. Tracker status is mirrored separately and never makes a build skip work. + plans, reports, and reviews. Sofia publishes leaf files when needed; build status lives in local joined plans. Tracker status is mirrored separately and never makes a build skip work. - **Your reviews stay your reviews.** The five sign-off moments below are where the skills produce something reviewable. Put your existing approvals at those points and the documents the skills write become the material those meetings already needed. The one thing that does change is where an edit goes: in the PRD the skills -read, not in a spec or a story, so that every derived document can be -regenerated from it. +read, not in a ticket's requirements or a story, so that every derived +document can be regenerated from it. ## Who Owns What @@ -84,8 +89,8 @@ regenerated from it. | Product manager | Brainstorming, Forge Idea, Deep Recon, then `bmad-product-brief` or `bmad-prfaq`, then `bmad-prd` | The PRD and its update cycle; the one-pager the steering committee reads | | Designer | `bmad-ux` | `DESIGN.md`, `EXPERIENCE.md` | | Tech lead or architect | `bmad-architecture` | The architecture spine | -| One engineer, per epic | `bmad-spec`, `bmad-ticket`, Build per story, `bmad-retrospective` | That epic: its spec, its `tickets.toml`, its verdict | -| Whoever tracks the whole | `bmad-ticket` | The ticket tree and plan statuses | +| One engineer, per epic | Sofia (`bmad-agent-po`), Build per story, `bmad-retrospective` | That epic: its file, its `tickets.toml`, its verdict | +| Whoever tracks the whole | Sofia (`bmad-agent-po`) | The ticket tree and plan statuses | The rows are roles, not headcount. One person can hold several; what matters is that each document has exactly one owner, because each has exactly one @@ -103,7 +108,7 @@ gates. | ------------------------- | --------------------------------------------------------------------- | ---------------------------- | | PRFAQ verdict | Whether the concept is strong enough to resource | Writing the PRD | | PRD validate | A findings report on the PRD without changing it | Design and architecture work | -| Architecture spine review | The decisions every epic will follow, with alternatives weighed | Writing specs for the epics | +| Architecture spine review | The decisions every epic will follow, with alternatives weighed | Writing the epics' requirements | | Ticket-breakdown approval | The entries, requirement coverage, and validated dependencies | Starting the agreed work | | Retrospective verdict | Did the epic meet its own acceptance criteria | Starting the next epic | @@ -113,17 +118,18 @@ trail. PRFAQ and Retrospective can run unattended (`-H`) when you want the same check without a conversation. :::note[Reviewers read copies; change the source] -People will review the PRD, the spine, and a spec, and they will ask for -changes in whichever one they happen to be reading. Apply the change where it -belongs: to the PRD if it is about what the product is, to the spine if it is -about how epics stay compatible, to the spec only if it is about that epic -alone. Then re-run the later skills. Editing a spec to work around a PRD that -no longer says the right thing is how the documents stop agreeing. +People will review the PRD, the spine, and an epic's ticket, and they will ask +for changes in whichever one they happen to be reading. Apply the change where +it belongs: to the PRD if it is about what the product is, to the spine if it +is about how epics stay compatible, to the epic's requirements only if it is +about that epic alone. Then re-run the later skills. Editing an epic's +requirements to work around a PRD that no longer says the right thing is how +the documents stop agreeing. ::: ## Several Epics at Once -One PRD, one spine, one spec per epic. Several engineers can each take an +One PRD, one spine, one ticket per epic. Several engineers can each take an epic at the same time when the boundaries are explicit; the spine is what makes that safe, because it records the calls two people would otherwise make differently: the API style, how state is changed, who owns shared data. Each @@ -139,14 +145,11 @@ They will. The path for a change is the same as the path for the original: 1. Run `bmad-prd` in **Update** mode with the change signal. It surfaces conflicts with earlier decisions before applying anything. 2. If the change touches a cross-epic decision, update the spine. -3. Re-run `bmad-spec` for each affected epic. It updates the spec in place - and keeps capability IDs stable, so stories that are unaffected stay - unaffected. -4. For the affected epics, re-slice the stories `bmad-spec` names as no longer - matching, then revise the remaining breakdown with `bmad-ticket`. Keep historical plans and completed states. - -For a change large enough to threaten the plan itself, run -`bmad-correct-course` before touching documents. +3. Tell Sofia what changed. She updates the affected tickets' requirements in + place and keeps requirement ids stable, so stories that are unaffected + stay unaffected, and names the entries that went stale. +4. For the affected epics, she re-slices those entries and revises the + remaining breakdown. Keep historical plans and completed states. ## What You Get diff --git a/docs/plan/set-up-the-ticket-tree.md b/docs/plan/set-up-the-ticket-tree.md index 6de1887da..288a6ef02 100644 --- a/docs/plan/set-up-the-ticket-tree.md +++ b/docs/plan/set-up-the-ticket-tree.md @@ -1,21 +1,21 @@ --- title: 'Set Up the Ticket Tree' -description: Set up an initiative store, choose where tickets are tracked, and plan and track work with bmad-ticket. +description: Set up an initiative store, choose where tickets are tracked, and plan and track work with Sofia, the Product Owner. sidebar: order: 8 --- -`bmad-ticket` is how BMad plans and tracks work: it breaks work into epics and stories in the shared ticket tree. Build, Build Auto, code review, and retrospective consume that tree. +Sofia, the Product Owner (`bmad-agent-po`), is how BMad plans and tracks work: she writes each ticket's requirements and breaks work into epics and stories in the shared ticket tree. Build, Build Auto, code review, and retrospective consume that tree. ## Install the Skills Install with the skills CLI. Run this in your project: ```bash -npx skills add bmad-code-org/BMAD-METHOD --skill bmad --skill bmod-core-tools --skill bmod-method --skill bmad-ticket +npx skills add bmad-code-org/BMAD-METHOD --skill bmad --skill bmod-core-tools --skill bmod-method --skill bmad-agent-po ``` -Add `--skill bmad-build` and any other skill you want in the same command. Then open your AI tool in the project, ask the `bmad` skill to run `bmad setup`, and check that the tool lists `bmad-ticket`. Update later by asking `bmad` to run `bmad setup` again. +Add `--skill bmad-build` and any other skill you want in the same command. Then open your AI tool in the project, ask the `bmad` skill to run `bmad setup`, and check that the tool lists `bmad-agent-po`. Update later by asking `bmad` to run `bmad setup` again. :::note[Prerequisites] You need Node.js with npm, Git, and [uv](https://docs.astral.sh/uv/). BMad setup and the ticketing scripts run through `uv`. @@ -73,7 +73,7 @@ Set the initiative you are working on in `_bmad/custom/config.user.toml`, which active_initiative = "initiative-checkout" ``` -The value is the initiative's folder name in the store. When it is unset, `bmad-ticket` offers to create the folder and record the setting for you. You can also ask the `bmad` skill to show, switch, create, or clear the active initiative at any time. +The value is the initiative's folder name in the store. When it is unset, Sofia offers to create the folder and record the setting for you. You can also ask the `bmad` skill to show, switch, create, or clear the active initiative at any time. :::tip[One workspace, many projects] If one workspace holds unrelated projects, tell your coding agent to follow the active initiative. Put a short rule in `AGENTS.md`, or whatever instruction file your tool reads, that names the setting and says which folders belong to which initiative: @@ -109,7 +109,7 @@ UX is the exception to the naming: `bmad-ux` writes two peer documents, `DESIGN. ## Configure Where Tickets Are Tracked -Out of the box, tickets are markdown files in the store, and there is nothing to set up. To use a tracker, say "set up the ticket store": `bmad-ticket` writes your choice to `_bmad/custom/ticketing-store-config.toml`. That file holds only what your project sets, such as the tracker's project key. Anything you add to it overrides the skill's default for that tracker, and your edits survive skill updates. +Out of the box, tickets are markdown files in the store, and there is nothing to set up. To use a tracker, say "set up the ticket store": Sofia writes your choice to `_bmad/custom/ticketing-store-config.toml`. That file holds only what your project sets, such as the tracker's project key. Anything you add to it overrides the skill's default for that tracker, and your edits survive skill updates. | Choice | What it means | | ------------- | ------------------------------------------------------------------------------------ | @@ -128,21 +128,22 @@ Repo is the default and the choice that has been tested most. The tracker option Hooks are not integrated yet, so nothing syncs on its own: a tracker and the ticket files are brought in line only when you run the skill. Hooks may be added later. ::: -## Use `bmad-ticket` +## Use Sofia -The skill turns intent into tickets a coding agent can build from, at three levels. An initiative holds epics. An epic holds stories, spikes, and bugs. An initiative or an epic is itself the specification at its level: it holds the requirements, and its children are cut from them. When the requirements outgrow the ticket, `bmad-spec` writes a spec folder inside that initiative or epic folder. +Sofia turns intent into tickets a coding agent can build from, at three levels. An initiative holds epics. An epic holds stories, spikes, and bugs. An initiative or an epic is itself the specification at its level: it holds the requirements, and its children are cut from them. Sofia writes those requirements from whatever you have: a PRD, a brief, notes, a transcript. Each line carries a stable `R` id and names its source, and detail that does not fit a line goes in a companion file beside the ticket, named in its References. -It takes almost any input. The best input is a `bmad-spec` output together with the documents that produced it. Give it the spec folder and it plans one epic whose stories cite the spec's `CAP-N` ids. A PRD alone, meeting notes, or a one-paragraph idea also work. +She takes almost any input: a PRD together with the documents that produced it, meeting notes, or a one-paragraph idea. The stories she plans cite the epic's `R` ids. | Say | What happens | | ---------------------------------------- | ------------------------------------------------------------------------------------------- | +| "Distill this PRD" | Writes the initiative's Requirements and Constraints from it, one `R` line per requirement, and offers to slice. | | "Split this initiative into epics" | Proposes epic boundaries from your source and records the agreed order in `tickets.toml`. | | "Incept the first epic" | Plans the whole epic with you into an ordered breakdown of stories. | | "What's next?" | Lists what is ready to refine or start, in progress, and blocked. | | "Review the stories" | Writes each story's file from its entry if needed, then reviews and improves it with you: description, check, references, order, and prerequisites. | | "File a bug: checkout ignores discounts" | Writes one ticket straight into `backlog/`, with no epic needed. | -Each initiative and epic keeps its breakdown in a `tickets.toml` file beside its ticket file. The initiative's file lists the epics in build order. An epic's file lists every planned story and bug as an entry, in build order, each with an `id` that names it under the epic: what it delivers, how it will be verified, what it waits on (`after`), and what is still uncertain. When something must be settled before implementation, the skill asks you to answer it or records it as the entry's `unknown`. It adds a spike when you ask for one. By default the last entry is a "Refactor sweep" story for cleanup found during the epic. +Each initiative and epic keeps its breakdown in a `tickets.toml` file beside its ticket file. The initiative's file lists the epics in build order. An epic's file lists every planned story and bug as an entry, in build order, each with an `id` that names it under the epic: what it delivers, how it will be verified, what it waits on (`after`), and what is still uncertain. When something must be settled before implementation, Sofia asks you to answer it or records it as the entry's `unknown`. She adds a spike when you ask for one. By default the last entry is a "Refactor sweep" story for cleanup found during the epic. An entry needs no file to be built. `bmad-build` plans the story's acceptance criteria when it builds, from the epic and the entry, so detail is not written months before it is used. It writes that plan beside `tickets.toml`, as `story--plan.md`, and the plan is never sent to a tracker. A story gets its own file only when you refine it or publish it to a tracker. From then on the file is truth: refining edits the file, and the entry keeps only the story's `id`, `type`, `title`, prerequisites (`after`, edited in both places), and `hitl`. @@ -150,22 +151,22 @@ A story's `after` can name a story in another epic, or a whole epic. Ask "what's ### How epics are cut -An epic is one capability that one owner delivers to production. A module, service, or bounded context can be an epic when it is also the ownership or deployment boundary. A unit the work only consumes or configures gets no epic. It is a touch point, named in the initiative's Boundaries with the epic that owns the work there. If your team cuts epics by its own rule, tell the skill and it offers to save the rule to `_bmad/custom/bmad-ticket.toml`. +An epic is one capability that one owner delivers to production. A module, service, or bounded context can be an epic when it is also the ownership or deployment boundary. A unit the work only consumes or configures gets no epic. It is a touch point, named in the initiative's Boundaries with the epic that owns the work there. If your team cuts epics by its own rule, tell Sofia and she offers to save the rule to `_bmad/custom/bmad-agent-po.toml`. -After the epics are agreed, the skill lists the decisions that more than one epic must adopt, such as a contract, a data format, or a shared value list. It offers `bmad-architecture` to settle them in the architecture spine. If you decline, each becomes a story in the opening epic that the other epics wait on. Work in one repo or one unit needs no architecture pass. It is needed from the second unit that must adopt a decision. +After the epics are agreed, Sofia lists the decisions that more than one epic must adopt, such as a contract, a data format, or a shared value list. She offers `bmad-architecture` to settle them in the architecture spine. If you decline, each becomes a story in the opening epic that the other epics wait on. Work in one repo or one unit needs no architecture pass. It is needed from the second unit that must adopt a decision. -When your source contradicts the code, the skill records a `Source conflict:` line in the Notes of the initiative or epic and tells you. When the source is a BMad spec, it offers to pass the correction to `bmad-spec`. +When your source contradicts the code, Sofia records a `Source conflict:` line in the Notes of the initiative or epic, tells you, and offers to log the correction in that ticket's requirements. ## Hand a Story to Build Name the story to `bmad-build`, for example "build story 1.2" for the second story of the first epic. There is no file to write first. Build reads the story's entry and its epic, plus the story file when you refined one. It plans the story's acceptance criteria from the epic's Requirements and Done when, the entry's description, and its `Verify:` check. :::note[Refining is optional] -A story needs no refining before `bmad-build`. Build refines it as part of the build: it questions you and writes the acceptance criteria itself. If you will build unattended, with `bmad-build-auto`, a loop, or a factory, nobody answers questions during the build, so review the sequence and each story with `bmad-ticket` first. `bmad-ticket` writes full acceptance criteria only for a bug, a ticket with no epic, or when you ask. +A story needs no refining before `bmad-build`. Build refines it as part of the build: it questions you and writes the acceptance criteria itself. If you will build unattended, with `bmad-build-auto`, a loop, or a factory, nobody answers questions during the build, so review the sequence and each story with Sofia first. She writes full acceptance criteria only for a bug, a ticket with no epic, or when you ask. ::: -The story's `status` lives in the build's plan. Build moves it as it works and stops at `built`; only you, or an orchestrator, mark a story done. When you have checked the work, say "mark story 1.2 done" to `bmad-ticket`. On the repo store that is an edit to the plan that you commit with your work. With a tracker, say "start story 1.2" before you build, so the ticket publishes if it has not and its card moves to in progress. The tracker's status is read into the story's file as `tracker_status`, so moving a card on the board never makes build skip planning. +The story's `status` lives in the build's plan. Build moves it as it works and stops at `built`; only you, or an orchestrator, mark a story done. When you have checked the work, say "mark story 1.2 done" to Sofia. On the repo store that is an edit to the plan that you commit with your work. With a tracker, say "start story 1.2" before you build, so the ticket publishes if it has not and its card moves to in progress. The tracker's status is read into the story's file as `tracker_status`, so moving a card on the board never makes build skip planning. ## Tell Us What You Find -Feedback helps improve `bmad-ticket` and its tracker integrations. The most useful reports say what you gave the skill, what you asked for, what it produced, and what you expected instead. Open a [GitHub issue](https://github.com/bmad-code-org/BMAD-METHOD/issues) with "bmad-ticket" in the title, or post in [Discord](https://discord.gg/gk8jAdXWmj). +Feedback helps improve Sofia and her tracker integrations. The most useful reports say what you gave her, what you asked for, what she produced, and what you expected instead. Open a [GitHub issue](https://github.com/bmad-code-org/BMAD-METHOD/issues) with "bmad-agent-po" in the title, or post in [Discord](https://discord.gg/gk8jAdXWmj). diff --git a/docs/reference/skills-and-agents.md b/docs/reference/skills-and-agents.md index fd53c3ed1..9652abbec 100644 --- a/docs/reference/skills-and-agents.md +++ b/docs/reference/skills-and-agents.md @@ -45,15 +45,16 @@ Open your skills directory to see every installed skill with its description. Ru ## Agents -The BMad Method module installs five named agents. Load one with its skill ID, then type a code from its menu. Codes are scoped to the agent that shows them: `CR` is a competitive teardown for the Analyst and a code review for the Developer. +The BMad Method module installs six named agents. Load one with its skill ID, then type a code from its menu. Codes are scoped to the agent that shows them: `CR` is a competitive teardown for the Analyst and a code review for the Developer. -| Agent | Skill ID | Codes | Menu | -| ---------------------- | ------------------------ | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Analyst (Mary) | `bmad-agent-analyst` | `BP`, `MR`, `DR`, `TR`, `TS`, `CR`, `UV`, `CB`, `WB`, `PC` | Brainstorm; market, domain, and technical research; technology selection; competitive teardown; user-voice research; product brief; PRFAQ challenge; project context | -| Product Manager (John) | `bmad-agent-pm` | `PRD`, `CC`, `TK` | Create, update, or validate a PRD; correct course; plan and manage tickets | -| Architect (Winston) | `bmad-agent-architect` | `CA`, `TK` | Architecture spine; plan work and dependencies | -| Developer (Amelia) | `bmad-agent-dev` | `BD`, `QA`, `CR`, `ER`, `TK` | Build; QA test generation; code review; epic retrospective; ticket planning and tracking | -| UX Designer (Sally) | `bmad-agent-ux-designer` | `CU` | UX design | +| Agent | Skill ID | Codes | Menu | +| ---------------------- | ------------------------ | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Analyst (Mary) | `bmad-agent-analyst` | `BP`, `MR`, `DR`, `TR`, `TS`, `CR`, `UV`, `CB`, `WB`, `PC` | Brainstorm; market, domain, and technical research; technology selection; competitive teardown; user-voice research; product brief; PRFAQ challenge; project context | +| Product Manager (John) | `bmad-agent-pm` | `PRD` | Create, update, or validate a PRD | +| Product Owner (Sofia) | `bmad-agent-po` | `SPEC`, `TICKETS`, `REFINE`, `BOARD`, `VALIDATE`, `REPLAN`, `STORE` | Requirements from a PRD, brief, or notes into a ticket; slice an initiative or epic; refine a ticket; the board; validation; replan a mid-flight change; tracker setup | +| Architect (Winston) | `bmad-agent-architect` | `CA` | Architecture spine | +| Developer (Amelia) | `bmad-agent-dev` | `BD`, `QA`, `CR`, `ER` | Build; QA test generation; code review; epic retrospective | +| UX Designer (Sally) | `bmad-agent-ux-designer` | `CU` | UX design | :::note[Where is Paige?] The Technical Writer (Paige) is on hiatus. Project context lives on: use the Analyst's `PC` code or invoke `bmad-project-context` directly. @@ -96,7 +97,7 @@ A structured second pass over what the model just produced. Instead of "try agai Dozens of methods are available. Examples: pre-mortem analysis, first principles thinking, inversion, red team vs. blue team, Socratic questioning, constraint removal, stakeholder mapping, and analogical reasoning. -The brief, PRD, UX, and spec skills offer it at their own pauses; you can also run it directly on anything recent in the conversation. +The brief, PRD, and UX skills offer it at their own pauses; you can also run it directly on anything recent in the conversation. :::tip[Start with a pre-mortem] Pre-mortem analysis is a good first pick for any spec or plan. It consistently finds gaps that a standard review misses. @@ -150,18 +151,15 @@ Puts your installed agents, or custom personas, in one conversation, in characte ## BMad Method Skills -The BMad Method module adds the five agents above and these workflow skills. The linked page covers when to use the skill and what it produces. +The BMad Method module adds the six agents above and these workflow skills. The linked page covers when to use the skill and what it produces. | Skill | Purpose | See | | ------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | `bmad-product-brief` | Create, update, or validate a product brief | [Define Requirements and a Specification](../plan/define-requirements-and-a-specification.md) | | `bmad-prfaq` | Stress-test a product concept with the Working Backwards PRFAQ method | [Define Requirements and a Specification](../plan/define-requirements-and-a-specification.md) | | `bmad-prd` | Create, update, or validate a PRD | [Define Requirements and a Specification](../plan/define-requirements-and-a-specification.md) | -| `bmad-spec` | Condense input into a short spec; hand story breakdown to `bmad-ticket` | [Define Requirements and a Specification](../plan/define-requirements-and-a-specification.md) | | `bmad-ux` | Capture the UX vision as `DESIGN.md` and `EXPERIENCE.md` | [Design UX and Architecture](../plan/design-ux-and-architecture.md) | | `bmad-architecture` | Record the architecture decisions that keep separately built parts consistent | [Design UX and Architecture](../plan/design-ux-and-architecture.md) | -| `bmad-ticket` | Plan and track initiatives, epics, and entries | [Break Work into Stories and Track It](../plan/break-work-into-stories-and-track-it.md) | -| `bmad-correct-course` | Assess a significant mid-sprint change and produce a change proposal | [Break Work into Stories and Track It](../plan/break-work-into-stories-and-track-it.md#correct-course) | | `bmad-project-context` | Set up, refresh, or audit the repository's agent instructions | [Set and Maintain Project Context](../existing-codebases/set-and-maintain-project-context.md) | | `bmad-build` | Turn a work item into working code, reviewed and verified | [Build a Change](../build/build-a-change.md) | | `bmad-build-auto` | Run one iteration of an unattended development loop | [Autonomous Development Loops](../build/autonomous-development-loops.md) | diff --git a/docs/start/install-bmad.md b/docs/start/install-bmad.md index b11fd7b9f..c0df19c6a 100644 --- a/docs/start/install-bmad.md +++ b/docs/start/install-bmad.md @@ -20,7 +20,7 @@ npx skills add bmad-code-org/BMAD-METHOD Select your coding tool and skills. Include `bmad` for setup and help, and the module records `bmod-core-tools` and `bmod-method` for the modules you use. To install a small set by name: ```bash -npx skills add bmad-code-org/BMAD-METHOD --skill bmad --skill bmod-core-tools --skill bmod-method --skill bmad-build --skill bmad-ticket +npx skills add bmad-code-org/BMAD-METHOD --skill bmad --skill bmod-core-tools --skill bmod-method --skill bmad-build --skill bmad-agent-po ``` Add review, retrospective, or other skills as needed. Keep project and global installation scopes consistent. diff --git a/skills/bmad-agent-architect/customize.toml b/skills/bmad-agent-architect/customize.toml index 477147116..55f4487b5 100644 --- a/skills/bmad-agent-architect/customize.toml +++ b/skills/bmad-agent-architect/customize.toml @@ -56,8 +56,3 @@ principles = [ code = "CA" description = "Produce the architecture spine: the invariants that keep independently-built units consistent" skill = "bmad-architecture" - -[[agent.menu]] -code = "TK" -description = "Plan work and dependencies through the ticket tree" -skill = "bmad-ticket" diff --git a/skills/bmad-agent-dev/customize.toml b/skills/bmad-agent-dev/customize.toml index b08775267..8a4132733 100644 --- a/skills/bmad-agent-dev/customize.toml +++ b/skills/bmad-agent-dev/customize.toml @@ -74,8 +74,3 @@ skill = "bmad-code-review" code = "ER" description = "Evidence-based review of a completed epic against its acceptance criteria" skill = "bmad-retrospective" - -[[agent.menu]] -code = "TK" -description = "Plan and track work through the ticket tree" -skill = "bmad-ticket" diff --git a/skills/bmad-agent-pm/customize.toml b/skills/bmad-agent-pm/customize.toml index fe817b642..9237a2e3a 100644 --- a/skills/bmad-agent-pm/customize.toml +++ b/skills/bmad-agent-pm/customize.toml @@ -56,13 +56,3 @@ principles = [ code = "PRD" description = "Create, update, or validate a PRD — state your intent or the skill will ask" skill = "bmad-prd" - -[[agent.menu]] -code = "CC" -description = "Determine how to proceed if major need for change is discovered mid implementation" -skill = "bmad-correct-course" - -[[agent.menu]] -code = "TK" -description = "Slice initiatives, plan epics, and manage tickets" -skill = "bmad-ticket" diff --git a/skills/bmad-architecture/SKILL.md b/skills/bmad-architecture/SKILL.md index 4c8b0c40b..fecb6e9f8 100644 --- a/skills/bmad-architecture/SKILL.md +++ b/skills/bmad-architecture/SKILL.md @@ -26,7 +26,7 @@ When the stack is open — greenfield, or a small/beginner project that could si ## Read the input to know the job -The input itself tells you what kind of job this is — read it rather than quizzing the user about it. A spec package (`spec-.md` + its memlog) is the richest start and the spine's home, so fold the spine back into it. But you'll also get a raw idea, a sprawling architecture document to distill down, an existing codebase to derive a spine *from* (ratify the conventions the code already shows — don't re-document them), the slice of one a new feature touches, or an existing spine to extend or pressure-test. Prefer a `.memlog.md` over re-reading the source it came from. Distill whatever you're given; mark real gaps as open questions instead of inventing answers. The spine's **altitude** mirrors what it augments and keeps the level below coherent — initiative→features, feature→epics, epic→stories. Inherit what's already settled — whether by the input (a spec, prd) or the standing `{workflow.persistent_facts}` — silently; don't re-decide or re-ask it. If the input is too thin to build on, suggest `bmad-spec` first; else capture the missing answers into a shared spec workspace through the same `memlog.py`, so `bmad-spec` can later derive the spec without drift. +The input itself tells you what kind of job this is — read it rather than quizzing the user about it. A ticket file (the initiative's or epic's `-.md` + its memlog) is the richest start and the spine's home: the finished spine is adopted as a References line in it. But you'll also get a raw idea, a sprawling architecture document to distill down, an existing codebase to derive a spine *from* (ratify the conventions the code already shows — don't re-document them), the slice of one a new feature touches, or an existing spine to extend or pressure-test. Prefer a `.memlog.md` over re-reading the source it came from. Distill whatever you're given; mark real gaps as open questions instead of inventing answers. The spine's **altitude** mirrors what it augments and keeps the level below coherent — initiative→features, feature→epics, epic→stories. Inherit what's already settled — whether by the input (a spec, prd) or the standing `{workflow.persistent_facts}` — silently; don't re-decide or re-ask it. If the input is too thin to build on, suggest asking Sofia (`bmad-agent-po`) to write the ticket's requirements first; else capture the missing answers into the ticket's `.memlog.md` through the same `memlog.py`, so Sofia can later derive its Requirements without drift. **Inheriting a parent spine** (e.g. pointed at one epic of a spec whose feature/initiative spine already exists): load the parent spine first and treat its `AD`s, conventions, and paradigm as **binding, read-only** constraints — log each as a `constraint` entry, list them under the spine's *Inherited Invariants* (parent `AD` IDs, never renumbered), and don't re-derive them. Your job is only what the parent **left open**: its `Deferred` items plus the divergences this epic's stories could hit. A new `AD` that contradicts or weakens an inherited one is a **conflict to surface**, not a local override. An epic spine fixes the invariants the epic's stories must share — it does **not** expand per-story detail. @@ -58,7 +58,7 @@ Writes go through the shared script (don't read the file back except on resume): 2. Resolve config: `uv run {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root} --key core.output_folder --key core.active_initiative`. `{date}` is the current system datetime. `{slug}` is what the architecture is about, in kebab-case: the run lands in `architecture-{slug}/architecture-{slug}.md`. - Script not found, or no `output_folder`: BMad is not set up here. Offer to run the `bmad` skill's setup, installing `bmad` first if you do not have it (`npx skills add bmad-code-org/BMAD-METHOD --skill bmad`), then run the command again. - No `active_initiative`: hand off to the `bmad` skill to set or create one, then run the command again and continue. Headless: write loose. -3. Headless (no interactive user) → follow `references/headless.md` for the whole run. Otherwise greet the user. Detect the intent from the conversation and input — **create** (the default), **update** an existing spine, or **validate** one (see those sections). If the real ask is requirements / UX / a capability contract / epic breakdown / an agent, invoke the `bmad-prd`, `bmad-ux`, `bmad-spec`, `bmad-ticket`, or `bmad-workflow-builder` (if the BMad Builder module is installed) skill instead. +3. Headless (no interactive user) → follow `references/headless.md` for the whole run. Otherwise greet the user. Detect the intent from the conversation and input — **create** (the default), **update** an existing spine, or **validate** one (see those sections). If the real ask is requirements / UX / a capability contract / epic breakdown / an agent, invoke the `bmad-prd`, `bmad-ux`, `bmad-agent-po` (Sofia, for the contract and the breakdown), or `bmad-workflow-builder` (if the BMad Builder module is installed) skill instead. 4. If a run folder for this target already exists under `{workflow.spine_output_path}`, offer to resume from its memlog rather than restart. 5. Interactive create: offer the working mode — **Coaching path** (default) or **Fast path** (see *How you work*) — before any drafting; default to Coaching unless the user asks for speed. 6. **Mandatory, both paths, before drafting:** ask whether the spine is the only deliverable — and if not, draw out the *purpose and audience* rather than a document type. "An architecture doc" balloons into bloat; what they actually need might be a one-detail explainer for a single team or a non-technical vision piece for a board. Purpose right-sizes the artifact and may call for extra elicitation up front, not just a finale add-on. @@ -78,8 +78,8 @@ Walk the sequence; reviewer fixes land before polish. 3. **Reviewer pass.** Run the Reviewer Gate (`references/reviewer-gate.md`). Resolve before polish. 4. **Triage.** Open questions and `[ASSUMPTION]` tags: blockers (unsafe for what's next) resolved one at a time; the rest deferred with a revisit condition in the memlog. 5. **Renderings & polish.** The spine is the build deliverable; with it and the memlog now in place, produce any *additional* human-facing artifact the user needs, scoped to the purpose and audience drawn out up front. The up-front question already flagged whether one's needed; if it wasn't, still offer one here, seeding concrete options: an interactive HTML+SVG deck to walk a team through the architecture and drive discussion, a fuller HTML/md solution design, a C4 set, or a view of how the work splits across teams/epics. Build only what they pick, right-sized to that purpose; apply `{workflow.doc_standards}` polish to that prose only, never to the spine. -6. **External handoffs.** Run `{workflow.external_handoffs}`; surface returned URLs/IDs. Offer to invoke the `bmad-spec` skill to adopt the spine as a companion, keeping `AD` IDs stable so downstream can cite them. -7. **Close.** Set the spine's own frontmatter `status: final`, `updated: {date}`; log a `memlog.py append --type event --text "spine finalized"` (the memlog has no status field). Share paths. Next, **lead with `bmad-spec`** — recommend adopting/refreshing the spine as a spec companion (always the top recommendation when a spec was an input, and a useful next step even when it wasn't), then `bmad-ticket` or — epic altitude — `bmad-build`; or invoke the `bmad` skill to route. +6. **External handoffs.** Run `{workflow.external_handoffs}`; surface returned URLs/IDs. Offer to ask Sofia (`bmad-agent-po`) to add the spine to the ticket's References as an adopted companion, keeping `AD` IDs stable so downstream can cite them. +7. **Close.** Set the spine's own frontmatter `status: final`, `updated: {date}`; log a `memlog.py append --type event --text "spine finalized"` (the memlog has no status field). Share paths. Next, **lead with Sofia** — recommend asking her to add or refresh the spine as a References line in the ticket (always the top recommendation when a ticket file was an input, and a useful next step even when it wasn't), then asking her to slice the ticket or — epic altitude — `bmad-build`; or invoke the `bmad` skill to route. 8. Run `{workflow.on_complete}`. ## Update diff --git a/skills/bmad-architecture/assets/spine-template.md b/skills/bmad-architecture/assets/spine-template.md index 56329f483..860ce414c 100644 --- a/skills/bmad-architecture/assets/spine-template.md +++ b/skills/bmad-architecture/assets/spine-template.md @@ -10,7 +10,6 @@ created: '{date}' updated: '{date}' binds: [] # capability / unit IDs governed (from the driving spec; at epic altitude, also the inherited parent AD ids) sources: [] -companions: [] --- # Architecture Spine — {name} @@ -72,7 +71,7 @@ companions: [] | Capability / Area | Lives in | Governed by | | --- | --- | --- | -| {CAP-id / area} | {component / module} | {AD-id, convention, paradigm} | +| {R-id / area} | {component / module} | {AD-id, convention, paradigm} | ## Deferred diff --git a/skills/bmad-architecture/references/headless.md b/skills/bmad-architecture/references/headless.md index 1d51b17c5..bd7a4552f 100644 --- a/skills/bmad-architecture/references/headless.md +++ b/skills/bmad-architecture/references/headless.md @@ -2,7 +2,7 @@ No interactive user: infer everything, ask nothing, but never invent — record inferences as `assumptions[]` and gaps that need a human as `open_questions[]`. Detect headless from a `headless: true` flag, a non-interactive / no-TTY invocation, an activation hook that declares it, or a first message that pre-supplies all inputs and asks for an artifact path back; when ambiguous, default to interactive. -Drive the run from the payload in the first message — `intent`, `altitude`, `purpose`, the driving input (spec package / PRD / raw intent / brownfield path), a parent spine path at lower altitude, and `doc_workspace` if a specific folder is required. Infer anything absent from the inputs or workspace; don't invent stack, constraints, or scope to fill a gap. You still verify named tech on the web (you can't ask, but you can check) and still drive every write through the shared `{project-root}/_bmad/scripts/memlog.py`. Run the full Reviewer Gate (`references/reviewer-gate.md`) non-interactively: `scripts/lint_spine.py` plus **every `{workflow.finalize_reviewers}` lens as a parallel subagent** (and any ad-hoc lens the spine's criticality warrants). Headless skips only the human picking from the menu — never the reviewers themselves; apply the clear fixes and record anything unresolved in `open_questions[]`. For a true authority collision, list it in `conflicts_with_prior_decisions[]`. For the Validate intent, always write the report to `{doc_workspace}` and add `"offer_to_update": true`. If intent stays ambiguous after inference, halt blocked. +Drive the run from the payload in the first message — `intent`, `altitude`, `purpose`, the driving input (ticket file / PRD / raw intent / brownfield path), a parent spine path at lower altitude, and `doc_workspace` if a specific folder is required. Infer anything absent from the inputs or workspace; don't invent stack, constraints, or scope to fill a gap. You still verify named tech on the web (you can't ask, but you can check) and still drive every write through the shared `{project-root}/_bmad/scripts/memlog.py`. Run the full Reviewer Gate (`references/reviewer-gate.md`) non-interactively: `scripts/lint_spine.py` plus **every `{workflow.finalize_reviewers}` lens as a parallel subagent** (and any ad-hoc lens the spine's criticality warrants). Headless skips only the human picking from the menu — never the reviewers themselves; apply the clear fixes and record anything unresolved in `open_questions[]`. For a true authority collision, list it in `conflicts_with_prior_decisions[]`. For the Validate intent, always write the report to `{doc_workspace}` and add `"offer_to_update": true`. If intent stays ambiguous after inference, halt blocked. End with JSON only, omitting keys for artifacts not produced — the shape below is the fully-produced (`complete`) case; a `blocked` run produces no spine, so it omits `spine`, `memlog`, and `companions` entirely (see the note under the block): diff --git a/skills/bmad-brainstorming/references/finalize.md b/skills/bmad-brainstorming/references/finalize.md index e026b936a..473371e52 100644 --- a/skills/bmad-brainstorming/references/finalize.md +++ b/skills/bmad-brainstorming/references/finalize.md @@ -23,4 +23,4 @@ In **Ideate for me** (and headless), the imaginative HTML keepsake is the delive If the session used invented techniques, offer to save a keeper into `{workflow.additional_techniques}` via `bmad-customize` user preferences. -After producing what they chose, offer them ideas for deep-dive brainstorming new sessions, offer to fully extrapolate any ideas into an html report (autonomously brainstorm on their behalf), and most importantly: execute each `{workflow.external_handoffs}` instruction. Then share the artifact paths (and any handoff destinations), invoke the `bmad` skill to suggest where this leads next in the BMad ecosystem, let them know if they feel a produced intent is detailed enough they could jump right into passing it to bmad-spec or any other analysis tool (outlined by `bmad` help) and run `{workflow.on_complete}` if non-empty. +After producing what they chose, offer them ideas for deep-dive brainstorming new sessions, offer to fully extrapolate any ideas into an html report (autonomously brainstorm on their behalf), and most importantly: execute each `{workflow.external_handoffs}` instruction. Then share the artifact paths (and any handoff destinations), invoke the `bmad` skill to suggest where this leads next in the BMad ecosystem, let them know if they feel a produced intent is detailed enough they could jump right into asking Sofia (`bmad-agent-po`) to write the initiative's requirements from it, or passing it to any other analysis tool (outlined by `bmad` help) and run `{workflow.on_complete}` if non-empty. diff --git a/skills/bmad-build-auto/step-01-clarify-and-route.md b/skills/bmad-build-auto/step-01-clarify-and-route.md index 52e80867a..81880a6f3 100644 --- a/skills/bmad-build-auto/step-01-clarify-and-route.md +++ b/skills/bmad-build-auto/step-01-clarify-and-route.md @@ -53,7 +53,7 @@ This runs on the output of `tickets.py find` for one ticket. Set `ticket_args` t - **Architecture** (`architecture-*/architecture-*.md`) — technical design decisions and constraints - **UX/Design** (`ux-*/`, with `DESIGN.md` and `EXPERIENCE.md`) — user experience and interaction design - **Product Brief** (`brief-*/brief-*.md`) — project vision and scope - - **Spec** (`spec-*/spec-*.md`) — the capability contract + - **Ticket file** (`initiative-*/initiative-*.md`, and `epic-*/epic-*.md` under it) — the capability contract: the active node's Requirements and Constraints - Scan the listing for folders matching these patterns. If any look relevant to the current intent, load them selectively — you don't need all of them, but you need the right constraints and requirements rather than guessing from code alone. 2. Resolve intent from the invocation prompt and loaded artifacts. Do not fantasize or leave open questions. If the intent cannot be resolved, HALT with status `blocked` and the unresolved questions as blocking condition. 3. Version control sanity check. If version control is unavailable, skip this check. Otherwise require a clean working tree, a branch that fits the intent, and writable repository metadata. For Git, run `git add --refresh -- .`, then confirm the tree is still clean; on failure or change, HALT with status `blocked` and blocking condition `version-control metadata not writable`. For a ticket from the tree, judge the branch against the epic, not the story. HALT on a dirty tree or obvious branch mismatch. diff --git a/skills/bmad-build/step-01-clarify-and-route.md b/skills/bmad-build/step-01-clarify-and-route.md index e3cf01a58..939416c0d 100644 --- a/skills/bmad-build/step-01-clarify-and-route.md +++ b/skills/bmad-build/step-01-clarify-and-route.md @@ -68,7 +68,7 @@ This runs on the output of `tickets.py find` for one ticket. Find's `description - **Architecture** (`architecture-*/architecture-*.md`) — technical design decisions and constraints - **UX/Design** (`ux-*/`, with `DESIGN.md` and `EXPERIENCE.md`) — user experience and interaction design - **Product Brief** (`brief-*/brief-*.md`) — project vision and scope - - **Spec** (`spec-*/spec-*.md`) — the capability contract + - **Ticket file** (`initiative-*/initiative-*.md`, and `epic-*/epic-*.md` under it) — the capability contract: the active node's Requirements and Constraints - Scan the listing for folders matching these patterns. If any look relevant to the current intent, load them selectively — you don't need all of them, but you need the right constraints and requirements rather than guessing from code alone. 2. Carry the intent and loaded evidence forward as-is. Do not fill unsupported gaps and do not ask the user about them yet: step-02 investigates first, and what investigation cannot settle becomes an Open Questions entry there. 3. Version control sanity check. Is the working tree clean? Does the current branch make sense for this intent — considering its name and recent history? If the tree is dirty or the branch is an obvious mismatch, HALT and ask the human before proceeding. If version control is unavailable, skip this check. diff --git a/skills/bmad-eval/evals/triggers.json b/skills/bmad-eval/evals/triggers.json index 960c7f579..a7b4f6b41 100644 --- a/skills/bmad-eval/evals/triggers.json +++ b/skills/bmad-eval/evals/triggers.json @@ -4,7 +4,7 @@ "should_trigger": true }, { - "query": "does bmad-ticket still beat the bare model? run a baseline", + "query": "does bmad-agent-po still beat the bare model? run a baseline", "should_trigger": true }, { diff --git a/skills/bmad-forge-idea/SKILL.md b/skills/bmad-forge-idea/SKILL.md index 89bac6f4c..43f9f686d 100644 --- a/skills/bmad-forge-idea/SKILL.md +++ b/skills/bmad-forge-idea/SKILL.md @@ -103,7 +103,7 @@ Voice the personas yourself by default. Spawn separate agents only when a branch The session can end in three valid states: -- **Hardened** — the idea is stronger and specific enough to use. Distill the memlog into the forged idea, `{workspace}/{workflow.run_folder_pattern}.md`. Keep it extremely short: only the decisions, rejected options, and reasons that matter downstream, in the user's meaning. Do not write a prose summary, template, or conversation recap. If it reads like a document, it is too long. If planning or dev skills are installed (`bmad-spec`, `bmad-prd`, `bmad-prfaq`, `bmad-build`), offer the file as their input; if none are, the file stands on its own — never treat a missing skill as an error. +- **Hardened** — the idea is stronger and specific enough to use. Distill the memlog into the forged idea, `{workspace}/{workflow.run_folder_pattern}.md`. Keep it extremely short: only the decisions, rejected options, and reasons that matter downstream, in the user's meaning. Do not write a prose summary, template, or conversation recap. If it reads like a document, it is too long. If planning or dev skills are installed (`bmad-agent-po`, `bmad-prd`, `bmad-prfaq`, `bmad-build`), offer the file as their input — to Sofia, the Product Owner, as what she writes the initiative's requirements from; if none are, the file stands on its own — never treat a missing skill as an error. - **Killed** — the idea does not hold up. Say so plainly and record why. Finding that out early is a valid outcome. - **Clearer** — the user understands the idea better, but there is no hardened idea to hand off. Leave the memlog as the record; no forged idea is needed. diff --git a/skills/bmad-prd/SKILL.md b/skills/bmad-prd/SKILL.md index 6b14188df..9c1fc2e5d 100644 --- a/skills/bmad-prd/SKILL.md +++ b/skills/bmad-prd/SKILL.md @@ -94,5 +94,5 @@ Tell the user the sequence in one sentence, then walk it. Polish goes last so it 4. **Triage open items.** All Open Questions, `[ASSUMPTION]` tags, `[NOTE FOR PM]` callouts. Phase-blockers (would make the PRD unsafe for UX/architecture/epics) surfaced one at a time and resolved; non-blockers deferred with owner + revisit condition logged via `memlog.py append`. If phase-blocker count is high, flag it. 5. **Polish.** Apply `{workflow.doc_standards}` to the PRD and `addendum.md` in declared order (structural passes before prose — prose should not polish soon-to-be-cut text). Parallelize across documents, sequential within. 6. **External handoffs.** Execute `{workflow.external_handoffs}`; surface returned URLs/IDs. Skip and flag unavailable tools. -7. **Close.** Set the PRD's frontmatter `status: final` and `updated` to `{date}` so future invocations distinguish this PRD from in-progress drafts. Record finalization via `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type event --text "PRD finalized"`. Share artifact paths. Common next: `bmad-ux`, `bmad-architecture`, `bmad-ticket`; invoke the `bmad` skill for authoritative routing. +7. **Close.** Set the PRD's frontmatter `status: final` and `updated` to `{date}` so future invocations distinguish this PRD from in-progress drafts. Record finalization via `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type event --text "PRD finalized"`. Share artifact paths. Common next: `bmad-ux`, `bmad-architecture`, or asking Sofia (`bmad-agent-po`) to write the initiative's requirements from the PRD; invoke the `bmad` skill for authoritative routing. 8. Run `{workflow.on_complete}` if non-empty. diff --git a/skills/bmad-retrospective/workflow.md b/skills/bmad-retrospective/workflow.md index c8fe13937..2dda8c7d0 100644 --- a/skills/bmad-retrospective/workflow.md +++ b/skills/bmad-retrospective/workflow.md @@ -96,4 +96,4 @@ Skip by default; never runs headless. When the user asks to "discuss it as a tea ### Phase 5 — Finalize -Finalize the retrospective document and stop. Read fully and follow `{{ rendered("references/retro-document.md") }}` for the document's location, frontmatter, and sections, and the terminal instruction that ends the run. The document is the run's only write: no status change, no `tickets.py mark`, and no edit to the epic file, any plan, or any story file. Closing the epic is `bmad-ticket`'s, confirmed by the user. +Finalize the retrospective document and stop. Read fully and follow `{{ rendered("references/retro-document.md") }}` for the document's location, frontmatter, and sections, and the terminal instruction that ends the run. The document is the run's only write: no status change, no `tickets.py mark`, and no edit to the epic file, any plan, or any story file. Closing the epic is Sofia's (`bmad-agent-po`), confirmed by the user. diff --git a/skills/bmad-ux/SKILL.md b/skills/bmad-ux/SKILL.md index 92451c231..02703d773 100644 --- a/skills/bmad-ux/SKILL.md +++ b/skills/bmad-ux/SKILL.md @@ -56,7 +56,7 @@ Activation is complete. If `activation_steps_prepend` or `activation_steps_appen **Capture; do not author.** The spines are distilled at Finalize toward the memlog. Decisions → `.memlog.md` (canonical), each appended via `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type --text "…"` — never hand-edited; a resume reloads it. Creative-tool artifacts → `.working/`. User-supplied visuals (Figma, sketches, brand decks, image folders) → `imports/`, one `memlog.py append` per item. Spines win on conflict. -**Source scan.** List candidate inputs by type — `-*/-*.md` for `brief`, `prd`, `spec`, `architecture`, `research` — in `{output_folder}/{active_initiative}/`, then `{output_folder}/`; surface paths only — never read content in the parent. User confirms which apply or adds others; subagent-extracts on confirm. +**Source scan.** List candidate inputs by type — `-*/-*.md` for `brief`, `prd`, `architecture`, `research`, and the ticket files (the initiative's own `initiative-*.md`, `epic-*/epic-*.md` under it) — in `{output_folder}/{active_initiative}/`, then `{output_folder}/`; surface paths only — never read content in the parent. User confirms which apply or adds others; subagent-extracts on confirm. Brain dump first — even when the user opens with paragraphs (that's intake). Subagent-extract big docs. One "anything else?" probe. Stakes: hobby / internal / consumer / regulated. @@ -91,5 +91,5 @@ Outcomes, in order: - **Key-screen mocks rendered.** Key-screens tool → `.working/` for surfaces where layout drives behavior or anchors visual language. - **Mock coverage confirmed.** Walk every IA surface; classify *mocked* vs *spine-only*. Ask: *"These will be built from spine tables alone — any need a visual reference?"* Render more if named; log spine-only choices. - **Layout extracted, artifacts promoted.** Distill subagent re-reads each `.working/` and `imports/` artifact; lifts visual decisions into DESIGN.md and behavioral decisions into EXPERIENCE.md. Promote `.working/` keepers to `mockups/` (HTML) or `wireframes/` (Excalidraw); imports stay. Inline relative links at relevant spine sections; state spines-win-on-conflict once. -- **Polished, handed off, closed.** Apply `{workflow.doc_standards}` in order. Execute `{workflow.external_handoffs}`; surface URLs. Set both files' `status: final`, `updated: {date}`. Log finalization via `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type event --text "spines finalized"`. Share paths. Common next: `bmad-architecture`, `bmad-ticket`, `bmad-build`. Run `{workflow.on_complete}`. +- **Polished, handed off, closed.** Apply `{workflow.doc_standards}` in order. Execute `{workflow.external_handoffs}`; surface URLs. Set both files' `status: final`, `updated: {date}`. Log finalization via `uv run {project-root}/_bmad/scripts/memlog.py append --workspace {doc_workspace} --type event --text "spines finalized"`. Share paths. Common next: `bmad-architecture`, asking Sofia (`bmad-agent-po`) to update the ticket's requirements from the spines, `bmad-build`. Run `{workflow.on_complete}`. diff --git a/skills/bmad/references/initiative.md b/skills/bmad/references/initiative.md index f61fecc51..dead51462 100644 --- a/skills/bmad/references/initiative.md +++ b/skills/bmad/references/initiative.md @@ -4,7 +4,7 @@ Work for one body of work lives in an initiative folder under `output_folder`. ` 1. Run `uv run {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root} --key core.output_folder --key core.active_initiative`. Script not found, or no `output_folder`: BMad is not set up here; offer `bmad setup` first. 2. Tell the user the active initiative, or none. List the `initiative-*` folders under `{output_folder}`, with `{project-root}` substituted. -3. The user picks one, asks for a new one, or clears it. For a new one, ask its name and create `{output_folder}/initiative-/initiative-.md`, `` the name in kebab-case, holding only frontmatter: `type: initiative`, `title`, `parent: none`. `bmad-ticket` fills it in when the user plans the work. +3. The user picks one, asks for a new one, or clears it. For a new one, ask its name and create `{output_folder}/initiative-/initiative-.md`, `` the name in kebab-case, holding only frontmatter: `type: initiative`, `title`, `parent: none`. Sofia (`bmad-agent-po`) fills it in when the user plans the work. 4. Write `active_initiative = ""` under `[core]` in `{project-root}/_bmad/custom/config.user.toml`, creating the file or the table when missing and keeping the rest of the file. Clearing removes the line. 5. Confirm the change in one line. diff --git a/skills/bmad/references/setup.md b/skills/bmad/references/setup.md index 6fdf7c599..c968aefbc 100644 --- a/skills/bmad/references/setup.md +++ b/skills/bmad/references/setup.md @@ -51,7 +51,7 @@ Then run with no mode flag, adding `--module-answers ` when you wrote one, - Duplicates: `--remove-copies ...`, paths exactly as listed. Keep the copy in use, or the newer one when `newer_copy_unused`. - Run the `install` commands the user accepts from `install_offers`, `absent_install`, `unmet_requirements` and `missing_module_records`. Before one that adds a module record (a `missing_module_records` entry, or a `bmod-` skill), run with `--source-record `, using the record's folder and the `source` of its `missing_module_records` or `unmet_requirements` entry, and show its `pre_install_message`; when its `state` is `could-not-check`, say so and install without a message. After the installs, run Config and `_bmad` again. -**Clean up v6.** Run `uv run --no-cache "{skill-root}/scripts/migrate_v6.py" --project-root "{project-root}" --skill "{skill-root}"`, plus each `--root`. It lists `legacy_leftovers`, `stale_config_keys` and `unused_customizations`: v6 traces v7 does not read. When any list is not empty, show the lists as given and offer once to remove them. On yes, run it again with `--clean`: it copies `_bmad/` to `.v6-v7-migration-backup-/` first, then removes them. Report the backup and what was removed. +**Clean up v6.** Run `uv run --no-cache "{skill-root}/scripts/migrate_v6.py" --project-root "{project-root}" --skill "{skill-root}"`, plus each `--root`. It lists `legacy_leftovers`, `stale_config_keys` and `unused_customizations`: v6 traces v7 does not read, with `notes` on them. When any list is not empty, show the lists and the notes as given and offer once to remove them. On yes, run it again with `--clean`: it copies `_bmad/` to `.v6-v7-migration-backup-/` first, then removes them. Report the backup and what was removed. **Migrations.** Do steps 1 and 2 of `references/migrate.md`, Find and Match. Name each that applies with its `title`, `from` and `to`, and ask; on yes, continue with its steps 3 and 4. diff --git a/skills/bmad/scripts/migrate_v6.py b/skills/bmad/scripts/migrate_v6.py index e5b53f76f..b5c7d89a9 100644 --- a/skills/bmad/scripts/migrate_v6.py +++ b/skills/bmad/scripts/migrate_v6.py @@ -2,15 +2,20 @@ # /// script # requires-python = ">=3.11" # /// -"""Report or remove the v6 traces in `_bmad/` that v7 does not read. +"""Report or remove the v6 traces in `_bmad/` that v7 does not read, or fold pre-release spec folders. -With no flag it lists them: the classic installer's files, the v6 install answers -v7 has no key for, and customizations of skills that are not installed. With -`--clean` it copies `_bmad/` to `.v6-v7-migration-backup-/`, which the -method migration reuses, then removes them. +With no flag it lists the traces: the classic installer's files, the v6 install answers +v7 has no key for, the customization keys of retired skills, and customizations of +skills that are not installed. With `--clean` it copies `_bmad/` to +`.v6-v7-migration-backup-/`, which the method migration reuses, then removes them. + +With `--fold-specs ` it folds every 6.13 pre-release spec folder (`spec-/` holding +`spec-.md`) under the output folder into the ticket that holds it, or into a new `backlog/` story when the folder +sits at the root; the method migration's guide says when to run it. Usage: uv run migrate_v6.py --project-root P --skill [--root R ...] [--clean] + uv run migrate_v6.py --project-root P --skill --fold-specs """ from __future__ import annotations @@ -18,6 +23,7 @@ from __future__ import annotations import argparse import datetime import importlib.util +import os import re import shutil import sys @@ -78,6 +84,24 @@ V6_KEYS = ( ) CENTRAL_CONFIGS = (TEAM_CONFIG, "_bmad/custom/config.toml", USER_CONFIG) BACKUP_PREFIX = ".v6-v7-migration-backup-" +# `[workflow]` keys the retired spec and ticket skills read that the agent they retired into does not. Only +# these are removed from that skill's `_bmad/custom/` files, and the note names what replaced them. +RETIRED_CUSTOM_KEYS = { + "bmad-agent-po": ( + "initiative_template", + "epic_template", + "story_template", + "spec_template", + "spec_output_path", + "run_folder_pattern", + "on_complete", + ), +} +RETIRED_CUSTOM_NOTE = ( + "{file}: {keys} are keys of retired skills and were dropped; bmad-agent-po reads ticket_template, " + "the one template for an initiative, epic or story, and _after (spec_after, tickets_after, ...) " + "in place of on_complete." +) def legacy_leftovers(project_root: Path) -> list[str]: @@ -109,6 +133,37 @@ def stale_config_keys(project_root: Path) -> list[tuple[str, tuple[str, ...]]]: return stale +def stale_custom_keys(project_root: Path, retirement: Retirement) -> list[tuple[str, tuple[str, ...]]]: + """The RETIRED_CUSTOM_KEYS set in a skill's `_bmad/custom/` files, a retired name's file carried over included.""" + custom = project_root / "_bmad" / "custom" + stale: list[tuple[str, tuple[str, ...]]] = [] + for skill, keys in RETIRED_CUSTOM_KEYS.items(): + names = [f"{skill}.toml", f"{skill}.user.toml"] + names += [old for old, new in (*retirement.renames, *retirement.unmoved) if new.split(".")[0] == skill] + for name in dict.fromkeys(names): + path = custom / name + if path.is_symlink() or not path.is_file() or not inside_bmad(path, project_root): + continue + try: + data = tomllib.loads(path.read_text(encoding="utf-8")) + except (OSError, UnicodeError, tomllib.TOMLDecodeError): + continue + stale += [ + (f"_bmad/custom/{name}", ("workflow", key)) + for key in keys + if lookup(data, ("workflow", key)) is not _MISSING + ] + return stale + + +def custom_key_notes(stale: list[tuple[str, tuple[str, ...]]]) -> list[str]: + """One note per file in a `stale_custom_keys` result.""" + return [ + RETIRED_CUSTOM_NOTE.format(file=file, keys=", ".join(key[-1] for f, key in stale if f == file)) + for file in dict.fromkeys(file for file, _key in stale) + ] + + def unused_customizations(project_root: Path, installation: Installation, retirement: Retirement) -> list[str]: """`_bmad/custom/` files of skills that are not installed. Files setup is about to rename are not unused.""" custom = project_root / "_bmad" / "custom" @@ -127,14 +182,17 @@ def unused_customizations(project_root: Path, installation: Installation, retire def traces_json(project_root: Path, installation: Installation, retirement: Retirement) -> dict[str, object]: + custom_keys = stale_custom_keys(project_root, retirement) return { "legacy_leftovers": legacy_leftovers(project_root), "stale_config_keys": [ - {"file": relative, "key": ".".join(path)} for relative, path in stale_config_keys(project_root) + {"file": relative, "key": ".".join(path)} + for relative, path in (*stale_config_keys(project_root), *custom_keys) ], "unused_customizations": [ f"_bmad/custom/{name}" for name in unused_customizations(project_root, installation, retirement) ], + "notes": custom_key_notes(custom_keys), } @@ -151,7 +209,8 @@ def clean(project_root: Path, skill_root: Path, *, roots: tuple[Path, ...] = ()) project_root, skill_root, installation, retired_skills(installation.modules, installation.modules) ) leftovers = legacy_leftovers(project_root) - stale = stale_config_keys(project_root) + custom_keys = stale_custom_keys(project_root, retirement) + stale = [*stale_config_keys(project_root), *custom_keys] unused = unused_customizations(project_root, installation, retirement) edits = { relative: text_without_keys( @@ -165,6 +224,7 @@ def clean(project_root: Path, skill_root: Path, *, roots: tuple[Path, ...] = ()) "removed_files": [], "removed_keys": [], "removed_customizations": [], + "notes": [], } if not (leftovers or stale or unused): return report @@ -184,6 +244,7 @@ def clean(project_root: Path, skill_root: Path, *, roots: tuple[Path, ...] = ()) report["removed_files"] = [f"_bmad/{relative}" for relative in leftovers] report["removed_keys"] = [{"file": relative, "key": ".".join(path)} for relative, path in stale] report["removed_customizations"] = [f"_bmad/custom/{name}" for name in unused] + report["notes"] = custom_key_notes(custom_keys) return report @@ -282,6 +343,511 @@ def lines_without_key(lines: list[str], table: tuple[str, ...], leaf: str) -> li return lines +# --- Pre-release spec folders --------------------------------------------------------------------------------- +# A 6.13 pre-release `spec-/` holds `spec-.md` (frontmatter lists `companions` and `sources`, sections Why, +# Capabilities with `CAP-` ids, Constraints, Non-goals, Success signal, Assumptions, Open Questions), `.memlog.md` +# and companions. The ticket file is the spec now, so the folder folds into the ticket that holds it. + +MEMLOG = ".memlog.md" +SPEC_FOLDER = re.compile(r"\Aspec-(?P[A-Za-z0-9][A-Za-z0-9._-]*)\Z") +CAP_ID = re.compile(r"\bCAP-(\d+)\b") +R_LINE = re.compile(r"^\s*-\s+R(\d+)\b", re.MULTILINE) +R_SOURCE = re.compile(r"^- R\d+ \((.*?)\)", re.MULTILINE) +CAPABILITY = re.compile(r"\A-\s+\*\*(CAP-(\d+))\*\*\s*(.*)\Z") +CAPABILITY_FIELD = re.compile(r"\A\s+-\s+\*\*(intent|success):\*\*\s*(.*)\Z") +HEADING = re.compile(r"(?m)^## (.+?)\s*$") +SECTION_ORDER = ( + "Description", + "Outcome", + "Requirements", + "Constraints", + "Done when", + "Boundaries", + "References", + "Notes", +) +UNTOUCHED_FOLDERS = ("archive", "archive-v6") +COMPANION_TYPES = {"DESIGN.md": "design", "EXPERIENCE.md": "experience", "ARCHITECTURE-SPINE.md": "architecture"} +TEXT_SUFFIXES = (".md", ".toml") + + +def fold_specs(project_root: Path, store: Path) -> dict[str, object]: + """Fold every pre-release spec folder under `store` into its ticket, parents before children.""" + if not store.is_dir(): + raise Exception(f"{store} is not a folder") + report: dict[str, object] = {"mode": "fold-specs", "folded": [], "problems": []} + for folder in spec_folders(store): + try: + report["folded"].append(fold_spec(project_root, store, folder)) + except FoldProblem as problem: + report["problems"].append({"spec": relative(folder, project_root), "problem": str(problem)}) + return report + + +class FoldProblem(Exception): + pass + + +def spec_folders(store: Path) -> list[Path]: + found: list[Path] = [] + for current, dirs, _files in os.walk(store): + dirs[:] = sorted(d for d in dirs if not d.startswith(".") and d not in UNTOUCHED_FOLDERS) + for name in dirs: + if SPEC_FOLDER.match(name) and (Path(current) / name / f"{name}.md").is_file(): + found.append(Path(current) / name) + return sorted(found, key=lambda path: (len(path.parts), path.as_posix())) + + +def relative(path: Path, root: Path) -> str: + return path.relative_to(root).as_posix() + + +def fold_spec(project_root: Path, store: Path, folder: Path) -> dict[str, object]: + slug = SPEC_FOLDER.match(folder.name)["slug"] + spec = parse_spec((folder / f"{folder.name}.md").read_text(encoding="utf-8")) + loose = folder.parent == store + if loose: + home = store / "backlog" + ticket = home / f"story-{slug}.md" + if ticket.exists(): + raise FoldProblem(f"{relative(ticket, project_root)} already exists") + else: + home = folder.parent + ticket = home / f"{home.name}.md" + if not ticket.is_file(): + raise FoldProblem("not inside a ticket folder (one holding a same-named .md) and not at the root") + moving = [ + path + for path in sorted(folder.iterdir(), key=lambda p: p.name) + if path.name not in (f"{folder.name}.md", MEMLOG) + and not path.name.endswith(".tmp") + and not path.name.startswith(".") + ] + taken = [path.name for path in moving if (home / path.name).exists()] + if taken: + raise FoldProblem(f"{', '.join(taken)} already exist(s) beside the ticket") + + existing = ticket.read_text(encoding="utf-8") if ticket.is_file() else "" + existing_requirements = section_body(existing, "Requirements") + offset = max((int(n) for n in R_LINE.findall(existing_requirements)), default=0) + ids = {f"CAP-{n}": f"R{n + offset}" for n in spec["capabilities"]} + + def map_cap(match: re.Match[str]) -> str: + return f"R{int(match.group(1)) + offset}" + + def map_caps(text: str) -> str: + """CAP ids mapped everywhere but inside a Requirements line's parentheses, where they are a folded spec's source ids.""" + out, last = [], 0 + for span in R_SOURCE.finditer(text): + out += [CAP_ID.sub(map_cap, text[last : span.start()]), span.group(0)] + last = span.end() + return "".join(out) + CAP_ID.sub(map_cap, text[last:]) + + # Lines the ticket wrote itself stay, less any that name the spec folder, the mapped ones after them; prose that + # only pointed at the folder is replaced. + own = "\n".join(line for line in existing_requirements.splitlines() if folder.name not in line) + own_lines = map_caps(own).strip("\n").splitlines() + if R_LINE.search(existing_requirements): + replace_requirements = True + else: + own_lines = [] + replace_requirements = not existing_requirements.strip() or folder.name in existing_requirements + + for key in ("constraints", "non_goals", "assumptions", "questions"): + spec[key] = [CAP_ID.sub(map_cap, line) for line in spec[key]] + spec["why"] = CAP_ID.sub(map_cap, spec["why"]) + spec["kept"] = [(name, CAP_ID.sub(map_cap, value)) for name, value in spec["kept"]] + spec["capabilities"] = {n: CAP_ID.sub(map_cap, line) for n, line in spec["capabilities"].items()} + + # References to the folder, wherever they are: `spec-/spec-.md` becomes the ticket, `spec-/x` the + # moved companion. A suffix rewrite keeps relative and absolute forms right, since the folder's parent is unchanged. + new_prefix = "backlog/" if loose else "" + rewrites = [(f"{folder.name}/{folder.name}.md", f"{new_prefix}{ticket.name}")] + rewrites += [(f"{folder.name}/{path.name}", f"{new_prefix}{path.name}") for path in moving] + rewritten: list[str] = [] + for path in text_files(store, skip=(folder,)): + text = path.read_text(encoding="utf-8") + changed = text + for old, new in rewrites: + changed = changed.replace(old, new) + if changed != text: + path.write_text(changed, encoding="utf-8") + rewritten.append(relative(path, project_root)) + for path in cap_scope(home, folder, moving) if not loose else companion_files(moving): + text = path.read_text(encoding="utf-8") + changed = map_caps(text) + if changed != text: + path.write_text(changed, encoding="utf-8") + if relative(path, project_root) not in rewritten: + rewritten.append(relative(path, project_root)) + + references, unresolved = companion_references(spec["companions"], folder, home, project_root, store) + sources = [rebase(item, folder, home, project_root, store) for item in spec["sources"]] + requirements = own_lines + [f"- {ids[f'CAP-{n}']} (CAP-{n}): {line}" for n, line in spec["capabilities"].items()] + if loose: + text = story_text(spec, home, slug, requirements, references, sources) + else: + text = folded_ticket_text( + ticket.read_text(encoding="utf-8"), ticket, spec, requirements, references, sources, replace_requirements + ) + home.mkdir(parents=True, exist_ok=True) + write_text(ticket, text) + + memlog = home / MEMLOG + spec_memlog = folder / MEMLOG + if spec_memlog.is_file(): + if memlog.is_file(): + append_text(memlog, memlog_body(spec_memlog)) + spec_memlog.unlink() + else: + shutil.move(spec_memlog, memlog) + elif not memlog.is_file(): + write_text(memlog, f"---\ntopic: {spec['title']}\nupdated: {datetime.datetime.now():%Y-%m-%dT%H:%M}\n---\n") + entries = [f"- (note) {name}, from {folder.name}: {value}" for name, value in spec["kept"]] + entries.append( + f"- (event) folded {folder.name}/ into {ticket.name}" + + ("; " + ", ".join(f"{old} is {new}" for old, new in ids.items()) if ids else "") + ) + append_text(memlog, "\n".join(entries) + "\n") + + for path in moving: + shutil.move(path, home / path.name) + (folder / f"{folder.name}.md").unlink() + shutil.rmtree(folder) + return { + "spec": relative(folder, project_root), + "ticket": relative(ticket, project_root), + "ids": ids, + "companions": [relative(home / path.name, project_root) for path in moving], + "unresolved": unresolved, + "memlog": relative(memlog, project_root), + "rewritten": rewritten, + } + + +def text_files(root: Path, *, skip: tuple[Path, ...] = ()) -> list[Path]: + """The .md and .toml files under root, hidden files, archives and `skip` folders left out.""" + found: list[Path] = [] + for current, dirs, files in os.walk(root): + dirs[:] = sorted( + d for d in dirs if not d.startswith(".") and d not in UNTOUCHED_FOLDERS and (Path(current) / d) not in skip + ) + found += [Path(current) / f for f in sorted(files) if f.endswith(TEXT_SUFFIXES) and not f.startswith(".")] + return found + + +def companion_files(moving: list[Path]) -> list[Path]: + files: list[Path] = [] + for path in moving: + files += text_files(path) if path.is_dir() else [path] if path.name.endswith(TEXT_SUFFIXES) else [] + return files + + +def cap_scope(home: Path, folder: Path, moving: list[Path]) -> list[Path]: + """Where a spec's `CAP` ids are cited: the ticket folder and its children, except a child container with a + spec of its own, folded or not, whose own fold maps its ids; only that child's ticket file, which cites the + parent's, is in.""" + files: list[Path] = [] + for current, dirs, names in os.walk(home): + here = Path(current) + if here == folder: + dirs[:] = [] + continue + files += [here / f for f in sorted(names) if f.endswith(TEXT_SUFFIXES) and not f.startswith(".")] + kept: list[str] = [] + for d in sorted(dirs): + if d.startswith(".") or d in UNTOUCHED_FOLDERS: + continue + child = here / d + if has_own_spec(child): + files.append(child / f"{d}.md") + continue + kept.append(d) + dirs[:] = kept + return files + companion_files(moving) + + +def has_own_spec(child: Path) -> bool: + ticket = child / f"{child.name}.md" + if not ticket.is_file(): + return False + if any(SPEC_FOLDER.match(p.name) and (p / f"{p.name}.md").is_file() for p in child.iterdir() if p.is_dir()): + return True + requirements = section_body(ticket.read_text(encoding="utf-8"), "Requirements") + return any(CAP_ID.search(span.group(1)) for span in R_SOURCE.finditer(requirements)) + + +def parse_spec(text: str) -> dict[str, object]: + meta, body = split_frontmatter(text) + title = next((line[2:].strip() for line in body.splitlines() if line.startswith("# ")), "") + sections = {name.lower(): content for name, content in split_sections(body)[1]} + capabilities: dict[int, str] = {} + current: dict[str, str] | None = None + blocks: list[tuple[int, dict[str, str]]] = [] + for line in sections.get("capabilities", "").splitlines(): + if match := CAPABILITY.match(line): + current = {"rest": match.group(3).strip()} + blocks.append((int(match.group(2)), current)) + elif current is not None and (field := CAPABILITY_FIELD.match(line)): + current[field.group(1)] = field.group(2).strip() + elif current is not None and line.strip(): + current["rest"] = f"{current['rest']} {line.strip()}".strip() + for number, block in blocks: + intent, success = block.get("intent", ""), block.get("success", "") + line = intent or block["rest"] + if success: + line = f"{line.rstrip('.')}. Success: {success}" + capabilities[number] = line + kept = [(name, prose(sections[name])) for name in ("why", "success signal") if sections.get(name, "").strip()] + return { + "title": title, + "companions": frontmatter_list(meta, "companions"), + "sources": frontmatter_list(meta, "sources"), + "capabilities": capabilities, + "constraints": bullets(sections.get("constraints", "")), + "non_goals": bullets(sections.get("non-goals", "")), + "assumptions": bullets(sections.get("assumptions", "")), + "questions": bullets(sections.get("open questions", "")), + "why": prose(sections.get("why", "")), + "kept": kept, + } + + +def prose(section: str) -> str: + """A section as one line, bullet markers dropped.""" + lines = [line.strip() for line in section.splitlines() if line.strip()] + return " ".join(line[2:].strip() if line.startswith("- ") else line for line in lines) + + +def split_frontmatter(text: str) -> tuple[list[str], str]: + lines = text.splitlines() + if not lines or lines[0].strip() != "---": + return [], text + end = next((i for i in range(1, len(lines)) if lines[i].strip() == "---"), None) + if end is None: + return [], text + return lines[1:end], "\n".join(lines[end + 1 :]) + + +def frontmatter_list(meta: list[str], key: str) -> list[str]: + """`key: [a, b]`, or `key:` followed by `- a` lines. Comments after the value are dropped.""" + items: list[str] = [] + for index, line in enumerate(meta): + if not re.match(rf"\A{re.escape(key)}\s*:", line): + continue + value = line.split(":", 1)[1].split("#", 1)[0].strip() + if value.startswith("["): + items = [item.strip().strip("'\"") for item in value.strip("[]").split(",")] + else: + for following in meta[index + 1 :]: + if not following.lstrip().startswith("- "): + break + items.append(following.lstrip()[2:].split("#", 1)[0].strip().strip("'\"")) + break + return [item for item in items if item] + + +def bullets(section: str) -> list[str]: + """Bullet lines as plain text, without placeholder comments.""" + return [line.strip()[2:].strip() for line in section.splitlines() if line.strip().startswith("- ")] + + +def split_sections(text: str) -> tuple[str, list[tuple[str, str]]]: + """(the text before the first `## `, [(name, body)]) with each body stripped of surrounding blank lines.""" + parts = HEADING.split(text) + return parts[0], [(parts[i], parts[i + 1].strip("\n")) for i in range(1, len(parts), 2)] + + +def join_sections(head: str, sections: list[tuple[str, str]]) -> str: + out = [head.rstrip("\n")] if head.strip() else [] + out += [f"## {name}\n\n{body}" if body else f"## {name}" for name, body in sections] + return "\n\n".join(out) + "\n" + + +def section_body(text: str, name: str) -> str: + return next((body for heading, body in split_sections(text)[1] if heading.lower() == name.lower()), "") + + +def add_lines(sections: list[tuple[str, str]], name: str, lines: list[str], *, replace: bool = False) -> None: + """Append lines to the section, replacing its body when `replace`; a missing section is created in template order.""" + if not lines: + return + for index, (heading, body) in enumerate(sections): + if heading.lower() == name.lower(): + if replace or not body: + body = "\n".join(lines) + else: + # A list after prose needs a blank line to be one. + separator = "\n" if body.splitlines()[-1].startswith("- ") else "\n\n" + body = f"{body}{separator}{chr(10).join(lines)}" + sections[index] = (heading, body) + return + rank = SECTION_ORDER.index(name) + position = next( + ( + i + for i, (heading, _b) in enumerate(sections) + if heading in SECTION_ORDER and SECTION_ORDER.index(heading) > rank + ), + len(sections), + ) + sections.insert(position, (name, "\n".join(lines))) + + +def folded_ticket_text( + text: str, + ticket: Path, + spec: dict, + requirements: list[str], + references: list[str], + sources: list[str], + replace_requirements: bool, +) -> str: + meta, body = split_frontmatter(text) + head, sections = split_sections(body) + add_lines(sections, "Requirements", requirements, replace=replace_requirements) + add_lines(sections, "Constraints", [f"- {line}" for line in spec["constraints"]]) + add_lines(sections, "Boundaries", [f"- Non-goal: {line}" for line in spec["non_goals"]]) + existing = section_body(body, "References") + self_reference = re.compile(rf"(^|/){re.escape(ticket.name)}(,|\s|$)") + kept = [ + line + for line in existing.splitlines() + if not (line.strip().startswith("- ") and self_reference.search(line.split(" — ", 1)[-1])) + ] + kept += [line for line in references if line not in existing] + index = next((i for i, (heading, _b) in enumerate(sections) if heading.lower() == "references"), None) + if index is None: + add_lines(sections, "References", kept) + else: + sections[index] = (sections[index][0], "\n".join(kept)) + add_lines( + sections, + "Notes", + [f"- Assumption: {line}" for line in spec["assumptions"]] + + [f"- Open question: {line}" for line in spec["questions"]], + ) + meta = with_sources(meta, sources) + if any(re.match(r"\Atype\s*:\s*initiative\b", line) for line in meta): + meta = [line for line in meta if not re.match(r"\Acovers\s*:", line)] + front = "---\n" + "\n".join(meta) + "\n---\n" if meta else "" + return front + join_sections(head, sections) + + +def with_sources(meta: list[str], sources: list[str]) -> list[str]: + if not sources: + return meta + index = next((i for i, line in enumerate(meta) if re.match(r"\Asources\s*:", line)), None) + merged = list(dict.fromkeys((frontmatter_list(meta, "sources") if index is not None else []) + sources)) + line = f"sources: [{', '.join(merged)}]" + if index is None: + return [*meta, line] + return [*meta[:index], line, *meta[index + 1 :]] + + +def story_text( + spec: dict, home: Path, slug: str, requirements: list[str], references: list[str], sources: list[str] +) -> str: + used = [ + int(match.group(1)) + for path in sorted(home.glob("*.md")) + if (match := re.search(r"(?m)^id:\s*(\d+)\s*$", path.read_text(encoding="utf-8"))) + ] + # `next_id` at the top of the folder's tickets.toml keeps a moved-out id from being reused. + floor = 0 + breakdown = home / "tickets.toml" + if breakdown.is_file(): + try: + floor = tomllib.loads(breakdown.read_text(encoding="utf-8")).get("next_id", 0) + except (OSError, UnicodeError, tomllib.TOMLDecodeError): + floor = 0 + if isinstance(floor, bool) or not isinstance(floor, int): + floor = 0 + title = spec["title"] or slug.replace("-", " ") + meta = [ + f"id: {max(max(used, default=0) + 1, floor)}", + "type: story", + f'title: "{title.replace(chr(34), chr(39))}"', + "parent: none", + "covers: []", + f"sources: [{', '.join(sources)}]", + "after: []", + "refined: false", + "hitl: false", + "risk: low", + ] + sections: list[tuple[str, str]] = [("Description", spec["why"] or title)] + add_lines(sections, "Requirements", requirements) + add_lines(sections, "Constraints", [f"- {line}" for line in spec["constraints"]]) + add_lines(sections, "Boundaries", [f"- Non-goal: {line}" for line in spec["non_goals"]]) + add_lines(sections, "References", references) + add_lines( + sections, + "Notes", + [f"- Assumption: {line}" for line in spec["assumptions"]] + + [f"- Open question: {line}" for line in spec["questions"]], + ) + return "---\n" + "\n".join(meta) + "\n---\n\n" + join_sections(f"# {title}", sections) + + +def companion_references( + items: list[str], folder: Path, home: Path, project_root: Path, store: Path +) -> tuple[list[str], list[str]]: + """A References line per companion: `companion — ` for one moving beside the ticket, else typed by its + file name with the path rebased to the ticket's folder. A path that resolves nowhere is kept as written.""" + lines: list[str] = [] + unresolved: list[str] = [] + for item in items: + target = resolve_companion(item, folder, project_root, store) + if target is None: + unresolved.append(item) + lines.append(f"- companion — {item}") + continue + if target.parent == folder: + lines.append(f"- companion — {target.name}") + continue + kind = COMPANION_TYPES.get(target.name) or re.split(r"[-_.]", target.stem, maxsplit=1)[0].lower() or "companion" + lines.append(f"- {kind} — {rebase(item, folder, home, project_root, store)}") + return lines, unresolved + + +def resolve_companion(item: str, folder: Path, project_root: Path, store: Path) -> Path | None: + for base in (folder, project_root, store): + candidate = (base / item).resolve() + if candidate.exists(): + return candidate + return None + + +def rebase(item: str, folder: Path, home: Path, project_root: Path, store: Path) -> str: + """The path as the ticket's folder sees it: a name beside it, a path from the project root, else relative.""" + target = resolve_companion(item, folder, project_root, store) + if target is None: + return item + if target.parent == folder: + return target.name + if target.is_relative_to(home.resolve()): + return target.relative_to(home.resolve()).as_posix() + if target.is_relative_to(project_root.resolve()): + return target.relative_to(project_root.resolve()).as_posix() + return Path(os.path.relpath(target, home.resolve())).as_posix() + + +def memlog_body(path: Path) -> str: + _meta, body = split_frontmatter(path.read_text(encoding="utf-8")) + body = body.strip("\n") + return body + "\n" if body else "" + + +def append_text(path: Path, text: str) -> None: + """Add text at the end of the file, on its own line. The memlog is append-only, so nothing above is touched.""" + if not text: + return + with path.open("r+", encoding="utf-8") as handle: + current = handle.read() + handle.write(("" if not current or current.endswith("\n") else "\n") + text) + + def report(project_root: Path, skill_root: Path, *, roots: tuple[Path, ...] = ()) -> dict[str, object]: """The traces `--clean` would remove. Reads only.""" reject_unusable_bmad(project_root) @@ -298,10 +864,18 @@ def main(argv: list[str] | None = None) -> int: parser.add_argument("--skill", type=Path, required=True) parser.add_argument("--root", type=Path, action="append", default=[], help="an active skills folder, repeated") parser.add_argument("--clean", action="store_true", help="back up _bmad, then remove the traces") + parser.add_argument( + "--fold-specs", type=Path, metavar="FOLDER", help="fold the pre-release spec folders under this output folder" + ) args = parser.parse_args(argv) + if args.fold_specs and args.clean: + parser.error("--fold-specs cannot be combined with --clean") project_root = args.project_root.resolve() skill_root = args.skill.resolve() roots = tuple(root.resolve() for root in args.root) + if args.fold_specs: + _setup.print_json(fold_specs(project_root, args.fold_specs.resolve())) + return 0 run = clean if args.clean else report _setup.print_json(run(project_root, skill_root, roots=roots)) return 0 diff --git a/skills/bmad/scripts/tests/test_render_skill.py b/skills/bmad/scripts/tests/test_render_skill.py index 89a567406..b27c3ad6f 100644 --- a/skills/bmad/scripts/tests/test_render_skill.py +++ b/skills/bmad/scripts/tests/test_render_skill.py @@ -433,7 +433,7 @@ class RenderSkillTests(unittest.TestCase): "[[agent.menu]]", 'code = "TICKETS"', 'description = "Slice the work"', - 'skill = "bmad-ticket"', + 'skill = "bmad-fixture-a"', "[workflow]", 'spec_before = ""', 'checks = ["shipped check"]', @@ -453,7 +453,7 @@ class RenderSkillTests(unittest.TestCase): "[[agent.menu]]", 'code = "TICKETS"', 'description = "Slice the work our way"', - 'skill = "bmad-ticket"', + 'skill = "bmad-fixture-a"', "[workflow]", 'checks = ["team check"]', "", @@ -467,7 +467,7 @@ class RenderSkillTests(unittest.TestCase): entry = rs.render( ws.project, skill, - assignments=['agent.menu=[{ code = "BOARD", description = "Show the board", skill = "bmad-ticket" }]'], + assignments=['agent.menu=[{ code = "BOARD", description = "Show the board", skill = "bmad-fixture-a" }]'], ) snap = entry.parent self.assertEqual( @@ -477,8 +477,8 @@ class RenderSkillTests(unittest.TestCase): "SPEC: Write the spec\nTICKETS: Slice the work our way\nBOARD: Show the board\n" "- code: SPEC\n description: Write the spec\n" f" prompt: Read `{snap.as_posix()}/references/spec.md` fully and follow it.\n" - "- code: TICKETS\n description: Slice the work our way\n skill: bmad-ticket\n" - "- code: BOARD\n description: Show the board\n skill: bmad-ticket\n" + "- code: TICKETS\n description: Slice the work our way\n skill: bmad-fixture-a\n" + "- code: BOARD\n description: Show the board\n skill: bmad-fixture-a\n" "- shipped check\n- team check\n", ) manifest = json.loads((snap / "manifest.json").read_text(encoding="utf-8")) @@ -490,8 +490,8 @@ class RenderSkillTests(unittest.TestCase): "description": "Write the spec", "prompt": "Read `{skill-root}/references/spec.md` fully and follow it.", }, - {"code": "TICKETS", "description": "Slice the work our way", "skill": "bmad-ticket"}, - {"code": "BOARD", "description": "Show the board", "skill": "bmad-ticket"}, + {"code": "TICKETS", "description": "Slice the work our way", "skill": "bmad-fixture-a"}, + {"code": "BOARD", "description": "Show the board", "skill": "bmad-fixture-a"}, ], ) self.assertEqual( diff --git a/skills/bmad/scripts/tests/test_stamp_release.py b/skills/bmad/scripts/tests/test_stamp_release.py index fb1c0f765..d290d6d1d 100644 --- a/skills/bmad/scripts/tests/test_stamp_release.py +++ b/skills/bmad/scripts/tests/test_stamp_release.py @@ -29,7 +29,7 @@ RECORD = ( SKILL = f'[skill]\nbmod = "{{bmod}}"\nsource = "{SOURCE}"\n' -MEMBERS = {"method": ("bmad-build", "bmad-spec"), "core-tools": ("bmad", "bmad-flow")} +MEMBERS = {"method": ("bmad-build", "bmad-fixture-a"), "core-tools": ("bmad", "bmad-flow")} def load_module(name: str, path: Path): diff --git a/skills/bmad/scripts/tests/test_validate_manifests.py b/skills/bmad/scripts/tests/test_validate_manifests.py index 637617f69..4f8c6fd06 100644 --- a/skills/bmad/scripts/tests/test_validate_manifests.py +++ b/skills/bmad/scripts/tests/test_validate_manifests.py @@ -17,7 +17,7 @@ METHOD_RECORD = ( 'code = "method"\n' 'version = "{version}"\n' f'update_source = "{SOURCE}"\n' - 'skills = ["bmad-build", "bmad-spec"]\n' + 'skills = ["bmad-build", "bmad-fixture-a"]\n' 'required_skills = ["bmad"]\n' f"{MESSAGES}" "\n" @@ -52,7 +52,7 @@ ROSTER = ( 'members = ["bmad-build", "guest"]\n' ) -MEMBERS = {"bmod-method": ("bmad-build", "bmad-spec"), "bmod-core-tools": ("bmad", "bmad-flow")} +MEMBERS = {"bmod-method": ("bmad-build", "bmad-fixture-a"), "bmod-core-tools": ("bmad", "bmad-flow")} def load_module(name: str, path: Path): @@ -120,7 +120,7 @@ class CleanTreeTests(ValidatorCase): path = self.skills / "bmod-method" / "bmod.toml" write(path, path.read_text(encoding="utf-8") + '\n[builder]\nversion = "9.9.9"\n') write( - self.skills / "bmad-spec" / "bmod.toml", + self.skills / "bmad-fixture-a" / "bmod.toml", SKILL.format(bmod="bmod-method") + "later = true\n\n[extra]\nx = 1\n", ) self.assertEqual(self.problems(), "") @@ -181,9 +181,9 @@ class FileRuleTests(ValidatorCase): self.assertIn("skills/bmad-orphan: missing bmod.toml", self.problems()) def test_file_the_runtime_parser_rejects(self): - write(self.skills / "bmad-spec" / "bmod.toml", 'note = "neither table"\n') + write(self.skills / "bmad-fixture-a" / "bmod.toml", 'note = "neither table"\n') problems = self.problems() - self.assertIn("skills/bmad-spec/bmod.toml: the runtime parser rejects this file", problems) + self.assertIn("skills/bmad-fixture-a/bmod.toml: the runtime parser rejects this file", problems) self.assertIn("[bmod] table, a [skill] table, or both", problems) def test_record_missing_a_required_key(self): @@ -206,9 +206,9 @@ class FileRuleTests(ValidatorCase): self.assertIn("'bmod.pre_install_message' must be a string", problems) def test_skill_missing_a_required_key(self): - write(self.skills / "bmad-spec" / "bmod.toml", '[skill]\nbmod = "bmod-method"\n') + write(self.skills / "bmad-fixture-a" / "bmod.toml", '[skill]\nbmod = "bmod-method"\n') problems = self.problems() - self.assertIn("skills/bmad-spec/bmod.toml", problems) + self.assertIn("skills/bmad-fixture-a/bmod.toml", problems) self.assertIn("'skill.source'", problems) def test_github_source_with_two_parts_is_accepted(self): @@ -218,7 +218,7 @@ class FileRuleTests(ValidatorCase): self.assertEqual(self.problems(), "") def test_github_source_with_one_part_is_rejected(self): - write(self.skills / "bmad-spec" / "bmod.toml", '[skill]\nbmod = "bmod-method"\nsource = "github:o"\n') + write(self.skills / "bmad-fixture-a" / "bmod.toml", '[skill]\nbmod = "bmod-method"\nsource = "github:o"\n') self.assertIn("github source must name owner/repo", self.problems()) @@ -285,8 +285,8 @@ class RetiredRuleTests(ValidatorCase): self.assertEqual(self.problems(), "") def test_a_retired_name_that_still_ships_is_a_problem(self): - self.retire("bmod-method", 'removed = ["bmad-spec"]\n') - self.assertIn("retires 'bmad-spec', but skills/bmad-spec still ships", self.problems()) + self.retire("bmod-method", 'removed = ["bmad-fixture-a"]\n') + self.assertIn("retires 'bmad-fixture-a', but skills/bmad-fixture-a still ships", self.problems()) def test_a_rename_to_a_skill_the_repository_does_not_ship_is_a_problem(self): self.retire("bmod-method", 'renamed = [{ from = "bmad-old", to = "bmad-missing" }]\n') @@ -331,14 +331,14 @@ class StampRuleTests(ValidatorCase): class MembershipRuleTests(ValidatorCase): def test_skill_naming_a_record_the_repo_lacks(self): - write(self.skills / "bmad-spec" / "bmod.toml", SKILL.format(bmod="bmod-absent")) + write(self.skills / "bmad-fixture-a" / "bmod.toml", SKILL.format(bmod="bmod-absent")) self.assertIn( - "skills/bmad-spec/bmod.toml: [skill] bmod names 'bmod-absent', which is not a module record", + "skills/bmad-fixture-a/bmod.toml: [skill] bmod names 'bmod-absent', which is not a module record", self.problems(), ) def test_skill_naming_a_folder_that_is_not_a_record(self): - write(self.skills / "bmad-spec" / "bmod.toml", SKILL.format(bmod="bmad-build")) + write(self.skills / "bmad-fixture-a" / "bmod.toml", SKILL.format(bmod="bmad-build")) self.assertIn("[skill] bmod names 'bmad-build', which is not a module record", self.problems()) def test_skill_its_record_does_not_list(self): @@ -350,28 +350,28 @@ class MembershipRuleTests(ValidatorCase): ) def test_listed_skill_the_repo_does_not_ship(self): - self.method_record('["bmad-build", "bmad-spec"]', '["bmad-build", "bmad-spec", "bmad-typo"]') + self.method_record('["bmad-build", "bmad-fixture-a"]', '["bmad-build", "bmad-fixture-a", "bmad-typo"]') self.assertIn( "skills/bmod-method/bmod.toml: lists the skill 'bmad-typo', which this repository does not ship", self.problems(), ) def test_listed_skill_that_names_another_record(self): - write(self.skills / "bmad-spec" / "bmod.toml", SKILL.format(bmod="bmod-core-tools")) + write(self.skills / "bmad-fixture-a" / "bmod.toml", SKILL.format(bmod="bmod-core-tools")) self.assertIn( - "skills/bmod-method/bmod.toml: lists the skill 'bmad-spec', but skills/bmad-spec/bmod.toml names " + "skills/bmod-method/bmod.toml: lists the skill 'bmad-fixture-a', but skills/bmad-fixture-a/bmod.toml names " "'bmod-core-tools' as its bmod", self.problems(), ) def test_listed_folder_that_is_a_record(self): - self.method_record('["bmad-build", "bmad-spec"]', '["bmad-build", "bmad-spec", "bmod-core-tools"]') + self.method_record('["bmad-build", "bmad-fixture-a"]', '["bmad-build", "bmad-fixture-a", "bmod-core-tools"]') self.assertIn("lists 'bmod-core-tools', which is a module record", self.problems()) def test_member_source_differs_from_its_record(self): - write(self.skills / "bmad-spec" / "bmod.toml", '[skill]\nbmod = "bmod-method"\nsource = "github:o/r"\n') + write(self.skills / "bmad-fixture-a" / "bmod.toml", '[skill]\nbmod = "bmod-method"\nsource = "github:o/r"\n') self.assertIn( - f"skills/bmad-spec/bmod.toml: [skill] source 'github:o/r' differs from skills/bmod-method/bmod.toml " + f"skills/bmad-fixture-a/bmod.toml: [skill] source 'github:o/r' differs from skills/bmod-method/bmod.toml " f"update_source {SOURCE!r}", self.problems(), ) @@ -390,7 +390,7 @@ class RequirementRuleTests(ValidatorCase): write(path, path.read_text(encoding="utf-8") + line) def test_plain_names_in_this_repo_are_accepted_in_both_tables(self): - self.add("bmad-build", 'required_skills = ["bmad"]\nrecommended_skills = ["bmad-spec"]\n') + self.add("bmad-build", 'required_skills = ["bmad"]\nrecommended_skills = ["bmad-fixture-a"]\n') self.assertEqual(self.problems(), "") def test_required_plain_name_the_repo_lacks(self): diff --git a/skills/bmod-method/help/analysis-skills.md b/skills/bmod-method/help/analysis-skills.md index dce6b8a95..ffd849d15 100644 --- a/skills/bmod-method/help/analysis-skills.md +++ b/skills/bmod-method/help/analysis-skills.md @@ -1,8 +1,8 @@ # Analysis skills in detail -Read this when the question is about `bmad-product-brief` or `bmad-prfaq`: what each gives, when to pick it, when not to, and how the two relate. Both exist to give `bmad-spec` better input. +Read this when the question is about `bmad-product-brief` or `bmad-prfaq`: what each gives, when to pick it, when not to, and how the two relate. Both exist to give Sofia (`bmad-agent-po`) better input for a ticket's requirements. -**`bmad-product-brief`** — describes a product the user already believes in. The light way to give `bmad-spec` good input: more than a hand-written intent file, much less than a full PRD. +**`bmad-product-brief`** — describes a product the user already believes in. The light way to give Sofia good input: more than a hand-written intent file, much less than a full PRD. - Gives: a 1-2 page brief (problem, solution, who it serves, what is different, success criteria, scope, vision) that is the user's own, plus an addendum holding detail meant for later documents. - Pick when: the user knows what they want and needs it written down, would otherwise hand-write an intent file and wants it sharper, does not need the rigor of a PRD, needs a pitch or alignment document, has material to distill, is short on time (its fast path drafts everything with `[ASSUMPTION]` tags), or has a brief to update or pressure-test. - Not when: the user doubts the idea itself → `bmad-prfaq`. The brief never asks whether the product should exist. Requirements need ids, journeys, and metrics, or compliance and many stakeholders are involved → `bmad-prd`. diff --git a/skills/bmod-method/help/artifact-lifetime.md b/skills/bmod-method/help/artifact-lifetime.md index bee642417..0f231ccaf 100644 --- a/skills/bmod-method/help/artifact-lifetime.md +++ b/skills/bmod-method/help/artifact-lifetime.md @@ -2,7 +2,7 @@ Joined plans are live ticket state, including after work is done. Keep them beside their entries or backlog leaves. They preserve status, baseline revisions, implementation evidence, and review findings for later work and retrospective. -Before closing an epic, run `bmad-retrospective`, decide how to handle its findings, and close through `bmad-ticket`. Keep the epic, entries, plans, existing leaf files, and retrospective together. Deleting plans can turn completed entries back into planned work. +Before closing an epic, run `bmad-retrospective`, decide how to handle its findings, and close through Sofia (`bmad-agent-po`). Keep the epic, entries, plans, existing leaf files, and retrospective together. Deleting plans can turn completed entries back into planned work. Scope ordinary agent reads to the active initiative and current epic in `AGENTS.md`. Historical plans are evidence, not a replacement for the current code. Superseded planning documents can be archived once their references and requirement sources remain accessible. Never remove live plans as part of that cleanup. diff --git a/skills/bmod-method/help/existing-codebase.md b/skills/bmod-method/help/existing-codebase.md index fe179f68f..e975ae5f4 100644 --- a/skills/bmod-method/help/existing-codebase.md +++ b/skills/bmod-method/help/existing-codebase.md @@ -10,7 +10,7 @@ BMad is installed per project. The existing repository needs its own install and 2. **`bmad-walkthrough`**, when the user does not know the code. It guides them through a file, directory, commit, or PR at their own pace: intent first, then broad strokes, then detail. 3. **`bmad-architecture`**, only when needed. It can start from the codebase and ratify the conventions worth keeping in a short decisions list. Skip it when the codebase is well documented or the changes are small. 4. **`bmad-build`** for the first change. Pick something one session can finish. A change that follows established patterns, such as a new route in a layered API, needs no planning skill: a short intent file or a few sentences is enough input. -5. **`bmad-spec`**, then one `bmad-build` per story, when a change is bigger than one session. +5. **Sofia** (`bmad-agent-po`) to write the ticket's requirements and slice the stories, then one `bmad-build` per story, when a change is bigger than one session. 6. **`bmad-qa-generate-e2e-tests`**, when the inherited app has little test coverage. It generates API and end-to-end tests for features that already exist. If the codebase is inconsistent or has few tests, cleanup first pays back in every later session (`help/preparing-a-repo-for-agents.md`). diff --git a/skills/bmod-method/help/help.md b/skills/bmod-method/help/help.md index c691c9179..6747f7036 100644 --- a/skills/bmod-method/help/help.md +++ b/skills/bmod-method/help/help.md @@ -6,7 +6,7 @@ This document covers the skills of the `method` module: what each one gives the The method turns an intent of any size into working software. Recommend the smallest path that safely fits the work; never march the user through every skill. A project may hold only some of these skills: recommend from what is installed, and say plainly when a step has no installed skill rather than inventing a substitute. -**`bmad-spec` is the hub.** It condenses any input, at any altitude, into a spec folder: `spec-.md` plus companion files, the contract every build reads. A user can talk to it directly. Every analysis and planning skill exists to give the user better material to feed into a spec, and they can run in any order, before or after the spec exists, because the spec is re-derived from a running log and never hand-merged. After any of them finishes, the usual next step is to fold its result into the spec with `bmad-spec`. +**The ticket is the spec.** The initiative, epic or story file is the spec at its level: Sofia, the Product Owner (`bmad-agent-po`), fills its Requirements from whatever the user has (a PRD, a brief, notes, a transcript) and slices from it. The four phases are analysis (ideation, research, is it worth building), planning (what exactly, and in what slices), implementation (build it), and validation (is it right). They describe what kind of help a skill gives. They are not a mandatory sequence to complete. @@ -19,8 +19,8 @@ This document should be enough to route the user and say what to do next. Each t | Topic file | Read when the user asks about | |---|---| | `help/analysis-skills.md` | `bmad-product-brief` or `bmad-prfaq` in depth: what each gives, when to pick it, when not to, how they relate. | -| `help/planning-skills.md` | `bmad-spec`, `bmad-prd`, `bmad-ux`, `bmad-architecture`, `bmad-ticket` in depth. | -| `help/implementation-skills.md` | `bmad-build`, `bmad-build-auto`, or `bmad-correct-course` in depth. | +| `help/planning-skills.md` | `bmad-agent-po` (Sofia), `bmad-prd`, `bmad-ux`, `bmad-architecture` in depth. | +| `help/implementation-skills.md` | `bmad-build` or `bmad-build-auto` in depth. | | `help/validation-skills.md` | `bmad-code-review`, `bmad-walkthrough`, `bmad-qa-generate-e2e-tests`, or `bmad-retrospective` in depth. | | `help/prototyping.md` | Prototyping or vibe coding first, what a prototype is good for (worth doing, feasibility, complexity, unknowns), non-engineers prototyping, what to do with a prototype afterwards, whether planning still matters after a good first version. | | `help/existing-codebase.md` | Using BMad on an inherited, brownfield, or long-lived codebase. | @@ -28,8 +28,8 @@ This document should be enough to route the user and say what to do next. Each t | `help/artifact-lifetime.md` | Whether to keep PRDs, specs, stories, and build records after the work is done, archiving, closing out an epic, keeping old plans from misleading agents. | | `help/monorepo-and-polyrepo.md` | Where to install BMad and keep planning when work spans one repository or several; the workspace layout for a poly repo. | | `help/working-in-an-organization.md` | A team or enterprise: an existing PRD, Jira or another tracker, approvals and sign-off, document owners, several engineers in parallel, requirements changing mid-flight. | -| `help/ticketing-and-epics.md` | How `bmad-ticket` works (initiatives, epic inception, the breakdown, building from an entry, refining), plan status, review, and retrospective. | -| `help/ticketing-setup.md` | Setting up and driving `bmad-ticket`: the store, several repos, trackers, the phrases to say, the hand-off to `bmad-build`. | +| `help/ticketing-and-epics.md` | How Sofia's ticket tree works (initiatives, epic inception, the breakdown, building from an entry, refining), plan status, review, and retrospective. | +| `help/ticketing-setup.md` | Setting up and driving the ticket tree with Sofia: the store, several repos, trackers, the phrases to say, the hand-off to `bmad-build`. | | `help/unattended-builds.md` | `bmad-build-auto`, building stories with no human present, a blocked run and how to retry, what to check after a run. | | `help/review-choices.md` | Review depth, skipping review, another review pass, when to stop, slow reviews, customizing review. | | `help/project-context.md` | `bmad-project-context` in depth: what belongs in `AGENTS.md`, why the block is small, its intents, removing a rule. | @@ -40,15 +40,15 @@ This document should be enough to route the user and say what to do next. Each t - Obvious and low-risk (typo, formatting, config): just make the edit. No skill. - Yes → `bmad-build`. No planning skill first. - Bigger than one session: does the user already have enough to say or paste (an idea they can explain in detail, notes, intent.md, single ticket, a transcript, a brief, a PRD)? - - Yes → `bmad-spec`, then `bmad-ticket` with the spec folder to plan the stories. Then one `bmad-build` per story. - - No → find what is missing, run the skill that supplies it, then `bmad-spec`: + - Yes → Sofia (`bmad-agent-po`): she writes the ticket's Requirements from it and slices the stories. Then one `bmad-build` per story. + - No → find what is missing, run the skill that supplies it, then Sofia: - They cannot name a customer or a problem → `bmad-forge-idea` or `bmad-brainstorming` (core tools), if installed. - Unsure the idea is worth building → `bmad-prfaq`. - Sure of the idea, but it is not written down or not shareable → `bmad-product-brief`. It is also the lighter choice when a full PRD is more than the work needs. - Requirements need real detail, or many stakeholders, compliance, or integrations are involved → `bmad-prd`. - The look and feel matter, or the user thinks best in screens and flows → `bmad-ux`. Starting with UX is a normal way in. - Separate people, agents, or sessions could build parts that do not fit together → `bmad-architecture`. -- Wants to prototype first, or is unsure the work is worth doing, feasible, or how complex it is → encourage a prototype. It suits enterprise work as much as hobby work, greenfield or existing code, and non-engineers can build one. A throwaway needs no skill. Afterwards the user decides to throw it away or keep it, and either way what it taught them goes into `bmad-spec` (`help/prototyping.md`). +- Wants to prototype first, or is unsure the work is worth doing, feasible, or how complex it is → encourage a prototype. It suits enterprise work as much as hobby work, greenfield or existing code, and non-engineers can build one. A throwaway needs no skill. Afterwards the user decides to throw it away or keep it, and either way what it taught them goes to Sofia (`help/prototyping.md`). - Risk, unclear requirements, architectural reach, or coordination between people push work up a tier even when it is small. ## Match the situation @@ -57,30 +57,31 @@ Situations the tree above does not settle. | The user says or has | Recommend | Because | |---|---|---| -| "I want to start from the design" | `bmad-ux` | UX may lead. Feed its files to `bmad-prd` when requirements still need drawing out, to `bmad-product-brief` for a lighter write-up, or straight to `bmad-spec` when they say enough. | -| A prototype, and asks what now | Decide: throw away or keep | Thrown away, the notes go to `bmad-spec`. Kept, treat it as an existing codebase (`help/prototyping.md`). | +| "Create a spec", "write the requirements", "distill this PRD", "slice this", "tickets", "what's ready", "correct course", "ask Sofia", "the product owner" | `bmad-agent-po` | Each phrase dispatches to one of Sofia's capabilities (SPEC, TICKETS, REFINE, BOARD, VALIDATE, REPLAN, STORE); no menu is shown. | +| "I want to start from the design" | `bmad-ux` | UX may lead. Feed its files to `bmad-prd` when requirements still need drawing out, to `bmad-product-brief` for a lighter write-up, or straight to Sofia when they say enough. | +| A prototype, and asks what now | Decide: throw away or keep | Thrown away, the notes go to Sofia. Kept, treat it as an existing codebase (`help/prototyping.md`). | | Work spans several repositories | Install BMad at a workspace root that holds them all, with planning kept there | One session then reaches the plan and every project (`help/monorepo-and-polyrepo.md`). | | "How do I get my repo ready for AI agents?", or agents keep producing inconsistent work | Consistent patterns, a good initial `AGENTS.md`, end-to-end tests, and cleanup first when quality is low | Agents copy what they find (`help/preparing-a-repo-for-agents.md`). | | "Do I keep the PRD, spec, and stories once it is built?" | Keep joined plans and their evidence | Plans own live ticket state; scope routine reads to the active initiative (`help/artifact-lifetime.md`). | | An inherited or brownfield codebase | A small `bmad-build` change first; `bmad-project-context` and `bmad-walkthrough` as needed | No up-front documentation pass is required (`help/existing-codebase.md`). | | "I don't know architecture, stacks, or hosting" | `bmad-architecture`, or the architect agent to talk it through | It coaches, recommends a current starter, and lays out options with reasons for the user to choose. Technical knowledge is not needed to start. | -| "An app for X" and nothing more | `bmad-product-brief`, or `bmad-prd` when the stakes call for full requirements | `bmad-spec` distills and will not coach; the input is too thin for it. The brief is the lighter of the two. | -| A PRD and architecture, several epics, wants tracking | `bmad-ticket` | One tree of epics, entries, and joined plans. | +| "An app for X" and nothing more | `bmad-product-brief`, or `bmad-prd` when the stakes call for full requirements | Sofia distills and will not coach; the input is too thin for her. The brief is the lighter of the two. | +| A PRD and architecture, several epics, wants tracking | `bmad-agent-po` | One tree of epics, entries, and joined plans. | | A team with an existing PRD, a tracker, approvals, or several engineers | The full path only when approvers, parallel teams, or required documents call for it | The existing PRD is input, each document has one owner, and sign-off attaches to skill results (`help/working-in-an-organization.md`). | -| "Can BMad build my stories by itself?" | `bmad-build-auto`, dispatched per story by a loop | It suits settled decisions and well specified stories, with someone reading the results. For work planned with `bmad-ticket`, give it the ticket, one run per ticket (`help/unattended-builds.md`). | -| Wants tickets or a tracker (Jira, Linear, GitHub) as the record | `bmad-ticket` | Tickets are the board. The build moves a ticket's `status` in its plan as far as `built`, and the user marks it done. | -| "Where are we?" | `bmad-ticket` status | Reads the ticket tree and joined plan statuses. | +| "Can BMad build my stories by itself?" | `bmad-build-auto`, dispatched per story by a loop | It suits settled decisions and well specified stories, with someone reading the results. For work planned with Sofia, give it the ticket, one run per ticket (`help/unattended-builds.md`). | +| Wants tickets or a tracker (Jira, Linear, GitHub) as the record | `bmad-agent-po` | Tickets are the board. The build moves a ticket's `status` in its plan as far as `built`, and the user marks it done. | +| "Where are we?" | `bmad-agent-po`, "what's the status" | Reads the ticket tree and joined plan statuses. | | A v6 project (`epics.md`, `sprint-status.yaml`, dated folders under the planning folder) that wants the v7 layout | `bmad migrate method` | The module ships `migration-1.toml`: the rules for turning the planning folder into an initiative, moving document folders into it whole, and turning epics and sprint status into a ticket tree. Unrecognized files outside the planning folder stay where they are. The `bmad` skill plans it with the user, then performs it. | | A PR, a branch, a ticket in review, or code `bmad-build` did not write | `bmad-code-review` | Agent lenses over any diff. With no argument it offers the tickets in review. | | "Walk me through what changed" | `bmad-walkthrough` | The human is the reviewer. | | Every ticket of an epic is built, done, or dropped | `bmad-retrospective` | It judges the whole against the epic's Done when and the initiative's requirements. | -| A big change surfaced mid-build | A spec-only change: update through `bmad-spec`. Otherwise `bmad-correct-course`. | Correct course needs a PRD or a spec and halts when it has neither. The user describes the affected epics and stories. | +| A big change surfaced mid-build | `bmad-agent-po`, "this changed, what now" | She reads the tree, updates the ticket's Requirements when what is required changed, names what went stale, re-slices or drops it, and records the reason as a dated Decision line. | | Wants an expert to think a phase through with, or is unsure where to begin in it | The agent for that phase (see "The agents") | It guides across turns and runs the phase's skills from its menu. | | Agents keep making the same mistake in this repo | `bmad-project-context` | It records the rule in `AGENTS.md`. | ## The skills -One line per skill: what it is for and what it writes. The files it writes are how to tell what is already done. Paths are in the active initiative's folder, `{output_folder}/{active_initiative}/`, or in `{output_folder}/` when none is active; each document is a `-/` folder holding `-.md`. When none is active, `bmad-product-brief`, `bmad-prd`, `bmad-ux`, `bmad-architecture`, `bmad-spec`, and `bmad-correct-course` hand off to the `bmad` skill to set one; `bmad-brainstorming`, `bmad-deep-recon`, `bmad-forge-idea`, `bmad-prfaq`, `bmad-party-mode`, and `bmad-build` ask once per session whether the work belongs to one; the rest write to `{output_folder}/`. Showing, switching, creating, or clearing the active initiative is a `bmad` request. `planning_artifacts` and `implementation_artifacts` are no longer read; a v6 project moves its files with `bmad migrate method`. Open the phase file for the full picture of a skill: what it gives, when to pick it, when not to. +One line per skill: what it is for and what it writes. The files it writes are how to tell what is already done. Paths are in the active initiative's folder, `{output_folder}/{active_initiative}/`, or in `{output_folder}/` when none is active; each document is a `-/` folder holding `-.md`. When none is active, `bmad-product-brief`, `bmad-prd`, `bmad-ux`, and `bmad-architecture` hand off to the `bmad` skill to set one; `bmad-agent-po` offers to create one; `bmad-brainstorming`, `bmad-deep-recon`, `bmad-forge-idea`, `bmad-prfaq`, `bmad-party-mode`, and `bmad-build` ask once per session whether the work belongs to one; the rest write to `{output_folder}/`. Showing, switching, creating, or clearing the active initiative is a `bmad` request. `planning_artifacts` and `implementation_artifacts` are no longer read; a v6 project moves its files with `bmad migrate method`. Open the phase file for the full picture of a skill: what it gives, when to pick it, when not to. | Skill | For | Writes | |---|---|---| @@ -88,15 +89,13 @@ One line per skill: what it is for and what it writes. The files it writes are h | `bmad-product-brief` | A 1-2 page brief of a product the user believes in. Lighter than a PRD, sharper than a hand-written intent file. It does not judge the idea. | `brief-/brief-.md` | | `bmad-prfaq` | Tests whether a concept survives scrutiny: press release, hard FAQs, researched claims, a verdict. | `prfaq-/prfaq-.md`, plus `-distillate.md` beside it | | **Planning** (`help/planning-skills.md`) | | | -| `bmad-spec` | The hub. Distills any input into the contract builds read, and updates it. It does not slice or coach; splitting work into stories is `bmad-ticket`. | `spec-/spec-.md` and companions | +| `bmad-agent-po` | Sofia, the Product Owner. Writes a ticket's Requirements and Constraints from any input, with a stable `R` id per line naming its source, and updates them when the input changes; she does not coach. Slices initiatives into epics and epics into stories, refines a ticket, runs the board (ready, next, blocked, done), validates, replans a change mid-flight, and connects a tracker. Stories are entries in `tickets.toml`; a file is written only when one is reviewed, refined, or published. Bugs go to `backlog/`. | The ticket file, with `.memlog.md` and companions beside it; the ticket tree under `{output_folder}/{active_initiative}/`, plus `{output_folder}/backlog/` | | `bmad-prd` | Coaches detailed requirements out of the user, sized to the stakes. Also updates and validates a PRD. | `prd-/prd-.md` | | `bmad-ux` | How the product looks and works. May lead, follow, or stand alone. Can produce mocks and wireframes. | `ux-/` with `DESIGN.md`, `EXPERIENCE.md`, and `ux-.md` naming them | | `bmad-architecture` | Settles only the decisions that keep separately built parts consistent. Coaches a user with no architecture knowledge, recommends a current starter, and covers hosting and deployment. | `architecture-/architecture-.md` | -| `bmad-ticket` | Plans initiatives, epics, and stories as a ticket tree and runs it as a board: ready, next, blocked, done. Stories are entries in `tickets.toml`; a file is written only when one is reviewed, refined, or published. Bugs go to `backlog/`. Optional tracker. | The ticket tree under `{output_folder}/{active_initiative}/`, plus `{output_folder}/backlog/` | | **Implementation** (`help/implementation-skills.md`) | | | | `bmad-build` | One session of delivery: clarifies intent, plans, implements, reviews, commits. The default for any real change. Takes free text, a ticket from the tree (nothing means the next ready one), or any file as intent. | A ticket's plan beside `tickets.toml`, or `plan-.md`; `deferred-work.md` | | `bmad-build-auto` | One unattended build of one ticket, dispatched by a loop or script. Never for attended work. | The same plans as `bmad-build` | -| `bmad-correct-course` | Assesses a significant midstream change. Needs a PRD or a spec; lists epic and story changes for `bmad-ticket`. | `change-/change-.md` | | **Validation** (`help/validation-skills.md`) | | | | `bmad-code-review` | Agent review of any diff, PR, or branch, with triaged findings. Redundant right after a thorough `bmad-build` review of the same change. | A dated block in the plan's `## Code Review` section, or chat | | `bmad-walkthrough` | The human reviews a change block by block, guided. Also a way to learn unfamiliar code. | `walkthrough-/` with the narrative and a `-log.md` | @@ -107,7 +106,7 @@ One line per skill: what it is for and what it writes. The files it writes are h ### Slicing and tracking the work -`bmad-ticket` plans and tracks work in one ticket tree. An initiative holds epics; each epic's `tickets.toml` holds ordered entries. Build an entry directly without making a story file. Standalone stories and bugs can be direct intent or backlog leaves. Plans own status and remain live after completion. Builds stop at `built`; the user or orchestrator marks `done`. See `help/ticketing-setup.md`. +Sofia (`bmad-agent-po`) plans and tracks work in one ticket tree. An initiative holds epics; each epic's `tickets.toml` holds ordered entries. Build an entry directly without making a story file. Standalone stories and bugs can be direct intent or backlog leaves. Plans own status and remain live after completion. Builds stop at `built`; the user or orchestrator marks `done`. See `help/ticketing-setup.md`. ### Who reviews what @@ -120,15 +119,16 @@ One line per skill: what it is for and what it writes. The files it writes are h ## The agents -Five named experts, each owning a phase and staying in the conversation across turns. An agent carries its role's judgment, offers a menu of the skills it owns, and helps the user decide what to do and why before and between skill runs. Offer one whenever the user wants an expert to work with rather than a single skill to run, is unsure where to begin in a phase, or likes the experience of interacting with unique personas. In the future these agents will have the ability to retain memory and work autonomously which is why they are still a core part of the project. +Six named experts, each owning a phase and staying in the conversation across turns. An agent carries its role's judgment, offers a menu of the skills it owns, and helps the user decide what to do and why before and between skill runs. Offer one whenever the user wants an expert to work with rather than a single skill to run, is unsure where to begin in a phase, or likes the experience of interacting with unique personas. In the future these agents will have the ability to retain memory and work autonomously which is why they are still a core part of the project. | Agent | Phase | Work with them to | |---|---|---| | Mary, analyst — `bmad-agent-analyst` | Analysis | Brainstorm, research a market, domain, technology, or competitor, then shape a brief or a PRFAQ. | -| John, product manager — `bmad-agent-pm` | Planning | Turn a vision into a PRD, epics and stories, check readiness, and handle a midstream change. | +| John, product manager — `bmad-agent-pm` | Planning | Turn a vision into a PRD and check readiness. | +| Sofia, product owner — `bmad-agent-po` | Planning | Write a ticket's requirements from whatever exists, slice it into epics and stories, run the board, and replan a change mid-flight. No menu: say what you want. | | Sally, UX designer — `bmad-agent-ux-designer` | Planning | Work out how the product looks and behaves. | | Winston, architect — `bmad-agent-architect` | Planning | Settle the technical decisions that keep the parts consistent, and check readiness. | -| Amelia, developer — `bmad-agent-dev` | Implementation and validation | Build stories, generate tests, review code, manage the ticket board, and run a retrospective. | +| Amelia, developer — `bmad-agent-dev` | Implementation and validation | Build stories, generate tests, review code, and run a retrospective. | An agent and its skills are two ways into the same work: a skill run directly does the job, and an agent adds a guide who knows the whole phase. `bmad-party-mode` brings the agents together in one discussion and offers the `product-team` room. @@ -136,13 +136,12 @@ An agent and its skills are two ways into the same work: a skill run directly do | Just finished | Offer next | |---|---| -| `bmad-product-brief`, `bmad-prfaq` | `bmad-spec` with the result as input. `bmad-prd` first when the requirements still need drawing out. After a PRFAQ verdict with serious gaps, address those before anything else. | -| `bmad-prd` | `bmad-spec` to absorb it. `bmad-ux` when the UI matters; `bmad-architecture` when parts must fit together. | -| `bmad-ux` | `bmad-spec` to adopt the files as companions. When UX came first and requirements are still thin, `bmad-prd` or the lighter `bmad-product-brief` with the UX files as input. | -| `bmad-architecture` | `bmad-spec` to adopt the spine as a companion. | -| `bmad-spec` | Its open questions and assumptions, if any. Then `bmad-ticket` with the spec folder to plan the stories, and `bmad-build` per story, or straight to `bmad-build` when one session can do it. | +| `bmad-product-brief`, `bmad-prfaq` | `bmad-prd` when the requirements still need drawing out. After a PRFAQ verdict with serious gaps, address those before anything else. | +| `bmad-prd` | `bmad-ux` when the UI matters; `bmad-architecture` when parts must fit together. | +| `bmad-ux` | When UX came first and requirements are still thin, `bmad-prd` or the lighter `bmad-product-brief` with the UX files as input. | +| Sofia has written a ticket's Requirements | Its open questions and assumptions, if any. Then she slices the stories, and `bmad-build` per story, or straight to `bmad-build` when one session can do it. | | `bmad-build` | Open a PR, or `bmad-walkthrough` when a person wants to understand the change. Once the user marks the ticket done, the next ticket. `bmad-qa-generate-e2e-tests` when end-to-end coverage is wanted. | -| The last ticket of an epic | `bmad-retrospective`, then a refactoring pass over the whole changeset, which is commonly skipped (`help/preparing-a-repo-for-agents.md`). Then close the epic through `bmad-ticket` and retain its plans (`help/artifact-lifetime.md`). | +| The last ticket of an epic | `bmad-retrospective`, then a refactoring pass over the whole changeset, which is commonly skipped (`help/preparing-a-repo-for-agents.md`). Then close the epic through Sofia and retain its plans (`help/artifact-lifetime.md`). | ## Answering "what's next?" diff --git a/skills/bmod-method/help/implementation-skills.md b/skills/bmod-method/help/implementation-skills.md index 40255859e..3a57fd750 100644 --- a/skills/bmod-method/help/implementation-skills.md +++ b/skills/bmod-method/help/implementation-skills.md @@ -1,6 +1,6 @@ # Implementation skills in detail -Read this when the question is about `bmad-build`, `bmad-build-auto`, or `bmad-correct-course`. For running stories without a human, see `help/unattended-builds.md`. +Read this when the question is about `bmad-build` or `bmad-build-auto`. For running stories without a human, see `help/unattended-builds.md`. **`bmad-build`** — one session of delivery: clarifies intent, plans, implements, reviews, and presents a commit. - Takes: free text however brief; a ticket from the tree, named by a ref such as `1.2`, its file, or its title; nothing, for the next ready ticket of the active initiative; a plan to resume; any other file as intent; or the recent conversation. @@ -15,9 +15,4 @@ Read this when the question is about `bmad-build`, `bmad-build-auto`, or `bmad-c - Fits when: decisions and patterns are stable and the tickets are well specified. - Writes: the same plans as `bmad-build`. -**`bmad-correct-course`** — assesses a significant midstream change. -- Gives: a change proposal covering impact across PRD, epics, architecture, and UX; a recommended path (adjust, roll back, or cut scope); and proposed edits. It drafts the edits to the PRD, architecture, UX, and stories and does not apply them: the user applies those through the owning skills. Its handoff lists the added, removed, resequenced, or rescoped epics and stories for the user to apply with `bmad-ticket`. -- Pick when: a ticket exposes something that reaches across artifacts, such as a technical limit, a new or misread requirement, a pivot, or a failed approach. -- Not when: there is neither a PRD nor a spec (it halts). A change that touches only the spec → update it with `bmad-spec`. A change that only re-slices tickets → `bmad-ticket`. -- Reads: the PRD or spec, plus architecture and UX when present. It reads no epics file or ticket tree; the user describes the affected epics and stories. -- Writes: `{output_folder}/{active_initiative}/change-/change-.md`. +A significant midstream change, such as a technical limit, a new or misread requirement, a pivot, or a failed approach, goes to Sofia (`bmad-agent-po`, `help/planning-skills.md`): "this changed, what now". She reads the ticket tree, proposes the path (adjust, roll back, or cut scope), updates the ticket's Requirements when what is required changed, re-slices or drops the affected entries, and records the reason as a dated Decision line. diff --git a/skills/bmod-method/help/planning-skills.md b/skills/bmod-method/help/planning-skills.md index 689a70756..1507f07be 100644 --- a/skills/bmod-method/help/planning-skills.md +++ b/skills/bmod-method/help/planning-skills.md @@ -1,38 +1,37 @@ # Planning skills in detail -Read this when the question is about `bmad-spec`, `bmad-prd`, `bmad-ux`, `bmad-architecture`, or the skills that slice and track work: what each gives, when to pick it, when not to, and what it writes. +Read this when the question is about Sofia (`bmad-agent-po`), `bmad-prd`, `bmad-ux`, or `bmad-architecture`: what each gives, when to pick it, when not to, and what it writes. -**`bmad-spec`** — the hub. Condenses any input into the contract builds read. -- Gives: a spec folder with `spec-.md` (why, capabilities with stable ids, constraints, non-goals, success signal) and companions. It adopts UX files and an architecture spine as companions and absorbs a PRD or brief as a source. On request it hands the spec folder to `bmad-ticket` to be planned into stories. It also updates and validates an existing spec. -- Pick when: the user has anything to distill, or can explain the idea in detail; after any other analysis or planning skill finishes; when requirements change on the spec route (it appends to its log, re-derives the spec, and names the tickets that no longer match). -- Not when: the input is a bare idea. It distills and does not coach → `bmad-product-brief` first, or `bmad-prd` when full requirements are needed. -- Splitting into stories is not this skill: send the user to `bmad-ticket` with the spec folder, which plans one epic whose stories cite the spec's `CAP-N` ids. After writing a spec that reads as several slices, `bmad-spec` offers that hand-off once. -- Writes: `{output_folder}/{active_initiative}/spec-/` holding `spec-.md` and companions, or `spec-/` inside the epic folder when the spec is for an epic. +**`bmad-agent-po`** — Sofia, the Product Owner. Writes a ticket's requirements from any input and slices from them; the ticket file is the spec at its level. +- Gives: the ticket's Requirements and Constraints, one line per requirement with a stable `R` id naming its source (`R1 (FR-2, FR-5)`) or, on an epic, its parent's id (`R1 (parent R3)`), plus its Assumption and Open question lines and companions beside the ticket. She adopts UX files and an architecture spine as References lines and absorbs a PRD or brief as a source. She then slices: an initiative into epics, an epic into entries whose `covers` cite the `R` ids. She also updates and validates the requirements of an existing ticket. The user reaches her by name ("ask Sofia", "the product owner") or by saying what they want ("distill this PRD", "slice this initiative", "what's ready"); no menu is shown. +- Pick when: the user has anything to distill, or can explain the idea in detail; after any other analysis or planning skill finishes; when requirements change (she appends to the ticket's `.memlog.md`, re-derives the lines, and names the tickets that no longer match). +- Not when: the input is a bare idea. She distills and does not coach → `bmad-product-brief` first, or `bmad-prd` when full requirements are needed. +- Writes: the ticket file, `-/-.md` under `{output_folder}/{active_initiative}/`, with `.memlog.md` and companions beside it, and `tickets.toml` beside each container. **`bmad-prd`** — coaches detailed requirements out of the user. - Gives: a PRD sized to the stakes (about 2 pages for a hobby project, longer for a launch): features, requirements with stable ids, user journeys, non-goals, MVP scope, metrics. Fast path or coaching path. Also updates and validates an existing PRD. -- Pick when: the idea is too thin for `bmad-spec`; a consumer or multi-stakeholder product; compliance, integration, or SLA concerns; an existing PRD needs editing or critique. -- Not when: scope is one or two stories → `bmad-build`. A lighter document will do → `bmad-product-brief`, then `bmad-spec`. A brief is an optional input, never a prerequisite. +- Pick when: the idea is too thin for Sofia; a consumer or multi-stakeholder product; compliance, integration, or SLA concerns; an existing PRD needs editing or critique. +- Not when: scope is one or two stories → `bmad-build`. A lighter document will do → `bmad-product-brief`, then Sofia. A brief is an optional input, never a prerequisite. - Writes: `{output_folder}/{active_initiative}/prd-/prd-.md`. **`bmad-ux`** — captures how the product looks and how it works. It may lead, follow, or stand alone. - Gives: `DESIGN.md` (visual tokens and rules) and `EXPERIENCE.md` (structure, states, interactions, accessibility, key flows), optionally mockups and wireframes. It captures the user's vision and never imposes one. A design-handoff mode builds a prompt for an external design tool. - Pick when: the UI is a significant part of the work; the user wants to design first and derive requirements from the design; the user has design assets to fold in; design will happen in an outside tool but a contract is still needed. - Not when: there is no meaningful UI. -- UX first: its files are good input to `bmad-prd` when requirements still need drawing out, to `bmad-product-brief` when a lighter write-up will do, or straight to `bmad-spec` when the design already says enough. -- In the spec: both files are adopted as companions. Change them with `bmad-ux` update, not through the spec. +- UX first: its files are good input to `bmad-prd` when requirements still need drawing out, to `bmad-product-brief` when a lighter write-up will do, or straight to Sofia when the design already says enough. +- In the ticket: both files are adopted as References lines. Change them with `bmad-ux` update, not through the ticket. - Writes: `{output_folder}/{active_initiative}/ux-/`: `DESIGN.md`, `EXPERIENCE.md`, and `ux-.md` naming them. **`bmad-architecture`** — fixes only the decisions that keep separately built parts consistent. - For a user new to architecture: it coaches by default, so the user needs no architecture knowledge to start. When the stack is open it recommends a well-known current starter, checked on the web first, because a good starter settles a coherent set of decisions for free. For each big call (paradigm, stack or starter, major boundaries, and where and how it is deployed and hosted) it lays out the realistic options and why it leans one way, then the user chooses. Its fast path drafts everything with `[ASSUMPTION]` tags to correct. -- Gives: a terse spine of decisions with stable ids, plus a list of what it deliberately leaves open. Not a full architecture document unless the user asks for one. Works at initiative, feature, or epic altitude, and can start from a spec, a raw idea, an existing codebase, or a sprawling document to distill. +- Gives: a terse spine of decisions with stable ids, plus a list of what it deliberately leaves open. Not a full architecture document unless the user asks for one. Works at initiative, feature, or epic altitude, and can start from a ticket's requirements, a raw idea, an existing codebase, or a sprawling document to distill. - Pick when: two units built independently could choose incompatibly; an initiative has been cut into epics and more than one epic must adopt the same contract, format, or value list; the stack is open; the user does not know what stack, starter, or hosting to choose; a brownfield codebase has conventions worth ratifying; a feature touches an existing system. -- Not when: the input is too thin → `bmad-spec` first. One session builds all of it → skip. -- Next: it offers to have `bmad-spec` adopt the spine as a companion. Recommend that first. +- Not when: the input is too thin → Sofia first. One session builds all of it → skip. +- Next: it offers to add the spine to the ticket's References through Sofia. Recommend that first. - Writes: `{output_folder}/{active_initiative}/architecture-/architecture-.md`. ## Slicing and tracking the work -`bmad-ticket` plans and tracks work in one ticket tree. An initiative holds epics; each epic's `tickets.toml` holds ordered entries. Build an entry directly without making a story file. Standalone stories and bugs can be direct intent or backlog leaves. Plans own status and remain live after completion. Builds stop at `built`; the user or orchestrator marks `done`. See `help/ticketing-setup.md`. +Sofia plans and tracks work in one ticket tree. An initiative holds epics; each epic's `tickets.toml` holds ordered entries. Build an entry directly without making a story file. Standalone stories and bugs can be direct intent or backlog leaves. Plans own status and remain live after completion. Builds stop at `built`; the user or orchestrator marks `done`. See `help/ticketing-setup.md`. -**`bmad-ticket`** — slices initiatives into epics, incepts each epic into entries, refines when needed, and manages the board and optional tracker publishing. A spec, PRD, or described intent is valid input. Requirements stay in the epic and entries cite them with `covers`. A file is needed for refinement or tracker publishing, not to start a build. Tracker stores are lightly tested. +She slices initiatives into epics, incepts each epic into entries, refines when needed, and manages the board and optional tracker publishing. A PRD or described intent is valid input. Requirements stay in the epic and entries cite them with `covers`. A file is needed for refinement or tracker publishing, not to start a build. Tracker stores are lightly tested. diff --git a/skills/bmod-method/help/prototyping.md b/skills/bmod-method/help/prototyping.md index 920f0d2b7..5794f795f 100644 --- a/skills/bmod-method/help/prototyping.md +++ b/skills/bmod-method/help/prototyping.md @@ -1,6 +1,6 @@ # Prototyping with the method -Prototypes are underused, in hobby work and in the enterprise alike. Recommend one readily. The method has no prototype phase and needs none: a prototype is a fast, cheap way to learn, and what it teaches is some of the best input `bmad-spec` can get. Current models can produce an impressive first version from one prompt. +Prototypes are underused, in hobby work and in the enterprise alike. Recommend one readily. The method has no prototype phase and needs none: a prototype is a fast, cheap way to learn, and what it teaches is some of the best input Sofia (`bmad-agent-po`) can get for a ticket's requirements. Current models can produce an impressive first version from one prompt. ## What a prototype is good for @@ -31,8 +31,8 @@ Both work. In an existing codebase, a prototype on a branch shows how a change s Ask the user to decide this on purpose. The outcome to avoid is a prototype that becomes the real product with nobody deciding it should, and with no spec. -- **Throw it away.** The prototype was research. Have the user note what it taught them: what worked, what surprised them, what users said, what they would do differently. Feed those notes, and the prototype itself if useful, to `bmad-spec`, or to `bmad-product-brief` first when the picture is still loose. Then build cleanly, one story at a time. This is the usual choice in an enterprise codebase and whenever a non-engineer built the prototype. -- **Keep it.** Treat it as an existing codebase (see `help/existing-codebase.md`): `bmad-project-context` to record the rules agents must follow, `bmad-architecture` to ratify the decisions worth keeping and name what is still open, then `bmad-spec` for the rest of the work. +- **Throw it away.** The prototype was research. Have the user note what it taught them: what worked, what surprised them, what users said, what they would do differently. Feed those notes, and the prototype itself if useful, to Sofia, or to `bmad-product-brief` first when the picture is still loose. Then build cleanly, one story at a time. This is the usual choice in an enterprise codebase and whenever a non-engineer built the prototype. +- **Keep it.** Treat it as an existing codebase (see `help/existing-codebase.md`): `bmad-project-context` to record the rules agents must follow, `bmad-architecture` to ratify the decisions worth keeping and name what is still open, then Sofia for the rest of the work. - **Drop the idea.** The prototype showed it is not worth doing. Nothing more is needed. ## Why plan at all after a good prototype diff --git a/skills/bmod-method/help/ticketing-and-epics.md b/skills/bmod-method/help/ticketing-and-epics.md index 448cd5451..28b4c2e52 100644 --- a/skills/bmod-method/help/ticketing-and-epics.md +++ b/skills/bmod-method/help/ticketing-and-epics.md @@ -1,6 +1,6 @@ # The ticket tree -`bmad-ticket` plans and tracks work in one tree. An initiative holds epics; each epic's `tickets.toml` holds ordered entries with ids, coverage, prerequisites, and verification. Build an entry directly. A review, a refinement, or tracker publishing adds a leaf file. Standalone stories and bugs can be direct build intent or backlog leaves. +Sofia (`bmad-agent-po`) plans and tracks work in one tree. An initiative holds epics; each epic's `tickets.toml` holds ordered entries with ids, coverage, prerequisites, and verification. Build an entry directly. A review, a refinement, or tracker publishing adds a leaf file. Standalone stories and bugs can be direct build intent or backlog leaves. Plans own status and baseline evidence and stay local on every store. Build stops at `built`; the user or orchestrator marks `done`. Code review appends dated findings to the plan without changing status. Retrospective writes `epic--retrospective.md` directly in the epic, proposing action items without creating tickets or closing it. Keep completed plans as live state and historical evidence. diff --git a/skills/bmod-method/help/ticketing-setup.md b/skills/bmod-method/help/ticketing-setup.md index 547b92069..a8a538837 100644 --- a/skills/bmod-method/help/ticketing-setup.md +++ b/skills/bmod-method/help/ticketing-setup.md @@ -1,10 +1,10 @@ -# Setting up and using bmad-ticket +# Setting up and driving the ticket tree with Sofia -Use this when a user asks how to set up or drive `bmad-ticket`. For the shared ticket-tree design, see `help/ticketing-and-epics.md`. +Use this when a user asks how to set up or drive the ticket tree with Sofia (`bmad-agent-po`). For the shared ticket-tree design, see `help/ticketing-and-epics.md`. ## Where the store lives -- The ticket tree is the folder `active_initiative` names under `output_folder`: an initiative with its epics inside, a single epic, or a backlog folder of epics or one-off stories, as the user wants. Tickets with no epic go in `{output_folder}/backlog/`. With no active folder, the skill offers to create one and record it. +- The ticket tree is the folder `active_initiative` names under `output_folder`: an initiative with its epics inside, a single epic, or a backlog folder of epics or one-off stories, as the user wants. Tickets with no epic go in `{output_folder}/backlog/`. With no active folder, Sofia offers to create one and record it. - An epic's stories are entries in its `tickets.toml`; a story gets a markdown file only when it is reviewed, refined, or published. Tickets with no epic are markdown files. - `output_folder` is `_bmad-output` unless changed under `[core]` in `_bmad/custom/config.toml` (committed, applies to the team). @@ -14,15 +14,15 @@ Install BMad in the workspace folder that holds the repos, put the store there, ## Existing planning documents -Copy a brief, PRD, UX design, or architecture into the initiative folder as `-/-.md`, for example `initiative-checkout/prd-checkout/prd-checkout.md`. The UX files keep their names, `DESIGN.md` and `EXPERIENCE.md`, inside `ux-/`. Copy, do not move, so other skills still find their files. The best input is a `bmad-spec` output with its source documents. `bmad-spec` offers to hand its spec folder to this skill, which does the story breakdown; the stories cite the spec's `CAP-N` ids. +Copy a brief, PRD, UX design, or architecture into the initiative folder as `-/-.md`, for example `initiative-checkout/prd-checkout/prd-checkout.md`. The UX files keep their names, `DESIGN.md` and `EXPERIENCE.md`, inside `ux-/`. Copy, do not move, so other skills still find their files. Sofia writes the initiative's Requirements from them, each line naming its source id; each epic's lines are cut from those, and the stories cite their epic's `R` ids. ## Trackers - Tickets are files in the store unless the user connects a tracker: "set up the ticket store", or "reconfigure the ticket store" to change it later. Nothing needs setting up for the repo store. - The choice goes in `_bmad/custom/ticketing-store-config.toml`. It holds only what the project sets, such as the tracker's project key; a key added there overrides the skill's default for that tracker. -- Choices: repo (the default; files under version control, no account), GitHub Issues, Jira, Linear, Notion, or Trello. The skill checks the needed CLI or connection at setup. +- Choices: repo (the default; files under version control, no account), GitHub Issues, Jira, Linear, Notion, or Trello. Sofia checks the needed CLI or connection at setup. - Repo is the most tested. The trackers are lightly tested. -- With a tracker, the markdown files stay the working copy. Nothing syncs on its own: files and tracker line up only when the user runs the skill. The skill never pushes. +- With a tracker, the markdown files stay the working copy. Nothing syncs on its own: files and tracker line up only when the user asks Sofia. She never pushes. ## What the user says @@ -40,9 +40,9 @@ Copy a brief, PRD, UX design, or architecture into the initiative folder as `--plan.md`, beside `tickets.toml`. The plan carries the ticket's `status`, which the build moves as far as `built`. After reviewing the work, the user says "mark story 1.2 done". ## Feedback -Open an issue at github.com/bmad-code-org/BMAD-METHOD with "bmad-ticket" in the title, or post in the BMad Discord. Useful reports say what was given, asked, produced, and expected. +Open an issue at github.com/bmad-code-org/BMAD-METHOD with "bmad-agent-po" in the title, or post in the BMad Discord. Useful reports say what was given, asked, produced, and expected. diff --git a/skills/bmod-method/help/unattended-builds.md b/skills/bmod-method/help/unattended-builds.md index 85b247c3d..a1dee1d62 100644 --- a/skills/bmod-method/help/unattended-builds.md +++ b/skills/bmod-method/help/unattended-builds.md @@ -20,7 +20,7 @@ Use this when the user asks about `bmad-build-auto`, building tickets with no hu - A ticket's plan sits beside `tickets.toml`, or in `backlog/` for a backlog ticket, at the path `tickets.py find` returns, with `ticket` and `baseline_revision` in its frontmatter. Other work gets `{output_folder}/{active_initiative}/plan-.md`, or `{output_folder}/plan-.md` with no initiative active. - A successful run ends at `built`, which the board shows as review. Only the user or an orchestrator marks the ticket done, with `tickets.py mark done`. -- This is the repo store. On a tracker store, `next` and `mark` refuse, so name the ticket and move it through `bmad-ticket`. +- This is the repo store. On a tracker store, `next` and `mark` refuse, so name the ticket and move it through Sofia (`bmad-agent-po`). ## Resume follows the plan's status diff --git a/skills/bmod-method/help/working-in-an-organization.md b/skills/bmod-method/help/working-in-an-organization.md index 7a9ccd4c7..9d1a7023c 100644 --- a/skills/bmod-method/help/working-in-an-organization.md +++ b/skills/bmod-method/help/working-in-an-organization.md @@ -4,7 +4,7 @@ Use this when the work belongs to a team or enterprise: a PRD already exists, a ## When the full path is warranted -A single builder, or a small team that already agrees, goes straight to `bmad-spec` and needs no PRD. Recommend the full path (PRD, architecture, one spec per epic, tracking) only when one of these is true: +A single builder, or a small team that already agrees, goes straight to Sofia (`bmad-agent-po`) and needs no PRD. Recommend the full path (PRD, architecture, requirements on every epic, tracking) only when one of these is true: - People who did not do the thinking must approve what the product is. - Several epics, teams, or agents build against the same decisions and must not diverge. @@ -17,6 +17,7 @@ Before any of this, a product manager, designer, or analyst can prototype the id - Point `bmad-prd` at the existing PRD. Validate gives a findings report and changes nothing. Create rewrites the same requirements in the shape later skills read, with `[ASSUMPTION]` tags on what it filled in. - When the source PRD changes, run `bmad-prd` update. Tell the user never to hand-edit `prd-.md`. - `bmad-ux` and `bmad-architecture` start from the existing design system, architecture document, or codebase. +- When the PRD and a ticket's Requirements disagree, the ticket's Requirements win and the PRD is a source: Sofia surfaces the contradiction and the user decides. ## One owner per document @@ -27,8 +28,8 @@ Each document has one skill that writes it, so give it one owner. One person can | Product manager | `bmad-prd` | `prd-.md` and its updates | | Designer | `bmad-ux` | `DESIGN.md`, `EXPERIENCE.md` | | Tech lead | `bmad-architecture` | The architecture spine | -| One engineer per epic | `bmad-spec`, `bmad-build`, `bmad-retrospective` | That epic's spec, stories, verdict | -| Whoever tracks the whole | `bmad-ticket` | the ticket tree | +| One engineer per epic | `bmad-agent-po`, `bmad-build`, `bmad-retrospective` | That epic's requirements, stories, verdict | +| Whoever tracks the whole | `bmad-agent-po` | the ticket tree | Several engineers can each take an epic at once. An epic-level spine inherits the parent spine's decisions as binding. @@ -40,8 +41,8 @@ Each moment produces a written result an approval can attach to. Advise placing |---|---| | `bmad-prfaq` verdict | Writing the PRD | | `bmad-prd` validate | Design and architecture work | -| Architecture spine review | Writing epic specs | -| `bmad-ticket` planning approval | Accepting the breakdown and its dependencies | +| Architecture spine review | Writing the epics' requirements | +| `bmad-agent-po` planning approval | Accepting the breakdown and its dependencies | | `bmad-retrospective` verdict | Starting the next epic | `bmad-prfaq` and `bmad-retrospective` accept `-H` to run without a conversation. @@ -52,13 +53,13 @@ Reviewers ask for changes in whichever document they are reading. Apply the chan 1. `bmad-prd` update. It surfaces conflicts with earlier decisions before applying anything. 2. `bmad-architecture` update when a decision shared across epics changes. -3. `bmad-spec` for each affected epic. Capability ids stay stable, and it says which stories no longer match. -4. Story breakdown or `bmad-ticket` again. Existing plans retain status. +3. Sofia updates the initiative's Requirements from the PRD. Requirement ids stay stable, and she names the epics and stories that no longer match. +4. Sofia re-slices them. Existing plans retain status. -For a change that threatens the plan itself, run `bmad-correct-course` first. It needs a PRD or a spec. +For a change that threatens the plan itself, Sofia replans first: "this changed, what now". ## Tracker integration - Nothing syncs with Jira or any tracker automatically, in either direction. - Repo-store status lives in each joined plan. Builds stop at `built`; the user or orchestrator marks `done`. -- `bmad-ticket` can publish tickets to Jira, Linear, or GitHub. When the skill runs, the tracker's status is read into the leaf file as `tracker_status`. The build's own `status` stays in its separate joined plan; tracker status never drives the build (`help/ticketing-and-epics.md`). +- Sofia can publish tickets to Jira, Linear, or GitHub. When she runs, the tracker's status is read into the leaf file as `tracker_status`. The build's own `status` stays in its separate joined plan; tracker status never drives the build (`help/ticketing-and-epics.md`). diff --git a/skills/bmod-method/migration-1.toml b/skills/bmod-method/migration-1.toml index 8eda8284c..b3edc265c 100644 --- a/skills/bmod-method/migration-1.toml +++ b/skills/bmod-method/migration-1.toml @@ -5,7 +5,7 @@ module = "method" from = "6" to = "7" title = "Move v6 planning and implementation artifacts into the v7 initiative layout" -summary = "Turns the v6 planning folder into a v7 initiative with its documents moved as whole folders, turns epics.md and sprint-status.yaml into a ticket tree with joined plans, sets the active initiative, and, when the user wants it, puts the store in its own repository or a workspace." +summary = "Turns the v6 planning folder into a v7 initiative with its documents moved as whole folders, turns epics.md and sprint-status.yaml into a ticket tree with joined plans, folds 6.13 pre-release spec folders into their tickets, sets the active initiative, and, when the user wants it, puts the store in its own repository or a workspace." # Read-only signals. Any one of them makes this migration worth offering; the first two are v6 for certain. detect = """ @@ -13,7 +13,8 @@ detect = """ - Story files in the implementation folder named `--.md` or `spec---.md` whose frontmatter has `route:` and `status:` (the v6 build spec shape). - Dated artifact folders under the planning folder: `prds/prd--/prd.md`, `briefs/brief--/brief.md`, `ux-designs/ux--/DESIGN.md`, `architecture/architecture--/ARCHITECTURE-SPINE.md`. - `specs/spec-/SPEC.md` under `core.output_folder`, with or without `stories.yaml` and a `stories/` folder. -It applies only while there is no `active_initiative` under `[core]` in `_bmad/custom/config.user.toml` and no `initiative-*/` folder holding a same-named file under the output folder. A project that already has those, with `tickets.toml` and a ticketing store config, is on v7; say so and stop unless the user names v6 leftovers to bring in. +- `spec-/spec-.md` with `.memlog.md` beside it, inside an `initiative-*/` or `epic-*/` folder or at the output folder's root: a 6.13 pre-release spec. +It applies only while there is no `active_initiative` under `[core]` in `_bmad/custom/config.user.toml` and no `initiative-*/` folder holding a same-named file under the output folder. A project that already has those, with `tickets.toml` and a ticketing store config, is on v7; say so and stop unless the user names v6 leftovers to bring in, or a pre-release spec folder is present: then make the backup, write the plan and wait for approval, run "Pre-release spec folders", the checklist items on the fold and on live paths, and the report. """ # The layout the migration moves toward. `` is `{output_folder}` from the BMad config. @@ -49,7 +50,7 @@ questions = """ precautions = """ - Nothing changes before the plan is approved. The only writes before approval are the backup and the plan file. - Nothing is deleted unless the user chose it. The backup is never committed, moved, or edited. -- Read in full only what the migration rewrites: `epics.md`, `sprint-status.yaml`, `stories.yaml`, story files, and retrospectives. Classify everything else from its path, frontmatter, and first heading, and move it unchanged. +- Read in full only what the migration rewrites: `epics.md`, `sprint-status.yaml`, `stories.yaml`, `SPEC.md`, story files, and retrospectives. Classify everything else from its path, frontmatter, and first heading, and move it unchanged. - When the files are under git, every move is a `git mv`, so history follows the file. """ @@ -67,14 +68,15 @@ Run these in order. Each names the section that holds its rules. 7. Create the initiative and move the documents ("Documents"). 8. Write the ticket tree and plans ("The ticket tree"). 9. Convert spec folders with stories ("Spec folders with stories.yaml"). -10. Place loose implementation files ("Loose implementation files"). -11. List what stays in place ("What stays in place"). -12. Rewrite paths ("Path rewrites"). -13. Update configuration and clean up ("Configuration and cleanup"). -14. Run every `checklist` item and record its result in the plan. Fix and recheck a failure, or report it; never skip one. -15. Report ("Report"). +10. Fold pre-release spec folders ("Pre-release spec folders"). +11. Place loose implementation files ("Loose implementation files"). +12. List what stays in place ("What stays in place"). +13. Rewrite paths ("Path rewrites"). +14. Update configuration and clean up ("Configuration and cleanup"). +15. Run every `checklist` item and record its result in the plan. Fix and recheck a failure, or report it; never skip one. +16. Report ("Report"). -In a repository that already existed, commit the current state before step 6 and after each of steps 6 to 13, the message naming the step; never stage the backup. In a repository the migration creates, commit nothing until the report offers it. Never push. +In a repository that already existed, commit the current state before step 6 and after each of steps 6 to 14, the message naming the step; never stage the backup. In a repository the migration creates, commit nothing until the report offers it. Never push. ## Source folders @@ -108,7 +110,7 @@ Inside the initiative, each v6 document moves as a unit: its run folder with eve | `ux-designs/ux--/`, or flat `ux*.md` | router beside `DESIGN.md` and `EXPERIENCE.md` | `bmad-ux` | | `prfaq-.md` with its `-distillate.md` | `prfaq-.md`; the distillate moves with it | `bmad-prfaq` | | `research/--/`, or flat `research/*-research-.md` | the run's report | `bmad-deep-recon`, v6 research | -| `sprint-change-proposal-.md` | itself | `bmad-correct-course` | +| `sprint-change-proposal-.md` | itself | v6 correct course | | `implementation-readiness*.md` | itself | v6 readiness check | | A sharded document (`/index.md` plus sections) | `index.md` | any | @@ -116,13 +118,13 @@ From elsewhere in the output folder, a `brainstorming/` session and a `specs/spe ## The ticket tree -Write from `bmad-ticket`'s templates (`bmad-ticket/assets/`). +Write from `bmad-agent-po`'s ticket template (`bmad-agent-po/assets/ticket-template.md`), the one template for an initiative, an epic and a story. -Initiative envelope: `initiative-/initiative-.md` from the initiative template. Title from the project name; Description and Outcome from the PRD's summary; Done when from the PRD's goals; References naming the PRD, architecture, and UX folders; `covers` the PRD's requirement ids. Its `tickets.toml` gets one `[[epic]]` per `## Epic N:` in `epics.md`, in that order: `id = N`, `slug = "epic-"`, `title`, `covers` the FR ids the coverage map assigns to that epic. Where `epics.md` states what one epic needs from another, write `after = [{ epic = , needs = "..." }]` there and `after: [epic-]` in the dependent epic's file, which holds every ticket under it until the named epic is done. +Initiative envelope: `initiative-/initiative-.md` from the ticket template. Title from the project name; Description and Outcome from the PRD's summary; Requirements one line per PRD requirement, `R (FR-m): `, numbered from 1, and no `covers`; Constraints the PRD's constraints; Done when from the PRD's goals; References naming the PRD, architecture, and UX folders. Its `tickets.toml` gets one `[[epic]]` per `## Epic N:` in `epics.md`, in that order: `id = N`, `slug = "epic-"`, `title`, `covers` the initiative's `R` ids whose FR the coverage map assigns to that epic. Where `epics.md` states what one epic needs from another, write `after = [{ epic = , needs = "..." }]` there and `after: [epic-]` in the dependent epic's file, which holds every ticket under it until the named epic is done. -Each epic: `epic-/epic-.md` from the epic template. The slug is the epic title, kebab-case, without its number. Description and Outcome from the epic goal; Requirements from the FR lines the coverage map assigns to it, each keeping its id; Done when written from those requirements and confirmed with the user; `parent` the initiative folder; `status` from `sprint-status.yaml`, `in-progress` and `done` as they are and no `status` line for `backlog`. An epic's v6 retro reports combine into `epic--retrospective.md` in its folder, the name `bmad-retrospective` reads, and an `epic-N-retrospective: done` line becomes a `Retrospective:` line in the epic's Notes naming it. +Each epic: `epic-/epic-.md` from the ticket template. The slug is the epic title, kebab-case, without its number. Description and Outcome from the epic goal; Requirements the initiative lines the coverage map assigns to it, `R (parent R): `, in the PRD's words; Constraints the initiative's constraints that bind it; Done when written from those requirements and confirmed with the user; `parent` the initiative folder; `status` from `sprint-status.yaml`, `in-progress` and `done` as they are and no `status` line for `backlog`. An epic's v6 retro reports combine into `epic--retrospective.md` in its folder, the name `bmad-retrospective` reads, and an `epic-N-retrospective: done` line becomes a `Retrospective:` line in the epic's Notes naming it. -Each `### Story N.M:` becomes an `[[entry]]` in that epic's `tickets.toml`, in the order written: `id = M` as v6 wrote it, quoted when it has a letter (`id = "6a"` for Story 2.6a), `type = "story"`, `title`, `description` (the "I want" sentence, restated as what exists when it is done), `verify` (one line distilled from its acceptance criteria), `covers` (the FR ids its criteria trace to, from the coverage map), `after = []` unless `epics.md` states that the story needs another, `hitl = false`, `risk = "low"` unless the text says otherwise, and `v6_key = "N-M-"`, the v6 key, kept for search. An epic or story heading with no number gets the next unused number, in the order written. A story file with no matching `### Story` gets an entry written from the file's own intent, with the next unused `id`, and the plan flags it. +Each `### Story N.M:` becomes an `[[entry]]` in that epic's `tickets.toml`, in the order written: `id = M` as v6 wrote it, quoted when it has a letter (`id = "6a"` for Story 2.6a), `type = "story"`, `title`, `description` (the "I want" sentence, restated as what exists when it is done), `verify` (one line distilled from its acceptance criteria), `covers` (the epic's `R` ids its criteria trace to, through the coverage map), `after = []` unless `epics.md` states that the story needs another, `hitl = false`, `risk = "low"` unless the text says otherwise, and `v6_key = "N-M-"`, the v6 key, kept for search. An epic or story heading with no number gets the next unused number, in the order written. A story file with no matching `### Story` gets an entry written from the file's own intent, with the next unused `id`, and the plan flags it. ### Build records @@ -148,7 +150,11 @@ Each `open` or `in-progress` item in `sprint-status.yaml`'s `action_items` becom ## Spec folders with stories.yaml -A `spec-/` holding `stories.yaml` and `stories/` that belongs to the initiative becomes an epic: create `epic-/` with the spec folder inside it as its requirement source; the epic's and each entry's `covers` cite the spec's `CAP-N` ids. Each `stories.yaml` entry becomes an `[[entry]]` in list order with its `id`, `title`, `description`, original criteria, and verification. An id with a dash loses it (`3-2` becomes `32`, `CC-2` becomes `CC2`; the next unused number on a clash), and the original goes in `v6_key`. Each declared prerequisite becomes an `after` naming the migrated sibling id, cross-epic entry, or epic slug; report any that do not resolve for correction without dropping them, and never infer one from list order. The build record rules apply to its entries: each `stories/-.md`, matched by its original id, moves as `story--plan.md`, and an entry with no record gets a plan only when tracking shows a later status. `RETROSPECTIVE.md` follows the retrospective rule. Move `stories.yaml` to `archive-v6/` unchanged. Add the epic to the initiative's `tickets.toml`; its `status` is `done` when every story is. +A `spec-/` holding `stories.yaml` and `stories/` that belongs to the initiative becomes an epic: create `epic-/` and render `SPEC.md` into `epic-.md`: each `CAP-N` becomes a Requirements line `R (CAP-N): `, in order, its constraints become Constraints, its non-goals go to Boundaries, and its companions move beside the epic file, each a References line; each entry's `covers` cites the `R` ids. Each `stories.yaml` entry becomes an `[[entry]]` in list order with its `id`, `title`, `description`, original criteria, and verification. An id with a dash loses it (`3-2` becomes `32`, `CC-2` becomes `CC2`; the next unused number on a clash), and the original goes in `v6_key`. Each declared prerequisite becomes an `after` naming the migrated sibling id, cross-epic entry, or epic slug; report any that do not resolve for correction without dropping them, and never infer one from list order. The build record rules apply to its entries: each `stories/-.md`, matched by its original id, moves as `story--plan.md`, and an entry with no record gets a plan only when tracking shows a later status. `RETROSPECTIVE.md` follows the retrospective rule. Move `stories.yaml` and `SPEC.md` to `archive-v6/` unchanged. Add the epic to the initiative's `tickets.toml`; its `status` is `done` when every story is. + +## Pre-release spec folders + +A `spec-/` holding `spec-.md` and `.memlog.md` is a 6.13 pre-release spec; the initiative, epic or story file is the spec at its level now. Run `uv run /scripts/migrate_v6.py --project-root --skill --fold-specs ` once. A folder inside a ticket folder folds into that ticket: the capabilities become Requirements lines, `CAP-` mapped to `R` after any `R` lines the ticket has, in the ticket, its `tickets.toml` and its children; the constraints become Constraints, the non-goals Boundaries lines, the assumptions and open questions Notes lines; the memlog entries are appended to the ticket's `.memlog.md` in order, with the why, the success signal and the fold recorded after them; the companions move beside the ticket as References lines; every reference to the folder in the store is rewritten to the new paths; and the folder is removed. A folder at `` becomes `backlog/story-.md` the same way, its memlog and companions in `backlog/`. The script prints each fold with its id map, the files it rewrote and the companion paths it could not resolve, and `problems` for a folder it left alone (a companion whose name is taken, a folder outside a ticket); fix those by hand and run it again. Record the folds in the plan. ## Loose implementation files @@ -166,13 +172,13 @@ These stay where they are, listed in the plan and the report with the reason: ## Path rewrites -Search the store for each old path and rewrite live references to it: `companions:`, `inputDocuments:`, References sections, prose links. This is a search, not a read of every document. Every moved file outside `archive-v6/` and `archive/` also has its own relative links and paths recomputed from its new folder, so they reach the same target whether it moved or not. Archived files and files left in place stay unchanged. +Search the store for each old path and rewrite live references to it: `inputDocuments:`, References sections, prose links. This is a search, not a read of every document. Every moved file outside `archive-v6/` and `archive/` also has its own relative links and paths recomputed from its new folder, so they reach the same target whether it moved or not. Archived files and files left in place stay unchanged. Then search the rest of the project, outside the store and `_bmad/`, for the old source folder paths. Never edit those files: list each one, with its line, in the plan and the report as a reference the user updates. ## Configuration and cleanup -Record `active_initiative = "initiative-"` under `[core]` in `_bmad/custom/config.user.toml`, creating the file if needed. When the store moved (`workspace`), set `output_folder` under `[core]` in `_bmad/custom/config.toml`, the committed team override, never in the installer-managed `_bmad/config.toml`. A `_bmad/custom/` override that names a moved path, such as a `persistent_facts` line, is shown in the plan with its new path and rewritten on approval. Leave `planning_artifacts` and `implementation_artifacts` alone; `bmad setup` owns the rest of `_bmad/`. The repo store needs no store config. +Record `active_initiative = "initiative-"` under `[core]` in `_bmad/custom/config.user.toml`, creating the file if needed. When the store moved (`workspace`), set `output_folder` under `[core]` in `_bmad/custom/config.toml`, the committed team override, never in the installer-managed `_bmad/config.toml`. A `_bmad/custom/` override that names a moved path, such as a `persistent_facts` line, is shown in the plan with its new path and rewritten on approval. Leave `planning_artifacts` and `implementation_artifacts` alone; `bmad setup` owns the rest of `_bmad/`. Its v6 cleanup (`migrate_v6.py --clean`) drops the customization keys the retired skills read, `initiative_template`, `epic_template`, `story_template`, `spec_template`, `spec_output_path`, `run_folder_pattern` and `on_complete`, from `_bmad/custom/bmad-agent-po.toml` and its `.user` file, and prints a note naming `ticket_template` and `_after` in their place; run it when setup has not. The repo store needs no store config. Remove empty v6 folders left behind, and add a `.gitkeep` to any created folder that is empty. @@ -180,7 +186,7 @@ Remove empty v6 folders left behind, and add a `.gitkeep` to any created folder Set the plan's `status` to `done`. When the migration created the store's repository, offer its initial commit now; declined, say the tree is uncommitted. -Report the tree with `uv run /_bmad/method/scripts/tickets.py --project-root status `, the archived sources, the files left in place, and missing baselines. Name the backup folder and say it is the user's to delete once they are satisfied, or now if version control already holds the old state. Say that `bmad-ticket` replaces the removed `bmad-sprint-planning` and `bmad-create-epics-and-stories`. Next: `bmad-build` on the next entry, with no story file pulled; `bmad-code-review` on a historical plan, leaving its status; `bmad-retrospective` on the epic; `bmad-project-context` for a root `AGENTS.md` naming the active initiative and any workspace conventions. Standalone intent remains valid build input. +Report the tree with `uv run /_bmad/method/scripts/tickets.py --project-root status `, the archived sources, the files left in place, and missing baselines. Name the backup folder and say it is the user's to delete once they are satisfied, or now if version control already holds the old state. Say that Sofia, the Product Owner (`bmad-agent-po`), replaces `bmad-sprint-planning`, `bmad-create-epics-and-stories`, `bmad-ticket`, `bmad-spec` and `bmad-correct-course`. Next: `bmad-build` on the next entry, with no story file pulled; `bmad-code-review` on a historical plan, leaving its status; `bmad-retrospective` on the epic; `bmad-project-context` for a root `AGENTS.md` naming the active initiative and any workspace conventions. Standalone intent remains valid build input. """ # What the repository answers in `questions` mean in files. Each is shown as a target tree in the plan and done @@ -201,8 +207,9 @@ checklist = [ "`uv run /_bmad/method/scripts/tickets.py --project-root status ` exits 0 for every initiative; story counts match the archived tracking sources, excluding epic and retrospective keys, with backlog counted separately.", "Every classic or spec story is one entry, every story's build records one plan (or, when folded, its entry plus archived files), and no epic story files were written. `find` returns each expected plan, joined by id in an epic and by file stem in backlog.", "Every plan has a build type and a mapped status; done stays done, review stays in-review, nothing is relabeled built, supported baselines are kept, and missing ones are disclosed. Retrospectives sit directly in their epic with `epic`, only an evidenced verdict, and no ticket or leaf type.", - "Every epic `covers` id exists in the initiative's requirement source, every entry `covers` id in its epic's Requirements, and every requirement in the PRD's coverage map has at least one entry.", - "Every live path in `companions:`, `inputDocuments:`, References sections, and prose links resolves to a file from the folder that holds it now; archived files and files left in place keep their original paths. A path already dead in v6 is listed apart and does not fail the item; one the migration broke does.", + "Every epic `covers` id is a line in the initiative's Requirements, every entry `covers` id one in its epic's Requirements, and every requirement in the PRD's coverage map has at least one entry.", + "No `spec-/` folder holding `spec-.md` remains under the output folder; each ticket it folded into holds the spec's lines in Requirements and Constraints, its companions beside it as References lines, and one `.memlog.md` with the spec's entries in order, and no `CAP-` id remains outside `archive-v6/`, `archive/`, a Requirements line's parentheses or `.memlog.md`.", + "Every live path in `inputDocuments:`, References sections, and prose links resolves to a file from the folder that holds it now; archived files and files left in place keep their original paths. A path already dead in v6 is listed apart and does not fail the item; one the migration broke does.", "`_bmad/custom/config.user.toml` names the active initiative under `[core]`, and `core.output_folder` resolves to the store.", "The store is under git as the user answered, no file is tracked by two repositories, and in a new workspace `bmad status` at its root reports the installation current.", "`.v6-v7-migration-backup-/` holds every file the source folders held before the migration, and no commit includes it.", diff --git a/skills/bmod-method/roster.toml b/skills/bmod-method/roster.toml index 774b98ce5..11bafa99b 100644 --- a/skills/bmod-method/roster.toml +++ b/skills/bmod-method/roster.toml @@ -24,6 +24,14 @@ icon = "📋" title = "Product Manager" persona = "Drives Jobs-to-be-Done over template filling, user value first, technical feasibility is a constraint not the driver. Speaks like a detective interrogating a cold case: short questions, sharper follow-ups, every 'why?' tightening the net." +[[members]] +code = "bmad-agent-po" +skill = "bmad-agent-po" +name = "Sofia" +icon = "🎯" +title = "Product Owner" +persona = "Owns what each ticket requires, how the work is split and ordered, what is accepted as done, and what changes when the plan meets reality; every line on the backlog names where it came from. Speaks plain and short: what is in, what is out, and why — says no early and kindly, with the reason, and asks only what is unsettled, with the answer she would pick." + [[members]] code = "bmad-agent-ux-designer" skill = "bmad-agent-ux-designer" diff --git a/tools/tests/test_bmad_setup.py b/tools/tests/test_bmad_setup.py index ff9c7c386..c606bf3f7 100644 --- a/tools/tests/test_bmad_setup.py +++ b/tools/tests/test_bmad_setup.py @@ -3314,7 +3314,7 @@ class BmadRetiredSkillTests(unittest.TestCase): self.tool_skills = self.project / ".claude" / "skills" def method( - self, *, renamed=(), removed=(), skills=("bmad-ticket", "bmad-code-review"), update_source=BMAD_SOURCE + self, *, renamed=(), removed=(), skills=("bmad-new", "bmad-code-review"), update_source=BMAD_SOURCE ) -> None: write_bmod(self.installed, "bmod-method", "method", skills=skills, update_source=update_source) write(self.installed / "bmod-method" / "retired.toml", retired_toml(renamed, removed)) @@ -3324,7 +3324,7 @@ class BmadRetiredSkillTests(unittest.TestCase): return self.tool_skills / name def test_a_renamed_skill_is_offered_for_removal_and_its_customization_moves(self): - self.method(renamed=(("bmad-preview-ticketing", "bmad-ticket"),)) + self.method(renamed=(("bmad-preview-ticketing", "bmad-new"),)) self.tool_skill("bmad-preview-ticketing") custom = self.project / "_bmad" / "custom" write(custom / "bmad-preview-ticketing.toml", "[workflow]\nkeep = true\n") @@ -3340,7 +3340,7 @@ class BmadRetiredSkillTests(unittest.TestCase): { "skill": "bmad-preview-ticketing", "module": "method", - "renamed_to": "bmad-ticket", + "renamed_to": "bmad-new", "paths": [".claude/skills/bmad-preview-ticketing"], "global": False, } @@ -3348,18 +3348,18 @@ class BmadRetiredSkillTests(unittest.TestCase): ) self.assertEqual( report["custom_renames"], - [{"from": "_bmad/custom/bmad-preview-ticketing.toml", "to": "_bmad/custom/bmad-ticket.toml"}], + [{"from": "_bmad/custom/bmad-preview-ticketing.toml", "to": "_bmad/custom/bmad-new.toml"}], ) - self.assertEqual((custom / "bmad-ticket.toml").read_text(), "[workflow]\nkeep = true\n") + self.assertEqual((custom / "bmad-new.toml").read_text(), "[workflow]\nkeep = true\n") self.assertFalse((custom / "bmad-preview-ticketing.toml").exists()) self.assertEqual( report["install_offers"], [ { - "skill": "bmad-ticket", + "skill": "bmad-new", "replaces": "bmad-preview-ticketing", "module": "method", - "install": "npx skills add bmad-code-org/BMAD-METHOD --skill bmad-ticket", + "install": "npx skills add bmad-code-org/BMAD-METHOD --skill bmad-new", } ], ) @@ -3411,22 +3411,22 @@ class BmadRetiredSkillTests(unittest.TestCase): self.assertTrue((custom / "bmad-gone.toml").is_file()) def test_an_existing_new_customization_is_never_overwritten(self): - self.method(renamed=(("bmad-old", "bmad-ticket"),)) + self.method(renamed=(("bmad-old", "bmad-new"),)) custom = self.project / "_bmad" / "custom" write(custom / "bmad-old.user.toml", "old = 1\n") - write(custom / "bmad-ticket.user.toml", "new = 1\n") + write(custom / "bmad-new.user.toml", "new = 1\n") report = setup_report(self, self.project, self.skill) self.assertEqual(report["custom_renames"], []) self.assertEqual( report["custom_not_renamed"], - [{"from": "_bmad/custom/bmad-old.user.toml", "to": "_bmad/custom/bmad-ticket.user.toml"}], + [{"from": "_bmad/custom/bmad-old.user.toml", "to": "_bmad/custom/bmad-new.user.toml"}], ) self.assertEqual((custom / "bmad-old.user.toml").read_text(), "old = 1\n") - self.assertEqual((custom / "bmad-ticket.user.toml").read_text(), "new = 1\n") + self.assertEqual((custom / "bmad-new.user.toml").read_text(), "new = 1\n") def test_two_renames_to_one_name_never_overwrite_a_customization(self): - self.method(renamed=(("bmad-a", "bmad-ticket"), ("bmad-b", "bmad-ticket"))) + self.method(renamed=(("bmad-a", "bmad-new"), ("bmad-b", "bmad-new"))) custom = self.project / "_bmad" / "custom" write(custom / "bmad-a.user.toml", "a = 1\n") write(custom / "bmad-b.user.toml", "b = 1\n") @@ -3434,19 +3434,19 @@ class BmadRetiredSkillTests(unittest.TestCase): report = setup_report(self, self.project, self.skill) self.assertEqual( report["custom_renames"], - [{"from": "_bmad/custom/bmad-a.user.toml", "to": "_bmad/custom/bmad-ticket.user.toml"}], + [{"from": "_bmad/custom/bmad-a.user.toml", "to": "_bmad/custom/bmad-new.user.toml"}], ) self.assertEqual( report["custom_not_renamed"], - [{"from": "_bmad/custom/bmad-b.user.toml", "to": "_bmad/custom/bmad-ticket.user.toml"}], + [{"from": "_bmad/custom/bmad-b.user.toml", "to": "_bmad/custom/bmad-new.user.toml"}], ) - self.assertEqual((custom / "bmad-ticket.user.toml").read_text(), "a = 1\n") + self.assertEqual((custom / "bmad-new.user.toml").read_text(), "a = 1\n") self.assertEqual((custom / "bmad-b.user.toml").read_text(), "b = 1\n") def test_no_install_is_offered_when_the_new_name_is_in_another_active_root(self): - self.method(renamed=(("bmad-old", "bmad-ticket"),)) + self.method(renamed=(("bmad-old", "bmad-new"),)) self.tool_skill("bmad-old") - self.tool_skill("bmad-ticket") + self.tool_skill("bmad-new") report = setup_report(self, self.project, self.skill, "--root", str(self.tool_skills)) self.assertEqual(report["install_offers"], []) @@ -3458,7 +3458,7 @@ class BmadRetiredSkillTests(unittest.TestCase): self.assertEqual(result.returncode, 2) def test_status_reports_the_same_and_changes_nothing(self): - self.method(renamed=(("bmad-old", "bmad-ticket"),), update_source="file:skills") + self.method(renamed=(("bmad-old", "bmad-new"),), update_source="file:skills") self.tool_skill("bmad-old") write(self.project / "_bmad" / "custom" / "bmad-old.toml", "x = 1\n") before = snapshot(self.project) @@ -3526,10 +3526,10 @@ class BmadRetiredSkillTests(unittest.TestCase): installed, "bmod-method", "method", - skills=("bmad-ticket",), + skills=("bmad-new",), update_source=BMAD_SOURCE, ) - write(installed / "bmod-method" / "retired.toml", retired_toml((("bmad-old", "bmad-ticket"),), ())) + write(installed / "bmod-method" / "retired.toml", retired_toml((("bmad-old", "bmad-new"),), ())) write(installed / "bmad-old" / "SKILL.md", "x\n") lock = home / ".agents" / ".skill-lock.json" write(lock, json.dumps({"version": 3, "skills": {"bmad-old": {}, "bmad": {}}}, indent=2)) @@ -3551,7 +3551,7 @@ class BmadRetiredSkillTests(unittest.TestCase): self.assertEqual(entry["paths"], ["~/.claude/skills/bmad-old"]) self.assertTrue(entry["global"]) (offer,) = report["install_offers"] - self.assertEqual(offer["install"], "npx skills add bmad-code-org/BMAD-METHOD --skill bmad-ticket -g") + self.assertEqual(offer["install"], "npx skills add bmad-code-org/BMAD-METHOD --skill bmad-new -g") removal = run("--remove-retired", "bmad-old") self.assertEqual(removal["removed"], ["~/.claude/skills/bmad-old"]) @@ -3561,7 +3561,7 @@ class BmadRetiredSkillTests(unittest.TestCase): self.assertFalse((installed / "bmad-old").exists()) def test_only_retired_names_can_be_removed(self): - self.method(removed=("bmad-old",), skills=("bmad-ticket", "bmad-code-review")) + self.method(removed=("bmad-old",), skills=("bmad-new", "bmad-code-review")) self.tool_skill("bmad-code-review") self.tool_skill("bmad-old") write(self.project / "skills-lock.json", "{ not json") diff --git a/tools/tests/test_migrate_v6.py b/tools/tests/test_migrate_v6.py index 29d2ed5e0..b5d43feb9 100644 --- a/tools/tests/test_migrate_v6.py +++ b/tools/tests/test_migrate_v6.py @@ -37,10 +37,10 @@ class MigrateV6Tests(unittest.TestCase): self.installed, "bmod-method", "method", - skills=("bmad-ticket",), + skills=("bmad-agent-po",), questions=({"key": "store", "prompt": "Store?", "default": "repo"},), ) - write(self.installed / "bmad-ticket" / "SKILL.md", "---\nname: bmad-ticket\n---\n") + write(self.installed / "bmad-agent-po" / "SKILL.md", "---\nname: bmad-agent-po\n---\n") setup_report(self, self.project, self.skill, *module_answers_args(self.project, {"method": {"store": "repo"}})) self.bmad = self.project / "_bmad" @@ -66,7 +66,7 @@ class MigrateV6Tests(unittest.TestCase): self.bmad / "custom" / "config.user.toml", '# mine\n[core]\nactive_initiative = "initiative-a"\ncommunication_language = "Hungarian"\n', ) - write(self.bmad / "custom" / "bmad-ticket.toml", "[workflow]\nkeep = true\n") + write(self.bmad / "custom" / "bmad-agent-po.toml", "[workflow]\nkeep = true\n") write(self.bmad / "custom" / "bmad-sprint-planning.user.toml", "[workflow]\nold = true\n") write(self.bmad / "custom" / "ticketing-store-config.toml", "kind = 'repo'\n") @@ -101,7 +101,7 @@ class MigrateV6Tests(unittest.TestCase): self.assertFalse((self.bmad / "core").exists()) self.assertFalse((self.bmad / "config.user.toml").exists()) self.assertFalse((self.bmad / "custom" / "bmad-sprint-planning.user.toml").exists()) - self.assertTrue((self.bmad / "custom" / "bmad-ticket.toml").exists()) + self.assertTrue((self.bmad / "custom" / "bmad-agent-po.toml").exists()) self.assertTrue((self.bmad / "custom" / "ticketing-store-config.toml").exists()) user = (self.bmad / "custom" / "config.user.toml").read_text(encoding="utf-8") self.assertEqual(user, '# mine\n[core]\nactive_initiative = "initiative-a"\n') @@ -187,6 +187,46 @@ class MigrateV6Tests(unittest.TestCase): self.assertEqual(report["legacy_leftovers"], []) self.assertTrue((outside / "v6-shims" / "shim.md").exists()) + def test_retired_customization_keys_are_dropped_with_a_note(self): + write( + self.installed / "bmod-method" / "retired.toml", 'renamed = [{ from = "bmad-old", to = "bmad-agent-po" }]\n' + ) + custom = self.bmad / "custom" + write( + custom / "bmad-agent-po.toml", + '[workflow]\n# mine\nstory_template = "x.md"\npublication = "on_start"\non_complete = "say done"\n', + ) + write(custom / "bmad-agent-po.user.toml", '[workflow]\nspec_output_path = "{output_folder}"\n') + write(custom / "bmad-old.toml", '[workflow]\nrun_folder_pattern = "x-{slug}"\n') + + report = self.migrate() + self.assertEqual( + report["stale_config_keys"], + [ + {"file": "_bmad/custom/bmad-agent-po.toml", "key": "workflow.story_template"}, + {"file": "_bmad/custom/bmad-agent-po.toml", "key": "workflow.on_complete"}, + {"file": "_bmad/custom/bmad-agent-po.user.toml", "key": "workflow.spec_output_path"}, + {"file": "_bmad/custom/bmad-old.toml", "key": "workflow.run_folder_pattern"}, + ], + ) + + cleaned = self.migrate("--clean") + self.assertEqual( + cleaned["removed_keys"], + report["stale_config_keys"], + ) + self.assertEqual(len(cleaned["notes"]), 3) + self.assertIn("_bmad/custom/bmad-agent-po.toml: story_template, on_complete", cleaned["notes"][0]) + self.assertIn("ticket_template", cleaned["notes"][0]) + self.assertIn("_after", cleaned["notes"][0]) + self.assertEqual( + (custom / "bmad-agent-po.toml").read_text(encoding="utf-8"), + '[workflow]\n# mine\npublication = "on_start"\n', + ) + self.assertEqual(tomllib.loads((custom / "bmad-agent-po.user.toml").read_text(encoding="utf-8")), {}) + self.assertEqual(tomllib.loads((custom / "bmad-old.toml").read_text(encoding="utf-8")), {}) + self.assertEqual(self.migrate()["notes"], []) + def test_every_classic_installer_trace_is_listed(self): leftovers = ( "_config/manifest.yaml", @@ -216,3 +256,392 @@ class MigrateV6Tests(unittest.TestCase): "bmm/v6-shims", ], ) + + +def spec_frontmatter(**lists: str) -> str: + """The pre-release spec's frontmatter. Built, not spelled out: the retired keys must not appear as text outside the migration rules.""" + return "---\nid: SPEC-ball\n" + "".join(f"{key}: {value}\n" for key, value in lists.items()) + "---\n" + + +SPEC_BODY = """ +# Ball flight physics + +## Why + +Players leave when the ball flies wrong. + +## Capabilities + +- **CAP-1** + - **intent:** A player sees the ball follow a physically plausible arc. + - **success:** Carry distance within 3% of the reference table. +- **CAP-2** + - **intent:** A player sets the launch angle and sees the arc change. + - **success:** The arc updates within one frame. + +## Constraints + +- Simulation runs at 120 Hz on the minimum device. + +## Non-goals + +- Wind. + +## Success signal + +- A tester cannot tell the arc from the reference footage. + +## Assumptions + +- Assumed metric units. + +## Open Questions + +- Is spin in scope for CAP-2? +""" + +SPEC_BALL = ( + spec_frontmatter(companions="[glossary.md, ../../ux-golf/DESIGN.md]", sources="[../../prd-golf/prd-golf.md]") + + SPEC_BODY +) + +MEMLOG_BALL = """\ +--- +topic: Ball flight physics +updated: 2026-09-01T10:00 +--- + +- (capability) CAP-1 plausible arc +- (decision) metric units +- (capability) CAP-2 launch angle +""" + +EPIC_BALL = """\ +--- +type: epic +title: "Ball flight" +parent: initiative-golf +covers: [R2] +after: [] +assignee: "" +risk: medium +--- + +# Ball flight + +## Description + +The spec at spec-ball/ owns the capabilities. + +## Outcome + +Players see a realistic flight; the spec's success signal is the measure. + +## Requirements + +See spec-ball/spec-ball.md. + +## Done when + +1. Every capability works end to end. + +## Boundaries + +The physics engine. Not rendering (epic-render). + +## References + +- parent — _bmad-output/initiative-golf/initiative-golf.md, section Requirements +- spec — _bmad-output/initiative-golf/epic-ball/spec-ball/spec-ball.md + +## Notes + +- Decision: metric units (2026-09-01). +""" + + +def memlog_entries(path: Path) -> list[str]: + body = path.read_text(encoding="utf-8").split("---\n", 2)[2] + return [line for line in body.splitlines() if line.startswith("- ")] + + +def reference_paths(path: Path) -> list[str]: + section = path.read_text(encoding="utf-8").split("## References\n", 1)[1].split("\n## ", 1)[0] + return [line.split(" — ", 1)[1].split(",", 1)[0].strip() for line in section.splitlines() if line.startswith("- ")] + + +class FoldSpecsTests(unittest.TestCase): + def setUp(self): + temp = tempfile.TemporaryDirectory() + self.addCleanup(temp.cleanup) + self.project = Path(temp.name).resolve() / "project" + self.store = self.project / "_bmad-output" + self.initiative = self.store / "initiative-golf" + write(self.initiative / "initiative-golf.md", "---\ntype: initiative\n---\n\n# Golf\n") + write(self.initiative / "ux-golf" / "DESIGN.md", "design\n") + write(self.initiative / "prd-golf" / "prd-golf.md", "prd\n") + + def fold(self) -> dict: + command = [ + sys.executable, + str(MIGRATE_V6_PY), + "--project-root", + str(self.project), + "--skill", + str(self.project), + ] + result = subprocess.run( + [*command, "--fold-specs", str(self.store)], text=True, capture_output=True, check=False + ) + self.assertEqual(result.returncode, 0, msg=result.stderr) + return json.loads(result.stdout) + + def assert_references_resolve(self, ticket: Path) -> None: + for location in reference_paths(ticket): + self.assertTrue( + (ticket.parent / location).exists() or (self.project / location).exists(), + msg=f"{ticket.name}: {location} does not resolve", + ) + + def write_epic_with_spec(self) -> Path: + epic = self.initiative / "epic-ball" + write(epic / "epic-ball.md", EPIC_BALL) + write( + epic / "tickets.toml", + '[[entry]]\nid = 1\ntype = "story"\ntitle = "Arc"\ncovers = ["CAP-1"]\n\n' + '[[entry]]\nid = 2\ntype = "story"\ntitle = "Launch angle"\ncovers = ["CAP-2"]\n', + ) + write( + epic / "story-launch-angle.md", + "---\nid: 2\ntype: story\ncovers: [CAP-2]\n---\n\n# Launch angle\n\n## References\n\n" + "- spec — spec-ball/spec-ball.md, CAP-2\n", + ) + write(epic / "spec-ball" / "spec-ball.md", SPEC_BALL) + write(epic / "spec-ball" / ".memlog.md", MEMLOG_BALL) + write(epic / "spec-ball" / "glossary.md", "Carry: distance in the air.\n") + return epic + + def test_a_spec_folder_inside_a_ticket_folds_into_it(self): + epic = self.write_epic_with_spec() + + report = self.fold() + + (fold,) = report["folded"] + self.assertEqual(report["problems"], []) + self.assertEqual(fold["ids"], {"CAP-1": "R1", "CAP-2": "R2"}) + self.assertFalse((epic / "spec-ball").exists()) + self.assertEqual((epic / "glossary.md").read_text(encoding="utf-8"), "Carry: distance in the air.\n") + self.assertEqual( + memlog_entries(epic / ".memlog.md"), + [ + "- (capability) CAP-1 plausible arc", + "- (decision) metric units", + "- (capability) CAP-2 launch angle", + "- (note) why, from spec-ball: Players leave when the ball flies wrong.", + "- (note) success signal, from spec-ball: A tester cannot tell the arc from the reference footage.", + "- (event) folded spec-ball/ into epic-ball.md; CAP-1 is R1, CAP-2 is R2", + ], + ) + text = (epic / "epic-ball.md").read_text(encoding="utf-8") + self.assertIn("covers: [R2]\nafter: []", text) + self.assertIn("sources: [_bmad-output/initiative-golf/prd-golf/prd-golf.md]\n---", text) + self.assertIn( + "## Requirements\n\n" + "- R1 (CAP-1): A player sees the ball follow a physically plausible arc. " + "Success: Carry distance within 3% of the reference table.\n" + "- R2 (CAP-2): A player sets the launch angle and sees the arc change. " + "Success: The arc updates within one frame.\n\n" + "## Constraints\n\n- Simulation runs at 120 Hz on the minimum device.\n\n## Done when", + text, + ) + self.assertIn("Not rendering (epic-render).\n\n- Non-goal: Wind.\n\n## References", text) + self.assertIn( + "## References\n\n" + "- parent — _bmad-output/initiative-golf/initiative-golf.md, section Requirements\n" + "- companion — glossary.md\n" + "- design — _bmad-output/initiative-golf/ux-golf/DESIGN.md\n\n## Notes", + text, + ) + self.assertIn( + "- Decision: metric units (2026-09-01).\n- Assumption: Assumed metric units.\n- Open question: Is spin in scope for R2?\n", + text, + ) + self.assertNotIn("CAP-", (epic / "tickets.toml").read_text(encoding="utf-8")) + self.assertIn('covers = ["R2"]', (epic / "tickets.toml").read_text(encoding="utf-8")) + story = (epic / "story-launch-angle.md").read_text(encoding="utf-8") + self.assertIn("covers: [R2]", story) + self.assertIn("- spec — epic-ball.md, R2\n", story) + self.assert_references_resolve(epic / "epic-ball.md") + self.assert_references_resolve(epic / "story-launch-angle.md") + self.assertEqual(self.fold()["folded"], []) + + def test_the_spec_ids_follow_the_requirements_a_ticket_already_has(self): + epic = self.write_epic_with_spec() + write( + epic / "epic-ball.md", + EPIC_BALL.replace("See spec-ball/spec-ball.md.", "- R1 (parent R2): The arc is real."), + ) + + (fold,) = self.fold()["folded"] + + self.assertEqual(fold["ids"], {"CAP-1": "R2", "CAP-2": "R3"}) + text = (epic / "epic-ball.md").read_text(encoding="utf-8") + self.assertIn("- R1 (parent R2): The arc is real.\n- R2 (CAP-1): ", text) + self.assertIn('covers = ["R3"]', (epic / "tickets.toml").read_text(encoding="utf-8")) + + def test_the_tickets_own_lines_stay_when_prose_beside_them_names_the_folder(self): + epic = self.write_epic_with_spec() + write( + epic / "epic-ball.md", + EPIC_BALL.replace( + "See spec-ball/spec-ball.md.", + "- R1 (parent R2): The arc is real.\n\nThe rest is in spec-ball/spec-ball.md.", + ), + ) + + (fold,) = self.fold()["folded"] + + self.assertEqual(fold["ids"], {"CAP-1": "R2", "CAP-2": "R3"}) + text = (epic / "epic-ball.md").read_text(encoding="utf-8") + self.assertIn("## Requirements\n\n- R1 (parent R2): The arc is real.\n- R2 (CAP-1): ", text) + self.assertNotIn("spec-ball", text.split("## Requirements", 1)[1].split("## Constraints", 1)[0]) + self.assertIn('covers = ["R3"]', (epic / "tickets.toml").read_text(encoding="utf-8")) + + def test_requirements_a_ticket_wrote_in_another_form_are_kept(self): + epic = self.write_epic_with_spec() + write( + epic / "epic-ball.md", + EPIC_BALL.replace( + "See spec-ball/spec-ball.md.", "- E1 (R2): The arc is real.\n- E2 (R2): The angle is set." + ), + ) + + (fold,) = self.fold()["folded"] + + self.assertEqual(fold["ids"], {"CAP-1": "R1", "CAP-2": "R2"}) + text = (epic / "epic-ball.md").read_text(encoding="utf-8") + self.assertIn("- E1 (R2): The arc is real.\n- E2 (R2): The angle is set.\n- R1 (CAP-1): ", text) + + def test_dot_files_in_the_spec_folder_are_not_companions(self): + epic = self.write_epic_with_spec() + write(epic / "spec-ball" / ".DS_Store", "") + write(epic / ".DS_Store", "") + + report = self.fold() + + (fold,) = report["folded"] + self.assertEqual(report["problems"], []) + self.assertEqual(fold["companions"], ["_bmad-output/initiative-golf/epic-ball/glossary.md"]) + self.assertFalse((epic / "spec-ball").exists()) + + def test_a_loose_spec_folder_becomes_a_backlog_story(self): + loose = self.store / "spec-caddie" + write( + loose / "spec-caddie.md", + spec_frontmatter(companions="[notes.md]", sources="[]") + + SPEC_BODY.replace("# Ball flight physics", "# Caddie advice"), + ) + write(loose / ".memlog.md", MEMLOG_BALL) + write(loose / "notes.md", "notes\n") + write(self.store / "backlog" / "bug-slice.md", "---\nid: 4\ntype: bug\n---\n\n# Slice\n") + write( + self.initiative / "initiative-golf.md", + "---\ntype: initiative\n---\n\n# Golf\n\n## References\n\n- spec — _bmad-output/spec-caddie/spec-caddie.md\n", + ) + + report = self.fold() + + (fold,) = report["folded"] + self.assertEqual(fold["ticket"], "_bmad-output/backlog/story-caddie.md") + self.assertFalse(loose.exists()) + story = self.store / "backlog" / "story-caddie.md" + text = story.read_text(encoding="utf-8") + self.assertTrue( + text.startswith( + '---\nid: 5\ntype: story\ntitle: "Caddie advice"\nparent: none\ncovers: []\nsources: []\nafter: []\n' + "refined: false\nhitl: false\nrisk: low\n---\n\n# Caddie advice\n\n## Description\n\n" + "Players leave when the ball flies wrong.\n\n## Requirements\n\n- R1 (CAP-1): " + ), + msg=text, + ) + self.assertIn("## Constraints\n\n- Simulation runs", text) + self.assertIn( + "## Boundaries\n\n- Non-goal: Wind.\n\n## References\n\n- companion — notes.md\n\n## Notes\n\n- Assumption:", + text, + ) + self.assertNotIn("## Outcome", text) + self.assertEqual((self.store / "backlog" / "notes.md").read_text(encoding="utf-8"), "notes\n") + entries = memlog_entries(self.store / "backlog" / ".memlog.md") + self.assertEqual( + entries[:3], + ["- (capability) CAP-1 plausible arc", "- (decision) metric units", "- (capability) CAP-2 launch angle"], + ) + self.assertEqual(entries[-1], "- (event) folded spec-caddie/ into story-caddie.md; CAP-1 is R1, CAP-2 is R2") + self.assertIn( + "- spec — _bmad-output/backlog/story-caddie.md", + (self.initiative / "initiative-golf.md").read_text(encoding="utf-8"), + ) + self.assert_references_resolve(story) + self.assert_references_resolve(self.initiative / "initiative-golf.md") + + def test_a_loose_story_takes_the_backlog_next_id_when_it_is_higher(self): + loose = self.store / "spec-caddie" + write(loose / "spec-caddie.md", spec_frontmatter(companions="[]", sources="[]") + SPEC_BODY) + write(loose / ".memlog.md", MEMLOG_BALL) + write(self.store / "backlog" / "bug-slice.md", "---\nid: 4\ntype: bug\n---\n\n# Slice\n") + write(self.store / "backlog" / "tickets.toml", "next_id = 9\n") + + self.fold() + + text = (self.store / "backlog" / "story-caddie.md").read_text(encoding="utf-8") + self.assertTrue(text.startswith("---\nid: 9\n"), msg=text) + + def test_a_companion_whose_name_is_taken_is_reported_and_nothing_moves(self): + epic = self.write_epic_with_spec() + write(epic / "glossary.md", "the epic's own\n") + before = snapshot(self.project) + + report = self.fold() + + self.assertEqual(report["folded"], []) + self.assertEqual( + report["problems"], + [ + { + "spec": "_bmad-output/initiative-golf/epic-ball/spec-ball", + "problem": "glossary.md already exist(s) beside the ticket", + } + ], + ) + self.assertEqual(snapshot(self.project), before) + + def test_a_parent_folded_on_a_later_run_leaves_the_folded_childs_ids_alone(self): + epic = self.write_epic_with_spec() + write( + self.initiative / "spec-golf" / "spec-golf.md", + spec_frontmatter(companions="[glossary.md]", sources="[]") + + SPEC_BODY.replace("# Ball flight physics", "# Golf"), + ) + write(self.initiative / "spec-golf" / "glossary.md", "Golf terms.\n") + write(self.initiative / "glossary.md", "the initiative's own\n") + + first = self.fold() + + self.assertEqual( + [fold["spec"] for fold in first["folded"]], ["_bmad-output/initiative-golf/epic-ball/spec-ball"] + ) + self.assertEqual([problem["spec"] for problem in first["problems"]], ["_bmad-output/initiative-golf/spec-golf"]) + (self.initiative / "glossary.md").unlink() + + second = self.fold() + + self.assertEqual([fold["spec"] for fold in second["folded"]], ["_bmad-output/initiative-golf/spec-golf"]) + self.assertEqual(second["problems"], []) + self.assertFalse((self.initiative / "spec-golf").exists()) + self.assertEqual((self.initiative / "glossary.md").read_text(encoding="utf-8"), "Golf terms.\n") + self.assertIn("- R1 (CAP-1): ", (self.initiative / "initiative-golf.md").read_text(encoding="utf-8")) + text = (epic / "epic-ball.md").read_text(encoding="utf-8") + self.assertIn("- R1 (CAP-1): A player sees the ball follow a physically plausible arc.", text) + self.assertIn("- R2 (CAP-2): A player sets the launch angle and sees the arc change.", text) + self.assertEqual((epic / "glossary.md").read_text(encoding="utf-8"), "Carry: distance in the air.\n") + self.assertNotIn("CAP-", (epic / "story-launch-angle.md").read_text(encoding="utf-8"))