| name | prd-quality-rubric |
| description | Critic scoring rubric for drafted specs. Defines dimensions D1–D4 (both modes) and D5–D8 (update mode only), per-dimension scoring guidance, weighted aggregation, verdict thresholds (pass/revise/block). Replaces literal-diff-based rubric. Triggers on: score the spec, critic rubric, D1 mandatory coverage, D2 gated appropriateness, verdict thresholds. |
PRD Quality Rubric
This skill is the source of truth for @prd-critic's scoring. It
defines the dimensions, scoring guidance per dimension, weighted
aggregation, and verdict rules.
When to Use This Skill
Load this skill when:
- Scoring a drafted spec (
@prd-critic).
- Authoring or maintaining the eval
spec.yaml rubric list (the
rubric IDs in the eval spec mirror the dimension IDs here).
Domain neutrality
Dimensions are domain-neutral. The critic does NOT penalise the
absence of an industry-specific section the user did not request,
and does NOT reward the presence of one. User-approved Stop A
overrides for section names are honoured at face value (D3).
Dimensions
D1 — mandatory-coverage (both modes)
Question: Are all mandatory sections from the
prd-template catalogue present and non-empty (or marked [TBD]
with a corresponding OQ-NN entry in "Open Questions")?
Scoring: count present / total mandatory; partial credit for
[TBD] entries that have a matching OQ entry.
Common deductions: missing "Open Questions" section entirely;
mandatory section present but empty without [TBD].
D2 — gated-appropriateness (both modes)
Question: For each complexity-gated section, is the
include/omit decision justified by the §heuristic given the inputs
AND the user-chosen spec_kind (see prd-template)?
Scoring:
- Penalise bloat: gated section present without a triggering
axis being detectable in the inputs, OR without
spec_kind
permitting it.
- Penalise underspecification: axis present in the inputs AND
spec_kind permits inclusion, but the section is omitted.
- Reward correctly omitted sections with a clear rationale in
section-decisions-json (including
gated-omitted-by-spec-kind).
- In
spec_kind: product, do NOT penalise omission of
implementation-shaped sections (Data Model, API Contract,
Capacity & Performance Targets, Threat Model Summary, Versioning
& Deprecation Policy, NFR↔FR Traceability) regardless of axis.
experience-surface axis (the UX section). The gated
"User Experience: Personas, Journeys & Roles" section is scored
like any other gated section: reward correct omission when
the axis did not fire (non-UI-forward spec, or a single-affordance
tweak caught by the non-fire guardrail); penalise bloat when
the section is present without a UI-forward signal; penalise
underspecification when the axis clearly fired (new
screens/flows/nav, or role-varying UI behaviour) but the section
is missing. Do NOT penalise a non-UI-forward spec for lacking the
section — the concepts belong woven per
spec-driven-prd-best-practices §11 instead (scored under D4/D11,
not D1/D2).
D3 — naming-consistency (both modes)
Question: Do section names match the neutral catalogue, OR a
Stop-A-approved override?
Scoring: any drift from the catalogue without an override
deducts. Industry-specific labels without explicit Stop A approval
deduct heavily.
D4 — content-quality (both modes)
Question: Is the prose clear; are FRs in EARS shape; are
acceptance criteria testable and nested under their FR; do NFRs
(when present) trace to FRs; is there fabrication; is evidence
discipline upheld; is format hygiene upheld?
Scoring: penalise:
- Non-EARS FR statements (multiple
shalls, missing
<system> shall, intention-shaped triggers, passive-voice
responses). See spec-driven-prd-best-practices §4a.
- Non-testable acceptance criteria; ACs not nested under their FR;
ACs without
AC-<FR>.<n> IDs.
- Untraceable claims (no footnote and not derivable from the
context pack).
- Vague goals without baseline + target + measurement window.
- Missing P0/P1/P2 priority on FRs.
- Evidence-discipline violations. Apply the following severity
schedule (overrides the default-to-minor rule):
- blocker — any footnote whose URL resolves to a
gitignored / session-local / local-scratch path (per
spec-driven-prd-best-practices §8 and .gitignore).
Verdict goes to block regardless of weighted score. The
drafter is required to remove the footnote (not relabel it,
not move it to a "References" appendix).
- blocker — any footnote pointing at a non-authoritative or
non-primary source (e.g. a third-party blog post quoting the
standard instead of the standard itself). The drafter must
replace it with the primary source or drop it.
- major — a footnote that adds no value (decorative or
redundant; a competent reader could act on the section
without it). The drafter must drop it; verdict revises.
- major —
S1, S2 numeric scheme; "Citations" appendix
table present.
- minor — formatting issues (bare section reference instead
of an anchored link, missing URL on a footnote that is
otherwise valid).
- Format-hygiene violations: hard-wrapped body paragraphs;
bold-as-header; structure carried by bolded lines instead of
###/#### headers.
- Upper-section isolation violations (per
prd-template §"Per-section isolation contract"):
- Solution Summary restates problem narrative, or names owners
/ metrics / FRs / rollout detail → major per offending
section.
- Goals restates the problem, or names owners, or describes
solution mechanics → major per offending section.
- Problem Statement preempts the solution ("we will add…"),
or names owners, or pre-states goals → major per
offending section.
- Ownership / stakeholder content surfacing in any non-Ownership
upper section → major.
- Repeat offence across three or more upper sections → escalate
one finding to blocker.
User-context weaving (non-blocking quality guidance). When
user-context concepts are present, reward good craft but do NOT
treat their absence as a mandatory-coverage (D1) miss — these are
woven-context quality signals, not required sections:
- Personas that carry a JTBD job statement in the
"When … I want to … so I can …" form (minor credit; a persona
with a vague need is a
minor deduction, not a blocker).
- Journey steps that decompose into event-driven FRs/ACs rather
than sitting as narrative only.
- Role-conditioned behaviour expressed as EARS optional-feature
FRs (
Where <role> is included, the <system> shall …) with an
AC pinning the deny path.
Absence of a JTBD column, journey mapping, or role matrix on a
non-UI-forward spec is NOT a deduction (constraint: no new
blanket-mandatory section). Strict scoring of these on UI-forward
specs lives in D11.
D9 — scope-discipline (both modes when spec_kind is product or mixed; null otherwise)
Question: In product / mixed mode, is implementation
content kept out of FRs and confined to a "Technical
Considerations" appendix (mixed) or omitted entirely (product)?
Scoring: penalise:
- Any FR that names an internal component, library, datastore,
framework, language, or specific API in
product / mixed
mode.
- Inline implementation content next to FRs in
mixed mode (it
belongs in "Technical Considerations").
- Boilerplate "implementation is out of scope" / "technical design
choices are out of scope" entries in Out of Scope.
In spec_kind: technical, D9 is reported as null (not 0) — the
dimension does not apply.
D5 — changelog-completeness (publish-transition turns only)
Question: When the spec transitioned Status: draft → published
this turn (V8 — version-bump-json.kind ∈ {publish-initial, publish-redraft}), does CHANGELOG.md exist, use the
Keep-a-Changelog categories, and account for every change visible
between prior and revised?
Scope: D5 applies only on publish-transition turns. On any
turn where Status: draft at end-of-turn (initial draft OR
re-draft window edit without publish intent), D5 is reported as
null, not 0 — drafts produce no CHANGELOG (V3 / V17 / OQ-5).
Scoring: any diff that does not appear in at least one
category deducts. Wrong category placement is a minor deduction.
D6 — id-stability (update mode only)
Question: Do all prior requirement IDs (FR-, NFR-, AC-, R-,
OQ-, TS-) still resolve in the revised spec?
Scoring: silent deletion of an ID is a blocker finding.
Renumbering is a blocker finding.
Sub-rubric d6.removal-by-status (severity: blocker; defined
by prd-evolution §3 + versioning-discipline §V9):
- In
Status: draft (initial OR re-draft window — applies to any
item removed regardless of whether it was newly added in-window
or carried over from a prior published version): a removed
item that left a [Deprecated] / [Removed] stub heading, or
any prose narrating the removal ("formerly FR-07…", "removed
in this revision…"), in the draft body is a blocker
finding. Drafts delete cleanly per prd-evolution §3a / §3b
(interpretation (a)).
- In
Status: draft (any flavour): a removed in-window FR/NFR/R/
OQ/TS that did NOT trigger renumbering of successors (leaving a
numbering gap) is a blocker finding. Prior-published IDs
are NOT renumbered (V9 over the ID is permanent) — gaps in the
prior-published number space are expected and not a finding.
- In
Status: draft (re-draft window): a removed prior-published
ID for which state.json:pending_published_id_deletions does
NOT contain a matching entry is a blocker finding. The
drafter must queue the deletion for publish-time
re-materialisation; silent drops break V9 at the next publish.
- In
Status: published end-of-turn (publish-transition turns
only): a deleted prior-published item with no [Deprecated] /
[Removed] stub heading in the published artefact is a
blocker finding (V9 published-contract violation). At
publish, every entry that was in
pending_published_id_deletions MUST have been re-materialised
in the published artefact body (V8 step 4a).
- In
Status: published end-of-turn: a renumbered prior-published
item is a blocker finding (V9 numbering immutability).
D7 — versioning-correctness (update mode only)
Question: Does the version bump match the prd-evolution
rule (MAJOR/MINOR/PATCH)? Is the Updates: / Obsoletes: header
present?
Scoring: wrong bump or missing header is a major finding.
Sub-rubric d7.draft-no-change-tracking (severity: blocker;
defined by prd-evolution §5):
Applies in any turn where the spec ends Status: draft (initial
draft OR re-draft window with no publish intent). The drafted
spec body MUST NOT contain ANY of:
- A
## Changes since heading (any version suffix or "since vN"
variant).
- A
## Revision History, ## Changelog, or ## What's Changed
heading.
- An inline
[Changed in v...] marker (anywhere — heading, list
item, or paragraph).
- An HTML / markdown comment narrating what changed
(
<!-- changed: ... -->, <!-- added in v... -->).
- Prose narration of revision history ("Added in this revision",
"Removed since v1.0", inline parenthetical "(updated)" tags).
- A
CHANGELOG.md mutation this turn (overlap with
d7.publish-changelog — re-stated here so the rubric is
self-contained when the critic runs against a draft turn).
Any single occurrence is a blocker finding. The verdict
escalates to block regardless of weighted score. The fix is
to delete the offending construct (not move it, not rename it).
D8 — section-stability (update mode only)
Question: No silent renumbering. No removed gated section
without a changelog rationale.
Scoring: silently removed gated sections deduct heavily.
D10 — edit-minimalism (update mode only)
Question: Did the drafter make the smallest set of edits that fully
reflects (a) the user's stated feedback, (b) any Stop-A-approved missing
context, and (c) the ID-stability mechanics those edits trigger — and
NOTHING else?
Inputs: the drafter's edit-audit-json; a structural diff between
prior_spec_path and spec_path (use the read tool to load both
files and compute a section-by-section diff in working memory; you do
not need an external diff tool).
Scoring (start at 1.0; deduct):
- For each modified or reordered span present in the diff but NOT
listed in
edit-audit-json: deduct 0.2 and emit a major finding.
An edit the drafter cannot account for is by definition
unjustified.
- For each edit whose recorded
reason does not survive scrutiny —
e.g. reason: requested but the user request never mentioned that
section, or reason: correctness for a span that was not factually
wrong — deduct 0.2 and emit a major finding.
- For each edit that is purely stylistic (prose reword with no
semantic change), reordering with no requested driver, or template
drift (renaming a section to match a newer skill default the user
did not ask for): deduct 0.3 and emit a
major finding. If three
or more such edits are present, escalate ONE finding to blocker.
- If the drafter omitted
edit-audit-json entirely in update mode:
emit a single blocker finding and score D10 = 0.
- Upper-section edit ratchet. Any modified or reordered span
whose location is in Document Information (excluding version
mechanics), Problem Statement, Goals & Success Metrics, Users
& Personas, Stakeholders & Reviewers, or Solution Summary
carries a 2× weight against D10. An upper-section edit whose
justification does not quote / directly paraphrase a user
request: deduct 0.4 (not 0.2) and emit a major finding.
Two or more such edits in one turn: escalate ONE finding to
blocker. Rationale: the user explicitly named upper-section
stability as a hard discipline; D10's standard schedule
under-weights it.
- Per-statement scoring. D10 evaluates sentence-level diffs,
not span-level diffs. For each sentence that differs between
prior_spec_path and spec_path:
- If listed as a sentence-level entry in
edit-audit-json with
a surviving reason → no deduction.
- If not listed, OR listed only at paragraph/section
granularity → deduct 0.2 and emit
major.
- If the only difference is whitespace, punctuation,
capitalisation, grammar, or stylistic word-substitution
(i.e. the kind of change §0's per-statement gate routes to
REVERT) → deduct 0.3 and emit
major. Three or more such
sentences → escalate ONE finding to blocker.
- Default disposition in findings. D10 findings whose
recommended
fix would normally be "drop / re-justify the edit"
MUST instead read "revert the sentence to the prior wording at
<prior_spec_path>:<line>". The fix wording matters: it tells
the next drafter pass that REVERT — not re-edit — is the
required remediation.
Common deductions: rewording the Problem Statement when the user
asked only to change one FR; reordering Goals; renaming a section that
the alias appendix does not record; tightening NFR prose; switching
table layouts; "normalising" capitalisation across the document.
Out of scope for D10: ADD-only edits that the user requested
(e.g. "add an FR for X") — those are the legitimate change. D10 polices
edits to the prior content, not new content.
D11 — user-context-grounding (scored ONLY when experience-surface fired; null otherwise)
Question: For a UI-forward spec (the experience-surface axis
fired and the "User Experience: Personas, Journeys & Roles" section
is present), is the user-context treatment concrete and grounded?
Applicability: D11 is scored only when experience-surface
fired for this spec. On every other spec (non-UI-forward, or the
non-fire guardrail applied) D11 is reported as null, not 0 —
same null-not-zero convention as D5–D8/D10. This localises the
strictness to UI-forward specs and keeps it out of every other
spec's score, honouring the "no new blanket-mandatory section"
constraint.
Scoring (start at 1.0; deduct):
- Personas in the dedicated section lack a concrete JTBD job
statement (the "When … I want to … so I can …" form): deduct 0.2
per persona,
minor.
- At least one user journey for the primary affected flow is missing
or is narrative-only (no phase/step table, no mapping to FRs):
deduct 0.3,
major.
- Role-varying behaviour is described but no Roles × Permissions
matrix (or equivalent role→capability mapping) is present: deduct
0.3,
major. If the matrix omits the deny-by-default posture or
no AC pins a deny path, deduct a further 0.2, minor.
- User-context content is fabricated (invented persona/job/journey/
role with no basis in the context pack, interview answers, or a
cited source, and not marked
[TBD]/OQ-NN): this is a
fabrication finding — blocker (verdict block), consistent
with D4's fabrication rule.
Reward: concrete, source-grounded personas + JTBD + at least one
FR-decomposed journey + a deny-by-default role matrix → 1.0.
Weighted aggregation
Equal-weight mean of applicable dimensions only. Dimensions not
applicable in the current mode are reported as null, not 0,
in scores-json. D10 follows the same null-not-zero rule as D5–D8:
it is included in the weighted mean only when mode == update.
weighted = mean(dim for dim in [D1..D11] if dim is not null)
Verdict rules
- block — any
blocker finding (e.g. silent ID deletion,
fabricated facts, mandatory section missing AND no OQ entry).
- revise —
weighted < 0.7 OR any major finding.
- pass — otherwise.
Severity guidance
| Severity | When to use |
|---|
| blocker | The spec must not ship as-is. Examples: silent ID deletion in update mode; mandatory section missing without OQ; fabricated facts; any footnote citing a gitignored / non-authoritative / non-primary source (per spec-driven-prd-best-practices §8). |
| major | Significant quality issue that should block this draft. Examples: NFR section present without any traceable axis; non-testable AC across most requirements; wrong version bump kind. |
| minor | Polish issue. Examples: a single non-testable AC; missing citation on one claim; naming drift on one heading. |
Threshold tuning
The 0.7 threshold for revise is tunable. Consumers who want to
lower the bar for first drafts can raise it; consumers in
high-trust pipelines can lower it. Adjust here, not in the
.agent.md of @prd-critic.
Quality checklist (for the critic itself)