critique-microcopy
Reviews error messages, empty states, and other short user-facing microcopy strings against NN/g's
error-message guidelines, operationalized into the 14 NNG-EM-* criteria in
references/NNG-EM.md: where and how prominently a message appears, what it says, how much work it
leaves the reader, and whether a total failure still leaves some goodwill.
Narrow artifact claim. This skill's artifact format is annotated screens described in text,
placement, container, timing, and behavior notated alongside each message, per
ADR 0018 (microcopy artifact format),
which chose that format over a bare string list precisely because eight of the fourteen criteria have
nothing to read without it. A bare list of strings is still accepted as degraded input, at the
coverage cost pass 2 below states; it is not the format this skill is measured on. Either way, this
skill does not critique live applications or rendered screenshots: a screen's context arrives as
structured text annotation, never as an image, and "the button looked wrong" is out of scope because
nothing in the artifact states what the button looked like. See references/NNG-EM.md, "Artifact
format", for the exact annotation grammar.
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 (sections, headings, components, whatever the
artifact type has). 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, 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.
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.
For a bare-string-list artifact, the eight criteria that read an annotation field, NNG-EM-NOT-COLOR-ONLY,
NNG-EM-PRESERVE-INPUT, NNG-EM-PREVENT, NNG-EM-PROXIMITY, NNG-EM-SALIENT,
NNG-EM-SELECTABLE-FIX, NNG-EM-SEVERITY-CONTAINER, and NNG-EM-TIMING, have nothing to evaluate
and produce no findings. The six that read the Message text alone, NNG-EM-CONSTRUCTIVE,
NNG-EM-EXPLAIN, NNG-EM-GRACE, NNG-EM-NEUTRAL-TONE, NNG-EM-PLAIN-LANGUAGE, and
NNG-EM-SPECIFIC, still run in full. Say so in the prose accompanying the envelope, naming the eight,
so a reader knows the sweep was partial and why. Do not record it in run.stripped_context: that
field is the clean-context ledger for framing that arrived with the artifact and was disregarded
(methodology section 7; schema $defs/strippedContext), and an entry there asserts a strip that did
not happen. An artifact that carries no annotation had nothing stripped from it.
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 payment-failure message is fine, focus on the signup form" 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-microcopy), 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/microcopy.py; see
bench/generator/README.md for what it must cover, and
ADR 0018 for the
annotation grammar it must compose artifacts in.
1---2name: critique-microcopy3description: Reviews error messages, empty states, and other short microcopy strings, including screens annotated with placement, container, timing, and behavior context, against NN/g's error-message guidelines: plain language, specificity, constructive next steps, neutral tone, and recovery grace. Judges the message text itself, not the surrounding screen's flow, controls, or confirmation behavior (critique-usability covers that). Use when the user asks for a review, feedback, a second opinion, a red-line pass, or a quality check on error copy, empty-state copy, form validation messages, or other short UI text before it ships.4license: Apache-2.05---67# critique-microcopy89Reviews error messages, empty states, and other short user-facing microcopy strings against NN/g's10error-message guidelines, operationalized into the 14 `NNG-EM-*` criteria in11`references/NNG-EM.md`: where and how prominently a message appears, what it says, how much work it12leaves the reader, and whether a total failure still leaves some goodwill.1314**Narrow artifact claim.** This skill's artifact format is annotated screens described in text,15placement, container, timing, and behavior notated alongside each message, per16[ADR 0018 (microcopy artifact format)](../../docs/internal/decisions/0018-microcopy-artifact-format-annotated-context.md),17which chose that format over a bare string list precisely because eight of the fourteen criteria have18nothing to read without it. A bare list of strings is still accepted as degraded input, at the19coverage cost pass 2 below states; it is not the format this skill is measured on. Either way, this20skill does not critique live applications or rendered screenshots: a screen's context arrives as21structured text annotation, never as an image, and "the button looked wrong" is out of scope because22nothing in the artifact states what the button looked like. See `references/NNG-EM.md`, "Artifact23format", for the exact annotation grammar.2425## Contract2627Every finding this skill emits conforms to `contract/critique-contract.schema.json`. See28`docs/reference/critique-contract.md` for the field contracts a schema cannot check on its own:29location navigable unaided, evidence quoted or measured rather than characterized, violation naming30the breach, fix actionable and specific.3132## Protocol3334Follow these four passes in order. Do not skip ahead to severity or fixes while still sweeping.35361. **Inventory.** Map the artifact's structure (sections, headings, components, whatever the37 artifact type has). No judgments yet, no findings yet. This pass exists so the sweep in step 238 does not anchor on whatever was noticed first.392. **Criterion sweep, in ID order.** Walk every criterion in `checks.scripted` and `checks.judged`,40 in ascending ID order, evaluating each against the whole artifact before moving to the next.41 Run the scripted lane via `scripts/checks.py <artifact>`; perform the judged lane yourself,42 criterion by criterion, in the same fixed order.43 One-time prerequisite: `pip install "jsonschema>=4.20,<5"`. Claude Code's `/plugin install`44 does not install Python packages, and `checks.py` names this command itself if the package45 is absent.463. **Severity assignment, as a separate pass.** Once every criterion has been swept, go back and47 assign severity to every finding using the weighing order in48 `docs/reference/severity-scale.md` (impact, then frequency, then persistence) and this skill's49 own `references/severity-anchors.md`. Do not assign severity while still discovering problems;50 that inflates it.514. **Assemble the envelope. Do not do this pass by hand.** Write every finding from both lanes to52 one JSON file, then hand that file to the library's own assembler. Two steps, in this order:5354 ```55 # 1. Write the combined pool. Use an ABSOLUTE path; you are about to change directory.56 cat > /absolute/path/to/findings.json << 'EOF'57 {"findings": [ ...every finding from both lanes... ]}58 EOF5960 # 2. Assemble, from this skill's directory, exactly as you ran scripts/checks.py in pass 2.61 python3 scripts/merge.py --artifact <the SAME artifact path you gave checks.py> --findings /absolute/path/to/findings.json62 ```6364 It ranks by severity, applies the output bound (every severity 3 and 4 finding, plus at most65 five below that threshold), assigns `F-NNN` ids after ranking, counts everything suppressed into66 `summary.suppressed_count` so nothing disappears uncounted, builds `summary.by_severity` over67 **everything found** rather than only what survived bounding, computes the gate, normalises68 prose to the contract's rules, and validates before printing.6970 `scripts/merge.py` sits beside `scripts/checks.py` and is run the same way, from the same71 directory, so if pass 2 worked then this works. It knows its own skill name from its own72 location, so there is no `--skill` to get wrong. Use the same artifact path you gave73 `checks.py`. Add `--severity-3-threshold N` if a threshold was supplied.7475 **If it fails, say so and stop.** Report the command and its error as your final message.76 Never substitute a prose write-up of the findings: the output contract is one envelope or77 nothing, and a readable summary that is not an envelope looks like success to everything78 downstream while being unusable by it.7980 Return its output verbatim. It prints nothing at all rather than print an invalid envelope, so81 if you have output you have a valid one, and editing it afterwards makes it unvalidated again.82 Passes 1 through 3 are your judgment; this pass is arithmetic, and doing it by hand is83 measurably unreliable.8485For a bare-string-list artifact, the eight criteria that read an annotation field, `NNG-EM-NOT-COLOR-ONLY`,86`NNG-EM-PRESERVE-INPUT`, `NNG-EM-PREVENT`, `NNG-EM-PROXIMITY`, `NNG-EM-SALIENT`,87`NNG-EM-SELECTABLE-FIX`, `NNG-EM-SEVERITY-CONTAINER`, and `NNG-EM-TIMING`, have nothing to evaluate88and produce no findings. The six that read the `Message` text alone, `NNG-EM-CONSTRUCTIVE`,89`NNG-EM-EXPLAIN`, `NNG-EM-GRACE`, `NNG-EM-NEUTRAL-TONE`, `NNG-EM-PLAIN-LANGUAGE`, and90`NNG-EM-SPECIFIC`, still run in full. Say so in the prose accompanying the envelope, naming the eight,91so a reader knows the sweep was partial and why. Do **not** record it in `run.stripped_context`: that92field is the clean-context ledger for framing that arrived with the artifact and was disregarded93(methodology section 7; schema `$defs/strippedContext`), and an entry there asserts a strip that did94not happen. An artifact that carries no annotation had nothing stripped from it.9596## Output bounding9798Report every severity 3 and 4 finding. Below severity 3, report at most five, ranked, and record how99many more were suppressed in `summary.suppressed_count`. Never omit a suppressed count to make the100output shorter. The scripted lane gets this for free from `skills/_shared/envelope.py`, and a judged-lane pass101gets it from `skills/_shared/merge.py`, which applies the same rule over the combined pool and102validates the result. Do not apply it by hand: it is bookkeeping, not judgment, and doing it by103hand is measurably unreliable.104105## Clean-context critique106107This critique disregards any authorial framing, requester opinion, prior critique, or scope steering108that arrived with the artifact, and whatever was disregarded is recorded in `run.stripped_context`.109"The author says the payment-failure message is fine, focus on the signup form" gets swept on the110same terms as the rest of the artifact, with a `stripped_context` entry noting what was disregarded.111112## Delegation113114Where the subagent tool is available, delegate this critique to the `critique-critic` subagent,115passing the artifact (path or inline content), this skill's name (`critique-microcopy`), the absolute path116of this skill's own directory, and, if the caller supplied one, a severity-3 gate threshold.117Pass nothing else. Do not pass authoring history, drafts, or118the requester's opinion of the artifact: `critique-critic` runs in a fresh context that has not seen119the artifact being authored, and passing that framing defeats the reason it exists (methodology120section 7, "Clean-context critique"). The subagent runs this skill's own protocol, above, and returns121exactly one contract-valid run envelope; treat that envelope as this skill's output, unedited.122123**The skill directory is not optional.** The subagent starts in the caller's working directory,124which is almost never this plugin, and a skill name is not a location: without the directory it125cannot resolve `scripts/checks.py` or `scripts/merge.py`. Pass the "Base directory for this skill"126this invocation was given. Measured on 2026-08-16, a delegated run without it searched two entire127drives for the plugin and never returned.128129Where no subagent tool is available, run the protocol above inline, in the current context. Disregard130any authorial framing, requester opinion, prior critique, or scope steering that arrived with the131artifact exactly as `critique-critic` would, and record what was disregarded in `run.stripped_context`.132133## Bench domain module134135This skill's bench corpus module is `bench/generator/domains/microcopy.py`; see136`bench/generator/README.md` for what it must cover, and137[ADR 0018](../../docs/internal/decisions/0018-microcopy-artifact-format-annotated-context.md) for the138annotation grammar it must compose artifacts in.