Templates
August 24, 2026 · View on GitHub
Domain: memory-docs
Overview
Fab ships artifact templates in $(fab kit-path)/templates/ that skills fill with concrete content. Each template is a markdown scaffold with guidance comments (<!-- -->) that instruct the agent — comments are not preserved in output. This doc covers the three artifact templates (intake, plan, memory) and the memory file format used in docs/memory/. The memory file format is itself a shipped template (templates/memory.md, the third alongside intake/plan) (2fm8), read on demand by the doc skills via $(fab kit-path)/templates/memory.md the same way _generation.md reads the intake/plan templates. The plan carries the requirement discipline as a ## Requirements section. None of the artifact templates carry a **Status**: header line; .status.yaml is the state of record. The templates' guidance comments use plan/apply-entry vocabulary throughout: the intake is "the primary input for plan generation (apply entry)", and its Assumptions state-transfer comment names "the apply-entry agent (which co-generates plan.md)".
Requirements
intake.md
The intake captures intent, scope, approach, and open questions. Structure:
- Origin — How the change was initiated: description text, interaction mode (one-shot vs. conversational), key decisions from the conversation. Provides traceability for how the intake was developed
- Why — Motivation, 1-3 sentences
- What Changes — Specific capabilities added, modified, or removed
- Affected Memory — Flat list of memory files affected by this change, each with an inline marker:
(new),(modify), or(remove). Kebab-case identifiers matchingdocs/memory/paths. An entry is either flat —{domain}/{file-name}— or, when a domain has been split into sub-domains, sub-domained —{domain}/{sub-domain}/{file-name}(the flat form stays valid for un-split domains). The sub-domained form is what drives the up-to-3-hop selective-load walk (see context-loading.md) - Impact — Affected code areas, APIs, dependencies
- Open Questions — Plain list of questions the agent couldn't resolve from context. SRAD handles prioritization at plan generation (apply entry) — no explicit blocking/deferred labels needed
plan.md
The plan is the single apply-stage artifact, containing the requirement discipline, the implementation task list, and the declarative acceptance criteria. Co-located with other change artifacts at the change root (fab/changes/{name}/plan.md). Generated by the apply skill at entry (Plan Generation Procedure in _generation.md), before any task is executed; skipped on resume when plan.md already exists.
Three heading-keyed sections form the stable parser contract (## Requirements, ## Tasks, ## Acceptance):
## Requirements— the requirement discipline absorbed from the formerspec.md(j6cs). RFC 2119 statements (MUST/SHALL/SHOULD/MAY) with stableR#IDs, each with at least one GIVEN/WHEN/THEN scenario, organized by domain section when the change touches multiple domains. Generated FIRST in the apply-entry pass, from the intake-derived design. Optional subsections (added only when needed, omitted entirely otherwise): Non-Goals (- {what} — {reason}), Design Decisions (each entry in the four-field shape — Decision / Why / Rejected / Introduced by — matching the memory## Design Decisionsentry shape, FKF §3.3, so hydrate's pattern capture lifts a plan DD entry into memory DD without reshaping), Deprecated Requirements (Reason + Migration fields, only when removing existing requirements). The section reads as a straightforward requirements document with no delta markers — the agent infers new-vs-changed by comparing against memory files during hydrate. It MUST NOT contain[NEEDS CLARIFICATION]markers (an intake-only construct); under-specified points are recorded as graded SRAD assumptions in## Assumptionsinstead. The plan's trailing## Assumptionstable records three grades only (Certain/Confident/Tentative) — Unresolved is an intake-only construct (apply decides-and-records, it never leaves a decision Unresolved);_preamble.md's "all four grades" rule is correspondingly scoped to intake artifacts (uliv).## Tasks— consumed by apply. Imperative work items in phase order:- Task format:
- [ ] {ID} [{markers}] {Description with file paths} - IDs: Sequential (T001, T002, ...)
- Markers:
[P]for parallelizable tasks (different files, no dependencies) - Phases: Setup → Core Implementation → Integration & Edge Cases → Polish
- Optional
## Execution Ordersubsection MAY follow## Tasksfor non-obvious dependencies
- Task format:
## Acceptance— consumed by review. Declarative acceptance criteria. Categories:- Functional Completeness — Every spec requirement has working implementation
- Behavioral Correctness — Changed requirements behave as specified
- Removal Verification — Deprecated requirements are actually gone
- Scenario Coverage — Key scenarios exercised
- Edge Cases & Error Handling — Error states, boundary conditions
- Code Quality — Always included. Two baseline items (pattern consistency, no unnecessary duplication) when no
code_qualityconfig; expanded with per-principle and per-anti_pattern items when config exists - Security — Only if the change has security surface
Acceptance items use A-{NNN} IDs. All acceptance items MUST pass before hydrate. Items not applicable are marked - [x] A-NNN **N/A**: {reason}. Project-specific categories from config.yaml's checklist.extra_categories are added to the defaults.
Traceability annotations are REQUIRED (j6cs): each ## Tasks item carries a <!-- R# --> trace annotation naming the requirement it implements; each requirement-derived ## Acceptance item names its requirement (e.g., A-001 R2: {outcome}). Non-requirement-derived categories are exempt (uliv): Code Quality and checklist.extra_categories items carry no R# reference — they use - [ ] A-{NNN}: {outcome}, optionally with a label before the outcome (A-007 Pattern consistency: ...) — because they assess cross-cutting properties of the diff, not a specific requirement (the unscoped mandate was violated by the template's own A-007/A-008 examples). This R# → T# → test → A# chain lets the autonomous apply↔review loop localize a failing requirement-derived acceptance item back to its requirement.
The third optional parser-contract section is ## Deletion Candidates — appended by the review sub-agent (Validation Step 8 in _review.md), NOT by apply or by the template scaffold. The section is review-owned and may be read by hydrate informationally. When present, it lists candidates as `{file:line or symbol}` — {one-line justification}; when no candidates are found, it contains the literal None — this change adds new functionality without making existing code redundant. Placement: appended below ## Notes (or end of file if ## Notes is absent). Replaced (not duplicated) on rework cycles. Omitted entirely (not even "None") when change_type is in the shared parsimony/deletion-candidate skip list [docs, chore, ci]. Distinct from ## Acceptance > ### Removal Verification — that section verifies planned removals declared in plan.md's ## Requirements > ### Deprecated Requirements; ## Deletion Candidates records discovered (unplanned) removal opportunities the apply agent missed. The plan.md template documents the section in a guidance comment but does NOT scaffold a placeholder section (it is review-generated, lazily, and only when applicable).
Phases execute sequentially. Within a phase, [P] tasks MAY execute in parallel. Each task SHOULD be completable in one focused session and MUST include exact file paths. For larger changes spanning multiple user stories, Phase 2 SHOULD be split into per-story sub-phases (Phase 2a, 2b, etc.). Phase/category subheadings under ## Requirements, ## Tasks, and ## Acceptance are presentational — they MAY vary per change. The three ## parent headings are what the pipeline parses (apply reads ## Tasks, review reads ## Requirements + ## Acceptance).
Generation is contextual — the unified Plan Generation Procedure reads intake.md, emits ## Requirements from the intake-derived design, then walks those requirements once emitting paired Task + Acceptance entries. Design-decision and quality inputs come from the project constitution (quality standards) and config.yaml code_quality section (principles and anti-patterns). The co-generation invariant (single skill call, single context window) is the alignment guarantee; REQUIRED trace annotations (<!-- R# -->/R#) make the chain explicit.
.status.yaml (Initial State)
The .status.yaml template initializes a new change with the following structure:
- No
stage:field — current stage is derived from theactiveorreadyentry in the progress map - All stages start as
pendingin the template —fab change newstarts intake asactivewith properstage_metricstracking./fab-newthen advances intake toreadyafter generating the artifact. issues: []— placed afterchange_type. Array of external tracker IDs (e.g.,["DEV-123"]). Managed exclusively viafab status add-issue/get-issues. Populated by/fab-newwhen created from a tracker ticket.
Initial progress map (template) — 6 stages:
progress:
intake: pending
apply: pending
review: pending
hydrate: pending
ship: pending
review-pr: pending
Neither a progress.tasks nor a progress.spec key is present — plan generation and requirement capture are apply-internal sub-steps, not separate stages.
The plan block initializes to:
plan:
generated: false
task_count: 0
acceptance_count: 0
acceptance_completed: 0
Counts are recomputed by fab status refresh/internal/refresh.Refresh, self-healed at the transition seams (fab status advance/finish, fab preflight), which parses plan.md section-by-section (heading-bounded count of - [ ] and - [x] items between ## Tasks/## Acceptance and the next ## heading). It is defensive — missing sections do not zero out valid values.
The confidence block initializes to zero counts and score 0.0 — a new change has no assessed confidence. The score is computed by fab score from intake.md (the sole scoring source) (j6cs), persisted by /fab-new and /fab-draft (both via the shared _intake Step 7) and recomputed by /fab-clarify. The template carries no indicative key.
See change-lifecycle.md for the full .status.yaml field reference and state vocabulary.
Skill Frontmatter
Skill files in $(fab kit-path)/skills/ use YAML frontmatter with these fields:
name(required) — skill identifier, used for symlink naming and invocationdescription(required) — short description shown in skill listings
helpers: and the One-Shared-Helper-Per-Phase Decomposition
A skill MAY declare additional helper files via a helpers: frontmatter list. The mechanism, the allowed-values allowlist, and the per-skill mapping are authoritatively documented in _shared/context-loading.md § Skill Helper Declaration — not duplicated here. What matters for skill-file structure is the decomposition principle that mechanism enables: the workflow factors into one shared internal helper per pipeline phase, each a shared body parameterized by call-site-specific knobs, with call-site tails staying in the consumer files:
| Phase | Helper | Knob(s) | Consumers |
|---|---|---|---|
| artifact mechanics | _generation | — | fab-new, fab-draft, fab-continue, fab-ff, fab-fff, fab-adopt |
| review mechanics | _review | {mode} | fab-continue, fab-ff, fab-fff, fab-adopt |
| pre-intake orchestration | _intake | {questioning-mode} | fab-new, fab-draft, fab-proceed |
| post-intake orchestration | _pipeline | {driver}, {terminal} | fab-ff, fab-fff, fab-adopt |
The symmetry is complete (3xaj): _intake (pre-boundary intake creation) is the counterpart to _pipeline (post-boundary orchestration), pivoting on the single context-bearing boundary — intake. Each helper file follows the established internal-helper frontmatter shape (user-invocable: false, disable-model-invocation: true, metadata: internal: true) and is a flat src/kit/skills/_*.md canonical source (the directory-per-skill _{name}/SKILL.md form is the deployed copy fab sync writes under .claude/skills/). The internal helpers carry no helpers: of their own (consumer-declared model). See pipeline/planning-skills.md § The _intake Shared Create-Intake Procedure for the full decomposition.
Memory File Format (docs/memory/)
Memory files are the source of truth for system behavior and design decisions. The conventional shape is a shipped canonical template (2fm8) at src/kit/templates/memory.md — the third artifact template alongside intake.md/plan.md. The doc skills (/fab-continue hydrate, all three /docs-hydrate-memory modes) read it on demand via $(fab kit-path)/templates/memory.md rather than inlining the shape — the exact $(fab kit-path)/templates/intake.md read pattern _generation.md already uses, collapsing the formerly duplicated shape to one source of truth (FKF's whole point). The template carries the FKF frontmatter (type: memory + description: placeholder) and the Overview/Requirements(+Scenario)/Design Decisions skeleton, with no ## Changelog and a bundle-relative cross-link example. Its guidance comments cite $(fab kit-path)/reference/fkf.md §3.3 and spell out the normative body-style rules a writer must honor at file-creation time: present-truth prose (no transition narration, no superseded behavior), headings carry no change-ids, no operational TODOs (follow-ups belong in the backlog or change folder), and rationale into a four-field ## Design Decisions entry (Decision / Why / Rejected / Introduced by) with the changelog-bullet shape (- **{change-id} — retired X**) banned inside it. The conventional headings themselves stay SHOULD-where-applicable, not MUST. Structure:
type: memory+description:frontmatter — a leading YAML frontmatter block above the# H1carrying the two required FKF fields (fkf.md §2 items 1–2):--- type: memory description: "One-line summary used by the generated domain-index row." ---type: memory(FKF §3.1) — the constant FKF type stamped on every memory (concept) file. It is machine-stamped by every memory writer, never hand-curated. The doc skills author it onto every new/backfilled memory file (8fr5) —/fab-continuehydrate and all three/docs-hydrate-memorymodes (ingest authors it, generate's template carries it, backfill stamps it body-preservingly).fab memory-indexprovides only the preserve-when-present round-trip (keepstype: memoryon a file it round-trips, never strips it) (bmzo); it never bulk-stamps. A pre-existing file lackingtype: memoryis benign (FKF consumers degrade gracefully) and is stamped by the next writer that touches itdescription:(FKF §3.2) — a curated one-line summary, a single-line frontmatter scalar with a 500-character soft cap (the unit is characters/runes, measured on the value after quote-stripping). It is a routing signal for the always-load layer, not a summary of record — detail (requirements, design decisions, prose) belongs in the file BODY (## Overview/## Requirements/## Design Decisions), never in the description. It MUST be free of change-ids — neither a trailing— xu0k-style suffix nor a(d9rs)-style citation (provenance citations belong in the body, §3.3). Load-bearing: the generated domain index reads each row's Description from this field (auto-deriving it from the H1/Overview is lossy — see the design decision below). Authored and kept accurate by every memory writer (hydrate,/docs-hydrate-memory,docs-reorg-memory). The template'sdescription:placeholder guidance states the cap. The cap enforces in two tiers:fab memory-indexemits a non-fatal advisory stderr warning for a description in the 501–1000 range (a trim nag that never fails--check), and BLOCKS — failsfab memory-index --check— on a gross over-cap description strictly longer than 1000 characters (2× the soft cap). A registry-gated change-id in the description is likewise a blocking finding — the §3.2 ban is enforced (see Memory Tree Shape below)
- Overview — 1-2 sentences describing what the file covers
- Requirements — Using RFC 2119 keywords, with GIVEN/WHEN/THEN scenarios
- Design Decisions — Durable architectural decisions extracted from specs during hydration. Each includes decision, rationale, rejected alternatives, and a
*Introduced by*: {change-name}provenance field - Body style: present-truth (§3.3, normative) — the body describes what IS, in present tense, as a statement of record — not an accumulated log of edits. No transition narration: never "renamed X→Y in {id}", "this inverts/supersedes {id}'s claim", or "was
old.value". Superseded behavior is never described in the body — the previous state belongs to the per-folder generatedlog.md(§6), git history, and archived change folders; consolidating a section to current truth (dropping the superseded description) is the correct edit, not a loss. Provenance is citation-only, and headings carry none — the sole permitted body provenance is a trailing(change-id)citation and the*Introduced by*: {change-name}field on a Design Decision; a citation marks where a current fact came from, it does not narrate a transition. A heading names its topic, never a change:## Dispatch States, never### Dispatch States (xu0k)or## xu0k — dispatch states— change-ids appear only as trailing citations in body text. No operational TODOs — follow-up work items (TODOs, "still needs X", next-step checklists) are never memory-body content; they belong in the project backlog (fab/backlog.md) or the originating change folder (a body states what IS, not what remains to be done). Rationale survives distillation — as Design Decisions entries — "don't re-break this" content lives in Design Decisions'Why/Rejectedas durable present-tense design intent (a rejected alternative is a design fact, not narration); token savings come from dropping narration, never rationale. Any why, rejected alternative, or constraint explanation goes into a## Design Decisionsentry in the four-field shape (Decision / Why / Rejected / Introduced by), never as inline narration in Overview/Requirements prose; the changelog-bullet shape is banned inside## Design Decisions—- **{change-id} — retired X**is change history (log.md's job, §6), not a design decision, and a DD entry heading is a decision title, never a change-id. The sole tombstone carve-out is_shared/removed-domains.md, whose body is removal records (a citation-carrying tombstone ledger, not transition narration — protected by the--checktier-2 tombstone-loss guard and thedocs-reorg-memorycarve-out that authors it) - No
## Changelogsection — FKF removes the per-file changelog table (§3.3); change history lives in the generated per-folderlog.md(see Generatedlog.mdbelow). The doc skills write no## Changelogsections (8fr5) —/fab-continuehydrate and/docs-hydrate-memoryrecord the one-line what-changed once viafab status set-summary {change} "..."(the C-litesummary:source field, §6.3, thatfab memory-indexjoins with git history to generatelog.md). The pre-FKF per-file## Changelogtables are stripped (oovf); their rows are preserved verbatim as per-folderlog.seed.mdseed entries thatfab memory-indexmerges beneath the git projection (see Generatedlog.mdbelow). Memory↔memory cross-links written into a file body use the bundle-relative/...form (§7); links out of the bundle stay repo-relative/absolute-URL
Index Hierarchy (generated — never hand-edited)
All index levels (and the per-folder log.md documented below) (bmzo) are generated artifacts written by fab memory-index (see _cli-fab.md → ## fab memory-index); agents never hand-edit them. The index render is a pure function of folder contents + each file's description: frontmatter — content-only, with no git dates (a git log projection is HEAD/branch-relative and so not idempotent) — mirroring the internal/prmeta Render/Gather pattern, so the output is byte-stable / idempotent. The batched git log --date=short --name-status -- docs/memory pass feeds log.md only — its per-commit verb/history; the index does not consume it. (The log.md render reuses that batched git pass but is not a pure projection of it — log.md generation is freeze-on-write / append-only (tayp); see Generated log.md below.)
- Top-level (
docs/memory/index.md): domains-only —| Domain | Description |. There is no inlined per-file "Memory Files" / file-list column (tciy) — a hand roster silently drifts; each domain row's Description is read from that domainindex.md'sdescription:frontmatter (round-tripped by the generator). The root index is also prepended (bmzo) with the FKF version frontmatter block---\nfkf_version: "0.1"\n---(FKF §8) — the onlyindex.mdpermitted frontmatter beyond the generator's output; no domain/sub-domain index carries it. This is a byte-stable change to the root output: every regen writes the block, and a tree lacking it is benign drift (tier 1) on--check, never destructive loss - Domain-level (
docs/memory/{domain}/index.md): file rows —| File | Description |. Description comes from each topic file'sdescription:frontmatter; the index carries no dates (content-only (ugde) — recency-at-a-glance lives in the per-folderlog.md). When the domain folder contains sub-domains, the domain index also carries a## Sub-Domainstable (| Sub-Domain | Description |, linking to{sub-domain}/index.md) — emitted only when sub-domains exist, so a sub-domain-free domain index carries no## Sub-Domainstable - Sub-domain-level (
docs/memory/{domain}/{sub-domain}/index.md): generated for every{domain}/{sub-domain}/directory holding ≥1 non-index.md, with the same| File | Description |file-row contract as a domain index (the file-row render is tier-agnostic — relative[file](file.md)links are correct from a sub-domain index too). This is the third addressing tier; the External{domain}/{sub-domain}/{file}form addresses files under it (sx7a) - All links in
index.mdSHALL be relative (the index-link convention is unchanged by FKF; the bundle-relative/...rule of FKF §7 governs memory↔memory cross-links inside topic-file bodies andlog.mdentries, not generated index rows)
Generated log.md (FKF C-lite)
Alongside the index tiers, fab memory-index emits a per-folder log.md for every domain and sub-domain folder with attributable git history (FKF §6) (bmzo). Like index.md, log.md is a single-writer, byte-stable generated artifact — agents never hand-edit it. Its generation is freeze-on-write (tayp) (see Freeze-on-Write below): the existing log.md is authoritative and write-once, so regeneration is append-only rather than a from-scratch re-projection of live git. It is the C-lite change log that FKF designates to replace the per-file ## Changelog tables: the doc skills write no changelog rows (they record the what-changed via fab status set-summary instead — see the No-## Changelog bullet above and Hydration Rule 5) (8fr5), and the pre-FKF per-file tables are stripped (oovf). The cutover's pre-FKF history is preserved via a per-folder log.seed.md seed input (FKF §6 seed-merge): a curated sidecar in the §6.2 entry format that fab memory-index parses and merges beneath the git-projected entries (de-duplicated, idempotent), so the rich pre-FKF rows survive every regen under their own authored dates without a live .status.yaml summary: to project from. The seed is read-only input — never the generated output — so the single-writer discipline holds.
Generated shape (FKF §6.2):
# Log — {Title}
<!-- Generated by `fab memory-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)
- C-lite join. Each entry joins two sources, neither hand-edited: git history (the when / which file / change-id, from the batched
git log --name-statuspass —log.mdis its sole consumer (ugde)) with each change's.status.yamlsummary:field (the what — set viafab status set-summary, written once into the change's own file so it has zero conflict surface). This kills the same-day-changelog-collision that per-file## Changelogtables suffered. - Format.
# Log — {Title}H1 + theDo not hand-editgenerated comment, then entries date-grouped, newest date first (## YYYY-MM-DD). Each entry is- {**Verb** }[base](/{domain}[/{sub}]/base.md) — {summary-or-slug} ({change-id}): an optional leading bold verb (**Creation**/**Deprecation**/**Update**, derived from the commit's git name-status A/D/M·R·C, omitted when ambiguous), a bundle-relative link (FKF §7 — beginning with/, resolved fromdocs/memory/), the change'ssummary(or the change slug when no summary exists — §6.3 graceful fallback), and the(change-id)token. One line per change per file. - change-id join (registry-gated, graceful degradation). The id is recovered from the commit message and gated against the change registry (
fab/changes/*+archive/**give the canonical(id, folder)set). The merge-commit branch token (Merge pull request #N from owner/<folder>) is the only recoverable shape and is effective only on legacy true-merge history; against this repo's squash-merged history (subjectsfeat: … (#NNN)) it recovers ≈0 ids, so most entries degrade gracefully — the(change-id)is omitted and the descriptive line falls back to the commit subject (still a conflict-free git projection), or to—when empty. The format physically exists and self-heals as FKF-era changes land curated summaries on true-attributable commits. - Empty-folder skip. A folder with no attributable commits is skipped — no empty
log.mdis written (the target set equals "folders with history" — or with alog.seed.md). - Freeze-on-write (tayp).
log.mdis not re-projected from scratch each run. A pure projection of live git is non-deterministic — squash-merge rewrites commit subjects/counts and branch-delete makes the originals unreachable — so a from-scratch regen churns every contributor'slog.md. Instead the existinglog.mdis authoritative and write-once:fab memory-indexreads it back (parsing the §6.2 render viaparseLog, the same grammarlog.seed.mduses), treats those entries as immutable (never reworded / re-dated / dropped), and appends only newly-discovered entries — keyed on(file-base, change-id)(NOT the commit hash; squash + branch-delete makes the hash unreachable, the change-id survives in the folder name + registry). A new unattributable commit (a migration, direct-mainedit, or squash that dropped the branch token) is frozen, not re-projected — it is NOT added after first write (accepted tradeoff: tooling commits leave no log trace), while frozen unattributable lines already present stay verbatim. Bootstrap (the first run on an empty/absentlog.md) is not a special mode — it projects-and-freezes through the same code path; there is no--first-generationflag.fab memory-index --rebuildis the destructive escape hatch: it discards the frozen state and re-projects everylog.mdfrom current git (the pre-freeze behavior, opt-in — it re-projects unattributable commits too), for a corrupted log or a deliberate re-baseline. Thelog.seed.mdseed-merge is preserved beneath the projection at first write /--rebuild. Existing projects re-baseline onto this model via the one-time2.5.5-to-2.6.0migration (--rebuild+ commit; see distribution/migrations.md). --checktier under freeze-on-write (tayp). Alllog.md--checkdrift stays benign drift (tier 1), never destructive loss — alog.mdis a git projection, not a row-table index, so the description/tombstone/grouping detectors are skipped for it (theIsLogguard), and there is no new tier-2 category (the OQ4 decision (bmzo) holds). The comparison basis:--checkcompares the committedlog.mdagainst the freeze-on-write merge — a from-scratch projection would false-fail on legitimately-frozen squashed-away lines. A committed log that is a valid superset of the merge PASSES (exit 0); a benign FAIL (tier 1) means the merge would append a missing attributable(file-base, change-id)entry (forgot to regenerate-and-commit) or cannot reproduce a hand-edited frozen line (single-writer discipline violated — a clean reword that round-trips through the §6.2 grammar is accepted as the new frozen truth). See pipeline/schemas.md § Freeze-on-Writelog.mdGeneration, fkf.md §6.4, and_cli-fab.md§ fab memory-index for the full contract.
Memory Tree Shape (SHOULD guidance)
To keep folders from becoming maximally wide (the source of central files that churn on every change), shape bounds are advisory — surfaced as non-fatal fab memory-index stderr warnings and the docs-reorg-memory Shape Report, never enforced:
- ~12 topic files per folder (soft upper bound) — over this,
fab memory-indexwarns anddocs-reorg-memoryflags a split candidate. Lower floor ~5 before a sub-domain earns its own index (advisory only — not warned on) - Max depth ≤3 path segments under
docs/memory/({domain}/{sub-domain}/{topic}.md) — equivalently folder depth ≤2 (domain = 1, sub-domain = 2); over this warns. The two countings are reconciled (d9rs): thedocs-reorg-memoryShape Report'sDepthcolumn counts folder levels, so its⚠ over depthfires for any folder deeper than 2 — whose files sit at ≥4 path segments - Introduce a sub-domain only reactively — when a real cluster of ≥8 cohesive files exists in one domain. Never pre-build hierarchy
- Reserved domains
_shared/(cross-cutting) and_unsorted/(staging) are exempt from the width warning
Four additional advisory ⚠ warnings are FKF present-truth debt meters (mxgu), all topic-file-scoped (index.md / log.md / log.seed.md excluded, except _unsorted which is per-folder) and, like the shape bounds, non-fatal — they never affect the --check exit code:
- Narration-marker density — a topic file whose body carries ≥ 5 narration markers warns (
⚠ … has <N> narration markers (threshold: 5) — distillation debt; consider /docs-distill-memory). A marker is a case-insensitive hit of the transition stemsno longer/previously/renamed/supersedOR a registry-gated change-id token in the body that falls outside the two §3.3-sanctioned citation positions — the same false-positive-free registry gating the change-id blocking check andlog.mdattribution use. A trailing(change-id)citation and a change-id on an*Introduced by*:field line do NOT count — they are the provenance distillation KEEPS, so a fully-distilled file clears the flag; a change-id woven into prose still counts (a density signal for narrated ids). The meter is advisory (it never affects the--checkexit code) - Per-file size — a topic file over 400 lines OR 15360 bytes (15KB) warns (either bound;
⚠ … is <N> lines / <M>KB (soft cap: ~400 lines / ~15KB) — consider splitting). The emitted stderr line reports size in kilobytes (bytes / 1024); the raw byte count is available on the--jsonsurface viawarnings[].bytes. Line count useswc -lsemantics (a trailing-newline file is not overcounted). Mega-files are split candidates fordocs-reorg-memory _unsorted/non-empty — adocs/memory/_unsorted/holding ≥ 1 topic file warns (⚠ docs/memory/_unsorted holds <N> staged file(s) — triage into domains (staging should trend to empty)). A presence signal, not a shape bound —_unsortedkeeps its width exemption- Broken bundle-relative links — a
](/...)link target in a topic-file body that does not resolve on disk underdocs/memory/warns (⚠ … links to /path.md — target does not exist). Only/-prefixed (bundle-relative, FKF §7) targets are checked; repo-relative and external links are out of scope. The scan skips both fenced code blocks and inline code spans (a link shown inside code markup is documentation, not a live cross-link). Advisory, matching FKF §7's "consumers MUST tolerate broken links" posture — this is the author-side nag that finds them
The over-500-character description: warning is the fifth advisory kind (a trim nag for the 501–1000 range — over 1000 it escalates to blocking, below). It rides the --check --json warnings[] array as kind description-length (count = the description's rune length), alongside the four debt meters — so the JSON warnings[] carries five kinds (description-length, narration-density, file-size, unsorted-nonempty, broken-link), the machine surface the /docs-distill-memory survey consumes as its canonical signal source instead of an agent-side frontmatter re-check. Its threshold DescriptionLenWarnThreshold = 500 and the four meters' thresholds (DescriptionBlockingLenThreshold = 1000, NarrationMarkerWarnThreshold = 5, FileSizeLineWarnThreshold = 400, FileSizeByteWarnThreshold = 15360) are hardcoded package consts in the shape-bound-const family (like WidthWarnThreshold), not config-overridable.
Blocking Content Class + Never-Hand-Merge Generated Indexes
fab memory-index behaviors distinct from the advisory shape/debt warnings above defend the generated index/log.md against silent corruption, bad routing signals, and merge toil:
- Four signatures form a BLOCKING content class (fails
--checkindependent of drift). Because the index reads each file'sdescription:verbatim, an offending description silently propagates into the generated row — and the drift check alone can't catch it (committed byte-identical to regenerated).fab memory-indextreats four signatures on a topic file or domain/sub-domainindex.mdstub as blocking: (a) an unclosed frontmatter block (opens---, no subsequent standalone---); (b) adescription:value that starts with a quote but fails quote-stripping (an unterminated quote, e.g. a closing fence glued onto the value as"…text…"---); (c) a registry-gated change-id indescription:(a fullYYMMDD-XXXX-slugfolder-name token whose folder is registered, or a bare registered 4-char id — the §3.2 ban is enforced;code/yaml/any unregistered 4-char word never trips it); (d) a gross over-capdescription:strictly longer than 1000 characters (2× the soft cap). It makesfab memory-index --checkfail (exit ≥ 1) independent of index drift, enumerating the offending file(s) with a fix-the-file remediation distinct from the→ /docs-reorg-memorypointer — repair the frontmatter, or trim/de-cite thedescription:, not reorg. None is a--checkdestructive-loss (tier-2) category: tier 2 still exits 2 and still fires the hydrate/reorg refuse-before-regen guards, blocking findings floor the exit at 1 regardless of tier (tier 2 wins when both co-occur), and the--jsonlosses[].categoryenum is unchanged (an additivemalformedarray carries all four blocking kinds —malformed-fence/malformed-description/description-change-id/description-over-cap; the JSON key staysmalformedfor consumer compatibility even though the internal predicate isIsBlocking()). This is the corruption/bad-signal-blocks half of the asymmetry with the advisory nags above. Schema/exit-code detail: pipeline/schemas.md § Blocking Content Class. - Never hand-merge a generated
index.md/log.md. The index andlog.mdare single-writer, byte-stable generated artifacts (FKF §5/§6). On any merge conflict indocs/memory/**/index.mdorlog.md, the correct resolution is mechanical, never a hand-merge: (1) resolve the conflicts in the topic files (and.status.yaml/log.seed.mdseed inputs) only, (2) re-runfab memory-index, (3) take its output wholesale as the resolution —fab memory-index --checkat review-pr backstops staleness. Hand-merging a generated index is exactly the failure mode that carries a corrupted row from one branch onto another. A non-normative.gitattributesmerge-driver recipe (a driver that takes either side and defers to regeneration) is documented as an aside in fkf.md §5 — documentation only, not auto-installed. The operational pointers live in the skills where agents meet merges:/git-pr(its ship-time index-refresh sub-step) and/git-pr-review(its PR-feedback conflict path) — see pipeline/execution-skills.md.
The file-moving rebalance (splitting an over-wide domain into sub-domains, rewriting the links the move breaks) is the docs-reorg-memory skill's job (propose-then-apply). On approval the skill moves files, rewrites the links the move breaks, authors description: frontmatter on new files, runs fab memory-index, and enforces a no-dangling-link guard. The rebalancer is FKF-aware (R11): (1) every moved file keeps its FKF frontmatter (type: memory + description:) — the move never strips or regenerates it; (2) the links it rewrites are bundle-relative (/{domain}/{file}.md, FKF §7) — a same-folder move does not break the link (a bundle-relative link is rooted at docs/memory/ not at the file's location), so only links whose target changed domain/sub-domain (the path after the leading /) need rewriting; (3) the split/merge flow writes each new sub-domain's description:-only stub before fab memory-index, per the Index Ownership model below. The apply path follows the Index Ownership model (see hydrate § Index Ownership Model): each new sub-domain's description:-only index.md stub is created BEFORE fab memory-index runs, and the no-dangling-link hard block carries an abort/rollback escape: a dangling link that cannot be rewritten (target genuinely gone, or ambiguous) aborts that migration — roll back its moves and link rewrites, re-run fab memory-index, report the rollback, continue with the remaining approved migrations — instead of blocking indefinitely. fab memory-index detects and warns (across the recursive sub-domain tree) and regenerates the sub-domain index tier; the moves and link rewrites are skill-driven (Pure Prompt Play). The sibling docs-reorg-specs rebalancer is the negative of this FKF-awareness: it carries a guard never to stamp FKF frontmatter (type:/description:) on moved spec files — specs stay human-curated and frontmatter-free (Constitution VI, FKF §9) — and the optional specs-index generator borrow (FKF §9) is declined (oovf) (specs keep their hand-rewritten index; see specs-index).
The rebalance operates at both folder and file granularity. Alongside the domain-level split-domain / merge-domain / flatten / move Migration Map kinds, reorg owns two file-granularity kinds:
split-file— fan one over-size, multi-topic file into ≥2 topic files in the same domain/sub-domain (the file-granularity parallel ofsplit-domain). A file is a split candidate when it exceeds the size soft cap (mxgu) (~400 lines OR ~15KB, from the Shape Report's file rows sourced fromwarnings[]file-size; older-binary fallback measures during the read-all-files pass), but a split is proposed only when its heading clusters show ≥2 genuine topics — a long-but-cohesive file is reported over-size and left alone (the same reactive soft-SHOULD stance as the folder bounds). Each cluster's blocks move verbatim into a new topic file carryingtype: memory+ a fresh change-id-freedescription:(restyling prose stays/docs-distill-memory's job); the original path is kept for the dominant topic when one exists, else the emptied original is removed. New files target ~300 lines. Link Impact extends tosplit-file: an anchored inbound bundle-relative link (#heading) follows the file its heading moved to; an un-anchored inbound link retargets to the dominant-topic file; ambiguity (no dominant topic AND un-anchored inbound links) takes the abort escape above.merge-file— fold a duplicate-coverage file's unique sections into a canonical sibling and delete the emptied file (the file-granularity parallel ofmerge-domain), via themove-sectionmachinery with the same Link Impact note + no-dangling-link guard; every inbound link that pointed at the emptied file is rewritten to the canonical file.
docs-reorg-memory reorganizes structure — it moves files and rewrites links; it never rewrites body prose to a style. Corpus-style remediation is /docs-distill-memory (see distill): rewriting an existing domain's topic-file bodies + description: frontmatter to the FKF §3.2/§3.3 present-truth style (dropping transition narration, capping over-cap descriptions). It is the body-prose counterpart to this structural rebalancer — the two are complementary passes over the same tree, and neither substitutes for the other. The split-file/merge-file division mirrors this: reorg splits and merges files (structure, verbatim body moves), distill rewrites within a file to a style — and distill's within-file byte-identical dedup is distinct from reorg's cross-file duplicate coverage below.
Duplicate-coverage detection and _unsorted/ triage are two analysis passes reorg runs alongside theme identification, feeding the same report → confirm → apply flow:
- Duplicate coverage — flag the same topic covered in 2+ files (signals: near-identical filenames or
description:frontmatter, the same filename in two domains, heavy heading overlap). Output is a## Duplicate Coveragetable (topic / files / evidence / proposed canonical home), omitted when none is found. Remediation rides the Migration Map: amerge-filerow, or plainmove-sectionrows for partial overlap. The report notes the tie to the open single-sourcing seam audit as a cross-reference, not scope — this pass surfaces cross-file duplication; the audit is a separate effort. _unsorted/triage —_unsorted/keeps its width/depth exemption (never split/merged/flattened), but gains a triage listing: every staged topic file gets a per-file proposal,moveto a named domain (the existing kind — the default) ordeletefor stale ephemera whose content is superseded or recorded elsewhere. Everydeleterequires explicit per-file confirmation (git recoverability bounds the risk); staging should trend to empty. Signal:warnings[]kindunsorted-nonempty; older-binary fallback: a directdocs/memory/_unsorted/folder listing.
Beyond the file-moving rebalance, docs-reorg-memory is also the single front door for migrating a pre-fab-kit, hand-curated tree to the fab-kit convention (5ewp). Its compatibility detection is mechanical (glwc): reorg calls fab memory-index --check --json and consumes its structured output — exit 0/1 = nothing to relocate or backfill (the born-compatible case; the Compatibility report is skipped entirely), exit 2 = the JSON losses[] enumerates each divergence by category (description = a curated description that would regenerate to —; tombstone = a row whose docs/memory/-relative link target is absent on disk, with external/absolute links already excluded by the primitive; grouping = a custom structural heading in the root index.md the domains-only regen would flatten), each carrying path (the index file) and detail (the lost text / dropped link target / flattened heading). This rewires the three prose signals (5ewp) to the Go primitive — single source of truth, no prose re-stating frontmatter.Field semantics / tombstone heuristics / flatten rules. The older-binary fallback (--check loss-tier / --json unavailable) retains the legacy prose detection and warns the user to upgrade fab. Findings surface in a ## Compatibility (pre-fab-kit memory tree detected) block of the approve-before-mutate report (omitted entirely when there are none); tombstone candidates are still user-confirmed before relocation. On approval, reorg orchestrates in strict order — behavior unchanged, now consuming the primitive's output: (1) relocate confirmed tombstones → docs/memory/_shared/removed-domains.md — reorg authors this one mechanical file (description: frontmatter + H1 + verbatim tombstone rows with change IDs preserved; merge-not-duplicate if it exists), bounded to mechanical row movement, explicitly NOT per-file synthesis; (2) dispatch /docs-hydrate-memory's backfill mode as a general-purpose sub-agent (standard subagent context, names the operation, passes no manifest, signals reorg-dispatched so backfill defers regen — see hydrate § Backfill Mode Behavior); (3) rebalance + run fab memory-index once as the single regen for the whole orchestration. The restructure/author seam is preserved: reorg detects + relocates the mechanical row, per-file description synthesis lives in hydrate's backfill. Decline = report findings and stop without mutating (reorg's existing approval posture).
Hydration Rules
When /fab-continue (hydrate) hydrates into memory files:
- New file: Create from the shipped template read on demand via
$(fab kit-path)/templates/memory.md(2fm8) — including the FKF frontmatter (bothtype: memoryand a curateddescription:). If the domain is new, create the domain folder - Existing file (merge as current truth): Compare spec requirements against current file. Rewrite the section that already documents the topic to state current truth — keyed on the topic/section, not the change; superseded statements are removed, not narrated (no "renamed X→Y in {id}", no "was
old.value"), provenance in the body stays citation-only, and headings carry no change-ids (a heading names its topic, never a change) (§3.3). Minimize edits to unchanged sections. Keep thedescription:frontmatter accurate and free of change-ids (and stamptype: memoryif the pre-existing file lacks it); after any body edit, re-check thedescription:still routes — one line, ≤500 chars, change-id-free (§3.2). Memory↔memory cross-links written use the bundle-relative/...form (FKF §7) - Design decisions: Extract durable decisions from spec into a
## Design Decisionsentry in the four-field shape (Decision / Why / Rejected / Introduced by) — any why / rejected alternative / constraint lives here, never as inline narration in Overview/Requirements prose. Skip tactical details. Record provenance in citation form — the*Introduced by*: {change-name}field (or a trailing(change-id)citation), not free-text change-name narration; the changelog-bullet shape (- **{change-id} — retired X**) is banned inside the section (that islog.md's job, §6) (§3.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, and confirm each touched
description:still routes. Scoped to this run's own writes, not a corpus sweep (draining pre-existing debt is/docs-distill-memory's job) - Index + log updates: Run
fab memory-index— it regenerates the root (domains-only) + every domain + every sub-domain index, and the per-folderlog.mdfiles and the root-indexfkf_versionfrontmatter (bmzo). The indexes regenerate deterministically from folder contents + frontmatter (content-only, no dates) (ugde); thelog.mdfiles regenerate append-only under the freeze-on-write model (the existing log is authoritative, only new(file-base, change-id)entries are appended; see Generatedlog.mdabove) (tayp), so hydrating a memory file does not churn unrelatedlog.mdentries. Never hand-edit index rows orlog.md; the command is the single writer of all of them - What-changed summary (not a changelog row): Hydrate appends no per-file
## Changelogrow (8fr5). Instead it records the one-line what-changed once viafab status set-summary {change} "<one-line what-changed>"— the C-litesummary:source field (FKF §6.3) thatfab memory-indexjoins with git history to generate the per-folderlog.md. The summary lives in the change's own.status.yaml(zero conflict surface), authored once at hydrate
Design Decisions
Requirements Without Delta Markers
Decision: plan.md's ## Requirements is a straightforward requirements document with no ADDED/MODIFIED/REMOVED markers. The agent infers deltas by comparing against memory files during hydration.
Why: Reads naturally. No coupling between the requirement format and the hydration mechanism.
Rejected: Explicit delta markers — clutter requirements, require discipline, fragile to editing.
Introduced by: doc/fab-spec/TEMPLATES.md; Updated by: 260601-j6cs-merge-spec-into-apply (the source artifact moved from a standalone spec.md to plan.md's ## Requirements)
Phases by Implementation Concern, Not User Story
Decision: Tasks under plan.md ## Tasks are grouped by concern (setup, core, integration, polish) rather than by user story.
Why: Fab changes tend to be smaller and more focused than greenfield features. Per-concern grouping matches the typical change scope. Larger changes can still split Phase 2 into per-story sub-phases.
Rejected: OpenSpec's flat numbered groups (too minimal for dependency tracking). SpecKit's per-user-story phases (overkill for typical Fab changes).
Introduced by: doc/fab-spec/TEMPLATES.md
Acceptance Validates Implementation, Not Requirement Quality
Decision: The plan.md ## Acceptance section tests "does the code match the requirements?" not "are the requirements well-written?"
Why: Requirement quality is addressed at intake via /fab-clarify and at apply-entry requirement generation. The acceptance section focuses on what matters at review time.
Rejected: SpecKit-style requirement-quality checklist — duplicates planning-stage work.
Introduced by: doc/fab-spec/TEMPLATES.md; Updated by: 260601-j6cs-merge-spec-into-apply (requirements live in plan.md; quality addressed at intake + apply-entry, not a spec stage)
Non-Goals Before Requirements, Design Decisions After
Decision: Within plan.md's ## Requirements section, Non-Goals is placed before domain requirement sections; Design Decisions is placed after them and before Deprecated Requirements.
Why: A reader needs to know what's excluded before reading what's included; rationale makes more sense after seeing the requirements it justifies.
Rejected: Both sections at the end — loses the scoping benefit of Non-Goals appearing early.
Introduced by: 260211-r4w8-spec-template-sections; Updated by: 260601-j6cs-merge-spec-into-apply (these subsections moved from the removed spec.md template into plan.md's ## Requirements)
Plan Co-located at Change Root
Decision: The plan lives at fab/changes/{name}/plan.md alongside other artifacts. Predecessors tasks.md and checklist.md are merged into plan.md (qszh); the legacy files lived at the same level so co-location is preserved.
Why: All change artifacts (intake.md, spec.md, plan.md, .status.yaml) live at the change root. The merger from tasks.md + checklist.md removed the only artifact split that existed at this level.
Rejected: Keep tasks.md + checklist.md separately — drift risk between sibling artifacts (qszh).
Introduced by: 260212-ipoe-checklist-folder-location; Updated by: 260423-qszh-merge-tasks-checklist (merged into single plan.md)
plan.md Replaces tasks.md + checklist.md (Single Apply-Stage Artifact)
Decision: The apply stage produces a single plan.md artifact containing both ## Tasks (consumed by apply) and ## Acceptance (consumed by review). The prior tasks.md + checklist.md pair is gone — neither template ships in the kit, neither is generated by skills, and the tasks stage that gated their generation is removed from the pipeline.
Why: Two separate generation passes over the same spec produced silent drift between sibling artifacts (a requirement might be tracked in tasks.md but missed from checklist.md). Merging into one artifact under one skill call (apply entry) makes drift mechanically impossible — single context window, single LLM, single file. Two heading-keyed sections preserve the imperative-vs-declarative framing that apply and review depend on (apply wants "do X"; review wants "X is done and correct"). Section headings (## Tasks, ## Acceptance) are the stable parser contract; phase/category subheadings underneath are presentational.
Rejected: Keep two files with cross-check step (more ceremony, not less — agents already struggled to keep them in sync). Drop checklist, review reads tasks directly (loses declarative framing). Section markers like <!-- TASKS-START --> (uglier, identical guarantees). Single mixed list with item-type markers (destroys imperative-vs-declarative split).
Introduced by: 260423-qszh-merge-tasks-checklist
A-NNN for New Plans; CHK-NNN Preserved for In-Flight Migrations
Decision: Newly generated plan.md files use A-001, A-002, ... (zero-padded) for acceptance items. The 1.8.0-to-1.9.0 migration preserves CHK-NNN IDs verbatim for in-flight changes — no mid-change ID swap.
Why: A clean prefix for new artifacts signals the new model. Rewriting IDs mid-change risks breaking outstanding rework annotations or open clarifications. Mixed prefix coexistence in fab/changes/ is acceptable because in-flight changes are short-lived (days to weeks) and migrate away naturally as they complete.
Rejected: Rename all CHK-NNN to A-NNN during migration (risks breaking rework references). Keep CHK-NNN for new plans (confuses the new-vs-old boundary; A- better matches the new section name ## Acceptance).
Introduced by: 260423-qszh-merge-tasks-checklist
Acceptance R# Mandate Scoped to Requirement-Derived Categories
Decision: The "each ## Acceptance item names the requirement it accepts" rule applies only to requirement-derived categories (Functional Completeness, Behavioral Correctness, Removal Verification, Scenario Coverage, Edge Cases & Error Handling, Security). Code Quality and checklist.extra_categories items are exempt — format - [ ] A-{NNN}: {outcome} (an optional label may precede the outcome), no R# reference. Both _generation.md (step 4 traceability bullet + step 6) and the plan.md template comments (header TRACEABILITY comment + ## Acceptance section comment) state the split.
Why: Code Quality and extra-category items assess cross-cutting properties of the whole diff (pattern consistency, no duplication, project-configured categories like documentation accuracy), not a specific requirement — the unscoped MUST forced agents to invent fake R# references, and the template's own A-007/A-008 examples violated the rule as written. Scoping the mandate keeps the R# → T# → test → A# chain meaningful where review actually uses it while legalizing the shape every real plan already produced.
Rejected: Requiring a synthetic R# for exempt items — fake traceability is worse than none. Dropping the R# mandate entirely — loses the failing-item-to-requirement localization the autonomous apply↔review loop depends on.
Introduced by: 260611-uliv-skills-staleness-sweep-frontmatter-fixes
Optional Sections Omitted Rather Than Empty
Decision: Non-Goals and Design Decisions are omitted entirely when not needed, rather than included with "N/A" or empty content. Why: Empty sections add noise; omission is cleaner and consistent with how Deprecated Requirements already works. Rejected: Always include with "N/A" — adds boilerplate to simple changes. Introduced by: 260211-r4w8-spec-template-sections
Flat Affected Memory With Inline Markers
Decision: The intake template's Affected Memory section uses a single flat list with (new), (modify), (remove) inline markers instead of three headed subsections.
Why: Most changes touch 1-2 files, leaving empty subsection headings. A flat list is more compact and eliminates structural overhead for the common case.
Rejected: Keep three subsections (New Files / Modified Files / Removed Files) — empty headings add noise for typical changes.
Introduced by: 260213-v4rx-simplify-templates
Generated Memory Index + description: Frontmatter (Domains-Only Root)
Decision: Memory indexes are generated by fab memory-index from a new per-file description: frontmatter field, not hand-maintained — content-only (the index carries no dates) (ugde). The root index is domains-only (the inlined per-file column is dropped); descriptions live co-located in each file's frontmatter.
Why: The hand-maintained per-row index cells (description, and formerly a Last Updated date) were the dominant merge-conflict and drift source — rewritten on nearly every memory edit, and silently wrong (the former stale dates, a root roster listing 18 files when 20+ existed). A generated, byte-stable index removes the hand-edit class entirely. The Last Updated date column is dropped (ugde) because a git log projection is HEAD/branch-relative, so the date half was never idempotent; dated recency lives in the per-folder log.md. The description: is a curated one-liner that cannot be auto-derived without loss (an H1 + first-Overview-sentence extraction breaks tables — hydrate.md's Overview contains literal | pipes — and degrades the always-load routing signal), so it is stored as co-located frontmatter (the Starlight lesson). The root goes domains-only because the root index is near-zero churn and only the domain index carries the volatile file list.
Rejected: Hand-edited index rows (the prior approach — the churn this change exists to kill). Auto-deriving descriptions from H1/Overview (lossy, breaks tables). Keeping the per-file column on the root (re-introduces a wide, drift-prone hand roster). Splitting wide domains first to relocate the hot row (Approach A — only moves the conflict and manufactures a one-time link-rewrite bomb; deferred to the follow-up rebalancer (sx7a)).
Introduced by: 260607-tciy-memory-tree-shape-rebalance; Updated by: 260607-sx7a-reorg-memory-shape-rebalance (the deferred file-moving rebalancer apply path + the sub-domain index tier it generates are now shipped — fab memory-index recurses one level and the parent index gains a conditional ## Sub-Domains table); 260625-ugde-memory-index-drop-date-column (dropped the Last Updated date column — a HEAD/branch-relative git log projection that was never idempotent — so the index is now content-only; dated recency lives in log.md)
SRAD-Driven Open Questions (No BLOCKING/DEFERRED Labels)
Decision: The intake's Open Questions section uses a plain list without explicit priority markers. SRAD handles prioritization at intake (scoring) and at apply-entry requirement generation. Why: The BLOCKING/DEFERRED labels duplicated SRAD's prioritization work. SRAD already evaluates reversibility and agent competence to decide what to ask vs. assume. Rejected: Keep BLOCKING/DEFERRED markers — redundant with SRAD framework, adds template complexity. Introduced by: 260213-v4rx-simplify-templates
Optional Sections as Guidance Comment, Not Template Scaffolding
Decision: The optional requirement subsections (Non-Goals, Design Decisions, Deprecated Requirements) — in plan.md's ## Requirements (j6cs) — are documented in a single guidance comment rather than as standing template sections with placeholder content.
Why: Placeholder content nudges fill-in-the-blank behavior even when sections should be omitted entirely. A comment block preserves the format reference without creating empty scaffolding.
Rejected: Keep standing sections with placeholders — nudges unnecessary fill-in behavior for simple changes.
Introduced by: 260213-v4rx-simplify-templates; Updated by: 260601-j6cs-merge-spec-into-apply (subsections moved from the removed spec.md template into plan.md's ## Requirements)