Fab Templates

September 9, 2026 · View on GitHub

Templates that ship in $(fab kit-path)/templates/. Each is a markdown scaffold that skills fill with concrete content. Guidance comments (<!-- -->) are instructions to the agent, not preserved in output.


.status.yaml

State Vocabulary

All status fields draw from a fixed set of states. This prevents ad-hoc state names from creeping in across skills.

StateMeaningUsed by
pendingNot yet startedAll stages (template seed; intake only transiently at creation — /fab-new starts it immediately)
activeCurrently being worked onAll stages
readyArtifact exists, open for refinement before finishintake, apply, review, hydrate (not ship/review-pr — advance is rejected there)
doneCompleted successfullyAll stages
skippedIntentionally bypassed (cascades downstream)All stages except intake
failedCompleted with failures requiring reworkreview, review-pr

progress map keys: intake | apply | review | hydrate | ship | review-pr

The current stage is derived from the progress map — the entry marked active is the current stage. There is no separate stage: field. The per-stage allowed-state sets and transitions live in the Go state machine (src/go/fab/internal/status) — every fab status event validates its target state against them.

Template

# Generated by /fab-new (from $(fab kit-path)/templates/status.yaml).
# Mutated only via the fab CLI (incl. `fab status refresh`) — never hand-edited.

id: {XXXX}                         # 4-char change ID (immutable; matches the folder's {XXXX} component)
name: {YYMMDD-XXXX-slug}
created: {ISO_8601_DATETIME}       # e.g., 2026-01-15T14:30:00Z
created_by: {USER}                 # Auto-detected: gh api user .login, then git config user.name, then "unknown"
change_type: feat                  # feat | fix | refactor | docs | test | ci | chore
issues: []                         # Issue IDs (fab status add-issue — idempotent)
progress:
  intake: pending                  # → active | ready | done
  apply: pending                   # pending | active | ready | done | skipped — co-generates plan.md (## Requirements + ## Tasks + ## Acceptance) at entry, then runs tasks
  review: pending                  # pending | active | ready | done | failed | skipped
  hydrate: pending                 # pending | active | ready | done | skipped
  ship: pending                    # pending | active | done | skipped
  review-pr: pending               # pending | active | done | failed | skipped
plan:
  generated: false                 # set true on first plan.md write
  task_count: 0                    # count of - [ ] + - [x] items under plan.md ## Tasks
  acceptance_count: 0              # count of - [ ] + - [x] items under plan.md ## Acceptance
  acceptance_completed: 0          # count of - [x] items under plan.md ## Acceptance
confidence:
  certain: 0                       # count of Certain-graded SRAD decisions
  confident: 0                     # count of Confident-graded decisions
  tentative: 0                     # count of Tentative-graded decisions
  unresolved: 0                    # count of Unresolved-graded decisions
  score: 0.0                       # derived score (0.0–5.0)
stage_metrics: {}                  # populated by the fab status CLI as stages progress
prs: []                            # PR URLs (fab status add-pr — idempotent)
summary: ""                        # per-change one-line log summary (FKF C-lite log.md source, §6.3) — optional, omitempty
# true_impact: lazily created on first stage-finish that computes it (no placeholder here).
last_updated: {ISO_8601_DATETIME}

Field notes:

  • id is the immutable 4-char change ID — the canonical short reference accepted by every fab command.
  • created_by is write-once — set at change creation time by /fab-new, never modified afterward. Auto-detected by a three-step ladder in internal/change.detectCreatedBy: the GitHub login from gh api user --jq .login first, then git config user.name, then the literal "unknown" when neither yields a non-empty value. Skills reading this field must tolerate its absence (older changes won't have it).
  • change_type classifies the change (PR type, expected_min decision counts — see Change Types). Defaults to feat; inferred from intake content by fab status refresh (self-healed at the transition seams; fab status set-change-type for explicit overrides).
  • issues / prs are append-only ID/URL arrays maintained by fab status add-issue / add-pr (/git-pr records the PR URL on ship).
  • The current stage is derived from the progress map — the entry marked active is the current stage. All skills read this first.
  • failed is set on review (by /fab-continue review or the ff/fff rework loop) and on review-pr (by /git-pr-review). The entry remains failed so /fab-status shows the failure; start is the failed→active recovery event.
  • plan.task_count, plan.acceptance_count, and plan.acceptance_completed are recomputed by fab status refresh (self-healed at the transition seams — fab status advance/finish, fab preflight), which re-counts checkbox items in each section. Apply mutates ## Tasks; review mutates ## Acceptance checkboxes in place. The legacy set-checklist CLI is removed — use set-acceptance (fab status set-acceptance <change> <field> <value>) for explicit overrides.
  • confidence block initializes to zero counts and score 0.0 — a new change has no assessed confidence. Computed by fab score --stage intake (via /fab-new and /fab-draft — both through the shared _intake Step 7 — plus /fab-clarify, and fab status refresh self-healed at the transition seams); intake scoring is authoritative. When SRAD dimensions are recorded (fab status set-confidence-fuzzy), the block also carries fuzzy: true and a dimensions: map (signal/reversibility/competence/disambiguation).
  • stage_metrics is populated by the fab status CLI as stages progress — tracks started_at, completed_at, driver, and iterations per stage (/git-pr-review additionally writes review-pr phase/reviewer sub-state).
  • true_impact is written lazily by the apply-finish, hydrate-finish, and ship-finish hooks (line counts from fab impact); the template carries no placeholder. In the standard pipeline the ship-finish write is the authoritative one — apply/hydrate run before any commit exists (HEAD == merge-base), so they write zeros until ship supersedes them.
  • summary is the per-change one-line log summary — the FKF C-lite source line fab docs-index docs/memory joins with git history to generate log.md (see fkf.md §6.3). Optional (yaml:"summary,omitempty", modeled on change_type_source): an empty/absent summary serializes to nothing and degrades gracefully (the generator falls back to the change slug). Written via fab status set-summary <change> <text> / read via get-summary — the conflict-free write path (each change touches only its own .status.yaml). The template seeds summary: "" to document the field; no stage auto-populates it (authoring wiring is a later FKF change).
  • last_updated is refreshed on every status change.

intake.md

# Intake: {CHANGE_NAME}

**Change**: {YYMMDD-XXXX-slug}
**Created**: {DATE}

## Origin

<!-- How was this change initiated? Include the user's raw input/prompt, the interaction
     mode (one-shot vs. conversational), and key decisions from the conversation.
     This section provides traceability — downstream agents need to understand not just
     WHAT was decided, but HOW the decision was reached. -->

> {USER_INPUT}

## Why

<!-- Explain the motivation substantively:
     1. What problem does this solve? (the pain point)
     2. What happens if we don't fix it? (the consequence)
     3. Why this approach over alternatives? (the reasoning)
     A single sentence is almost never enough. -->

## What Changes

<!-- Be specific about new capabilities, modifications, or removals.
     Use subsections (### per change area) for multi-part changes.
     Include concrete examples: code blocks, config snippets, exact behavior.
     This section is the primary input for plan generation (apply entry) — if a design decision
     was made with specific values, include them here. Do not summarize or abstract. -->

## Affected Memory

<!-- Which memory files will be created, modified, or removed by this change.
     Use kebab-case identifiers matching docs/memory/ paths. Mark each with (new), (modify), or (remove).
     Only list if spec-level behavior changes — implementation-only changes don't need memory updates.
     A file in a split (sub-domained) domain takes the optional 3-part form
     `{domain}/{sub-domain}/{file-name}`; the flat `{domain}/{file-name}` form stays valid for
     un-split domains. -->

- `{domain}/{file-name}`: ({new|modify|remove}) {description}
- `{domain}/{sub-domain}/{file-name}`: ({new|modify|remove}) {description}  <!-- optional 3-part form for sub-domained files -->

## Impact

<!-- Affected code areas, APIs, dependencies, systems. Helps scope the plan. -->

## Open Questions

<!-- Clarifying questions the agent couldn't resolve from context alone.
     SRAD handles prioritization at plan generation (apply entry) — no need for explicit
     blocking/deferred labels here. Just list the questions. -->

- {question}

## Assumptions

<!-- STATE TRANSFER: This table is the sole continuity mechanism between the intake-stage
     agent and the apply-entry agent (which co-generates plan.md). Pipeline stages may execute
     in separate agent contexts with no shared memory — this table is what gives downstream
     agents visibility into what was decided, assumed, or left open. Every row must be substantive.

     All four SRAD grades (Certain, Confident, Tentative, Unresolved) are recorded.
     Scores column is required for every row.
     Unresolved rows must include status context in Rationale (e.g., "Asked — user undecided"). -->

| # | Grade | Decision | Rationale | Scores |
|---|-------|----------|-----------|--------|
| 1 | {Certain|Confident|Tentative|Unresolved} | {decision summary} | {why this grade} | S:nn R:nn A:nn D:nn |

{N} assumptions ({Ce} certain, {Co} confident, {T} tentative, {U} unresolved).

Design rationale: OpenSpec's concise Why/What/Impact structure, plus explicit memory mapping (a flat Affected Memory list with (new)/(modify)/(remove) markers). Open questions carry no priority labels — the legacy [BLOCKING]/[DEFERRED] markers were removed in 1.10.0; SRAD grading (the Assumptions table + the single intake gate) is what prioritizes and bounds question-asking now.


Change Requirements (now a section of plan.md)

As of 1.10.0, the separate spec.md artifact is removed. Requirement capture lives in plan.md's ## Requirements section, co-generated with ## Tasks and ## Acceptance at apply entry (one pass — the strongest alignment guarantee). The requirement discipline is unchanged: RFC 2119 keywords (MUST/SHALL/SHOULD/MAY), at least one GIVEN/WHEN/THEN scenario per requirement, stable R# IDs, optional ### Non-Goals / ### Design Decisions / ### Deprecated Requirements subsections. No [NEEDS CLARIFICATION] markers — those are an intake-only construct; an under-specified requirement at apply becomes a graded SRAD ## Assumptions row instead.

Design rationale: After the confidence gate moved to intake (1.10.0), nothing reads a separate spec.md programmatically — it would be generated, never machine-read, and hidden. Folding requirements into plan.md makes the same agent that writes a requirement immediately consume it for tasks and acceptance, eliminating the seam the former spec→plan boundary created. The agent infers what's new vs changed by comparing against existing memory files during hydration. Domain sections within ## Requirements keep multi-domain changes organized.


plan.md (Apply-stage Plan: Requirements + Tasks + Acceptance)

plan.md is co-generated at apply entry by the unified Plan Generation Procedure and consumed by both apply (which reads ## Tasks) and review (which reads ## Acceptance against ## Requirements). Section headings (## Requirements, ## Tasks, ## Acceptance) and the R#/T{NNN}/A-{NNN} ID formats are the stable parser contract — phase/category subheadings under each are presentational and may vary per change. Trace annotations are REQUIRED: each ## Tasks item carries <!-- R# -->; each requirement-derived ## Acceptance item names its R# (Code Quality and checklist.extra_categories items carry no R# — A-{NNN}: {outcome}).

# Plan: {CHANGE_NAME}

**Change**: {YYMMDD-XXXX-slug}
**Intake**: `intake.md`

## Requirements

### {Domain}: {Topic}

#### R1: {Requirement Name}
{Requirement text using SHALL/MUST/SHOULD/MAY}

- **GIVEN** {precondition}
- **WHEN** {action or event}
- **THEN** {expected outcome}

<!--
  AUTO-GENERATED by /fab-continue (or /fab-ff/fab-fff) at the apply stage entry.

  TASK FORMAT: - [ ] T{NNN} [{markers}] {Description with file paths} <!-- R# -->
  ACCEPTANCE FORMAT (requirement-derived): - [ ] A-{NNN} R#: {declarative outcome}
  ACCEPTANCE FORMAT (Code Quality / extra categories): - [ ] A-{NNN}: {declarative outcome}

  IDs are sequential, three-digit, zero-padded.
-->

## Tasks

### Phase 1: Setup
- [ ] T001 {setup task with file path} <!-- R1 -->
- [ ] T002 [P] {parallel setup task} <!-- R1 -->

### Phase 2: Core Implementation
- [ ] T003 {implementation task referencing specific file path} <!-- R2 -->
- [ ] T004 {task that depends on T003} <!-- R2 -->
- [ ] T005 [P] {independent task} <!-- R1 -->

### Phase 3: Integration & Edge Cases
- [ ] T006 {integration task} <!-- R2 -->
- [ ] T007 {error handling task} <!-- R2 -->

### Phase 4: Polish
- [ ] T008 {polish task} <!-- R1 -->

## Execution Order

- T003 blocks T004
- T005 is independent, can run alongside T003-T004

## Acceptance

### Functional Completeness
- [ ] A-001 R1: {specific verifiable criterion}
- [ ] A-002 R2: {criterion}

### Behavioral Correctness
- [ ] A-003 R2: {what changed and how to verify}

### Removal Verification
- [ ] A-004 R#: {confirm removed, no dead code}

### Scenario Coverage
- [ ] A-005 R#: {how to verify — test exists, manual check, etc.}

### Edge Cases & Error Handling
- [ ] A-006 R#: {what should happen}

### Code Quality
- [ ] A-007 Pattern consistency: New code follows naming and structural patterns of surrounding code
- [ ] A-008 No unnecessary duplication: Existing utilities reused where applicable

### Security
- [ ] A-009 R#: {verification}

## Notes

- Check items as you review: `- [x]`
- All acceptance items must pass before `/fab-continue` (hydrate)
- If an item is not applicable, mark checked and prefix with **N/A**: `- [x] A-NNN **N/A**: {reason}`

Design rationale: Requirements, apply (imperative tasks), and review (declarative acceptance) are three framings of the same change; co-generating all three in a single skill call eliminates the drift between sibling artifacts the legacy spec.mdtasks.md + checklist.md boundary was prone to. Heading-based parsing keeps the parser contract stable while leaving phase/category subheadings ergonomic. [P] markers and per-phase grouping are preserved verbatim from the legacy tasks.md convention.

For larger changes spanning multiple user stories, split Phase 2 into per-story sub-phases:

### Phase 2a: User Story — {Title} (P1)
- [ ] T003 [US1] {task}

### Phase 2b: User Story — {Title} (P2)
- [ ] T006 [US2] {task}

Acceptance Generation

The unified Plan Generation Procedure generates ## Requirements from the intake, then walks those requirements once and emits, for each requirement, a Task entry under ## Tasks and an Acceptance entry under ## Acceptance. Acceptance items are derived from:

  • ## Requirements (the requirements and scenarios for this change, co-generated above)
  • Project code-quality.md (principles + anti-patterns)
  • Project constitution (quality standards)
  • Optional checklist.extra_categories from config.yaml

In-flight changes that pre-date this schema keep their legacy CHK-NNN IDs verbatim through migration; only newly generated plans use A-NNN.

Example: Filled Plan

# Plan: 260115-a7k2-add-oauth

**Change**: 260115-a7k2-add-oauth
**Intake**: `intake.md`

## Requirements

### Auth: OAuth2 Support

#### R1: OAuth2 Provider Support
The system SHALL support authentication via external OAuth2 providers (Google OpenID Connect, GitHub OAuth2).

- **GIVEN** a user on the login page
- **WHEN** they click "Sign in with Google"
- **THEN** they are redirected to Google's OAuth consent screen and authenticated on approval

## Tasks

### Phase 1: Setup
- [ ] T001 [P] Add OAuth client config to `src/config/auth.ts` <!-- R1 -->
- [ ] T002 [P] Install OAuth provider SDK dependency <!-- R1 -->

### Phase 2: Core Implementation
- [ ] T003 Implement `OAuthClient` in `src/auth/oauth.ts` <!-- R1 -->
- [ ] T004 Wire OAuth callback handler in `src/api/auth/callback.ts` <!-- R1 -->

### Phase 3: Integration & Edge Cases
- [ ] T005 Handle OAuth provider timeout with clear error in `src/auth/oauth.ts` <!-- R1 -->

## Execution Order

- T003 blocks T004

## Acceptance

### Functional Completeness
- [ ] A-001 R1: Google and GitHub sign-in flows implemented
- [ ] A-002 R1: Provider configuration stored in environment

### Scenario Coverage
- [ ] A-003 R1: Redirect to provider consent screen works end-to-end
- [ ] A-004 R1: Callback handles approval and creates session
- [ ] A-005 R1: Callback handles denial gracefully

### Edge Cases & Error Handling
- [ ] A-006 R1: Provider unavailable — timeout with clear error message

### Security
- [ ] A-007 R1: OAuth tokens stored securely (not in localStorage)
- [ ] A-008 R1: CSRF protection on OAuth callback

## Notes
- Check items as you review: `- [x]`
- All acceptance items must pass before `/fab-continue` (hydrate)
- If an item is not applicable, mark checked and prefix with **N/A**: `- [x] A-NNN **N/A**: {reason}`

Memory File Format (docs/memory/)

Memory files are the source of truth for what the system does and why it works the way it does. They contain requirements (from plan.md ## Requirements), organized hierarchically with index files for navigation.

Directory Structure

docs/memory/
├── index.md                    # Top-level index: lists all domains
├── auth/
│   ├── index.md                # Domain index: lists files + any sub-domains in auth/
│   ├── authentication.md       # Individual memory file
│   └── authorization.md
├── payments/
│   ├── index.md
│   ├── checkout.md
│   ├── refunds.md
│   └── disputes/               # Sub-domain (a split cluster ≥8 cohesive files)
│       ├── index.md            # Sub-domain index: lists files in payments/disputes/
│       ├── chargebacks.md      # Sub-domained memory file
│       └── arbitration.md
└── users/
    ├── index.md
    └── registration.md

A sub-domain is a folder one level under a domain dir holding its own topic files ({domain}/{sub-domain}/{topic}.md — depth 3, the max). docs-reorg-memory creates one when an over-wide domain has a real cluster of ≥8 cohesive files; un-split domains stay flat.

Generated Indexes (fab docs-index docs/memory)

All index tiers are generated artifacts — never hand-edit them. fab docs-index docs/memory regenerates the root, every domain index, AND every sub-domain index deterministically from folder contents (each file's description: frontmatter + H1). The render is content-only — the indexes carry no dates, because a git log projection is HEAD/branch-relative and so never idempotent; dated recency lives in the per-folder log.md instead. The output is byte-stable / idempotent, so two branches can never produce conflicting hand-edits to the same index row, and any residual textual conflict auto-resolves by re-running fab docs-index docs/memory post-merge. See _cli-fab.md## fab docs-index.

Top-Level Index (docs/memory/index.md) — domains-only

The root index is domains-only (no inlined per-file "Memory Files" column — that column silently drifts as files are added). Each domain row's Description is read from the domain index.md's description: frontmatter (round-tripped by the generator). The root index — and only the root — is prepended with the FKF version frontmatter block (FKF §8); no domain or sub-domain index carries it.

---
fkf_version: "0.1"
---
# Memory Index

> **Generated by `fab docs-index`** — do not hand-edit. Re-run after any memory write.

| Domain | Description |
|--------|-------------|
| [auth](auth/index.md) | Authentication and authorization |
| [payments](payments/index.md) | Payment processing and billing |
| [users](users/index.md) | User management |

Domain Index (docs/memory/{domain}/index.md)

The domain index carries its own curated one-liner as description: frontmatter (the source for the root row), then file rows. Descriptions come from each file's description: frontmatter. The index carries no dates — it is a pure function of content (recency lives in log.md).

---
description: "Authentication and authorization"
---
# {Domain} Documentation

> **Generated by `fab docs-index`** — do not hand-edit.

| File | Description |
|------|-------------|
| [authentication](authentication.md) | User login, session management, OAuth |
| [authorization](authorization.md) | Roles, permissions, access control |

When a domain contains sub-domains, the generated domain index appends a ## Sub-Domains table after the file rows (linking each sub-domain to its own index.md). The section is emitted only when sub-domains exist, so a flat domain index is byte-identical to the form above:

## Sub-Domains

| Sub-Domain | Description |
|------------|-------------|
| [disputes](disputes/index.md) | Chargebacks, arbitration, dispute lifecycle |

Sub-Domain Index (docs/memory/{domain}/{sub-domain}/index.md)

A sub-domain (a split cluster) gets its own generated index, rendered with the same file-row contract as a domain index — relative [file](file.md) links are correct from the sub-domain folder. It carries its own curated one-liner as description: frontmatter (the source for the parent domain index's sub-domain row).

---
description: "Chargebacks, arbitration, dispute lifecycle"
---
# {Sub-Domain} Documentation

> **Generated by `fab docs-index`** — do not hand-edit.

| File | Description |
|------|-------------|
| [arbitration](arbitration.md) | Dispute arbitration flow |
| [chargebacks](chargebacks.md) | Chargeback handling |

Generated log.md (docs/memory/{domain}[/{sub-domain}]/log.md)

Alongside the index tiers, fab docs-index docs/memory emits a per-folder log.md for every domain and sub-domain with attributable git history — the C-lite change log that replaces the per-file ## Changelog tables FKF removes (FKF §6). Like index.md it is single-writer and never hand-edited, but it is not a pure projection: generation is freeze-on-write, so the existing file is authoritative and regeneration only appends newly-discovered entries (FKF §6.4). A folder with no attributable commits and no seed is skipped — no empty log.md is written.

# Log — {Title}
<!-- Generated by `fab docs-index` from git history + per-change summaries. Do not hand-edit. -->

## 2026-06-13
- **Update** [migrations](/distribution/migrations.md) — surfaces the optional `agent.tiers` per-stage-model override; additive, no schema change. (260613-l3ja)

## 2026-06-12
- **Update** [migrations](/distribution/migrations.md) — drops the dead `stage_directives:` block. (260612-c5tr)

Entries are date-grouped newest-first. Each is an optional leading bold verb (**Creation** / **Deprecation** / **Update**, omitted when the git name-status is ambiguous), a bundle-relative link (FKF §7), the change's .status.yaml summary: (or the change slug when absent), and the (change-id) token — one line per change per file. The what-changed half is written once via fab status set-summary, never into log.md directly.

log.seed.md (curated seed input — never generated)

A per-folder log.seed.md is the one hand-authored file in this family: a curated sidecar in the same §6.2 entry format that fab docs-index docs/memory parses and merges beneath the git-projected entries, de-duplicated and idempotent. It exists because the pre-FKF per-file ## Changelog rows predate any .status.yaml summary: to project from — the seed preserves them verbatim under their own authored dates across every regen. It is read-only input: the generator never writes it, which is what keeps log.md's single-writer discipline intact. The doc skills treat it like a ledger — excluded from distillation, never rewritten to a style.

fab docs-index docs/memory --check Drift Tiers

--check regenerates in memory and compares against what is committed, classifying any difference into two tiers:

  • Tier 1 — benign drift (exit 1): the committed artifact is merely stale. Regenerate and commit. All log.md differences stay tier 1 by construction — a log is a git projection, not a row table, so the index-only detectors are skipped for it.
  • Tier 2 — destructive loss (exit 2): regeneration would drop hand-managed/historical content — a description that would regenerate to , a tombstone row whose link target is gone, or a custom structural heading the domains-only root render would flatten. Tier 2 fires the refuse-before-regen guards in hydrate and docs-reorg-memory.

Independent of both tiers, a blocking content class (malformed frontmatter, an unterminated description: quote, a change-id in description:, or a description past the 1000-character gross cap) floors the exit code at 1 whether or not anything drifted, with a fix-the-file remediation rather than a reorg pointer. Full taxonomy: memory-docs/templates.md.

Memory Tree Shape (SHOULD guidance)

Memory trees SHOULD stay neither too wide nor too deep — wide folders breed central files that churn on every change. The shape bounds (advisory, surfaced as fab docs-index docs/memory warnings and the docs-reorg-memory Shape Report):

  • ~12 topic files per folder (soft upper bound) — over this, fab docs-index docs/memory warns.
  • ~5 files lower bound before a sub-domain earns its own index.
  • Max depth 3: docs/memory/{domain}/{sub-domain}/{topic}.md — over this warns.
  • Introduce a sub-domain only reactively — when a real cluster of ≥8 cohesive files exists. Never pre-build hierarchy; let clusters emerge.
  • Reserved domains _shared/ (cross-cutting) and _unsorted/ (staging) are exempt from the width warning.
  • description: ≤ 500 characters (soft cap, FKF §3.2) — the cap enforces in two tiers: a description in the 501–1000 range draws an advisory fab docs-index docs/memory warning (a trim nag that never fails --check$), \text{and} \text{a} **\text{gross} \text{over}-\text{cap}** \text{description} \text{strictly} \text{longer} \text{than} **1000 \text{characters}** (2 \times \text{the} \text{soft} \text{cap}) **\text{BLOCKS}** — \text{it} \text{joins} \text{the} \text{blocking} \text{class} \text{below} \text{and} \text{fails} $--check.

Acting on these bounds (actually splitting/merging/flattening) is the docs-reorg-memory skill's job (propose-then-apply); fab docs-index docs/memory only detects and warns.

Blocking (distinct from the advisory bounds above): fab docs-index docs/memory also detects a blocking class that makes --check fail independent of index drift (a corrupted or offending row is byte-identical to its regeneration, so the drift check alone can't catch it). Four findings are members: malformed frontmatter (an unclosed --- block, or a description: value that fails quote-stripping — the glued-fence corruption), a registry-gated change-id in description: (the FKF §3.2 ban, now enforced — descriptions are routing signals; citations belong in the body), and a gross over-cap description: (> 1000 characters, 2× the 500 soft cap). None is a source-file loss fixed by a reorg — they are fixed by repairing the frontmatter, trimming the description, or moving change-id citations to the body — and none is a tier-2 destructive-loss category, so they do not fire the refuse-before-regen guards (FKF §3.2 / §5).

Individual File (docs/memory/{domain}/{name}.md)

Memory files carry the FKF frontmatter pair — the type: memory constant (FKF §3.1) and a curated description: one-liner (capped at 500 characters and free of change-ids, FKF §3.2 — a routing signal, not a summary of record; detail and provenance citations belong in the body) that fab docs-index docs/memory reads into the domain-index row. The description is co-located with the file (the Starlight lesson) so editing it never touches the hot index row.

The body states current truth in present tense (FKF §3.3): no transition narration ("renamed X→Y in {id}", "this inverts {id}'s claim", "was old.value") and no description of superseded behavior — the previous state lives in the per-folder generated log.md (§6), git history, and archived change folders. Body provenance is citation-only: a trailing (change-id) and the *Introduced by*: {change-name} field on a Design Decision. Headings carry no change-ids — a heading names its topic (## Dispatch States), never a change (### Dispatch States (xu0k)); a change-id stays a citation in body text. No operational TODOs — follow-up work items belong in the backlog (fab/backlog.md) or the change folder, never a memory body. Any why / rejected alternative goes into a ## Design Decisions entry in the four-field shape (Decision / Why / Rejected / Introduced by), never inline narration; the changelog-bullet shape (- **{change-id} — retired X**) is banned inside ## Design Decisions (change history is log.md's job, §6). There is no ## Changelog section — FKF removes the per-file changelog table (§3.3); change history lives in log.md.

---
type: memory
description: "One-line summary used by the generated domain index row."
---
# {File Name}

**Domain**: {domain}

## Overview

<!-- 1-2 sentences describing what this file covers. -->

## Requirements

### Requirement: {Requirement Name}
{Requirement text using SHALL/MUST/SHOULD/MAY}

#### Scenario: {Scenario Name}
- **GIVEN** {precondition}
- **WHEN** {action}
- **THEN** {expected outcome}

### Requirement: {Another Requirement}
{text}

#### Scenario: {Scenario Name}
- **GIVEN** {precondition}
- **WHEN** {action}
- **THEN** {expected outcome}

## Design Decisions

<!-- Durable architectural decisions. Only decisions with lasting relevance — skip tactical implementation details. -->

### {Decision Title}
**Decision**: {chosen approach}
**Why**: {rationale}
**Rejected**: {alternative and why it was worse}
*Introduced by*: {change-name}

Design rationale: The index-based hierarchy solves discoverability — agents and humans can navigate from top-level down to any requirement without scanning folders. The Design Decisions section captures durable "why" context (present-tense design intent, including rejected alternatives), so developers don't need to dig through archived changes to understand architectural choices. Change traceability lives in the per-folder generated log.md (the dated what) and in (change-id) / *Introduced by* citations — not in a per-file changelog table, and never in transition narration in the body. Domain indexes are a pure function of content (file names + descriptions + structure) — they carry no dates, so they are branch-independent and idempotent; recency-at-a-glance lives in each folder's log.md, generated by fab docs-index docs/memory.

Initial Memory (created by /fab-setup)

A fresh project starts with a single index file. The first /fab-continue (hydrate) populates domains and files:

docs/memory/
└── index.md
# Documentation Index

> Source of truth for system behavior and design. Updated by `/fab-continue` (hydrate) hydration.

<!-- No domains yet. Hydrate creates domain folders; `fab docs-index docs/memory` populates this table. -->

| Domain | Description |
|--------|-------------|

Hydration Rules

When /fab-continue (hydrate) hydrates plan.md ## Requirements into memory:

  1. New memory file: If the requirements reference a file that doesn't exist yet, create it from the individual memory file template (including the FKF frontmatter — type: memory + a curated, change-id-free description:) and place it under the domain folder, writing the body in present-truth style (FKF §3.3). If the domain doesn't exist, create the domain folder and a domain index.md carrying the domain's description: frontmatter.
  2. Existing memory file (merge as current truth): Compare the plan's ## Requirements against the current file to determine what's new, changed, or removed, then rewrite the affected section to state current truth — superseded statements are removed, not narrated as a change-keyed delta (FKF §3.3). Headings carry no change-ids (a heading names its topic, never a change); any why / rejected alternative goes into a four-field ## Design Decisions entry (Decision / Why / Rejected / Introduced by), never inline narration or a - **{change-id} — …** changelog bullet. Minimize edits to unchanged sections. Keep the description: frontmatter accurate and change-id-free, and re-check it after any body edit (§3.2).
  3. Post-hydrate self-check (before regen): Re-read every file touched this run and strip any transition phrasing / change-keyed delta paragraph / change-id heading reflexively introduced; confirm each touched description: still routes. A self-review of this run's own writes, not a corpus sweep.
  4. Index updates: Run fab docs-index docs/memory — it regenerates the root (domains-only) and every domain index deterministically from folder contents + frontmatter. Never hand-edit index rows; the command is the single writer.
  5. What-changed summary (not a changelog row): Record the one-line what-changed once via fab status set-summary {change} "<summary>" — the C-lite summary: source field (FKF §6.3) that fab docs-index docs/memory joins with git history to generate the per-folder log.md. Memory files carry no ## Changelog table (§3.3).
  6. Archive index: Maintain fab/changes/archive/index.md listing all completed changes (most-recent-first). Each entry includes the change folder name and a one-line description. /fab-archive (via fab change archive) prepends the entry when moving a change into the date-bucketed archive.