| name | prd-authoring |
| description | PRD format conventions, boundary rules, and template references. Load when writing or reviewing product requirements. |
| compatibility | ["claude-code","github-copilot","opencode","junie-cli"] |
| reads | ["docs/prd.md","docs/ubiquitous-language.md"] |
| metadata | {"version":"1.0","author":"team"} |
Pipeline Position
This skill operates inside the middle loop of the four-nested-loop pipeline (inner / middle / outer / architectural). A prd-entry record scopes one slice for the inner loop to implement. See agentic-harness.md for the loop model and the slice definition.
PRD Location
The PRD lives at docs/prd.md. The canonical domain vocabulary lives at docs/ubiquitous-language.md.
Ubiquitous Language Discipline
The ubiquitous language is durable memory — the agent's vocabulary across sessions and across other developers' agent teams. Treat it as load-bearing.
Inline updates. Domain terms resolve during the root elicitation (agentic-harness.md § Conversations Stay in Root); root loads this skill for it. A term has resolved when the human has committed to a specific word for a specific concept and the conversation has used it intentionally. Root carries every resolved term in the distillate; the product-requirements-expert appends them to docs/ubiquitous-language.md at dispatch, before drafting. Do not drop a resolved term from the distillate; do not defer an append to a second-use gate.
Term-drift challenge. When the human introduces a term that conflicts with an existing entry in docs/ubiquitous-language.md, root calls it out mid-conversation: name the existing definition, name the apparent new meaning, ask which is intended. A drift that reaches the dispatch unresolved returns as pushback, never absorbed silently into the PRD.
Entry format. Each entry has a one-sentence definition; an optional Relationships: line; an optional Avoid: line listing terms-not-to-use for the same concept. An example dialogue at the bottom of the file shows several terms interacting in a worked exchange. See docs/ubiquitous-language.md itself for the format header.
Forbidden: drafting the PRD with terms not in docs/ubiquitous-language.md. If a needed term is missing, write it first.
PRD Boundary Rule
The PRD describes what the system does. It must not contain how. It must not contain why — rationale lives in ADRs, referenced via the **ADR:** link.
Litmus test (what/how): If it would change when switching to another language, it belongs in docs/system-design.md, not the PRD.
Litmus test (state/history): If it explains why a decision was made (alternatives considered, trade-offs evaluated), it belongs in an ADR, not the PRD.
Discuss the how; record only the what. The boundary governs what you write, not what you discuss. Explore freely how the system might work — that is how the human discovers what they mean. The record carries only requirements and non-goals, never a how. Do not park implementation ideas in the handoff. Mechanism is the design stage's responsibility; the human, present there too, can raise an idea worth keeping. Recording a how, even as a note, pre-empts that stage and leaks mechanism into the requirement.
When the PRD needs to reference implementation details:
**Design:** See [system-design.md#section](system-design.md#section)
When the PRD needs to reference the rationale for a decision:
**ADR:** See [ADR: Title](adr/YYYY-MM-DD-title.md)
Two Layers: Requirements and Slices
Requirements live in docs/prd.md. Each REQ-XX-NNN captures one coherent product capability — what users eventually get. A requirement may carry many acceptance criteria and may take multiple sessions to fully implement. The PRD is the durable, current-complete-state projection of what the system does; it is not segmented by slices.
Slices live in .scratch/handoff.jsonl as prd-entry records. Each record is one unit of implementation work the inner loop can complete in one cycle. Multiple prd-entry records may target the same req_id over time — each shipping one slice of the requirement.
A prd-entry record may carry a subset of its REQ-XX-NNN's acceptance_criteria — the subset that's being implemented in this round. The PRD entry stays domain-coherent; the handoff record stays inner-loop-sized.
Slice-Sizing Rule
The slice rule applies to prd-entry records (units of work), not to REQ entries (units of intent). Each prd-entry is a right-sized vertical slice — it cuts through every architectural layer the behavior actually touches, and is small enough to ship in one inner-loop sequence while large enough that coordination overhead pays for itself.
A right-sized vertical slice satisfies all six:
- Cuts through every architectural layer the behavior actually touches — no layer-only slices ("just the repository", "just the entry point").
- Has a single primary deliverable surface — one of: code change, documentation change, schema change, configuration change. Tests for the primary surface count as part of it. A slice that bundles multiple surfaces ("implement the code AND update the PRD AND write an ADR AND regenerate goldens") burns the inner loop's context budget on surface-switching rather than depth — split it.
- The
acceptance_criteria you include ship as a single unit — no useful subset ships earlier.
- Implementable in one TDD plan, typically 3–10 cycles.
- Behaviorally named (the
title would make sense to a stranger reading it cold — "User can log in with email and password," not "Add a login handler").
- Independently reviewable and mergeable.
Both ends of the size range are failure modes:
- Too big. Inner loop can't complete in one session; design churns mid-implementation. Splitting tests: (1) if a strict subset of the included
acceptance_criteria could ship standalone and be useful, append a second prd-entry record covering the second slice (same req_id if the REQ holds together — you're slicing one requirement across multiple work cycles; a new req_id if the REQ itself needs splitting); (2) if the slice spans multiple deliverable surfaces, split by surface — each surface becomes its own prd-entry.
- Too small. Pipeline overhead (PRD lookup + design triage + TDD plan + roster reviews + change-grade) dominates the work. Batching test: if the slice would honestly take only 1–2 TDD cycles AND only makes sense alongside a sibling, write one
prd-entry covering the combined work instead of two trivial ones. Siblings may share a req_id or live under related REQs.
Slice-sizing is enforced at this skill (write-time, when authoring a prd-entry); the next skill applies the same tests at selection time. See agentic-harness.md for the full loop model and the two-layer model.
Prohibited Patterns in PRD
| Pattern | Severity | Fix |
|---|
| Implementation code blocks in the PRD | Critical | Move to system-design.md, link from PRD |
| Framework- or language-specific constructs | Critical | Describe behavior, not mechanism |
| Rationale prose (paragraphs explaining why a requirement or non-goal exists) | Critical | Move reasoning to an ADR; reference via the **ADR:** link (link only, no inline reasoning) |
| A blanket exemption that tells reviewers to skip a check (e.g. "doc-reviewers may skip the rationale-prose check here") | Critical | A document cannot grant itself an exemption. Fix the content or raise a per-instance escalation; never disable a reviewer check wholesale |
| Mechanism tables — CLI flag tables, exit-code tables, output-directory layouts, on-disk/file-format schemas | High | Move to system-design.md; state the behavior in prose and link with **Design:** |
Per-requirement contract scaffolding (Input / Output / Constraints / Depends On blocks) | High | Field tables in disguise — state the outcome in a "Done when" bullet; the signature lives in source, the constant in system-design.md |
| Internal code references (type, method, variable names) | High | Use behavioral language |
| Algorithm formulas or pseudocode | High | State behavioral constraints, move formulas to system-design.md |
| Low-level implementation constructs (concurrency primitives, regex, framework APIs) | High | Describe behavior, not mechanism |
| Hardcoded constant values | Medium | Reference a Constants section in system-design.md (create the section on first constant) |
Requirement Format
The PRD is narrative prose, not a catalog of structured entries. Write each capability area as a few sentences a newcomer reads top to bottom, and annotate each requirement inline with its ID where the prose expresses it. The prose carries intent; a short "Done when" list carries the bounded, testable contract.
This keeps the PRD human-readable and agent-parseable at once. It retires the per-requirement scaffolding — Input, Output, Constraints, Depends On — that duplicated the prd-entry handoff record and rotted against source (those are field tables in disguise; see § Prohibited Patterns).
The five parts
- Narrative. Prose grouped by capability area, describing what the system does. Tag each requirement inline where the prose states it:
... the tool runs as one command `[REQ-XX-NNN]`, and discovers configuration by precedence `[REQ-XX-MMM]`. IDs follow REQ-[A-Z]+-[0-9]{3} — exactly three digits, the pattern every handoff schema and gate enforces.
- Anchor. At a requirement's first mention, place
<a id="req-xx-nnn"></a> (lowercase, hyphenated) on its own line, so ADRs and system-design deep-link to prd.md#req-xx-nnn.
- "Done when" acceptance. After each capability group, a list whose every bullet opens with a requirement's
[REQ-ID] and states one bounded outcome in plain given/when/then language. The bullet is the contract — the prose is context, the bullet is what "done" means and what the fresh-eyes reviewer judges against. Every REQ-ID must appear in at least one bullet (the doctor's req-acceptance check enforces this).
- Links.
**ADR:** [ADR: Title](adr/...) for the decision trail; **Design:** [system-design.md#section](system-design.md#section) for mechanism. Each only when it exists.
- Edge cases (when the capability has them). A numbered
**Edge cases:** list beside "Done when", one bounded case per item. The numbering is stable and citable: test comments reference it, the test-reviewer checks each number has a dedicated test, and the system-design expert verifies the design accounts for each. This list is the "documented edge cases" every downstream stage names.
Worked example
<a id="req-xx-nnn"></a>
The tool runs as a single automatic command: one invocation reads transcripts,
updates the manifest, and regenerates every report `[REQ-XX-NNN]`. Configuration
resolves by precedence — an explicit flag wins, otherwise built-in defaults
apply, and a flag pointing at a missing file is materialized, not failed
`[REQ-XX-MMM]`.
**Done when:**
- `[REQ-XX-NNN]` a no-flag run scans the default input, writes the reports, and
prints a one-screen summary; an unknown flag exits with a usage error.
- `[REQ-XX-MMM]` a flag value overrides the built-in default; a missing flagged
path is created with defaults, not an error.
**ADR:** [ADR: CLI surface](adr/2026-01-01-cli-surface.md) · **Design:** [system-design.md#cli](system-design.md#cli)
Lifecycle, not a Status field
A requirement is active by being in the narrative — there is no per-requirement Status field and no synonym set to maintain. Retire a requirement by moving its ID to a ## Superseded list that maps it to its successor (or the reason it was withdrawn), so existing links still resolve. Never renumber an ID. The **ADR:** link is mandatory whenever an ADR records the decision behind the requirement; the **Design:** link is mandatory whenever the requirement defers a mechanism to system-design.md.
Autofix Audit (Run First on Every Dispatch)
Before working on the dispatch's main task, audit every type: "prd-autofix" record in .scratch/handoff.jsonl whose ts is later than your most recent type: "prd-entry" record (or any such record if you have not yet appended one). handoff.py audit-autofix (the code-quality-gate skill's autofix audit) has already re-checked the allowlist bounds mechanically; your job is the judgement check.
For each record, decide whether the change is legitimately mechanical:
- Legitimate. Writing-standards or structural fix that doesn't smuggle in a semantic shift. Common shape: sentence split, missing anchor added, broken intra-file link repaired.
- Illegitimate. The change reads as mechanical but moves requirement meaning — a "sentence split" drops a condition from a "Done when" bullet, a "writing-standards" rewrite changes an acceptance contract, a "structural" fix repoints a REQ anchor. These are substantive changes that escaped via mis-tagging.
For every illegitimate record:
- Apply the corrective edit to
docs/prd.md yourself — you own the file.
- Append a superseding
prd-entry for the affected req_id, noting the rejection in notes: "autofix-rejected: <handoff.jsonl line N>: <reason>". The design re-triage this triggers is deliberate — a semantic PRD change always re-enters the pipeline at Gate 1.
If every audited record is legitimate, skip silently — no entry needed. If the log contains no prd-autofix records, skip silently.
Feature Handoff Record (product-requirements-expert → system-design-expert)
When a feature is approved, append one record to .scratch/handoff.jsonl describing the scope. The record is the structured contract that system-design-expert consumes; the markdown PRD entry in docs/prd.md remains the human-authored source of truth.
File: .scratch/handoff.jsonl (append-only; one JSON object per line, terminated by \n).
Schema: schemas/scratch/prd-entry.schema.json. The routing gate (handoff-routing Gate 1) validates each record at the product-requirements-expert→system-design-expert transition; malformed records bounce back to you without consuming a system-design-expert dispatch.
Required fields:
| Field | Type | Notes |
|---|
type | "prd-entry" | Discriminator. |
req_id | string ^REQ-[A-Z]+-[0-9]{3}$ | Matches the PRD heading. |
ts | ISO 8601 string | Stamped by append; never composed by the author. |
author | "product-requirements-expert" | Pinned. |
title | string | Short requirement title. |
summary | string ≤ 400 chars | One- or two-sentence statement. No implementation details. |
acceptance_criteria | array of strings | Testable conditions. At least one. |
file_targets | array of strings | Paths likely to be touched. At least one. Best-effort; system-design-expert may revise. |
test_names | array of strings matching test_name_pattern from scripts/layout.toml | Test functions/methods expected to exist, matching test_name_pattern. At least one. Naming school: docs/testing-principles.md § Test Naming. |
Optional fields: non_goals, dependencies (other req_ids), notes.
Append-only discipline: Append records via python3 scripts/handoff.py append prd-entry (heredoc form per the handoff-append skill). Never edit, reorder, or delete prior records. If a prior record has a mistake, append a new record that supersedes it.
Example Record
Why JSONL, not markdown: the structural-rejection gate this enables converts system-design-expert retries into cheap upstream bounces before a subagent dispatch is consumed.
{"type":"prd-entry","req_id":"REQ-XX-099","author":"product-requirements-expert","title":"Cache miss diagnostics","summary":"Surface per-component cache miss rate so operators can spot reinjection hotspots.","acceptance_criteria":["report renders the cache-miss-rate per component","value matches cache-creation / (cache-creation + cache-read)"],"file_targets":["report/summary","report/summary_test"],"test_names":["<name matching test_name_pattern>"],"non_goals":["historical trend"]}
The test_names value is a placeholder: use names matching this project's test_name_pattern in scripts/layout.toml — the append gate rejects any other shape.
Writing Standards
Follow the writing standards in the document-writing skill — the language-agnostic rulebook the doc-reviewer enforces.