ADR-0015 — The change lifecycle: propose → review → archive#
Status#
Accepted — 2026-08-15
Context#
Until now the tool had no object for "a change in flight". A requirement was
either in docs/specs/traceability.md or it was not, and its history lived
only in git. Two consequences followed:
- There was no reviewable unit of intent. A reviewer opening a PR saw a
diff of
spec.md,use-cases.mdandtraceability.mdand had to reconstruct what behaviour was being proposed from the wreckage. The review happened over the code, not over the intent. - Brownfield was second class. Every command assumed a project was
generated by
initor expanded from a pack. Adding one requirement to an existing system meant hand-editing three files in lockstep.
The benchmark against OpenSpec (mejoras/openspec-benchmark-plan.md) made the
gap explicit: their entire product is this lifecycle, and it is the reason a
reviewer can look at a change and understand it in a minute.
Decision#
Introduce the change as a first-class object: a folder holding everything needed to understand and implement one proposed modification.
docs/specs/
├── capabilities/<cap>/spec.md ← source of truth: how it behaves today
├── changes/<change-id>/
│ ├── proposal.md ← why + scope
│ ├── design.md ← how (optional, full rigor only)
│ ├── tasks.md ← implementation checklist
│ ├── change.yaml ← schema, rigor, skip_specs, req_range
│ ├── specs/<cap>/spec.md ← DELTA (see ADR-0016)
│ └── features/<module>/<name>.feature ← proposed Gherkin
└── changes/archive/YYYY-MM-DD-<change-id>/ ← history, full context preservedSix commands: specgate change new | list | show | status | validate | archive.
Location: under docs/specs/, not a top-level openspec/-style directory.
The change lifecycle is part of the specification, and the specification
already lives in docs/specs/. A second root would split the spec surface in
two and force every existing command to learn about both.
Capabilities are optional and additive. docs/specs/capabilities/ is new;
spec.md, use-cases.md and the rest are untouched. A project generated
before this ADR has no changes/ directory, and every command above reports
"no active changes" rather than failing. That is a tested contract, not a
convention.
Rationale#
- The folder is the reviewable unit. Proposal, design, tasks and deltas in one place means a reviewer opens one directory instead of correlating a scattered diff. It also makes parallel work safe: two changes touching the same capability do not conflict as long as they touch different requirements.
- Archiving is an engineering act, not a filing act.
archivemerges the deltas into the capability specs and upserts the traceability matrix and materialises the proposed.featurefiles. The requirement lands under the existingvalidate --strict-tddgate. This is the whole point: where a documentation tool stops at markdown, the archive here arms a gate. - Dependencies are enablers, not gates.
change statusreports which artefact is possible to write next, never which one is mandatory. A change can skipdesign.mdentirely; lite rigor is the default. req_rangereserves ids at creation. Two changes worked in parallel would otherwise both mintREQ-014. The reservation scans the matrix, the capability specs, and every other change — including archived ones, because ids must never be recycled.
Alternatives considered#
- Keep everything in
traceability.mdand rely on git history. Rejected — the matrix records state, not intent. "REQ-014 exists and is Implemented" does not tell you why it was proposed, what was ruled out of scope, or what the alternative was. Git history holds the diff but not the argument. - A top-level
openspec/-shaped directory. Rejected — see above. Two spec roots is a worse problem than a longer path. - One file per change instead of a folder. Rejected — a single file cannot
hold per-capability deltas plus proposed
.featurefiles, and it makes the archive a rename rather than a merge. - Make capabilities mandatory, migrating
spec.mdinto them. Rejected for this phase — it would break every existing project. Capabilities are opt-in;archivecreates one on demand the first time a delta targets it.
Consequences#
Positive#
- Review happens over intent (
proposal.md+ spec deltas) before it happens over code. - Brownfield adoption no longer requires a pack or a generated project: any
repo with
docs/specs/can take a change. - The archive produces a queue item —
specgate planlists the new REQ immediately — which the harness can pick up without further glue.
Negative / trade-offs#
- Two places now describe requirements: the capability spec (behaviour) and
the traceability matrix (state). The risk of drift is real;
doctorgains a drift check in a later phase, andarchiveis the only writer of both. - Deeper paths (
docs/specs/changes/<id>/specs/<cap>/spec.md). Accepted as the cost of not splitting the spec root. - A change archived in
Draftdoes not immediately fail--strict-tdd; the gate fires when the requirement moves toIn Dev. Finishing a specification is not the same as starting to build it, and the status model already encodes that distinction.
Follow-ups#
- F1B:
specops diff --as-change— a pack version bump materialises a reviewable change instead of a file diff (see the benchmark plan §F1B). doctorchecks: orphaned deltas, archived changes with unchecked tasks, drift between the matrix and the capability specs.- A configurable artefact graph per schema (currently the built-in
spec-drivengraph is hard-coded inscripts/change/cli.ts).
References#
scripts/change/—common.ts,cli.ts,archive.ts- ADR-0016 — the delta spec format
- ADR-0011 — the
--strict-tddgate this lifecycle arms mejoras/openspec-benchmark-plan.md§F1