Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

Back to skills

Work

ASecurity

Use when the user says \"work this\", \"run a work loop\", \"craft this\", \"do this properly\", \"take this through clarify plan and verify\", \"run it through the gate\", \"/work\", \"/craft\", or hands over a substantial change that has no domain workflow of its own and should be planned, approved and independently verified before it lands. NEGATIVE ROUTING: a code change or bug fix is /dev; a dataset, table, figure or number is /ds; long-form prose is /writing; a talk built from a researc...

21 stars
0 votes
0 copies
0 views
Added 9/19/2026
toolsrustgoshellbashnodeexpressawsgitapi

Works with

claude codeterminalcliapi

Security Analysis

A100/100

Scanned 9/19/2026

Install to Claude Code

$npx -y skills add edwinhu/workflows --skill work --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Work?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Work
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/edwinhu-work/badge)](https://www.skillsdirectory.com/skills/edwinhu-work)

More formats (shields.io, HTML) on the badges page.

Download Zip
Files
SKILL.md
---
name: work
description: "Use when the user says \"work this\", \"run a work loop\", \"craft this\", \"do this properly\", \"take this through clarify plan and verify\", \"run it through the gate\", \"/work\", \"/craft\", or hands over a substantial change that has no domain workflow of its own and should be planned, approved and independently verified before it lands. NEGATIVE ROUTING: a code change or bug fix is /dev; a dataset, table, figure or number is /ds; long-form prose is /writing; a talk built from a research paper is /workshop; lecture notes or course slides are teaching:notes and teaching:slides; a skill, workflow or plugin in this repo is skill-creator, workflow-creator or plugin-creator. Each of those is this loop plus a domain gate, and work is only the fallback when none of them fits."
argument-hint: 'the task to run through the loop'
allowed-tools: [Bash, Read, Edit, Write, Grep, Glob, AskUserQuestion, EnterPlanMode, ExitPlanMode, Agent, Monitor, PushNotification]
---

# craft — clarify → plan → goal → workflow → human review

**What this skill carries** — grep `references/` for any subject the names below miss:
!`d=${CLAUDE_SKILL_DIR}; command -v skill-toc >/dev/null 2>&1 && exec skill-toc "$d"; s=$HOME/.claude/skills/plugin-utils/bin/skill-toc; [ -x "$s" ] && exec "$s" "$d"; echo "(skill-toc unavailable: references and scripts are NOT listed here — install the plugin-utils plugin, or start a new session so its bin/ reaches PATH)"`

A structured loop for tasks worth doing properly: clarify with the user, draft a plan they
edit and approve, self-set a goal, run `workflow.js` — dispatched through farm-out — to implement
and independently verify, then put the result in front of the human in tuicr. Human rejection
routes back to CLARIFY.

```
 ┌────────── human REJECT / criteria wrong ──────────────────────────────┐
 ▼                                                                       │
CLARIFY ─► PLAN ─► GOAL ─► workflow.js ──PASS──► HUMAN REVIEW (tuicr) ──┤
 (ask)    (draft,  (self-   IMPLEMENT, then       │ approved → done   │
           user     send)   VERIFY ∥ MECHANICAL   │ findings → fix ─► re-run
           edits)           ∥ third-party (opt-in,│            workflow.js (subset)
                            advisory) → JS gate   │
                              ▲    │
                              └─fix┘ FAIL — re-run selector is tasksThatFlagged
                                    + mechanicalThatFailed + lensesThatFlagged
```

**Plan review is computed and happens before dispatch, not inside it** — `plan-lint.ts` over the
built args and `plan-preflight.ts` executing their commands at baseline, enforced by
`work-dispatch.sh` while the run is still armed. No agent reads the plan markdown for defects
(see *Plan review*).

Everything lives in this skill directory — `workflow.js`, `references/third-party.md`,
`scripts/work-dispatch.sh` (Phase 3+4 in one call), `scripts/work-pending.sh` (is a dispatch
owed?), `scripts/compose-goal.sh` (the objective, and the ceiling the hold is armed with),
`scripts/human-review-gate.sh`, `scripts/work-result.sh`.
Nothing here depends on any plugin.

**The gate has a test suite; run it after touching `workflow.js`** — `node --check` proves only that
the file parses. `scripts/workflow-harness.mjs` executes the script for real against stubbed hooks,
and `scripts/workflow.test.ts` asserts the invariants that decide PASS/FAIL: fail-closed on every
dead agent, the three `redCommand` verdicts, `overallPass === false` implying a non-empty selector,
the `readOnly` n/a dimensions. `scripts/plan-review.test.ts` guards the computed plan-review rules
and the absence of the judged layer. Add both to `mechanicalChecks` on a run that edits the spine:

```bash
bun test ${CLAUDE_PLUGIN_ROOT}/skills/work/scripts/   # absolute path: a bare relative path is
# read as a NAME FILTER and exits 1 having matched nothing — identical to a real failure
```

## State

Two locations, one owner each:

| Path | Holds | Owner |
|---|---|---|
| `<plansDirectory>/<slug>.md` | the approved plan — **the run's authority**, the file that gets hashed | plan mode (native); craft only reads and hashes it |
| `.craft/<run-id>/` | args, verdict JSON, and `plan-<hash12>.md` — the archived bytes each round ran under | craft |

`plansDirectory` decides where that plan lives, and craft **honours whatever it is set to** —
`"./.claude/plans"` and `"./.planning"` (what the domain workflows use) are
equally valid. The value is resolved relative to the project root, so the plan is project-local and
craft hashes it in place — no copy. Unset at every tier, the default is `.claude/plans`. `run-id` is
a short date-slug like `0806-fix-auth`. Both `.craft/` and the plans directory want to be
gitignored — add them before the
first run if the repo would otherwise track them. Clean up `.craft/<run-id>/` when the run
completes, unless the user wants provenance kept; leave the plan file alone either way, it's plan
mode's. **The plan file is not durable and craft does not own it** — plan mode memoizes one slug per
session, so re-entering plan mode overwrites the plan in place, and the directory is gitignored. So
dispatch archives the bytes it hashed to `.craft/<run-id>/plan-<hash12>.md` (content-addressed: an
amended round adds one, never overwrites). That archive is the only copy of what a run was approved
with — if the plan matters beyond the run, `git add -f` it before cleaning the run dir. There is no `goal.md`: the plan holds the success criteria, and the goal is a
session-level condition (Phase 3), not a file.

## Phase 1 — CLARIFY

**Before any task reconnaissance**, AskUserQuestion on the axes that shape the plan (skip axes
the request already answers; batch up to 4 per call):

1. Desired outcome — what does done look like?
2. Exclusions — what must NOT change?
3. Constraints — style, deps, compatibility.
4. Observable success criteria — what command/check proves it? *A command with a meaningful
   exit code becomes a `mechanicalChecks` entry in the plan's Run sizing block.*
5. Review surface — working tree (default), commit range, or PR?
6. **Third-party review?** — none (default) / codex / gemini / both. *This is the only opt-in
   moment; a plan approved without the opt-in line never runs third-party.*
7. **Read-only runs only — use an agent team for discovery? (default yes.)** Ask this axis only
   when the run is an audit (`readOnly: true`); for a run that writes, the answer is always no and
   the axis is skipped. The honest tradeoff: a team of communicating auditors catches **cross-file**
   defects that isolated lenses structurally cannot see — each lens judges alone and no lens holds
   two files at once. The cost is that a team's findings are **correlated**, so refutation must stay
   **outside** the team. Answering no is a real option; it costs discovery breadth, not gate
   integrity. For where the team runs and how its findings reach the gate, see *Where the agent team
   lives*.

Gate: you can plan without guessing. If answers surface a trivial task, say so and exit the
loop — see red flags.

## Phase 2 — PLAN

EnterPlanMode. Explore, then draft a plan that MUST contain:

- **Task table**: `(id, name, work, writable paths, acceptance)` — acceptance is a checkable
  criterion per task, not a vibe. Add **`dependsOn`** to any row whose refs or inputs are files
  another row writes: it is the only thing that orders IMPLEMENT, and rows without it are
  implemented concurrently (so their writable paths must be disjoint).
- **Every claim is a `tasks[].acceptance` clause or a `mechanicalChecks` entry**, plus the review
  surface. A criterion belonging to no task belongs nowhere: it is a sentence nothing runs, and it is
  the largest defect surface a plan has. Whole-deliverable facts become a mechanicalCheck; per-task
  facts become that task's acceptance. Prose may explain WHY, never assert WHAT — the moment prose
  states a criterion there are two representations of one fact, and they drift within one amendment.
  `plan-lint`'s `prose-command` rule enforces the executable half: a command in prose that no
  acceptance, redCommand or mechanicalCheck runs is a MAJOR.
- **Run sizing** — the scrutiny the gate will apply (Phase 4 documents what each knob costs):

  ```
  ## Run sizing
  Review lenses:     criteria-vs-artifacts, scope-fidelity   (+ one line per added lens, with the risk it covers)
  Mechanical checks: <name> — `<exact command>`              (omit the section if none)
  Scored checks:     <key> — <what it scores>, ADVISORY: never gates  (opt-in, no default; omit if none)
  Test-first:        <task id> — `<redCommand>`               (one line per red-gated task; omit if none)
  Red dispositions:  <task id> — <why this task carries no red gate>  (one line per dispositioned task; omit if none)
  Third-party review: codex                                   (only if opted in at CLARIFY)
  ```

Every task fans out to 1 implementer + 1 verifier, plus 2 probes if it carries a `redCommand`; every
lens to 1 reviewer + up to `refutersPerLens` refuters; **every `priorFindings` entry costs one
refuter**; every `scoredChecks` item costs one agent, advisory or not. If that runs past ~50, the
plan is too coarse-grained for one gate: split it into sequenced craft runs.

**This is enforced, not advised.** `workflow.js` computes its own fan-out floor
(`2·tasks + 2·redGatedTasks + lenses + mechanicalChecks + scoredItems + priorFindings + thirdParty`)
at arg-validation and **throws before dispatching anything** if it exceeds `maxAgents` (default 50);
the error prints the per-dimension breakdown. Raising `maxAgents` is legitimate; raising it silently
at dispatch time is what the throw prevents, because sizing is the user's call at approval time.

**Sizing lives in the plan because it shapes the gate.** Choosing lenses or dropping a mechanical
check after approval would weaken the verdict without changing a byte the user signed off on — the
hash covers the `craft:dispatch` spec block, so anything that decides PASS/FAIL has to be inside it.

**An audit needs a plan too.** `readOnly` still requires `planPath` + `specHash`, and the plan it
hashes is a **charter**, not a work order: what is being audited, which lenses judge it, which
mechanical checks run, and the standing instruction that nothing may be written. The task table may
be empty. Do **not** shortcut by hashing the artifact under audit instead: the AUTHORITY block tells
every agent the hashed file is its *only authority*, so hashing the audited file would tell each lens
that the thing it is judging is the standard it judges against.

**A domain workflow's plan opens with YAML frontmatter `workflow: <name>`** (`dev`, `ds`, `writing`,
`workshop`, or plugin-qualified like `teaching:notes`) — it is what the re-seeded `Implement the
following plan:` prompt shows first, and what routes that session back to the owning skill.

**Arm the run before calling ExitPlanMode.** The plan must carry a dispatch block — every
`workflow.js` arg except `planPath`/`specHash`, which `work-dispatch.sh` injects because a block
cannot state its own hash:

```
<!-- craft:dispatch
{"runId": "0813-slug", "goalTurns": 12, "args": { … }}
-->
```

Writing it is what arms the run, and the plan is the only file plan mode may write — which is also
the only thing that survives approval. **Claude Code clears the context when a plan is approved near
the ceiling** and re-seeds a bare `Implement the following plan:` session with no craft in it, which
will otherwise implement in the main thread. While a plan is armed and no `.craft/*/args.json`
records its hash, `~/.claude/hooks/main-thread-guard.sh` denies Edit/Write/Agent in that project —
resolving the project from the nearest ancestor of `cwd`, so a `cd` cannot disarm it — and blocks the
turn from ending once; both name the dispatch command. It denies only what some task's
`writablePaths` covers (`work-dispatch.sh --covers`, failing closed when the spec cannot decide):
a path no implementer may write is not the run's output, which is what leaves the **red suite
authorable** here and nowhere else. For the one case that rule cannot reach — a path that IS a
task's output and must still exist before wave 1 — the plan declares `scaffoldPaths`, and
`work-dispatch.sh --scaffold` tells the guard to allow it. Without that list the only exit was
`--abandon`, which releases the guard for the **whole rest of the session** and silences the Stop
nudge along with it, so a plan defect became a disarmed run. A `readOnly` run never writes, so
there the Stop nudge is the only thing that fires. Derive the prose Run sizing block from the JSON;
`work-dispatch.sh` prints the fan-out it computes, so drift shows up before anything is dispatched.

The user edits the plan file and approves via ExitPlanMode. Then **hash the plan where plan mode
actually wrote it** — resolve the path, don't assume it:

```bash
# The configured location — plansDirectory, project-root-relative, default .claude/plans.
PLANS=$(jq -r '.plansDirectory // empty' .claude/settings.local.json .claude/settings.json \
  ~/.claude/settings.json 2>/dev/null | head -1); PLANS=${PLANS:-.claude/plans}
PLAN=$(ls -t "$PLANS"/*.md 2>/dev/null | head -1)
[ -n "$PLAN" ] || PLAN=$(ls -t ~/.claude/plans/*.md | head -1)   # fallback: a pre-setting session
bash ~/.claude/skills/workflows/skills/work/scripts/work-dispatch.sh --spec-hash "$PLAN"   # 64-hex spec hash
```

Whichever it resolves to is `planPath`. **Never copy the plan** — one file, hashed in place, is the
run's authority. A copy creates a second file that can drift from the one the user edits. (The run
dir's `plan-<hash12>.md` is not that: it is a dead snapshot named by its own hash, written by
dispatch and never read as authority, so nothing can edit it or drift from it.)

**Ensure `plansDirectory` is set.** Plans belong inside `projectDir`, alongside the work and the
agents. `plansDirectory` is [relative to the project
root](https://code.claude.com/docs/en/settings), so any project-relative value puts them there —
`"./.claude/plans"` and `"./.planning"` both work, and craft resolves whichever is set:

```bash
rg -n '"plansDirectory"' .claude/settings.local.json .claude/settings.json ~/.claude/settings.json 2>/dev/null
```

If no tier sets it, add one — `"plansDirectory": "./.claude/plans"` is this skill's default,
`"./.planning"` is what the domain workflows use — to the project's
`.claude/settings.json`, and gitignore that directory. Setting it at the user tier covers every
project at once, which is usually what you want. Precedence is Claude Code's own:
`.claude/settings.local.json` beats `.claude/settings.json` beats `~/.claude/settings.json`.

**It takes effect next session, not this one.** Plan mode fixes the plan's path when you enter it, so
a session that started before the setting was live still writes to `~/.claude/plans/` — which is why
the snippet above resolves the real path instead of asserting one. Do not stop the run over it, and
do not copy the file to make the path look right.

The plan's `craft:dispatch` spec block is the sole authority every dispatched agent gets; the prose
around it explains but never binds. Nothing re-derives it: the agents re-run `--spec-hash` themselves
and stop on mismatch, so an amended spec halts the run instead of silently changing the contract,
while fixing a typo in the rationale costs nothing.

## Phase 3 — HOLD + HEARTBEAT

**Arm a hold, not a goal.** No file holds the success criteria — the plan does, and it is what's
hashed. What the hold adds is mechanical: `hooks/until.ts` RUNS a check on every Stop, and while it
exits non-zero **the session starts another turn instead of returning control to the user**. That is
what runs craft's outer loop (gate FAIL → fix → re-run; tuicr findings → fix → re-review) without
the user prompting each step. `workflow.js` can't do this — it returns a verdict once.

**`work-dispatch.sh` does Phase 3 and Phase 4 in one call** — it reads the armed plan's dispatch
block, injects `planPath`/`specHash`, writes `args.json`, arms the hold, prints the `CronCreate`
call that arms the heartbeat, and starts `workflow.js` detached. Run it, **make the `CronCreate`
call it prints**, then skip to the Monitor; the rest of these two phases is what it does and why,
and what to check when it reports something odd:

```bash
bash ${CLAUDE_PLUGIN_ROOT}/skills/work/scripts/work-dispatch.sh                    # armed plan; or pass one
bash ${CLAUDE_PLUGIN_ROOT}/skills/work/scripts/work-dispatch.sh --provider codex   # "run craft through codex"
```

**The provider comes from the invocation, and every skill that wraps craft forwards it.** A provider
named in `$ARGUMENTS`, however it is spelled — `--provider codex`, `--dispatch codex`, "run this on
gpt" — is `--provider codex` on the dispatch line, whether craft was invoked directly or through `/dev`,
`/ds`, `/writing`, `/notes`, `/slides` or `/exams`. Those skills print their own dispatch recipe, so
each carries the flag too; dropping it silently runs the user's codex request on claude.
`--provider` is the ONE spelling: `work-dispatch.sh --dispatch` and `work-redispatch.sh --dispatch
codex` each exit 2 naming it, rather than dying on "unknown flag" or accepting a synonym.

**`--provider claude|codex|gemini` (default `claude`) runs the WHOLE spine on that provider** — it
reaches `farm.sh --provider`, whose wrapper remaps the tier names, so every `model: 'sonnet'` in
`workflow.js` follows with no arg change. Same flag on `work-redispatch.sh`, which is where it earns
its keep: when a round repeats its predecessor's failure exactly, a different provider is the lever
for framing lock-in (`references/convergence.md`). Whole-run granularity is structural — the provider
is chosen before `workflow.js` runs, so implementers and lenses cannot differ. It is deliberately not
written to `args.json`: differing between rounds is the point.

It needs nothing from the session's context, which is the point: a run whose context was cleared at
plan approval is recovered by this one command, with no re-exploration.

**The plan-review gates run before `args.json` is written, exiting 3 with the run still armed and
every artifact byte-identical.** Tier 1 is `plan-lint.ts` on the built args. The two probe gates
execute disjoint command sets, each command exactly once: **tier 2 runs every active task's
`redCommand`** through the script's own classifier, refusing `red-not-red` (exit 0 — the gate already
passes) and `could-not-run` (exit 127, a missing runner, `pytest` exit 4/5, or no test output at
all), so only non-zero *with* a real test result proceeds; **tier 2b runs every `mechanicalChecks`
cmd** at baseline via `plan-preflight.ts --only mechanical`, where only a `critical` refuses.
Acceptance commands are `plan-preflight`'s third probe kind and **no dispatch gate runs them** — run
`--only acceptance` by hand on a quiet tree, before arming.
`--no-red-probe` drops tier 2, `--no-mech-probe` drops tier 2b, `--no-lint` drops all of them, and
`CRAFT_RED_PROBE_TIMEOUT` / `CRAFT_MECH_PROBE_TIMEOUT` (300s each) bound their own tier's commands.
A task whose work is already COMPLETE can satisfy neither gate —
a `redCommand` is refused `red-not-red`, omitting it is refused `redcommand-missing` — so it declares
`redDisposition` instead; both scripts echo `red: N gated, M dispositioned` with each disposition,
beside the wave graph.

### Phase 3 arms TWO halves, and `work-dispatch.sh` can only arm one of them

**A DISPATCH THAT ENDS WITH A HOLD AND NO HEARTBEAT IS NOT DISPATCHED.** It is the unattended idle
this mechanism exists to prevent, and it is the likelier of the two failures because the half the
script cannot arm is the half a model has to notice.

| half | what it does | who arms it |
|---|---|---|
| **HOLD** | `hooks/until.ts` re-runs the check on every Stop and blocks the stop until it exits 0 | `work-dispatch.sh`, by writing a state file — **no action needed from you** |
| **HEARTBEAT** | a cron tick that starts a turn in a session that has already gone quiet | **you, with the `CronCreate` tool**, in the dispatch turn |

`CronCreate` is a model tool. There is no cron CLI, and a session-scoped cron lives in memory rather
than in `.claude/scheduled_tasks.json`, so no shell can raise one — which is why this script used to
self-send a `/loop` into its own pane instead. That transport is gone: it queued a line a detached
drainer typed only when the pane went IDLE, and a session working back-to-back never goes idle.
Measured 2026-09-16: `/goal` landed 127 times and missed 36, `/loop` landed 52 and missed 29.

So `work-dispatch.sh` **prints** the `CronCreate` call, with the exact cron expression and the exact
prompt text, as the last thing on every path that dispatches. **Make that call before your next
action, and report the job id it returns.** If `CronCreate` is unavailable, say so in one line —
never proceed as though the heartbeat were armed.

Nothing is typed into this session and nothing is queued, so there is no send to verify, no transport
to fall back to, and no ordering constraint against Phase 4.

**The hold's check is `work-result.sh`, normalised to 0/1.** Before the run returns there is no
`result.json` and `work-result.sh` exits 2, which `until-arm.sh` correctly refuses as could-not-run
rather than a verdict; the dispatch wraps it so the absent verdict reads as RED instead. A writing
run's hold closes on PASS alone; a `readOnly` run's closes on either verdict, because an audit's gate
legitimately FAILs and a PASS-conditioned hold would drive at an outcome the run is forbidden to
produce. The two escapes the objective states as prose — the round counter and the wall clock — are
also `--rounds` and `--minutes` on the armed hold, enforced by the hook rather than re-adjudicated.

`bash ${CLAUDE_PLUGIN_ROOT}/skills/until/scripts/until-arm.sh --status` settles whether the hold is
live. **`CronList` — the tool, not a shell — settles whether the heartbeat is.** Check both.

**Name the plan by PATH, never by a fixed sha256.** A pinned digest self-invalidates the first time
the FAIL loop does what this file prescribes: fix, **amend the plan, re-hash**, re-dispatch. The run
then PASSes against a hash the condition does not name, and the evaluator correctly reports the goal
unmet on finished work. The path is stable; the hash is the thing the loop is expected to change.

**Write the objective so a COMMAND settles it.** The hold runs the check; nothing reads the
conversation to decide whether the run is done. "The acceptance criteria hold" is unrunnable; a
`work-result.sh` exit code is the whole verdict. `compose-goal.sh` composes that text, and the
dispatch reuses it verbatim as the heartbeat's tick prompt — the one string present when a tick
fires into an otherwise empty session, which is why it carries the standing authority, the
continuation rule and the `CronDelete` teardown as well as the check.

The hold **self-clears**: `hooks/until.ts` removes the state file the moment the check exits 0, so
the normal ending needs no teardown. The heartbeat does not — a cron outlives the work and only
`CronDelete` cancels it. That asymmetry is why the tick text ends with the teardown instruction.

Phase 4 dispatches **in the same turn**, immediately after the hold is armed and the `CronCreate`
call is made. There is no queued message to wait out.

## Phase 4 — workflow.js

The args, annotated — write them to the args file as **plain JSON**, no comments, since the workflow
`JSON.parse`s it:

This fence is craft's ARGUMENT SCHEMA, not a workflow declaring its own gate.
`mechanicalChecks: [{name, cmd}, ...]` documents the parameter craft ACCEPTS; craft is the
engine that RUNS a workflow's checks and has none of its own to collapse, so P10 — which
governs what a generated workflow declares — is declared away for this region only:

<!-- wc-probe: ignore-entry-point:start -->
```js
{
  projectDir, planPath: "<the path $PLAN resolved to in Phase 2>", specHash: "<64-hex>",
  goal: "<one sentence>",
  tasks: [{id, name, work, writablePaths, acceptance, refs, redCommand|redDisposition}, ...], // from the plan's task table
  thirdParty: ["codex"],          // ONLY if the plan carries the opt-in line; else omit
  mechanicalChecks: [{name: "node-check", cmd: "node --check foo.js"}, ...],  // optional
  scoredChecks: [{key, items, prompt, schema, components, passthrough, refs, agentType}, ...], // optional; advisory, never gates
  reviewLenses: [{key, prompt, refs, agentType}, ...],            // optional; default is 2 lenses
  // Standard, and the one to carry on any multi-task plan. `work-dispatch.sh` PRINTS the wave shape
  // `dependsOn` produces; this asks whether it has to be that shape. MAJOR-capped, so it never blocks:
  // {key: "plan-parallelism", agentType: "Explore", refs: [],
  //  prompt: "Judge ONLY the approved plan's dependsOn edges. MAJOR at most, never CRITICAL — this is advisory. Given each task's `work`, `writablePaths` and `refs`: is any dependsOn edge unnecessary — could the two tasks run in the same wave? One finding per edge you would remove, naming the dependent, the dependency, and what the dependent actually needs from the dependency; if that is a BEHAVIOUR (an exit code, an observable effect) rather than a file the dependency writes, say so — an edge is a READ ordering, so a behaviour need is not one. Be specific or silent: a finding that names no concrete pair and no concrete reason is not a finding, and 'consider parallelising' is not one. Never argue for removing a test-first edge — the task that writes a failing test before the task that makes it pass — collapsing that means writing a test beside its fix, which is exactly what red-gating exists to prevent. Report nothing if every edge is load-bearing."},
  authorityExtra: "<domain rule appended to every agent's AUTHORITY block>",  // optional
  implementerAgentType: "…", verifierAgentType: "Explore",        // optional
  readOnly: true,                                                 // optional; audit an existing tree
  priorFindings: [{title, severity, detail, file, lens}, ...],    // optional; discoveries made outside this run
  freezeFindingSet: true, maxRounds: 3,                           // optional; set by work-redispatch.sh, not by hand
  maxAgents: 50, refutersPerLens: 8,                              // optional; fan-out ceilings — throws if the floor exceeds maxAgents
  refuterModel: "sonnet", refuterEffort: "medium",                // optional; null on either inherits the session default
  implementerEffort: "xhigh", verifierEffort: "medium",           // optional; null omits the key and inherits
  scoredEffort: "low", thirdPartyEffort: "low",                   // optional; null omits the key and inherits
}
```
<!-- wc-probe: ignore-entry-point:end -->

**Dispatch through farm-out, never the built-in `Workflow` tool** — the guard at
`~/.claude/hooks/main-thread-guard.sh` denies it unconditionally, so an in-session call is
dead. `farm.sh` sets `FARM_OUT_CHILD=1`, which is what lets its child make the call.
`work-dispatch.sh` runs exactly this and prints the wait loop with the run's paths filled in; what
follows is what it does, for when you are reading its output or a dispatch has gone wrong.

```bash
R=.craft/<run-id>; mkdir -p "$R"          # the runner refuses if --out's directory does not exist
# write the args object above to "$R/args.json" as JSON
# DETACHED, never foreground: a real gate runs 20-60 min and a foreground tool call caps out
# and kills it mid-run, as does a harness-tracked background task.
setsid nohup bash ${CLAUDE_PLUGIN_ROOT}/skills/farm-out/scripts/farm.sh \
  --workflow ${CLAUDE_PLUGIN_ROOT}/skills/work/workflow.js \
  --args "$PWD/$R/args.json" --out "$PWD/$R/result.json" --cwd "$PWD" \
  > "$R/run.log" 2>&1 < /dev/null &
# Wait for it: work-result.sh is one-shot and knows nothing about the dispatch. Watch BOTH the
# artifact and the process — a watcher that only greps for success is silent through a crash — and
# key liveness on THIS run's --out path, or a concurrent dispatch reads as proof ours is alive.
# farm-alive.sh keys on the RUN, not the runner's filename: the runner's event file carries
# out=<this run's --out> and its filename is the pid, so a rename cannot break the check.
while :; do
  [ -s "$PWD/$R/result.json" ] && break
  bash ${CLAUDE_PLUGIN_ROOT}/skills/work/scripts/farm-alive.sh "$PWD/$R/result.json" > /dev/null \
    || { echo "dispatch died with no verdict — see $R/run.log" >&2; exit 1; }
  sleep 30
done
bash ${CLAUDE_PLUGIN_ROOT}/skills/work/scripts/work-result.sh "$PWD/$R/result.json"
```

**`--loops N` executes that wait instead of printing it.** After dispatch `work-dispatch.sh --loops
N` hands the run to `work-loop.sh`, which polls with the liveness leg above, reads the verdict,
consults `converge-check.ts` every round, and redispatches to a cap of N rounds. N defaults to the
args `maxRounds` value, or 3 when absent; a non-numeric value is refused with exit 2 naming the flag.
`--loops 0` is the printed path unchanged — the wait loop above prints and the script exits 0.

The driver's exit codes: **0** PASS; **exit 1** the dispatch died with no verdict (see the run log it
names); **exit 2** `work-result.sh` refused the verdict; **exit 5** `converge-check.ts` reported NOT
CONVERGING, which is evidence about the BRIEF — re-plan rather than spend the remaining rounds;
**exit 6** the round cap was reached; **exit 7** a plan defect needs a scope decision, which is a
human's to make.

**Run that wait as a `Monitor`, not a foreground Bash call** — Bash caps at 10 minutes and a gate
runs 20-60. Pass the loop body as Monitor's `command` with `persistent: true` (no deadline), keeping
both terminal states: result written, and process gone without one. Then call `work-result.sh` when
it fires. Fall back to the loop across turns only where Monitor is unavailable — Bedrock, Vertex,
Foundry, or `DISABLE_TELEMETRY`/`CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` set. A Monitor dies with
the session; the detached run does not, so the hold and its heartbeat remain what survives a restart.

**When `work-result.sh` returns the verdict, send a `PushNotification` carrying the OUTCOME** —
"elide gate PASS, 33pp, both readings", never "the run finished". A run is a deliverable and takes
up to an hour, so the user has probably walked away; a notification that only reports completion
makes them come back to ask what happened, which is the thing it was supposed to save them. Here
only — not at dispatch, not at round boundaries.

`farm.sh` exits 2 on a malformed call (`--workflow` without `--out`, an `--args` file that is not
readable JSON) and non-zero when `--out` came back missing or not a JSON object. `work-result.sh`
then refuses (exit 2) unless the file is one object carrying `overallPass`, `verdict`, `scoreTable`,
`findings`, `tasksThatFlagged`, `mechanicalThatFailed` and `lensesThatFlagged` with the right types,
and prints the verdict and the score table on success. All three selectors are required: a return
dropping one channel would make a FAIL carried solely by that channel read as a clean run.

**State the residual plainly: mechanical claims are adjudicated, the rest is shape, not fidelity.**
A model transcribes the workflow's returned object into `--out`, so a fabricated object with the
right keys — outside the re-run mechanical claims — passes both checks. Reconcile the score table
against the plan's Run sizing: counts that cannot be squared with what the run was sized to dispatch
make the result unverified, not a PASS.

| param | type | effect |
|---|---|---|
| `readOnly` | `boolean` (default `false`) | Audit mode. **No Implement phase and no per-task verifiers are dispatched**, so `tasks[]` may be empty or absent (it is still required when `readOnly` is false). Lenses ∥ mechanical ∥ third-party run as usual, and **every dispatched leg** — lenses, refuters, mechanical probes and third-party runners — defaults to the `Explore` agent type, structurally no Edit/Write, unless a per-lens `agentType` says otherwise. `Explore` keeps `Bash`, so a probe can still run its command. **Residuals**, all from `Bash`, and this list is open rather than exhaustive: a `mechanicalChecks` `cmd` runs VERBATIM; any reference this spine tells a leg to follow can itself instruct a write; and `authorityExtra` and `reviewLenses[].prompt` are caller-supplied free text handed to every Bash-capable leg. What the agent type pins is the agent's volition — never what it is *told* to do. Anything a `readOnly` run hands a leg must itself be read-only. `meta.phases` is a **static five-entry literal** (`Implement, Verify, Mechanical, Third-party, Gate`) in both modes — the harness parses `meta` without running the script and rejects any computed value, so a mode-specific phase list is not expressible. `Implement` is therefore advertised and then never opened on a `readOnly` run; that is `workflow.js`'s own progress display and is cosmetic. Craft's lifecycle **Phase 1–5** (CLARIFY, PLAN, GOAL, workflow.js, HUMAN REVIEW) is a different axis and is unaffected. The task dimensions become **n/a** (`null`), not empty-and-clean: see the score-table note below. |
| `priorFindings` | `[{title, severity, detail, file?, lens?}]` | Findings discovered **outside** this run — typically by a main-chat agent team. They are not trusted: each is refuted by the same adversarial path a lens finding takes (same schema, same default-to-refuted-when-ambiguous, same fail-closed rule that a dead refuter keeps the finding), and only survivors reach the gate, where they are gated identically to lens findings. An entry with no `lens` is attributed to the reserved key `unattributed` — the same key an unkeyed `reviewLenses` entry falls back to, since it is the same situation — so a survivor always names a `lensesThatFlagged` entry. Set `lens` explicitly to get a more specific label. **`file?` is the path the finding is _about_** — it is rendered into the refuter's prompt as `[path]` for context and is never opened by craft. It is **not** a location craft reads findings from; for where a team actually writes them, see *Where the agent team lives*. `severity` must be `critical|major|minor`; a malformed entry throws at arg-validation, before any agent is dispatched. An optional `agentType` on an entry overrides the refuter's agent type for that finding alone. **Each entry costs one refuter agent**, and the fan-out is bounded by nothing but the array you pass — so the count feeds the ~50-agent ceiling below. A `minor` entry cannot change the verdict (only `critical|major` reach `survivingBlocking`), so it spends a full agent to move a display counter: submit `critical`/`major` unless you specifically want the minor counted. `scoreTable` then carries `priorFindingsSubmitted` / `priorFindingsSurviving`. |
| `mechanicalChecks` | `[{name: string, cmd: string}]` | Adds a `Mechanical` phase running in parallel with Verify. One low-effort probe agent per check runs `cmd` **verbatim** and reports `{name, exitCode, output}`; **the JS reads the exit code** — no agent asserts a pass. Fail closed: a dead or skipped probe is `exitCode: -1`, which counts as failed. Missing `name` or `cmd` throws. **A probe's report is still a claim, and the claim is adjudicated in a shell**: work-result.sh re-runs EVERY declared check — a claimed failure included, since the file is a model's transcription of the gate object — and refuses (exit 2) when the observed exit code disagrees, or when a claimed non-zero exit sits beside `overallPass: true`. The refusal names **which direction** the disagreement went, because they mean opposite things: a claimed FAILURE that passes on re-run is a probe-side flake — re-run the gate, do not re-plan — while a claimed PASS that fails on re-run is the case the adjudicator exists for. Both still exit 2; passing a non-reproducing failure would wave a genuinely flaky gate through. Re-running one command to confirm a claim is cheap and re-running N is not — which is why a workflow declares **one** mechanical entry point whose exit code is its whole mechanical verdict, never a list of commands (a list also drops a check silently, and nothing reports a check it never knew about). **A check's `cmd` must finish inside ~10 minutes, because it is run TWICE by two different callers that both cap there**: a probe agent runs it through its Bash tool (hard ceiling 600s, not tunable) and `work-result.sh` re-runs it to adjudicate the claim. A check that outlives that returns 124/137/143 — a kill, not an exit — which scored as a *failing gate* until work-result.sh learned to refuse it. Anything genuinely long (a scale run, a soak) goes **behind** the gate, not inside it: run it detached (`run_in_background`, or a `Monitor` when you want per-event notice) writing an artifact, and let `cmd` be the fast read of that artifact. **`overallPass` in `result.json` is NOT the verdict and must never be read directly, without exception** — the verdict is `work-result.sh`'s exit code (0 pass, 1 fail, 2 refused), and any caller that reads the file another way is a defect. |
| `scoredChecks` | `[{key, items, prompt, schema, components, refs, agentType?}]` (default off) | Weighted 0–10 scores, one agent per `items` entry, running in parallel with Verify. **The agent returns RAW COUNTS and craft computes every score in JS** from the caller-declared `components`, so no agent ever sees the formula it is scored by — an agent that reports its own score inflates it. **It is advisory and structurally cannot gate**: `overallPass` is computed without reading any scored value, there is no threshold and no `blockBelow`, it adds no selector channel, and even a dead agent does not flip the verdict. An unmeasured, dead or partially-reported item scores `null` with a reason — never `base`, never `0`. Absent or `[]` opens no phase and dispatches nothing, and the return then carries `scores: []` with `scoresRun`/`scoresReported` as `null` (n/a — render it as such, never `0`). A schema key that is not a declared count, a score-shaped name, or a `penalties` key the schema does not declare all throw at arg-validation, before any dispatch. **`passthrough: [<field>, …]`** declares the evidence a score never reads — the numeric denominators a finding is stated against and the item lists it is built from — which a count-only whitelist cannot express; it stays a whitelist (an undeclared field is still refused, a field cannot be both a penalty and passthrough, and the score-shaped-name check applies to it too). Declared fields come back on that item's entry under **`evidence`** — nested, so nothing can collide with a component name; absent entirely when none was declared or reported; present on a `null`-scored item and never on a dead agent. Each item counts against `maxAgents`. Contract, arithmetic and worked example: [`references/scored-checks.md`](${CLAUDE_PLUGIN_ROOT}/skills/work/references/scored-checks.md). |
| `tasks[].redCommand` | `string` (optional) | Test-first gate that is **executed**, never asserted: a probe agent runs the string verbatim before the implementer — the JS requires a **non-zero** exit — and a second probe runs it after, where the JS requires **zero**. Three failure verdicts, each fails the task and puts its id in `tasksThatFlagged`: `red-unproven` (a probe died or was skipped, `exitCode: -1`), `red-not-red` (exit 0 before, so the test proves nothing), `green-not-green` (non-zero after). Must be **one invocation** — the shell operators `` ; & \| ` $ > < ( ) { } `` and newlines throw at arg-validation, because the probe runs the string with its own authority and a shell program can fabricate RED; flags and quotes are fine, a multi-step check goes in a script you name. Costs **2 agents** against `maxAgents`; no probe is dispatched under `readOnly`. `scoreTable` then carries `redGated`, `redProven`, `redUnproven`, `redNotRed`, `greenNotGreen`, and the return carries `red` — feed it back as `priorResults.red` so a carried task keeps its adjudication instead of re-reading as unproven. It does **not** close everything: the command loads code the implementer may control, so keep `writablePaths` narrow. Absent leaves every existing caller byte-identical. |
| `tasks[].redDisposition` | `string` (optional) | The filed reason a task carries **no** red gate — for work already complete, where any `redCommand` would be refused `red-not-red`. plan-lint accepts it INSTEAD of `redCommand` (both declared is `red-both-declared`, MAJOR; neither is `redcommand-missing`, MAJOR; empty/whitespace reads as absent), and dispatch echoes it verbatim. **Its content is never validated** — non-empty is the whole check; grading prose is the non-terminating shape. Inert to workflow.js: no probe, no agent, no score field. |
| `scaffoldPaths` | `string[]` (optional) | Paths the plan authors **before** the dispatch, even though a task also writes them. Read only by `main-thread-guard.sh` via `work-dispatch.sh --scaffold` (0 declared, 1 not, 2 undecidable → the guard fails closed); it changes nothing about how implementers run, and `writablePaths` still governs who may write what during the run. Exists for the greenfield red gate: a `redCommand` on a surface that does not exist yet fails to import, which is `could-not-run`, so a stub has to be on disk before wave 1 — and a stub is the implementer's output, so `--covers` alone can only deny it. Keep it to the specific file: a scaffold covering a task's whole writable surface is `scaffold-swallows-task` (major) at plan-lint. Absent leaves every existing caller byte-identical. |
| `tasks[].dependsOn` | `string[]` (task ids, optional) | A **read ordering**: declare it when this task's `refs`, tests or inputs are files another task writes. IMPLEMENT then runs in waves — concurrent within a wave, waves in order. **Absent everywhere leaves every existing caller byte-identical**: one wave, `tasks[]` order. Refused at arg-validation, before any dispatch: a non-array or self-referencing value, an **unknown id** (a typo would silently drop the ordering it was written to enforce), a **cycle** (named with every id in it — a cycle means two tasks each need the other's output), and a wave whose tasks claim **overlapping `writablePaths`** (prefix-aware). An edge to a task outside `onlyTasks` is **satisfied, not unschedulable** — a prior run implemented it and its output is on disk, so refusing it would make every scoped re-run impossible. A `redCommand` still brackets its own implementer inside a wave, never a sibling's. |
| `tasks[].refs` | `string[]` (absolute paths) | Files the implementer must Read in full before working. Absent or `[]` injects nothing into the prompt. |
| `reviewLenses[].refs` | `string[]` (absolute paths) | The rules the lens judges against. **The lens is told to read them IN FULL; its refuters are only told the paths**, with an instruction not to open them unless the finding's own quoted evidence is insufficient (and to say so if they do). A lens is one agent doing open-ended reading; refuters are one agent *per finding*, so handing each the full ref set multiplies the run's largest read by the finding count. Absent or `[]` injects nothing either way. |
| `maxAgents` | `number` (default `50`) | Hard ceiling on the fan-out floor, checked at arg-validation. **Throws before any agent is dispatched**; the error names each dimension's count. Raise it deliberately, in the plan — see the sizing note above. |
| `refutersPerLens` | `number` (default `8`) | Cap on refuters dispatched per lens — the one fan-out term `maxAgents` cannot predict, since a lens returns as many findings as it finds. Findings are refuted **severity-first**, so the cap can never spend its budget on minors and drop a critical. Anything over the cap is reported as **submitted but not refuted** and **still stands** (`refuted: false`, with the reason saying so) — the same fail-closed rule a dead refuter gets, because in both cases no refutation happened. Never silently truncated. |
| `probeModel` | `string|null` (default `'sonnet'`) | Model for the **probe** legs — `mechanical:*`, `red:*`, `third-party:*`. A probe runs a command and reports `{name, exitCode, output}`; the JS reads the exit code and no probe asserts a pass, so there is no judgement to downgrade and the session's top tier would be spent supervising a subprocess. Probes outnumber every other non-refuter leg on a check-heavy run. Pass `null` to inherit the session model. **Recommended: `'gemini-3.1-flash-lite'`** — measured 2026-09-12 at 2/2 correct exit codes with a valid `MECHANICAL_SCHEMA` report on both a passing and a failing command, and it draws on a quota pool nothing else in the run uses, so a probe cannot push a real leg into a cooldown. `gpt-5.6-luna` scored the same but shares the Codex pool; `gpt-oss-120b-medium` failed both cases (Antigravity 500, and Claude Code logs `unrecognized_model`). |
| `verifierModel` | `string|null` (default `'sonnet'`) | Model for `verify:*`. A verifier judges ONE task against ONE stated acceptance criterion, with both the criterion and the evidence handed to it — bounded, like refutation, and one per task. Pass `null` to inherit. |
| `implementerModel` / `lensModel` | `string|null` (default inherit) | Set **deliberately**; default inherit so no caller's gate weakens silently. `lensModel` is where a downgrade costs most — lenses are the open-ended readers that find what nothing else does, so a cheaper lens is a weaker gate rather than a cheaper one. Implementers write the artifact the whole gate then judges. |
| `refuterModel` / `refuterEffort` | `string|null` (defaults `'sonnet'` / `'medium'`) | Model and reasoning effort for refuters only. Refutation is a bounded judgement against quoted evidence, not open-ended investigation, and refuters usually outnumber every other agent kind combined. Pass `null` to omit the key and inherit the session default — what a run wanting a maximally hard gate does. Lenses, implementers and verifiers are unaffected. |
| `implementerEffort` | `string|null` (default `'xhigh'`) | Reasoning effort for `implement:*`. Implementers write the artifact the whole gate then judges, and `xhigh` is the documented level for long-horizon agentic coding. Pass `null` to omit the key and inherit the session default. |
| `verifierEffort` | `string|null` (default `'medium'`) | Reasoning effort for `verify:*`. A verifier judges ONE task against ONE criterion with the evidence handed to it — bounded, like refutation, so it sits where refuters sit. Pass `null` to omit the key and inherit. |
| `scoredEffort` / `thirdPartyEffort` | `string|null` (defaults `'low'` / `'low'`) | Reasoning effort for `scored:*` and `third-party:*`. The scored leg reports **count fields** and the JS computes the composite; the third-party leg only shells out to an external CLI and parses its output. Neither has a judgement to downgrade. Pass `null` on either to omit the key and inherit. The `mechanical:*` and `red:*` probes are pinned at `low` and are not dialable; there is deliberately no global `lensEffort` — a lens sets `effort` on its own entry or inherits. |
| `authorityExtra` | `string` | Appended to the `AUTHORITY` block every dispatched agent receives — implementers, verifiers, lenses, refuters. Absent leaves `AUTHORITY` byte-identical. |
| `implementerAgentType` / `verifierAgentType` / `reviewLenses[].agentType` | `string` | Passed through as `agentType`. Use to pin a structurally read-only agent (`Explore` has no Edit/Write) for judges instead of trusting a prompt that says "modify nothing". Absent passes no key at all, so the dispatcher default applies. |
| `reviewLenses` | `[{key, prompt, model?, effort?}]` | Whole-deliverable review lenses. Defaults to 2 (`criteria-vs-artifacts`, `scope-fidelity`) when absent **or empty** — passing `[]` does not disable review. **A lens may carry its own `model` and `effort`**, so one run can mix providers — cheap lenses on a small model, expensive ones on a large one. `model` falls back to `lensModel` and then to inherit; `effort` has no global to fall back to (there is no `lensEffort`), so absent means the key is omitted and the leg inherits. Neither changes `lensModel`'s own semantics: it remains the default for every lens that names no model. Add one per named risk the defaults can't see; unfocused lenses mostly produce findings that get refuted. On a **scoped re-run** the set may be narrowed to the dispatch's blast radius — see the scoping rule above, whose three conditions (deterministic floor intact, scope recorded in the plan, no lens whose refs or subject changed) are what make it safe rather than a silent weakening. |
| `thirdParty` | `["codex"|"gemini"]` | Advisory only — never enters the gate arithmetic. Supply only if the plan carries the opt-in line. |
| `freezeFindingSet` + `maxRounds` | `boolean`, `int` (default 6) | The fix loop's exit condition. **Do not hand-write either: `work-redispatch.sh --dispatch` sets them** — see *The frozen finding set and the round cap*. Under the freeze a surviving *lens* finding of blocking severity goes to the return's `residue[]` and to `scoreTable.residue` instead of `survivingBlocking`; surviving `priorFindings` still gate, and the task and mechanical channels are untouched. |
| `onlyTasks` + `priorResults` | `string[]`, `{implemented, verified, red}` | Scopes a re-run after FAIL or human findings. **Do not hand-write these: `work-redispatch.sh --dispatch` derives both** from the previous verdict — `onlyTasks` is `tasksThatFlagged` closed under **transitive dependents** (a carried "verified" downstream of a re-run task was earned against code that no longer exists), plus any task the verdict settles no `implemented`/`verified`/`red` record for; `priorResults` carries the rest. An absent, unreadable or unparseable previous verdict falls back to a full re-run and says so. `--full` opts out. **Carry `red` too** — a red-gated task carried without it re-reads as `redUnproven` and fails a verdict the previous run already settled. |

Every knob is read off the plan's **Run sizing** block — no line there means the default stands.
Choosing one at dispatch time changes the verdict without changing the bytes the user approved.

`mechanicalChecks` are **whole-deliverable**: they always run, including under `onlyTasks`, and are
never carried forward from `priorResults` — carrying an empty set forward would be a vacuous pass.

**`reviewLenses` may be scoped to a dispatch's blast radius — and only because `mechanicalChecks`
cannot be.** Running all lenses every round is the right *default*. But a re-run that rewrites one
test file still pays every lens to re-read tens of thousands of tokens of refs to re-decide a
question nothing touched, and the review phase does not shrink under `onlyTasks` at all — measured at
~34 agents of every ~45-minute round. So a scoped dispatch may run the subset of lenses whose
**judgement could have changed**, provided all three hold:

1. **The deterministic floor is intact.** Every `mechanicalCheck` still runs, unconditionally. Lens
   scoping trades some *judgement* coverage while keeping **all** deterministic coverage — that is
   the only reason it is safe, and it is void the moment a run also trims its mechanical checks.
2. **The scope is in the approved plan's Run sizing**, naming which lenses ran, which did not, and
   why. Deciding it at dispatch time is the weaken-the-gate-after-approval move this file forbids.
3. **A lens whose refs or subject changed is NOT scoped out.** If the dispatch edited a file a lens
   reads, or the rule it judges against, that lens runs.

`scoreTable.lensesRun` reports what was dispatched, so a scoped round is visible in the verdict
rather than inferred. **A dropped lens is not a clean lens** — render it as *n/a*, never as zero
findings.

### Plan review — computed, over the args, before anything is spent

**Plan review is the review of the inputs `workflow.js` will run**, and it is finished before
dispatch. No agent reads the plan markdown looking for defects: Phase 2's rule that every claim is a
`tasks[].acceptance` clause or a `mechanicalChecks` entry makes the args object *be* the plan, so
reviewing the args reviews the whole thing. The judged layer that once did this measured 20/16/17
findings with 8/8/9 surviving across three dispatches of one plan, **zero title overlap** between
consecutive rounds and zero artifacts built — every finding real, and the reviewed surface growing in
response to its own output.

`work-dispatch.sh` enforces two tiers on the built args, before `args.json` is written; a blocking
finding exits 3 with the run still armed and every artifact byte-identical. Both run by hand too:

```
bun ~/.claude/skills/workflows/skills/work/scripts/plan-lint.ts      <plan.md|args.json>
bun ~/.claude/skills/workflows/skills/work/scripts/plan-preflight.ts <plan.md|args.json> --cwd <repo> [--only acceptance]
```

**Tier 1 — `plan-lint`** decides the plan's structured fields. Its `major`/`critical` rules block:
`dependson-cycle`, `dependson-missing`, `redcommand-existence-only`, `redcommand-missing`,
`red-both-declared`, `redcommand-disagreement`, `self-gating-task`, `writable-paths-overlap`,
`plan-table-column-arity`, `prose-command`, `lens-missing-severity`, `lens-missing-condition`,
`acceptance-is-the-mechanical-check`, `pipeline-exit-code`, `criterion-unmapped`, `count-mismatch`,
`work-artifact-unasserted` (scoped to tests and assertion scripts — things that exist to be RUN),
plus one worth spelling out:

- `gate-shell-operator` matches `workflow.js`'s own operator regex exactly, with no `bash -c`
  exemption, so tier 1 cannot pass a gate that arg-validation then refuses.

`acceptance-clause-uncommanded` and `redcommand-relative-path` are minor and advisory — deciding
whether a prose clause states a requirement is a judgement, not a lint.

**Tier 2 — `plan-preflight`** executes the args' own commands at BASELINE and reads the exit codes,
so give it a quiet tree. Live-service and network commands are skipped unless `--unsafe`.

- **`redCommand`s** (TIER 2, its own richer classifier): `red-not-red` (exit 0 — the gate already
  passes) and `could-not-run` refuse. **A red gate must produce real test-framework output**: an
  absence grep, or a `-t` filter matching nothing, exits 1 having run nothing and is `could-not-run`,
  never red. Every active task is probed at dispatch, *before any task runs* — so a gate pointing at
  a file a later task creates is refused too, and a run's failing tests must exist before it starts —
  author them before dispatching, which the guard permits because no task's `writablePaths` covers a
  suite that gates the run (one that did would be `self-gating-task`).
  **A suite that never loaded is `could-not-run`, not red**: a collection or import error means the
  surface under test does not exist, and a red that only proves that proves nothing about behaviour.
  Greenfield work therefore needs a stub that exists and fails — declare it in `scaffoldPaths`,
  which is what makes it authorable while the run is armed.
  Run by hand, `plan-preflight` decides on the exit code alone — `red-gate-already-green` (0),
  `gate-command-not-found` (127), `gate-timeout` (no status); it has no could-not-run classifier,
  which reads the probe's OUTPUT and lives only in `work-dispatch.sh`'s `red_probe_gate`.
- **`mechanicalChecks`** (TIER 2b, **including under `readOnly`** where they are the entire gate):
  only a `critical` refuses (127); a mixed red/green baseline is normal and reports
  as `baseline-split`, since the run is what closes the gap.
- **acceptance commands** (`--only acceptance`): `acceptance-command-not-found` (critical) when a
  command exits 127 — the clause names a binary this tree does not provide and can never mean what it
  claims; `acceptance-green-at-baseline` (major) when a task carries no `redCommand` and every one of
  its acceptance commands already exits 0, so nothing distinguishes "the work landed" from "the work
  was never started". A task with a `redCommand` is exempt — its red gate already carries that proof.
  **No dispatch gate executes these**: tier 2b passes `--only mechanical`, so acceptance probes are a
  by-hand pass, before arming the run.

The script throws on missing plan/hash/tasks — never "fix" that by inventing args; re-derive them
from the plan file. It runs IMPLEMENT, then VERIFY ∥ MECHANICAL ∥ THIRD-PARTY (blind per-task
verifiers + review lenses with adversarial refutation, alongside the deterministic probes and the
advisory external reviewers), and returns a **JS-computed gate**. IMPLEMENT runs in the waves
`dependsOn` declares.

**One tree, still.** Worktrees are deliberately not used: `workflow.js` has no filesystem, so it
could not merge them, and a merge agent's silent slip is indistinguishable from an implementer's
omission until a lens catches it. What makes concurrent implementers safe in that one tree is **not**
trust, it is arg-validation: same-wave tasks must have **pairwise-disjoint `writablePaths`**, checked
prefix-aware (`src` and `src/lib/x.ts` overlap) and refused before any agent is dispatched.
Serialising two such tasks with a `dependsOn` makes the same paths legal, because different waves
never write at the same time.

### Where the agent team lives (not in `workflow.js`)

**A team cannot run inside `workflow.js`.** The workflow dispatcher unions a fixed disallow list
(`["SendUserMessage", "Agent", "Workflow"]`) into whatever `agentType` a leg names, so **`Agent` is
stripped from every workflow leg regardless of type**; `SendMessage` survives, so a leg can message
something that already exists but cannot create anything to talk to. So on a `readOnly` run where
CLARIFY answered yes to the team axis, the team runs **before** the Phase 4 dispatch and hands its
discoveries in as `priorFindings`. Discovery gets the team; refutation and the gate stay outside it.

It also cannot run in this session: the guard denies the `Agent` tool outside its allowlist
(`Explore`, `Plan`, `librarian`, `codex:rescue`, `statusline-setup`, `plugin-dev:*`), so named
teammates are only spawnable inside a farm-out child. Run it through `farm-team.sh`, with **one
`--expect` per teammate**:

```bash
# --cwd places the LEAD; --expect resolves against the CALLER's cwd, so keep those absolute.
bash ${CLAUDE_PLUGIN_ROOT}/skills/farm-out/scripts/farm-team.sh --cwd "$PWD" \
  --prompt-file "$PWD"/.craft/<run-id>/team.txt \
  --expect "$PWD"/.craft/<run-id>/findings/<lens>.json \
  --expect "$PWD"/.craft/<run-id>/findings/<other-lens>.json
```

**The file is the record, the message is the signal.** Teammate delivery is not fully reliable
(`.claude/rules/agent-teams.md`), so each teammate writes **exactly one** file to
`.craft/<run-id>/findings/<lens>.json` before going idle, holding a JSON array:

```json
[{ "title": "…", "severity": "critical|major|minor", "detail": "…", "file": "src/app.rs" }]
```

Omit `lens` — it is the filename, and the lead stamps it while concatenating `findings/*.json` into
`priorFindings`. **A teammate that found nothing writes `[]`**: that is what makes "found nothing"
distinguishable from "died", and it is the whole reason the file exists rather than the message.

**The file count is the runner's exit code, not the lead's diligence.** One `--expect` per teammate
makes `farm-team.sh` exit non-zero naming every findings file that is missing or empty. Do not start
Phase 4 on a non-zero exit — name the teammate to the user. Do not file the missing teammate as a
`priorFinding` either: entries there are refuted with *default to refuted when ambiguous*, and no
evidence in the tree can confirm a negative about an agent that is gone, so the run would read clean.

On the result:

- Render the score table and verdict to the user — including `scoreTable.tasksTotal`,
  `survivingMinor` (survived refutation but too minor to block) and `thirdPartyAdvisoryFindings`
  (advisory, never in the gate arithmetic). `scoreTable.mechanicalRun` / `mechanicalPassed` are `0/0`
  when the phase was skipped — nothing checked, not everything clean.
- `scoreTable.lensesRun` / `lensesReported` are lenses **dispatched** vs lenses that came back. When
  `lensesReported < lensesRun`, each missing one contributes a synthesized `critical` finding (title:
  *lens agent died or was skipped — this review dimension did not run*) attributed to its lens key,
  so it appears in `findings` and in `lensesThatFlagged`, and fails the gate. A lens that ran and
  found nothing is counted in `lensesReported` — silence from a dead lens is never read as clean.
- **Third-party findings are advisory**: file each as a task with model attribution (`[codex] …`).
  They never block. A `status: unavailable` leg is reported as such, not as clean.
- **On a `readOnly` run** `tasksJudgedThisRun`, `implementedDone` and `verifyPassed` are `null` — the
  dimension **does not apply**, nothing was dispatched along it. That is neither zero nor clean:
  render it as *n/a*, because a `0` printed beside real counts reads as "checked and clean".
  `tasksThatFlagged` is correspondingly `[]` by design, and **both verdicts go to Phase 5** via the
  findings file — a FAIL there is not a defect to fix (see the note under the fix loop).
- **FAIL** → the re-run selector is all three of `tasksThatFlagged`, `mechanicalThatFailed` and
  `lensesThatFlagged`. Consume all three. See below. **PASS** → Phase 5.

### The FAIL fix loop — three selectors, not one

`overallPass === false` ⟺ at least one selector is non-empty. The gate fails on three independent
dimensions and two of them own no task: neither a failing mechanical check nor a surviving lens
finding — a judgment about the whole deliverable — can appear in `tasksThatFlagged`, so an empty
selector on a failing run means "re-run everything", not "nothing to fix".

| selector | what it names | how you fix it |
|---|---|---|
| `tasksThatFlagged` | task ids that were not done, failed verification, or whose implementer/verifier never reported | fix the work, re-invoke with `onlyTasks: [<those ids>]` + `priorResults: {implemented, verified, red}` from the last run — dropping `red` makes every carried red-gated task re-read as unproven |
| `mechanicalThatFailed` | `{name, exitCode, output}` for each check whose exit code was not 0 | **re-run the CHECK, not a task.** Fix the underlying issue the command reported, then re-invoke with the same `mechanicalChecks` — they always re-run, including under `onlyTasks`, so no extra selector arg is needed |
| `lensesThatFlagged` | distinct `lens` keys of findings that survived refutation (`findings[]` carries the detail) | **re-run the LENS, not a task.** Fix what the finding reported, then re-judge by re-invoking with those lenses still in `reviewLenses` — a lens judges the whole deliverable, so it re-runs on every invocation and needs no extra selector arg |

So a FAIL is handled as: fix the work behind `tasksThatFlagged`, fix whatever each entry in
`mechanicalThatFailed` reported (`exitCode: -1` means the probe died or was skipped — unchecked,
never passed), fix each surviving finding and let its lens in `lensesThatFlagged` re-judge it, then
re-invoke with `onlyTasks` scoped to the flagged ids **and** the unchanged `mechanicalChecks` and
`reviewLenses`. If all three selectors are empty on a FAIL, that is a bug in your reading of the
result — re-run everything. **A defect in the PLAN is not a gate verdict**: it is caught by tiers 1
and 2 before dispatch, and the remedy is to amend the plan file, re-hash it (`--spec-hash`) and
re-dispatch — amending by **REPLACEMENT, never accretion**, since a plan that grows every round
manufactures new surface for the next one. `plan-lint` computes this: a `work` cell carrying more
than one `ROUND <n>` marker is a MAJOR (`work-accretion`), and a tier-1 MAJOR refuses the dispatch.

### The frozen finding set and the round cap

**"This round's lenses raised nothing" is not an exit condition.** Findings never repeat between
rounds and generation does not fall as fixes land, so that test is a draw from a constant-rate
generator — see `references/convergence.md`. The exit condition is instead **"is the round-1 blocking
set closed?"**, which is finite and shrinks.

`work-redispatch.sh --dispatch` implements it; do not hand-wire any of it:

- On the advance to **round 2** it carries the previous verdict's surviving blocking findings into
  `priorFindings` and sets `freezeFindingSet`. **Once** — never re-derived, or the carried set tracks
  the generator instead of freezing against it. Each entry is adversarially refuted every round, so a
  fixed finding is one the refuter can now refute; that is what "closed" means.
- From round 2, a fresh **lens** finding is reported as `residue` and does not gate. It is real and it
  is not lost — it is the input to a follow-up run's `priorFindings`.
- `maxRounds` **defaults to 6**. The dispatch that would exceed it exits 4, spends no round, rotates
  no result and prints what is still open as a paste-ready `priorFindings` block. Another round after
  that is a human's decision (add `"maxRounds": <n>` to `args.json`), not the loop's.
- At round ≥ 3, or when the run dir's oldest archive is over 2 h old, it prints
  `scripts/converge-check.ts` — a computed diagnosis over the run's own `result-round*.json`
  (blocking sequence, generation slope, repeat rate, deliverable-vs-gate split, accretion markers).
  **Advisory**: it explains why a run had to be stopped, it does not stop one. Run it by hand any
  time: `bun ~/.claude/skills/workflows/skills/work/scripts/converge-check.ts <run-dir> [--json]` — exit 0
  CONVERGING, 1 NOT CONVERGING with reasons, 2 too short to judge.

### Text-only findings: fix inline, confirm with `readOnly`

When **every** surviving finding is a text defect in an already-built artifact — a doc that
contradicts the code, a command template that is wrong as written — re-dispatching implementers
rebuilds finished work to re-judge a few sentences. Instead:

1. **Fix the text inline.** The orchestrator may edit it; no implementer is needed.
2. **Verify each finding directly**, by the evidence the finding itself names — diff the two files,
   run the corrected command and show its exit code. Not "it looks right now".
3. **Confirm with a `readOnly` re-judge, sized to the fix — ONE reader, not the fleet.**
   `readOnly: true`, `tasks: []`, the unchanged `mechanicalChecks`, and **a single lens scoped to the
   findings that were fixed and the text that changed**: does each named defect actually close, and
   did the edit introduce a new contradiction? Record it in the plan's Run sizing and re-hash — the
   gate changed shape, so the user's approved sizing must say so.

   Do **not** re-run the whole `reviewLenses` set: four lenses plus refuters is up to ~40 agents to
   confirm a few sentences, against ~10 for one scoped lens and the probes. Refutation stays at full
   strength on that one lens — the cap is what makes an unrefuted finding stand. The probes stay
   dispatched rather than run inline: the JS gates on a probe's exit code, and an orchestrator running
   its own checks is back to self-report.

**The confirming pass is not optional.** Fixing findings inline and declaring victory is the
orchestrator certifying its own edits — the same self-report the gate exists to replace.

**Cap confirming passes at two.** A third consecutive confirming FAIL means stop and put the findings
to the user. The failure mode is not a loop that finds nothing; it is a loop where every finding is
individually real and severity quietly decays, so there is never an obvious moment to stop — judge
the *trend*, not the current finding.

**Text-only is a judgement about the FIX, not the severity.** A wrong command template is `critical`
and still text-only; a one-word doc change that alters what a script does is not. The test is whether
any task's implementation changes. If one does, it is a normal `onlyTasks` re-run.

**The fix loop does not apply to a `readOnly` run.** There, a FAIL is the *expected successful
outcome*: an audit that finds defects is an audit that worked. The selectors still name what was
found, but they are the audit's report, not a work queue — fixing what they name is a separate
**writing** run, with its own plan, its own hash, and its own gate. Route both verdicts to Phase 5.

## Phase 5 — HUMAN REVIEW

**Phase 5 runs AFTER the goal clears, and is no part of it.** The goal's terminal clause is craft's
own verdict; a human verdict is not something a session can work toward, so putting one in the goal
makes every run stoppable only by a person who may have walked away — measured 2026-08-22, a tested
and reversible bugfix sat behind that clause for ~18 hours while the outage it repaired stayed live.

Two consequences, and they are the whole of the change:

- **Deliver first.** A craft PASS means the suite is green, which is not the same as done. Where the
  run has a real-world claim — the account ingests mail again, the endpoint answers — establish THAT
  before opening review, so the reviewer sees something whose central assertion already holds.
- **Never block delivery on the TUI.** Open review on work that is delivered, or offer it and move
  on. The one case that still argues for reviewing before you ship is work that cannot be undone —
  a one-way migration, a deletion — where there is no rollback to fall back on. That is a judgement
  the orchestrator makes out loud with the user, not a gate this file imposes on every run.

```bash
# 30-minute timeout; NEVER run_in_background; NEVER relaunch on timeout (the TUI is still open)
bash ${CLAUDE_PLUGIN_ROOT}/skills/work/scripts/human-review-gate.sh -w --no-update-check
# or: -r <range> / pr <N> per the plan's review surface
```

### The read-only path — review the findings file, not the tree

A `readOnly` run changes no files, so `-w` opens an empty diff and tuicr returns `unreviewed`, which
the table below calls *not approval*. So on a `readOnly` run the orchestrator **first writes
`.craft/<run-id>/findings.md` from the object `workflow.js` returned**, then reviews that file:

```bash
bash ${CLAUDE_PLUGIN_ROOT}/skills/work/scripts/human-review-gate.sh \
  --file .craft/<run-id>/findings.md --no-update-check
```

The orchestrator writes it because **a Workflow script cannot**: its sandbox has no filesystem and no
Node API — the hooks are `agent`/`parallel`/`pipeline`/`log`/`phase`/`workflow`/`args`/`budget`.
(`human-review-gate.sh` forwards its args verbatim to tuicr, so `--file` needs no script change.)

`.craft/` is gitignored, so this file does not survive the machine. **A findings document worth
keeping must be moved somewhere tracked** — say so to the user when the audit found anything.

### What the findings file must contain — transcription, not judgement

The agent writing this file is the agent that ran the audit. If it summarises in its own voice, the
audit becomes self-reported — exactly the pattern the gate exists to prevent, reintroduced one step
after the gate. So: counts come from `scoreTable` verbatim, findings from `findings[]` verbatim, the
verdict from `verdict`. **Organise; do not grade.**

| section | source | rule |
|---|---|---|
| verdict + one-line scope | `verdict`, `judged` | verbatim |
| coverage | `scoreTable.lensesRun` vs `lensesReported` | **name every lens that did not report.** A missing dimension is not a clean one, and omitting it is silence that reads as clean |
| refutation | `scoreTable.lensFindings` and `refuted` | both numbers, so the kill rate is visible: 14 findings / 0 survivors is not the same run as one that found nothing |
| surviving findings | `findings[]` (already filtered to `!refuted`) | grouped by severity, each carrying its `lens` |
| refuted findings | `refuted[]` | title + `refuteReason` — kept, not dropped |
| mechanical | `mechanical[]` | `name`, `exitCode`, `output` |
| not checked | the plan's scope vs what actually ran | explicit, not omitted |

Blocks until the user quits tuicr, then prints one verdict JSON:

| verdict | meaning | action |
|---|---|---|
| `approved` | files marked reviewed, no new notes | clear goal, clean `.craft/`, done — offer commit/ship |
| `findings` | new human annotations | tactical loop below |
| `rejected` | a note contains `REJECT`, or PR is request-changes | strategic loop below |
| `unreviewed` | opened-and-quit, nothing touched | **not approval** — ask the user what they want |

**Tactical loop (findings, plan unchanged):** convert each comment to a task; fix via
workflow.js re-run scoped with `onlyTasks` (or directly for one-liners, then still re-verify via
the workflow); reply to each annotation in-session:
`tuicr review add --username "Claude" --session <slug> --target-file <path> --line <n> "<reply>"`;
relaunch the gate script.

**Strategic loop (REJECT):** the interpretation was wrong, not the execution. Clear the goal,
keep `.craft/<run>/` as provenance, start a fresh run-id, return to Phase 1 CLARIFY with the
rejection notes as input. **Cap: two rejections** → stop, summarize both misses, and escalate/
descope with the user rather than guessing a third time.

## Red flags

| Situation | Wrong move | Right move |
|---|---|---|
| Trivial edit (one file, obvious) | run the full loop | say it's overkill; just do it |
| Verifier needed for a task | let the implementer self-certify | separate verifier agent, blind to the report |
| Task is test-first | let the implementer report "RED confirmed", or make RED a review lens | give the task a `redCommand` — probes execute it on both sides of the implementer and the JS reads the two exit codes. A lens runs after the work and structurally cannot observe RED |
| Sizing not in the approved plan | pick lenses/checks at dispatch time | it shapes the gate — put it in the plan, re-hash, then dispatch |
| Tasks feel like they could run in parallel | fan out implementers yourself, or give each a worktree | declare `dependsOn` and let IMPLEMENT wave them — concurrent within a wave, and arg-validation refuses a wave whose `writablePaths` overlap, so safety is checked rather than trusted. Worktrees stay out: `workflow.js` cannot merge them (no filesystem), and a merge agent's silent slip reads as an implementer's omission |
| A task reads a file another task writes | rely on `tasks[]` array order | array order is not a contract the script enforces — declare `dependsOn: ['<id>']`. An unknown id and a cycle both throw before dispatch; an edge to a task outside `onlyTasks` is treated as satisfied, since a prior run put its output on disk |
| `until-arm.sh` exits non-zero at dispatch | proceed as though the hold were armed | it is NOT armed — this session stops at its first stopping point. Read the reason it printed (already green, or no session id) and fix it before walking away |
| Dispatch printed the `CronCreate` block | scroll past it; the hold covers it | the hold cannot restart a session that has gone quiet. Call `CronCreate` this turn and report the job id; `CronList` is what proves it |
| About to self-send a `/goal` or `/loop` into this session | any transport — herdr, agent-msg, a drainer | none of them land: a typed send needs the pane IDLE and a dispatching session never is. `until-arm.sh` writes a file and `CronCreate` is a tool call; both land inside the turn |
| Session opens on `Implement the following plan:` | implement it in the main thread | the context was cleared at approval — this is Phase 4, not the work. The plan's frontmatter `workflow:` names the skill to invoke first; then dispatch. `work-dispatch.sh` needs nothing you lost. Same answer when an Edit is denied for an armed run |
| Hold's check is a claim, not a command | "the tests in the plan pass" | the hook RUNS the check — give it a command whose exit code is the verdict, and `until-arm.sh` refuses one that is already green or cannot run |
| Recon would flood the conversation | read every file into this context | scout with a subagent during CLARIFY/PLAN — graded work still goes through workflow.js |
| One small task, workflow feels heavy | dispatch a lone subagent and accept its report | still workflow.js with one task — a self-report is not a verification |
| Tasks look like they need to talk to each other | reach for agent teams | **On a run that writes, no teams** — for the mechanical reason given under *IMPLEMENT runs in waves* above, not as a style preference. Tasks needing to talk means the plan under-specifies the boundary; fix the task table, re-hash. **The ban does not apply to `readOnly`**, where nothing writes and a team is the default (CLARIFY axis 7); see *Where the agent team lives* |
| Fan-out estimate > ~50 agents | widen the workflow anyway | split into sequenced craft runs, one gate each |
| Third-party found a "critical" | let it flip the gate | file as advisory task; gate stays JS-only |
| Treating tasksThatFlagged as the whole selector | re-run only the flagged task ids — or, if that list is empty on a FAIL, conclude there is nothing to fix | **The selector has three dimensions and only one of them owns tasks.** A failing mechanical check and a surviving lens finding each own no task, so neither can appear in `tasksThatFlagged`; an empty selector on a failing run means "re-run everything". Consume all three — `tasksThatFlagged` **and** `mechanicalThatFailed` **and** `lensesThatFlagged`. **On a `readOnly` run `tasksThatFlagged` is `[]` by design** (no task channel exists), so there the selector is the other two and at least one is guaranteed non-empty on a FAIL |
| A mechanical check failed | re-run the task nearest to it, or drop the check | fix what the command reported and re-invoke with the same `mechanicalChecks` — a failed check re-runs the CHECK |
| A lens finding survived refutation | hunt for the task to blame, or re-run everything | `lensesThatFlagged` names the lens; fix what `findings[]` reported and let that lens re-judge — a surviving finding re-runs the LENS |
| A `scoredChecks` composite comes back low | add a threshold or `blockBelow` so it fails the run, or read the score into some other gate | no such knob exists and adding one is not a configuration choice this parameter left open — gating on a composite chases redundancy minors. Read the score; gate on `mechanicalChecks` and on `critical|major` lens findings, which can be wrong in only one direction |
| `mechanicalRun: 0` in the score table | read it as "mechanics clean" | the phase was skipped; nothing was checked |
| `lensesReported < lensesRun` in the score table | read the missing lens's zero findings as a clean dimension | that lens never ran; the gate synthesizes a `critical` finding for it and fails. Re-run it — an unreviewed dimension is not a reviewed one |
| tuicr quit with 0 comments, 0 reviewed files | treat as approval | `unreviewed` — ask the user |
| Phase 5 on a `readOnly` run | `-w` over a tree nothing wrote to | there is no diff, so that always returns `unreviewed` — write `.craft/<run-id>/findings.md` and review it with `--file`, per *The read-only path* |
| A `readOnly` run returned FAIL | send it to the fix loop | that is the audit's successful outcome — it found defects. Take it to Phase 5; fixing is a separate writing run with its own plan and gate |
| PR review surface mid-loop | push new commits to the branch | don't — tuicr sessions key on head_sha; finish the loop first |
| Plan edited after approval | keep going | agents will halt on hash mismatch anyway — re-hash and restart Phase 4. `scripts/work-redispatch.sh <plan> <args.json> [--dispatch] [--full] [--no-lint] [--no-red-probe]` does the re-hash, refuses when the args name a different plan, and rotates a stale `result.json` so a previous verdict cannot be read as this run's. With `--dispatch` it runs both dispatch gates on the final args and exits 3 on a major/critical or a refused red probe, and exits 4 past `maxRounds`, spending no round and rotating nothing in either case |
| Review phase dominates every round | drop lenses, cheapen `lensModel`, or cut `refutersPerLens` | scope the lens SET to the dispatch's blast radius, recorded in the plan — the deterministic floor keeps running, so you trade judgement coverage and keep all mechanical coverage. Cheapening the lens model buys a weaker gate, not a cheaper one, and cutting refuters multiplies the survivors you then verify by hand |
| A lens ref is huge and slow to read | distil it into a summary ref | a condensed copy of craft's doctrine drifts and is what `spine-fidelity` exists to catch — the optimisation flags itself. Scope how often the lens runs; never fork what it reads |
| The plan looks wrong and you want an agent to review it | dispatch a lens that reads the plan markdown and blocks on what it finds | that loop does not terminate — the fix for round *n* is new surface round *n+1* finds real defects in (see *Plan review*). Plan review is `plan-lint.ts` + `plan-preflight.ts` over the args, and anything a reader would have caught belongs there as a rule |
| An acceptance clause already passes at baseline | ship it — the criterion holds | `acceptance-green-at-baseline`: nothing distinguishes "the work landed" from "the work was never started". Give the task a `redCommand`, or state a clause the work has to make true |
| Red gate is `grep -q <thing that should not exist>` | call it red because it exits 1 | that is `could-not-run` — no test framework ran. A red gate must produce real test output; write the assertion as a test |
| workflow.js throws on args | patch args from memory | re-derive from `.claude/plans/<slug>.md` |
| `.claude/plans/<slug>.md` doesn't resolve | copy the plan in from `~/.claude/plans/`, or dispatch the path anyway | resolve the real path and hash it there — plan mode fixes the plan's path at entry, so a session predating the `plansDirectory` setting still writes to `~/.claude/plans/`. Set the setting for next session; never copy (two files drift), never dispatch a path you haven't confirmed resolves |

Attribution

edwinhuedwinhu
View sourceMore from edwinhu →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

ucoz-landing-skill

Playbook for creating and editing uCoz landing pages via MCP tools (`templates_tool`, `ftp_tool`, `modules_tool`). Use for tasks such as: "build a landing page", "update the homepage as a landing page", "create a promo page on the homepage", "add a lead form / menu / SEO to the homepage". Homepage: `page_list`, `page_get`; first publish — `page_update` with full `page_tmpl`; HTML edits after generation — `patch_template` (module_id=2, template_id=1), not `update_template`. Activate the mail f...

107 votes

Paperclip

Interact with the Paperclip control plane API to manage tasks, coordinate with other agents, and follow company governance. Use when you need to check assignments, update task status, delegate work, post comments, set up or manage routines (recurring scheduled tasks), or call any Paperclip API endpoint. Do NOT use for the actual domain work itself (writing code, research, etc.) — only for Paperclip coordination.

798221 votes

Daw Music

Digital Audio Workstation usage, music composition, interactive music systems, and game audio implementation for immersive soundscapes.

761 votes

Instantly Rdsthomas Mission Control

Instantly.ai cold email outreach API - manage campaigns, leads, accounts, and analytics. Use for cold email automation, lead management, campaign creation/monitoring, and email account warmup.

761 votes

Caveman Compress

Compress natural language memory files (CLAUDE.md, todos, preferences) into caveman format to save input tokens. Preserves all technical substance, code, URLs, and structure. Compressed version overwrites the original file. Human-readable backup saved as FILE.original.md. Trigger: /caveman-compress FILEPATH or "compress memory file"

1023330 votes
View all in tools →