ADR-026: Feature specs drive the build
Status: Accepted (S1–S6 are built (core, agents, svc 3.0, app 1.0, template contract, launcher and dogfooding); specs 037–042, see STORY-037 to STORY-042)
Date: 2026-10-08
Builds on: ADR-024: Spec-driven bootstrap, ADR-011: Multi-repo plan format for /app:build-feature, ADR-015: Shape registry as code (shapes.yml)
Supersedes, when S3 ships: the --plan flag of ADR-024 and the single-repo half of STORY-036
Context
Section titled “Context”ADR-024 put a plan before the build, but the spec still does not drive the build:
- A spec has no home. Stories accumulate in one
docs/backlog.md; a feature’s behaviour is never described in one place that changes with the feature. - Acceptance criteria are free-text checkboxes. A test, a task or a review cannot cite them, and nothing proves that each one is covered.
- Tests are written after the code (
build-featurePhase 4), so coders have no executable definition of done. - Plan validation (
--plan) is prose the LLM follows; only gitops-app has a deterministic checker. - No approval gate exists (STORY-036), and a spec edited after planning goes unnoticed.
- Subagents cannot ask the user, so the product-manager guesses instead of asking.
Decision
Section titled “Decision”- One folder per feature:
docs/specs/<NNN>-<slug>/holdsspec.md(problem, stories, acceptance criteria, non-goals, open questions, changelog), optionaldesign.md(decisions, contract),plan.md(tasks) andverification.md(result of the verify step).docs/backlog.mdkeeps a table generated between<!-- spec-index:start/end -->markers; the text around it stays hand-written. - Criteria have ids:
- **AC-<NNN>.<n>** Given …, when …, then …. Ids are never reused or renumbered; a withdrawn criterion stays, struck through (~~**AC-007.3**~~ reason). The spec number replacesSTORY-NNNand continues its numbering. - Lifecycle:
draft → approved → building → done, andsuperseded. Onlycplat spec approvemovesdraft → approved, and it refuses while an open question is unanswered or no criterion exists. An amendment moves a spec back todraft. - Plans cite criteria: each task has
covers: [AC-…]; every active criterion is covered;spec_hashrecords the criteria the plan was made from, and a changed spec fails the check until it is re-planned. Tasks carrydoneso a build can resume. - Tests cite criteria: every active criterion appears in at least one test (name or comment). The test locations come from
test_globsinshapes.yml, so the trace is the same for every language. The build writes these tests first, from the spec, and they must fail before any code is written. - One format reference: the
spec-formatskill in the svc plugin (enabled in every repo) holds the formats; the product-manager, architect and reviewer read its files through${CLAUDE_PLUGIN_ROOT}, which Claude Code substitutes only inside the agent’s own plugin. Testers in other plugins carry just their language’s tagging idiom. The product-manager returns open questions instead of guessing; a read-only reviewer writesverification.md. - All of it is
cplat spec:new,check [--require STATUS],approve,set-status,task-done,hash,trace,index,list,migrate. Commands and agents call it; they never validate specs in prose. - Commands are renamed in a new major (
svc3.0,app1.0):/svc:spec,/svc:plan,/svc:build,/svc:verify,/svc:specsand the same verbs under/app:. Quick tasks and bug fixes stay spec-free but update a spec whose criteria they change.
Consequences
Section titled “Consequences”- Every criterion is traceable from the spec to a task, a test and the PR, and the checks are deterministic and tested.
- More files per feature, and a migration for existing repos:
cplat spec migrateconvertsSTORY-NNNstories, keeping their numbers and every non-story section of the backlog. It is opt-in and shows what it would do first. - The plan format is a superset of today’s single-repo plan (
covers,spec_hash,doneadded);templates/gitops-app/scripts/plan.pystays self-contained because it ships into generated repos. - The renamed commands are a breaking change;
docs/CHANGELOG.mdmaps old to new. There are no aliases (CLAUDE.md: no backwards-compat hacks).
Not included
Section titled “Not included”A required CI gate on traced criteria (it warns first; it becomes required in the next major), test results as evidence (the trace proves a test exists; the build proves it passes), and cross-repo criteria tracing beyond the per-repo spec slice.