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,114 @@
---
id: [the entry's id in tickets.toml; the folder's `next_id` for a ticket with no entry] # tracker_id and remote are written at publish on a tracker
type: bug
title: "[What is wrong, from the user's view]"
parent: [folder name of the epic, or of the initiative when there are no epics; none for a standalone bug in backlog/]
covers: []
after: [] # prerequisites only: a sibling's id; "<epic id>.<entry id>" in another epic; epic-<slug> for that whole epic
assignee: "" # a tracker's assignee, written by `tickets.py mirror`; otherwise assignee, blocked_at, and blocked_reason live in the plan
# status lives in the plan beside this file, not here; tracker_status by a tracker sync
refined: false # true once the user approves its criteria
hitl: false
risk: [low|medium|high]
severity: [P0|P1|P2|P3] # P0: outage, data loss, or security exposure. P1: core function broken, no workaround. P2: impaired, a workaround exists. P3: cosmetic
---
<!-- The refined shape. A done or dropped ticket stays as it was written. -->
# [Title]
## Description
[What the user sees go wrong and what should happen instead, from their side. 2–4 sentences.]
## Reproduction
[Exact steps, environment, and what happens versus what should happen. Logs or errors verbatim.]
## Cause Hypothesis
[Where the defect likely lives and why — a hypothesis, never a prescribed fix.]
## Acceptance Criteria
1. **[The expected behavior holds]**
**Given** [the state from the reproduction]
**When** [the reproduction steps are followed]
**Then** [what should happen, as it should now happen]
2. **Tests cover the condition found and fixed**
**Given** the test suite
**When** it runs
**Then** a test that fails on the defect and passes on the fix covers the reproduction, and one covers each related case the fix touched
3. **Or: no change is needed, with proof**
**Given** the reproduction
**When** it is run on the current code
**Then** the expected behavior already holds, or the report was mistaken, with the evidence recorded in Notes — this supersedes 1 and 2
## References
- parent — [path or url, or none]
- [source — path, section]
- [logs, screenshots, or sample data — location]
## Notes
[Only what is not in the repo or the source, and what is not settled. Cut if empty.]
- Decision: [a choice the user made, dated]
- Assumption: [a choice made while drafting that the user has not confirmed]
- Open question: [what is not settled; answering it is part of the ticket's work]
<!-- Example, not part of the ticket: match its level of detail. What is good here: a reproduction someone else can follow, with the actual and expected values; a cause hypothesis that is not a fix; one criterion for the behavior, one for the tests that cover the condition found and fixed, and one that supersedes both when the reproduction shows no change is needed. -->
```markdown
---
id: 7
type: bug
title: "Checkout total ignores an applied discount code after the shopper changes quantity"
parent: none
covers: []
refined: true
hitl: false
risk: medium
severity: P2
---
# Checkout total ignores an applied discount code after the shopper changes quantity
## Description
A shopper applies a valid code, then changes an item's quantity, and the total goes back to full price while the discount line still shows the code. They are charged full price at payment.
## Reproduction
1. Production, Chrome 130, logged out. Add "Canvas tote" (29.00) ×1 to the cart.
2. Apply SAVE10. Total shows 26.10, discount line shows SAVE10 −2.90.
3. Change quantity to 2.
4. Actual: total 58.00, discount line still SAVE10 −2.90. Expected: 52.20, discount line SAVE10 −5.80.
5. Continue to payment: amount charged is 58.00.
Log line at step 3: `pricing.recompute cart=… discount=null`.
## Cause Hypothesis
The quantity-change path recomputes the total without passing the applied code, so the discount is dropped while the UI keeps the stale line. Likely in the recompute call, not in the discount engine, since step 2 is right.
## Acceptance Criteria
1. **The expected behavior holds**
**Given** a cart with a valid code applied
**When** the shopper changes an item's quantity
**Then** the total recomputes with the code still applied and the discount line shows the new amount
2. **Tests cover the condition found and fixed**
**Given** the pricing test suite
**When** it runs
**Then** a test that fails on the defect and passes on the fix covers a quantity change with a code applied, and one covers removing an item with a code applied
3. **Or: no change is needed, with proof**
**Given** the reproduction
**When** it is run on the current code
**Then** the total already recomputes with the code applied, or the report was mistaken, with the evidence recorded in Notes — this supersedes 1 and 2
## References
- parent — none
- logs — support ticket #4471, attachment pricing.log
```
@@ -0,0 +1,107 @@
---
tracker_id: "" # with remote (the url), written at publish on a tracker; cut on the repo store
key: "" # tracker project or team for everything under this, when it differs from the store's
type: epic
title: "[The outcome this container exists to reach]"
parent: [folder name of the initiative]
covers: [parent capability ids this epic owns; keep these when adding an epic-local spec; a constraint is cited in References, not covered]
after: [] # epics this whole container waits on; the order of epics is in the initiative's tickets.toml
assignee: "" # status is added when work starts (in-progress | done | dropped)
risk: [low|medium|high — the highest expected among its children]
---
<!-- At initiative slicing, the envelope: frontmatter, Description, Outcome, Done when, Boundaries, References, and known Notes. Requirements are completed at this epic's inception, and its children are planned in `tickets.toml` beside this file. -->
# [Title]
## Description
[What is true when this is done and why it matters. With a spec, one paragraph pointing at it; without one, the intent in full.]
## Outcome
[One sentence: for whom, what changes, and the signal that shows it worked — the spec's, named not restated, when there is one.]
## Requirements
[The requirement source at this altitude: the source's lines as stable ids for children to cite, each mapping to a parent id in covers. Reuse the parent's ids when the lines are the parent's; when the epic splits or adds one, mint `E<n> (R<m>)` so the map is on the line. An epic with no parent ids, such as the platform baseline, leaves covers empty and cites the source section on each line instead. A referenced numbered source replaces it; a separate spec only when the source outgrows this section.]
## Done when
[Three to six checks the owner runs at the end, taken from the source's measures and limits. Closing every child is not one.]
## Boundaries
[Which boundary this container follows — team, service, UI, capability — and what it is not. Point at the spec's non-goals when there is one.]
## References
[`type — location, section`. The spec at this level when one exists (its references are followed from there); otherwise what this was split from.]
- parent — [path or url, section containing the upstream requirement ids]
- spec — [local spec when present; its capability ids map back to parent ids]
- constraint — [the source section that binds this container: privacy, platform, licensing, performance]
- [an input the spec does not carry — location, section]
## Notes
[What is not settled at this level. Cut if empty.]
- Assumption: [a choice made while slicing that the user has not confirmed]
- Open question: [what the source does not settle and which children wait on it]
- Unknown: [what is not yet known and which entries wait on it, recorded now so it is not found late]
- Parked: [a requirement id not placed on any child, and why, with the user's knowledge]
- Decision: [a choice the user made, dated, so it is not asked again — a declined suggestion belongs here too]
- Source conflict: [id or section — what the source says vs what the code or another source shows]
- Waits on [epic] because: [the one-line reason for each entry in after]
<!-- Example, not part of the ticket: an incepted epic with no spec of its own. The parent assigned R1–R4 to this epic from an unnumbered PRD; Requirements records those lines using the same ids. Done when holds deliverable checks. Notes holds decisions and unknowns. -->
```markdown
---
type: epic
title: "Shoppers manage their cart"
parent: initiative-checkout
covers: [R1, R2, R3, R4]
assignee: ""
risk: medium
---
# Shoppers manage their cart
## Description
A shopper adds items, changes quantities, applies discount codes, and recovers from every refusal with a reason — and the total they see is always the one the payment step receives.
## Outcome
Shoppers who reach the cart continue to payment more often because the total never surprises them; the PRD's cart-to-payment completion measure is the signal.
## Requirements
- R1: Add, remove, and change quantity of any item; the total updates at once. (PRD, Capabilities, Cart)
- R2: Apply one discount code; refused codes say why. (PRD, Capabilities, Cart)
- R3: The total shown is the total charged, before tax. (PRD, Capabilities, Cart)
- R4: Cart actions respond within 300 ms on the slowest supported device. (PRD, Constraints)
## Done when
1. R1–R3 work end to end on the live site, refusals included, with the reason shown.
2. The end-to-end suite proves the shown total equals the amount sent to payment on every cart path.
3. R4 holds on the slowest supported device under the load test.
4. A shopper with no code applied sees no change from today's cart.
## Boundaries
The cart UI. Not the pricing service (epic Pricing rules), not tax (epic Tax and payment).
## References
- prd — _bmad-output/initiative-checkout/prd-checkout/prd-checkout.md, sections Capabilities and Constraints
- design — https://figma.com/design/ab12cd/checkout, frame Cart
## Notes
- Decision: codes are case-insensitive (user's decision, 2026-08-12).
- Unknown: whether the discount engine can validate a code within R4; entry 04 waits on it.
```
@@ -0,0 +1,104 @@
---
tracker_id: "" # with remote (the url), written at publish on a tracker; cut on the repo store
key: "" # tracker project or team for everything under this, when it differs from the store's
type: initiative
title: "[The outcome this container exists to reach]"
parent: none
covers: [capability ids from the spec at this level, or from Requirements below when the source has none; a constraint is cited in References, not covered]
after: [] # epics this whole container waits on; the order of epics is in the initiative's tickets.toml
assignee: "" # status is added when work starts (in-progress | done | dropped)
risk: [low|medium|high — the highest expected among its children]
---
# [Title]
## Description
[What is true when this is done and why it matters. With a spec, one paragraph pointing at it; without one, the intent in full.]
## Outcome
[One sentence: for whom, what changes, and the signal that shows it worked — the spec's, named not restated, when there is one.]
## Requirements
[The requirement source at this altitude: the source's lines as stable ids for children to cite, each mapping to a source id in covers. A referenced numbered source replaces it; a separate spec only when the source outgrows this section.]
## Done when
[Three to six checks the owner runs at the end, taken from the source's measures and limits. Closing every child is not one.]
## Boundaries
[Which boundary this container follows — team, service, UI, capability — and what it is not. Point at the spec's non-goals when there is one. Then one line per unit the work touches that gets no epic, and the tracer path across epics in one sentence.]
- Touch point: [unit] — [what is consumed or configured there]; owner: epic-[slug]
## References
[`type — location, section`. The spec at this level when one exists (its references are followed from there); otherwise what this was split from.]
- spec — [path from {project-root}, or url for a remote source; section Capabilities]
- constraint — [the source section that binds this container: privacy, platform, licensing, performance]
- [an input the spec does not carry — location, section]
## Notes
[What is not settled at this level. Cut if empty.]
- Assumption: [a choice made while slicing that the user has not confirmed]
- Open question: [what the source does not settle and which children wait on it]
- Unknown: [what is not yet known and which entries wait on it, recorded now so it is not found late]
- Parked: [a requirement id not placed on any child, and why, with the user's knowledge]
- Decision: [a choice the user made, dated, so it is not asked again — a declined suggestion belongs here too]
- Source conflict: [id or section — what the source says vs what the code or another source shows]
- Waits on [epic] because: [the one-line reason for each entry in after]
<!-- An initiative is the business outcome its epics serve, tied to a company goal, usually spanning quarters and more than one boundary. For a solo developer or small team with no larger goal above it, a whole product is a fine initiative.
Example, not part of the ticket: match its level of detail. This one keeps a separate spec because its source outgrew the section: Description points at it, covers cites its ids, Requirements is cut, Outcome names its signal. Done when reads as business outcomes a product owner checks at the end, not deliverables. The epic order, with what each needs from the one before, is in `tickets.toml` beside this file. -->
```markdown
---
type: initiative
title: "Checkout that shoppers finish"
parent: none
covers: [C1, C2, C3, C4, C5, C6, P1, P2, T1]
assignee: ""
risk: high
---
# Checkout that shoppers finish
## Description
Shoppers go from cart to paid order in one pass, with the price they saw, on any supported device or as a guest. The spec owns the capabilities, constraints, and non-goals; this initiative delivers them for the fall release.
## Outcome
The Q4 revenue target depends on lifting cart-to-payment completion from 55% to 70%; this initiative owns that number, the spec's success signal, measured over a full quarter.
## Done when
1. Cart-to-payment completion holds at the spec's target for one full quarter after release.
2. Every capability in C1–C6, P1–P2, and T1 is live for all shoppers, not behind a flag.
3. Refund and chargeback rates are no worse than the quarter before release.
4. No P0 or P1 checkout bug open for more than a day during the first month.
## Boundaries
The web store's checkout flow, the payment-provider integration, and guest checkout across web and mobile. Not the order-management backend beyond the order it creates, not loyalty, not the catalog; see the spec's non-goals. Tracer path: one catalog item priced, carted, taxed, and paid by a signed-in shopper.
- Touch point: notification service — a new order-confirmation template, no code change; owner: epic-tax-and-payment
## References
- spec — _bmad-output/initiative-checkout/spec-checkout/spec-checkout.md
- constraint — the same spec, section Constraints, PCI scope and response time
- prd — _bmad-output/initiative-checkout/prd-checkout/prd-checkout.md, for history only
## Notes
- Parked: gift cards (C7); not in the fall release, user's call 2026-08-12.
- Decision: one payment provider for v1, replacing the current one (user's decision, 2026-08-12).
- Unknown: whether the tax service can meet the response-time constraint at peak; epic Tax and payment owns the answer.
```
@@ -0,0 +1,99 @@
---
id: [the entry's id in tickets.toml; the folder's `next_id` for a ticket with no entry] # tracker_id and remote are written at publish on a tracker
type: spike
title: "[The question this answers]"
parent: [folder name of the epic, or of the initiative when there are no epics; none for a standalone ticket in backlog/]
covers: []
after: [] # prerequisites only: a sibling's id; "<epic id>.<entry id>" in another epic; epic-<slug> for that whole epic
assignee: "" # a tracker's assignee, written by `tickets.py mirror`; otherwise assignee, blocked_at, and blocked_reason live in the plan
# status lives in the plan beside this file, not here; tracker_status by a tracker sync
refined: false # true once the user approves its full criteria; a pulled spike has this line only when its entry says `refine = true`
hitl: true
risk: [low|medium|high]
---
<!-- The refined shape. A done or dropped ticket stays as it was written. -->
# [Title]
## Description
[The question, why it is open, and which tickets wait on the answer.]
## Approach
[The steps, as many as the question needs: what to research, what to design, what to prototype, who signs off. The time box. What a good-enough answer looks like.]
## Acceptance Criteria
1. **[The candidates are compared on the criteria that decide]**
Verify: [where the comparison is recorded]
2. **[The prototype shows the chosen answer works for the cases that matter]**
Verify: [where the prototype and its results live]
3. **[The answer is recorded, with its consequences, where the waiting tickets can find it]**
Verify: [where]
4. **[The people who must accept it have]**
Verify: [who, recorded where]
## References
- parent — [path or url]
- [source — path, section]
## Notes
[Assumptions and open questions about the spike itself, each marked. Cut if empty.]
<!-- Example, not part of the ticket: match its level of detail. What is good here: the unknown was a placeholder in the architecture; the spike names who waits, runs research, design, prototype, and sign-off in a time box, says what good enough is, and records the answer where the stories will find it. hitl because people accept the finding. -->
```markdown
---
id: 2
type: spike
title: "How do field devices merge conflicting observations after days offline?"
parent: epic-field-sync
covers: []
refined: true
hitl: true
risk: low
---
# How do field devices merge conflicting observations after days offline?
## Description
The architecture left sync as a placeholder: "devices reconcile on reconnect." Researchers in the field edit the same observation records on several tablets for days without a link, then sync over a slow satellite connection. Three planned stories in this epic — edit observations offline, sync on reconnect, show merge results to the team lead — cannot be refined for implementation until we know how conflicting edits merge, what that does to the data model, and whether the link can carry it.
## Approach
Time box: four days. At the end, the best answer so far is recorded, not a perfect one.
1. Research: compare three ways to merge — a CRDT library, last-writer-wins per field with a conflict log, and a custom merge on the observation schema — on the criteria that decide: correctness on the six conflict cases the field team listed, payload size over a 2 kbps link, library maturity, and how much of the data model each forces us to change.
2. Design: a one-page note on the chosen approach, its data-model consequences, and the cases it does not resolve automatically.
3. Prototype: two tablets and a simulated link; run the six conflict cases and measure sync payload and time.
4. Sign-off: the lead engineer accepts the design; the field science lead accepts how unresolved conflicts are shown to people.
Good enough: all six cases merge as the field team expects, or the exceptions are listed with how a person resolves them, and a full day's edits sync in under ten minutes on the simulated link.
## Acceptance Criteria
1. **The three approaches are compared on the deciding criteria**
Verify: the comparison table is in epic-field-sync/spike-04-offline-merge-comparison.md
2. **The prototype merges the six conflict cases on a simulated link**
Verify: the prototype and its run log are under epic-field-sync/spike-04-offline-merge-prototype/, with payload and time per case
3. **The answer and its data-model consequences are recorded where the stories will find them**
Verify: the design note is epic-field-sync/spike-04-offline-merge-design.md and the choice is in epic-field-sync.md Notes as a decision
4. **The lead engineer and the field science lead have accepted it**
Verify: both named in that decision line with the date
## References
- parent — epic-field-sync/epic-field-sync.md
- architecture — docs/architecture.md, section Sync (the placeholder)
- field team conflict cases — docs/research/conflict-cases.md
## Notes
- Assumption: the satellite link is never better than 2 kbps up; confirm with the operations lead.
- Open question: is the conflict log kept forever, or pruned after the lead resolves it? The field science lead answers.
```
@@ -0,0 +1,113 @@
---
id: [the entry's id in tickets.toml; the folder's `next_id` for a ticket with no entry] # tracker_id and remote are written at publish on a tracker
type: story
title: "[What exists or works when this is done]"
parent: [folder name of the epic, or of the initiative when there are no epics; none for a standalone ticket in backlog/]
covers: [ids from the epic's spec, referenced numbered source, or Requirements; the ones this ticket delivers toward]
after: [] # prerequisites only: a sibling's id; "<epic id>.<entry id>" in another epic; epic-<slug> for that whole epic
assignee: "" # a tracker's assignee, written by `tickets.py mirror`; otherwise assignee, blocked_at, and blocked_reason live in the plan
# status lives in the plan beside this file, not here; tracker_status by a tracker sync
refined: false # true once the user approves its full criteria; a pulled story has this line only when its entry says `refine = true`
hitl: false
risk: [low|medium|high]
---
<!-- Pulled: a one-sentence Description, Acceptance Criteria as one Verify: line, References, local Notes. Reviewed: the same, gone over with the user. Refined, with numbered criteria and Boundaries: only a ticket with no epic, an entry with `refine = true`, or on request. A done or dropped ticket stays as it was written. -->
# [Title]
## Description
[One sentence, what this delivers; gone over with the user at review. With numbered criteria: what exists or works when this is done and how it advances the epic, 2–4 sentences; the criteria carry the proof, do not restate them.]
## Acceptance Criteria
[One line, `Verify: how it will be checked`, nothing else. No epic, `refine = true`, or on request: the numbered criteria below instead.]
1. **[Short name of the behavior]**
**Given** [the state before: data, user, config]
**When** [the one action]
**Then** [what is observed: a response, a record, a screen — something a person or a test can check]
**And** [a further observation from the same action, when there is one]
2. **[Short name of the behavior]**
**Given** [...]
**When** [...]
**Then** [...]
## Boundaries
- Must not change: [adjacent behavior a valid implementation could damage — name behavior, not files]
## References
- parent — [path or url]
- [source document — path, section]
- [design or prototype — path, for UI work]
## Notes
[Only what is not in the repo or the source, and what is not settled. Cut if empty.]
- [A frozen interface, a declined option]
- Decision: [a choice the user made, dated]
- Assumption: [a choice made while drafting that the user has not confirmed; confirmed, it becomes a Decision line]
- Open question: [what only this ticket waits on, answered as part of its work; one that touches siblings goes in the parent's Notes]
<!-- Example, not part of the ticket: match its level of detail. What is good here: the Description is what the shopper can do, end to end; every criterion states a rule, not an instance, with its failure path; Boundaries names behavior, not files; References points at the nearest document; Notes holds only what is not in the repo or the source, plus one assumption for the user to confirm. Numbered criteria because its entry says `refine = true`. -->
```markdown
---
id: 4
type: story
title: "A shopper applies a discount code and sees the new total"
parent: epic-cart-rules
covers: [R2, R3]
after: [3]
refined: true
hitl: false
risk: medium
---
# A shopper applies a discount code and sees the new total
## Description
A shopper with items in the cart enters a discount code, and the cart total updates to show the discount before tax. An invalid or expired code tells them why it was refused and leaves the total as it was.
## Acceptance Criteria
1. **Valid code reduces the total**
**Given** a cart and a valid discount code
**When** the shopper applies it
**Then** the total drops by the code's value and a discount line shows the code and the amount
2. **Expired code is refused with the reason**
**Given** a cart and an expired code
**When** the shopper applies it
**Then** the total is unchanged and the message says the code expired and when
3. **Unknown code is refused without revealing valid codes**
**Given** a cart and a code that does not exist
**When** the shopper applies it
**Then** the total is unchanged and the message says the code is not recognized, nothing more
4. **An applied code survives a quantity change**
**Given** an applied code
**When** the shopper changes an item's quantity
**Then** the total recomputes with the code still applied
5. **Payment receives the discounted total**
**Given** an applied code
**When** the shopper continues to payment
**Then** the amount sent to payment equals the total shown
## Boundaries
- Must not change: catalog prices, tax computation, the cart for shoppers who apply no code.
## References
- parent — _bmad-output/initiative-checkout/epic-cart-rules/epic-cart-rules.md, Requirements R2 and R3
- design — https://figma.com/design/ab12cd/checkout, frame Cart
## Notes
- Decision: the discount engine's `validate(code, cart) -> {amount, reason}` interface is frozen (2026-08-12).
- Assumption: the refusal messages above are final copy; no design text exists for them.
```
@@ -0,0 +1,51 @@
# tickets.toml — a container's agreed children. tickets.py reads it.
# The order of the tables is the build order: move a table to reorder. `id` names a child for good and is never reused; it is not an order.
# An epic's file holds [[entry]] tables. An initiative's holds [[epic]] tables; with no epics it holds [[entry]] tables.
# Keys tickets.py does not know are ignored, so a team may add its own.
# Status never lives here: an entry with no plan and no leaf file is planned; its plan beside it carries `status`, and on a tracker `tickets.py mirror` writes `tracker_status` in the leaf file.
# ---- In an epic folder: one table per planned story, spike, or bug ----
[[entry]]
id = 1 # the leaf file carries it as `id:`; refer to it as 1 here, as <epic id>.1 from another epic
type = "story" # story | spike | bug
title = "Cart service scaffold"
description = "Stands up the cart service with one add-item path through UI, API, and store."
verify = "A shopper adds one item on the deployed site and sees it in the cart."
covers = ["R1"] # ids from the epic's requirement source
after = [] # real prerequisites only, never the sequence; empty means it can run beside anything above it. A bare number is a sibling's id; everything else is quoted
hitl = true # a person runs the starter
risk = "low" # low | medium | high
# estimate = 2 # only when the user gives one; never proposed
# unknown = "..." # a question to settle with the user before this entry starts, one sentence; `tickets.py next` lists the entry under blocked until it is removed
# references = ["architecture-checkout/architecture-checkout.md#ad-8"] # what this entry needs beyond the epic's own References; pull writes them to the ticket
# notes = ["..."] # the user's words only, never filled by the agent; pull writes them to the ticket's Notes
# refine = true # refine to full acceptance criteria before it starts
# plan_checkpoint = true # a person reviews the ticket, or the builder's plan, before work starts
# done_checkpoint = true # work pauses after this ticket until a person says continue
[[entry]]
id = 2
type = "story"
title = "Apply and refuse discount codes"
description = "Adds code validation to the pricing call and shows the refusal reason."
verify = "A valid code lowers the total; an expired one is refused with its reason."
covers = ["R2", "R3"]
after = [1, "1.3"] # a sibling's id; "<epic id>.<entry id>" for an entry in another epic; "epic-<slug>" for that whole epic
hitl = false
risk = "medium"
# ---- In an initiative folder: one table per epic; table order is the build order ----
[[epic]]
id = 1 # entries in other epics refer to this epic's entries as 1.<entry id>
slug = "epic-pricing-rules" # the epic's folder name
title = "Pricing rules"
covers = ["P1", "P2"]
[[epic]]
id = 2
slug = "epic-cart-rules"
title = "Shoppers manage their cart"
covers = ["C1", "C2", "C3"]
after = [{ epic = 1, needs = "the one-function pricing contract" }] # what this epic needs from which; the entries that wait on it carry the `after`