refactor(pipeline): specs/plans are gitignored working files, never committed

Per-cycle specs and plans stop being git-tracked commit-then-git-rm
artefacts and become git-ignored working files: created on disk for the
active cycle, read across sessions from the working tree, and shell-rm'd
at cycle close by audit. Nothing is committed, so there is no
git-history copy — the durable record of a cycle's intent is the design
ledger only, and no durable artefact (code, test, doc, ledger) may cite
a spec/plan by number or path.

- conventions/pipeline/design/README: invert the git-tracked -> git-rm
  -> git-history-recovery model to gitignored -> shell-rm -> ledger-only.
- naming: drop the NNNN file counter; files are slug.md (fieldtest specs
  keep a fieldtest- prefix as the cycle-close discriminator).
- cycle number: separated from the (now-gone) file number, sourced from
  the feat(NNNN)/audit(NNNN) commit subjects, which cycle commits carry.
- specify/planner/fieldtest/fieldtester: no commit step; fieldtest splits
  fixtures (committed as code) from its spec (gitignored), with the
  cross-cycle rule that a fieldtest- spec belongs to the next cycle.
- boss: auto-sign auditability moves from the (boss-signed) commit
  subject to the run's reference issue; rollback reverts only code.
- audit: new Step 5 shell-rm's the cycle spec+plan after the architect
  reads them, sparing fieldtest- specs.

Companion machine config (outside this repo): the global git excludes
(~/.config/git/ignore) gains docs/specs/ and docs/plans/ so the ignore
applies to every project at once.
This commit is contained in:
2026-07-02 11:35:06 +02:00
parent 1a1077b7f7
commit 08bc2aa027
10 changed files with 185 additions and 105 deletions
+49 -24
View File
@@ -27,6 +27,15 @@ These directories are fixed. A project that keeps specs and plans
elsewhere is the rare exception and states the override in its
`CLAUDE.md` project facts.
Both directories are **git-ignored** — their contents are transient
working files, never committed (see § Lifecycle). The plugin ignores
them **globally**: `docs/specs/` and `docs/plans/` live in the user's
global git excludes (`~/.config/git/ignore`, the XDG default that
applies to every repo), next to the other plugin-wide ignores. One
entry set covers every project — no per-project `.gitignore` line is
needed, though a project MAY add its own if it wants the ignore to
travel with the repo.
## Lifecycle
`docs/specs/` and `docs/plans/` hold **only the active cycle's
@@ -35,35 +44,51 @@ valid for the cycle that produces it; its code shapes drift the moment
the code moves, so a stale one left lying around reads as a live API
reference and misleads the next agent that opens it.
The two directories stay **git-tracked**never git-ignored. A
spec/plan is committed while its cycle is live, and at **cycle close**
after `audit` is drift-clean — the orchestrator **retires** it: `git rm`
the spec and plan in the cycle-close commit. The deletion rides in that
commit, so at `HEAD` the directories hold only what is still in flight,
while the full text stays recoverable from git history
(`git show <rev>:<path>`).
The two directories are **git-ignored**their contents are never
committed. A spec/plan is a working file: present on disk while its
cycle is live (readable across sessions, since the working tree
persists), and at **cycle close** — after `audit` is drift-clean — the
orchestrator **discards** it with a plain shell `rm`. There is nothing
to `git rm` (it was never tracked) and no deletion commit is produced;
the working tree is simply left without a stale artefact. This is the
same shell-delete-an-uncommitted-file mechanic `specify` already uses on
its grounding-`BLOCK` failure path.
A spec/plan is therefore **transient in the repo**: present for its own
cycle, gone from `HEAD` afterwards. The durable record of a past cycle
is the design ledger (where the rationale is lifted during audit) + the
git history of the spec, plan, and code — never a live file under
`docs/specs` / `docs/plans`.
A spec/plan is therefore **transient on disk**: present for its own
cycle, `rm`'d afterwards, and — never having been committed — leaving no
trace in git. It is **not recoverable, by design**. The durable record
of a past cycle's design intent is the **design ledger only** (where the
rationale is lifted during audit); the implementation is committed as
normal code.
**No durable artefact may cite a spec or plan** — not by number, not by
path. Code, tests, docs, and ledger entries outlive the spec/plan that
prompted them, so any reference to `docs/specs/…` or "spec NNNN" would
dangle the moment the cycle closes. Cite the **design-ledger contract**
instead (e.g. a `C`-id); it is the durable record the spec's rationale
was lifted into.
## Naming
Counter-prefixed, per directory: `NNNN-slug.md`, 4-digit
zero-padded. The counter is per directory, assigned in creation
order, and stable for the life of the file. New files take the
next-higher number; `brainstorm` / `specify` / `planner` scan the
target directory for the next free number before writing.
Files are named `slug.md` — a short kebab-case identifier of the
cycle's topic (slug separator `-`). There is **no numeric prefix**:
because the directories are git-ignored, shell-`rm`'d at cycle close,
and cited by nothing (see § Lifecycle), they hold at most the active
cycle's handful of files, so a slug is already unique enough — the old
scan-for-the-next-free-number ritual bought nothing and is gone. A
`fieldtest`-produced spec takes a `fieldtest-` prefix
(`fieldtest-slug.md`): that prefix is the discriminator the cycle-close
sweep uses to spare it, because it belongs to the next cycle (see
`../fieldtest/SKILL.md`).
The number is a within-cycle handle, not a stable archival ID.
Because the directories hold only active artefacts (see § Lifecycle),
an emptied directory restarts at `0001` and a number freed by a
retired file may be reused — that recycling is fine, since the durable
record is git history, not the filename.
The slug separator is `-`.
**The file name is not the cycle number.** The **cycle number** that
appears in commit subjects (`feat(NNNN)`, `audit(NNNN)`) and in `git
log` cycle ranges is a distinct, monotonic identifier that must survive
spec/plan discard. Source it from the highest `NNNN` in the
`feat(NNNN)` / `audit(NNNN)` git-log subjects, plus one — never from a
file name (there is none) and never from a per-directory scan. Cycle
commits therefore MUST carry the zero-padded `(NNNN)` cycle number in
their subject.
## Vocabulary