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:
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:
- Failing test at whatever seam reaches the bug.
- Curl / HTTP script against a running service.
- CLI invocation with a fixture input, diffing output against a known-good snapshot.
- Headless driver script that drives the UI and asserts on DOM, console, or network.
- Replay a captured trace. Save a real payload, pcap, rosbag, ulog or event log to disk; replay it through the code path in isolation.
- Throwaway harness. A minimal subset of the system, one function call, mocked neighbours.
- Property / fuzz loop. For "sometimes wrong output": 1000 random inputs, look for the failure mode.
- 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 runcan drive it. - Differential loop. Same input through two versions or two configs, diff the outputs.
- 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.
- Debugger or REPL inspection where the environment allows it. One breakpoint beats ten logs.
- Targeted logs at the boundaries that distinguish hypotheses.
- 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:
- Turn the minimised repro into a failing test there.
- Watch it fail.
- Apply the fix.
- Watch it pass.
- 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 documentedmake landtarget, then alandscript in the repo. - If none is found, stop and ask how to deploy this repo. Do not infer docker /
compose commands from
Dockerfileordocker-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.
rebuild— rebuild the image, then restart. Slow but safe. Just do it. (Rebuild must produce compiled artifacts, not just a dev environment; chown outputs back to1001:1002if the build runs as root.)reset— tear 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 explicitresetword 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¶
- Append the item verbatim to
parked-ideas.md, creating the file if absent. - Acknowledge in one line:
📌 queued (N): <item>, where N is the list length. - 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
measuredcolumn.
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¶
- Graph. Write
perf/dataflow.yaml. Nodes are stages with the unit that runs them. Every non-inplacecell of the handoff matrix becomes its own copy or convert node, and the edge names it withvia:. Every kernel or runtime crossing — syscall, socket op, hot alloc, lock, wake, FFI — is acrossings:entry priced from §8. Nothing hides inside a library call. - 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.
- Floors.
sol.pyderives 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. - Critical path. Longest path through the DAG plus edge sync costs.
sol.pysums 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. - 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. - Measure with the real overlap, production cache state, and enough iterations for
a p99. Append to
measurements.csvwith the build and the graph digest.
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.pynames 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.
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.
latency — blocks 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¶
- merged PRs in the area
- closed-unmerged PRs in the area
- open issues in the area
- last release, or last commit if the project does not tag
- 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).
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:
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/2 → dt*dt, 0.5 → 1.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:
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 trapsreferences/subtraction.md— candidate enumeration, the guard→assertion conversion, hot-path considerationsreferences/interrogation.md— the question set with verifiers, sanitizer and coverage setup, what models get reliably wrongreferences/languages.md— fast-loop setup and test-filter syntax for C/C++/CMake, Python, Rust, Go, TypeScriptreferences/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-Cscripts/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.shruns a baseline build and test first unlessMUTATE_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/*— everysecrets.*andvars.*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
sudoneeds 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_urlbefore asking for the value that page shows.ask_secretfor anything secret,askotherwise.write_envevery persisted value;set_secretonly what CI actually reads.confirmbefore every irreversible action.- One focused task per stage — each
stageclears the screen, so nothing the human still needs may scroll away.
4. Verify and hand off¶
bash -n <script>, andshellcheckif 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_secretname matches asecrets.*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.mdline, 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:
- In-file step is the primary tier: what the agent does, in order.
- 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.
- 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.jsonscripts, config files, the directory layout,--helpoutput), 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.