Skip to content

Add bmad-ticket: deterministic ticket tree skill for the v7 planning lane - #2672

Open
bmadcode wants to merge 9 commits into
mainfrom
ticket-master
Open

Add bmad-ticket: deterministic ticket tree skill for the v7 planning lane#2672
bmadcode wants to merge 9 commits into
mainfrom
ticket-master

Conversation

@bmadcode

@bmadcode bmadcode commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

What

Adds bmad-ticket, the v7 deterministic ticketing skill (src/bmm-skills/plan/bmad-ticket/), replacing the v6 create-epics-and-stories / sprint-status pipeline for the planning lane, per the locked v7-spec-ticketing plan (D-1..D-36).

The skill

  • Three routes, cheap exit first: refine (one ticket, minimal ceremony), slice (open scope → detailed epic set, stops at epics), incept (one epic → its stories, just-in-time). Guided / quick / autonomous modes; inline Args surface with a side-effect-free --help path.
  • Ticket tree grammar: epic = folder + ticket.md envelope, leaves = KEY-n-slug.md, parent = containing folder, one stored fact (status), generated identity-only index.md. Derived state (frontier, board, rollups, graph) is always computed, never written.
  • Epic done is intentional, never calculated (D-23 revised): computed epic state is only not-started / in-progress; done/dropped are stored by a human call (retrospective or user), and only a stored done releases dependents.
  • Review-before-present: finalize_reviewers (adversarial-general, edge-case-hunter, verification-gap) run over every full draft before the user — or the autonomous gate — sees it. Skeleton-first drafting; coequal-expert opening.
  • Archive-on-done (D-36): when done lands on an epic, the skill offers to move its stories to .archive/<date>-<slug>/ as the dated record (--purge deletes when the record lives elsewhere, e.g. Jira). Envelope stays as the durable layer; archived ids are never reissued.

Deterministic layer (dual-homed: skill-local + src/scripts/)

  • update_ticket.py — the frontmatter write gate: transition-graph status gating (--force = explicit user override, gibberish always refused), hitl derived upward from risk, cycle refusal, newline-injection refusal, duplicate-id refusal, atomic writes, archive hint on epic done.
  • ticket_tree.pynext-id, index, validate, list, frontier, board, coverage --proposed, graph --mermaid (lanes + critical path), render (single epics-and-stories view), archive. Malformed files, duplicate ids, orphan leaves, and scalar list fields are detected, never silently swallowed.
  • Byte-identity sync test keeps both homes honest; 60+ tests green in each.

Integration + docs

  • bmad-spec hands off all decomposition to bmad-ticket with the route named (stories.yaml removed).
  • bmad-create-epics-and-stories → v6-shims: pitch-then-comply forward (JIT rationale; v6 single-file shape honored via slice → chained incept → render).
  • bmad-sprint-planning legacy boundary: existing sprint-status.yaml flows stay supported; net-new work never feeds it.
  • module-help.csv: bmad-ticket (TK) takes the row; prd/ux/architecture next-step pointers updated.
  • New explanation doc: docs/explanation/tickets-and-specs.md.
  • Full eval suite: 8 quality cases (autonomous + HITL), 8/8 trigger + 8/8 non-trigger queries.

Known seam (deliberate, follow-up slice)

bmad-build-auto still reads stories.yaml and HALTs when it is absent. The build lane's move to the ticket tree is a separate v7 slice; until it lands, the v7 planning lane and the v6 build lane don't chain. Tracked in the v7 plan.

🤖 Generated with Claude Code

https://claude.ai/code/session_012KL7CNy8457v7uQ7Xkmor4

…lane

One skill, three routes: refine (one ticket), slice (scope -> detailed
epic set), incept (one epic -> its stories through the breakdown-quiz
gate). Tickets live as one file per leaf with schema-1 frontmatter;
epic = folder + ticket.md envelope; one stored fact (status); index,
frontier, board, and coverage always derived, never written.

Deterministic layer (dual-homed: src/scripts canonical + bundled skill
copies, sync-tested):
- update_ticket.py: the only write path for existing frontmatter -
  field/type/value gating, lifecycle transition graph (configurable,
  --force for explicit user overrides), upward hitl derivation,
  cycle-closing edge rejection
- ticket_tree.py: next-id, index regen, validate (schema/placeholder/
  dep/cycle gate run after every write), list, frontier, board,
  coverage (--proposed for pre-gate checks)

Skill surface: swappable per-type templates (AC rendering is a
template concern), risk scoring with hard floors that bind proposals
but never the user, greenfield Epic-1 scaffolding guidance calibrated
to project purpose, bounded bundled questioning, autonomous return
contract for slice->incept chaining. 34 unit tests green in both
script homes.
…l suite

bmad-spec no longer authors epics or stories: the Story Breakdown
section becomes a handoff — on offer or direct request it invokes
bmad-ticket with SPEC.md + companions as source (slice = epic set with
CAP-N covers verbatim; incept = one epic's stories). stories.yaml and
its schema asset are retired; spec updates that touch capabilities now
point at the ticket-side coverage check.

bmad-ticket eval suite (evals/): 8 quality cases exercising autonomous
and hitl behavior end to end — quick refine from a messy bug report,
hard-floor enforcement (deletion+migration -> risk>=4/hitl true), user
override honored and recorded as a decision, PRD -> epic set with full
FR coverage, gate-approved guided write with field-for-field fidelity,
epic inception (vertical stories, verify tails, e2e criteria), update-
gate refusal relay, and greenfield Epic-1 calibration — plus 16 trigger
queries and fixtures (PRD, bug report, braindump, seeded NOTI tree).
First full run: 8/8 cases executed, 47/48 assertions passed (the one
fail was a rubric bug, since fixed); triggers 16/16 at 3 runs per
query. Rubrics hardened from grader critique: judgment checks
(verticality, dependency sanity, untouched siblings, derived-
requirement disclosure) alongside the structural ones.

Also: activation step 1 names the no-_bmad fallback explicitly, seen
recovering in every clean-room eval run.
…mad-ticket

- finalize_reviewers defaults to the three bmad-review lenses; drafts are
  reviewed and improved before the user (or autonomous gate) sees them
- skeleton-first checkpoint for multi-artifact runs (title + summary +
  covers before expensive full drafts)
- coequal-expert open floor: validate resources, invite guidance,
  converse on contradictory advice
- ticket_tree.py graph verb: parallel lanes, critical path, --mermaid
  render; sequencing-optimization offer wired after incept writes
@greptile-apps

greptile-apps Bot commented Aug 2, 2026

Copy link
Copy Markdown

Greptile Summary

This PR introduces bmad-ticket, the v7 deterministic ticketing skill, replacing the v6 create-epics-and-stories / sprint-status planning lane. It adds two stdlib-only Python scripts (ticket_tree.py, update_ticket.py) with atomic writes, transition gating, and cycle detection, dual-homed between src/scripts/ and the skill bundle with a byte-identity sync test.

  • New skill (bmad-ticket): three routes (refine / slice / incept), three modes (guided / quick / autonomous), finalize_reviewers on every draft, and explicit archive-on-done workflow via ticket_tree.py archive.
  • Deterministic scripts: update_ticket.py gates every frontmatter write (transition graph, hitl derivation, newline-injection refusal, cycle detection, atomic swap); ticket_tree.py provides next-id, validate, frontier, board, graph, render, and archive.
  • Integration: bmad-spec hands off decomposition to bmad-ticket; v6 skills become shims; the build-lane gap (bmad-build-auto still reading stories.yaml) is called out as a deliberate follow-up slice.

Confidence Score: 4/5

Safe to merge with the cmd_graph depth-computation bug tracked; all ticket-gating, writing, and validation paths are correct and well-tested.

The only defect found is in cmd_graph's iterative longest-path algorithm: when two sibling dependencies of the same parent also depend on each other (a diamond pattern), both are placed on onstack simultaneously, so the inner sibling's memo is computed as 0 before the outer sibling resolves. This causes lanes and critical_path to be wrong for a common planning topology. All other commands are correct and the test suite is otherwise thorough.

Files Needing Attention: src/scripts/ticket_tree.py — specifically the iterative depth loop in cmd_graph (lines 335-353); the stack.extend(pending) pattern needs to push one dep at a time to avoid premature memo computation for sibling deps.

Important Files Changed

Filename Overview
src/scripts/ticket_tree.py New deterministic read/derive script for the ticket tree; all commands except graph are correct, but the iterative longest-path depth algorithm in cmd_graph gives wrong memos for diamond dependency patterns, producing an incorrect critical_path and lanes output.
src/scripts/update_ticket.py Frontmatter write-gate with transition gating, cycle detection, atomic writes, hitl derivation, and newline-injection refusal; logic is correct and well-tested.
src/scripts/tests/test_ticket_tree.py Comprehensive test suite covering frontier gating, epic lifecycle, validate, archive, render, mermaid, and deep-chain survival; missing a diamond-dep graph test that would expose the depth-computation bug.
src/scripts/tests/test_update_ticket.py Thorough test suite with correct requires-python header; covers transitions, force, hitl derivation, cycle rejection, immutable fields, body preservation, and noop.
src/scripts/tests/test_dual_home_sync.py Byte-identity sync guard for the dual-homed scripts; skips gracefully when run from an installed layout without the sibling home.
src/bmm-skills/plan/bmad-ticket/SKILL.md Well-structured skill definition with three routes, mode table, tree-query guidance, and archive/v6-compat sections; resolution rules and arg surface are clear.
src/bmm-skills/plan/bmad-ticket/customize.toml Customization surface is well-documented with sensible defaults; lifecycle_transitions, hitl_threshold, finalize_reviewers, and severity_scale are all present and correct.
src/bmm-skills/plan/bmad-spec/SKILL.md Updated to hand off decomposition to bmad-ticket; the known build-lane seam is acknowledged in the PR description as a deliberate follow-up slice.
src/bmm-skills/plan/bmad-create-epics-and-stories/SKILL.md Replaced by pitch-then-comply v6-shim forwarding to bmad-ticket; v6 single-file shape is preserved via slice to chained incept to render.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    User([User request]) --> SKILL[bmad-ticket SKILL.md]
    SKILL --> RouteDetect{Route detection}
    RouteDetect -->|one ticket| Refine[Route 1: Refine]
    RouteDetect -->|open scope| Slice[Route 2: Slice]
    RouteDetect -->|one epic| Incept[Route 3: Incept]
    RouteDetect -->|status / update| Gates[Ticket gates]
    Refine --> WriteFile[Write ticket file]
    Slice --> WriteFile
    Incept --> WriteFile
    WriteFile --> Reviewers{finalize_reviewers}
    Reviewers --> Validate[ticket_tree.py validate]
    Validate --> Present[Present to user]
    Gates --> UpdateScript[update_ticket.py]
    UpdateScript --> TransitionGate{Transition graph + cycle check}
    TransitionGate -->|allowed| AtomicWrite[Atomic file write]
    TransitionGate -->|blocked| Fail[JSON error]
    AtomicWrite --> Done([Done])
Loading

Reviews (4): Last reviewed commit: "Merge branch 'main' into ticket-master" | Re-trigger Greptile

Comment thread src/scripts/tests/test_update_ticket.py
Comment on lines +130 to +136
## Epics and Stories (handoff to bmad-ticket, interactive-only)

Requires `SPEC.md` on disk — run the normal Operation first if it doesn't exist yet. Headless runs never do this, even when the invocation text asks for it: if mode detection (On Activation, step 4) resolved headless, skip this section entirely and proceed with the normal headless response. In interactive mode, offer it at most once per run when the input reads as multiple independently shippable slices; a decline ends the offer for this run, not forever. Also run it on direct request ("break this into stories") whenever `SPEC.md` exists. When a spec update runs and `stories.yaml` exists, check the story descriptions against the updated spec; if any no longer matches, say so and offer to re-run Story Breakdown. The update itself never rewrites `stories.yaml`.
Decomposition belongs to the `bmad-ticket` skill — it owns the ticket tree, the breakdown gates, and the coverage contract; bmad-spec never authors epics or stories itself. Requires `SPEC.md` on disk — run the normal Operation first if it doesn't exist yet. Headless runs never do this, even when the invocation text asks for it: if mode detection (On Activation, step 4) resolved headless, skip this section entirely and proceed with the normal headless response. In interactive mode, offer it at most once per run when the input reads as multiple independently shippable slices; a decline ends the offer for this run, not forever. Also run it on direct request ("break this into epics", "break this into stories") whenever `SPEC.md` exists.

Either way, walk the capabilities and constraints with the user and propose a story per independently reviewable slice — this is a conversation, not a silent render. For each story, ask the user for `spec_checkpoint`, `done_checkpoint`, and any `invoke_dev_with` note rather than defaulting them silently; capturing that human judgment is what the fields are for. If the conversation surfaces load-bearing detail beyond dispatch notes (a constraint, a design decision), route it into SPEC.md or a companion — `invoke_dev_with` carries dispatch notes only (Spec Law rule 7 still applies).
On acceptance, invoke `bmad-ticket` with the spec folder as input — `SPEC.md` plus its `companions:` are the source documents. Its slice route produces the epic set with `covers:` carrying this spec's `CAP-N` ids verbatim; its incept route produces one epic's stories on request. That trace spine is why capability IDs must stay stable (Spec Law rule 6): ticket-side coverage validates against them.

The output is `stories.yaml`, a sibling of `SPEC.md` inside the spec folder, discovered by that fixed name — same convention as `SPEC.md` and `.memlog.md`. Never list it in `companions:` and never point a frontmatter key at it: companions carry the what-to-build contract every consumer reads; `stories.yaml` is input for whichever tool dispatches the stories.

Field definitions, the validity rules, and a worked example live in `assets/stories-schema.md`. Before writing or re-writing the file, check every entry against those rules; fix violations rather than presenting a file that fails them. Record the check's verdict to `.memlog.md` (`append --type event`), the same discipline as Self-Validate.

Derive `stories.yaml` from `.memlog.md` exactly like any other spec-authored artifact: log each proposed story (`--type decision`) as the user agrees to it, then render. On a later run against the same spec folder, re-derive the same way, handling ids per the schema's update semantics.
When a spec update changes, adds, or retires capabilities and a ticket tree already covers this spec, say so — the tree may have drifted from the contract — and suggest re-running the ticket-side coverage check.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Build lane integration gap: stories.yaml removed but bmad-build-auto still reads it

bmad-spec is updated to never author epics or stories and stories-schema.md is removed, meaning no planning artifact now produces stories.yaml. However, src/bmm-skills/ship/bmad-build-auto/step-01-clarify-and-route.md still reads {spec_folder}/stories.yaml and explicitly HALTs with blocked if the file is missing. Any v7 project that runs the new planning lane and then tries to chain into bmad-build-auto will immediately halt. Either the build lane needs a corresponding v7 update (reading from the ticket tree), or this should be called out explicitly in the PR as a known broken integration pending a follow-up.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/bmm-skills/plan/bmad-spec/SKILL.md
Line: 130-136

Comment:
**Build lane integration gap: `stories.yaml` removed but `bmad-build-auto` still reads it**

`bmad-spec` is updated to never author epics or stories and `stories-schema.md` is removed, meaning no planning artifact now produces `stories.yaml`. However, `src/bmm-skills/ship/bmad-build-auto/step-01-clarify-and-route.md` still reads `{spec_folder}/stories.yaml` and explicitly HALTs with `blocked` if the file is missing. Any v7 project that runs the new planning lane and then tries to chain into `bmad-build-auto` will immediately halt. Either the build lane needs a corresponding v7 update (reading from the ticket tree), or this should be called out explicitly in the PR as a known broken integration pending a follow-up.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread src/scripts/ticket_tree.py Outdated
Comment thread src/scripts/ticket_tree.py
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds the bmad-ticket planning skill, ticket templates, configuration, workflow routes, evaluation fixtures, ticket-tree CLI tools, update utilities, mirrored-script tests, and delegation from bmad-spec away from stories.yaml.

Changes

Ticket workflow and tooling

Layer / File(s) Summary
Workflow delegation and ticket contracts
src/bmm-skills/plan/bmad-spec/SKILL.md, src/bmm-skills/plan/bmad-ticket/*, .gitignore
bmad-spec delegates interactive decomposition to bmad-ticket. The new skill defines ticket routes, templates, configuration, references, evaluation cases, fixtures, and trigger queries. The obsolete stories-schema.md documentation is removed.
Ticket-tree scanning and derived operations
src/scripts/ticket_tree.py, src/bmm-skills/plan/bmad-ticket/scripts/ticket_tree.py, src/scripts/tests/test_ticket_tree.py, src/bmm-skills/plan/bmad-ticket/scripts/tests/test_ticket_tree.py, */tests/test_dual_home_sync.py
Adds ticket-tree scanning, indexing, ID allocation, frontier and board reports, dependency graphs, coverage reports, schema validation, and mirrored-script consistency tests.
Ticket frontmatter updates
src/scripts/update_ticket.py, src/bmm-skills/plan/bmad-ticket/scripts/update_ticket.py, src/scripts/tests/test_update_ticket.py, src/bmm-skills/plan/bmad-ticket/scripts/tests/test_update_ticket.py
Adds atomic frontmatter updates with typed validation, lifecycle gates, forced transitions, risk-based HITL escalation, dependency checks, body preservation, JSON results, and comprehensive tests.

Estimated code review effort: 5 (Critical) | ~90 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Spec as bmad-spec
  participant Ticket as bmad-ticket
  participant Tree as ticket_tree.py
  participant Files as Ticket files
  Spec->>Ticket: Pass SPEC.md, companions, and CAP-N references
  Ticket->>Tree: Create or validate ticket-tree artifacts
  Tree->>Files: Write tickets and regenerate indexes
  Tree-->>Ticket: Return validation, coverage, and drift results
  Ticket-->>Spec: Report ticket results and capability-tree drift
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.97% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the addition of the bmad-ticket deterministic ticket tree skill, which is the main change.
Description check ✅ Passed The description directly explains the new bmad-ticket skill, deterministic tooling, integrations, tests, and known limitations.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ticket-master

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

🧹 Nitpick comments (3)
src/bmm-skills/plan/bmad-ticket/scripts/ticket_tree.py (3)

203-211: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Encode the link target in the generated index.

Line 207 inserts t['_rel'] into a Markdown inline link without encoding. cmd_validate only constrains the leaf filename prefix (line 375), so the remainder can contain spaces or parentheses. Both break the rendered link.

♻️ Proposed change
+from urllib.parse import quote
+
     def entry_lines(t, depth):
         pad = "  " * depth
         desc = t.get("description")
         tail = f" - {desc}" if desc else ""
-        lines = [f"{pad}* [{t.get('title', t['id'])}]({t['_rel']}){tail}"]
+        href = quote(t["_rel"])
+        lines = [f"{pad}* [{t.get('title', t['id'])}]({href}){tail}"]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bmm-skills/plan/bmad-ticket/scripts/ticket_tree.py` around lines 203 -
211, Update entry_lines to encode t["_rel"] before embedding it as the Markdown
link target, preserving the existing relative path while safely handling spaces,
parentheses, and other special characters permitted by cmd_validate.

310-321: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Escape more than the double quote in mermaid labels.

Line 316 replaces " with '. A title that contains [, ], or # still breaks the ["..."] node-label syntax and produces an unparseable diagram. The safest form is a mermaid entity escape for the bracket characters.

♻️ Proposed change
         for t in tickets:
             tid = str(t["id"])
-            title = str(t.get("title") or "").replace('"', "'")
+            title = (str(t.get("title") or "")
+                     .replace('"', "'")
+                     .replace("[", "`#91`;")
+                     .replace("]", "`#93`;"))
             suffix = "" if t["type"] == "epic" else f" ({t.get('status')})"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bmm-skills/plan/bmad-ticket/scripts/ticket_tree.py` around lines 310 -
321, Update the Mermaid label construction in the mermaid output block to escape
bracket characters and any other syntax-breaking label characters using
Mermaid-safe entity escapes, rather than only replacing double quotes. Preserve
the existing title and status formatting while ensuring generated node labels
remain parseable.

6-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the ticket_tree.py CLI verbs in docs/.

docs/how-to/customize-bmad.md documents _bmad/scripts with resolve_customization.py, but it does not describe the new ticket_tree.py CLI. Add coverage for next-id, index, validate, list, frontier, board, graph, and coverage, including the documented --root, --key, --path, --mermaid, --require, and --proposed flags.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bmm-skills/plan/bmad-ticket/scripts/ticket_tree.py` around lines 6 - 23,
Document the ticket_tree.py CLI in docs/how-to/customize-bmad.md, covering the
next-id, index, validate, list, frontier, board, graph, and coverage verbs.
Include each applicable --root, --key, --path, --mermaid, --require, and
--proposed flag, using the command usage and behavior described in
ticket_tree.py.

Sources: Path instructions, Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/bmm-skills/plan/bmad-spec/SKILL.md`:
- Around line 130-140: Before removing stories.yaml authoring or decomposition
from bmad-spec, add a migration/deprecation path that preserves
bmad-build-auto’s direct stories.yaml consumption. Update bmad-build-auto and
the referenced workflow-map, build-auto, and getting-deeper documentation to
define the transition and supported source of truth, then ensure the bmad-spec
“Epics and Stories” flow no longer creates an unsupported artifact.

In `@src/bmm-skills/plan/bmad-ticket/evals/fixtures/tree/index.md`:
- Around line 8-9: Update the leaf entry for NOTI-3-snooze-button.md to match
the title emitted by ticket_tree.py’s cmd_index, using either the ticket
frontmatter title or the fixture link text; ensure both resolve to “Snooze
button visual polish” so regeneration produces the expected index.

In `@src/bmm-skills/plan/bmad-ticket/scripts/tests/test_update_ticket.py`:
- Around line 136-140: Update the test file’s write_text calls in
test_hitl_raised_when_risk_crosses_threshold and test_cycle_rejected to pass
encoding="utf-8", and update their subsequent read_text calls to use the same
explicit encoding. Preserve the existing test data and assertions.

In `@src/bmm-skills/plan/bmad-ticket/scripts/ticket_tree.py`:
- Around line 73-86: Update the frontmatter sequence parsing in the key-handling
logic around parse_scalar so block list items may begin at zero indentation as
well as with whitespace. Ensure the loop recognizes lines matching “- ” at
column zero, collects them into fm[key], advances i past the sequence, and
preserves existing handling for indented sequences, inline arrays, and scalar
values.
- Around line 334-338: Update cmd_coverage so result["ok"] is false whenever
required IDs are uncovered, matching cmd_validate’s success convention; retain
true when all required IDs are covered or no requirements are provided, and
ensure the command exits non-zero when the coverage gate fails.
- Around line 365-376: Update cmd_validate’s ticket loop to validate t["type"]
against the known values epic and LEAF_TYPES before applying type-specific
filename checks. Report an error for missing, malformed, or unknown types, and
ensure invalid types do not proceed through either the epic or leaf validation
branch.
- Around line 426-454: Restrict the cycle detection block around the graph
traversal to the ticket selected by --path, so validation of one file does not
report unrelated tree-wide cycles; preserve full-graph scanning when no path is
supplied and ensure any reported cycle is relevant to the selected ticket.
Remove the dead stack = [] assignment immediately before break, and keep the
existing per-ticket validation behavior and error reporting consistent with the
selected scope.
- Around line 104-110: Update cmd_validate to detect duplicate ticket IDs after
its per-ticket validation loop by grouping tickets from scan by stringified id
and collecting each ticket’s _rel path. Add a validation error for every ID with
multiple paths, listing the paths in sorted order and using "(tree)" as the
error file. Leave scan’s by_id behavior unchanged.

In `@src/bmm-skills/plan/bmad-ticket/scripts/update_ticket.py`:
- Around line 244-246: Validate the fallback value loaded by the hitl_threshold
resolution in update_ticket before it reaches the comparison around line 318:
accept only an integer threshold, reject strings, floats, and other types with
the script’s documented single-JSON error response naming the required fix, and
preserve the CLI-provided threshold behavior.
- Around line 356-368: Preserve the existing ticket file permissions in the
atomic-write block under the changes handling flow: capture path.stat().st_mode
before creating the temporary file, then apply its permission bits to the temp
file with os.chmod before os.replace(tmp, path). Keep the existing cleanup
behavior, and flush/fsync the temporary file before replacement if implementing
the suggested durability improvement.
- Around line 13-20: Document the bmad-ticket CLI and ticket configuration,
including --root, --id, --path, --set, --transitions, --hitl-threshold, and
--force, plus workflow.lifecycle_transitions and workflow.hitl_threshold in
customize.toml. Add the guidance under the appropriate docs/ location and
describe bmad-ticket as the supported replacement for direct ticket frontmatter
edits.
- Around line 136-148: Update render so string values not already handled by
QUOTED_FIELDS are emitted as quoted, escaped YAML scalars whenever they are
unsafe as bare values, including values containing “: ” or starting with [, {,
#, &, *, or -. Preserve existing boolean, integer, list, explicitly
quoted-field, and empty plain-field handling, and apply the change to free-text
values such as discovered_from.

In `@src/bmm-skills/plan/bmad-ticket/SKILL.md`:
- Around line 1-13: Update the documentation references for the retired
bmad-create-epics-and-stories workflow and stories.yaml output to use
bmad-ticket and its ticket-tree output instead. Add bmad-ticket to the workflow
and skill documentation, including the Chinese, French, and English references
in reference/workflow-map.md, reference/commands.md, reference/build-auto.md,
and the tutorial, while preserving the existing language-specific structure and
command guidance.

In `@src/scripts/tests/test_dual_home_sync.py`:
- Around line 31-36: The missing-file skip in the dual-home sync loop hides real
drift and stops later scripts from being checked. In both
src/scripts/tests/test_dual_home_sync.py lines 31-36 and
src/bmm-skills/plan/bmad-ticket/scripts/tests/test_dual_home_sync.py lines
31-36, wrap each iteration of the SCRIPTS loop in self.subTest(script=name),
replace self.skipTest with assertTrue checks for both paths, and retain the
byte-equality assertion so both files remain identical.

In `@src/scripts/tests/test_update_ticket.py`:
- Around line 1-60: Update both src/scripts/tests/test_update_ticket.py lines
1-60 and src/bmm-skills/plan/bmad-ticket/scripts/tests/test_update_ticket.py
lines 1-60 identically: change the script requirement to Python >=3.11, make
run() raise an AssertionError containing proc.stderr when JSON parsing
proc.stdout fails, and add encoding="utf-8" to the write_text calls at lines 138
and 154 in each file.

In `@src/scripts/ticket_tree.py`:
- Around line 6-23: Apply all seven ticket_tree.py fixes in
src/scripts/ticket_tree.py (L73-L86, L104-L110, L203-L211, L310-L321, L334-L338,
L365-L376, and L426-L454), covering zero-indent block sequences, duplicate IDs,
index-link encoding, Mermaid label escaping, coverage ok semantics, type
validation, and --path-scoped cycle checks. Apply the identical changes at the
corresponding ranges in src/bmm-skills/plan/bmad-ticket/scripts/ticket_tree.py,
then verify both files are byte-for-byte identical.

In `@src/scripts/update_ticket.py`:
- Around line 170-193: The two script homes must receive identical updates: in
src/scripts/update_ticket.py lines 170-193 and
src/bmm-skills/plan/bmad-ticket/scripts/update_ticket.py lines 170-193, apply
all four requested fixes—make collect_tree reuse parse_frontmatter and reject
duplicate IDs, quote unsafe bare YAML scalars in render, validate hitl_threshold
loaded from customize.toml, and preserve the original file mode before
os.replace. Keep both files byte-for-byte identical so the dual-home sync test
passes.

---

Nitpick comments:
In `@src/bmm-skills/plan/bmad-ticket/scripts/ticket_tree.py`:
- Around line 203-211: Update entry_lines to encode t["_rel"] before embedding
it as the Markdown link target, preserving the existing relative path while
safely handling spaces, parentheses, and other special characters permitted by
cmd_validate.
- Around line 310-321: Update the Mermaid label construction in the mermaid
output block to escape bracket characters and any other syntax-breaking label
characters using Mermaid-safe entity escapes, rather than only replacing double
quotes. Preserve the existing title and status formatting while ensuring
generated node labels remain parseable.
- Around line 6-23: Document the ticket_tree.py CLI in
docs/how-to/customize-bmad.md, covering the next-id, index, validate, list,
frontier, board, graph, and coverage verbs. Include each applicable --root,
--key, --path, --mermaid, --require, and --proposed flag, using the command
usage and behavior described in ticket_tree.py.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: eb3f7cae-eb80-494c-bb74-578843e37108

📥 Commits

Reviewing files that changed from the base of the PR and between 770d425 and 4dd6e9c.

📒 Files selected for processing (31)
  • .gitignore
  • src/bmm-skills/plan/bmad-spec/SKILL.md
  • src/bmm-skills/plan/bmad-spec/assets/stories-schema.md
  • src/bmm-skills/plan/bmad-ticket/SKILL.md
  • src/bmm-skills/plan/bmad-ticket/assets/bug-template.md
  • src/bmm-skills/plan/bmad-ticket/assets/epic-template.md
  • src/bmm-skills/plan/bmad-ticket/assets/spike-template.md
  • src/bmm-skills/plan/bmad-ticket/assets/story-template.md
  • src/bmm-skills/plan/bmad-ticket/assets/task-template.md
  • src/bmm-skills/plan/bmad-ticket/customize.toml
  • src/bmm-skills/plan/bmad-ticket/evals/cases.json
  • src/bmm-skills/plan/bmad-ticket/evals/fixtures/bug-report.txt
  • src/bmm-skills/plan/bmad-ticket/evals/fixtures/greenfield-idea.md
  • src/bmm-skills/plan/bmad-ticket/evals/fixtures/prd-notify.md
  • src/bmm-skills/plan/bmad-ticket/evals/fixtures/tree/NOTI-3-snooze-button.md
  • src/bmm-skills/plan/bmad-ticket/evals/fixtures/tree/index.md
  • src/bmm-skills/plan/bmad-ticket/evals/fixtures/tree/notifications/ticket.md
  • src/bmm-skills/plan/bmad-ticket/evals/trigger-queries.json
  • src/bmm-skills/plan/bmad-ticket/references/greenfield-guidelines.md
  • src/bmm-skills/plan/bmad-ticket/references/incept-stories.md
  • src/bmm-skills/plan/bmad-ticket/references/slice-epics.md
  • src/bmm-skills/plan/bmad-ticket/scripts/tests/test_dual_home_sync.py
  • src/bmm-skills/plan/bmad-ticket/scripts/tests/test_ticket_tree.py
  • src/bmm-skills/plan/bmad-ticket/scripts/tests/test_update_ticket.py
  • src/bmm-skills/plan/bmad-ticket/scripts/ticket_tree.py
  • src/bmm-skills/plan/bmad-ticket/scripts/update_ticket.py
  • src/scripts/tests/test_dual_home_sync.py
  • src/scripts/tests/test_ticket_tree.py
  • src/scripts/tests/test_update_ticket.py
  • src/scripts/ticket_tree.py
  • src/scripts/update_ticket.py
💤 Files with no reviewable changes (1)
  • src/bmm-skills/plan/bmad-spec/assets/stories-schema.md

Comment on lines +130 to +140
## Epics and Stories (handoff to bmad-ticket, interactive-only)

Requires `SPEC.md` on disk — run the normal Operation first if it doesn't exist yet. Headless runs never do this, even when the invocation text asks for it: if mode detection (On Activation, step 4) resolved headless, skip this section entirely and proceed with the normal headless response. In interactive mode, offer it at most once per run when the input reads as multiple independently shippable slices; a decline ends the offer for this run, not forever. Also run it on direct request ("break this into stories") whenever `SPEC.md` exists. When a spec update runs and `stories.yaml` exists, check the story descriptions against the updated spec; if any no longer matches, say so and offer to re-run Story Breakdown. The update itself never rewrites `stories.yaml`.
Decomposition belongs to the `bmad-ticket` skill — it owns the ticket tree, the breakdown gates, and the coverage contract; bmad-spec never authors epics or stories itself. Requires `SPEC.md` on disk — run the normal Operation first if it doesn't exist yet. Headless runs never do this, even when the invocation text asks for it: if mode detection (On Activation, step 4) resolved headless, skip this section entirely and proceed with the normal headless response. In interactive mode, offer it at most once per run when the input reads as multiple independently shippable slices; a decline ends the offer for this run, not forever. Also run it on direct request ("break this into epics", "break this into stories") whenever `SPEC.md` exists.

Either way, walk the capabilities and constraints with the user and propose a story per independently reviewable slice — this is a conversation, not a silent render. For each story, ask the user for `spec_checkpoint`, `done_checkpoint`, and any `invoke_dev_with` note rather than defaulting them silently; capturing that human judgment is what the fields are for. If the conversation surfaces load-bearing detail beyond dispatch notes (a constraint, a design decision), route it into SPEC.md or a companion — `invoke_dev_with` carries dispatch notes only (Spec Law rule 7 still applies).
On acceptance, invoke `bmad-ticket` with the spec folder as input — `SPEC.md` plus its `companions:` are the source documents. Its slice route produces the epic set with `covers:` carrying this spec's `CAP-N` ids verbatim; its incept route produces one epic's stories on request. That trace spine is why capability IDs must stay stable (Spec Law rule 6): ticket-side coverage validates against them.

The output is `stories.yaml`, a sibling of `SPEC.md` inside the spec folder, discovered by that fixed name — same convention as `SPEC.md` and `.memlog.md`. Never list it in `companions:` and never point a frontmatter key at it: companions carry the what-to-build contract every consumer reads; `stories.yaml` is input for whichever tool dispatches the stories.

Field definitions, the validity rules, and a worked example live in `assets/stories-schema.md`. Before writing or re-writing the file, check every entry against those rules; fix violations rather than presenting a file that fails them. Record the check's verdict to `.memlog.md` (`append --type event`), the same discipline as Self-Validate.

Derive `stories.yaml` from `.memlog.md` exactly like any other spec-authored artifact: log each proposed story (`--type decision`) as the user agrees to it, then render. On a later run against the same spec folder, re-derive the same way, handling ids per the schema's update semantics.
When a spec update changes, adds, or retires capabilities and a ticket tree already covers this spec, say so — the tree may have drifted from the contract — and suggest re-running the ticket-side coverage check.

## Output

**Interactive** — share the spec folder path conversationally. Name the capability count, the companions produced, and the verdict in one or two sentences. Name the story count too if `stories.yaml` was written this run. If `assumptions[]` or `open_questions[]` are non-empty, list them (short — one line each) and invite the user to walk through them. Make clear that addressing them can update the source input (if it was a file), the spec, or both — whichever combination the user prefers. Do not dump JSON or present a wall of output.
**Interactive** — share the spec folder path conversationally. Name the capability count, the companions produced, and the verdict in one or two sentences. If `bmad-ticket` was invoked this run, name what it produced (the epic set, or an epic's stories) and where the tree lives. If `assumptions[]` or `open_questions[]` are non-empty, list them (short — one line each) and invite the user to walk through them. Make clear that addressing them can update the source input (if it was a file), the spec, or both — whichever combination the user prefers. Do not dump JSON or present a wall of output.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find lingering references to the removed artifact and the old breakdown workflow.
rg -n --hidden -g '!.git' 'stories\.yaml|stories-schema|Story Breakdown|story-breakdown'

# Confirm the referenced skill exists and is registered.
fd -t d 'bmad-ticket'
rg -n 'bmad-ticket' -g '*.yaml' -g '*.yml' -g '*.json' -g '*.toml' -g '*.md' | head -50

Repository: bmad-code-org/BMAD-METHOD

Length of output: 163


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repository files matching stories.yaml or bmad-ticket related names =="
git ls-files | awk '
tolower($0) ~ /stories\.yaml|story-breakdown|story_breakdown|bmad-ticket/bmad_ticket|bmad_ticket/ { print }
'

echo
echo "== lingering case-sensitive references =="
rg -n --hidden -g '!.git' 'stories\.yaml|stories-schema|Story Breakdown|story-breakdown' . || true

echo
echo "== broader case-insensitive story artifact references (excluding likely generated/build) =="
rg -n --hidden -g '!.git' -i 'stories\.yaml|story.*breakdown|breakdown.*story|bmad-ticket' . | head -200 || true

echo
echo "== docs references to bmad-spec spec contract artifacts =="
rg -n --hidden -g '!.git' -i 'stories\.yaml|story.*breakdown|breakdown.*story|EPIC|epics|CAP-N|companions|SPEC\.md' docs src documentation README.md . | head -300 || true

Repository: bmad-code-org/BMAD-METHOD

Length of output: 481


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repository files matching stories.yaml or bmad-ticket related names =="
git ls-files | grep -iE 'stories\.yaml|story-breakdown|story_breakdown|bmad-ticket|bmad_ticket' || true

echo
echo "== lingering case-sensitive references =="
rg -n --hidden -g '!.git' 'stories\.yaml|stories-schema|Story Breakdown|story-breakdown' . || true

echo
echo "== broader case-insensitive story artifact references excluding likely generated/build =="
rg -n --hidden -g '!.git' -i 'stories\.yaml|story.*breakdown|breakdown.*story|bmad-ticket' . | head -200 || true

echo
echo "== docs references to bmad-spec spec contract elements =="
rg -n --hidden -g '!.git' -i 'stories\.yaml|story.*breakdown|breakdown.*story|epic|epics|CAP-N|companions|SPEC\.md' . | head -300 || true

Repository: bmad-code-org/BMAD-METHOD

Length of output: 50381


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== bmad-spec SKILL section =="
sed -n '1,180p' src/bmm-skills/plan/bmad-spec/SKILL.md

echo
echo "== related skill SKILL files around story breakdown =="
for f in \
  src/bmm-skills/plan/bmad-create-epics-and-stories/SKILL.md \
  src/bmm-skills/plan/bmad-ticket/SKILL.md \
  src/bmm-skills/ship/bmad-build-auto/SKILL.md
do
  echo "--- $f"
  rg -n 'stories\.yaml|Story Breakdown|story-breakdown|bmad-ticket|SPEC\.md|companions' "$f" || true
done

echo
echo "== docs pages directly related to bmad-spec/bmad-create-epics-and-stories =="
rg -n 'bmad-(spec|create-epics-and-stories)|Story Breakdown|stories\.yaml|optional.*stories' \
  docs/reference/workflow-map.md docs/reference/commands.md \
  docs/reference/agents.md docs/tutorials/getting-started.md docs/tutorials/getting-deeper.md \
  src/bmm-skills -g '*.md' | head -200 || true

Repository: bmad-code-org/BMAD-METHOD

Length of output: 26409


Add a deprecation path before removing stories.yaml support.

bmad-build-auto still reads <spec-folder>/stories.yaml directly, and docs/reference/workflow-map.md, docs/reference/build-auto.md, and docs/tutorials/getting-deeper.md still treat stories.yaml as a BMad artifact. Update those sources or add a migration before bmad-spec stops authoring/decomposing through this artifact.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bmm-skills/plan/bmad-spec/SKILL.md` around lines 130 - 140, Before
removing stories.yaml authoring or decomposition from bmad-spec, add a
migration/deprecation path that preserves bmad-build-auto’s direct stories.yaml
consumption. Update bmad-build-auto and the referenced workflow-map, build-auto,
and getting-deeper documentation to define the transition and supported source
of truth, then ensure the bmad-spec “Epics and Stories” flow no longer creates
an unsupported artifact.

Source: Path instructions

Comment on lines +8 to +9
* [Notification service](notifications/ticket.md) - Real-time alerts users configure themselves
* [Snooze button polish](NOTI-3-snooze-button.md)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the index generator to confirm the exact line format for epics and leaves.
fd -t f 'ticket_tree.py' | while IFS= read -r f; do
  echo "=== $f ==="
  ast-grep outline "$f" --items all
done

# Show the index command implementation and any index-related tests.
rg -n -C 10 'def cmd_index|def build_index|generated: true' --glob '*.py'
rg -n -C 5 'Snooze button' --glob '*'

Repository: bmad-code-org/BMAD-METHOD

Length of output: 4295


🏁 Script executed:

#!/bin/bash
set -u

echo "=== src/bmm-skills/plan/bmad-ticket/scripts/ticket_tree.py: cmd_index and scan ==="
sed -n '93,229p' src/bmm-skills/plan/bmad-ticket/scripts/ticket_tree.py

echo
echo "=== src/bmm-skills/plan/bmad-ticket/evals/fixtures/tree/index.md ==="
cat -n src/bmm-skills/plan/bmad-ticket/evals/fixtures/tree/index.md

echo
echo "=== relevant ticket frontmatter ==="
cat -n src/bmm-skills/plan/bmad-ticket/evals/fixtures/tree/notifications/ticket.md
cat -n src/bmm-skills/plan/bmad-ticket/evals/fixtures/tree/NOTI-3-snooze-button.md

echo
echo "=== tests around generated index ==="
sed -n '1,220p' src/bmm-skills/plan/bmad-ticket/scripts/tests/test_ticket_tree.py

Repository: bmad-code-org/BMAD-METHOD

Length of output: 14360


Update the leaf link text or its frontmatter title.

ticket_tree.py index emits leaf entries from the ticket title, so cmd_index regenerates * [Snooze button visual polish](NOTI-3-snooze-button.md). The fixture uses bare leaf link text Snooze button polish and will fail comparison after regeneration. Rename the task title to that text, or update the fixture to the generated title.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bmm-skills/plan/bmad-ticket/evals/fixtures/tree/index.md` around lines 8
- 9, Update the leaf entry for NOTI-3-snooze-button.md to match the title
emitted by ticket_tree.py’s cmd_index, using either the ticket frontmatter title
or the fixture link text; ensure both resolve to “Snooze button visual polish”
so regeneration produces the expected index.

Comment on lines +136 to +140
def test_hitl_raised_when_risk_crosses_threshold(self):
low = self.root / "ALRT-50-low.md"
low.write_text(STORY.replace("id: ALRT-12", "id: ALRT-50")
.replace("risk: 3", "risk: 2")
.replace("hitl: true", "hitl: false"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add encoding="utf-8" to the in-test write_text calls.

Line 138 omits the encoding, and so does line 154 in test_cycle_rejected. setUp passes encoding="utf-8" at lines 69, 71, and 73. The STORY fixture contains an em dash at line 31. Without an explicit encoding, write_text uses the platform default. On a runner whose default encoding is not UTF-8, both tests raise UnicodeEncodeError.

The subsequent read_text() calls at lines 143, 150, 167, and 179 have the same gap.

💚 Proposed fix
         low = self.root / "ALRT-50-low.md"
         low.write_text(STORY.replace("id: ALRT-12", "id: ALRT-50")
                             .replace("risk: 3", "risk: 2")
-                            .replace("hitl: true", "hitl: false"))
+                            .replace("hitl: true", "hitl: false"),
+                       encoding="utf-8")
         (self.root / "alert-rules" / "ALRT-13-rule-eval.md").write_text(
             STORY.replace("id: ALRT-12", "id: ALRT-13")
-                 .replace("depends_on: []", "depends_on: [ALRT-12]"))
+                 .replace("depends_on: []", "depends_on: [ALRT-12]"),
+            encoding="utf-8")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bmm-skills/plan/bmad-ticket/scripts/tests/test_update_ticket.py` around
lines 136 - 140, Update the test file’s write_text calls in
test_hitl_raised_when_risk_crosses_threshold and test_cycle_rejected to pass
encoding="utf-8", and update their subsequent read_text calls to use the same
explicit encoding. Preserve the existing test data and assertions.

Comment on lines +73 to +86
if rest == "":
items = []
j = i + 1
while j < len(lines) and re.match(r"^\s+-\s+", lines[j]):
items.append(parse_scalar(re.sub(r"^\s+-\s+", "", lines[j])))
j += 1
if j > i + 1:
fm[key] = items
i = j
continue
fm[key] = ""
elif rest.startswith("["):
inner = rest[1:-1].strip() if rest.endswith("]") else rest[1:].strip()
fm[key] = [parse_scalar(p) for p in inner.split(",")] if inner else []

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Accept zero-indent YAML block sequences in frontmatter.

Line 76 only matches list items that begin with whitespace (^\s+-\s+). Zero-indent block sequences are valid YAML under a mapping key and are the default output of most YAML writers:

depends_on:
- NOTI-1
- NOTI-2

With the current parser, that input sets fm["depends_on"] = "" at line 83. cmd_validate then reports depends_on must be a list, and cmd_graph and cmd_frontier see no edges. A hand-edited ticket that uses the common form fails for a reason the error text does not explain.

🐛 Proposed fix
             if rest == "":
                 items = []
                 j = i + 1
-                while j < len(lines) and re.match(r"^\s+-\s+", lines[j]):
-                    items.append(parse_scalar(re.sub(r"^\s+-\s+", "", lines[j])))
+                while j < len(lines) and re.match(r"^\s*-\s+", lines[j]):
+                    items.append(parse_scalar(re.sub(r"^\s*-\s+", "", lines[j])))
                     j += 1
                 if j > i + 1:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bmm-skills/plan/bmad-ticket/scripts/ticket_tree.py` around lines 73 - 86,
Update the frontmatter sequence parsing in the key-handling logic around
parse_scalar so block list items may begin at zero indentation as well as with
whitespace. Ensure the loop recognizes lines matching “- ” at column zero,
collects them into fm[key], advances i past the sequence, and preserves existing
handling for indented sequences, inline arrays, and scalar values.

Comment on lines +104 to +110
if not fm or "id" not in fm or "type" not in fm:
continue
fm["_path"] = p
fm["_rel"] = p.relative_to(root).as_posix()
tickets.append(fm)
by_id[str(fm["id"])] = fm
return tickets, by_id

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Detect duplicate ticket ids.

Line 109 overwrites by_id when two files carry the same id. Nothing in cmd_validate reports the collision. The consequences are silent:

  • dep_done and the depends_on existence check at line 398 resolve to whichever file was scanned last.
  • cmd_graph line 276 keys deps_of by id, so the two files collapse into one node and the first file's edges disappear.
  • cmd_index still lists both files, so the index and the graph disagree.

Duplicate ids are reachable: a copied ticket file, a merge of two branches, or two agents that call next-id concurrently all produce them.

🐛 Proposed fix — record duplicates in `scan` and report them in `cmd_validate`
 def scan(root):
     """Return (tickets, by_id). Each ticket: frontmatter + _path (Path) + _rel (str)."""
     tickets = []
     by_id = {}
@@
         fm["_path"] = p
         fm["_rel"] = p.relative_to(root).as_posix()
         tickets.append(fm)
         by_id[str(fm["id"])] = fm
     return tickets, by_id

Add the check inside cmd_validate, after the per-ticket loop:

    seen_ids = {}
    for t in tickets:
        seen_ids.setdefault(str(t.get("id")), []).append(t["_rel"])
    for tid, rels in sorted(seen_ids.items()):
        if len(rels) > 1:
            errors.append({"file": "(tree)",
                           "error": f"duplicate id '{tid}' in: " + ", ".join(sorted(rels))})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bmm-skills/plan/bmad-ticket/scripts/ticket_tree.py` around lines 104 -
110, Update cmd_validate to detect duplicate ticket IDs after its per-ticket
validation loop by grouping tickets from scan by stringified id and collecting
each ticket’s _rel path. Add a validation error for every ID with multiple
paths, listing the paths in sorted order and using "(tree)" as the error file.
Leave scan’s by_id behavior unchanged.

Comment on lines +1 to +13
---
name: bmad-ticket
description: Turns any input into epics, stories, bugs, tasks, and spikes. Use when the user says "make a ticket", "create a story", "break this into stories", "turn this PRD into epics", or "inception for [epic]".
---

# BMad Ticket

## Overview

Act as the user's slicing partner: they hold the product knowledge; you hold the craft of shaping work into tickets an agent can build from. Take almost any input — a sentence, a bug report, a PRD, a spec, a brief, the current conversation — and produce well-formed tickets in the ticket tree at `{workflow.tickets_output_path}`. The consumer sets the bar: a fresh context must be able to build from a ticket using only what it carries and points at — behavior, acceptance criteria with verification, dependencies, trace ids, typed-document pointers.

Three routes, cheap exit first. **Refine** writes one ticket with minimal ceremony. **Slice** decomposes open scope into the detailed epic set. **Incept** turns one epic into its stories. Never march a one-ticket request through inception altitude.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find docs referencing the retired pipeline or listing planning skills, to check if they need updating for bmad-ticket.
set -euo pipefail

fd -e md . docs 2>/dev/null | while IFS= read -r f; do
  if rg -qi 'stories\.yaml|sprint-status|epics and stories|bmad-spec|bmad-ticket' "$f"; then
    echo "=== $f ==="
    rg -n -C2 -i 'stories\.yaml|sprint-status|epics and stories|bmad-spec|bmad-ticket' "$f"
  fi
done

Repository: bmad-code-org/BMAD-METHOD

Length of output: 23533


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- candidate files ---\n'
git ls-files | awk '$0 ~ /^docs\/.*\.md$/ || $0 ~ /^src\/bmm-skills\/plan\/bmad-ticket\/SKILL\.md$/ || $0 ~ /^src\/bmm-skills\/plan/ || $0 ~ /bmad-spec|bmad-sprint-planning|sprint-status|stories.yaml|bmad-create-epics-and-stories|bmad-ticket/' | sed -n '1,240p'

printf '\n--- docs count and key references ---\n'
python3 - <<'PY'
import pathlib, re
terms = ['stories\.yaml', 'sprint-status', 'bmad-create-epics-and-stories', 'bmad-sprint-planning', 'epics and stories', 'bmad-ticket']
docs = sorted(pathlib.Path('docs').glob('**/*.md')) if pathlib.Path('docs').exists() else []
refs = {term: [] for term in terms}
for p in docs:
    text = p.read_text(errors='ignore')
    for term in terms:
        if re.search(term, text, re.I):
            refs[term].append(str(p))
for term, files in refs.items():
    print(f"{term}: {len(files)}")
    for f in files[:20]:
        print(" ", f)
PY

printf '\n--- docs/reference/commands.md around skill list ---\n'
sed -n '70,115p' docs/reference/commands.md 2>/dev/null || true

printf '\n--- docs/reference/workflow-map.md around phase 2/3/skills ---\n'
sed -n '2,90p' docs/reference/workflow-map.md 2>/dev/null || true

printf '\n--- docs/reference/build-auto.md around stories.yaml usage ---\n'
sed -n '50,165p' docs/reference/build-auto.md 2>/dev/null || true

printf '\n--- docs/tutorials/getting-deeper.md around stories.yaml usage ---\n'
sed -n '135,225p' docs/tutorials/getting-deeper.md 2>/dev/null || true

printf '\n--- docs/explanation/sprint-planning.md top ---\n'
sed -n '1,120p' docs/explanation/sprint-planning.md 2>/dev/null || true

Repository: bmad-code-org/BMAD-METHOD

Length of output: 254


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- candidate files ---'
(git ls-files | awk '$0 ~ /^docs\/.*\.md$/ || $0 ~ /^src\/bmm-skills\/plan\/bmad-ticket\/SKILL\.md$/ || $0 ~ /^src\/bmm-skills\/plan/ || $0 ~ /bmad-spec|bmad-sprint-planning|sprint-status|stories.yaml|bmad-create-epics-and-stories|bmad-ticket/' | sed -n '1,240p') || true

printf '%s\n' ''
printf '%s\n' '--- docs count and key references ---'
python3 - <<'PY'
import pathlib, re
terms = ['stories\\.yaml', 'sprint-status', 'bmad-create-epics-and-stories', 'bmad-sprint-planning', 'epics and stories', 'bmad-ticket']
docs = sorted(pathlib.Path('docs').glob('**/*.md')) if pathlib.Path('docs').exists() else []
refs = {term: [] for term in terms}
for p in docs:
    text = p.read_text(errors='ignore')
    for term in terms:
        if re.search(term, text, re.I):
            refs[term].append(str(p))
for term, files in refs.items():
    print(f"{term}: {len(files)}")
    for f in files[:20]:
        print(" ", f)
PY

printf '%s\n' ''
printf '%s\n' '--- docs/reference/commands.md around skill list ---'
sed -n '70,115p' docs/reference/commands.md 2>/dev/null || true

printf '%s\n' ''
printf '%s\n' '--- docs/reference/workflow-map.md around phase 2/3/skills ---'
sed -n '2,90p' docs/reference/workflow-map.md 2>/dev/null || true

printf '%s\n' ''
printf '%s\n' '--- docs/reference/build-auto.md around stories.yaml usage ---'
sed -n '50,165p' docs/reference/build-auto.md 2>/dev/null || true

printf '%s\n' ''
printf '%s\n' '--- docs/tutorials/getting-deeper.md around stories.yaml usage ---'
sed -n '135,225p' docs/tutorials/getting-deeper.md 2>/dev/null || true

printf '%s\n' ''
printf '%s\n' '--- docs/explanation/sprint-planning.md top ---'
sed -n '1,120p' docs/explanation/sprint-planning.md 2>/dev/null || true

Repository: bmad-code-org/BMAD-METHOD

Length of output: 40281


Update the docs for bmad-ticket and retirement of the v6 epics/stories path.

bmad-ticket is not documented, while docs/ still lists bmad-create-epics-and-stories as the broken-down work workflow and still describes stories.yaml as generated spec output in Chinese/French/English docs (reference/workflow-map.md, reference/commands.md, reference/build-auto.md, and the tutorial). Add bmad-ticket to the workflow/skill docs and replace the removed v6 path references with the new ticket-tree output.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bmm-skills/plan/bmad-ticket/SKILL.md` around lines 1 - 13, Update the
documentation references for the retired bmad-create-epics-and-stories workflow
and stories.yaml output to use bmad-ticket and its ticket-tree output instead.
Add bmad-ticket to the workflow and skill documentation, including the Chinese,
French, and English references in reference/workflow-map.md,
reference/commands.md, reference/build-auto.md, and the tutorial, while
preserving the existing language-specific structure and command guidance.

Source: Path instructions

Comment on lines +31 to +36
for name in SCRIPTS:
a, b = canonical / name, bundled / name
if not (a.is_file() and b.is_file()):
self.skipTest(f"{name} missing from one home")
self.assertEqual(a.read_bytes(), b.read_bytes(),
f"{name} has drifted between src/scripts and the bundled copy")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

The missing-file skip hides drift in both copies of the sync test. find_src_root already skips the installed layout at line 28. Once the src root resolves, both homes exist, so a missing script is real drift rather than a layout difference. Line 34 turns that drift into a passing skip, and self.skipTest aborts the method before the remaining scripts are compared.

  • src/scripts/tests/test_dual_home_sync.py#L31-L36: replace the missing-file skipTest with assertTrue checks on each path, and wrap the loop body in self.subTest(script=name).
  • src/bmm-skills/plan/bmad-ticket/scripts/tests/test_dual_home_sync.py#L31-L36: apply the identical change so the two test files stay byte-identical.
📍 Affects 2 files
  • src/scripts/tests/test_dual_home_sync.py#L31-L36 (this comment)
  • src/bmm-skills/plan/bmad-ticket/scripts/tests/test_dual_home_sync.py#L31-L36
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/scripts/tests/test_dual_home_sync.py` around lines 31 - 36, The
missing-file skip in the dual-home sync loop hides real drift and stops later
scripts from being checked. In both src/scripts/tests/test_dual_home_sync.py
lines 31-36 and
src/bmm-skills/plan/bmad-ticket/scripts/tests/test_dual_home_sync.py lines
31-36, wrap each iteration of the SCRIPTS loop in self.subTest(script=name),
replace self.skipTest with assertTrue checks for both paths, and retain the
byte-equality assertion so both files remain identical.

Comment thread src/scripts/tests/test_update_ticket.py Outdated
Comment on lines +1 to +60
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.9"
# dependencies = []
# ///
"""Tests for update_ticket.py — run: uv run python -m unittest discover -s scripts/tests"""

import json
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path

SCRIPT = Path(__file__).parent.parent / "update_ticket.py"

STORY = """---
schema: 1
id: ALRT-12
type: story
title: "Rule CRUD"
status: backlog
depends_on: []
covers: [CAP-4, FR-12]
discovered_from: ""
risk: 3
hitl: true
created: 2026-08-01
---

# ALRT-12 — Rule CRUD

## Context

Body must never change.
"""

BUG = STORY.replace("type: story", "type: bug").replace("id: ALRT-12", "id: ALRT-20") \
.replace("risk: 3", "severity: 3\nrisk: 3")

EPIC = """---
schema: 1
id: ALRT-3
type: epic
title: "Alert rules"
description: "Rules people can manage"
depends_on: []
covers: [CAP-4]
risk: 3
created: 2026-08-01
---

# ALRT-3 — Alert rules
"""


def run(*args):
proc = subprocess.run([sys.executable, str(SCRIPT), *args],
capture_output=True, text=True)
return proc.returncode, json.loads(proc.stdout)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Apply every test fix to both test homes. The two test files are byte-identical. Both declare requires-python = ">=3.9" while update_ticket.py requires 3.11 for tomllib, both discard stderr in run(), and both omit encoding="utf-8" in the in-test write_text calls.

  • src/scripts/tests/test_update_ticket.py#L1-L60: set requires-python to >=3.11 at line 3, and raise an AssertionError that includes stderr when json.loads(proc.stdout) fails at line 60. Also add encoding="utf-8" to the write_text calls at lines 138 and 154.
  • src/bmm-skills/plan/bmad-ticket/scripts/tests/test_update_ticket.py#L1-L60: apply the identical edits at the same line ranges.
🧰 Tools
🪛 ast-grep (0.45.0)

[error] 57-58: Command coming from incoming request
Context: subprocess.run([sys.executable, str(SCRIPT), *args],
capture_output=True, text=True)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)

🪛 Ruff (0.16.0)

[error] 58-58: subprocess call: check for execution of untrusted input

(S603)

📍 Affects 2 files
  • src/scripts/tests/test_update_ticket.py#L1-L60 (this comment)
  • src/bmm-skills/plan/bmad-ticket/scripts/tests/test_update_ticket.py#L1-L60
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/scripts/tests/test_update_ticket.py` around lines 1 - 60, Update both
src/scripts/tests/test_update_ticket.py lines 1-60 and
src/bmm-skills/plan/bmad-ticket/scripts/tests/test_update_ticket.py lines 1-60
identically: change the script requirement to Python >=3.11, make run() raise an
AssertionError containing proc.stderr when JSON parsing proc.stdout fails, and
add encoding="utf-8" to the write_text calls at lines 138 and 154 in each file.

Comment on lines +6 to +23
"""Deterministic read/derive operations over a bmad-ticket tree (schema 1).

Everything here is derived by scan — nothing is stored. Verbs:

next-id next free ticket id uv run ticket_tree.py next-id --root R [--key KEY]
index regenerate index.md uv run ticket_tree.py index --root R [--key KEY]
validate schema gate over the tree uv run ticket_tree.py validate --root R [--path FILE]
list id/type/title/status/path uv run ticket_tree.py list --root R
frontier workable leaves uv run ticket_tree.py frontier --root R
board status rollup (epics too) uv run ticket_tree.py board --root R
graph dependency graph + lanes uv run ticket_tree.py graph --root R [--mermaid]
coverage covers: vs an inventory uv run ticket_tree.py coverage --root R [--require "CAP-1,FR-2"] [--proposed "CAP-3"]

Output is one JSON object per call. Stdlib only. Dual-homed: canonical at
src/scripts/ (installed to {project-root}/_bmad/scripts/ for any skill or
agent to call); bmad-ticket bundles an identical copy and uses it locally.
Keep both in sync.
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Apply every ticket_tree.py fix to both homes. The two files are byte-identical, and test_dual_home_sync.py asserts that. Each finding raised on the bundled copy — zero-indent block sequences (L73-L86), duplicate ids (L104-L110), index link encoding (L203-L211), mermaid label escaping (L310-L321), coverage ok semantics (L334-L338), missing type validation (L365-L376), and the --path scope of the cycle check (L426-L454) — must land at the same lines in both copies, or the sync test fails.

  • src/scripts/ticket_tree.py#L6-L23: apply all seven fixes here, then confirm the file matches the bundled copy byte for byte.
  • src/bmm-skills/plan/bmad-ticket/scripts/ticket_tree.py#L6-L23: apply the same seven fixes here; this copy carries the individual per-site comments.
📍 Affects 2 files
  • src/scripts/ticket_tree.py#L6-L23 (this comment)
  • src/bmm-skills/plan/bmad-ticket/scripts/ticket_tree.py#L6-L23
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/scripts/ticket_tree.py` around lines 6 - 23, Apply all seven
ticket_tree.py fixes in src/scripts/ticket_tree.py (L73-L86, L104-L110,
L203-L211, L310-L321, L334-L338, L365-L376, and L426-L454), covering zero-indent
block sequences, duplicate IDs, index-link encoding, Mermaid label escaping,
coverage ok semantics, type validation, and --path-scoped cycle checks. Apply
the identical changes at the corresponding ranges in
src/bmm-skills/plan/bmad-ticket/scripts/ticket_tree.py, then verify both files
are byte-for-byte identical.

Comment thread src/scripts/update_ticket.py Outdated
Comment on lines +170 to +193
def collect_tree(root):
"""Map every ticket in the tree: id -> path, and id -> depends_on list."""
ids, deps = {}, {}
for p in sorted(root.rglob("*.md")):
try:
lines = p.read_text(encoding="utf-8").splitlines(keepends=True)
except (OSError, UnicodeDecodeError):
continue
if not lines or lines[0].strip() != "---":
continue
tid, tdeps = None, []
for line in lines[1:60]:
if line.strip() == "---":
break
m = re.match(r"^id:\s*(\S+)\s*$", line)
if m:
tid = m.group(1)
m = re.match(r"^depends_on:\s*\[(.*)\]\s*$", line)
if m:
tdeps = [x.strip() for x in m.group(1).split(",") if x.strip()]
if tid:
ids[tid] = p
deps[tid] = tdeps
return ids, deps

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Apply every update_ticket.py fix to both script homes. The two copies are byte-identical, and the module docstring at lines 18-20 requires them to stay in sync. Each defect raised on the bundled copy exists unchanged in the canonical copy, so every fix needs a matching edit in both files.

  • src/scripts/update_ticket.py#L170-L193: apply all four fixes here — reuse parse_frontmatter in collect_tree and reject duplicate ids (L170-193), quote unsafe bare YAML scalars in render (L136-148), type-check hitl_threshold from customize.toml (L244-246), and preserve the original file mode before os.replace (L356-368).
  • src/bmm-skills/plan/bmad-ticket/scripts/update_ticket.py#L170-L193: apply the identical four fixes at the same line ranges.

Confirm that the dual-home sync test compares the two files byte for byte, so a one-sided fix fails the build.

#!/bin/bash
# Confirm the two script homes are identical and that a sync test enforces it.
fd -t f 'update_ticket.py' -x sha256sum {}
fd -t f 'test_dual_home_sync.py' -x cat -n {}
📍 Affects 2 files
  • src/scripts/update_ticket.py#L170-L193 (this comment)
  • src/bmm-skills/plan/bmad-ticket/scripts/update_ticket.py#L170-L193
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/scripts/update_ticket.py` around lines 170 - 193, The two script homes
must receive identical updates: in src/scripts/update_ticket.py lines 170-193
and src/bmm-skills/plan/bmad-ticket/scripts/update_ticket.py lines 170-193,
apply all four requested fixes—make collect_tree reuse parse_frontmatter and
reject duplicate IDs, quote unsafe bare YAML scalars in render, validate
hitl_threshold loaded from customize.toml, and preserve the original file mode
before os.replace. Keep both files byte-for-byte identical so the dual-home sync
test passes.

…l-only

Review round (Greptile + two deep review passes, 34 findings):
- update_ticket: fix splice collision that could silently drop an inserted
  field; parse with the same frontmatter parser as ticket_tree (block-style
  depends_on now reaches the cycle gate, quoted ids resolve); refuse
  newlines in values; refuse duplicated ids; JSON-clean malformed-target
  errors; pass-through --transitions/--hitl-threshold documented as the
  override path
- ticket_tree: duplicate ids, unparseable files, orphan leaves, scalar
  list fields, and unclosed inline lists are now detected instead of
  silently swallowed; iterative graph depth (no recursion limit); mermaid
  ids/labels sanitized; coverage --proposed separated from real coverage;
  fs errors keep the JSON contract; validate --path skips tree-wide cycles
- Epic state (D-23 revised): computed state is only not-started or
  in-progress; done/dropped are stored intentionally (retrospective or
  user) and never calculated; only a stored done releases dependents
- Autonomous bootstrap key (D-34): request > project_keys map > derived
  3-5 uppercase letters, flagged unconfirmed in the completion report
- Docs: planning_artifacts namespace, slice route scores epic risk,
  progressive-write scoped post-gate, refine gets no lens pass, bmad-spec
  handoff names the route; task/bug/spike templates gain References +
  Dev Notes; spike risk unhardcoded; bug discovered_from placeholder fixed
- Tests: 18 new regressions incl. 1200-deep graph chains; test headers
  bumped to >=3.11 (tomllib)
…ion surface

- bmad-create-epics-and-stories moves to v6-shims as a pitch-then-comply
  forward to bmad-ticket: recommends JIT inception (epics now, stories
  when an epic's work begins; epic envelopes T-shirt size without
  stories), honors the v6 shape on explicit request
- ticket_tree.py render verb: deterministic single epics-and-stories
  markdown view over the tree (generated banner, tree stays the source
  of truth); dual-homed with tests
- references/v6-migration.md: compliance flow (slice -> chained incept ->
  render), true-monofile fallback only on explicit tree refusal,
  per-epic mid-project migration (in-flight work finishes in
  sprint-status.yaml, one story one system, cross-seam waits as prose)
- bmad-sprint-planning legacy boundary: existing-file flows supported,
  net-new generation redirects to the ticket tree
- module-help.csv: bmad-ticket (TK) replaces the create-epics-and-stories
  row; prd/ux/architecture next-step pointers updated
- bmad-ticket activation: open floor marked interactive-only; updates and
  status questions named route-free; Args block in Overview (inline
  invocation surface; --help early exit with no side effects)
…ishes

- ticket_tree.py archive verb: a done/dropped epic's leaves move to
  .archive/<date>-<slug>/ (dot folders are off every verb's board);
  --purge deletes instead when the record of truth lives elsewhere
  (e.g. stories synced to Jira); satisfied depends_on edges into the
  archived set are dropped; the envelope stays live carrying the
  epic-level covers
- Guards: stored done/dropped required; all leaves finished; live deps
  on non-done archived leaves refuse; sub-epics archive first
- next-id scans dot folders so archived ids are never reissued
- update_ticket returns an archive hint when done lands on an epic;
  the skill offers the archive — never automatic
- write_index extracted for reuse; regression tests in both homes
The theory (checked vs unchecked writing, stories as dated records),
the altitude ladder, the ticket tree rules, the three routes with the
just-in-time rationale, the human partner's role in quality workflows,
lifecycle + archive-on-done, the everyday-commands table, and the v6
landing pad. Written to the docs style guide (one deliberate deviation:
four mermaid diagrams instead of one, for explainability).
- Atomic writers preserve the target's file mode (mkstemp's 0600 no
  longer flips ticket permissions) and fsync before replace
- _rewrite_depends_on scoped to the frontmatter block
- Zero-indent YAML block sequences accepted by both parsers
- validate flags unknown ticket types; index link targets encoded
- hitl_threshold type-checked from customize.toml; update_ticket gains
  a top-level catch so unexpected errors keep the JSON contract
- Risky bare string values quoted on render; collect_tree skips dot
  folders so archived tickets are not updatable by --id
- Sync test asserts (not skips) on a missing copy; test run() surfaces
  stderr on non-JSON output; regression tests for each fix
@bmadcode

bmadcode commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author

Re CodeRabbit's suggestion that coverage should return ok: false when uncovered is non-empty: declining deliberately. Across every verb in ticket_tree.py, ok means "the command ran and the output is trustworthy" — an uncovered requirement is a finding, not a failure, and callers (the skill's routes and the eval rubrics) read the uncovered array for the verdict. Overloading ok would make a successful coverage report indistinguishable from a broken run.

All other actionable findings from this round are addressed in 12c411a: file-mode preservation + fsync in the atomic writers, frontmatter-scoped _rewrite_depends_on, zero-indent block-sequence parsing, unknown-type validation, hitl_threshold type-checking with a top-level JSON-contract catch, risky-scalar quoting, encoded index links, and the sync test asserting instead of skipping — each with a regression test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant