Code Map cannot say which files a story writes and which it must not touch

Author: johnkattenhornCreated Sep 17, 2026Updated Sep 17, 2026

The Code Map is asked to carry two different kinds of file and has one way to write them down. step-02-plan.md:23:

Put the investigation into ## Code Map: paths, symbols or lines, what to reuse, and what not to change.

(Emphasis mine.)

spec-template.md gives one shape for both:

- `FILE` -- ROLE_OR_RELEVANCE

So "this story writes this file" and "this file exists, leave it alone" come out looking identical, and the difference survives only in the English of the role text.

What it looks like in practice

A Code Map bmad-build wrote on 2026-09-17, planning a story that adds two new modules:

- `src/mdlinks/records.py` -- NEW. The whole deliverable. Six frozen
  dataclasses, `MdlinksError`, `sort_key`.
- `pyproject.toml:15` -- `packages = ["src/scratch"]`. **Blocker, read below.**
  Hatch exposes only `scratch`; a new `src/mdlinks` is not packaged.
- `src/scratch/__init__.py` -- existing package, unrelated. **Do not modify or
  remove.**
- `tests/test_smoke.py` -- imports `scratch`. **Do not modify.** It exists so
  `pytest` collects something; breaking it fails the gate for an unrelated reason.

Four bullets, one identical shape, and four different relationships: a file to create, a file to edit, a file to leave alone, and a file that must keep working. Two of them carry Do not modify in bold prose, which is the only signal there is.

Why it matters inside BMAD

step-03-implement.md:33 makes the spec the subagent's sole source of truth and tells the dispatcher not to restate ownership boundaries, because "that material already lives in it (investigation findings in its Code Map…)". The implementing subagent therefore has to read **Do not modify.** out of prose to know which files it may write. That works because the subagent is a language model. It is still the only place in the spec where a hard boundary is expressed in a form nothing can check.

It also means a spec cannot be linted for it. A review step can ask whether the acceptance criteria are Given/When/Then, and cannot ask whether the story touched a file its own Code Map told it not to.

Where I hit it

I am building a scheduler that runs BMAD stories as parallel lanes, and it takes each story's file list from its Code Map to decide which stories can run at once without writing over each other. Reading all four bullets as files this story will touch makes it claim two it is forbidden to modify, so other stories that legitimately need them wait for nothing.

Inferring the difference from **Do not modify.** is not something I want to ship. It is a bold phrase today and prose tomorrow, and a wrong reading is a lane writing where a spec told it not to.

The ask

Somewhere in the bullet that says which of the two a file is, in a form that does not depend on reading the annotation. I have no view on the spelling and it is yours to choose, but as an illustration of the size of it:

- `src/mdlinks/records.py` (write) -- NEW. The whole deliverable.
- `tests/test_smoke.py` (read) -- imports `scratch`. It exists so `pytest`
  collects something.

Defaulting an unmarked bullet to the current behaviour would keep every existing spec valid.

If the answer is that the Code Map is for orientation and never intended to be machine-read, that is a useful answer too and I will stop treating it as a contract.

Source: bmad-code-org/BMAD-METHOD