Adopting without a rewrite

Day 3: Choose your adventure · Lesson 5 of 5 · 12 min

Adopt a spec framework on the next ticket, on the smallest subset that answers your failure, in files your team can read without the tool, and plan for a network that will not let you install it.

Watch first · 4 min

OpenSpec on a Real Project: What the Demos Don’t Show

Dmitrii Balakin, 2026-07-18, 3 min 46 s, from 7:46. Listen for what Balakin says review-before-code bought him and what the pile of Markdown files cost, and hold that list against this lesson's subset section. Note where his specs drifted from the code, because that is where your own adoption will stall. It is one engineer, one project and one of the three tracks, so treat it as a field report, not a recommendation.

Adopt on the next ticket, not on the backlog

The rewrite instinct waits until everything has a spec. But nobody reads a spec for code that already works, and the first evidence arrives a week later. Put the next ticket through the framework and leave the rest alone. Five tickets give you five real artifacts, an opinion you can defend, and something for the two-week signal from Lessons 1 and 2 to count.

Map what exists before you write anything new

The one exception is a map the machine writes, and only LID writes one.

OpenSpec's specs cover only the change in hand, so it needs no map.

LID's /arrow-maintenance:map-codebase reverse-engineers an existing codebase's design tree for you to reconcile. On the build machine (LID 831c1959, 2026-09-22) it wrote thirteen documents carrying 63 EARS requirement IDs and changed no source or test file; the skill's own instructions did not load in that run, so the tree is LID-shaped rather than LID-generated.

Fix its wrong inferences before you cite its IDs in code.

BMAD's specs cover only the change in hand, so it needs no map.

The subset you keep

Adoptions die in a framework's long tail. Write your subset down, because the default is all of it.

OpenSpec: the change folder, the three delta headers a first change needs (## ADDED Requirements, ## MODIFIED Requirements, ## REMOVED Requirements), and /opsx:propose, /opsx:apply and /opsx:archive. Its other commands and the inspection CLI can wait.

LID: EARS requirements with semantic IDs and their [x], [ ] and [D] status markers (implemented, gap, deferred), and a // @spec line comment on the topmost function that owns the behavior. Save the drift audit, /arrow-maintenance:arrow-maintenance, for before a review relies on the map's IDs.

BMAD: SPEC.md, stories.yaml and one file per story, the ready-for-dev status, /bmad-build, and bmad-code-review for repairs. The rest of the 29 skills, sprint planning included, wait for epic-sized work.

What survives a locked-down network

Your track's “Network blocked?” box in Exercise 1 shows what survives when the install source is blocked.

OpenSpec comes from the public npm registry and needs Node 20.19.0 or higher. Its tarball installs only if npm reaches a local mirror, because the /opsx commands call the openspec CLI before they write anything.

Turn OpenSpec's telemetry off:

openspec config set telemetry.enabled false

LID comes from a plugin marketplace on GitHub. It runs from the vendored plugins with --plugin-dir.

BMAD comes from the public npm registry and needs Node 20.12 or later and uv with Python 3.11 or later, which uv downloads itself when the network allows; bmad-build and bmad-build-auto halt without uv. Its files copy in, but behind a blocked network uv and its Python must already be installed.

The lasting answer is a mirror: pin a version, mirror it, upgrade on purpose.

The artifacts have to be readable without the tool

All three store specs as plain text, Markdown plus BMAD's stories.yaml, so a reviewer without the tool can read them in a pull request, and adoption stays reversible. Test one artifact from your Exercise 2 run: if a reviewer new to the framework could not approve or reject it, the tool's vocabulary is now part of every review.

Committing it: the team file and your file

Commit the framework's own files: openspec/ plus the commands and skills openspec init wrote under .claude/.

Commit the framework's own files: LID's docs/ tree. LID's plugins stay at user scope, one install per teammate.

Commit the framework's own files: _bmad/, its skills under .claude/skills/ and the spec folders under _bmad-output/implementation-artifacts/.

BMAD has its own personal file: _bmad/config.user.toml holds your install answers, and the installer writes your system username there and into two config.yaml files. Check all three before the first commit, and keep config.user.toml out, like the settings.local.json below.

Commit .claude/settings.json too, with the permission rules the framework's commands need. “Yes, and don't ask again” on a shell command saves its rule to the personal .claude/settings.local.json, which takes precedence, so a command that runs unprompted for you may be allowed nowhere else until its rule moves into the committed file.

Keeping CLAUDE.md from growing

A framework can write into CLAUDE.md, which loads into every session and is paid for on every turn. On the build machine (OpenSpec 1.13.1, BMAD 6.12.0, 2026-09-22) OpenSpec and BMAD left it untouched, and LID's /arrow-maintenance:map-codebase run appended a 29-line ## LID section.

A line @path/to/import inside CLAUDE.md pulls in another file, so a framework's directives can live in their own file, deletable in one edit. Imported files load at launch too, so that changes organization, not cost. It also fails LID: update-lid looks for its directives and ## LID block in AGENTS.md if it exists, otherwise in CLAUDE.md itself, and writes back what it misses, so on the LID track the block stays in whichever of the two your project uses.

From inside day3-work, compare the shipped file with yours:

Swift lane
wc -l ../sandbox/swift/CLAUDE.md CLAUDE.md
Kotlin lane
wc -l ../sandbox/kotlin/CLAUDE.md CLAUDE.md

If your CLAUDE.md line counts more than the shipped file's, the framework owes the difference under Day 2's net-zero line cap.

Only if your CLAUDE.md contains @ imports, add each imported file to the command, for example for a line @docs/framework-directives.md:

Swift lane
wc -l ../sandbox/swift/CLAUDE.md CLAUDE.md docs/framework-directives.md
Kotlin lane
wc -l ../sandbox/kotlin/CLAUDE.md CLAUDE.md docs/framework-directives.md

Then compare the shipped file's count with the sum of your files' counts. Skip wc's total line: it adds the shipped file in too.

When not to adopt

The biggest case: a module with no tests, like a payments module with no XCTest target or only a flaky Gradle UI test. Every framework here assumes a sensor that can tell the agent it is wrong; without one, it files specs nobody can check.

Adopt a test suite first. A team norm: no framework goes on a module until its tests can fail for the reason the spec cares about.

Skip the framework, too, for a one-off script or throwaway code, where nobody will read the spec again, and on a team that merges without review, where nobody reads it the first time.

Quick check

Your framework appends a dozen lines of directives to CLAUDE.md. What do you do with them, and why does it matter more than it looks?

Move them into their own file and pull it in with @path/to/import, so they are readable in one place and removable in one edit, unless the framework reads them back from CLAUDE.md, as LID's update-lid does. That alone saves nothing: CLAUDE.md and the files it imports load into every session, and you pay for them on every turn. Day 2's net-zero line cap applies across both files.

You are told to adopt a spec framework on a module that has no tests. What do you do first, and what is the reasoning?

Write the tests first. Every framework assumes something that can tell the agent it got it wrong; with no failing command available, the acceptance criteria cannot fail, and the framework produces documents instead of checks.

Sources

Framework documentation accessed 2026-09-22; Claude Code documentation accessed 2026-09-22 and 2026-09-23. Versions pinned for Day 3: OpenSpec 1.13.1, LID commit 831c1959, BMAD 6.12.0. Entries marked observed were recorded on the build machine on 2026-09-22 in scratch copies of the Swift lane.