Skip to content

← Back to list

BMAD: plan before writing code

If I just write code, I lose the thread by week three. If I over-plan, I lose it sooner. I had to learn to plan without drowning in planning.

3 min read

If I just write code, I lose the thread by week three. If I over-plan, I lose it sooner. I had to learn to plan without drowning in planning.

Diagram of the BMAD branch model: one feat branch per story, merged --no-ff to staging, with staging cutting versioned merges into main.

The context

This repo isn’t a one-evening project: 9 epics, around forty stories. I’ve carried it solo — no team, no company roadmap, no external deadline. That freedom is also the easiest trap for a personal project: the next “interesting” thing displaces the current one every Friday night.

The only thing I knew that fought that was a lightweight planning discipline. BMAD is the one I adopted — a method with short phases and versioned artefacts kept next to the code.

The decision

BMAD is just nine phases in strict order, each with a versioned artefact:

analyze → plan → architect → stories → validate
       → sprint → dev → review → qa → retro
  • analyze yields a brief (_bmad/briefs/*.md): what we want, why, what’s out.
  • plan yields the PRD (_bmad/planning/prd.md): concrete requirements, metrics, gates.
  • architect yields architecture.md: layering, protocols, threading model.
  • stories slices the architecture into small epics and stories (_bmad/epics/, _bmad/stories/). One story per PR.
  • validate is the PO sweep: no orphan stories, no requirement without a story.
  • sprint groups stories into a sprint.
  • dev, review, qa are the three implementation phases for a story.
  • retro closes the sprint with what’s done differently in the next one.

Each phase has a slash command (/bmad-analyze, /bmad-plan, …). The crucial part: artefacts live in the repo (_bmad/). Not in Notion, not in Linear. If the plan doesn’t live next to the code, it goes stale without anyone noticing.

Branches: one per story

Three branch kinds and that’s it:

BranchRole
mainStable releases. Never pushed to directly.
stagingIntegration. BMAD artefacts land here.
feature/story-XX-XX-...One per implementation story.

Merges are always --no-ff (GitHub: “Create a merge commit”). No squash, no rebase. The Git graph keeps the bubble per story — you can see when a story started, when it landed, what it reverted. Squash erases that; rebase too.

More graph noise? Yes. That’s the price of visual traceability. Two months later, when you’re searching for when transport.serial changed and why, the graph shows it; the squash doesn’t.

Why ruff.extend-exclude is anchored to specific stories. Every legacy path excluded in pyproject.toml carries a comment pointing to the story that retires it. When the story completes, the exclude goes with it. Without that coupling, legacy paths become permanent: nobody dares touch them because “maybe we still need them”. With the coupling, the debt has an expiry date the day it’s incurred.

The real cost is higher than I would have admitted before. Every feature carries a mini brief, two review passes (code + QA), and a retro at the sprint. If this were just code, the first days would go faster.

The reward: epic 09 (aligning the vocabulary to Lumware) was planned, executed, and merged in hours. The entire python2arduino → lumware rename, docs, imports, tests. Not because the work was small, but because the structure to do it already existed. Without BMAD it would have been a week of bugs.

What comes next

We’ve covered the whole system. What’s left is why it carries the name it does and why the color palette is R/G/B/W and not something else. Next post: the spectrum identity.