Work-Order Critique (the adversarial-critique rung)
The judgment layer above the deterministic gates. Gates always run (mechanism A); this is the
opt-in/forced critique on top. All verdict math is in the kernels — this skill orchestrates, it
never decides the verdict in prose. Contracts: references/critique-envelope.md (the _critique.json
③ reads) + references/critic-prompt-contract.md (the hostility contract + disk-collected discipline).
The critic is the wo-critic agent.
Inputs (from ③ — trusted runtime paths, never a transcript)
<wo-file> · <worktree> · <checkpoint_before>..<checkpoint_after> · <review_ref> (per-WO gate
envelopes) · unattended (bool) · budget_ok (bool, ④'s seam) · override_used (handle) ·
produced_changes (handle). The WO's frozen gate_floor / verified / collapsed_scc are NOT
transcribed by the skill — wo-risk-classify.sh reads them from <wo-file> via ①'s deterministic,
anchor-rejecting parser (wo-compile.sh frontmatter, H1), so a model mis-read can never disable the
forced-on red-team.
Algorithm
KERNEL="${CLAUDE_PLUGIN_ROOT}/scripts"
WO_ID="wo-NN" # the discriminator from the WO id
CDIR="<task>/work-orders/${WO_ID}.critics" # MEMORY repo — the builder's worktree CANNOT read it (M2)
CRIT_REF="<task>/work-orders/${WO_ID}._critique.json"
mkdir -p "$CDIR"
1 — risk tier inputs + oracle-tamper guard (runs BEFORE critics).
git -C "<worktree>" diff <before>..<after> --name-only > "$CDIR/files.txt"
The oracle-tamper guard runs before the risk classifier and before any critic is spawned — catching tamper
before any critic budget is spent. It needs --name-status (so deletions are visible); this is a SEPARATE
diff alongside files.txt. The guard is fail-closed: if the kernel exits non-zero or returns anything other
than a well-formed verdict object (the gate could not run), the WO HALTs with oracle_check_error — it never
falls through to the critics. Only a clean exit-0 verdict with tamper_detected:false proceeds.
The kernel is framework-agnostic: it monitors only the oracle-file list it is handed. Reconstruct that
list on the fly from the active framework's first-party recipe each run — never from a persistent project
file a builder could empty. Read the ## Oracle files declaration out of the resolved recipe body (the
recipe-resolution protocol already loaded it for this task's framework + phase; union the declarations across
the resolved recipes that apply — review/standards plus any VR/E2E recipes the project set up), emit it as the
kernel's --oracle-files JSON array, and write it to $CDIR/oracle-files.json. The list re-derives from the
trusted recipe every run, so there is no mutable local knob to disable monitoring. A framework that declares no
oracle files yields an empty array — an honest "no oracle configured" verdict (oracle_configured:false), not
a silent pass.
# derive NAME-STATUS diff — --name-only hides deletions (D); the oracle check needs them
git -C "<worktree>" diff <before>..<after> --name-status > "$CDIR/name-status.txt"
# RECONSTRUCT the oracle-file list from the resolved recipe's `## Oracle files` declaration (re-derived
# each run — NOT read from a project file). Each rule: {type, globs[], changes[], oracle_class, severity}.
# An empty array (`[]`) is the honest "no oracle configured" state. Write it fresh every run.
printf '%s' "$ORACLE_FILES_JSON" > "$CDIR/oracle-files.json" # from the resolved recipe body, this run
# invoke wo-01's kernel; pass the WO file PATH (safe read for oracle_update field, H1 — never paste diff
# content) AND the freshly-reconstructed oracle-file list (the kernel hardcodes no framework knowledge).
# Capture BOTH stdout AND the exit code: the kernel exits 2 with NO JSON on stdout on bad args / unreadable
# inputs / a malformed --oracle-files contract.
ORACLE=$(bash "$KERNEL/wo-oracle-check.sh" "<wo-file>" --diff-from "$CDIR/name-status.txt" \
--oracle-files "$CDIR/oracle-files.json"); ORACLE_RC=$?
# FAIL-CLOSED guard (defense-in-depth): a non-zero exit OR stdout that is not a well-formed verdict object
# means the oracle could NOT render a verdict — the gate did not run. Treat that as a HALT, NEVER a
# fall-through to critics. A builder who breaks the invocation (empties the rules file to a non-array,
# corrupts the diff, kills the kernel) must not thereby skip oracle monitoring and reach the critic stage.
# This is a SEPARATE reason from a genuine tamper finding so ③ / a human can tell "oracle broke" from
# "oracle caught a cheat".
if [ "$ORACLE_RC" -ne 0 ] || ! printf '%s' "$ORACLE" | jq -e 'type=="object" and has("tamper_detected")' >/dev/null 2>&1; then
jq -nc --arg wo "$WO_ID" --arg r "oracle_check_error" --arg at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'{wo_id:$wo, reason:$r, at:$at}' > "<task>/work-orders/${WO_ID}.HALT"
# emit the compact line (oracle_check_error), RETURN — the loop's terminal-HALT path escalates. NOT a critic skip.
# on tamper_detected → write oracle_tamper HALT (jq-built; NEVER string-concatenated) and RETURN — critics NOT spawned
elif [ "$(printf '%s' "$ORACLE" | jq -r '.tamper_detected')" = "true" ]; then
jq -nc --arg wo "$WO_ID" --arg r "oracle_tamper" --arg at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'{wo_id:$wo, reason:$r, at:$at}' > "<task>/work-orders/${WO_ID}.HALT"
# emit the compact line (forward oracle signals[]), RETURN — the loop's terminal-HALT path escalates
else
# severity:flag signals → record into the compact line; proceed to risk tier + critics below
fi
# pass the WO FILE PATH — the kernel reads verified/gate_floor/collapsed_scc via the safe parser (H1)
TIER=$(bash "$KERNEL/wo-risk-classify.sh" "<wo-file>" --files-from "$CDIR/files.txt" | jq -r '.risk_tier')
2 — run decision (AR-D: unattended DEFAULTS true).
runMode := project-state-read.sh <project-root> | jq -r '.runMode' # absent/bad → interactive (fail-closed)
unattended := (runMode == "autonomous") OR (the input is boolean ? it : true)
# autonomous authoritatively forces unattended=true; interactive/absent keeps the existing
# default-true floor — interactive NEVER downgrades forced-on critique (orchestrator_context_hygiene Q6).
forced = unattended AND (TIER=="high" OR verified=="false" OR override_used=="true")
dialed = read the dial: task.md `## Critique` block > project_state `**Critique:**` > off
required = forced OR dialed
run = forced OR (dialed AND TIER meets the dial's min tier)
Skip paths are kernel-produced (so required ⇒ blocking is honored — never hand-write blocking):
# not run, OR (run AND NOT budget_ok): a REQUIRED skip is NEVER silently non-blocking.
bash "$KERNEL/wo-critique-aggregate.sh" --wo "$WO_ID" --tier "$TIER" --mode none --expected 0 \
--critics-dir "$CDIR" --evaluated false $( [ "$required" = true ] && echo --required ) > "$CRIT_REF"
# then: if blocking → write the HALT marker (step 5); emit the compact line (step 6); RETURN.
# (A forced/dialed-high WO with NOT budget_ok therefore HALTs — it cannot be silently budget-skipped.)
3 — form by tier (+ ${CLAUDE_EFFORT} floor). Lenses come from risk-tiering-rules.json tier_lenses:
low → 1 critic {correctness} · medium → panel {security, correctness} · high → red-team
{security, correctness, meets-ac}. ${CLAUDE_EFFORT} ∈ {xhigh,max} raises the floor one step (never
lowers high). A security lens is guaranteed at medium+ (so executable-code changes always get one).
4 — spawn the critics (AR-F: fan-out is the unattended primitive). For each lens, spawn ONE
wo-critic agent via the Task tool (fresh, independent — NOT a fork). Give each, as trusted
runtime context: <worktree>, <before>..<after>, <review_ref>, the WO ## Done = checklist, the
design block, its lens, the methodology block, and its output path
$CDIR/${WO_ID}.critic-<k>.json. Do
not read the Task return for the verdict — the critic writes its verdict file; you read that
(disk-is-truth). MODE="fanout"; EXPECTED=<number of critics spawned>.
The methodology block goes to all three lenses, meets-ac included. Write it to
$CDIR/methodology.txt and inject that file's content verbatim, inside
=== METHODOLOGY (source=dev-guides, refs=…) === … === END METHODOLOGY ===. Compose it from ONE named section, never the whole
file: awk '/^### The observation gets recorded/,0' "${CLAUDE_PLUGIN_ROOT}/references/tdd-workflow.md", unioned with every
development/tdd-spec-driven/* slug in the task's _dev-guides-load.json
guides_actually_loaded[] when that record exists — already-loaded material, so nothing is fetched
here and no critic is given permission to go looking. The rest of that file is build-time procedure
a critic cannot act on, and padding a critic prompt buys a repair round.
Never from the task folder, which is where the builder's own account of its tests lives. Without
it a critic cannot tell a repair that met the standard from a change that moved it.
repair-accept-check.sh halts on a modified test and routes the decision to a person, which stops
the build closing on its own reason but says nothing about which of the two happened. Rule and
rationale: references/critic-prompt-contract.md.
The design block goes to all three lenses too. A work-order has no architecture component file,
and it does not need one: ## Build context is the architecture slice for this unit, pasted rather
than referenced, which is exactly what a component file's design body is. Write it to
$CDIR/design.txt and inject that file verbatim, inside
=== COMPONENT DESIGN (source=<wo path>#Build context) === … === END COMPONENT DESIGN ===.
Extract it mechanically — awk '/^## Build context/{f=1;next} f&&/^## /{exit} f' — never a
summary, for the reason the methodology cut is a fixed heading. Without it a critic cannot tell a
finding whose remedy fits the mechanism the design names from one whose only fix is to build the
unit another way, and only the second kind is design_change. A work-order missing the section
renders an empty block: an absent design is not a design that permits anything, and a critic that
was handed nothing marks nothing. Rule: references/critic-prompt-contract.md.
TeamCreate is an attended-only escalation (AR-F), not the unattended default — one-team-per-session makes it unusable in a per-WO loop. If a team IS used and falls back, set
MODE="team-fallback-to-fanout"(the kernel blocks a degraded high WO). Pre-flight the team slot; never silently ship the weaker fan-out on a high WO that demanded a team.
5 — aggregate (fail-closed kernel) + the ②-owned degrade.
bash "$KERNEL/wo-critique-aggregate.sh" --wo "$WO_ID" --tier "$TIER" --mode "$MODE" \
--expected "$EXPECTED" --critics-dir "$CDIR" --evaluated true \
$( [ "$required" = true ] && echo --required ) \
$( [ "$produced_changes" = false ] && echo --diff-empty ) > "$CRIT_REF"
if [ "$(jq -r '.blocking' "$CRIT_REF")" = "true" ]; then
# ②-owned tooth (AR-B): write the HALT marker with the KERNEL's halt_reason (M2 — not a skill-computed
# label). NEVER edit /review's _review.json.
jq -nc --arg wo "$WO_ID" --argjson r "$(jq -c '.halt_reason' "$CRIT_REF")" \
--arg at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'{wo_id:$wo, reason:$r, at:$at}' > "<task>/work-orders/${WO_ID}.HALT"
fi
6 — compact line (progress only; the truth is the on-disk _critique.json ③ re-reads at merge).
wo-NN critique=<overall> tier=<tier> mode=<mode> blocking=<bool> critique_ref=<path>
7 — RETURN to ③. ③ owns the merge decision + status; ② only produced the verdict + the HALT marker.
Enforcement is ③'s lane (or a human / /goal reading the non-green wo-ship-gate.sh line) — there is
no interim automated merge-enforcement until ③ ships (honest, AR-B).
Hard boundaries (what this skill is NOT)
- No verdict logic in prose —
wo-risk-classify.sh+wo-critique-aggregate.shdecide; this skill only routes inputs and spawns critics. - Never edits
_review.json(lane + clobber; AR-B). ②'s teeth are the_critique.jsonblockingfield + thewo-NN.HALTmarker + thewo-ship-gate.shverdict. - No status / merge / PR — that is ③. No budget governor — that is ④ (this skill only honors
budget_ok). - Fresh critics, never forks — the honest-validation guarantee. Critic verdicts come from files, never the Task transcript / mailbox prose.