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.
| State | Meaning | Used by |
|---|---|---|
pending | Not yet started | All stages (template seed; intake only transiently at creation — /fab-new starts it immediately) |
active | Currently being worked on | All stages |
ready | Artifact exists, open for refinement before finish | intake, apply, review, hydrate (not ship/review-pr — advance is rejected there) |
done | Completed successfully | All stages |
skipped | Intentionally bypassed (cascades downstream) | All stages except intake |
failed | Completed with failures requiring rework | review, 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:
idis the immutable 4-char change ID — the canonical short reference accepted by everyfabcommand.created_byis write-once — set at change creation time by/fab-new, never modified afterward. Auto-detected by a three-step ladder ininternal/change.detectCreatedBy: the GitHub login fromgh api user --jq .loginfirst, thengit 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_typeclassifies the change (PR type,expected_mindecision counts — see Change Types). Defaults tofeat; inferred from intake content byfab status refresh(self-healed at the transition seams;fab status set-change-typefor explicit overrides).issues/prsare append-only ID/URL arrays maintained byfab status add-issue/add-pr(/git-prrecords the PR URL on ship).- The current stage is derived from the
progressmap — the entry markedactiveis the current stage. All skills read this first. failedis set on review (by/fab-continuereview or the ff/fff rework loop) and on review-pr (by/git-pr-review). The entry remainsfailedso/fab-statusshows the failure;startis the failed→active recovery event.plan.task_count,plan.acceptance_count, andplan.acceptance_completedare recomputed byfab 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## Acceptancecheckboxes in place. The legacyset-checklistCLI is removed — useset-acceptance(fab status set-acceptance <change> <field> <value>) for explicit overrides.confidenceblock initializes to zero counts and score 0.0 — a new change has no assessed confidence. Computed byfab score --stage intake(via/fab-newand/fab-draft— both through the shared_intakeStep 7 — plus/fab-clarify, andfab status refreshself-healed at the transition seams); intake scoring is authoritative. When SRAD dimensions are recorded (fab status set-confidence-fuzzy), the block also carriesfuzzy: trueand adimensions:map (signal/reversibility/competence/disambiguation).stage_metricsis populated by thefab statusCLI as stages progress — tracksstarted_at,completed_at,driver, anditerationsper stage (/git-pr-reviewadditionally writesreview-prphase/reviewersub-state).true_impactis written lazily by the apply-finish, hydrate-finish, and ship-finish hooks (line counts fromfab 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.summaryis the per-change one-line log summary — the FKF C-lite source linefab docs-index docs/memoryjoins with git history to generatelog.md(see fkf.md §6.3). Optional (yaml:"summary,omitempty", modeled onchange_type_source): an empty/absent summary serializes to nothing and degrades gracefully (the generator falls back to the change slug). Written viafab status set-summary <change> <text>/ read viaget-summary— the conflict-free write path (each change touches only its own.status.yaml). The template seedssummary: ""to document the field; no stage auto-populates it (authoring wiring is a later FKF change).last_updatedis 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.md → tasks.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_categoriesfromconfig.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/memoryregenerates the root, every domain index, AND every sub-domain index deterministically from folder contents (each file'sdescription:frontmatter + H1). The render is content-only — the indexes carry no dates, because agit logprojection is HEAD/branch-relative and so never idempotent; dated recency lives in the per-folderlog.mdinstead. 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-runningfab docs-index docs/memorypost-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.mddifferences 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 anddocs-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/memorywarns. - ~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 advisoryfab docs-index docs/memorywarning (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-memoryskill's job (propose-then-apply);fab docs-index docs/memoryonly detects and warns.Blocking (distinct from the advisory bounds above):
fab docs-index docs/memoryalso detects a blocking class that makes--checkfail 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 adescription:value that fails quote-stripping — the glued-fence corruption), a registry-gated change-id indescription:(the FKF §3.2 ban, now enforced — descriptions are routing signals; citations belong in the body), and a gross over-capdescription:(> 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:
- 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-freedescription:) 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 domainindex.mdcarrying the domain'sdescription:frontmatter. - Existing memory file (merge as current truth): Compare the plan's
## Requirementsagainst 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 Decisionsentry (Decision / Why / Rejected / Introduced by), never inline narration or a- **{change-id} — …**changelog bullet. Minimize edits to unchanged sections. Keep thedescription:frontmatter accurate and change-id-free, and re-check it after any body edit (§3.2). - 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. - 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. - What-changed summary (not a changelog row): Record the one-line what-changed once via
fab status set-summary {change} "<summary>"— the C-litesummary:source field (FKF §6.3) thatfab docs-index docs/memoryjoins with git history to generate the per-folderlog.md. Memory files carry no## Changelogtable (§3.3). - Archive index: Maintain
fab/changes/archive/index.mdlisting all completed changes (most-recent-first). Each entry includes the change folder name and a one-line description./fab-archive(viafab change archive) prepends the entry when moving a change into the date-bucketed archive.