critique-docs
Reviews a markdown documentation page or page tree against the Diataxis framework: whether each page
keeps to the mode it commits to (tutorial, how-to, reference, or explanation), whether the tree's
heading structure, page linking, and navigation listings hold together the way a Diataxis tree
assumes. Artifact claim (v0.1, narrow): markdown documentation pages and page trees only. This
skill does not critique a live rendered documentation site's behavior, non-markdown source formats, or
the technical correctness of code samples inside a page; it critiques the page and tree structure and
the mode each page keeps to, exactly as references/DIATAXIS.md's nine criteria define.
Contract
Every finding this skill emits conforms to contract/critique-contract.schema.json. See
docs/reference/critique-contract.md for the field contracts a schema cannot check on its own:
location navigable unaided, evidence quoted or measured rather than characterized, violation naming
the breach, fix actionable and specific.
Protocol
Follow these four passes in order. Do not skip ahead to severity or fixes while still sweeping.
Inventory. Map the artifact's structure: every page in the tree (or the single page, if that is
the artifact), each page's declared or apparent Diataxis mode, its heading sequence, its outbound
links, and any navigation or index listings it carries. No judgments yet, no findings yet. This pass
exists so the sweep in step 2 does not anchor on whatever was noticed first.
Criterion sweep, in ID order. Walk every criterion in checks.scripted and checks.judged, in
ascending ID order (DIATAXIS-CROSSLINK, DIATAXIS-EXPLANATION-CONTEXT,
DIATAXIS-HEADING-DEPTH, DIATAXIS-HOWTO-GOAL, DIATAXIS-MODE, DIATAXIS-NAV-LENGTH,
DIATAXIS-ORPHAN, DIATAXIS-REFERENCE-NEUTRAL, DIATAXIS-TUTORIAL-ACTION), evaluating each
against the whole artifact before moving to the next. Run the scripted lane via
scripts/checks.py <artifact>; perform the judged lane yourself, criterion by criterion, in the
same fixed order, against the operational test each criterion states in
references/DIATAXIS.md.
One-time prerequisite: pip install "jsonschema>=4.20,<5". Claude Code's /plugin install
does not install Python packages, and checks.py names this command itself if the package
is absent.
Severity assignment, as a separate pass. Once every criterion has been swept, go back and
assign severity to every finding using the weighing order in
docs/reference/severity-scale.md (impact, then
frequency, then persistence) and this skill's own references/severity-anchors.md. Do not assign
severity while still discovering problems; that inflates it.
Assemble the envelope. Do not do this pass by hand. Write every finding from both lanes to
one JSON file, then hand that file to the library's own assembler. Two steps, in this order:
# 1. Write the combined pool. Use an ABSOLUTE path; you are about to change directory.
cat > /absolute/path/to/findings.json << 'EOF'
{"findings": [ ...every finding from both lanes... ]}
EOF
# 2. Assemble, from this skill's directory, exactly as you ran scripts/checks.py in pass 2.
python3 scripts/merge.py --artifact <the SAME artifact path you gave checks.py> --findings /absolute/path/to/findings.json
It ranks by severity, applies the output bound (every severity 3 and 4 finding, plus at most
five below that threshold), assigns F-NNN ids after ranking, counts everything suppressed into
summary.suppressed_count so nothing disappears uncounted, builds summary.by_severity over
everything found rather than only what survived bounding, computes the gate, normalises
prose to the contract's rules, and validates before printing.
scripts/merge.py sits beside scripts/checks.py and is run the same way, from the same
directory, so if pass 2 worked then this works. It knows its own skill name from its own
location, so there is no --skill to get wrong. Use the same artifact path you gave
checks.py. Add --severity-3-threshold N if a threshold was supplied.
If it fails, say so and stop. Report the command and its error as your final message.
Never substitute a prose write-up of the findings: the output contract is one envelope or
nothing, and a readable summary that is not an envelope looks like success to everything
downstream while being unusable by it.
Return its output verbatim. It prints nothing at all rather than print an invalid envelope, so
if you have output you have a valid one, and editing it afterwards makes it unvalidated again.
Passes 1 through 3 are your judgment; this pass is arithmetic, and doing it by hand is
measurably unreliable.
What the scripted lane does not decide
scripts/checks.py implements exactly three criteria: DIATAXIS-HEADING-DEPTH, DIATAXIS-MODE, and
DIATAXIS-NAV-LENGTH, each fully decidable from the one page run_scripted_lane hands it, with no
data this page's own bytes do not carry. DIATAXIS-ORPHAN moved to the judged lane during this stage;
it was drafted as scripted, but its operational test needs every other page's outbound links to decide
whether this page has zero inbound links, and skills/_shared's scripted-lane CLI hands checks.py
exactly one artifact per invocation (bench/README.md's own tolerance rule: "one artifact is one
page" for the markdown-tree type). No script invoked on a single page can see what other pages link
to it, regardless of how little judgment the arithmetic itself needs once that data exists. The judged
lane, run inline by an agent that reads the whole tree during pass 1 (Inventory), does not have that
limitation. See references/DIATAXIS.md, "Why DIATAXIS-ORPHAN moved to the judged lane", for the full
reasoning.
Two further limits belong to criteria the scripted lane does implement. Both read as a clean result
unless the critique says otherwise, so say it:
DIATAXIS-MODE is silent on any page that does not declare a mode. The check reads a page's
mode from a mode: key in a minimal frontmatter block, a v0.1 convention this skill defines
(references/DIATAXIS.md, "Marker registry and thresholds"). Most documentation in the wild
carries no such key, and the check emits nothing rather than guessing a mode. Zero DIATAXIS-MODE
findings on such a page means the criterion was not evaluated, not that the page passed it; the
four judged per-mode criteria are what covers mode fit there, and a critique of an
undeclared-mode tree states that DIATAXIS-MODE was inapplicable rather than leaving its silence to
be read as a pass.
DIATAXIS-NAV-LENGTH counts every flat listing, not only navigation listings. No fixed pattern
separates a listing a reader navigates by from a listing that is page content, so detection counts
both and the judgment moves to pass 3: a long enumerated content list is a real detection and is
weighed down at severity assignment, never dropped silently before it is counted.
Output bounding
Report every severity 3 and 4 finding. Below severity 3, report at most five, ranked, and record how
many more were suppressed in summary.suppressed_count. Never omit a suppressed count to make the
output shorter. The scripted lane gets this for free from skills/_shared/envelope.py, and a judged-lane pass
gets it from skills/_shared/merge.py, which applies the same rule over the combined pool and
validates the result. Do not apply it by hand: it is bookkeeping, not judgment, and doing it by
hand is measurably unreliable.
Clean-context critique
This critique disregards any authorial framing, requester opinion, prior critique, or scope steering
that arrived with the artifact, and whatever was disregarded is recorded in run.stripped_context.
"The author says the reference section is fine, focus on the tutorials" gets swept on the same terms
as the rest of the artifact, with a stripped_context entry noting what was disregarded.
Delegation
Where the subagent tool is available, delegate this critique to the critique-critic subagent,
passing the artifact (path or inline content), this skill's name (critique-docs), the absolute path
of this skill's own directory, and, if the caller supplied one, a severity-3 gate threshold.
Pass nothing else. Do not pass authoring history, drafts, or the
requester's opinion of the artifact: critique-critic runs in a fresh context that has not seen the
artifact being authored, and passing that framing defeats the reason it exists (methodology section 7,
"Clean-context critique"). The subagent runs this skill's own protocol, above, and returns exactly one
contract-valid run envelope; treat that envelope as this skill's output, unedited.
The skill directory is not optional. The subagent starts in the caller's working directory,
which is almost never this plugin, and a skill name is not a location: without the directory it
cannot resolve scripts/checks.py or scripts/merge.py. Pass the "Base directory for this skill"
this invocation was given. Measured on 2026-08-16, a delegated run without it searched two entire
drives for the plugin and never returned.
Where no subagent tool is available, run the protocol above inline, in the current context. Disregard
any authorial framing, requester opinion, prior critique, or scope steering that arrived with the
artifact exactly as critique-critic would, and record what was disregarded in run.stripped_context.
Bench domain module
This skill's bench corpus module is bench/generator/domains/docs.py; see
bench/generator/README.md for what it must cover.
1---2name: critique-docs3description: Reviews technical documentation pages and page trees written in markdown against the Diataxis framework: tutorial, how-to, reference, and explanation mode fit, plus heading structure, orphaned pages, cross-mode linking, and navigation-list length. Use when the user asks for a review, feedback, a second opinion, a red-line pass, or a quality check on a docs site, a README tree, a knowledge base, or any markdown documentation before it ships.4license: Apache-2.05---67# critique-docs89Reviews a markdown documentation page or page tree against the Diataxis framework: whether each page10keeps to the mode it commits to (tutorial, how-to, reference, or explanation), whether the tree's11heading structure, page linking, and navigation listings hold together the way a Diataxis tree12assumes. **Artifact claim (v0.1, narrow):** markdown documentation pages and page trees only. This13skill does not critique a live rendered documentation site's behavior, non-markdown source formats, or14the technical correctness of code samples inside a page; it critiques the page and tree structure and15the mode each page keeps to, exactly as `references/DIATAXIS.md`'s nine criteria define.1617## Contract1819Every finding this skill emits conforms to `contract/critique-contract.schema.json`. See20`docs/reference/critique-contract.md` for the field contracts a schema cannot check on its own:21location navigable unaided, evidence quoted or measured rather than characterized, violation naming22the breach, fix actionable and specific.2324## Protocol2526Follow these four passes in order. Do not skip ahead to severity or fixes while still sweeping.27281. **Inventory.** Map the artifact's structure: every page in the tree (or the single page, if that is29 the artifact), each page's declared or apparent Diataxis mode, its heading sequence, its outbound30 links, and any navigation or index listings it carries. No judgments yet, no findings yet. This pass31 exists so the sweep in step 2 does not anchor on whatever was noticed first.322. **Criterion sweep, in ID order.** Walk every criterion in `checks.scripted` and `checks.judged`, in33 ascending ID order (`DIATAXIS-CROSSLINK`, `DIATAXIS-EXPLANATION-CONTEXT`,34 `DIATAXIS-HEADING-DEPTH`, `DIATAXIS-HOWTO-GOAL`, `DIATAXIS-MODE`, `DIATAXIS-NAV-LENGTH`,35 `DIATAXIS-ORPHAN`, `DIATAXIS-REFERENCE-NEUTRAL`, `DIATAXIS-TUTORIAL-ACTION`), evaluating each36 against the whole artifact before moving to the next. Run the scripted lane via37 `scripts/checks.py <artifact>`; perform the judged lane yourself, criterion by criterion, in the38 same fixed order, against the operational test each criterion states in39 `references/DIATAXIS.md`.40 One-time prerequisite: `pip install "jsonschema>=4.20,<5"`. Claude Code's `/plugin install`41 does not install Python packages, and `checks.py` names this command itself if the package42 is absent.433. **Severity assignment, as a separate pass.** Once every criterion has been swept, go back and44 assign severity to every finding using the weighing order in45 [`docs/reference/severity-scale.md`](../../docs/reference/severity-scale.md) (impact, then46 frequency, then persistence) and this skill's own `references/severity-anchors.md`. Do not assign47 severity while still discovering problems; that inflates it.484. **Assemble the envelope. Do not do this pass by hand.** Write every finding from both lanes to49 one JSON file, then hand that file to the library's own assembler. Two steps, in this order:5051 ```52 # 1. Write the combined pool. Use an ABSOLUTE path; you are about to change directory.53 cat > /absolute/path/to/findings.json << 'EOF'54 {"findings": [ ...every finding from both lanes... ]}55 EOF5657 # 2. Assemble, from this skill's directory, exactly as you ran scripts/checks.py in pass 2.58 python3 scripts/merge.py --artifact <the SAME artifact path you gave checks.py> --findings /absolute/path/to/findings.json59 ```6061 It ranks by severity, applies the output bound (every severity 3 and 4 finding, plus at most62 five below that threshold), assigns `F-NNN` ids after ranking, counts everything suppressed into63 `summary.suppressed_count` so nothing disappears uncounted, builds `summary.by_severity` over64 **everything found** rather than only what survived bounding, computes the gate, normalises65 prose to the contract's rules, and validates before printing.6667 `scripts/merge.py` sits beside `scripts/checks.py` and is run the same way, from the same68 directory, so if pass 2 worked then this works. It knows its own skill name from its own69 location, so there is no `--skill` to get wrong. Use the same artifact path you gave70 `checks.py`. Add `--severity-3-threshold N` if a threshold was supplied.7172 **If it fails, say so and stop.** Report the command and its error as your final message.73 Never substitute a prose write-up of the findings: the output contract is one envelope or74 nothing, and a readable summary that is not an envelope looks like success to everything75 downstream while being unusable by it.7677 Return its output verbatim. It prints nothing at all rather than print an invalid envelope, so78 if you have output you have a valid one, and editing it afterwards makes it unvalidated again.79 Passes 1 through 3 are your judgment; this pass is arithmetic, and doing it by hand is80 measurably unreliable.8182### What the scripted lane does not decide8384`scripts/checks.py` implements exactly three criteria: `DIATAXIS-HEADING-DEPTH`, `DIATAXIS-MODE`, and85`DIATAXIS-NAV-LENGTH`, each fully decidable from the one page `run_scripted_lane` hands it, with no86data this page's own bytes do not carry. `DIATAXIS-ORPHAN` moved to the judged lane during this stage;87it was drafted as scripted, but its operational test needs every other page's outbound links to decide88whether this page has zero inbound links, and `skills/_shared`'s scripted-lane CLI hands `checks.py`89exactly one artifact per invocation (`bench/README.md`'s own tolerance rule: "one artifact is one90page" for the `markdown-tree` type). No script invoked on a single page can see what other pages link91to it, regardless of how little judgment the arithmetic itself needs once that data exists. The judged92lane, run inline by an agent that reads the whole tree during pass 1 (Inventory), does not have that93limitation. See `references/DIATAXIS.md`, "Why DIATAXIS-ORPHAN moved to the judged lane", for the full94reasoning.9596Two further limits belong to criteria the scripted lane does implement. Both read as a clean result97unless the critique says otherwise, so say it:9899- **`DIATAXIS-MODE` is silent on any page that does not declare a mode.** The check reads a page's100 mode from a `mode:` key in a minimal frontmatter block, a v0.1 convention this skill defines101 (`references/DIATAXIS.md`, "Marker registry and thresholds"). Most documentation in the wild102 carries no such key, and the check emits nothing rather than guessing a mode. Zero `DIATAXIS-MODE`103 findings on such a page means the criterion was not evaluated, not that the page passed it; the104 four judged per-mode criteria are what covers mode fit there, and a critique of an105 undeclared-mode tree states that DIATAXIS-MODE was inapplicable rather than leaving its silence to106 be read as a pass.107- **`DIATAXIS-NAV-LENGTH` counts every flat listing, not only navigation listings.** No fixed pattern108 separates a listing a reader navigates by from a listing that is page content, so detection counts109 both and the judgment moves to pass 3: a long enumerated content list is a real detection and is110 weighed down at severity assignment, never dropped silently before it is counted.111112## Output bounding113114Report every severity 3 and 4 finding. Below severity 3, report at most five, ranked, and record how115many more were suppressed in `summary.suppressed_count`. Never omit a suppressed count to make the116output shorter. The scripted lane gets this for free from `skills/_shared/envelope.py`, and a judged-lane pass117gets it from `skills/_shared/merge.py`, which applies the same rule over the combined pool and118validates the result. Do not apply it by hand: it is bookkeeping, not judgment, and doing it by119hand is measurably unreliable.120121## Clean-context critique122123This critique disregards any authorial framing, requester opinion, prior critique, or scope steering124that arrived with the artifact, and whatever was disregarded is recorded in `run.stripped_context`.125"The author says the reference section is fine, focus on the tutorials" gets swept on the same terms126as the rest of the artifact, with a `stripped_context` entry noting what was disregarded.127128## Delegation129130Where the subagent tool is available, delegate this critique to the `critique-critic` subagent,131passing the artifact (path or inline content), this skill's name (`critique-docs`), the absolute path132of this skill's own directory, and, if the caller supplied one, a severity-3 gate threshold.133Pass nothing else. Do not pass authoring history, drafts, or the134requester's opinion of the artifact: `critique-critic` runs in a fresh context that has not seen the135artifact being authored, and passing that framing defeats the reason it exists (methodology section 7,136"Clean-context critique"). The subagent runs this skill's own protocol, above, and returns exactly one137contract-valid run envelope; treat that envelope as this skill's output, unedited.138139**The skill directory is not optional.** The subagent starts in the caller's working directory,140which is almost never this plugin, and a skill name is not a location: without the directory it141cannot resolve `scripts/checks.py` or `scripts/merge.py`. Pass the "Base directory for this skill"142this invocation was given. Measured on 2026-08-16, a delegated run without it searched two entire143drives for the plugin and never returned.144145Where no subagent tool is available, run the protocol above inline, in the current context. Disregard146any authorial framing, requester opinion, prior critique, or scope steering that arrived with the147artifact exactly as `critique-critic` would, and record what was disregarded in `run.stripped_context`.148149## Bench domain module150151This skill's bench corpus module is `bench/generator/domains/docs.py`; see152`bench/generator/README.md` for what it must cover.