@sabaiway/agent-workflow-engine 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,33 @@ All notable changes to the methodology engine. Versions are this **package's** n
4
4
  they are distinct from the **deployment-lineage** stamp written into a project's `docs/ai/`
5
5
  (which tracks the shared `agent-workflow` lineage, head `3.0.0`).
6
6
 
7
+ ## 3.3.0 — the feature-spec canon: `references/specs.md`, the `Spec-first` lens bullet, specs as plan rows (AD-112)
8
+
9
+ - **`references/specs.md`** (NEW) — the canon of the feature-spec layer in the family's own
10
+ vocabulary: where specs live and how the navigator counts them; the FROZEN schema (kinds,
11
+ statuses and their forward-only transitions, the caps and the numeric fan-out / promotion
12
+ thresholds, the slug pattern, the scenario-binding grammar, the module-root grammar, the
13
+ Out-of-scope rule, the advisory unbound warning); the shape per kind; the 33-row refusal table
14
+ the deployed reader enforces; lifecycle, per-scenario binding and atomic plan approval; the
15
+ complete feature-vs-page precedence table; spec-driven feature-sliced architecture with ONE
16
+ dependency rule and an honestly stated review-level enforcement altitude; spec vs ADR; the
17
+ retroactive onboarding path; the scale budget. `test/specs-canon.test.mjs` pins this file against
18
+ the reader's `SPEC_SCHEMA` — no second source of the numbers — reads the repo-only fixture corpus
19
+ (81 files: every accept case clean, every refuse case exactly its one rule) and reads the two
20
+ rendered templates clean.
21
+ - **`references/agent-rules-lens.md`** gains ONE bullet, `Spec-first`: a plan names its governing
22
+ spec(s) — zero, one or many, one per touched slice — each one's Out of scope bounds that slice
23
+ (no global union; a conflict is a spec revision BEFORE approval); a new feature's draft spec
24
+ exists AT plan review, a contract change rides the plan as a proposed revision, and the revision
25
+ lands with the code; page-only coverage governs as an adoption shim. The outgoing body is appended
26
+ to the append-only prior store, so every unmodified deployment converges on the next kit touch.
27
+ - **`references/planning.md`** — Goal and boundary names the governing spec(s); a contract change
28
+ is a ledger row present at review (`create` = draft spec, `modify` = revision; the landing row
29
+ moves `draft -> live`, a removal row `live -> retired`). **`references/procedures.md`** —
30
+ plan-authoring step 2 names the governing spec(s) and writes the `create` / `modify` spec rows so
31
+ they exist at review; plan-execution step 3 lands the approved draft or revision WITH the code.
32
+ - `provides` stays `["plan"]`; the payload pins (tarball, installer) name the new file.
33
+
7
34
  ## 3.2.0 — the state table comes BEFORE the guard: enumerate by proof, never by exclusion (AD-111)
8
35
 
9
36
  The canon already said a repeat finding in one subarea routes to SUBTRACTION rather than a fourth
package/README.md CHANGED
@@ -42,6 +42,12 @@ the canonical methodology reference on disk:
42
42
  - [`references/planning.md`](references/planning.md) — the **plan canon**: the capped plan shape
43
43
  (Goal and boundary · Module ledger · Verification), the subtraction rubric, the plan-file
44
44
  lifecycle, the `queue.md` series index, and the mandatory final **Phase: Cleanup**.
45
+ - [`references/specs.md`](references/specs.md) — the **feature-spec canon**: the durable
46
+ per-feature contract layer under `docs/ai/specs/` (Contract · Scenarios · Out of scope · Module),
47
+ the frozen schema (kinds, statuses, forward-only transitions, the numeric fan-out and promotion
48
+ thresholds, the scenario-binding grammar, the refusal table), governing specs cited per plan, the
49
+ feature-vs-page precedence table, spec-driven feature-sliced architecture, and the retroactive
50
+ onboarding path. The deployed reader `scripts/spec-schema.mjs` (memory) is pinned against it.
45
51
  - [`references/methodology-slot.md`](references/methodology-slot.md) — the **bounded**
46
52
  methodology fragment the composition root injects into a deployed `AGENTS.md` (a short summary +
47
53
  pointer, kept under the entry point's line cap).
package/SKILL.md CHANGED
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  name: agent-workflow-engine
3
- description: Canonical home of the agent-workflow planning methodology — the capped plan shape (goal and boundary, module ledger, verification), plan lifecycle, queue.md series index, mandatory Cleanup phase, the bounded methodology slot fragment, the orchestration-recipe vocabulary (Solo / Reviewed / Council / Delegated), and the activity-procedures canon (plan-authoring / plan-execution, with typed recipe slots). A published, installable npm package (available:true) that *provides* the methodology text; it mutates nothing. The composition root (agent-workflow-kit) reads this canon LIVE from the installed engine and injects the bounded slots from it — one source of truth, no bundled mirror; `npx @sabaiway/agent-workflow-kit@latest init` installs the engine.
3
+ description: Canonical home of the agent-workflow planning methodology — the capped plan shape (goal and boundary, module ledger, verification), plan lifecycle, queue.md series index, mandatory Cleanup phase, the feature-spec canon (the durable per-feature contract layer with its frozen schema and Out-of-scope discipline), the bounded methodology slot fragment, the orchestration-recipe vocabulary (Solo / Reviewed / Council / Delegated), and the activity-procedures canon (plan-authoring / plan-execution, with typed recipe slots). A published, installable npm package (available:true) that *provides* the methodology text; it mutates nothing. The composition root (agent-workflow-kit) reads this canon LIVE from the installed engine and injects the bounded slots from it — one source of truth, no bundled mirror; `npx @sabaiway/agent-workflow-kit@latest init` installs the engine.
4
4
  disable-model-invocation: true
5
5
  metadata:
6
- version: '3.2.0'
6
+ version: '3.3.0'
7
7
  ---
8
8
 
9
9
  # agent-workflow-engine
@@ -22,6 +22,13 @@ slot fill is needed but the engine is absent, the kit's reconcile **fails loudly
22
22
  (Goal and boundary · Module ledger · Verification — literal headings), the subtraction rubric for
23
23
  review, the plan-file lifecycle (`docs/plans/*.md`, gitignored, never committed), the `queue.md`
24
24
  series index and the mandatory final **Phase: Cleanup**.
25
+ - [`references/specs.md`](references/specs.md) — the **feature-spec canon**: the durable per-feature
26
+ contract layer under `docs/ai/specs/` (Contract · Scenarios · Out of scope · Module), the frozen
27
+ schema (kinds, statuses, forward-only transitions, the numeric fan-out and promotion thresholds,
28
+ the scenario-binding grammar, the refusal table the deployed reader enforces), governing specs
29
+ cited per plan, the feature-vs-page precedence table, spec-driven feature-sliced architecture,
30
+ and the retroactive onboarding path. The memory substrate's deployed reader
31
+ `scripts/spec-schema.mjs` is pinned against this canon by the engine's own test.
25
32
  - [`references/methodology-slot.md`](references/methodology-slot.md) — the **bounded** fragment the
26
33
  composition root injects into a deployed project's `AGENTS.md`, between the
27
34
  `<!-- workflow:methodology:start -->` / `<!-- workflow:methodology:end -->` markers. A short
package/capability.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "schema": 1,
4
4
  "name": "agent-workflow-engine",
5
5
  "kind": "methodology-engine",
6
- "version": "3.2.0",
6
+ "version": "3.3.0",
7
7
  "available": true,
8
8
  "provides": ["plan"],
9
9
  "roles": {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sabaiway/agent-workflow-engine",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "Canonical home of the agent-workflow planning methodology — the capped plan shape (goal and boundary, module ledger, verification), plan lifecycle, queue.md series index, and mandatory Cleanup phase, consumed by the kit (composition root). The methodology engine of the agent-workflow family.",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -176,3 +176,21 @@ Apply these when authoring a plan, reviewing, folding a finding, or editing code
176
176
  - **Recipe fidelity.** Council runs every backend the recipe names, **every round**; silently dropping a ready backend for quota/convenience is a forbidden downgrade — an unavailable backend is a LOUD, stated degrade, never a quiet drop.
177
177
  - **ExitPlanMode ≠ execute.** A harness "approved — start coding" prompt authorizes the PLAN only; this methodology overrides it. Continue into execution only as a DELIBERATE transition after the plan + cold-start prompt exist, never an implicit slide.
178
178
  - **Cost lanes.** Route every step to the **cheapest adequate executor** — L0 deterministic script (the batched gate matrix over `gates.json`, the rotation `--check`s) · L1 cheap subagent (extraction/drafting only; the orchestrator verifies) · L2 subscription bridge · L3 frontier judgment. A step with **no named guardrail does not move down** a lane, and the **red lines never move down** (council review models · real code · ADR/handover/changelog-entry wording · persuasive copy · go/no-go · the approval asks). Own-error repair: salvage recorded state first (L0/L1, batched), never frontier re-derivation. **Prompt economy:** read-only fan-out (research/sweeps/extraction) runs ONLY on restricted-tool vehicles — a full-tool subagent for read-only work is a forbidden lane downgrade (invisible prompt-flood + blast radius), and a subagent is never told to shell out for facts obtainable read-only; the orchestrator's own shell form is ONE plain pipeline per call (a `;`/`&&` chain or env-prefixed invocation never matches a prefix allow rule); a fan-out launcher that gates per call yields to the agent-spawn lane — capability-gated: without restricted-tool vehicles (generic full-tool spawning does not count), read-only research stays in the orchestrator's own context, never a vehicle mandate a host cannot satisfy. Judgment, code, synthesis stay at the frontier lane (a task that genuinely runs/writes keeps a full-tool subagent); honest limit: no deterministic gate classifies a dispatch — canon at the point of use + placed vehicles + the retro loop. **Writer economy:** a stage's repeated WRITER commands batch — evidence declarations ride consecutive plain invocations of ONE allow-listed tool, other stage writers combine via one launcher per stage; never an unbatched writer scatter (each gated write is its own prompt).
179
+
180
+ <!-- prior: 2026-08-23 (AD-112) — before the spec-first bullet (the spec layer) -->
181
+ ### 2.x. Planning, review & process-fidelity invariants
182
+ Apply these when authoring a plan, reviewing, folding a finding, or editing code — the layer read **before any code change**. (Full canon: the project's planning / workflow-methodology + orchestration canon. This section is rendered from that canon and refreshed on upgrade; a custom edit is preserved verbatim, but flagged.)
183
+ - **Fold by code, not prose.** Before folding a code-touching finding into a plan or change, read the cited `file:line` and cite it — a prose fold drifts from the code and seeds the next bug.
184
+ - **Finding scope (plan-execution) — name the invariant BEFORE the edit.** During EXECUTION only — a plan under authoring has no shipped behaviour to call a live defect in, so plan-review carries none of this. Every finding names the invariant its fix would enforce, and where that invariant already lives decides the disposition: already an acceptance criterion of the phase → **fold here**; it would have to be ADDED → ship the **narrow fix** for the found site (red first, then green) and queue ONLY the generalization — a deferral row carries the invariant, the origin `file:line`, the narrow fix, its proof and a residual exposure declared NOT live; no correct narrow fix → **blocking**: the phase does not close, and it is **never queued**. Two bars declared before each round: a finding counts only if it changes a **WRITE/REMOVE decision** or is a false statement in shipped text; a repeat finding in one subarea **routes to SUBTRACTION**, not a fourth patch.
185
+ - **Right altitude.** Pin intent + invariants + acceptance criteria (named tests); leave fine code-mechanics to Execute, where prose cannot diverge from reality.
186
+ - **No code-mechanics in the plan.** A ledger row carries its path and anchor, and Verification carries the exact commands (the plan-shape canon) — checked syntax: the plan's own Verification runs them against an explicit expected outcome or gate; the only other syntax a plan may carry is a literal fixture/schema fragment a named test copies or validates. Un-run, logic-bearing syntax — control-flow, a regex, a glob, a grammar, an algorithm body, a mini-DSL — never lives in plan prose, however plausible or shell-verified it looks: a fold or draft that wants one is the trigger to write the test instead.
187
+ - **Test-as-spec.** Fold a code-touching finding into a red→green TEST, not a prose paragraph — the gate is the only deterministic checker; a paragraph cannot self-check.
188
+ - **Characterize-first.** Before editing UNCOVERED code, pin its current behavior in a green test, then edit — any unintended change goes red. Never edit what has no checker; first give it one. Keep edits atomic/reversible; prefer SUBTRACTIVE folds.
189
+ - **State table BEFORE the guard — enumerate by PROOF, never by exclusion.** The subtraction rule above fires on a repeat finding, which is a LATE signal: by then the review has paid for each miss. The EARLY signal is structural — a decision whose input has **several independent state dimensions** (is it tracked? do the bytes still match the source? does the neighbouring file exist?). Write the table first, admit the write with **ONE conjunction of proven facts**, and funnel every other cell into a single refusal; the table is then the table-driven test. An exclusion list (`if (bad1) return; if (bad2) return;`) fails **OPEN** on the first state nobody enumerated — and "unreadable" is a state, distinct from "absent". A reviewer cannot save you here: it judges the patch in front of it and can only name the NEXT missing state, one round at a time.
190
+ - **Fold minimally — prose has no checker.** An ephemeral, gitignored plan is prose with no executable checker; fold **minimally, in ONE place** and run a **self-consistency** read across the plan before every re-review — a fold that drifts several prose spots is what turns a 2-round review into churn.
191
+ - **Heavy review at the diff.** Plan-review settles architecture only (≤2 rounds, stop at the pre-existing→fold-induced crossover); the exhaustive per-row review runs against real compiling code + the full suite, where a regression fails a gate immediately. **Backend divergence** (one backend grounded-ships while another keeps revising mechanics) IS that crossover — resolve at altitude, don't exhaust the strictest backend; route an all-mechanics/CI or prose-only artifact to a **thin plan + diff-review**.
192
+ - **Convergence bar.** A review loop is CLEAN only when one round returns **0 blockers + 0 majors** from EVERY backend the recipe names (nits + a ship verdict is the stop). Folding ≠ convergence — re-review after folding.
193
+ - **Per-round emission.** Every review round emits **{round N · finding-origin tally · per-backend verdict}** so the crossover is a computed, visible signal, not a remembered rule.
194
+ - **Recipe fidelity.** Council runs every backend the recipe names, **every round**; silently dropping a ready backend for quota/convenience is a forbidden downgrade — an unavailable backend is a LOUD, stated degrade, never a quiet drop.
195
+ - **ExitPlanMode ≠ execute.** A harness "approved — start coding" prompt authorizes the PLAN only; this methodology overrides it. Continue into execution only as a DELIBERATE transition after the plan + cold-start prompt exist, never an implicit slide.
196
+ - **Cost lanes.** Route every step to the **cheapest adequate executor** — L0 deterministic script (the batched gate matrix over `gates.json`, the rotation `--check`s) · L1 cheap subagent (extraction/drafting only; the orchestrator verifies) · L2 subscription bridge · L3 frontier judgment. A step with **no named guardrail does not move down** a lane, and the **red lines never move down** (council review models · real code · ADR/handover/changelog-entry wording · persuasive copy · go/no-go · the approval asks). Own-error repair: salvage recorded state first (L0/L1, batched), never frontier re-derivation. **Prompt economy:** read-only fan-out (research/sweeps/extraction) runs ONLY on restricted-tool vehicles — a full-tool subagent for read-only work is a forbidden lane downgrade (invisible prompt-flood + blast radius), and a subagent is never told to shell out for facts obtainable read-only; the orchestrator's own shell form is ONE plain pipeline per call (a `;`/`&&` chain or env-prefixed invocation never matches a prefix allow rule); a fan-out launcher that gates per call yields to the agent-spawn lane — capability-gated: without restricted-tool vehicles (generic full-tool spawning does not count), read-only research stays in the orchestrator's own context, never a vehicle mandate a host cannot satisfy. Judgment, code, synthesis stay at the frontier lane (a task that genuinely runs/writes keeps a full-tool subagent); honest limit: no deterministic gate classifies a dispatch — canon at the point of use + placed vehicles + the retro loop. **Writer economy:** a stage's repeated WRITER commands batch — evidence declarations ride consecutive plain invocations of ONE allow-listed tool, other stage writers combine via one launcher per stage; never an unbatched writer scatter (each gated write is its own prompt).
@@ -3,6 +3,7 @@ Apply these when authoring a plan, reviewing, folding a finding, or editing code
3
3
  - **Fold by code, not prose.** Before folding a code-touching finding into a plan or change, read the cited `file:line` and cite it — a prose fold drifts from the code and seeds the next bug.
4
4
  - **Finding scope (plan-execution) — name the invariant BEFORE the edit.** During EXECUTION only — a plan under authoring has no shipped behaviour to call a live defect in, so plan-review carries none of this. Every finding names the invariant its fix would enforce, and where that invariant already lives decides the disposition: already an acceptance criterion of the phase → **fold here**; it would have to be ADDED → ship the **narrow fix** for the found site (red first, then green) and queue ONLY the generalization — a deferral row carries the invariant, the origin `file:line`, the narrow fix, its proof and a residual exposure declared NOT live; no correct narrow fix → **blocking**: the phase does not close, and it is **never queued**. Two bars declared before each round: a finding counts only if it changes a **WRITE/REMOVE decision** or is a false statement in shipped text; a repeat finding in one subarea **routes to SUBTRACTION**, not a fourth patch.
5
5
  - **Right altitude.** Pin intent + invariants + acceptance criteria (named tests); leave fine code-mechanics to Execute, where prose cannot diverge from reality.
6
+ - **Spec-first.** A plan names its GOVERNING spec(s) — zero, one or many, one per touched spec-covered slice (the feature spec under `docs/ai/specs/`; page-only coverage governs as an ADOPTION SHIM, with Out of scope + Revision stated inline in the plan). Each cited spec's Out of scope bounds that slice's work and the plan's non-goals restate it per slice — no global union; a cross-spec conflict is resolved by a spec revision BEFORE approval, never by silent precedence. A NEW feature's draft spec exists AT plan review (a `create` row); a change to a governed contract rides the plan as its proposed revision (a `modify` row); approval confirms plan and contract atomically, and the revision lands with the code. Scenario bindings are per scenario: a new scenario is `unbound` until its test lands in the same plan, and a status never regresses for an extension.
6
7
  - **No code-mechanics in the plan.** A ledger row carries its path and anchor, and Verification carries the exact commands (the plan-shape canon) — checked syntax: the plan's own Verification runs them against an explicit expected outcome or gate; the only other syntax a plan may carry is a literal fixture/schema fragment a named test copies or validates. Un-run, logic-bearing syntax — control-flow, a regex, a glob, a grammar, an algorithm body, a mini-DSL — never lives in plan prose, however plausible or shell-verified it looks: a fold or draft that wants one is the trigger to write the test instead.
7
8
  - **Test-as-spec.** Fold a code-touching finding into a red→green TEST, not a prose paragraph — the gate is the only deterministic checker; a paragraph cannot self-check.
8
9
  - **Characterize-first.** Before editing UNCOVERED code, pin its current behavior in a green test, then edit — any unintended change goes red. Never edit what has no checker; first give it one. Keep edits atomic/reversible; prefer SUBTRACTIVE folds.
@@ -24,7 +24,8 @@ A plan that does not fit is not under-described. Either the TASK is too big —
24
24
  independently verifiable boundaries, never by document size — or it is a SWEEP (below).
25
25
 
26
26
  - **Goal and boundary** (10 lines) — the observable outcome, what behaviour is preserved, explicit
27
- non-goals.
27
+ non-goals, and the GOVERNING spec(s) ([`specs.md`](specs.md)): zero, one or many — one per touched
28
+ spec-covered slice — each cited spec's Out of scope restated as a non-goal for that slice.
28
29
  - **Module ledger** (60 lines) — the single list of paths, and the plan's execution order.
29
30
  - **Verification** (20 lines) — the acceptance check, plus one command that validates the whole ledger.
30
31
  - **Phase: Cleanup** and **Next steps** (human-actionable only) share the 10 reserved lines.
@@ -41,6 +42,11 @@ One row per path, ≤200 bytes per row, six fields:
41
42
  only anchor on a path above it or on existing code. There is no separate step/phase numbering — the
42
43
  only phases are session boundaries in a multi-session plan, and Cleanup.
43
44
 
45
+ **A contract change is a row, present at review.** A NEW feature's draft spec is a `create` row
46
+ (`docs/ai/specs/<slug>.md`) and a proposed REVISION of a governed contract is a `modify` row — both
47
+ written WITH the plan, so approval confirms the plan and every cited draft or revision atomically; the
48
+ landing row moves the spec `draft -> live`, a removal row `live -> retired`.
49
+
44
50
  A `create` row's responsibility names the **exported surface** the module must provide — the names
45
51
  other rows import. That surface does not exist in the checkout yet, so it cannot be derived from it;
46
52
  this is the one interface contract a plan owes its executor.
@@ -33,7 +33,10 @@ Slots: review
33
33
 
34
34
  1. **Research** — the exact files, contracts and constraints touched.
35
35
  2. **Draft** — write to the shape [`planning.md`](planning.md) fixes; its *Module ledger* decides
36
- the layout and every budget before any file exists — a size gate is only the backstop.
36
+ the layout and every budget before any file exists — a size gate is only the backstop. Name the
37
+ governing spec(s) in *Goal and boundary* ([`specs.md`](specs.md)); a new feature's draft spec is
38
+ a `create` row and a revision of a governed contract a `modify` row, both written here so they
39
+ exist AT review.
37
40
  3. **Self-review** — apply *What gets cut*; fold by code (read and cite the `file:line`); update
38
41
  `queue.md` for a series.
39
42
  4. **review {recipe}** — Solo (self-review only) / Reviewed (one backend) / Council (both; you
@@ -67,7 +70,8 @@ Each ledger row is one logical commit.
67
70
  readiness (`--override <slot>=<recipe>` per run).
68
71
  2. **If `execute` resolved to Delegated, dispatch execution FIRST** — the backend returns a diff
69
72
  (codex-exec) *before* you integrate; otherwise implement directly.
70
- 3. **Implement / integrate** — your own edits or the reviewed delegated diff.
73
+ 3. **Implement / integrate** — your own edits or the reviewed delegated diff; a spec row lands its
74
+ approved draft or revision WITH the code ([`specs.md`](specs.md)).
71
75
  4. **Self-review** — the change against its [`planning.md`](planning.md) ledger row and the plan's
72
76
  Verification, under the project's reuse and clean-code rules; fold by code (cite the
73
77
  `file:line`); **characterize-first**: pin uncovered code's behaviour in a green test before
@@ -0,0 +1,190 @@
1
+ # Feature Specs
2
+
3
+ The durable per-feature CONTRACT layer of `docs/ai/`: what a feature accepts and returns, which
4
+ scenarios pin it, and — the core value — what is OUT of scope. A plan is the ephemeral delta; a spec
5
+ is the living, name-addressed WHAT; an ADR is the immutable, numbered WHY. Specs are not numbered.
6
+
7
+ The layer is a continuation of the family's own practice, not a methodology import: the existing
8
+ plan-approval checkpoint confirms a contract, the existing `maxLines` caps bound every file, the
9
+ existing `[[AD-NNN]]` convention links the why, and hidden mode covers the store with no new
10
+ machinery. **No new blocking checkpoints**: statuses are lifecycle markers, never approval gates.
11
+
12
+ ## Where specs live
13
+
14
+ - `docs/ai/specs/`, inside the memory substrate, hierarchical by domain and feature slice, built for
15
+ thousands of specs. The store root `docs/ai/specs/index.md` links up to `technical_specification.md`
16
+ (the top spec stays the system root).
17
+ - Every folder's `index.md` lists ONLY its immediate children; there is no global per-spec index.
18
+ The always-loaded navigator `docs/ai/index.md` carries ONE counted `specs/` row (specs / parts /
19
+ indexes) generated from the store, so adding or removing a valid spec changes the navigator and the
20
+ freshness gate sees it. A file the reader refuses keeps its own visible navigator row — never
21
+ swallowed by the aggregate.
22
+ - "Well-formed" has ONE definition: the deployed reader `scripts/spec-schema.mjs`
23
+ (`readSpecDocument(text, rel)`), read through by the navigator collapse and by the structural
24
+ checker alike. It is pure text — it imports nothing, opens no file, and models no markdown code
25
+ (a fence line refuses; a spec carries no code sample, so no line is ever ambiguous).
26
+
27
+ ## The frozen schema
28
+
29
+ These values are frozen; the reader's `SPEC_SCHEMA` carries them and the canon test pins this file
30
+ against it.
31
+
32
+ - **Frontmatter**: the substrate six keys — `type: spec`, `lastUpdated`, `scope`, `staleAfter`,
33
+ `owner`, `maxLines` — plus `kind`; the key set is CLOSED (an unknown or repeated key refuses).
34
+ `status` and an integer `revision >= 1` ride ONLY on
35
+ `kind: spec` (the contract root owns them); a `part` or an `index` carrying either REFUSES.
36
+ - **kind** — `index` | `spec` | `part` (the entity discriminant: an `index.md` with `kind: spec` is a
37
+ promoted contract root, with `kind: index` a domain navigator).
38
+ - **status** — `draft` | `live` | `retired`. Transitions run forward only: draft -> live (the
39
+ landing row of the approved plan) and live -> retired (the removal row), never backwards.
40
+ `revision` increments by one per live contract change.
41
+ - **Caps and thresholds** — `maxLines: 80` on an index, `maxLines: 150` on a spec or a part. Fan-out:
42
+ an index lists at most 30 immediate children; beyond that it subdivides along feature-slice /
43
+ subdomain boundaries, never into arbitrary buckets. Promotion: a spec over its own `maxLines: 150`
44
+ becomes `<slug>/index.md` (`kind: spec`) plus `part` files beside it — recursive, no depth limit.
45
+ - **slug** — the file stem of a flat spec, the folder name of a promoted root or a domain index:
46
+ `^[a-z0-9]+(-[a-z0-9]+)*$`. Every path segment under the store is a slug.
47
+ - **Scenario line** — `- S<N> <name> :: <repo-relative test path> :: spec:<slug>/S<N>` or
48
+ `- S<N> <name> :: unbound`; N runs contiguously from 1; the marker equals the line's own id.
49
+ - **`## Module`** — bullets of repo-relative paths: ONE `dir/` root OR a literal file list. `..`,
50
+ an absolute path, a backslash, a glob, and a dir + file mix each REFUSE. A retired spec may carry
51
+ `*(empty)*`.
52
+ - **`## Out of scope`** — at least one non-blank bullet, or exactly `*(empty)*` when the emptiness is a decision.
53
+ - An `unbound` scenario on a `live` spec is an advisory warning, never a refusal.
54
+
55
+ ## Shape per kind
56
+
57
+ - **`index`** — frontmatter: the six keys + `kind: index` (+ `maxLines: 80`). Headings: `# <title>`,
58
+ `## Children` — each immediate child exactly once as `- [name](./<child>.md)` or
59
+ `- [name](./<child>/index.md)`, at most 30. No `status`, no `revision`, no `## Scenarios`,
60
+ `## Out of scope`, `## Module` or `## Parts`. The store root additionally carries, before its
61
+ first section, exactly the line `> Up: [technical_specification.md](../technical_specification.md)`.
62
+ - **`spec`** — frontmatter: the six keys + `kind: spec` + `status` + `revision` (+ `maxLines: 150`).
63
+ File: `<slug>.md` (flat) or `<slug>/index.md` (promoted). Headings in order: `# Spec: <title>`,
64
+ `## Contract`, `## Scenarios`, `## Out of scope`, `## Module`, optional `## Parts` (promoted roots
65
+ only: each part exactly once as `- [name](./<part>.md)`), optional `## Links` (`[[AD-NNN]]`, page
66
+ citations).
67
+ - **`part`** — frontmatter: the six keys + `kind: part` (+ `maxLines: 150`). File: `<name>.md` beside
68
+ a promoted root. Heading: `# Part: <title>`. No `status`, `revision`, `## Scenarios`,
69
+ `## Out of scope`, `## Module` or `## Parts` — the root owns them.
70
+
71
+ ## Refusals
72
+
73
+ The reader names exactly one rule per defect. The repo-only fixture corpus carries at least one
74
+ refuse case per rule and an accept case per kind; a refuse fixture yields exactly its rule.
75
+
76
+ | Rule | Refuses |
77
+ |------|---------|
78
+ | `frontmatter` | no YAML frontmatter |
79
+ | `frontmatter-key` | an unknown key, a repeated key, or a line that is not `key: value` (the key set is closed; this ends the read) |
80
+ | `substrate-key` | one of the six substrate keys is missing |
81
+ | `type` | `type` is not `spec` |
82
+ | `kind` | `kind` missing or outside `index`, `spec`, `part` |
83
+ | `maxlines` | `maxLines` differs from the kind's frozen cap |
84
+ | `status` | a spec's `status` missing or outside `draft`, `live`, `retired` |
85
+ | `revision` | a spec's `revision` missing or not an integer >= 1 |
86
+ | `root-owns` | a part or an index carrying `status` or `revision` |
87
+ | `slug` | a path segment outside the slug pattern |
88
+ | `kind-path` | an index not named `index.md`, a part named `index.md` or placed at the store root, a contract root at the store root |
89
+ | `root-uplink` | the store root without the exact up-link line before its first section |
90
+ | `title` | the first heading is not the kind's title form, or a section opens before it |
91
+ | `section-missing` | a required section absent |
92
+ | `section-order` | the required and optional sections out of order |
93
+ | `section-forbidden` | a section the kind never carries |
94
+ | `fence` | a code fence line — a spec document carries no code sample; the reader models no markdown code |
95
+ | `children-link` | a `## Children` line that is not a child link to a slug |
96
+ | `children-duplicate` | a child listed twice |
97
+ | `fan-out` | more than 30 children |
98
+ | `scenario-line` | a `## Scenarios` line outside the scenario grammar |
99
+ | `scenario-number` | scenario ids not contiguous from 1 |
100
+ | `scenario-marker` | a marker whose slug or id differs from its own line |
101
+ | `scenario-path` | a binding's test path that is not a repo-relative file |
102
+ | `out-of-scope` | no non-blank bullet and not exactly `*(empty)*` |
103
+ | `module-line` | a `## Module` line that is not a `- <path>` bullet |
104
+ | `module-empty` | no module path and not exactly `*(empty)*` (a retired spec may carry the marker instead of a path) |
105
+ | `module-traversal` | a module path with `..` |
106
+ | `module-absolute` | an absolute module path |
107
+ | `module-backslash` | a module path with a backslash |
108
+ | `module-glob` | a module path with a wildcard |
109
+ | `module-mix` | neither ONE `dir/` root nor a literal file list |
110
+ | `parts` | `## Parts` on a flat spec, a malformed part link, or a part listed twice |
111
+
112
+ ## Lifecycle, binding and approval
113
+
114
+ - **One entity.** A spec is the durable contract; the ephemeral plan is the delta vehicle; the spec
115
+ revision lands with the code. There is no change-spec entity.
116
+ - **Governing specs are plural.** A plan cites ZERO (nothing spec-covered touched — legal during
117
+ adoption), ONE or MANY governing specs — one per touched spec-covered slice; a shared-module change
118
+ cites the specs of every slice whose contract it can alter.
119
+ - **Out of scope composes PER GOVERNING SLICE — there is no global union.** Each cited spec's
120
+ exclusions bound only the work inside that slice, and the plan's non-goals restate them per slice.
121
+ A cross-spec conflict is resolved by a spec REVISION BEFORE plan approval — never by silent
122
+ precedence or review-time improvisation.
123
+ - **Every contract change is reviewable at plan review.** A NEW feature's draft spec is authored WITH
124
+ the plan and exists AT review (a `create` ledger row); a plan that ALTERS a governed contract
125
+ carries the proposed revision at review the same way (a `modify` row). Approval confirms the plan
126
+ and every cited draft or revision atomically; the rows then land them. A contract is never
127
+ "confirmed" before it is visible.
128
+ - **Test-as-spec binding is per scenario.** Every scenario names the test that pins it; a NEW
129
+ scenario on a live spec is individually `unbound` until its test lands within the same plan — the
130
+ spec's status never regresses for an extension. The binding is literal and language-agnostic: the
131
+ marker `spec:<slug>/S<N>` must occur exactly once in the named file. The honest claim is an
132
+ advisory structural pin; runnability and greenness stay the test suite's own gate.
133
+ - **Containment** is lexical AND realpath on every path-bearing field (bindings, child links, module
134
+ roots). The reader judges the lexical half; realpath and symlink escapes are the structural
135
+ checker's duty (`spec-check`, a later slice), together with marker existence and uniqueness, link
136
+ resolution and the global invariants (slug uniqueness, module-root overlap, tree acyclicity) that
137
+ only a full-tree sweep can prove.
138
+
139
+ ## Precedence — feature spec and page spec
140
+
141
+ | Feature spec | Page spec | Governs |
142
+ |--------------|-----------|---------|
143
+ | present | present | the feature spec; `pages/<page>.md` is a subordinate view-layer document cited from it |
144
+ | absent | present | the page spec, as an ADOPTION SHIM — the citing plan states Out of scope + Revision inline |
145
+ | present | absent | the feature spec |
146
+
147
+ `PAGE_TEMPLATE.md` is not upgraded for this; the durable fix is authoring the feature spec through
148
+ the retroactive path below.
149
+
150
+ ## Spec and code
151
+
152
+ - **A discoverable bijection.** The spec path mirrors the feature-slice path; a slug is IDENTICAL to
153
+ the slice or module name for NEW and deliberately refactored slices. A LEGACY module keeps its
154
+ existing name — the spec's declared module root IS the mapping, no rename ride-alongs; folder
155
+ promotion keeps the slug. A module root is a DIRECTORY or a DECLARED FILE SET, so a file-based
156
+ boundary binds without an architectural refactor.
157
+ - **Spec drives modular, feature-sliced architecture.** Code is organized by feature slice, not by
158
+ technical layer; a spec's drill-down mirrors the slice's module boundaries; the pure rule / IO
159
+ shell split is the default module anatomy, composing with the source-size practice (a module you
160
+ can hold whole is the unit of review).
161
+ - **ONE dependency rule: the dependency graph is explicit and acyclic.** A slice depends on `shared`
162
+ and on another slice's PUBLIC contract only when that edge is DECLARED in its spec; an undeclared
163
+ cross-slice reach-in is refused at review.
164
+ - **Enforcement altitude, stated honestly.** This series the dependency and reach-in rules are
165
+ REVIEW-level canon defaults for NEW and deliberately refactored slices — no machine enforcement
166
+ (declared-edge fields, cycle checks, reach-in detection) ships; a legacy layered project is not
167
+ rendered nonconforming and adopts per feature through the retroactive path.
168
+
169
+ ## Spec and ADR
170
+
171
+ Complementary, never competing: an ADR is the immutable, numbered, chronological WHY; a spec is the
172
+ living, name-addressed WHAT. A spec cites its shaping ADRs in `## Links` via `[[AD-NNN]]`; an ADR
173
+ that changes a contract triggers a spec revision.
174
+
175
+ ## Retroactive coverage
176
+
177
+ The onboarding path for an existing feature: read the code, author a `draft` spec from it (the
178
+ contract it actually keeps, the scenarios its tests already pin, the Out of scope its boundaries
179
+ imply), then human review promotes it to `live` through a plan. Used to dogfood the family's own
180
+ subsystems; a legacy module binds through a declared file set, never a refactor ride-along.
181
+
182
+ ## Scale
183
+
184
+ The navigator walk is O(all docs) per pre-commit; the spec store joins it. The budget is a numeric
185
+ release gate: 1500 ms for both hook runs over a valid 1000-spec, 30-per-folder tree (the median of
186
+ three trials), measured by the repo's scale probe before every release of the checker — exceeding it
187
+ is fixed inside the walk (cheaper per-file work, early exits), never queued. The structural checker
188
+ is O(changed) by construction: it takes explicit spec paths, and `--all` is the explicit full sweep;
189
+ a git-derived "changed" lane is refused, because hidden mode ignores `docs/ai/**` and git would see
190
+ no spec at all.