aboutsummaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/specs/2026-07-01-docs-lifecycle-spec.org106
1 files changed, 92 insertions, 14 deletions
diff --git a/docs/specs/2026-07-01-docs-lifecycle-spec.org b/docs/specs/2026-07-01-docs-lifecycle-spec.org
index 081abdd..80c3cd0 100644
--- a/docs/specs/2026-07-01-docs-lifecycle-spec.org
+++ b/docs/specs/2026-07-01-docs-lifecycle-spec.org
@@ -1,6 +1,7 @@
#+TITLE: Docs Lifecycle — Spec
#+AUTHOR: Craig Jennings & Claude
#+DATE: 2026-07-01
+#+TODO: TODO | DONE
#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED
* DRAFT Docs lifecycle
@@ -39,7 +40,7 @@ Two forces beyond triage cost:
** Goals
- A directory listing answers "which docs are specs, and what state is each in" without opening files.
-- Status transitions cost one keyword edit — no rename, no link surgery.
+- Status transitions cost one small in-file edit (keyword + history line + Metadata mirror) — no rename, no link surgery.
- Cross-doc spec links survive moves and renames.
- The shape is captured once as a general rule (=docs-lifecycle=) so future artifact collections (brainstorm piles, recording queues) can reuse it.
- Every existing project's =docs/design/= pile gets sorted exactly once, with human confirmation on each classification.
@@ -66,6 +67,7 @@ Two forces beyond triage cost:
Each spec's first element after the file header is a single top-level *status heading* carrying the org TODO keyword:
#+begin_example
+,#+TODO: TODO | DONE
,#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED
,* DOING <spec short name>
@@ -75,8 +77,15 @@ Each spec's first element after the file header is a single top-level *status he
- <dated one-line history entries, newest first>
#+end_example
-- *The keyword is authoritative.* The Metadata table's =Status= field mirrors it in lowercase for readers already in the table; on disagreement the heading wins.
-- *Vocabulary:* =DRAFT= (being written) → =READY= (review passed, buildable) → =DOING= (implementation in progress) → =IMPLEMENTED= / =SUPERSEDED= / =CANCELLED= (terminal). This supersedes the old =TODO | DONE SUPERSEDED CANCELLED= header line; the retrofit updates it.
+- *The keyword is authoritative.* The Metadata table's =Status= field mirrors it in lowercase for readers already in the table, and a status transition updates keyword + history line + mirror in the same edit; on disagreement the heading wins.
+- *Two keyword sequences, no collisions.* The lifecycle sequence *joins* — never replaces — the =TODO | DONE= sequence that the =* Decisions= and =* Review findings= task machinery depends on. The two sequences share no keyword (the old header's =SUPERSEDED CANCELLED= done-states migrate to the lifecycle sequence; a legacy =CANCELLED= decision heading still parses as a done-state there, so =[/]= cookies stay mechanically correct). The retrofit rewrites each legacy header to carry both lines.
+- *Vocabulary:* =DRAFT= (being written) → =READY= (review passed, buildable) → =DOING= (implementation in progress) → =IMPLEMENTED= / =SUPERSEDED= / =CANCELLED= (terminal).
+- *Transition ownership — every flip has a named owner:*
+ - =DRAFT= — spec-create stamps it at authoring time.
+ - =DRAFT= → =READY= — spec-review, on a passing gate (keyword + history line + mirror in the review pass).
+ - =READY= → =DOING= — spec-response, when it decomposes the phases into build tasks.
+ - =DOING= → =IMPLEMENTED= — the session that completes the final implementation phase. To make that a tracked obligation rather than a memory, spec-response's phase-to-task breakdown *always emits a final task*: "flip the spec to IMPLEMENTED + history line." Safety net: task-audit's reconcile phase flags any =DOING= spec whose implementation tasks are all closed. This is the mechanism whose absence produced the .emacs.d six-shipped-specs-with-no-record failure; "a human remembers" is explicitly not the design.
+ - =SUPERSEDED= / =CANCELLED= — whoever makes the call, with the reason in the history line.
- *Glanceability without opening files:* one grep gives the full board —
#+begin_src sh
@@ -91,6 +100,8 @@ Each spec's first element after the file header is a single top-level *status he
The status heading carries an =:ID:= UUID. Cross-doc references to a spec use =[[id:<uuid>]]= rather than =file:= paths, so the one move the retrofit performs (and any future move) can't orphan them. =file:= links remain fine for intra-doc anchors and for notes that never move. The KB's existing id-resolution recipe applies: =rg ':ID:[[:space:]]+<uuid>' docs/=.
+*Emacs resolution prerequisite (named, .emacs.d-side).* =org-id-locations= only indexes agenda files and files org has visited, so a fresh =:ID:= in =docs/specs/= won't resolve on click in a live Emacs until the id index learns about project docs — via =org-id-extra-files= covering the =docs/specs/= globs, or a periodic =org-id-update-id-locations= run. Without this, id links trade visibly-broken =file:= links for invisible-until-clicked broken ones. The Phase 4 note to .emacs.d carries this ask; until it lands, the =rg= recipe above is the manual fallback.
+
** The =docs-lifecycle= rule (the generalization)
A new =claude-rules/docs-lifecycle.md= captures the reusable shape, with spec-create as the first instance:
@@ -104,12 +115,23 @@ A new =claude-rules/docs-lifecycle.md= captures the reusable shape, with spec-cr
A synced helper, =.ai/scripts/spec-sort=, run once per project:
-1. *Classify* each =docs/design/*.org= (and stray =docs/*-spec.org=) by heuristic: a doc with a =Decisions= section plus =Implementation phases= or a =Metadata= table is a spec candidate; everything else is a note. The heuristic *proposes*; a human confirms every move (classification is a judgment call — Craig, 2026-06-28).
-2. *Move* confirmed specs to =docs/specs/=, prepend the status heading (keyword proposed from the doc's current Status field or review history; confirmed by the human), assign an =:ID:=, and update the =#+TODO:= header line.
-3. *Relink* inbound =file:= references (=todo.org=, =notes.org=, =.ai/=, other docs) to the new path — and to =id:= form where the target is a spec.
-4. *Stamp* =:LAST_SPEC_SORT: YYYY-MM-DD= in =notes.org= Workflow State.
+1. *Classify* each =docs/**/*.org= outside =docs/specs/= by one predicate: a doc carrying *both* a =Decisions= heading *and* an =Implementation phases= heading is a spec candidate; everything else is a note. (A =Metadata= table alone does not qualify — real counter-case: =docs/design/task-review.org= has a Metadata table and no spine, and is a note.) The heuristic *proposes*; a human confirms every move (classification is a judgment call — Craig, 2026-06-28).
+2. *Move* confirmed specs to =docs/specs/=, *renaming to carry the =-spec.org= suffix* when the file lacks it (spec-review's Phase 0 precondition requires it — a retrofitted spec must be reviewable in its new home). Prepend the status heading (keyword proposed from the doc's current Status field or review history; confirmed by the human), assign an =:ID:=, and rewrite the keyword header to the two-sequence form above.
+3. *Relink* under an explicit contract:
+ - *Rewritten roots (project-owned):* =todo.org=, =.ai/notes.org=, =docs/**=, =.ai/project-workflows/=, =.ai/project-scripts/=. The rewrite recomputes each link's relative path from the linking file's directory to the new location; links whose target is a spec are additionally offered as =[[id:...]]= conversion.
+ - *Reported, never rewritten:* =.ai/sessions/= archives (frozen history), git history, and synced template paths (=.ai/workflows/=, =.ai/scripts/=, =.ai/protocols.org=) — a downstream edit there is reverted by the next template sync, so the report names the canonical rulesets file that needs the edit instead.
+ - *Supported link shapes:* org =[[file:...]]= links, relative or project-root-anchored, with or without a description. Bare-path mentions in prose or scripts are *reported for manual handling*, never rewritten.
+ - *Safety:* dry-run report is the default; =--apply= writes. After apply, a residue grep for each old path across the rewritten roots must return zero or =spec-sort= exits non-zero naming the residue. Ambiguous cases (two candidates sharing a basename, an unparseable link) are listed and left untouched, and the run exits non-zero until each is resolved or explicitly waived.
+4. *Stamp* =:LAST_SPEC_SORT: YYYY-MM-DD= in =.ai/notes.org='s =* Workflow State= section — the same surface as =:LAST_AUDIT:= and =:LAST_INBOX_PROCESS:=, created idempotently (append the section if the file lacks it) exactly as task-audit already does.
+
+*The startup nudge — concrete contract.* Phase A's parallel batch gains one read-only probe:
+
+#+begin_src bash
+[ -d docs/design ] && ! grep -qs ':LAST_SPEC_SORT:' .ai/notes.org \
+ && echo "spec-sort: docs/design present, never sorted"
+#+end_src
-A startup nudge (one line in Phase C) fires in projects that have a =docs/design/= but no =:LAST_SPEC_SORT:= marker, so each project runs the sort exactly once without template sync having to perform it.
+Phase C surfaces one line when the probe printed ("this project's docs pile has never been spec-sorted — say 'run spec-sort' to sort it") and stays silent otherwise. Projects without a =docs/design/= never see it; a stamped marker permanently clears it.
* Alternatives Considered
@@ -158,10 +180,50 @@ All five were settled with Craig on 2026-06-28 (recorded in todo.org; migrated h
- Decision: ship a confirmed classify-move-relink helper (=spec-sort=) plus a startup nudge gated on =:LAST_SPEC_SORT:=; the helper proposes, a human confirms. Pilot on rulesets first.
- Consequences: easier — every project converges without manual archaeology; harder — the helper needs real relink logic and tests, and classification stays a judgment call.
+* Review findings [9/9]
+
+Two independent reviews (Codex, 2026-07-01 22:22; a fresh-context Claude agent, 2026-07-01 22:25) converged on =Not ready= with the same worst finding. All nine findings were dispositioned accept and fixed in the responder pass below; each carries its response.
+
+** DONE Org TODO vocabulary drops decision and finding task states :blocking:
+(Codex; the Claude reviewer found the same, adding that keywords must be unique across sequences so a naive two-line fix collides on =SUPERSEDED=/=CANCELLED=.) The spec's example header replaced the file-level keyword vocabulary, so =TODO=/=DONE= stopped being task states and the =[/]= cookies that gate readiness went vacuous — this file itself was the first casualty.
+Response: the scheme is now two collision-free sequences — =TODO | DONE= for decisions/findings, =DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED= for lifecycle (the old header's =SUPERSEDED CANCELLED= done-states migrate to the lifecycle sequence, and a legacy =CANCELLED= decision still parses as a done-state, so cookies stay correct). This file's own header now carries both lines; the Design section documents the two-sequence rule and the retrofit rewrites legacy headers to it. New acceptance criterion: cookies must compute by org, not hand counting.
+
+** DONE Relink behavior is too vague for a safe migration :blocking:
+(Codex; the Claude reviewer independently flagged the synced-.ai/ slice — a downstream rewrite there is reverted by the next template sync, e.g. =startup.org:154='s reference to a spec candidate.) The retrofit named no scan scope, link-shape list, rewrite rule, residue policy, or dry-run format — the implementer would have had to invent the migration's data-safety contract.
+Response: the retrofit section now carries the explicit contract: rewritten roots (=todo.org=, =.ai/notes.org=, =docs/**=, project-owned =.ai/= dirs), reported-never-rewritten surfaces (=.ai/sessions/=, git history, synced template paths — with the canonical rulesets file named in the report), supported link shapes (org =file:= links; bare paths report-only), relative-path recomputation, dry-run default with =--apply=, post-apply residue grep gating exit status, and refuse-loudly on ambiguity.
+
+** DONE Sort marker and startup nudge do not name the actual state surface :blocking:
+(Codex; the Claude reviewer rated the same gap minor — Codex's version was sharper: startup reads =.ai/notes.org=, not a root =notes.org=, and Workflow State may not exist.)
+Response: the marker is pinned to =.ai/notes.org='s =* Workflow State= (the =:LAST_AUDIT:= / =:LAST_INBOX_PROCESS:= surface), created idempotently as task-audit already does; the Design section now spells the Phase A probe command, its exact fire condition, and the Phase C one-liner.
+
+** DONE Phase order can strand legacy specs behind the new review precondition :blocking:
+(Codex; the Claude reviewer found the same at medium severity.) Hardening spec-review's path precondition in Phase 1 while piles stay unsorted until Phases 3-4 would make every legacy spec unreviewable in the gap.
+Response: Phase 1 now carries the compatibility rule — legacy =-spec.org= locations stay reviewable (with a "run spec-sort" nudge) until the project stamps =:LAST_SPEC_SORT:=; the precondition hardens only after. Acceptance criterion 5 updated to match.
+
+** DONE No owner for the DOING → IMPLEMENTED flip :blocking:
+(Claude reviewer.) spec-create owns =DRAFT= and spec-review owns =DRAFT= → =READY=, but implementation finishes outside the spec trio, and "a human edits it" is the exact mechanism whose failure produced this spec (.emacs.d's six shipped-but-unmarked specs).
+Response: the Design section now has a transition-ownership table naming an owner for every flip. =READY= → =DOING= belongs to spec-response; =DOING= → =IMPLEMENTED= is a tracked obligation — spec-response's phase-to-task breakdown always emits a final "flip the spec" task — with task-audit's reconcile pass as the safety net (flag any =DOING= spec whose implementation tasks are all closed). Phase 1 includes both workflow edits.
+
+** DONE Classification heuristic is precedence-ambiguous
+(Claude reviewer.) "Decisions plus phases or Metadata table" reads two ways, and =docs/design/task-review.org= (Metadata table, no spine) classifies differently under each.
+Response: one predicate now — spec candidate iff the doc carries *both* a =Decisions= heading *and* an =Implementation phases= heading; a Metadata table alone does not qualify. The task-review.org counter-case is cited in the retrofit step.
+
+** DONE spec-sort never renames moved files to the -spec.org suffix
+(Claude reviewer.) spec-review's Phase 0 hard-requires the suffix, so a retrofitted legacy spec without it would be unreviewable in its new home.
+Response: retrofit step 2 now renames moved files to carry =-spec.org= when they lack it; the relink pass covers the rename like any move. Acceptance criterion 3 checks the suffix on the re-homed root specs.
+
+** DONE Clicked id: links won't resolve in Craig's Emacs
+(Claude reviewer.) =org-id-locations= indexes only agenda and visited files, so fresh =:ID:=s in =docs/specs/= are invisible-until-clicked broken — the convention would trade visible link breakage for invisible breakage.
+Response: named as an explicit .emacs.d-side prerequisite in the Rename-safe-links section (=org-id-extra-files= over =docs/specs/= globs, or periodic =org-id-update-id-locations=), carried in the Phase 4 note to .emacs.d, with the =rg= recipe as the interim fallback.
+
+** DONE Acceptance criterion 2 contradicts the Metadata Status mirror
+(Claude reviewer.) "Exactly one keyword edit" was irreconcilable with the mandated mirror update.
+Response: a transition is now defined everywhere as three lines in one file — keyword, history line, mirror — still no rename and no link edits. Goals, Design, and criterion 2 all say the same thing.
+
* Implementation phases
** Phase 1 — Rule + template updates
-Write =claude-rules/docs-lifecycle.md=. Update spec-create (emit into =docs/specs/=, new =#+TODO:= vocabulary, status heading with =:ID:= in the template, transition mechanics), spec-review (precondition path =docs/specs/=; flipping =DRAFT= → =READY= on a passing review updates the heading + history line), and spec-response (status flips it owns). Tree stays working: new specs land in the new shape; old specs are untouched until Phase 3.
+Write =claude-rules/docs-lifecycle.md=. Update spec-create (emit into =docs/specs/=, the two-sequence keyword header, status heading with =:ID:= in the template, transition mechanics), spec-review (path expectation with the compatibility rule below; flipping =DRAFT= → =READY= on a passing review updates keyword + history + mirror), spec-response (owns =READY= → =DOING=, and its phase-to-task breakdown always emits the final "flip to IMPLEMENTED" task), and task-audit (one reconcile bullet: flag a =DOING= spec whose implementation tasks are all closed). *Compatibility rule:* spec-review keeps accepting legacy =-spec.org= locations (=docs/= root, =docs/design/=) until the project's =:LAST_SPEC_SORT:= is stamped, nudging "run spec-sort" when it meets one; only after the stamp does the =docs/specs/= precondition harden. No legacy spec is ever unreviewable during the transition. Tree stays working: new specs land in the new shape; old specs remain reviewable until their project sorts.
** Phase 2 — The =spec-sort= helper
Build =.ai/scripts/spec-sort= (classify → confirm → move + prepend status heading + assign =:ID:= → relink inbound references → stamp =:LAST_SPEC_SORT:=), with bats coverage for classification, moving, relinking, idempotence, and the confirm gate. Tree stays working: the script is callable but nothing invokes it yet.
@@ -170,14 +232,15 @@ Build =.ai/scripts/spec-sort= (classify → confirm → move + prepend status he
Run =spec-sort= against rulesets' own =docs/= (41 design files, 3 spec-spine candidates, 2 stray root specs). Fix what the pilot surfaces before any other project runs it. Tree stays working: moves are confirmed one by one, links updated in the same pass.
** Phase 4 — Startup nudge + broadcast
-Add the Phase C nudge (fires when =docs/design/= exists and =:LAST_SPEC_SORT:= is absent). Send .emacs.d a note that the convention is live and its ~28-doc pile is ready to sort. Tree stays working: the nudge is one read-only line per session until acted on.
+Add the Phase A probe + Phase C nudge line (the concrete contract in the Design retrofit section). Send .emacs.d a note that the convention is live, its ~28-doc pile is ready to sort, and the org-id resolution prerequisite needs a home (=org-id-extra-files= over project =docs/specs/= globs, or a periodic =org-id-update-id-locations=). Tree stays working: the nudge is one read-only line per session until acted on.
* Acceptance criteria
- [ ] =rg '^\* (DRAFT|READY|DOING|IMPLEMENTED|SUPERSEDED|CANCELLED) ' docs/specs/= lists every rulesets spec with its state, and the answer matches reality.
-- [ ] A status transition on a spec changes exactly one keyword plus one history line — no rename, no link edits.
-- [ ] Every doc remaining in rulesets =docs/design/= is a note (no Decisions + phases spine); both stray =docs/= root specs are re-homed.
-- [ ] All inbound =todo.org= / =notes.org= / doc links to moved specs resolve after the pilot.
-- [ ] spec-create emits new specs into =docs/specs/= in the new shape; spec-review refuses a spec outside it.
+- [ ] A status transition on a spec changes exactly three lines in one file — the keyword, a history line, and the Metadata mirror — with no rename and no link edits.
+- [ ] Every doc remaining in rulesets =docs/design/= is a note (lacks the Decisions + Implementation-phases spine); both stray =docs/= root specs are re-homed and carry the =-spec.org= suffix.
+- [ ] All inbound links in the rewritten roots resolve after the pilot, and the post-apply residue grep returns zero.
+- [ ] The spec's own decision/finding =[/]= cookies compute correctly under the two-sequence keyword header (org, not hand counting).
+- [ ] spec-create emits new specs into =docs/specs/= in the new shape; spec-review accepts legacy locations until =:LAST_SPEC_SORT:= is stamped and refuses them after.
- [ ] A project with an unsorted =docs/design/= gets the startup nudge; one confirmed =spec-sort= run clears it via =:LAST_SPEC_SORT:=.
* Readiness dimensions
@@ -212,3 +275,18 @@ bats for =spec-sort= (classification, move, relink, idempotence, confirm gate, m
- What: initial draft, written from the five pre-ratified decisions.
- Why: the queued-specs half of the 2026-06-30 session goal; decisions were settled 2026-06-28 and needed migration into a buildable spec.
- Artifacts: todo.org task "Spec storage location + lifecycle-status convention"; source proposal above.
+
+** 2026-07-01 Wed @ 22:22:34 -0400 — Codex — reviewer
+- What changed or was recommended: rubric =Not ready=. Four blocking findings were added: preserve Org task keywords while adding lifecycle status, make =spec-sort= relinking executable and failure-safe, define the actual =.ai/notes.org= marker/startup-nudge contract, and avoid stranding legacy specs behind a stricter path precondition before retrofit.
+- Why: current rulesets workflows still depend on =TODO= / =DONE= decision and finding tasks, startup state lives in =.ai/notes.org=, and the repo still contains formal specs outside =docs/specs/= until the migration runs.
+- Artifacts: Review findings section; current-state checks against =.ai/workflows/spec-create.org=, =.ai/workflows/spec-review.org=, =.ai/workflows/startup.org=, =scripts/sync-check.sh=, and =todo.org=.
+
+** 2026-07-01 Wed @ 22:25:00 -0400 — Claude (fresh-context agent) — reviewer
+- What: rubric =Not ready=. Independently found Codex's keyword-vocabulary blocker (adding the cross-sequence uniqueness wrinkle) and the stranded-legacy-specs and marker-surface gaps, plus five findings of its own: no owner for the =DOING= → =IMPLEMENTED= flip (blocking), the precedence-ambiguous classification heuristic, the missing =-spec.org= rename in spec-sort, org-id click-resolution in a live Emacs, and the criterion-2/mirror contradiction.
+- Why: fresh-eyes adversarial pass requested by Craig after his own read found nothing; the two reviews converging on the same worst bug from independent context is the confidence signal.
+- Artifacts: Review findings section (findings 5-9); spot-checks against real repo files (=docs/design/task-review.org=, the two stray root specs, =startup.org:154=).
+
+** 2026-07-01 Wed @ 22:30:06 -0400 — Claude — responder
+- What: merged both reviews into one findings ledger (nine findings, all dispositioned accept) and fixed all nine in place: two-sequence keyword header (applied to this file itself), transition-ownership table with the tracked flip-to-IMPLEMENTED task, single classification predicate, the -spec.org rename step, the full relink data-safety contract, the =.ai/notes.org= marker + Phase A/C startup contract, the legacy-location compatibility rule, the org-id Emacs prerequisite, and the three-line transition definition. Acceptance criteria updated to match.
+- Why: Craig approved fixing all nine ("1", 2026-07-01); none touched the five ratified decisions.
+- Artifacts: Review findings section (responses inline per finding); the fixed sections themselves.