feat(scanners): a redundancy claim that belongs to one model is scoped to it

Anthropic documents that Claude Opus 5 verifies its own work, and that telling
it to double-check or to delegate verification to a subagent causes
over-verification -- token cost with no quality gain. The general subtraction
detector (BP-SUB-001) already surfaces those blocks for every user, with no
model-awareness at all.

`optimize --subtract --for-model <name>` adds the missing half. It ANNOTATES a
subset of the candidates --subtract already produced; it is not a second
detector and can never widen the candidate set. A second SUBTRACT_DETECTORS
entry would have collided with BP-SUB-001 on de-dup, and a prose-only signal in
the agent prompt would have been untestable.

There is no auto-detection, by measurement rather than omission: a CLAUDE.md has
no frontmatter and no resolvable target model, and this operator's own `route`
skill deliberately runs a different model per session -- the same file is read
by whichever model comes next. So the model is named, and the citation is
reported as conditional everywhere a human sees it (agent report copy, and the
Step 7a listing that is the last surface before an approval file).

Precision comes from the TARGET, not the verb list. Measured across the
409-file corpus: 392 BP-SUB-001 candidates, 31 (7.9%) carry a verify verb, and
0 also carry a reflexive or delegated target. Two independent raw-text greps
found 0 as well, so the zero is the corpus rather than an over-narrow regex.
Those 31 verb-only blocks -- "sjekk relevante config-filer", "Type-sjekk:
pyright", "To verify plugin functionality" -- are exactly the false positives a
verb-only version would have produced, which is BP-JUDG-001's 7/7 failure
arriving one lens over. The numbers live in the register entry's note and are
pinned by a test, because a session that cannot see the measurement reads the
zero as a broken detector and loosens it.

`recognized` is reported separately from `matchedCount`: a typo'd model name and
a genuinely clean config both yield zero, and without the distinction the CLI
would report a silent no-op as good news. Dogfooded on the real machine --
`opus-5` gives recognized:true/matchedCount:0, `oppus5` gives recognized:false.

source.published is absent because the guide carries no visible publish date;
its absence is asserted so a later session does not invent one to match the
other entries' shape. Both quoted sentences were verified verbatim 2026-08-12.

The payload stays additive -- forModel and per-candidate modelScope appear only
under the flag, so a plain --subtract run is byte-identical to before (asserted
on the serialized bytes, since a key set to undefined passes a shallow check).

Suite 1724 -> 1752 (+28). The one remaining failure is the pre-existing
drift-cli --output-file crash, untouched by this work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BRuXt6tZyowi8QYNKLSHQm
This commit is contained in:
Kjell Tore Guttormsen 2026-08-12 23:16:23 +02:00
commit 7df8e0d65b
11 changed files with 650 additions and 10 deletions

View file

@ -38,8 +38,8 @@ whether the line is *really* that kind of instruction:
## The subtraction lens (`--subtract` only)
Present only when the payload has a `subtract` block. It asks the inverse of
every other lens: *what is no longer earning its always-loaded rent?* Three
things make it different, and all three are non-negotiable.
every other lens: *what is no longer earning its always-loaded rent?* Four
things make it different, and all four are non-negotiable.
**1. The floor is not yours to decide.** A deterministic pre-step has already
excluded every block carrying a local fact — a code span, path, domain, version
@ -64,6 +64,15 @@ earned its place — its subject matter recurs in the repo's own history — is
2 even when its classification is "compensatory". Reporting it as dead weight is
wrong even though the label matches.
**4. A model-scoped citation is conditional, never authoritative.** When a
candidate carries `modelScope` (only under `--for-model`), apply the SAME tier-2
/ tier-3 judgement as any other `compensatory-instruction` candidate — the model
tag sharpens the citation, it does not bypass precision rule 1. State it as
conditional in the report copy ("redundant if targeting {model}; this operator
may run other models in other sessions"), never as a settled fact for all future
sessions. The tag is an annotation on a candidate the general detector already
found; it is not evidence that the block is dead.
Rank kept candidates by always-loaded token cost, and state the total payoff.
Frame it as *rent*, never as a mistake: this config was correct when written.
@ -82,6 +91,12 @@ You receive an `optimize-lens` payload (JSON) with:
register, detectors }`. Each candidate spans `line``endLine` (a whole leaf
block, not one line) and carries `signalText` plus the BP-SUB-001 register
block. Everything load-bearing was already removed before you saw this.
Under `--for-model <name>` the block also carries `forModel`
(`{ requested, recognized, matchedCount }`), and a SUBSET of its candidates
carry `modelScope` (`{ registerId, claim, requestedModel }`). If
`recognized` is `false`, the operator named a model the register does not
cover — say so, and do not treat the absence of tags as evidence of a clean
config.
Always **Read the actual CLAUDE.md file(s)** named in the candidates before
judging — `signalText` is one line out of context; the surrounding lines decide
@ -150,6 +165,8 @@ to return) — ranked by token cost, with a payoff total. For each:}
**{file}:{line}-{endLine}** — {what the block says, in one line} · ~{N} tok/turn
Tier: {dead | earned — and why}
Source: {register.source.url}
Model-scoped: {only if the candidate carries `modelScope`} {claim}, for
{requestedModel} (conditional — verify this is still the model you target)
```
Omit any section with zero kept findings (except keep the "left alone" note when