v2.3.0: what a more mature method taught the kit

Released 2026-09-09. A release that started as a reading of another project and ended as ten closed issues, most of them about the kit believing things it had not checked.

What we were trying to fix

The prompt was blunt: BMAD-METHOD is more mature than this kit, go and find out what it does better. Most of what it does better is not transposable, since it is a workflow runtime with its own installer and its own document model. Three mechanisms were. Its reviewers are read-only sub-agents that receive a diff as a file path and return text, never edits. Its spec review reads the spec only after the reviewer has traced the diff, so the spec cannot lead the reading. And it has a third review axis the kit lacked entirely: the verification gap, which asks not whether the code is right but whether anything would notice if it were wrong.

The rest of the release came from the open issues, read in the same sitting. Several of them were the same finding wearing different clothes: a guard, a verdict or a state file that answered confidently from partial evidence.

What we decided

implement-issue reviews on three axes from one staged diff file (#480). Standards, Spec and Verification each go to a read-only reviewer in its own worktree, findings come back as text, and the parent applies them. Standards never runs a fixer. Every prompt file reference in the kit must now resolve, checked by tests/skills/check-file-refs.py, because the port found a dead link in the shared recap on its first pass.

The guards refuse a write from a worktree that was destroyed mid-run (#469). make-worktree.sh records each worktree’s path in the repository config, and the three guarded git scripts compare it to where they actually stand before touching a branch. A worker whose worktree had been torn down under it used to be caught by luck, when the branch happened to differ; now it is caught by design.

merge-pr judges the base branch by sha and falls back to the workflow runs for that sha when the check-runs API fails (#479). Three unverified verdicts in a row are reported as a finding rather than absorbed. The Stop gate’s state file is keyed by host as well as owner and repository (#471), decision-tally.sh reads its non-terminal verdicts from the registry instead of a hard-coded list (#378), and the never-wait rule now recognises a merge worker that had to push and restart CI (#478). CI runs the parse sweep under a real bash 3.2 in a container (#144), so the static emulation’s blind spots stopped being load-bearing. The migration dashboard emits English, matching the reference docs that had already been translated (#432).

What got cut

Most of BMAD. The rendered skill snapshot, the layered customisation files, the doctor command and the docs site were all read and all declined: each one adds a surface the kit would have to keep true, and the kit’s doctrine is that length is paid on every run. The three mechanisms that did land are the ones that removed a failure rather than adding a feature.

Five open issues were also left where they were, on purpose: one depends on a tool the kit does not ship, two are large enough to deserve their own release, and two are reflections rather than defects.

What bit us

The release title gate, three times. A change to shipped content must carry a title that cuts a release, and docs: and ci: do not, so three PRs came back red for their titles alone. The rule was right each time. The lesson is to dry-run the gate before opening the PR, which implement-issue already says and a hand-opened PR skips.

pr_verdict.py in the portfolio tooling read a superseded, cancelled run as a red check on a green PR, twice, and cost two resyncs to obtain a clean sha. That one was fixed the same day, in its own repository: a sha carries a history per job, and only the latest run of each job is a verdict.

And the merge-base signature in base-run-verdict.sh was NUL-joined, which bash silently drops, so the comparison it fed had never compared anything. It is a JSON array now.