feat(optimize): add --subtract, the subtraction axis, behind a deterministic floor
Every command so far asked an addition question — what to add, what to move, what it costs. Nothing asked what is no longer earning its always-loaded rent. This adds that axis as a fourth lensCheck on the existing hybrid motor rather than a new scanner or a 22nd command: the measured payoff (~18% of one file) justifies a mode, not machinery. It is the only lens that proposes REMOVING config, so it carries a guarantee the others don't need: a load-bearing block is never a candidate. Precision is asymmetric — a missed dead line costs a few tokens per turn, a deleted one costs a wrong remote or a broken script — so the floor is decided in code (lib/floor-exclusion.mjs) before the opus judge sees anything, never in prose. Granularity is the leaf block, with two structural exceptions: a paragraph ending in ':' merges with the list it introduces, and an ordered list is a contract whose steps inherit floor from any sibling. Unordered lists deliberately do not inherit — a load-bearing bullet and a disposable one routinely share a list, and container-reasoning is the error the hand-built ground truth exists to catch. Verified against that ground truth (built before any classifier existed), with the comparison machine-checked rather than read by eye: zero load-bearing blocks proposed, 11/18 deletable groups surfaced, ~756 tok ~ 18% of a ~4300 token file — inside the pre-registered band. The first run found five floor violations the synthesized fixture missed; each got a structural rule and a fixture shape so it cannot regress. Three real bugs the dogfood run exposed, all now covered: - JS \b is ASCII-only, so /\bunngå\b/ never matches — every Norwegian keyword ending in æ/ø/å was silently dead. - A bare word/word is not a path; "pros/cons" vetoed the largest deletable block until PATH_RE was tightened to rooted paths and globs. - "Mid-sentence" must key on a preceding lowercase letter; the loose version read **bold labels:** and quoted openers as entities, costing 4 of 11 groups. BP-SUB-001 is grounded entirely in the Anthropic steering blog already cited by BP-MECH-001..004 and asserts nothing from the talk that motivated the feature — no "80%", no ablation figure. Suite 1365 -> 1382/0. Frozen v5.0.0 snapshots untouched; plain optimize output byte-identical on identical input (--subtract adds keys only when passed). knowledge-refresh-cli's reference date moved to 2026-08-01: its premise that every seed entry was verified 2026-06-20 expired when BP-SUB-001 got a genuine verification date, and backdating the entry to fit the test would have been a lie about when its source was checked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RW2haJXbxZpKivKHseSXNh
This commit is contained in:
parent
c0625c568f
commit
e9921d3c9d
13 changed files with 1080 additions and 32 deletions
|
|
@ -33,6 +33,39 @@ whether the line is *really* that kind of instruction:
|
|||
| `claude-md-lifecycle-phrasing` | BP-MECH-001 | a recurring automation the model is *told* to perform ("after every commit, run X") — something that should happen deterministically, not at the model's discretion | a **hook** (PreToolUse / PostToolUse / Stop) |
|
||||
| `unscoped-path-specific-instruction` | BP-MECH-002 | a constraint that only applies when a *specific* file/path/glob is touched, sitting in root CLAUDE.md where it loads every turn regardless | a **path-scoped rule** (`.claude/rules/` with `paths:` frontmatter) |
|
||||
| `never-instruction` | BP-MECH-004 | an *absolute* prohibition — something that must NEVER happen, where relying on the model to remember is the wrong guarantee | a **permission deny rule** or PreToolUse hook |
|
||||
| `compensatory-instruction` | BP-SUB-001 | **`--subtract` mode only.** an instruction that corrects general model *behaviour* rather than stating a local fact — so it pays an always-loaded token cost without telling the model anything it could not work out | **removal**, re-added only if the model actually stumbles |
|
||||
|
||||
## 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.
|
||||
|
||||
**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
|
||||
pin, policy invariant, or an unresolved capitalized entity — plus the steps of
|
||||
any ordered list whose siblings carry one. You never see those blocks, and you
|
||||
must not reason about whether some *other* block ought to be deleted. Judge only
|
||||
what you are given. Precision is asymmetric: a missed dead line costs a few
|
||||
tokens per turn; a deleted load-bearing line costs a wrong remote, a broken
|
||||
script, or a lost afternoon.
|
||||
|
||||
**2. Staleness is NOT a deletion signal.** A block that pins an outdated version
|
||||
("use Opus 4.8") is a *dead-reference* problem for `drift` / `CA-CML`, not a
|
||||
subtraction finding. The instruction is still load-bearing — it encodes a
|
||||
decision only the operator can make; it is merely out of date. Recommending
|
||||
deletion because content looks stale is a category error. Say "this looks
|
||||
outdated" if you must, but never as a removal candidate.
|
||||
|
||||
**3. Tier 2 is not tier 3.** Deletable splits into *earned* (compensatory, but
|
||||
this model still stumbles on it, so it returns) and *dead* (never missed). Sort
|
||||
every candidate into one of the two and say which. A block that has visibly
|
||||
earned its place — its subject matter recurs in the repo's own history — is tier
|
||||
2 even when its classification is "compensatory". Reporting it as dead weight is
|
||||
wrong even though the label matches.
|
||||
|
||||
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.
|
||||
|
||||
## Input
|
||||
|
||||
|
|
@ -45,6 +78,10 @@ You receive an `optimize-lens` payload (JSON) with:
|
|||
`recommendation`, `severity`, `source`). Only CONFIRMED register rules reach
|
||||
you.
|
||||
- `register` — the full confirmed prose-judgment entries, for reference.
|
||||
- `subtract` — **present only under `--subtract`.** `{ enabled, candidates,
|
||||
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.
|
||||
|
||||
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
|
||||
|
|
@ -104,6 +141,15 @@ Write `optimization-lens-report.md` to the session directory (≤120 lines).
|
|||
{Brief, honest: candidates you dropped and why — "line 22 mentions a path but is
|
||||
a cross-reference, not an instruction." This is the precision gate showing its
|
||||
work. Keep to a few lines.}
|
||||
|
||||
## No longer earning its rent (--subtract only)
|
||||
|
||||
{Omit entirely unless the payload has a `subtract` block. Two sub-lists —
|
||||
**Dead** (tier 3, out and never missed) and **Earned** (tier 2, out but likely
|
||||
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}
|
||||
```
|
||||
|
||||
Omit any section with zero kept findings (except keep the "left alone" note when
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue