| name | prd |
| description | Author a single Product Requirements Document at docs/spec/{slug}.md. Loaded into the calling agent's context; the agent drafts the PRD per the format authority below. Trigger: "create PRD", "draft PRD", "write a product requirements document", "decompose this into a spec under docs/spec/", "write up requirements for", "scope this feature".
|
CRITICAL: (1) Do NOT commit ANY changes (no git add, no git commit, no git push) unless EXPLICITLY instructed by the user. (2) This is a leaf skill. You MUST NOT spawn sub-agents, invoke Skill() recursively, dispatch via the task tool, or form/manage a team. The calling agent owns downstream routing after this skill returns. (3) Under Opencode the calling agent is a one-shot task-tool dispatch: the artifact is written to disk and the confirmation emitted into the calling agent's context; the calling agent carries the outcome in its returned summary to team-lead. There is no peer-messaging channel and no SendMessage.
PRD — Author a Product Requirements Document
You are the PRD Author. You produce a single Product Requirements Document at
docs/spec/{slug}.md and return. The calling agent (typically @project-manager)
drafts the content; this skill is the format authority — section list, frontmatter
contract, output path, reserved-name refusal, and collision handling all live here.
Docs paths (this skill). Master: ~/.config/opencode/skills/team-doctrine/references/docs-paths.md — repo: src/user/opencode/skills/team-doctrine/references/docs-paths.md (maintained copy).
- Writes:
docs/spec/{slug}.md (PRDs only — NOT the 7 reserved names).
- Reads:
docs/spec/, docs/tdd/, docs/ux/.
- Always singular docs/spec/ — never docs/specs/.
Argument Handling
The argument is a single positional <topic> (free-text, 3-10 words describing the
artifact). No flags, no other args.
If <topic> is missing or empty:
Error: Usage: Skill(prd, "<topic>") — describe the artifact in 3-10 words.
If extra positional args are passed beyond <topic>, ignore them silently.
Slug derivation (deterministic):
lower = lowercase(topic)
cleaned = re.sub(r'[^a-z0-9]+', '-', lower)
trimmed = cleaned.strip('-')
truncated = trimmed[:60]
- Prefer a word boundary in [40, 60):
boundary = truncated.rfind('-', 40, 60). If
boundary != -1, set truncated = truncated[:boundary].
truncated = truncated.strip('-') (re-trim after step 5).
- If
truncated == "", ABORT: Error: Topic must contain at least one alphanumeric character.
- Use
truncated as {slug}.
When to Use
- A feature-level Product Requirements Document is needed for a non-trivial product surface (new feature, UX-driven change, scope-defined initiative) and should land at
docs/spec/{slug}.md as the authoritative product record. Pick PRD over TDD when scope precedes architecture — what and why is uncertain, not how.
- The calling agent (typically
@project-manager) is producing a PRD before decomposition into Docket issues so reviewers and implementers share one product definition.
- The team-lead Large Task pattern (
~/.config/opencode/agents/team-lead.md — repo: src/user/opencode/agents/team-lead.md) requests a PRD as the entry point for product-defined initiatives — this skill is the canonical path.
When NOT to Use
- Inline scoping notes, advisory replies, decomposition comments, or scratch ideas
that are not meant to live at
docs/spec/.
- Technical Design Documents (architecture, system design, multi-step migration):
use
Skill(tdd, "<topic>").
- Architecture Decision Records (single decisions): use
Skill(adr, "<topic>").
- UX / design specs: use
Skill(ux-spec, "<topic>").
- Project-wide engineering specs (the 7 reserved names: architecture, security,
operations, performance, code-quality, review-strategy, testing): owned by the
init-specs skill. This skill HARD-REFUSES those names — see Pre-flight step 4
and Failure Modes.
Pre-flight
- Resolve
{slug} from <topic> per the Argument Handling slug rule above.
- Resolve
{output_path} as docs/spec/{slug}.md. The output directory is
docs/spec/.
- Resolve context:
{today_date} = Bash date +%Y-%m-%d.
{project_name} = Bash basename $(git rev-parse --show-toplevel).
{updated_by} = the calling agent's identifier (e.g., @project-manager).
- Reserved-name refusal: if
{slug} matches a name in the Failure Modes Reserved-Name List, ABORT per the Failure Mode table (no overwrite path) — checked before collision so reserved files never reach the overwrite dialog.
- Check collision:
Glob docs/spec/{slug}.md. If a file exists at
{output_path}, run the COLLISION_DIALOG below.
If a file already exists at the target output path, invoke question:
question({
questions: [{
header: "File exists",
question: "{output_path} already exists. How should I proceed?",
options: [
{label: "Pick new slug",
description: "I'll suggest {slug}-2 (or you can supply a new topic)"},
{label: "Overwrite",
description: "Replace the existing file (destructive — uncommitted changes will be lost)"},
{label: "Cancel",
description: "Stop without writing"}
]
}]
})
- "Pick new slug" → suggest
{slug}-2, then {slug}-3, etc. via free-text follow-up.
- "Overwrite" → proceed to Authoring Procedure; the existing file will be replaced on Write.
- "Cancel" → emit
Cancelled — no file written. and end.
Dispatched-subagent caveat. question may be unavailable in a dispatched one-shot subagent context (the operator is reached via team-lead relay, not directly) — if you cannot get an overwrite decision, do NOT Write: emit Blocked: {output_path} exists; overwrite needs operator confirmation — surface this in your returned summary for team-lead to relay to the operator. and end.
Never silently overwrite. There is no "append" option — partial appends produce
malformed frontmatter.
Authoring Procedure
- Gather prior art:
Grep -r "{topic-keywords}" docs/ and read related PRDs
already in docs/spec/ (the 7 reserved engineering specs — see the Reserved-Name
List — are project-level conventions, not PRDs; skip them unless the PRD genuinely
depends on one). Read any TDDs in docs/tdd/ or design specs in docs/ux/ that
touch the same surface — the new PRD should reference, not contradict, prior accepted
product definitions, and record each one the PRD builds on in the dependencies
frontmatter field so reviewers and decomposition can trace the lineage.
- Probe Docket (informational): run
docket issue list --sort priority:asc --json (high-priority active tickets) and docket issue list --tree (existing epics whose decomposition may overlap). Surface any intersecting issues under a "Pre-existing Docket issues" sub-bullet in Risks & Open Questions.
- Draft the frontmatter per the Required Frontmatter contract below. Set
maturity: "draft" initially.
- Draft each Required Section in order (see Output Contract → Required
Sections). Every section listed MUST appear, in the order shown.
- Mermaid diagrams: per the Mermaid Mandate subsection below, include at least one Mermaid block.
- Proceed to Validation Before Save — that step is the single source of
truth for frontmatter, sections, Mermaid, and placeholder checks.
Output Contract
Required Frontmatter
---
project: "{project_name}"
maturity: "draft"
last_updated: "{today_date}"
updated_by: "{updated_by}"
scope: "{one-liner describing what the PRD covers}"
owner: "{owning agent or team, e.g. @project-manager}"
dependencies:
- {relative path to related doc, or empty list}
---
Field rules:
project = basename $(git rev-parse --show-toplevel).
maturity is the doc-class ladder for living product definitions — one of
proof-of-concept | draft | experimental | stable. New PRDs start at draft.
- PRDs do NOT use a
status field. status is reserved for in-flight workflow
artifacts (TDDs and ADRs). PRDs are living product definitions — they take
maturity from the init-specs family ladder.
last_updated is ISO date YYYY-MM-DD.
updated_by is the calling agent identifier (@project-manager, etc.).
scope is a one-line description of what the PRD covers — populated by the
calling agent.
owner is the responsible agent or team handle.
dependencies is a YAML array of related-file paths (relative to the doc); use
[] if none.
Required Sections
The PRD body MUST contain these top-level sections, in this order. Each is a
## heading in the drafted document.
- Problem Statement — what the product surface is, why now, who is affected,
constraints, business context.
- Goals — concrete, testable outcomes the PRD commits to.
- Non-Goals — explicit out-of-scope items, including future-work flags.
- User Stories / Use Cases — narrative scenarios from the operator/user
perspective, with explicit per-story priority (P0/P1/P2 or MVP/polish — pick one
scheme and apply it consistently). Bare "with priorities" without a named scheme
is a defect.
- Requirements — functional and non-functional, prioritized using MoSCoW (Must / Should / Could / Won't). Each requirement MUST be testable: a reviewer must be able to point at a behavior and say "this satisfies / does not satisfy" without a follow-up clarification.
- Success Metrics — quantitative measures that validate Goals are met. Each
metric MUST name (a) what is measured, (b) the measurement method, and (c) a
numeric target or threshold. "Improve UX" is a defect; "p95 first-token latency
under 800ms measured via /metrics endpoint" is acceptable.
- Risks & Open Questions — risk table (likelihood/impact/mitigation); open
questions resolved or escalated before decomposition into Docket issues.
Mermaid Mandate
PRDs require at least one ```mermaid (lowercase, no space) fenced block — user journey, state diagram, or component map. Unlike TDDs, there is no pure-policy override; Validation §5 enforces this.
Validation Before Save
Before invoking Write, verify in the calling agent's context:
- Frontmatter fields — all of
project, maturity, last_updated,
updated_by, scope, owner, dependencies present and non-empty
(dependencies may be the empty list []).
- No
status field — PRDs use maturity, not status. Presence of status
in frontmatter is a defect.
maturity value — within the allowed set defined under Field rules above.
- Section order — the body contains all top-level sections enumerated in "Required Sections" above, as
## headings, in the order listed. Count only ## headings at column 0 outside ``` code fences — a PRD that documents another doc/skill may embed ##/### example headings inside fences; those are content, not structure.
- Mermaid presence & shape — at least one
```mermaid fenced block in the body, and the block's first non-blank line declares a Mermaid diagram-type keyword (e.g. graph/flowchart, sequenceDiagram, stateDiagram, erDiagram, journey, classDiagram, gantt). An empty or typeless block fails — it renders broken yet passes a presence-only check. Renderer-based syntax validation is out of scope (no mermaid CLI in-repo).
- Placeholder scan — body contains no literal
{slug}, {topic},
{project_name}, TBD, or TODO text outside of code-fenced examples.
- Success Metrics concreteness — every bullet/item under the Success Metrics
section contains at least one digit OR a comparison operator (
<, >, ≤, ≥,
=). A Success Metrics section with zero numeric targets is a defect.
If any check fails, ABORT (no fix-and-retry — the skill validates then writes
in a single pass; repair is the calling agent's responsibility):
Error: validation failed: {field/section} — {detail}.
The calling agent fixes the issue in its own context and re-invokes
Skill(prd, "<topic>").
Save & Return
After Validation Before Save passes:
-
Bash mkdir -p {output_dir} (idempotent).
-
Write {output_path} with the drafted content.
-
Emit a single confirmation line:
Created {output_path}
End. Do NOT echo the file body, do NOT send peer messages, do NOT invoke other skills.
The calling agent owns next steps (vote requests, decomposition, peer notification).
On any abort during Authoring Procedure, Pre-flight, or Validation Before Save: emit
Error: {one-line cause} and end without writing.
On operator Cancel during the collision dialog: emit
Cancelled — no file written. and end without writing.
Failure Modes
Reserved-Name List
The 7 names below are owned by the init-specs skill (project-wide engineering specs)
and HARD-REFUSED by this skill. There is no overwrite path.
architecture
security
operations
performance
code-quality
review-strategy
testing
Failure Mode Table
| Trigger | Handling |
|---|
<topic> missing or empty | Abort: Error: Usage: Skill(prd, "<topic>") — describe the artifact in 3-10 words. |
| Slug empty after sanitization (e.g., all-CJK or all-punct topic) | Abort: Error: Topic must contain at least one alphanumeric character. |
| Slug matches a reserved name (see list above) | Abort: Error: '{slug}.md' is a reserved name owned by the init-specs skill. Pick a different topic or use the init-specs skill to bootstrap project specs. No overwrite path. |
| Output file already exists (and slug is not reserved) | Run COLLISION_DIALOG; never silently overwrite. On Cancel: Cancelled — no file written. |
| Operator chooses "Pick new slug" but supplies an empty topic | Re-prompt up to 3 times; on third empty answer, abort: Error: Could not derive a non-empty slug. |
| Validation Before Save fails | Abort with Error: validation failed: {field/section} — {detail}. No retry — calling agent re-invokes. |
| Mermaid mandate not satisfied | Abort: Error: validation failed: Mermaid block missing — PRD requires at least one mermaid fenced block (user journey, state, or component map). |
Frontmatter contains status field | Abort: Error: validation failed: frontmatter — PRDs use 'maturity', not 'status'. Remove the status field. |
maturity value outside the allowed set | Abort: Error: validation failed: frontmatter — 'maturity' must be one of proof-of-concept | draft | experimental | stable. Got '{value}'. |
| Success Metrics section has no numeric targets | Abort: Error: validation failed: Success Metrics — every metric must include a numeric target or threshold (e.g., 'p95 < 800ms'). Vague metrics are rejected. |
| Filesystem write fails (permissions, disk, read-only mount) | Surface raw error: Error: Write failed — {raw error}. Do NOT retry. The calling agent reports to the operator. |