Skip to content

Skills

Included verbatim from skills/*/SKILL.md, frontmatter dropped.

Blind

Two ranked lists of mechanisms, produced under different information, and the difference between them.

You adjudicate — you are the one who can go to the bench. This skill generates widely and hands you discriminating measurements. It is not a neutral judge.

Why it is built this way

Suppressing a hypothesis already in context does not work. So the blind pass is never told to ignore anything: it runs in a process that never receives it.

Every guard here is structural. Nothing relies on an agent choosing to obey.

Leak Closed by
memory index of solved mechanisms separate process, neutral cwd
user rules and CLAUDE.md --restricted
skills firing and injecting method --disable-slash-commands
reading outside the bundle --restricted confines file tools to cwd
branch names, commit messages export with no .git
plan docs, issues, prior runs export exclusions

Run — /blind <workstream>

1. Manifest

Ask for, and write to <work>/manifest.md — the blind pass reads it and can only read inside <work>. Copy it to <record>/manifest.md, which is the committed copy.

  • One symptom line. What was observed. Not what you think causes it.
  • Evidence artifacts — logs, plots, CSVs, bags. Copy each into <work>/evidence/ and record the in-run path. Copy unconditionally, even when the artifact is also tracked in the repo; otherwise the audit trail names a path the agent did not use.
  • The pin — the commit the evidence was captured from.
  • Capture-tree dirtiness. Record whether the tree was clean at capture, and what was modified if not. A dirty tree means the pin names code that is not quite the code that ran.

  • Untracked dependency. Run at the pin:

git -C <repo> status --porcelain --untracked-files=all

Subtract the export file list. What remains is what the export silently drops. Show that list and ask which entries were load-bearing for the capture. An export missing a load-bearing path diagnoses code that did not produce the data.

  • Hypothesis-term hits. You hold the hypothesis; the blind process never will. So grep the export for its terms — mechanism names, component names — and show the hits. Ask which paths to withhold. Record the answer.

Identifiers still leak, and measurably more than by nudging a ranking. In an A/B where two trees differed only in naming, the loaded arm asserted a sensor dropout that appeared nowhere in the evidence — 14 mentions per run against zero in the neutral arm — and pinned that mechanism first every time, where the neutral arm ranked it 1st, 2nd and 5th. A name introduces an entity.

So rename nothing to hide it. The table's support column carries observed or assumed: <condition> instead, and an assumed row citing something the evidence never shows is a candidate identifier leak, not a finding.

That guard is partial and the rate is measured, not assumed. Over ten runs it caught the injected entity 7 times; the column was filled every time, but three runs called it observed when the condition appeared nowhere in the data. An inline marker rather than a column managed 4 of 10. So read the observed rows too — roughly one leak in three arrives wearing the wrong label. Say in the manifest that identifiers were not sanitised.

2. Freeze

Manifest and pin are now fixed. Nothing downstream may edit either. A pin chosen after the informed pass is a pin the informed reasoning selected.

3. Informed pass — main session

Runs first. A subagent's report returns to its parent, so a blind pass running first would leave its ranking in context while the informed table was written.

Write the table from references/schema.md using the full domain context: the hypothesis, the repo history, the issues, everything. IDs are I1..In.

Write it to <record>/informed.md before spawning anything.

4. Blind pass and rejection review — parallel

Blind. Build the working root, then run it out of process:

mkdir -p <work>/tree <work>/evidence
git -C <repo> archive --format=tar <pin> -- . \
  ':(exclude)docs/hypotheses' <accepted exclusions> | tar -x -C <work>/tree
cd <work> && claude -p \
  --restricted --disable-slash-commands --strict-mcp-config \
  --tools Read Grep Glob \
  --system-prompt-file ~/.claude/skills/blind/references/blind-prompt.md \
  "$(cat manifest.md)" < /dev/null > <record>/blind.md

docs/hypotheses is excluded unconditionally — it is this skill's own output, so run N would otherwise ship runs 1..N-1.

The blind process gets no memory, no rules, no skills, no MCP, and file tools it cannot point outside <work> — verified: a read by absolute path outside the working root is refused.

--tools is an allowlist, not a denylist, and that is deliberate. --restricted alone leaves WebSearch, Agent and the messaging tools in place; web access breaks the premise that everything relevant is in the bundle, and an Agent child may inherit none of these restrictions. Naming the three tools the pass needs survives new tools being added; a denylist does not.

Rejection. In-process subagent, full prose access, working root plus the issues and plan docs the blind pass is denied. It holds the hypothesis by design; blinding it protects nothing and costs it the history that makes a review sharp.

Give it a generation target, never a prohibition:

Write the review that rejects this hypothesis. Name the failure mode that shows first in flight data. State the measurement that would already have caught it.

Write to <record>/rejection.md.

5. Difference

Map B ids to I ids under one criterion: same predicted signature and same discriminating measurement. Anything else is a different mechanism wearing a similar name. The informed pass never saw the blind ids and cannot do this itself.

Then assign the five verdicts from references/schema.md. Write <record>/diff.md.

6. Pre-registration

The verdicts name measurements. Pick which to run, then write the matching template — references/prereg-filter-consistency.md or references/prereg-ablation.md — and commit it as its own commit, before any result exists. The commit boundary is what makes "pre-registered" checkable later.

Score — /blind close <record-dir>

Read the pre-registration before asking for any numbers. Then take the measured values and mark each band pass, fail or inconclusive as written. The band is not renegotiated after the fact; that is the whole point of writing it down first.

Record which mechanism the bench actually implicated, and whether it came from the blind list, the informed list, or neither. Neither is the finding that justifies running this at all.

Commit as a second commit in the record dir.

Layout

Two locations, deliberately.

<record>  <repo>/docs/hypotheses/<date>-<slug>/   committed
          manifest.md informed.md blind.md rejection.md diff.md
          prereg.md results.md

<work>    <scratchpad>/blind/<date>-<slug>/       never committed
          tree/       export at the pin, no .git
          evidence/   copied artifacts

The export must not live in the repo it came from — committed it duplicates the tree, untracked it corrupts the untracked accounting step 1 depends on.

Blinding is a trade

It buys independence and costs the domain knowledge that makes a candidate list useful. The blind pass is not the better pass. A blind list full of generic failure modes means the bundle was too thin, not that the mechanisms are ranked right.

Diagnose

A discipline for hard bugs. Skip a phase only with an explicit reason.

Is this the right tool?

Skip this skill and just fix it when the cause is visible on the first read: a typo, a stack trace pointing at the line, a bug the user has already localised. The phases cost more than the reading. Say you are skipping and why.

Reach for it when the bug has resisted one look, is intermittent, appeared between two known-good states, or is a performance regression.

Redact

This skill shows commands, output and captured artifacts. Redact every secret first: write <REDACTED> in its place. Build loops against environment variables so credentials stay in the environment rather than in what you show. Captured artifacts carry auth headers and tokens; quote only the lines carrying signal. If the redacted output is not enough to diagnose, say so and ask.

Phase 1 — Build a feedback loop

This is the skill. Everything after it is mechanical. With a tight loop that goes red on this bug, bisection, hypothesis testing and instrumentation all just consume it. Without one, no amount of reading code will save you.

Spend disproportionate effort here. Exhaust the list below before concluding no loop exists.

Keep a local, untracked notes file of per-toolchain recipes for your own stack; this skill's phases are the discipline, not the recipe list. Ways to construct a loop, in roughly this order:

  1. Failing test at whatever seam reaches the bug.
  2. Curl / HTTP script against a running service.
  3. CLI invocation with a fixture input, diffing output against a known-good snapshot.
  4. Headless driver script that drives the UI and asserts on DOM, console, or network.
  5. Replay a captured trace. Save a real payload, pcap, rosbag, ulog or event log to disk; replay it through the code path in isolation.
  6. Throwaway harness. A minimal subset of the system, one function call, mocked neighbours.
  7. Property / fuzz loop. For "sometimes wrong output": 1000 random inputs, look for the failure mode.
  8. Bisection harness. If it appeared between two known states (commit, image tag, dataset, firmware version), automate "boot at state X, check, repeat" so git bisect run can drive it.
  9. Differential loop. Same input through two versions or two configs, diff the outputs.
  10. Human-driven loop. Last resort, when a human must physically act — power a bench, click a dashboard, plug a cable. Call the Skill tool with "wizard" to generate the walkthrough so the loop stays structured and its captured values come back to you.

Tighten it

Treat the loop as a product. Once you have a loop:

  • Faster. Cache setup, skip unrelated init, narrow to one test target.
  • Sharper. Assert the specific symptom, never "did not crash".
  • More deterministic. Pin time, seed RNG, isolate the filesystem, freeze the network, fix the tile/scenario seed.

A 30-second flaky loop is barely better than none. Tighten until you will re-run it without thinking about the cost.

Non-deterministic bugs

The goal is not a clean repro, it is a higher reproduction rate. Loop the trigger 100 times, parallelise, add load, narrow the timing window, inject sleeps. A 50% flake is debuggable; 1% is not. Keep raising the rate until it is.

Completion criterion

Phase 1 is done when you can name one command that you have already run at least once, showing the invocation and its output, and that is:

  • Red-capable — it drives the actual bug code path and asserts the user's exact symptom, so it goes red on this bug and green once fixed.
  • Deterministic — the same verdict every run, or a pinned high repro rate.
  • Fast — seconds, not minutes.
  • Agent-runnable — you can run it unattended.

Verify the baseline is green before trusting a red. A suite already failing makes every perturbation read as caught.

If you catch yourself reading code to build a theory before this command exists, stop: jumping to a hypothesis is the exact failure this skill prevents. No red-capable command, no Phase 2.

When you genuinely cannot build one, stop and say so. List what you tried, and ask for one of: access to an environment that reproduces it, a redacted captured artifact, or permission to add temporary instrumentation in place. Do not proceed to hypothesise without a loop.

Phase 2 — Reproduce and minimise

Run the loop. Watch it go red. Confirm:

  • It produces the failure the user described, not a different one nearby. Wrong bug, wrong fix.
  • It reproduces across runs, or at a rate high enough to debug against.
  • You have captured the exact symptom, so later phases can prove the fix addresses it.

Then shrink to the smallest scenario that still goes red. Cut inputs, callers, config, data and steps one at a time, re-running after each cut. Done when every remaining element is load-bearing: removing any one makes it go green.

A minimal repro shrinks the hypothesis space and becomes the regression test.

Phase 3 — Hypothesise

Generate 3 to 5 ranked hypotheses before testing any of them. Single-hypothesis generation anchors on the first plausible idea.

Each must be falsifiable — state the prediction:

"If X is the cause, then changing Y makes the bug disappear / changing Z makes it worse."

A hypothesis with no prediction is a vibe. Discard or sharpen it.

Show the ranked list before testing. Domain knowledge re-ranks it instantly ("we changed #3 last week", "we already ruled out #1"). Do not block on it — proceed with your ranking if nobody answers.

Phase 4 — Instrument

Each probe maps to a specific prediction. Change one variable at a time.

  1. Debugger or REPL inspection where the environment allows it. One breakpoint beats ten logs.
  2. Targeted logs at the boundaries that distinguish hypotheses.
  3. Never "log everything and grep".

Tag every debug line with a unique prefix, e.g. [DEBUG-a4f2]. Cleanup becomes one grep. Untagged instrumentation survives; tagged instrumentation dies.

Watch for guards that are not guards: a runtime that strips assertions makes an assert probe silently absent.

Performance regressions take the other branch. Logs are usually wrong there. Establish a baseline measurement first — timing harness, profiler, query plan, frame timestamps — then bisect against it. Measure first, fix second.

Phase 5 — Fix and regression test

Write the regression test before the fix, but only if a correct seam exists: one where the test exercises the real bug pattern as it occurs at the call site. A seam too shallow to reproduce the chain that triggered the bug gives false confidence.

If no correct seam exists, that is itself the finding. Note it: the architecture is preventing this bug from being locked down. Do not write the shallow test instead.

With a correct seam:

  1. Turn the minimised repro into a failing test there.
  2. Watch it fail.
  3. Apply the fix.
  4. Watch it pass.
  5. Re-run the Phase 1 loop against the original, un-minimised scenario.

Phase 6 — Cleanup

Required before declaring done:

  • [ ] The original repro no longer reproduces (re-run the Phase 1 loop).
  • [ ] The regression test passes, or the absence of a seam is documented.
  • [ ] All [DEBUG-...] instrumentation removed — grep the prefix to prove it.
  • [ ] Throwaway harnesses deleted, or kept somewhere clearly marked.
  • [ ] The hypothesis that turned out correct is stated in the commit or PR, so the next person reading it learns the mechanism and not just the diff.

Land

Deploy the current work into Docker for system testing. All docker knowledge lives in a per-repo entrypoint; this skill orchestrates and guards it, and never guesses docker commands.

This is the one path for "make the change work in the real app". When the built-in run skill is reaching for a project-specific way to launch, this is it.

Entrypoint

  • Look for a per-repo entrypoint in this order: .claude/land.sh, then a documented make land target, then a land script in the repo.
  • If none is found, stop and ask how to deploy this repo. Do not infer docker / compose commands from Dockerfile or docker-compose.yml.

Tiers (from the argument)

  • no argument — sync + restart: push current code into the container and bounce it. Cheap and reversible. Just do it.
  • rebuildrebuild the image, then restart. Slow but safe. Just do it. (Rebuild must produce compiled artifacts, not just a dev environment; chown outputs back to 1001:1002 if the build runs as root.)
  • resettear down containers / volumes / other services and recreate. Destructive. Before executing, print the exact kill-list (which containers, volumes, and services will be destroyed) and proceed only after showing it. The explicit reset word is the intent, but reset is never blind.

Pass the tier through to the entrypoint. Report what ran and the result.

PS

Capture a tangential ("p.s.") idea and return to what we were doing.

Where it goes

parked-ideas.md in the session scratchpad directory named in your context. One line per item, appended, each stamped with the time it was parked.

The list dies with the session. /done offers every line as a follow-up issue before that happens; the drain step below is for picking one up while the session is still live.

With an argument

  1. Append the item verbatim to parked-ideas.md, creating the file if absent.
  2. Acknowledge in one line: 📌 queued (N): <item>, where N is the list length.
  3. Return to whatever we were doing. Leave the item unexpanded, unplanned and unstarted until it is picked up explicitly.

With no argument

Two cases, decided by what just happened:

  • A tangent was raised in the conversation and not yet parked: park that, as above.
  • Nothing to park: print the current list, numbered, and stop. This is the drain step — reading the list is how you decide what to pick up before the session ends.

Offering it

When you notice a tangent worth keeping rather than being asked, offer it in one line and carry on with the current work in the same turn. A tangent worth parking is not worth a pause.

Simplify

Review for unnecessary complexity. One line per finding: location, what to cut, what replaces it. The best outcome is the target getting shorter. Report only — lists findings, applies nothing.

Tiers

  • Default: the current diff (recently changed code).
  • repo: the whole tree instead of a diff. Rank findings biggest cut first.

Tags

  • delete: dead code, unused flexibility, speculative feature. Replacement: nothing.
  • stdlib: hand-rolled thing the standard library ships. Name the function.
  • native: dependency or code doing what the platform already does. Name the feature.
  • yagni: abstraction with one implementation, config nobody sets, layer with one caller.
  • shrink: same logic, fewer lines. Show the shorter form.
  • upstream: workaround for behaviour a third-party OSS dependency owns — never our own wrappers or internal libs. Replacement: run /upstream.

Hunt

Deps the stdlib or platform already ships, single-implementation interfaces, factories with one product, wrappers that only delegate, dead flags and config, hand-rolled stdlib.

Format

<file>:L<line>: <tag> <what>. <replacement>.

End with net: -<N> lines possible. Nothing to cut: Lean already. Ship.

Boundaries

Scope: over-engineering and complexity only. Correctness bugs, security holes, and performance are out of scope — route them to /complicate. Report-only, applies nothing.

Speed-of-light budgeting

An optimisation with no floor under it is a guess. This skill puts the floor in the repo first — derived from what the machine was measured to do, not what the vendor says it does — and then refuses the work that the floor says is not there.

Companion to verify-generated-diff: that one gates correctness, this one gates performance.

Hard rules

  • Never use theoretical, isolated, or vendor bandwidth in a floor.
  • Never budget against the mean when the window is a deadline.
  • Never treat copy, convert, syscall, alloc, lock, or runtime dispatch as free.
  • Never profile before the floor exists.
  • Never keep an SOL across a graph change.
  • Never report a stage faster than SOL as a result.
  • Never write a datasheet number in a measured column.

Artefacts

All in perf/, in the repo:

file written by holds
machine_model.md Phase 1, by hand from bench output §1–§8, one yaml block of rows
handoff_matrix.md Phase 1 §3, unit × unit
dataflow.yaml Phase 2, by hand the graph
sol_table.md scripts/sol.py floors, regime, critical path
budget.md scripts/sol.py allowed p99 per node
measurements.csv Phase 2 step 6 node, mean, p99, SOL, ratio, build, date, graph
budget_revisions.md by hand the only way past a failing gate

A repo built on two machines carries one machine's machine_model.md. The config: header is load-bearing: a model whose header does not match the target in front of you is a Phase 1 trigger, never a floor source.

sol.py needs PyYAML. Nothing else.

Phase 1 — machine model

Once per target config. Copy templates/machine_model.md to perf/, fill the config: header, and work through §1–§8 with the bench scripts. Every row gets a method: line and a date. A row you have not benched keeps measured: false; sol.py reads it and refuses to spend it, which is the correct outcome — a placeholder in a floor is worse than a missing floor because it looks like an answer.

Clocks locked, or the steady state modelled and said so. §7's equilibrium is minutes away; a number taken in the first thirty seconds is a number about a cold machine.

Exit condition: every unit and mechanism the hot path touches has a measured row, the §5 knee is measured, and somebody else could rerun each row from its method:.

Phase 2 — SOL and budget

  1. Graph. Write perf/dataflow.yaml. Nodes are stages with the unit that runs them. Every non-inplace cell of the handoff matrix becomes its own copy or convert node, and the edge names it with via:. Every kernel or runtime crossing — syscall, socket op, hot alloc, lock, wake, FFI — is a crossings: entry priced from §8. Nothing hides inside a library call.
  2. Fundamental work. Per node, the compulsory ops and compulsory bytes the algorithm requires. A miss, a re-read or a conversion is a design decision, so it is a node — not part of another node's compulsory work.
  3. Floors. sol.py derives them: compute = ops ÷ §4 sustained rate; memory = bytes ÷ the bandwidth available under this graph's overlap; tax = per-crossing §8 cost plus bytes ÷ mechanism ceiling. SOL is the largest of the three plus dispatch plus completion, and the table records which one won.
  4. Critical path. Longest path through the DAG plus edge sync costs. sol.py sums overlapping fabric demand against §5's knee and, if it is over, degrades every shared-memory node's bandwidth and re-derives until the graph is self-consistent. If the resulting critical path exceeds the window minus margin, it stops: the graph is wrong, and no implementation reaches that deadline. Do not start coding.
  5. Budgets. budget.md, per node, as a fraction of SOL — 0.70 memory-bound, 0.50 dispatch-dominated, 0.85 tight compute. Margin 25% on a shared fabric, 10% on bare metal.
  6. Measure with the real overlap, production cache state, and enough iterations for a p99. Append to measurements.csv with the build and the graph digest.
python3 scripts/sol.py --perf perf

Phase 3 — decide and gate

Let ratio = measured ÷ SOL.

  • ratio ≥ 0.7 — refuse to optimise the implementation. Say so plainly: this stage is within 30% of what the machine was measured to do, so tuning the code cannot buy what is being asked for. Only a graph change helps — fewer bytes, fewer ops, an in-place handoff, a different unit. Propose one, or stop. Do not profile.
  • ratio ≤ 0.3 — proceed, overhead first. Test whether the node is overhead-bound (dispatch, sync, small work, crossings) before touching the kernel. If tax dominates, the only allowed fix is fewer crossings: batch, coalesce, sendmmsg, an arena, shared memory. Only then static cycle analysis (llvm-mca, OSACA) for CPU nodes or roofline placement for GPU nodes. llvm-mca output is analysis, never a floor — it is a model, and a model in a measured column is the thing the hard rules forbid.
  • 0.3 < ratio < 0.7 — report both options with the remaining margin, and ask.
  • ratio > 1.0 — MODEL_DEFECT. A stage cannot beat its own floor. sol.py names the machine-model row that produced the winning floor; re-measure it. Do not report a win.

Any structural change re-derives the SOL before anything is compared. The graph digest in budget.md and measurements.csv enforces it: a row measured against a different graph is refused, not compared.

python3 scripts/budget_check.py --perf perf   # PERF_GATE: pass|fail

Gate: a node p99 over budget, or the critical path over the window minus margin, fails — unless budget_revisions.md carries a dated entry naming that node and saying why the derived budget was wrong. "It is slow" is not a reason; that is the finding, not the revision.

§8 has a bench. Both halves print a paste-ready tax: fragment with method: and date: already filled, and the C++ one warms up first because §7 says a cold number is about a cold machine:

g++ -O2 -std=c++17 -pthread -o tax_bench scripts/tax_bench.cpp && ./tax_bench
python3 scripts/tax_bench.py     # ffi_crossing and gil_acquire only

A real-NIC crossing needs a peer host, so no row here can measure it. It stays measured: false until somebody benches it against the peer the hot path uses.

§4 has one too. It prints a units: block for cpu (every core) and cpu_1core (one pinned core), plus cpu_python for a stage the interpreter runs:

g++ -O3 -std=c++17 -pthread -o unit_bench scripts/unit_bench.cpp && ./unit_bench
python3 scripts/unit_bench.py    # the cpu_python unit

-O3 is load-bearing. gcc only auto-vectorises there, and on the JP6 Orin the same source reads 1.75e10 op/s at -O2 against 4.76e10 at -O3. A compute floor built from the -O2 number is 2.7× too low, which raises every SOL above it and makes a stage that has real headroom read as though it were already at the metal. The bench refuses to compile unoptimised rather than let that happen quietly.

Pick the unit a node actually runs on. Measured on that same target, a Python stage sustains 2.1e7 op/s against the native single core's 5.96e9 — 282× — while its bandwidth row is identical, because a bytearray copy is the machine's memcpy. One unit cannot carry both.

§2, §5, §6 and §7 have benches too. Build what the target can take — the CUDA lane needs nvcc, everything else needs a compiler and Python:

nvcc -O3 -std=c++17 -o fabric_bw scripts/fabric_bw.cu && ./fabric_bw   # §2 + the gpu unit
g++ -O3 -std=c++17 -pthread -o load_gen scripts/load_gen.cpp
nvcc -O3 -std=c++17 -o load_gen_cuda scripts/load_gen.cu
python3 scripts/contention_sweep.py --load-gen ./load_gen \
        --load-gen-cuda ./load_gen_cuda                               # §5, the knee
g++ -O3 -std=c++17 -pthread -o sched_bench scripts/sched_bench.cpp && ./sched_bench  # §6
python3 scripts/thermal_watch.py                                      # §7, run this first

Run §7 first. It reports the equilibrium the other benches should use as their --warmup-seconds, and whether clocks: may say modelled-steady-state at all.

fabric_bw prints the integrated flag, and it changes the graph: on an integrated part a host-to-device copy never leaves DRAM, so it is a copy node the graph can often delete rather than a link it must pay for. It also refuses to derive a bandwidth from the bus width and clock — doing that on the Orin gave a figure below what the streaming kernel measured, and a ceiling you can exceed is arithmetic, not a datasheet.

No perf is needed anywhere. §6's rows — timer jitter, context switch, core to core, what pinning buys — are all observable from a process watching its own clock. What perf adds is attribution, which is a debugging tool for a stage that has already blown its budget, not an input to a floor.

lanes/ holds what dominates on each kind of target: soc.md is measured on a JP6 Orin, and workstation.md, mcu.md and phone.md carry no measured rows at all and say so. None of their numbers may be copied into a machine_model.md.

Not here yet

No DLA or other fixed-function row is measured anywhere, and the real-NIC crossing still needs a peer host. Those rows of machine_model.md stay filled by hand from whatever harness the repo already has, and every row still needs its method: and its date — that requirement does not relax because the bench is missing. A row you cannot yet measure stays measured: false and sol.py refuses to build a floor on it, which is the whole point: an unbuilt bench blocks a stage, it does not silently price it.

Phases 2 and 3 are complete and enforced by sol.py and budget_check.py.

Upstream

Where does this feature live: our tree, the dependency, or both in a stated order. Report only. One verdict, the evidence behind it, one ledger line.

Verdicts — exactly one, always

verdict meaning
local-permanent ours forever, no upstream attempt
shim-now-plus-PR ship the local shim, submit upstream in parallel, drop the shim on release
upstream-only-blocked no local copy; the work waits on the merge
fork-pinned patch the dependency and pin it; the pin is a maintenance tax
issue-first-no-code file the issue, write nothing, wait for a maintainer

Discriminators — all four, every run

generality — does the patch need one of our concepts to make sense. ours: an internal-project concept appears in the patch itself — see the banned-name list in ~/.claude/rules/public-surface.md. general: it stands alone for any user of the dependency.

latencyblocks if a delivery date depends on the merge, else free.

receptivity — looked up, never asked, never settled from memory alone. receptive: a comparable PR merged within 12 months, or an open issue or roadmap entry asking for it. hostile: a comparable PR closed unmerged, a maintainer statement against the design, or no release in 18 months. A relationship constraint is not a receptivity signal — it caps who writes the text, not whether the patch would land. unknown: neither. Out of queries with nothing found is unknown, not hostile.

carry cost — what the local alternative costs to keep: a named maintenance tax and a named trigger to revisit. Breaks ties only, on two rows.

Precheck — is there an upstream at all

No OSS project accepts the patch — a closed-source or vendor-only SDK (NVIDIA jetson_multimedia_api, DeepStream, a binary driver) — then the verdict is local-permanent and no query is spent. The table below assumes a maintainer who can say yes.

Receptivity lookup — five queries, hard cap

  1. merged PRs in the area
  2. closed-unmerged PRs in the area
  3. open issues in the area
  4. last release, or last commit if the project does not tag
  5. CONTRIBUTING or roadmap — only if 1-4 come back ambiguous

GitHub: gh pr list --repo <r> --search '<area>' --state merged --limit 5, the same with --state closed, gh issue list --repo <r> --search '<area>', gh release list --repo <r> --limit 1. GitLab (Eigen, freedesktop): the project REST API — /projects/<id>/merge_requests?scope=all&state=merged&search=<area>, /issues?search=<area>, /repository/tags?per_page=1. GStreamer's github.com/GStreamer/gstreamer is a mirror with PRs and issues disabled — querying it returns empty, not hostile. Use the freedesktop API.

Every receptivity claim carries a URL or it is not evidence.

Decision table

generality latency receptivity verdict
ours * * local-permanent
general blocks receptive shim-now-plus-PR
general blocks unknown shim-now-plus-PR, issue filed alongside
general blocks hostile carry cost decides
general free receptive upstream-only-blocked
general free unknown issue-first-no-code
general free hostile carry cost decides

Carry cost decides on the two hostile rows only, along one axis: shim or pin. A shim at the dependency's API boundary wins. fork-pinned only when no such shim exists — then record the pin's tax and its revisit trigger with it. A hostile row carried by a shim, with no upstream attempt left open, is local-permanent.

Scrub — mandatory on every non-local verdict

  • origin repo of the feature
  • internal names to replace, and what to replace them with: the banned-name list in ~/.claude/rules/public-surface.md, which is the only copy. Do not restate it here or anywhere else that gets committed.
  • identity to commit under: never the work address, never a Signed-off-by
  • the grep to run before pushing: build the alternation from that same list and run it over the patch

A GStreamer or freedesktop target caps the output. Name the points the MR, commit message or comment must cover, and stop. Never draft the text. nirbheek's final warning of 2026-03-25 stands.

Ledger

~/.claude/upstream-verdicts.md, append-only, one line per verdict:

YYYY-MM-DD | <dependency> | <feature> | <verdict> | <evidence + URL> | scrub: <origin, names, identity, grep result + date> | revisit: <trigger>

The scrub field is comma-separated — | is the column delimiter. On a local verdict it is scrub: n/a (local). Record the identity as a label — personal identity, work identity — never a literal address: the ledger is committed and the pre-commit hook blocks emails. A recorded scrub says what was checked and when; it never substitutes for re-running the grep before a push.

Read it before scoring. A hit on the same dependency and feature short-circuits: print the prior verdict with its date, evidence and scrub, and spend no queries. Re-score only once the recorded revisit trigger has fired.

Output

verdict: <one of five>
because: generality=<> latency=<> receptivity=<> — <the URL that decided it>
scrub:   <origin> | <names> | <identity> | <grep>
next:    1. ...
ledger:  <the appended line>

Where a step is "file an issue" or "open an MR", next names the points the text must cover. It never contains the text.

Boundaries

Third-party OSS dependencies only — not our own repos, not internal shared libs, not standards bodies. Forward-looking only: no repo scan, no pin sweep, no ledger sweep. Report only: opens no issue, pushes no branch, writes nothing but its own ledger line.

Verifying a Generated Diff

Code written together with its own tests has a specific failure mode: the tests encode what the code does rather than what it should do. Green means the two agree, which they always will. So the normal reviewer heuristic — read the changed assertions, they're the spec — inverts and becomes actively misleading.

This skill replaces reading with perturbation. Three passes, run in order:

Pass Question it answers Perturbation
A. Mutation Do the tests test anything? Corrupt the logic
B. Subtraction Is this code load-bearing? Remove the code
C. Interrogation What does this assume? None — claims + verifiers

A and B are the same loop: perturb → build → test → classify → revert. The revert must be automatic, which is what scripts/mutate.sh is for.

Run A first. There is no point reasoning carefully about code whose tests turn out to be decorative.

Before starting: is this the right tool?

Skip the passes and just read the diff if it's under ~50 lines. The passes cost more than the reading.

Stop and say so if the diff is more than a few hundred lines of unfamiliar generated logic. These passes assume you can already name the three or four functions that carry the real logic. If nobody can, the problem is upstream — the change needs splitting or regenerating in reviewable increments, and no amount of verification rigor substitutes for that. Say this plainly rather than performing a review that can't work.

Step 0 — Establish the ground

Do these four things before any perturbation. Skipping any of them makes every later result uninterpretable.

1. Find the review baseline. Everything is diffed against the last point the user actually understood, not against HEAD (which moves when an agent commits).

git tag -f reviewed <sha>          # or HEAD~N, or the branch point
git diff --stat -M reviewed | tail -1

If the user doesn't know the baseline, ask — it's a one-line question and guessing wrong wastes the whole session.

2. Confirm the baseline is green. A red suite before you start turns every result into noise.

3. Get the loop fast. Mutation is N rebuilds. Under ~20 s incremental and this works; at four minutes it doesn't and the user won't run it again. Find the single test target rather than building the tree. See references/languages.md for per-toolchain setup.

4. Recover the real intent. The agent's summary of its own diff is circular — it's derived from the diff, so it can only agree with it. The non-circular artifacts are, in order of preference: a spec, an ICD, a ticket, or the user's original prompt. Ask which exists. If none does, say so explicitly: the passes can then only establish internal consistency, not correctness, and that's a weaker result the user should know they're getting.

Then use it, before any perturbation. Read the diff against that artifact and report three things, quoting the line each came from:

  • requirements it asks for that are missing or partial
  • behaviour in the diff that nobody asked for
  • requirements that look implemented but look wrong

This is cheap, it runs before the first build, and it is the only pass that can catch a change that is internally consistent and answers the wrong question. Report it separately from the mutation findings and never merge the two into one ranked list: a suite that kills every mutant while missing a requirement is exactly what the separation exists to expose.

Step 1 — Pick the targets

Three or four functions, not the whole diff. Want: real decisions — comparisons, arithmetic, loops, state transitions. Skip constructors, getters, logging, forwarding.

git diff --numstat -M reviewed | sort -k1 -nr | head
git diff -M reviewed | rg '^\+' | rg -c '\b(if|for|while)\b|[<>]=?|\*|/'

Then read for domain verbs in the changed names — predict, update, gate, validate, wrap, normalize, quantize, parse, pack, merge. Those are where a wrong answer is plausible and silent.

Also run the orientation survey, which is cheap and catches things reading misses:

scripts/survey.sh reviewed

It reports scope (files, new files), reinvention candidates (new symbols whose names already exist elsewhere), swallowed exceptions, and hot-path hazards.

Step 2 — Pass A: mutation

Full detail in references/mutation.md. The core:

Write four mutants per target function, one of each kind:

Mutant Edit Catches
Boundary <<=, >>= off-by-one, edge conditions
Constant dt*dt/2dt*dt, 0.51.0 wrong coefficient
Branch condition → true (or false) branch never exercised
Stub inject an early return at the top of the body test asserts nothing real

Put them in a TSV and run:

scripts/mutate.sh <file> '<literal-old>' '<literal-new>' [test-filter]
scripts/run_mutants.sh mutants.tsv

Classify:

  • KILLED — a test went red. That test does real work.
  • KILLED, oracle-dependent — a test went red, but its expected value is computed rather than stated: derived in the test body, taken from a snapshot generated by accepting current output, or read through the same helper the code uses. Report this as its own finding, not as a pass.
  • SURVIVED — green with wrong logic. This is the finding.
  • SURVIVED on a stub — the test for that function is worthless, not weak.
  • BUILD-FAIL — the compiler caught it; no information about the tests. The mutant was badly chosen. Rewrite it type-correct.

A KILLED verdict establishes that the test is sensitive to that mutation. It does not establish that the test's expected value came from anywhere but the code's own logic, and mutation cannot establish it: perturb the code and a computed oracle disagrees exactly as a real one would. So a test that encodes the same wrong formula as the implementation kills every mutant and still cannot catch a wrong formula. That is what the second verdict is for.

For each survivor: write the test that would have killed it, from the spec, not from the code — writing it from the code reproduces the original problem. Re-run the mutant to confirm it's now KILLED. If it isn't, the new test is also tautological. Commit tests separately from production changes.

For each oracle-dependent kill: replace the computed expected value with an independent one — a literal from the spec or ICD, a worked example, a capture from known-good hardware. If no independent source exists, say so; the test then pins behaviour against regression and nothing more, which is worth having and worth labelling.

Keep the TSV in the repo. It's a regression check on the test suite, which nothing else provides.

Step 3 — Pass B: subtraction

Full detail in references/subtraction.md. Same harness, new is empty.

The important correction to the naive version of this idea: survival does not mean the code is dead. It means the tests don't cover it. Those are different findings with different fixes. Resolve each survivor with a second question you answer yourself — can I construct an input that reaches this line and misbehaves without the guard?

Deletion Breaking input constructible? Verdict Action
KILLED Load-bearing, tested Leave it
SURVIVED Yes Load-bearing, untested Write that test
SURVIVED No, invariant guaranteed upstream Dead defensiveness Delete it
SURVIVED Unsure Unknown Guard → assertion + counter

The last row is the most useful outcome. A guard that can't be justified or disproved should stop silently absorbing the case and start announcing it, so the truth arrives from the field instead of never.

Separately and non-negotiably: any catch-all introduced by the change that doesn't rethrow, log with context, or set an error state gets removed or narrowed. scripts/survey.sh lists them.

Step 4 — Pass C: interrogation

Full detail in references/interrogation.md.

Never ask for an explanation of the code; fluent prose gets absorbed as understanding without a single claim being checked, which is the worst outcome because it removes the sense that anything remains to be checked. Ask for enumerable claims, then verify each with a tool. The question is worthless without its verifier.

Question Verifier
What does this assume about its inputs that it doesn't validate? Write the test that violates each assumption
What units / frame / scale / wrap range does each parameter use, and where is that documented? The spec or ICD — this is where generated code is reliably wrong and the compiler silent
What breaks if this is called from two threads? A two-thread test under a thread sanitizer
Which callers are affected if the return convention changes? git grep, then LSP references — not the model's answer; it invents call sites
What in this file is dead? Coverage report; zero-coverage lines in new files

Question hygiene: open rather than leading ("what does this assume about its inputs?" not "is this correct?" — the second gets agreement). Interrogate from a fresh context given only the diff, not told it was generated, so it reasons from the code alone as the reviewer must. Treat every answer as a candidate list.

Step 4b — Performance, when the repo has a budget

If perf/budget.md exists and the diff touches a file the budget names, these three passes say nothing about whether it is still fast enough. Run the performance gate:

python3 ~/.claude/skills/sol-budget/scripts/budget_check.py --perf perf

PERF_GATE: fail is a finding like any other — report it with the node and the overrun. PERF_GATE: pass goes in the report too, because a silent pass and a gate nobody ran look identical.

No perf/budget.md means no performance claim either way; say that under "Not established" rather than implying the diff is neutral. The sol-budget skill builds the budget.

Step 5 — Report

Report findings, not activity. Structure:

## Verdict
<one sentence: what is now trustworthy and what isn't>

## Spec
<missing or partial requirements, unasked-for behaviour, requirements
implemented wrong — each quoting the artifact. "No spec available" if none
existed. Never merged with Findings below.>

## Findings
<each: what, how it was established, what it means, suggested fix>

## Test suite assessment
<mutants run / killed / survived / killed-but-oracle-dependent, and specifically
which functions have worthless tests>

## Not established
<what these passes could not check — usually: correctness against a spec that
doesn't exist, behavior under real hardware/load, anything outside the target
functions>

The "Not established" section is required. The failure mode of a verification pass is leaving the user with unearned confidence, and the honest scope of the result is part of the result.

Reference files

  • references/mutation.md — mutant catalogue, how to write a stub mutant per language, handling flaky and slow suites, equivalent-mutant traps
  • references/subtraction.md — candidate enumeration, the guard→assertion conversion, hot-path considerations
  • references/interrogation.md — the question set with verifiers, sanitizer and coverage setup, what models get reliably wrong
  • references/languages.md — fast-loop setup and test-filter syntax for C/C++/CMake, Python, Rust, Go, TypeScript
  • references/verifying-generated-diffs.md — the same three-pass procedure as long-form prose, worked end to end on a C++/CMake example; read it to understand the reasoning, not as a lookup table

Scripts

  • scripts/mutate.sh — one mutant: refuses on a dirty file, substitutes, builds, tests, classifies, reverts on every exit path including Ctrl-C
  • scripts/run_mutants.sh — batch runner over a TSV
  • scripts/survey.sh — orientation: scope, reinvention, swallowed exceptions, hot-path hazards

  • mutants.tsv.example — annotated starting set covering all four mutant kinds plus a Pass B deletion

Configure via environment: BUILD_CMD, TEST_CMD, BUILD_DIR, BUILD_TARGET. Defaults are CMake/ctest; see references/languages.md for other stacks.

Two harness details that exist because getting them wrong produces confidently wrong results rather than errors:

  • mutate.sh runs a baseline build and test first unless MUTATE_BASELINE_OK=1. Without it, a missing test runner or a filter matching zero tests makes every mutant report KILLED — and KILLED reads as good news. A harness whose failure mode is false reassurance is worse than none.
  • Deletion mutants in a TSV use the literal token <DELETE>, never an empty field. Tab is IFS whitespace, so an empty field silently shifts every later column.

Safety

These scripts edit source files. They refuse to run on a file with uncommitted changes and restore via git checkout -- on every exit path — but that means a crash between write and restore leaves a mutated file. Before starting, confirm the working tree is clean and committed, and tell the user that's why. If they have uncommitted work they care about, have them commit or stash it first rather than working around the check.

Never leave a mutant in place. If a run is interrupted, verify with git status and git diff before doing anything else.

Wizard

A wizard is a bash script that walks a human, stage by stage, through a manual procedure that is tedious by hand and tedious to re-explain every session. It opens each URL, says what to click and copy, captures the values, writes them where they belong, confirms before anything irreversible, and shows how many stages remain.

The UX is already solved by template.sh: stage progress, hidden secret entry, cross-platform URL opening, idempotent .env upserts, gh secret / gh variable writes, confirmation gates, and a closing summary. Your job is only to scope the procedure and author its stages. The library above the STAGES marker is identical in every wizard. Never hand-edit it.

A wizard is ephemeral by default: one run, written to a scratch path, deleted afterwards. Commit it only when the user wants a repeatable setup path in the repo.

1. Scope the procedure

Work out every manual step and every value captured along the way. Read the repo first, do not ask cold:

  • Setup: .env, .env.example, .env.*, README, docker-compose*, framework config, and .github/workflows/* — every secrets.* and vars.* reference is a value the wizard must produce.
  • Migration or cutover: the current state, the target state, and every irreversible action between them.
  • Hardware or bench work: which box, which user, whether sudo needs a password, what has to be physically touched.

Then show the ordered stage list and the values each produces, and confirm it. The user may add, drop, or reorder.

Done when every stage is named in order and, for each value, you know where the human gets it, where it is written (.env, a CI secret, both, or nowhere — some stages are pure actions), and whether it is secret.

2. Map each stage's journey

For each stage, write the precise path: which URL, what to do there, where the value appears, which variable it fills. "Dashboard → Developers → API keys → Reveal test key → copy."

Where you do not know the current UI or the exact command, say so and ask, or check the docs. Never invent steps that may not exist — a wrong click path is worse than no wizard, because the human trusts it.

Done when every stage traces to instructions a stranger could follow.

3. Author it

Copy template.sh to the target path. Replace the example stage with one stage per step, in dependency order, and set TOTAL_STAGES.

Hold the bar the template sets:

  • open_url before asking for the value that page shows.
  • ask_secret for anything secret, ask otherwise.
  • write_env every persisted value; set_secret only what CI actually reads.
  • confirm before every irreversible action.
  • One focused task per stage — each stage clears the screen, so nothing the human still needs may scroll away.

4. Verify and hand off

  • bash -n <script>, and shellcheck if available.
  • chmod +x <script>.
  • Do not run it end-to-end yourself. It opens browsers and blocks on human input. Trace it statically instead: every value from step 1 is captured and lands where step 1 said, and every set_secret name matches a secrets.* reference in CI exactly.
  • Tell the user how to run it, and that ! bash <path> runs it in-session so its output lands in the conversation.
  • If it is a repeatable setup path, commit it and link it from the README, so the next person runs the script instead of asking an agent.

Reference for writing any document an agent consumes: a skill, an AGENTS.md / CLAUDE.md, a doc reached by a pointer. The packaging differs; the writing does not: the same levers make each one predictable, since the agent takes the same process every run rather than producing the same output.

When the document you're writing is a skill, read SKILL-MECHANICS.md for frontmatter, invocation choice, and router skills.

Context pointers

A context pointer is a reference held in the agent's context that names some out-of-context material and encodes the condition for reaching it. A skill's description is one; a line in AGENTS.md naming a doc is the same object. The pointer's wording, not its target, decides when the agent reaches the material, and how reliably. A must-have target behind a weakly worded pointer is a variance bug: sharpen the wording first, and inline the material only if sharpening fails.

A pointer does two jobs: state what the material is, and list the branches that should trigger reaching it (a branch is a distinct case the document handles, so different runs take different paths through it). Every word of an always-loaded pointer costs on every turn, so it earns even harder pruning than the body:

  • Front-load the leading word: the pointer is where it does its triggering work.
  • One trigger per branch. Synonyms that rename a single branch are one branch written twice; collapse them and keep only genuinely distinct branches.
  • Cut identity the body already carries.

The two loads

Every document and pointer you add spends one of two budgets:

  • Context load is the cost of always-loaded material on the agent's window: an AGENTS.md line, a skill description, anything sitting in context every turn, spending tokens and attention whether or not it fires.
  • Cognitive load is the cost on the human: which documents exist and when to reach for each. The human is the index. Not a cost to minimise: it is the price of human agency; spend it where human judgement matters, remove it where it does not.

Material reached only through a pointer escapes context load at the price of the pointer's own line; material with no pointer at all rides entirely on cognitive load.

Information hierarchy

A document is built from two content types: steps (the ordered actions the agent performs) and reference (definitions, rules, facts consulted on demand). The two mix freely: all steps (a recipe), all reference (a review's rules, this skill), or both. The core decision is where each piece sits on the information hierarchy, a ladder ranked by how immediately the agent needs the material:

  1. In-file step is the primary tier: what the agent does, in order.
  2. In-file reference is consulted on demand. Often a legitimately flat peer-set (every rule of a review on one rung), which is a fine arrangement, not a smell.
  3. Disclosed reference is pushed out into a separate file, reached by a context pointer, loaded only when the pointer fires. Spans a sibling file in the same folder through fully external reference that lives anywhere and any document can point at.

Push too little down and the top bloats; push too much and you hide material the agent actually needs. That tension is the whole decision.

Progressive disclosure is the move down the ladder (out of the main file and behind a pointer) so the top stays legible. Not primarily a token optimisation: it is how the hierarchy is protected. Branching is the cleanest disclosure test: inline what every branch needs, and push behind a pointer what only some branches reach. When a document has steps, in-file reference that should be disclosed buries them and turns attending to them into a coin-flip: a variance lever, not just a legibility one.

Co-location is the within-file companion: where the ladder decides how far down a piece sits, co-location decides what sits beside it once there. Keep a concept's definition, rules, and caveats under one heading rather than scattered, so reading one part brings its neighbours with it. The test: the document should read like documentation written for the agent. Grouped material reads that way; scattered material does not. (Distinct from duplication: that repeats one meaning in two places; scattering fragments one meaning across many.)

Sprawl is the failure mode here: a document simply too long, even when every line is live and unique. Attention thins across the excess, and every extra line is one more to keep relevant. The cure is the ladder: disclose reference behind pointers, and split by branch or sequence so each path carries only what it needs.

Steps and completion criteria

Every step ends on a completion criterion, the condition that tells the agent the work is done. Two properties make it a lever:

  • Clarity: can the agent tell done from not-done? A vague bound ("understanding reached") invites premature completion: ending the step before it is genuinely done, attention slipping to being done. The visible steps still ahead (the post-completion steps) supply the pull; the criterion's clarity is the resistance. Defend in order: sharpen the bound first (local and cheap); only if it is irreducibly fuzzy and you observe the rush, hide the later steps by splitting the sequence. Hiding only works across a real context boundary (a hand-off or a subagent dispatch; an inline call leaves the later steps in context and clears nothing).
  • Demand: how much it requires. "Every modified model accounted for" forces thorough work where "produce a change list" does not. Demand drives legwork (the digging the agent does within the work, latent in the wording rather than written as its own step), and it is not step-bound: "every rule applied" binds a body of flat reference just as "every step done" binds a sequence, which is how an all-reference document still carries an exhaustiveness bar.

The strongest criteria are both checkable and exhaustive.

When to split

Splitting one document into two spends one of the two loads, so split only when the cut earns it:

  • By sequence: split a run of steps where the post-completion steps tempt the agent to rush the one in front of it. Keeping them out of view drives more legwork on the current task. Beware the reverse: merging sequences exposes each step's later steps to what follows, inviting premature completion.
  • By invocation, skill-specific: see SKILL-MECHANICS.md.

Leading words

A leading word is a compact concept already living in the model's pretraining that the agent thinks with while running the document (lesson, fog of war, tracer bullets). Repeated as a token, never as a sentence, it accumulates a distributed definition and anchors a whole region of behaviour in the fewest tokens, by recruiting priors the model already holds. Coining your own works if you define it clearly, but a made-up word recruits no priors: you pay in definition tokens what a pretrained word gives free; reach for an existing word first.

It anchors twice. In the body, execution: the agent reaches for the same behaviour every time the word appears, and inside flat reference it focuses attention on a class of thing to look for. In a pointer, invocation: when the same word lives in your prompts, your docs, and your codebase, the agent links that shared language to the material and reaches it more reliably.

Hunt for opportunities to refactor with leading words. A triad spelled out at three sites, a pointer spending a sentence to gesture at one idea. Each is a passage begging to collapse into a single token:

  • "fast, deterministic, low-overhead" → tight (a tight loop).
  • "a loop you believe in" → red, turning a fuzzy gate into a binary observable state (the loop goes red on the bug, or it doesn't).

You win twice: fewer tokens, and a sharper hook for the agent to hang its thinking on. Assume every document is carrying restatements that leading words retire. Go find them.

Negation is the failure mode beside this lever: steering by prohibition drags the forbidden behaviour into context and makes it more available, not less. Don't think of an elephant, and the elephant is all there is; the negation is a weak modifier the strongly-activated concept overruns, so the ban half-reads as an instruction to do the thing. Prompt the positive: state the target behaviour ("write one-line comments") so the banned one is never spoken. A prohibition earns its place only as a hard guardrail you cannot phrase positively; even then, pair it with the positive target so attention lands on what to do.

Pruning

  • Keep each meaning in a single source of truth: one authoritative place, so changing the behaviour is a one-place edit. Duplication (the same meaning in more than one place) costs maintenance and tokens, and inflates a meaning's prominence on the ladder past its real rank. (The accidental inverse of a leading word, which repeats a token on purpose, never the meaning.)
  • The environment is a source of truth too (package.json scripts, config files, the directory layout, --help output), and a document that restates it is a cache: a copy of a lookup, earning its load only when the lookup is expensive. Cache what the agent cannot find by looking: the unwritten convention, the reason behind a choice, the gotcha no config confesses. Leave the one-file, one-command lookups to the environment, where they cannot go stale.
  • Check every line for relevance: does it still bear on what the document does? A line loses relevance by never bearing on the task (mere exposition, or a branch that should be disclosed) or by going stale as the behaviour or world it describes changes. Shorter documents are easier to keep relevant. Without a pruning discipline the default fate is sediment: stale layers that settle because adding feels safe and removing feels risky, until you must core down through them to find what is still live.
  • Hunt no-ops sentence by sentence: an instruction the model already obeys by default pays load to say nothing. The test (does it change behaviour versus the default?) is model-relative, not reader-relative: two people disagreeing about a no-op disagree about the default, and settle it by running the document, not by debate. When a sentence fails, delete the whole sentence rather than trim words from it. The test also grades leading words: a word too weak to beat the default (be thorough when the agent is already thorough-ish) is a no-op, and the fix is a stronger word (relentless), not a different technique.