SuperAPI AI Directory

ctx-spec

Coding & development

View repository

Updated 10-16 01:19

About this skill

ctx-spec — Write Docs an Agent Can Build From

The lifetime model and shared conventions live in ../ctx; this is the spec / ADR / design how-to layer.

How a spec is written — the writing standard

A spec is an authored doc an agent reads once and builds from. Four orthogonal axes govern every line; each governs a different thing, so they compose without clashing:

  1. Status — normative vs informative. A spec states conclusions and constraints (normative). A note, example, or figure is never a requirement (informative). State a binding claim as a MUST/SHALL line — don't let a diagram imply it.
  2. Strength — RFC 2119. MUST / SHOULD / MAY, all-caps, grep-able (all-caps only per RFC 8174). Lower-case "must" is prose, not a requirement.
  3. Why-discipline (IRB) — which "why" may stay. Classify every "why" by what it does for the agent that reads the spec:
    • INTENT — a one-clause so-that that scopes a rule (X, so that Y) so the agent generalizes to cases the spec never enumerated. Stays inline with the rule.
    • RATIONALE — the justification of a choice among alternatives (chose X over Y because Z). Goes to an ADR (Considered options + Decision outcome), never the spec body.
    • BACKGROUND — context the model already knows, or history that changes no action. Cut it.
    • Delete-test: delete the clause — does an edge-case action change? Generalizes the rule → INTENT (keep). Only justifies a past choice → RATIONALE (→ ADR). Nothing → BACKGROUND (cut).
  4. Granularity — specify the constraint, not the mechanism; testable (EARS), versioned. Its own section below (the most important one).

The one anti-pattern (mode bleed): a spec that teaches, compares options, or narrates what happened has let Explanation leak into a normative doc. Rationale → an ADR; comparison / review thinking → a report. The single "why" that belongs in a spec is INTENT — it carries normative force, because deleting it changes conformance at the edges.

Execution Procedure

write_spec_or_record(target) → build_grade_doc

kind = classify(target)   # subsystem-spec | decision(ADR) | design | architecture(HLD) | theory

if kind == "theory":                        # psychology / research-evidence / a chosen framework
    split(target): normative_extract → spec (principle) + decisions (why-this-framework) ; raw_body → scratch/

if kind == "subsystem-spec":
    apply(granularity_rule)                 # specify constraint not mechanism; no hand-written LLD
    fill(spec_template)                     # Tier A precise (EARS + interfaces + schemas + invariants) · Tier B loose (HLD sketch)
    mark_open_questions()                   # [NEEDS CLARIFICATION] while draft

if kind == "decision":
    assert decided(target) ∧ significant(target)   # a MADE, significant choice — never a "proposed" parking lot for open options
    n = next_adr_number()                   # NNNN, 4-digit, sequential, NEVER reused
    write(f"decisions/{n}-title.md", adr_template)   # rich enough that "why X not Y" is derivable later
    if reverses(prev): set(prev.status, f"Superseded by {n}"); link_bidirectional(prev, n)   # supersede in place, never delete
    update("decisions/README.md")           # index row: number · title · status · date

if kind == "design":
    read("references/design-system.md")     # flat-first structure · layer taxonomy · DTCG tokens · motion · content boundary · tool-free standard

if kind == "architecture":
    place_hld()                             # C4 L1+L2 → overview.md ; L3 → per-subsystem Tier B ; L4 → agent generates

assert tierA_is_testable ∧ out_of_scope_stated ∧ no_hand_written_LLD    # GATE
apply("../ctx/references/consistency.md")                               # cross-cutting rules + conventions — verify before commit

The granularity rule (the most important thing)

Specify the constraint, not the mechanism. Loosen the prose, harden the tests.

  • Spec to: verifiable acceptance criteria (EARS / Given-When-Then) + named interfaces + data schemas + invariants + security constraints + explicit out-of-scope. Stop there.
  • Do NOT hand-write low-level design (class/method internals) — the agent generates that in code. Big-company BRD→PRD→HLD→LLD collapses: BRD+PRD fold into one spec, HLD survives, LLD disappears as a human doc.
  • Under-specify → the agent drifts/invents intent. Over-specify → you've hand-written pseudo-code (waterfall, brittle). Sweet spot: constraints + acceptance criteria; the agent picks the how. ("session lookup must be O(1)", not "use a HashMap".)
  • Scale-dependent: loose specs for prototypes/recoverable work; hardened contracts for transactional/regulated systems.
  • Traceable end-to-end. A user story threads forward through its requirement → design → task and stays followable (story → REQ-### → design → T###, where the T### task rows live in progress/ and point back to a REQ-###); IDs are immutable and never reused. A criterion nobody can trace to a task won't get built; a task with no requirement is scope creep.
MUST be machine-precise SHOULD stay loose (agent decides)
interfaces / API contracts / signatures internal implementation approach
data schemas + types internal class/module structure
acceptance criteria (EARS/GWT, binary pass/fail) algorithm choice where any correct one works
invariants & business rules file organization, private naming
security / auth / privacy / compliance micro-optimizations
error & edge taxonomy; non-functional limits
out-of-scope / non-goals (agents can't infer from omission)

spec.md template (per subsystem)

# <subsystem> Spec
status: draft|active · owner: <name> · last-verified: <YYYY-MM-DD>

## 1. Intent & why            [MUST] problem + who + why now
## 2. Non-goals               [MUST] what it explicitly does NOT do
## 3. User scenarios          [MUST] As <role> I want <X> so that <Y> — P1/P2/P3

— TIER A · INTENT/BEHAVIOR (precise, verifiable) —
## 4. Requirements            [MUST] one row per requirement, each with an immutable `REQ-###` id
                              e.g. `REQ-001` <the requirement> — traces from scenario §3
## 5. Acceptance criteria     [MUST] EARS: WHEN <trigger> the system SHALL <response>; one clause = one test
                              each criterion cites the `REQ-###` it verifies
## 6. Interfaces & contracts  [MUST] public signatures, endpoints, event schemas
## 7. Data models             [MUST if data] entities, field types/constraints
## 8. Invariants & rules       [MUST if domain logic]
## 9. Constraints             [MUST] security/auth/privacy + non-functional limits
## 10. Error & edge behavior  [MUST] failure taxonomy, retry/fallback

— TIER B · CURRENT STRUCTURE (descriptive, deliberately loose) —
## 11. Architecture sketch    [OPT] HLD only — components + connections (Mermaid). NOT class-level
## 12. Existing code map       [OPT] where it lives / files to touch
## 13. Implementation notes    [OPT] hints; agent may override with rationale

## 14. Open questions [NEEDS CLARIFICATION]  [MUST while draft]
## 15. Assumptions            [OPT]

# Tasks are NOT part of the spec. The ordered T### task list + status live in progress/
# (ctx-progress); each T### points back to the REQ-### here that it implements.

The REQ-### (§4) fields make each requirement individually addressable. The traceability chain story (§3) → REQ-### (§4) → acceptance criterion (§5) / design (Tier B) → T### (in progress/) is followable end-to-end — the spec owns story→design; the T### task rows live in progress/, each pointing back to the REQ-### here that it implements. IDs are immutable and never reused (granularity rule above; a hard constraint — see § Hard constraints below).

Tasks live in progress, not the spec. The spec ends at requirements + design (§1–§13, §14–§15). The ordered task list — the T### rows, their status (done / active / blocked), and the working construction plan — lives in progress/ (ctx-progress); each T### points back to the REQ-### it implements. Rule of thumb: spec defines what must be true; progress tracks the doing. This keeps the durable contract (spec, never archived while the product lives) cleanly separate from the transient plan (progress, archived when done). A one-off chore with no durable product-truth delta gets no spec at all — the whole plan is just progress/.

EARS keywords: Ubiquitous the system SHALL …; Event WHEN <trigger> …; State WHILE <state> …; Unwanted IF <cond> THEN … SHALL …; Optional WHERE <feature> …. Review Tier A like a contract (precise/testable); Tier B like a map (orient, not binding). If you're writing Tier B to method level, move that constraint UP into Tier A as an acceptance criterion or interface.

Truth arbiter

  • Intent (what should be) → the spec is authoritative; if spec says X and code does Y, the code is wrong.
  • Behavior (what actually is) → the running code + tests are authoritative; a doc that disagrees is stale.
  • Bridge: compile EARS/GWT acceptance criteria into CI tests so the two stay aligned.
  • Recovering from drift — when the agent builds the wrong thing, fix the spec first, then re-run; never patch code past a stale spec. The spec is the durable artifact, code is its current expression; treat the spec as a living doc, not a one-shot prompt. (Stale specs are the #1 spec-driven-dev failure — Kiro / Spec-Kit; updating the spec first is how you keep it alive.)
  • When the drift is code-vs-doc (behavior changed but the spec didn't) rather than agent-vs-spec, keeping the two in truth-sync in the same change is ../ctx/references/consistency.md Rule 2 (same change, no drift) — this section states the authority model; that rule enforces it on a behavior-changing commit.

Theory & evidence — conceptual content CAN be normative

A theory / psychology model / research-evidence input is not disposable by default (normative = "binding on what we build", not "concrete vs abstract"). Route it by splitting:

  • Normative extract → the SOT. Which paradigm and how it's applied → a spec design principle. Why this framework, not others → an ADR.
  • Raw literature / evidence body → scratch/ (disposable), cited by the spec/decision. Reliability flows from the SOT link, not from the file sitting there.
  • Classic theory → just name it (the model already knows it). New (2026) theory → cite the URL + a one-line summary in the spec. A theory you designed yourself IS a spec (you authored it — write it as a principle).

ADR (decision record) format, numbering, and how it's read

# NNNN. <short noun-phrase title>
* Status: proposed | accepted | deprecated | superseded by NNNN     — see status lifecycle below
* Date: YYYY-MM-DD · Deciders: <names>
## Context and problem statement   — the forces in tension
## Decision drivers       (optional)
## Considered options              — real alternatives (not straw men)
## Decision outcome                — chosen option, because …
### Consequences                   — good AND bad
## Confirmation           (optional) — how it's enforced (review / test / lint)

What belongs IN an ADR (the content boundary)

An ADR records a decision that was actually MADE, rich enough that "why X, not Y" is DERIVABLE from it later — this derivability is exactly why the collection stores no long-term explanation docs. It holds:

  • the decision made + its real considered alternatives (not straw men),
  • the outcome (chosen because…) + consequences (good AND bad), + optional confirmation.

What stays OUT:

Not an ADR Goes to
Undecided deliberation (options still open) progress/ or the live session — NEVER a "proposed" parking-lot ADR
Mutable current-state fact spec/
Implementation detail code / spec/
An event log ("what happened") git history

Significance bar: write an ADR when a decision is architecturally significant / costly-to-reverse / non-obvious / would-otherwise-be-relitigated. Trivial reversible choices get no ADR — they're noise.

Rejected-alternative clause (soft norm). When a choice meaningfully turned away from an option, capture it — even a one-line decision-log entry should read — rejected X because Y where relevant. This feeds the reject-log that the read-side scan consults, so the same option isn't reintroduced later. (In a full ADR this is already carried by Considered options + Decision outcome; the clause matters most for the lightweight one-liners.)

Before deciding — read the trail (read-side scan). Before an architecturally-significant choice, SCAN decisions/ + the reject-log so you don't re-litigate a settled question or reintroduce a rejected concept. This is the write-side payoff of the append-only trail — a global habit (see ../ctx/SKILL.md § the read-side habit; a MUST in § Hard constraints below).

Status lifecycle (precise)

Status Meaning
proposed Transient, awaiting acceptance. NOT a place to park undecided options — a proposed ADR is a decision about to land, not an open debate.
accepted Current; the decision is in force.
deprecated The decision was retired and NOTHING replaces it — it no longer applies. (Distinct from superseded.)
superseded by NNNN Replaced by a specific new ADR. Bidirectional link; old body left untouched.
rejected A choice that was considered and declined before ever being adopted — distinct from deprecated (which was accepted, then retired). Feeds the reject-log read-side scan (see ctx-merge) so a declined concept isn't quietly reintroduced under a new label.

Live-log tag mapping. A real project often tracks decisions inline with lightweight tags before they become formal ADRs. Map them: [LOCKED] = accepted; [DRAFT] = proposed (a decision about to land, not an open debate); [OPEN] = not yet a decision at all → it belongs in progress/ (or a spec [NEEDS CLARIFICATION] block), NOT in decisions/. Only [LOCKED]/[DRAFT] graduate to an ADR; [OPEN] never does until it's decided.

  • One decision per file — decisions/NNNN-title-with-dashes.md. NOT many-in-one-file: per-file numbering, per-file status, and per-file supersede links all assume it, and an agent can open exactly the one it needs (cheaper than loading a monolith).
  • Numbering: sequential, 4-digit zero-padded (0001), monotonic, never reused — Nygard's original rule.
  • Immutability: a decided ADR is NEVER rewritten. You only ever APPEND a supersede pointer and flip the old status — the original clause text stays exactly as written. Reversing a decision = supersede in place, NEVER edit-the-old / delete. Write a NEW ADR with Supersedes: NNNN; flip the old to Superseded by NNNN (bidirectional). The old file stays, body untouched — the truth is the whole chain. This is a strong, documented CONVENTION (Nygard, Fowler, AWS, Microsoft, adr-tools), not an ad-hoc choice; the append-only trail ("we thought X, then Y") is exactly what prevents re-litigating settled decisions.
  • The index carries the token cost, not a forced read. decisions/README.md is a table (number · title · status · date · supersedes/superseded-by), generatable by adr-tools/adr-log. An agent reads the index first, reads spec/ for "what to build", and opens a specific ADR only for "why is this like this / can I change it". A superseded ADR costs one index row, not a full read — so keeping history is cheap.
  • ADRs are never archived (unlike specs) — see archive rule below.

Worked example — the supersede chain (bidirectional, in place)

decisions/0003-session-store.md (flipped when 0007 lands — body below the header untouched):

# 0003. Session store in Postgres
* Status: Superseded by 0007
* Date: 2026-02-10
Chose Postgres for session rows: one datastore, transactional with the user table.

decisions/0007-session-store-redis.md:

# 0007. Move session store to Redis
* Status: accepted · Supersedes: 0003
* Date: 2026-06-30
p99 session lookup missed the O(1) target under load; moved to Redis. Trade-off: a second datastore to operate.

The chain (0003 ⇄ 0007) reads in place — no deletion, no archive; the index carries both rows.

Worked example — minimal spec skeleton (Tier A vs Tier B headers)

# Auth Spec — status: draft · last-verified: 2026-06-30
## 4. Requirements          [Tier A] REQ-001 an expired token must be rejected
## 5. Acceptance criteria   [Tier A] WHEN a token expires the system SHALL return 401 (verifies REQ-001)  ← one clause = one test
## 6. Interfaces            [Tier A] POST /session → {token, exp}
## 11. Architecture sketch  [Tier B] gateway → auth-svc → session store (Mermaid, not class-level)
# the T001 task (implement token-expiry check → REQ-001) lives in progress/, NOT the spec

HLD / architecture (C4) — placement

  • System level (C4 Context + Container) → ONE top-level ctx/overview.md. Whole-system shape, tech choices, inter-service comms. Global, slow-changing.
  • Subsystem level (C4 Component) → inside each spec/<subsystem>.md Tier B. Co-evolves with its spec.
  • Code level (C4 L4) → not drawn; the agent generates it.
  • Diagrams: Mermaid-as-code (flowchart / sequenceDiagram / erDiagram render natively on GitHub; C4 Mermaid blocks do NOT render on GitHub — use PlantUML+C4-PlantUML if true C4 is needed).

spec/design/ — design is a spec subtype

Design (layout / color / type / motion) can't be expressed precisely in Markdown, so under spec/design/ the .json (tokens) and .html (comps) files carry the truth directly — but the two differ: token JSON is the diffable contract (the true SOT), an .html comp is reference-grade intent (not pixel-truth). The structure (flat, graduating to subfolders when it grows), the layer taxonomy, the DTCG token model, motion (global tokens + local choreography), the tool-free design standard, and the content boundary are in references/design-system.md. Read it before writing any design doc.

Archive rule (spec vs decision — they differ)

Follows the lifetime class (global rule in ../ctx/references/consistency.md § Archive mechanics):

  • spec (LIVING, detailed): localized change → edit in place; foundational rewrite → move the WHOLE old spec to spec/archive/ under the canonical archive filename (defined once in ../ctx/references/consistency.md § Archive mechanics) and write ONE clean new <name>.md reusing the canonical name (the active spec stays a single elegant current-truth doc — never a pile of deprecated sections). Record the why we rewrote as an ADR. Ripple-fix references in the same commit.
  • decisions (APPEND-ONLY): never archived — supersede in place (above). The trail is the value.
  • Archive folder is per-folder, plain name archive/, verbatim move + pointer, never delete by default.

Hard constraints (spec authoring)

The MUST/NEVER lines below are the domain constraints for spec / ADR / design authoring — binding, load-before-act; they fire when a spec, decision, or design doc is written. The cross-cutting rules (single-source, same-change, verify-canonical, the gate) live in ../ctx/references/consistency.md and apply on top. The formats/templates above teach; these lines bind.

Status & why-discipline

  • MUST write a spec as conclusions + constraints (normative content). NEVER embed teaching, tutorials, discursive narrative, or option-comparison in a spec — that is Explanation bleeding into a normative doc; move it to a reports/ doc (for review) or an ADR (the why).
  • MAY carry INTENT inline — a one-clause so-that that scopes a rule so the agent generalizes correctly (X, so that Y); this is the one "why" that stays, because deleting it changes edge-case conformance. MUST NOT embed RATIONALE ("why we chose X over Y") → that belongs in the ADR's Considered options + Decision outcome. MUST cut BACKGROUND (context the model already knows / history that changes no action).
  • NEVER treat a figure, example, or note as a binding requirement — illustrative content is informative, not normative. State the normative claim as a MUST/SHALL line; do not let a diagram imply it.
  • MUST run a term-operation consistency check before naming a new concept in a spec: if the term's full established meaning conflicts with the requirement it's attached to, drop the term and keep only the requirement (same gate as skill-forge/references/skill-format.md § Term Usage's Consistency gate, adopted from ctx-report's term-operation-consistency-gate).

Testable, versioned, machine-readable

  • MUST use testable acceptance criteria (EARS WHEN <trigger> the system SHALL <response> / Given-When-Then) with quantified thresholds — one clause = one test, binary pass/fail. NEVER an unfalsifiable criterion ("should be fast" → "P95 < 200 ms").
  • MUST give versioned tech choices (Postgres 16, not "a database") and machine-readable contracts (interfaces / signatures / endpoints / event schemas / data models with types).
  • MUST specify the constraint, not the mechanism — no hand-written low-level design ("session lookup must be O(1)", not "use a HashMap"). State non-goals / out-of-scope explicitly (an agent can't infer them from omission).

Traceability & drift recovery

  • MUST keep the ID chain traceable end-to-end — story → requirement (REQ-###) → design (spec) → task (T###, in progress/), immutable, never reused, followable. NEVER ship a requirement whose acceptance criterion no task will implement, nor (in progress/) a T### task that traces to no REQ-###.
  • MUST, on agent misbehavior, fix the spec first then re-run — the spec is the artifact, code is its current expression; NEVER patch code past a stale spec or treat the spec as a one-shot prompt.

No dangling pointers

  • MUST NOT leave a dangling pointer — e.g. "see Q1–Q5" / "per §4" / "→ ADR-0007" where the target is undefined, unwritten, or unresolved. Every forward reference in a spec resolves to a defined, current target in the same commit. (Ties to consistency.md Rule 1's stale-pointer gate.)

ADR discipline

  • MUST, before an architecturally-significant choice, scan decisions/ + the reject-log — do not re-litigate a settled decision or reintroduce a previously-rejected concept. (The read-side payoff of the append-only trail; global habit in ../ctx/SKILL.md § the read-side habit.)
  • MUST have an ADR record only DECIDED choices — a made choice + its real alternatives + consequences. NEVER park undecided options as a "proposed" ADR to be filled in later; undecided deliberations stay in progress/ (or a spec's [NEEDS CLARIFICATION] block) until decided, then land as an accepted ADR.
  • MUST number ADRs NNNN (4-digit, sequential, monotonic, never reused); one decision per file; reverse only by supersede-in-place (bidirectional status flip, old body untouched — mechanics above).