basic BMAD setup

This commit is contained in:
Ebbe Baß
2026-10-15 08:57:23 +02:00
parent 44029127dc
commit 5e747654b9
438 changed files with 41818 additions and 0 deletions
@@ -0,0 +1,80 @@
# Board
Operations on existing tickets. Read an existing ticket before changing it; if it has been published to a tracker, query its current remote state too.
`tickets.py` below is `uv run {skill-root}/scripts/tickets.py --project-root {project-root}`. `tickets.py <command> --help` gives a command's arguments and output keys.
## Reading the tree
- A ticket the user names — "refine 1.2", "start ABCD-13", "build the cart service scaffold" — resolves through `tickets.py find [<folder>] <ref>` before you pull, review, refine, start, or mark it. `<ref>` is `<epic id>.<entry id>`, an entry id inside the epic folder, a tracker id, a file name, or an unbroken phrase from the title. When the user's words are looser than that, read `status` and pick the ticket yourself, asking when more than one fits. In the result, `story_file` is null until the entry is pulled, and `plan` is where the plan is or goes.
- Progress lives on tickets, not in a separate sprint or status file. `tickets.py next [<folder>]` groups the open tickets by what can happen next; `tickets.py status [<folder>]` reports every ticket with its `status`, `tracker_status`, and `state`, what it blocks, and counts by state.
- `<folder>` is an epic (its folder name is enough), `backlog`, or the initiative for all its epics at once. Left out, it is the active initiative: `next` and `status` add `backlog`, the same view of `backlog/`, so show both, and `find`, `mark`, and `mirror` look in `backlog/` for a ticket the initiative does not hold. A done standalone ticket stays in `backlog/` beside its plan.
- With a tracker, bring the tree up to date first (Tracker sync, below) and pass `--synced` to `next`.
When one of these comes back with something in it, act on it:
| Key | Means | Do |
|---|---|---|
| `unpinned_after` | an epic has tickets, but none waits on the epic its `after` names | with the user, add the prerequisite to the entry that needs it |
| `undeclared_after` | an entry waits on an epic its own epic does not declare | with the user, add that epic to this epic's `after` in the initiative's breakdown with its `needs`, or drop the prerequisite |
| `order_conflict` | an epic waits on one later in build order | reorder or merge the epics, or record why in a `Decision:` line |
| `drift`, on a row | the leaf file's `after` or `hitl` differs from the entry's; the row gives both values | show them to the user and make them equal |
| `problems` | a plan whose `ticket` names nothing, or whose `status` is unknown, so its ticket reads as blocked | show each to the user and fix the plan; `mark` sets a valid status |
| `waiting_on`, on a `blocked` row of `next` | the prerequisites not yet done or in review | say what the ticket waits on |
| `unknown`, on a `blocked` row of `next` | a question that must be settled before the ticket starts | settle it with the user, remove it, and record the answer as a dated `Decision:` line: in the file's Notes, or in its epic's Notes when it has no file |
## Publish
Approving a breakdown records it in the epic's `tickets.toml`; it does not start work. On the repo store, committing the approved `tickets.toml` is the publish. On a tracker, `{workflow.publication}` says when: `at_inception` offers to publish the agreed set after validation; `on_start` publishes each ticket when it starts. An explicit user request takes precedence.
Publishing is not a status: on a tracker it is the remote item existing, with `tracker_id` and `remote` in the leaf file. Publish through `write`:
- The leaf's file is written first, with `tickets.py pull` when it has none, because the file is the body the tracker receives: the whole file without the frontmatter lines `tracker_id`, `remote`, `tracker_status`, `status`, and `assignee`.
- Create parents before children, and a prerequisite before what waits on it. A prerequisite that is still local joins the proposed publication scope. An `after` naming a sibling's id or `<epic id>.<entry id>` is that entry's remote item; `epic-<slug>` is the epic's item.
- A type mapped to `""` is never sent: it stays local and its children attach to the nearest published ancestor, which is how an initiative behaves on a tracker whose hierarchy has no level above the epic. Say so once when it first matters rather than at every publish.
- At first publish, or when the tracker lacks something the maps or `fields` name — a label, list, status, or property — offer `setup`.
- Then mirror what the tracker returned (Tracker sync). `mirror` writes leaves only: a published container's `tracker_id` and `remote` go into its own file by edit.
An unrefined ticket may be published for planning visibility. One that needs refining keeps `refined: false`, and that line stays in the published body; the `backlog` state means available to start. Refinement updates that same file and remote item, preserving identity.
## Start
Candidates are `next`'s `ready_to_start` rows: tickets, with or without a file, that are not started, blocked, or waiting to be refined, whose prerequisites are done or in review, whose `gated_by` epics are done, and that carry no `unknown`. Offer the first, in build order; when work can run in parallel, offer every one that is unassigned.
Before starting a candidate, read it and its source, passing its row's `ref` to `find`. One in `ready_to_refine` is refined first per `ticket.md`. Confirm it is in `ready_to_start` and not assigned to someone else. Then, with the user's approval, publish it if it is not yet published, and start it. On a tracker, `write` transitions the item to `[tickets.status].in-progress` and the mirror sets `tracker_status: in-progress`. On either store the ticket then goes to the build, `bmad-build` or `bmad-build-auto`, which writes `status` in the ticket's plan as it works; on the repo store that is all starting is. An entry's `plan_checkpoint` waits for a person to approve the ticket, or the builder's plan when it is not refined, and `done_checkpoint` waits after the ticket closes. Both are on the ticket's row, and the orchestrator that dispatches an unattended run does the waiting: `bmad-build-auto` reads neither, so run without an orchestrator nothing pauses.
## Status
A leaf's `status`, `blocked_at`, and `blocked_reason` live in its plan, `<type>-<slug>-plan.md` beside where its file is or would be, joined by the plan's `ticket` field; a leaf file's own `status` is read only when there is no plan. Who writes each status:
| Status | Written by |
|---|---|
| `draft`, `ready-for-dev`, `in-progress`, `in-review`, `built` | `bmad-build`, `bmad-build-auto` as they work |
| `blocked` | `bmad-build-auto` when it halts, with the reason in the plan |
| `done` | the user, or an orchestrator, through `tickets.py mark` |
| `dropped` | `bmad-ticket`, when the user says so |
A ticket's state is `planned` for an entry with no file and no plan; else `tracker_status` when present; else from `status`: none, `draft`, or `ready-for-dev` is `backlog`; `in-progress` or `blocked` is `in-progress`; `in-review` or `built` is `review`; `done` and `dropped` are themselves. `tracker_status` exists only on a tracker store: the BMad word for the tracker's current status.
No skill moves a ticket past `built`, which means the build finished and nobody has called it done. `bmad-ticket` never moves a leaf on its own: it changes a leaf's status or assignee only when the user asks, for `done`, `dropped`, or a person working the ticket by hand. When the user says a ticket is done, mark it for them.
- **Repo store.** `tickets.py mark [<folder>] <ref> <status> [--assignee <who>] [--blocked <reason>]`, then the commit the `write` verb describes. `mark` takes its folder and ref as `find` does, writes the plan and never the leaf file, and creates a plan holding only frontmatter when there is none. It writes what it is told; the checks in this file are yours. The assignee is in the plan.
- **Tracker.** `write` transitions the item to `[tickets.status].<state>`, the ticket's `state`, and sets its assignee; it never sends `status` as a word. Then mirror the item (Tracker sync): its status comes back as `tracker_status` and its assignee lands in the leaf file. `mark` refuses a tracker store.
- **Waiting on a person or an answer**, not on a prerequisite, on the repo store: `mark` with the ticket's current status, `draft` when it has none, and `--blocked <reason>` sets `blocked_at` (today) and `blocked_reason`, and `next` lists the ticket under `blocked`; a `mark` without it clears both when the ticket moves.
- **A container's** `status` is `bmad-ticket`'s, an edit to its file: absent until the first ticket under it starts, then `in-progress`, and `done` or `dropped` when the user confirms it; containers never take review. A published container gets the same transition through `write`.
## Close, drop, move
- Closing every child does not close the parent. Run the closure check in `validate.md` against its requirements and Done when; the user confirms the parent is complete.
- Drop only after a `Dropped:` line says why: in the ticket's Notes, or in its epic's Notes when the ticket has no file. Then set its status to `dropped` as Status says; an entry never published to the tracker takes `tracker_status: dropped` through `mirror`. An entry is never deleted from an approved `tickets.toml`, so its id is never reused. A dropped ticket still blocks what waits on it, in any epic: `status` lists them under `blocks`; remove or repoint it in each one's `after` with the user. Cancelling a container cancels its descendants after the user confirms.
- Moving a leaf to another parent: first write the old folder's `next_id` (from `tickets.py status <folder>`) at the top of its `tickets.toml` as `next_id = <n>`, so the id that leaves is not given out again. Then move its entry from the old `tickets.toml` to its place in the new one's build order, with that folder's `next_id` (from `tickets.py status <folder>`) as its id; move the leaf file and its plan into the new folder; set the file's `id` and `parent` and the plan's `ticket` to the new id. Then rewrite both directions of `after`: its own, where a sibling it left is now `<epic id>.<entry id>`, and every `after` that named it. Move its `covers` ids between the two epics when the requirement moves with it. An epic moves as its whole folder, with its `[[epic]]` table. Then run `tickets.py status` on the initiative and clear what it reports.
## Tracker sync
The tree is the working copy and a tracker is its remote; nothing syncs on its own. After every `write`, and after the `query` that precedes a board view, put what the tracker holds into the tree with `tickets.py mirror [<folder>]`. It reads a JSON array on stdin, one object per ticket: `ref` (or the `tracker_id` the ticket already carries) and any of `tracker_id`, `remote`, `tracker_status`, `assignee`, and `after`. `tracker_status` is the BMad state the tracker's status maps back to through `[tickets.status]`. After a `write`, it is the state that was sent. After a `query`, when two states share one tracker status and `query` does not say how to tell them apart, keep the ticket's current `tracker_status` if it is one of them, else take the earlier state. `assignee` is the tracker's name for the person; `after` lists the prerequisites as tracker ids, each a string, since a bare number there is a sibling's entry id. `mirror` pulls a leaf file when the entry has none, writes only the keys given, removes a line whose value is an empty string, and never writes `status`, which is the build's.
- A leaf the tracker holds and the tree does not comes back under `unmatched`, also when the mirror failed because a known ticket's `after` names it: add its `[[entry]]` to the parent's `tickets.toml` with the folder's `next_id` and the item's type, title, and a one-sentence description, mirror it again with `ref` naming the new entry, then put the item's body in the pulled file.
- A published container is queried with its leaves, and `mirror` does not write containers. When the tracker's status for one differs from its file's, tell the user; closing it here still takes the closure check.
- A body that differs from the file: show both and ask.
- A ticket the tracker shows under another parent is moved as Close, drop, move says, then mirrored again.
- Commit the changed files on the current branch when `output_folder` is in a git repo; never push.
@@ -0,0 +1,100 @@
# Slicing
An initiative is sliced into epics: containers the product owner and developer own and complete. Inception plans the whole selected epic so AI agents can build it. The user's preference for a split comes first.
When an initiative has epics, every story is under an epic. Work that fits one epic is proposed as one epic; say so. Never offer an initiative without epics: it happens only at the user's request, is planned like an epic with stories directly underneath, and you say once that the initiative folder fills with ticket files.
## Every container, in order
Apply this when authoring the initiative or incepting the selected epic.
1. **Envelope.** A document in the folder is an input, not the container. When the container file is missing, offer to create it from its template: title, a paragraph of intent, Outcome, Done when, boundaries, known decisions, and references. An epic also records its parent and the parent requirement ids it owns in `covers`. Done when is three to six checks at the altitude of the source's ids; it is the definition of the container and is never deferred.
2. **The requirement source at this altitude.** The template's Requirements placeholder says what the section holds. A numbered spec anywhere in the container's folder is the source instead. Offer `bmad-spec` only when the source outgrows the section or the user asks, and pass it the container's folder: its `spec-<slug>/` goes inside that folder, owns the ids, and `covers` still maps them upward. The architecture spine, UX design, and research inform everything below either way.
3. **Complete the container** per `ticket.md`: the requirement source, constraints as References, known unknowns in Notes, and a re-read of Outcome and Done when against the source. The product owner and developer agree on its scope: propose the fields together with reasons, discuss what is unsettled rather than repeating decisions already made, and confirm before slicing.
## Learn the codebase and team first
Start from the parent chain: the parent ticket, its spec, and what they reference, including the design when the work is user-facing. Then the codebase: greenfield or brownfield; mono or poly repo; team, service, and UI boundaries; vocabulary and recorded decisions — from the architecture document, else the repo layout. Then the areas the work will touch, enough to see which stories would change the same code. Tell the user what you read and concluded; ask what is wrong or missing and whether there are other references or tools you do not already know of.
Where the source contradicts the code or another source, add `Source conflict: <id or section> — <what the source says> vs <what was found>` to the Notes of the container whose source it is, and tell the user. When the source is a BMad spec, offer to pass the correction to `bmad-spec`.
## Ask the questions that decide the split
Use what is already known. Ask the remaining questions that change the split: what is first worth demoing; what is least certain; what will the first piece teach about the rest; whether the user has a split in mind; how the team defines epics. Group related questions and say which answer you would pick and why.
When the user states how their team cuts epics, offer to save it as `slice_to_epics` under `[workflow]` in `{project-root}/_bmad/custom/bmad-ticket.toml`.
Record each choice the user makes while slicing — a boundary, the tracer bullet, the sequencing, deferred scope, a declined suggestion — as a dated `Decision:` line in the container's Notes when it is made, not after approval.
## Initiative into epics
Existing epics are the working set: read them first and revise them in place, or drop one per `board.md` when it no longer fits; add only after the user confirms the set is insufficient. Draw the boundaries by the team's own rule, `{workflow.slice_to_epics}`, when it is set. Otherwise:
- An epic is one capability from the source, or a tightly coupled pair, delivered to production by one owner: a dev or pair with agent lanes.
- Propose epics along the source's capabilities. Merge two when one owner and one module deliver both. Two epics need at most a contract between them; say what each needs from the ones before it.
- A unit (module, service, or bounded context) is an epic boundary when it is also the ownership or deployment boundary and its Done when still reads as something a product owner can check. A module that is only a code folder is not.
- An epic whose boundary names more than one outcome or owner is two epics.
Under either rule:
- No boundary applies: one epic. Split only for a distinct outcome, owner, or a part the user wants usable early, never for a ticket count.
- 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.
- The platform baseline (scaffold, environments, CI, deployment, operations) is the opening epic, the first `[[epic]]` in the initiative's breakdown, or the first stories of the first epic.
- Every epic delivers to production; its Done when includes the integrated verification for what it delivers.
Draft the set, run the tree check in `validate.md` on the draft, then present it in recommended build order and say in one sentence which rule cut it. For each epic: title, one to three sentences of what is true when it is done, the parent ids it owns, its boundary, and what it needs from the epics before it. With the set, present:
- the tracer path across epics, when the first demo cuts through several;
- every touch point, with the epic that owns it;
- the decisions more than one epic must adopt: a contract, a message or data format, a shared value list. One repo or one unit has none.
Work with the user on order, boundaries, merges, splits, and anything unplaced.
On confirmation, write the initiative's `tickets.toml` from `{skill-root}/assets/tickets-template.toml`: one `[[epic]]` per epic in build order, each with an `id`, `after` naming what it needs and from which epic; `after` on an epic file only for a whole-epic gate. Each new epic gets its folder and envelope with Outcome and Done when.
For the decisions more than one epic must adopt, offer `bmad-architecture` to settle them in the spine, and cite the spine section in each adopting epic's References. Declined: add each as a `story` entry (`hitl = true`) in the `tickets.toml` of the opening epic, and give each adopting epic `after = [{ epic = <opening>, needs = <the decision> }]`; its entries name that entry in `after` at their inception.
Stop at this level unless the user wants to incept an epic now; complete and plan only the selected epic. The others retain their scope, references, and place in the order without a breakdown.
## Epic into stories
The epic is ready to be worked and its spec exists or the user chose to go without. Read its breakdown, every child ticket, and their results before proposing changes. Plan the entire epic, not only its next story. Put each unknown that must be settled before implementation to the user: answer it now, record it as the `unknown` of the entries it affects, or add a spike when they ask for one. Never guess the design of work that depends on it.
What makes a good story:
- It is one implementation step toward the epic's Done when, small enough that one agent session, starting from the entry, its epic, and the source, plans and finishes it. It need not be user-visible on its own; the epic is the unit of value. Too small when setup outweighs the work: merge. An epic that is itself one session's work gets one story.
- The first entry is the tracer bullet: the thinnest path through every layer the epic touches, proving they connect. Whatever setup that needs, including a starter the user runs, is a hitl step on that entry. For it, and for anything the user wants demoable, the entry says what someone can see running when it is done. After the tracer bullet comes the story the user is least sure of.
- A lane is a run of stories that touch the same code, in order; stories in different lanes never touch the same code. Two that would are one story, or one waits on the other. A boundary two lanes share gets its interface and a stub as its own early story, so both lanes open at once.
- Done is one runnable check. A story whose check needs another story's work goes after it.
- Split, never shrink: "for now", "placeholder", "simplified", "wired later" is a second story.
- Setup and each hitl step go on the first story that needs them, under the relevant epic and never loose under the initiative; initiative-wide setup belongs to the opening epic. Tests are part of every story; the one test story is the closing end-to-end suite below.
- Eight to twelve stories is typical, not a limit. Fifteen can be right when they are one lane with one owner; six can be too many when two owners are inside. Past the typical range, say so and offer a split first; the user decides.
What opens and closes the breakdown:
- Offer an opening refactor when poor code quality or missing standards would make the epic's tickets hard.
- An epic of more than three entries gets a closing story "Refactor sweep" that waits on every other entry except a closing end-to-end suite. Its scope is set when it starts, from the plans and the review findings deferred during the epic. It takes cleanup only; scope pushed out of another story is a new story. Propose it by default; when the user declines, record a `Decision:` line in the epic's Notes.
- One closing end-to-end suite across the epic, after the sweep, is offered when the source has a test plan or the user wants one.
Draft one breakdown in build order, run the set check in `validate.md` on the draft, then present it, and adjust size, order, and prerequisites with the user until they approve the set. Each entry follows `{skill-root}/assets/tickets-template.toml` and has an `id` it keeps for good; the order of the tables is the build order. What the template does not say:
- `description` says what the entry delivers toward the ids in its `covers`, and `verify` how that result will be checked: one sentence each.
- `after` holds real prerequisites only, never the sequence. For each `after` on this epic in the initiative's breakdown, put the provider in `after` of every entry that needs it: `<epic id>.<entry id>` when the providing entry exists, `epic-<slug>` until it does.
- `refine = true` only where the user asks for full criteria on that entry; a bug gets them without it.
- `references` holds only what the entry needs beyond the epic's own References: a spine section, a design screen, a document. `notes` holds only what the user said, in their words.
- When the epic will run unattended, ask for `plan_checkpoint` and `done_checkpoint` per entry, proposing `plan_checkpoint` on a high-risk one.
- Each touch point this epic owns is an entry or part of one.
With the draft, name the tracer bullet, what can run in parallel, and any deferred scope. With the approval question, ask once whether the user wants to change a description or add a note or reference to any entry.
A split at inception is a second epic folder and envelope, a new `[[epic]]` in the initiative's breakdown with its `after`, the covers ids moved, and the agreed entries placed under the right epic; no file is renamed.
On approval, write the whole set into the epic's `tickets.toml`. No leaf file is written until its entry is pulled.
Then run `uv run {skill-root}/scripts/tickets.py --project-root {project-root} status <initiative folder>`. In `epics`, this epic's `blocks` lists the tickets in other epics whose `after` names the whole epic; replace each with the entry that delivers what it waits for. Clear any `unpinned_after`, `undeclared_after`, or `order_conflict` it reports as `board.md` says. Publication follows `board.md`.
Then tell the user the entries are ready to build, and what that means for how they build. With `bmad-build`, each ticket is refined during the build: the builder questions the user and writes the criteria itself, so refining here repeats that work. With `bmad-build-auto`, a loop, or a factory, nobody answers questions during the build and the entry is all the builder gets: recommend one more review of the sequence and of each entry now (`ticket.md`), and offer the checkpoints where none are set.
## Re-slicing
When completed work changes the picture, revisit the whole remaining breakdown with the user. Update unstarted entries and tickets in place, preserving each `id`; published changes go through the store. Do not rewrite completed or active work as a new breakdown. Run the set check on the revised draft before the user approves it, and record the reason in the epic's Notes.
@@ -0,0 +1,14 @@
# Setting up the store
The store config is two layers that `{skill-root}/scripts/read_store.py` reads as one. The base is the skill's starter for the store, `{skill-root}/config/<store>-ticketing.toml`, which updates with the skill. Over it goes the project's file, `{project-root}/_bmad/custom/ticketing-store-config.toml`: it names the store and holds only what this project sets, and a key it sets replaces the starter's. With no project file the store is the repo store, and nothing needs setting up.
Run this when the user wants a tracker connected or the store reconfigured or switched.
1. **Choose.** `uv run {skill-root}/scripts/read_store.py --starters` lists the shipped stores with a description of each. Every hosted one has a free tier; choosing it costs an account setup, or only the config when the user already has an account. The user may also bring a config of their own, as a file or a url.
2. **Write the project file.** `[tickets]` with `store` and the address fields the starter leaves empty — key, repo, site, team, database, board — filled from the user's answers; the comments in the starter file say what each one is. Copy nothing else from the starter: a key the project file leaves out follows the skill as it updates. A project file that is a whole copy of a starter predates this layout: cut it down to `store`, the address fields, and what the team changed. A config the user brings goes into the project file whole; so does a store with no starter, modelled on the nearest one. For the repo store, delete the project file.
3. **Connect.** Offer to run the store's `setup` verb: tool install, auth, and the labels, lists, statuses, or properties the maps name. Where the tracker's types or statuses differ from the starter's maps, set those entries under `[tickets.types]` or `[tickets.status]` in the project file. A state the tracker has no status for takes the status of the state before it, never an empty value: with no review status, `review` takes the in-progress one.
4. **Prove it.** Offer a test. A store that already has items: `query` a few back — that proves auth, the maps, and the path back. An empty store: create one ticket in `backlog/` titled "BMad setup test — safe to delete" and prove `write` and `query`: create it, query it back, move it to `in-progress`, assign it, then drop it. Give the user the `remote` url so they can watch the item and its history.
A house rule on top of a verb, such as a label every new issue takes, goes in `persistent_facts` in this skill's customization. Set a whole verb under `[verbs]` in the project file only when the store's commands differ, because a replaced verb no longer follows the skill.
Then go on with what the user came to do. When that is done, offer once a `bmad-customize` pass on this skill: its `customize.toml` holds the team's epic boundary rule, publication timing, own checks, and templates.
@@ -0,0 +1,56 @@
# Writing a ticket
A ticket starts from what is known: what exists when it is done, how it will be verified, what must not change, and what is already decided. Ask only what remains unsettled, offering a default for each, then draft into the ticket tree. Under an epic with a breakdown, the ticket is an entry, with the keys `{skill-root}/assets/tickets-template.toml` shows, placed where it goes in the build order; it gets a file now only when it carries full criteria, and otherwise `pull` writes one when it is reviewed or published. A standalone ticket is a file in `backlog/`. Either takes its `id` from `next_id` in `uv run {skill-root}/scripts/tickets.py --project-root {project-root} status <folder>`. Open only the template for the type: `{workflow.initiative_template}`, `{workflow.epic_template}`, `{workflow.story_template}`, `{workflow.spike_template}`, or `{workflow.bug_template}`. Its placeholders say what each section holds; its example is the level of detail to match, not copied.
## Each fact lives in one place
The input (PRD, brief, notes) owns the product argument. Reference a level's requirement source rather than duplicating it. The container owns Description, Outcome, Done when, Boundaries, References, Notes. An epic's `covers` records the parent requirement ids it owns, and every id it assigns locally — in Requirements or its own spec — maps to one of them. An initiative's `covers` records its source ids. Adding a local spec does not replace upstream coverage; update affected references and child mappings with it.
A story under an epic is one slice of its build order: `covers` cites ids from the epic's requirement source, and its description says what it delivers toward them. Several stories can cover one requirement. Where full criteria are written ("Reviewing or refining an existing ticket" says when), add criteria for what changes and for the failure paths, boundaries, and binding decisions; point at the source for the rest.
## Rules the template cannot carry
- Acceptance criteria, where they are written, in the template's format: each is a check the implementer can prove it met without pointing at code, and together they are enough that building the wrong thing cannot pass. Behavior that must stay as it is goes under Boundaries.
- Every sentence is short and declarative, in common words and the project's own vocabulary: the reader has only the ticket. Say each thing once, with no invented term, filler, or metaphor. When the user's text misses this or the criteria rules, offer the rewrite with the reason and let the user decide; show what is missing, not only what is written.
- A reference is a line in References, `type — location, section`: location is a path from `{project-root}`, or a url for a remote source, and is opened before it is cited. Name the nearest document, not the documents behind it. `reference` in the store's `tickets` table, read at activation, says what else that store needs; the repo store has none.
- No source-code paths or snippets; the builder reads the repo. A snippet stays only when it is the decision itself, not an illustration of it. A path the user wants recorded goes in Notes.
- A UI ticket links its design in References; criteria stay functional, layout lives in the design. No design and user-facing: offer `bmad-ux` first; declined, say the builder will guess the layout unless they add details in Notes.
- `hitl: true` only when a person must do part of the work; say which step in the Description, and spell known steps out in the criteria or Notes.
- Risk on every ticket: `low`, `medium`, or `high`, the business impact if this change is wrong, proposed with a one-line reason; the user's value wins. A high-risk ticket names one check outside its own criteria in Notes: a person who confirms, a suite beyond the ticket's tests, a monitor. The build confirms or raises the risk in its plan, which is where a CI process reads it to decide how much review a change gets.
- Severity on a bug, as the bug template defines it. An `estimate` is recorded only when the user gives one: on the entry or in the ticket's frontmatter, carried to a tracker as the store's `fields` says. Never propose one.
- A bug carries a reproduction and a cause hypothesis, never a fix. Missing steps: ask; unclear: tighten until someone else could follow them. Run them when cheap; if the behavior already holds, say so with evidence and create nothing. Criteria include tests for the condition found and fixed, and name the other valid outcome: proof no change is needed.
- A spike names the question, who waits on the answer, and where it is recorded. A spike is `hitl`. When tickets in more than one epic wait on the answer, it is a decision several epics adopt: handle it per `slice.md`, not as a spike inside one of them.
- Notes holds what is not in the repo or the source and what is unsettled, each line marked, and only what is local to this ticket. Anything touching more than one ticket lives in the parent's Notes and is referenced; an unknown that gates work goes to the user, and becomes a spike its dependents list in `after` only when they ask for one. `Assumption:` — offer each; confirmed, it becomes a dated decision; corrected, the ticket changes. `Open question:` — answering it is part of the ticket's work when it starts. `Unknown:` — it gates the start, and `tickets.py next` lists the ticket under `blocked` while the line is there; settle it with the user before the build begins, as a dated decision. Never resolve any of them by guessing.
## Reviewing or refining an existing ticket
Resolve what the user names with `uv run {skill-root}/scripts/tickets.py --project-root {project-root} find [<folder>] <ref>`: `<epic id>.<entry id>`, an entry id inside the epic folder, a tracker id, a file name, or an unbroken phrase from the title. When its `story_file` is null, pull the file with `tickets.py pull <epic folder> <id>` (same `--project-root`); `pull` writes a fixed layout, not the template. A done or dropped ticket and its plan are the record: do not reshape them to a newer template.
Full acceptance criteria are written here only for a bug, a ticket with no epic, and an entry that carries `refine = true`, which is set when the user asks for full criteria on that entry and is never proposed for a story. `tickets.py next` lists these under `ready_to_refine`. An epic's other stories go to the build as they are: the builder questions the user and writes the criteria into its plan. They are reviewed only when the user asks, and a request to refine one of them is a review unless the user asks for full criteria.
Where a change is written:
| Change | Written to |
|---|---|
| description, `Verify:`, criteria, references, notes | the file |
| `title`, `type`, `after`, `hitl` | the file and the entry both; the file's `after` and `hitl` win, and `tickets.py status` reports a difference in either as `drift` |
| full criteria asked for on a story | `refine = true` on the entry, `refined: false` in the file |
| order | the tables in `tickets.toml` |
Once the ticket is published, every change also goes through `write`.
**Review** — the next story by default, several or all on request. Read the file, its source, and the finished siblings and their plans. With the user, improve the description, `Verify:`, references, and notes; adjust order and prerequisites, and run the set check in `validate.md` on the revised draft when they change. A review sets neither `status` nor `refined`: the file is intent that is ready for the build, which plans the story's criteria from the epic's Requirements and Done when and the story's description and `Verify:` check, which it may extend and never weaken.
**Refine** — before the ticket starts. Read the ticket, its source, the finished siblings and the plans beside them, and the code it will touch at the revision the work starts from. Where the source contradicts the code or another source, add `Source conflict: <id or section> — <what the source says> vs <what was found>` to the Notes of the container whose source it is and tell the user, offering to pass the correction to `bmad-spec` when the source is a BMad spec; a ticket covering that id stays `refined: false` until a `Decision:` line settles it. Confirm its prerequisites are done or in review, resolve questions that prevent implementation, and expand it per its type's template and "Rules the template cannot carry": criteria, boundaries, references, and decisions. Set `refined: true` when it passes self-review in full and the user approves it.
**A ticket that already carries full criteria, or a container** — read it and open what it references; query its remote state if it is published to a tracker. With the user: confirm they still agree with it; check its description against the current requirement source, criteria and references; find what is missing, unclear, or wrong; settle questions that prevent implementation. A container may end in a re-slice per `slice.md`.
A reply approves what was presented; publication and starting work are approved separately unless the user asks for them together. None of these changes status or assignee: preserve identity and any existing `status`, `tracker_status`, and assignee.
## Self-review before the user sees it
Read it back at its current level of detail: an entry needs its description and verification approach; a refined ticket needs runnable acceptance criteria and settled prerequisites. Check size (one agent session plans and finishes it), wording, references, and source consistency. Fix what you find; mention changes to the user's intent.
## After approval
On the repo store, save a new or changed ticket through the `write` verb: the commit it describes. On a tracker the ticket stays in the tree until it is published, a separate approval, per `board.md`. A ticket written or refined with full criteria carries `refined: true` once the user approves it, and gets the single-ticket check in `validate.md` before any build starts it.
@@ -0,0 +1,44 @@
# Validation checks
One section per scope. `validate.md` says which sections an agent runs and what it is given.
## Ticket
- Every file or link referenced exists and opens; no unfilled placeholder; every assumption is marked.
- Nothing contradicts the requirement source, its companions, the architecture, or a recorded decision.
- An entry has a description, known uncertainty, and a `verify` check someone other than the builder can run. Full criteria are required before execution only for a bug, an entry with `refine = true`, and a ticket with no epic.
- A ticket that needs full criteria and whose `covers` includes an id named in a `Source conflict:` line with no later `Decision:` settling it has `refined: false`.
## Set
The set is every entry in the epic's `tickets.toml` or draft breakdown, pulled or not.
- Together the tickets account for every requirement in the epic's spec, referenced numbered source, or Requirements, except scope deferred with the user. Each `covers` id exists there; several tickets may cover one id, each description saying which part it delivers.
- UX, architecture, constraints, and integration work are accounted for even when they lack ids. The combined results must satisfy the epic's Done when; citing an id alone is not coverage.
- Every `after` is a real prerequisite, those in other epics included, and none restates the order. Once `tickets.toml` exists, `tickets.py status` on the epic runs clean: no cycle, no `drift`, no `unpinned_after`, `undeclared_after`, or `order_conflict`. The tracer bullet (the first entry: the thinnest path through every layer the epic touches) and the sequencing decisions are in the epic's Notes.
- Given the epic, an entry, and its references, a builder could write that story's acceptance criteria if it had to. If it could not, fix the epic's requirements or the references; the entry stays one sentence each for `description` and `verify`.
- An epic of more than three entries closes with a "Refactor sweep" story that waits on every other entry except a closing end-to-end suite, or a `Decision:` line says why not.
## Tree
The tree is the initiative and its epic envelopes, drafted or written.
- Every initiative requirement has an accountable epic or an agreed deferral. Shared requirements say which part each epic delivers; no unexplained overlap.
- Each epic's `covers` cites parent ids. Every id it assigns in Requirements or a local spec maps to one of them, and a child citing a local id resolves through that map to a parent id; an epic with empty `covers` cites a source section on every line instead. Verify the map resolves; a non-empty `covers` is not coverage.
- Every unit the source touches is an epic, a touch point (a unit the work only consumes or configures, named in the initiative's Boundaries with the epic that owns the work there), or named out of scope.
- Every decision two or more epics must adopt has one home: a spine section, or an entry in the opening epic (the first `[[epic]]`) that the others wait on.
- Every epic envelope has Outcome, Done when, boundaries, upstream coverage, and references; a future epic needs neither a breakdown nor a completed spec. The initiative's `tickets.toml` lists every epic in build order, and each `after` names what is needed and from which epic. It opens with a platform-baseline epic (scaffold, environments, CI, deployment, operations), or a `Decision:` line says why not. Each epic's Done when includes production delivery.
- With epics, no leaf sits directly under the initiative.
## Dependencies
Missing prerequisites, run with the set and with the tree. An item is an entry in the set and an epic in the tree; a prerequisite is an `after` on either.
- Needs: list what must exist before each item can start and before its check can run: code, schema, setup, test tooling, fixtures, an entry point, a decision, an investigation's answer. The item builds it itself, or something in its `after` delivers it.
- Collisions: two items with no dependency path between them can run at the same time, so they share no code, config, schema, or setup. Where they would, one goes in the other's `after` or they merge.
- Shared setup: whatever more than one item needs (test harness, scaffold, schema, a shared component or contract, an integration) has one owner, the earliest item that needs it, and the others list it in `after`.
- Handoffs: where one item relies on another's output (a default, an interface, a link target), both descriptions say so, so neither builder invents its own.
## Closure
- Verify the container's Done when against implementation evidence and its requirements, including constraints and companions. All known children done is evidence, not proof the parent is complete; explain any dropped work and confirm remaining scope with the user.
@@ -0,0 +1,17 @@
# Validating tickets
The checks are in `{skill-root}/references/validate-checks.md`, one section per scope: Ticket, Set, Tree, Dependencies, Closure. Agents that were not in this conversation run them; each is given that file's path and the names of its sections, and reads them there. `tickets.py` reads only the prerequisites that were written; one that is missing is for these agents to find.
- **A proposed set of epics, a proposed epic breakdown, or a re-slice:** on the draft, before the user is asked to approve it. This always runs; no path, mode, or setting skips it. First walk the draft through the Dependencies section yourself and fix what that finds, then give the agent the corrected draft as text. Present the draft with what the check changed and what it confirmed. Run it again when the user's changes add, remove, merge, or reorder items. At initiative slicing, check the tree's scope ownership without requiring stories or full detail in future epics.
- **A refined ticket:** before execution.
- **A container:** before closure, its implemented coverage and Done when.
How:
- One subagent per epic: its container, the draft breakdown or `tickets.toml`, every child ticket including completed work, requirement source and companions, and the sections Ticket, Set, and Dependencies. One subagent for the tree: the initiative, the draft or written epic envelopes, source, and the sections Tree and Dependencies. A single ticket: one subagent with its source and the section Ticket. Closure: the section Closure.
- A team's own checks are in `{workflow.checks}`, one array per scope with the same names; give each agent the entries for its sections as text.
- Where tickets carry full criteria (`refined: true`), say which they are and point the agent at "Rules the template cannot carry" in `{skill-root}/references/ticket.md` for the criteria and risk rules. Once `tickets.toml` exists, give the full `tickets.py status` command for the folder.
- Merge findings into fix (mechanical), suggest (a guideline, with its reason), or ask (needs the user). Resolve coverage gaps, missing prerequisites, and contradictions before proceeding; the user decides suggestions and scope changes.
- A declined suggestion recorded as a `Decision:` line is not raised again unless new evidence changes its basis.
The user may request any scope independently.