HKV now flags hooks that build hookSpecificOutput.additionalContext from
un-grepped command output as an INFO advisory (weight 0, never severity-bearing
— excluded from the self-audit nonInfo set). That field enters Claude's context
every time the hook fires (plain stdout on exit 0 does not), so an unfiltered
payload is a recurring per-turn token cost.
- New lib scanners/lib/hook-additional-context.mjs: pure assessHookAdditionalContext
(unit-tested, no IO) + IO wrapper assessHookContextForRepo (walk hooks->scripts).
Heuristic: additionalContext + verbose-prone capture (cat/git log/execSync/…) &&
no filter (grep/head/jq/.slice). Deliberately low precision -> advisory only.
- HKV: emits the advisory inline on scripts it already reads (after the M5 verbose
check). Additive, info severity -> frozen v5.0.0 + SC-5 snapshots untouched.
- feature-gap: new filterHookLeverFinding companion, fires ONLY when >=1 chatty
hook is detected — surfaces the filter-before-Claude-reads lever (CC
filter-test-output.sh). Silent otherwise (opportunity, not noise).
- docs: README HKV + GAP scanner rows; scanner-internals.md HKV row + full B5
implementation note. CLAUDE.md kept lean ([skip-docs]); B5 fully documented in
README + scanner-internals.
Mechanism verified 2026-06-23 against code.claude.com/docs context-window.md
(additionalContext enters context; plain stdout does not). Suite 1239 -> 1254 green.
Version/badges/CHANGELOG wait for the v5.10 release cut.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
By default Claude Code defers MCP tool schemas (names-only, ~120 tok; full
schemas on demand via tool search). CA-TOK-006 detects config-file signals that
force the FULL schemas into the always-loaded prefix every turn:
- settings.json env.ENABLE_TOOL_SEARCH="false" (high)
- "ToolSearch" in permissions.deny (high)
- configured model is a Haiku model (medium)
- per-server .mcp.json alwaysLoad:true (CC v2.1.121+) (high)
auto[:N] is threshold mode (info, not a trigger).
New engine lib/mcp-deferral.mjs: pure assessMcpDeferral({settings,mcpServers})
(unit-tested, no IO) + thin IO wrapper assessMcpDeferralForRepo shared by TOK and
GAP. Severity scales with aggregate forced-upfront tokens (medium-confidence
reasons cap at medium). feature-gap cliOverMcpLeverFinding fires only as a
companion to CA-TOK-006 (prefer gh/aws/gcloud over MCP for common ops).
Honest scoping (Verifiseringsplikt): triggers on config files ONLY — never
process.env shell vars. Vertex / custom ANTHROPIC_BASE_URL / runtime /model
switch are launch state (would flap snapshots machine-dependently), so they are
DISCLOSED in every finding, not triggered. Tool-level anthropic/alwaysLoad and
claude.ai connectors likewise disclosed. Mechanism verified 2026-06-23 against
code.claude.com/docs (context-window.md, mcp.md#configure-tool-search +
#exempt-a-server-from-deferral, costs.md); the prefix-cache invalidation claim
was NOT-CONFIRMED in docs and is not asserted.
alwaysLoad added to CA-MCP VALID_SERVER_FIELDS (no longer flagged as unknown).
active-config-reader surfaces per-server alwaysLoad. Byte-stable: CA-TOK-006
fires only on new conditions; frozen v5.0.0 + SC-5 snapshots untouched.
Tests 1215 -> 1239 (engine 16, integration 5, lever 2, mcp-field guard 1).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cuts the v5.9.0 release bundling the three hardening gaps shipped since v5.8.0:
B1 (agent-listing budget — new orchestrated scanner AGT, count 15→16),
B2 (machine-wide always-loaded token roll-up in the campaign ledger), and
B3 (cache-aware filtering + stale plugin-cache disk-cleanup finding).
Version sync: plugin.json 5.8.0→5.9.0; README badges version 5.9.0 /
scanners 15→16 / tests 1168→1215; README intro + Health prose + scanner
table (new AGT row, TOK 7th pattern) + self-audit prose 15→16; new
version-history row; CHANGELOG [5.9.0]; CLAUDE.md finding-ID example (+CA-AGT)
and test count 1168→1215 / 67→68 files (scanner dir 35→36).
Gate: `self-audit --json --check-readme` -> readmeCheck.passed:true,
mismatches:[]. Full suite green (1215, 0 fail). Tag + catalog ref-bump follow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The B3a cache filter already exposes discovery.staleCacheVersions; surface them
as a finding so the user knows the superseded plugin versions are safe to delete.
Honesty (Verifiseringsplikt): the finding loads on ZERO turns, so it must NOT
read as a per-turn token cost. TOK normally humanizes to "Wasted tokens"; a new
per-finding category override ('plugin-cache-hygiene' -> "Dead config") plus a
dedicated humanizer translation ("Old plugin versions are sitting on disk (safe
to delete) ... cost zero tokens per turn ... housekeeping, not a performance
problem") keep the prose accurate instead of the generic "using more space"
default. evidence carries the explicit "zero live-context impact" note.
- token-hotspots: Pattern H emits CA-TOK (low) from discovery.staleCacheVersions
when stale versions exist (`--global`); silent otherwise.
- humanizer: CATEGORY_TO_IMPACT lets a finding's category override the
scanner-default impact label (raw `category` field unchanged -> --json/--raw
byte-stable). humanizer-data: honest static translation for the finding.
- Tests: finding fires/severity/category, lists stale keys + zero-impact note,
silent when none; humanizer override -> Dead config; honest translation locked.
- Docs: tokens command render note (disk-cleanup, not a token problem) +
--no-exclude-cache flag; README + CLAUDE.md rows (7 patterns, cache-aware).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stale ~/.claude/plugins/cache versions polluted token-hotspots ranking and
inflated CNF duplicate-hook findings with config that loads on zero turns.
installPaths point INTO the cache, so a blunt "skip all of plugins/cache"
would drop ACTIVE plugins — the filter is therefore version-aware: it reads
the adjacent installed_plugins.json, keeps active version dirs, drops only
stale ones (and exposes them via discovery.staleCacheVersions for B3b).
- file-discovery: cacheVersionKey() + applyCacheFilter() (active vs stale via
installed_plugins.json; HOME-independent, derives the manifest from the cache
path); discoverConfigFiles/Multi gain { excludeCache } + staleCacheVersions.
Absent/unparseable manifest -> no filtering (never silently drop live config).
- token-hotspots-cli + scan-orchestrator: --exclude-cache (default ON for these
live-cost scans) / --no-exclude-cache restores the full walk.
- Tests: cacheVersionKey unit cases; stale-dropped/active-kept/no-manifest
discovery cases; CNF-drop proof (soft-spot verified, not assumed:
include=1 -> exclude=0 duplicate-hook findings).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wires the user-facing surface of the machine-wide token roll-up (executable CLI
shipped in B2b-2, d664b70). The campaign report (Step 2) now renders rollUp.tokens:
the headline machine-wide always-loaded total split into the shared global layer
(paid once, every repo) + per-repo deltas, plus a ranked 'most expensive repos'
table. New mode 'refresh-tokens' (Step 6) documents the human-approved live sweep —
idempotent, skips unreadable repos, names any skipped so the bill's coverage stays
honest (Verifiseringsplikt).
Shapes documented carefully: rollUp.tokens.{sharedGlobal,perRepoDelta,machineWide}
are flat number maps; byRepo[] is the ranked {name,path,always,...} list. campaign-cli
already emitted rollUp.tokens (B2a) — no reader change; this is rendering only.
commands/campaign.md (73 lines) is the primary doc; README + CLAUDE.md campaign rows
get the matching one-line summary. Markdown-only → suite unchanged at 1198 green;
campaign command stays judgment-driven (not byte-stable).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The IO half of the machine-wide token roll-up (B2a shipped the pure data model).
New campaign-write-cli subcommand 'refresh-tokens': for every tracked repo it runs
readActiveConfig → buildManifest → splitManifestByOwnership, stores each repo's
per-repo delta via setRepoTokens, and captures the HOME-derived shared global layer
ONCE (from the first repo that reads cleanly) via setSharedGlobal.
Capturing shared once is the structural counted-once guard: a repo that fails to
read is recorded in 'skipped' and its delta omitted, never aborting the sweep.
Idempotent — a re-sweep replaces (setters overwrite), never accumulates. Determinism
unchanged: the clock is still read only via --reference-date.
TDD: 3 integration tests first (RED → GREEN) against a fixture with a dominant global
CLAUDE.md + two repos. Counted-once guard asserts each stored delta stays a small
fraction of the shared layer (a double-fold would make each delta EXCEED shared).
Caught a per-repo-MCP misclassification (fixed in 82f881a) + a test-side shape slip
(rollUp flattens its token buckets to numbers; stored summaries keep {tokens,count}).
Suite 1198 green (1195 + 3). Manifest + all frozen snapshots untouched.
[skip-docs]: internal sweep CLI (campaign plumbing), no user-facing surface yet. The
rendered token-bill in commands/campaign.md + CLAUDE.md rows land in B2b-3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
readClaudeJsonProjectSlice(repoPath) returns the slice keyed to the SPECIFIC
repo path (exact / longest-prefix match), so MCP servers under
~/.claude.json:projects are per-repo: they load only in their own project and
differ across repos. B2b-1 wrongly grouped them with the shared global layer
(reasoning from file location, not the keyed slice). The live sweep captures
the shared layer ONCE from the first repo — folding a per-repo slice into it
would silently drop every other repo's claude.json MCP servers.
Corrected: SHARED_GLOBAL_SOURCES = {user, managed}; the only machine-global
MCP is plugin-provided (plugin: prefix). Per-repo MCP (.mcp.json AND the
~/.claude.json project slice) → delta. Tests updated.
[skip-docs]: internal classifier correction, no user-facing surface yet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pure classifier splitManifestByOwnership(sources) → {shared, delta}, the
FS-free core that B2b's live cross-repo sweep will feed into the campaign
ledger's setSharedGlobal/setRepoTokens (B2a).
classifyOwnership maps each source string to its layer:
shared : user | managed | plugin:* | ~/.claude.json:projects (global MCP)
delta : project | local | .mcp.json | @import | unrecognized
Anything not positively global falls to delta, so a source is never silently
folded into the once-counted shared layer (a wrong fold HIDES machine-wide
cost; a wrong delta is at worst visibly attributed to a repo). Both layers
carry the canonical summarizeByLoadPattern shape so the ledger setters consume
them verbatim.
TDD: 6 unit tests first (RED → GREEN). buildManifest/CLI output unchanged →
manifest snapshot byte-identical. Suite 1195 green (1189 + 6).
[skip-docs]: internal pure export only, no user-facing surface yet. User-facing
docs (commands/campaign.md + CLAUDE.md manifest/campaign rows) land in B2b-3
when refresh-tokens + the rendered token-bill ship.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend the campaign ledger with a machine-wide always-loaded token bill, the
pure data-model + aggregation half of B2. Deliberately does NOT do live I/O:
the cross-repo readActiveConfig sweep that populates real numbers is B2b.
Design — shared layer counted ONCE, structurally:
- Ledger root gets an optional `sharedGlobal` summary (the always-loaded layer
paid in every repo: global CLAUDE.md + agent listing + global MCP + unscoped
global rules + active plugins' always components), stored ONCE via the new
setSharedGlobal().
- Each repo entry gets an optional `tokens` summary (its PER-REPO delta only),
set via the new setRepoTokens(); addRepo now seeds `tokens: null` (mirrors
findingsBySeverity).
- rollUp() stays PURE and gains a `tokens` aggregate: machineWide = sharedGlobal
+ Σ(per-repo deltas), so the shared layer is counted exactly once by
construction — the structural guard against the historic double-count bug.
Plus a `byRepo` table ranked DESC by always-loaded cost ("most expensive
repos") with a deterministic name tie-break.
Summary shape mirrors manifest's summarizeByLoadPattern exactly
({always|onDemand|external|unknown: {tokens,count}}) so B2b wires in trivially.
Keeps rollUp pure (no filesystem I/O) → byte-stable, respects the THIN campaign
motor invariant. Chosen over the plan's literal "run readActiveConfig inside
rollUp" which would break both (operator-approved deviation).
- 8 new ledger tests incl. the counted-once regression guard + byRepo ranking.
- Updated addRepo shape test (+tokens:null) and campaign-cli EMPTY_ROLLUP.
- No frozen snapshot affected (campaign is v5.7, absent from v5.0.0 baselines).
Suite green: 1189 tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Flag any single active agent whose description exceeds a 500-char soft cap
(the same bloat threshold TOK pattern F applies to SKILL.md descriptions).
Every char of an agent description re-enters context in the always-loaded
agent listing on every turn, so a long one is a per-turn cost.
Framing is deliberately ADVISORY, severity low: unlike CA-SKL-001 agents
have NO verified per-description cap, so nothing is dropped — this is a bloat
advisory, never a truncation claim. Per-agent advisories are emitted BEFORE
the aggregate roll-up (mirrors SKL 001->002).
- PER_AGENT_DESC_SOFT_CAP (=500) added to lib/agent-listing-budget.mjs as the
single source of truth, disclosed as a heuristic.
- Per-agent loop reuses measureActiveAgentListing()'s agents[] (name, source,
pluginName, path, descLength) — no new enumeration.
- 5 new tests (fire/strict-boundary/no-truncation-claim/recommendation/order).
Byte-stable: AGT already stripped from frozen v5.0.0 baselines; SC-5 unchanged
(HOME-scoped, hermetic HOME has no agents). Suite green: 1181 tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New AGT scanner flags the aggregate always-loaded cost of the agent listing
(every active agent's name+description is injected each turn so the model
knows what it can delegate to). Mirrors the SKL skill-listing pattern but
encodes the key honesty caveat: the agent-listing mechanism is INFERRED
(agents are absent from Claude Code's documented context breakdown), so the
token figure is an UPPER-BOUND estimate and the aggregate budget is a
config-audit heuristic anchored on 200k — not a CC-documented allotment.
- scanners/agent-listing-scanner.mjs + scanners/lib/agent-listing-budget.mjs
- 8 TDD tests (tests/scanners/agent-listing-scanner.test.mjs)
- AGT folds into the Token Efficiency health area (scoring.mjs)
- byte-stability: AGT added to strip-added-scanner (frozen v5.0.0 baselines
left untouched, same as OST/OPT); SC-5 default-output snapshot refreshed
- suite 1168 -> 1176 green
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump plugin.json 5.7.0 -> 5.8.0, README version badge + version-history row,
and CHANGELOG [5.8.0]. Covers the full Fase 2 campaign motor (durable ledger,
read-only campaign-cli, human-approved campaign-write-cli + /config-audit
campaign, cross-repo backlog, per-repo plan export with execution-by-reuse)
plus the pre-release cleanup (knowledge-refresh wiring, CLAUDE.md trim).
Scanner count 15, agents 7, commands 20 -> 21, tests 1091 -> 1168. All counts
verified green via self-audit --check-readme.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CLAUDE.md was 540 lines (CML >500 MEDIUM, config-grade B/89). Move the 19
per-scanner/per-block implementation notes (v5.6/v5.7 design rationale +
primary-source verification + byte-stability lessons) verbatim into
docs/scanner-internals.md under a new "Implementation notes" section, leaving a
read-on-demand pointer. Add a real "## Conventions" section pointing at
.claude/rules/ — this clears CA-CML-001 (missing recommended section), which the
moved detail headings had been masking via incidental rule/style keyword matches.
CLAUDE.md 540->134 lines / 8.5k chars; config-grade A/97; CML findings: none;
readmeCheck green; full suite 1168 green (hermetic).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
knowledge-refresh shipped as a command file in v5.7 Fase 1 Chunk 3 but was
never added to the /config-audit router (argument-hint + routing list) or the
help command table, so `/config-audit knowledge-refresh` did not route. Add it
in both. (optimize was already fully wired — the STATE note was stale.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes Block 4 (4b backlog + 4c export/execution). Asymmetric: plan
export is new testable code; execution is pure reuse of the existing
per-repo implement/rollback (no new execution machinery), per the plan's
"reuse existing backup/rollback".
Plan export ("planer følger arbeidsstedet"):
- scanners/lib/campaign-export.mjs (pure, now injected, 8 tests):
planExportPath(repo,sessionId) -> <repo>/docs/config-audit-plan-<sessionId>.md
(sessionId-keyed so same-day re-audits never collide);
buildPlanExportDocument({...,now}) -> provenance header + verbatim plan.
- scanners/campaign-export-cli.mjs (-cli, read-only by default, 10 tests):
--repo resolves the repo's linked session, reads its action-plan.md,
assembles the doc, emits {exportable,problems,targetPath,document}. Two
gates -> exit 1 advisory: no-session-linked / no-action-plan. Writes the
file ONLY under opt-in --write (byte-faithful copy; the LLM never re-types
a 200-line plan). --sessions-dir override for hermetic tests; exit 0/1/3.
Command: commands/campaign.md gains an `export <path>` mode (Step 6:
preview -> approve -> --write), then routes the user to the existing
/config-audit implement (backup + verify) + rollback + set-status
implemented. Nothing auto-written (Verifiseringsplikt).
Byte-stable: lib + -cli + command-doc only -> scanner count stays 15,
agents 7, commands 21 (export is a mode, not a new command), SC-5 +
backcompat suite untouched. suite 1150->1168. Block 4a (migrateLedger)
still deferred to the first breaking schema change.
Docs: CLAUDE.md section + badge 1150->1168/65->67 files; README badge +
campaign row + Testing prose (fixed stale 1055/59 -> true 1168/67).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the THIN machine-wide campaign surface (ledger + roll-up + status).
The write half of the campaign motor, mirroring how knowledge-refresh gates
register writes:
- scanners/campaign-write-cli.mjs (-cli → NOT a scanner; 11 tests): init/add/
set-status, each a thin wrapper over the invariant-enforcing lib transforms
(createLedger/addRepo/setRepoStatus) + saveLedger — path-normalization/dedup,
idempotent add, status-lifecycle guard and updatedDate bump never hand-rolled.
init refuses to clobber an existing/corrupt ledger (exit 1, file untouched);
add auto-inits + reports added vs skipped; set-status takes --findings/--session.
Deterministic: --reference-date is the only clock read, injected as `now`.
Exit 0=write, 1=advisory no-op, 3=error.
- commands/campaign.md (opus, no Web): thin orchestrator — always reports first
(read-only campaign-cli), then proposes init/add/set-status and invokes ONE
write-CLI subcommand only on explicit human approval (Verifiseringsplikt; never
hand-edits the JSON). add --discover finds git repos under a root to pick from.
Not byte-stable (own command, outside snapshot suite) like /config-audit optimize
+ knowledge-refresh. Both CLIs are -cli → scanner count stays 15, snapshot suite
untouched. commands 20→21, suite 1127→1138 (+11), test files 64→65.
Docs: CLAUDE.md §campaign-write-cli + command table + Testing badge; README
commands badge 20→21 + table row; help.md + router wiring.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the durable ledger that sits ABOVE individual config-audit sessions for a
machine-wide audit campaign: repo list + per-repo lifecycle (pending → audited →
planned → implemented) + a machine-wide roll-up by status and severity.
scanners/lib/campaign-ledger.mjs — same hybrid split as knowledge-refresh:
- PURE transforms (createLedger / addRepo / setRepoStatus / rollUp) with `now`
injected (YYYY-MM-DD, never the clock) → deterministic + unit-testable.
- soft validateLedger (returns {valid,errors}, never throws) for loaded data;
transforms throw on programmer error (invalid status, unknown path).
- thin IO shell (defaultLedgerPath / loadLedger→null-on-ENOENT / saveLedger).
- persists to ~/.claude/config-audit/campaign-ledger.json — OUTSIDE the plugin
dir (next to sessions/) so it survives uninstall/reinstall/upgrade.
- schemaVersion stamped from the start → cheap Block 4 migration.
THIN scope (Block 3a, operator-approved): ledger + roll-up + persistence only —
no CLI/command/execution (Blocks 3b/3c/4). Internal plumbing, byte-stable until
consumed: no `export async function scan` + lives in lib/ → scanner count stays
15, no orchestrator wiring, SC-5 unchanged.
tests/lib/campaign-ledger.test.mjs — 28 tests (TDD, red→green). Full hermetic
suite 1091→1119 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 'living' half of the v5.7 living knowledge base. Same hybrid split as the
optimization lens (Chunk 2b): a deterministic, byte-stable, unit-tested core +
a web/judgment command shell.
- scanners/lib/knowledge-refresh.mjs: pure assessFreshness(register,
{referenceDate, staleAfterDays=90}) — age-based fresh/stale classification of
source.verified; referenceDate injected (never reads the clock) → fully
deterministic. 15 tests.
- scanners/knowledge-refresh-cli.mjs: -cli (NOT an orchestrated scanner →
scanner count stays 15, suite byte-stable). Read-only — never writes the
register, never hits the network. --reference-date/--stale-after/--dry-run,
exit 0/1/3. 8 tests.
- commands/knowledge-refresh.md (opus): CLI stale-report → re-verify each stale
entry by re-reading its source.url → poll CC changelog + Anthropic blog →
apply ONLY human-approved writes, then re-validate the register. No unverified
claim is ever auto-written (Verifiseringsplikt). Web-driven → not byte-stable.
No new agent, no new orchestrated scanner. Docs/badges: commands 19→20,
tests 1068→1091 (20 lib + 32 scanner test files). self-audit A/A, readmeCheck passed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This repo's only remote is the PUBLIC open/config-audit mirror. The global
continuity rule (refined) says a repo with a public remote must keep STATE.md
LOCAL-ONLY (gitignored), because the only available push would leak internal
state-of-play. STATE.md was tracked here under the old "always tracked" rule,
which silently assumed a private remote.
Untrack STATE.md (kept on disk for continuity; the session-start hook reads it
from disk regardless of git status) and add it to .gitignore. Mirrors the
linkedin-studio precedent (commit 9338454). Operator-approved 2026-06-21.
Note: prior chore(state) commits remain in public history (accepted — scrub not
requested).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The recall+precision halves of the CA-OPT hybrid motor for the three
mechanism-fit cases the deterministic OPT scanner (2a) deliberately skips:
lifecycle→hook (BP-MECH-001), unscoped path-specific→rule (BP-MECH-002),
absolute "never"→permission (BP-MECH-004).
New:
- scanners/lib/lens-prefilter.mjs — cheap, recall-oriented line scan of the
CLAUDE.md body; detector names mirror the register lensCheck fields; skips
fenced code, gates the path class on an instruction verb. Pure + 13 tests.
- scanners/optimize-lens-cli.mjs — discovery + OPT scanner + pre-filter; attaches
only the CONFIRMED register entry to each candidate (unverifiable → dropped,
Verifiseringsplikt); emits {deterministic, candidates, register, counts}.
- agents/optimization-lens-agent.md — opus precision gate (7th agent, orange):
reads the real CLAUDE.md, drops low-confidence candidates, keeps only genuine
opportunities, cites register id + source.
- commands/optimize.md — /config-audit optimize orchestrates pre-filter→agent→report.
Agent-driven → deliberately NOT byte-stable (own command, outside the snapshot
suite). No new orchestrated scanner → scanner count stays 15. Counts: agents
6→7, commands 18→19, suite 1055→1068. Self-audit A/A unchanged, readmeCheck
passed (clean HOME). Plan: docs/v5.7-optimization-lens-plan.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
token-hotspots.test.mjs ran scan() under the developer's real HOME, so
readActiveConfig leaked ~/.claude (user CLAUDE.md cascade + ambient MCP/
plugins) into every fixture result. This made assertions machine-dependent:
green on a clean HOME, red on a populated one (small-cascade tipped past the
10k-token threshold; sonnet-era gained ambient MCP findings).
Wrap the HOME-dependent scan() call in the shared withHermeticHome helper —
same isolation the OST test and the snapshot/byte-stability suite already use.
Full suite (1055) now green on BOTH real and clean HOME.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Session boundary. Both next tasks pre-approved by operator (2026-06-21), to run in order in a fresh session: (1) TOK test HOME-isolation fix, (2) Chunk 2b opus analyzer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First detector of the 'is the config OPTIMAL?' axis (vs the existing 'correct?' scanners). New orchestrated scanner family CA-OPT (count 14->15), the deterministic half of the hybrid optimization lens.
CA-OPT-001 (low, Missed opportunity): a multi-step procedure in CLAUDE.md (>=6 consecutive numbered steps) that belongs in a skill. Reads recommendation + provenance from the best-practices register (BP-MECH-003). Conservative by design; the negative corpus proves null false-positives. Prose-judgment cases (lifecycle->hook, 'never'->permission) are deferred to the Chunk 2b opus analyzer.
Wiring mirrors OST: orchestrator entry, humanizer (OPT->'Missed opportunity' + family), scoring (OPT->'CLAUDE.md', existing area -> no new posture row -> byte-stable), strip-helper (OST,OPT), SC-5 regenerated under hermetic HOME (additive OPT entry only). 10 new tests; suite 1045->1055, self-audit A/A, readmeCheck passed (all verified with a clean HOME).
Note: the pre-existing TOK test reads the real ~/.claude (non-hermetic) -> run the suite with a clean HOME for deterministic results. Tracked as a separate follow-up.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add knowledge/best-practices.json: a machine-readable, provenance-stamped, schema-validated best-practices register — the source of truth for the upcoming v5.7 optimization lens (CA-OPT). 13 seed entries migrated from the v5.5 V-rows (loading-model + compaction facts) and the Anthropic 'Steering Claude Code' blog (mechanism-fit rules); each entry carries source.url + verified date + confidence. Only confirmed claims are user-facing (Verifiseringsplikt).
scanners/lib/best-practices-register.mjs: zero-dependency loader + validator (loadRegister/validateRegister/getEntry, native JSON.parse — not YAML, since the repo is zero-dep and yaml-parser.mjs can't parse arrays-of-objects). tests/lib/best-practices-register.test.mjs: 22 tests (schema, provenance integrity, negative cases, lookup).
Byte-stable: no scanner consumes the register yet (Chunk 2), so all scanner output is unchanged. Suite 1023->1045, self-audit A/A, readmeCheck passed. Full design: docs/v5.7-optimization-lens-plan.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Visjons-diskusjon gjennomført (F1-tuning: korrekt? → optimal?). Visjonen dekomponert i 4 byggeklosser, sekvensert i 2 faser. Fase 1 (optimerings-linse + levende kunnskapsbase) designet og operatør-låst; Fase 2 (maskinvid kampanje + varig backlog) utsatt til eget GO.
Låste beslutninger: strukturert register (linsen leser; markdown som speil); ny familie CA-OPT med hybrid motor (determ. pre-filter → opus-analyzer), presisjons-gated. GO-ready plan i docs/v5.7-optimization-lens-plan.md; STATE oppdatert. Ingen produksjonskode (bevisst diskusjons-sesjon).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bundles the v5.6 work already on main into a release:
- Foundation (62d910e): active-config-reader enumerates rules/agents/output
styles with a loadPattern/survivesCompaction/derivationConfidence triple;
frontmatter parser reads YAML block sequences.
- B (bb647ce, 778b517): load-pattern accounting — manifest reports
component-level sources with an always-loaded subtotal; token-hotspots tags
each hotspot with its load pattern.
- C (e3b044a): new orchestrated OST scanner (CA-OST-001/002/003), scanner
count 13 -> 14, all claims doc-verified.
Version synced: plugin.json 5.5.0->5.6.0, README version badge +
version-history row, CHANGELOG [5.6.0] section. Frozen v5.0.0 snapshots
preserved via strip-helpers (--json/--raw byte-stable for the original 13
scanners); only SC-5 default-output regenerated. Suite 1023 pass, self-audit
A/A (config 93, plugin 100), readmeCheck passed, mismatches [].
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New orchestrated scanner output-style-scanner.mjs — first new family since
SKL. Three findings, each pinned to a CONFIRMED V-row of the steering-model
plan + re-verified against code.claude.com/docs/en/output-styles:
- CA-OST-001 (medium, V10): user/project custom style missing
keep-coding-instructions:true (default false) → silently strips built-in
software-engineering instructions when active. Scoped to user/project.
- CA-OST-002 (low, V11): plugin style with force-for-plugin:true overrides the
user's selected outputStyle. Verifiseringsplikt correction — the plan bullet
said "project/user style," but force-for-plugin is plugin-styles-only per the
docs, so the check keys on source==='plugin'.
- CA-OST-003 (medium): settings outputStyle matching no built-in
(Default/Explanatory/Learning/Proactive, case-insensitive) nor discovered
custom style → dead config.
Byte-stability — a scanner addition, not a field addition. Growing the
scanners array + scanners_ok cannot be hidden by a field strip, but re-seeding
frozen v5.0.0 (the SKL precedent) would now bake in B2's hotspot triple +
claudeMd drift. So, per the B2 lesson, frozen v5.0.0 snapshots are PRESERVED
and the OST entry is stripped at compare time via new
tests/helpers/strip-added-scanner.mjs (wired into json/raw-backcompat + the
Step 5/6 humanizer tests); only SC-5 default-output is regenerated (additive
OST entry, diff reviewed). OST is fixture-gated (no output styles on
marketplace-medium / hermetic HOME → silent).
Wiring: orchestrator; humanizer (OST→Configuration mistake) + humanizer-data
OST family (title-coupled); scoring (OST→Settings, keeps 10 areas). Suite
1012→1023 (+11). Badges: scanners 14, tests 1023, TRANSLATIONS families 15.
Lore swept: README, CLAUDE.md, scanner-internals, humanizer.md. self-audit A/A.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Annotate every ranked TOK hotspot with the load-pattern triple
(loadPattern/survivesCompaction/derivationConfidence):
- hotspotLoadPattern() maps each discovery `type` → a deriveLoadPattern kind.
Rules reuse activeConfig.rules for precise `scoped` handling; claude-md maps
by scope. Two new deriveLoadPattern kinds back the rest: `command`
(on-demand — body loads on /invoke) and `harness-config` (external —
settings/keybindings/.mcp.json/hooks.json/plugin.json configure the CLI, not
the model context, so they cost no per-turn context tokens). Honest split:
the .mcp.json FILE is external; the MCP server's tool schemas are a separate
`always` hotspot.
Byte-stability — the opposite of B1's manifest. token-hotspots IS a byte-equal
SC-6/SC-7 CLI, and its hotspots ride inside scan-orchestrator + posture, so the
change touched SIX frozen-v5.0.0 comparisons across five test files. Resolved by
preserving the frozen baselines: a shared tests/helpers/strip-hotspot-load-pattern.mjs
strips the additive triple before each byte-equal compare (proves the original
schema is byte-identical). SC-5 default-output snapshots (scan-orchestrator +
token-hotspots) regenerated — diff reviewed as additive-only.
Tests 1008→1012. Self-audit A/A, scanner count unchanged at 13 (C bumps to 14).
Completes v5.6 B (B1 manifest + B2 token-hotspots).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Consume the v5.6 Foundation enumeration in buildManifest:
- Component-level sources: drop the coarse `kind:'plugin'` roll-up (it
double-counted skills/rules/agents already enumerated once). Kinds are now
claude-md/skill/rule/agent/output-style/mcp-server/hook.
- Every source carries loadPattern/survivesCompaction/derivationConfidence.
Rules/agents/output-styles propagate the foundation-derived values; CLAUDE.md
maps scope→kind (all cascade files always-loaded); skills are tagged on-demand
(skill-body) so the body cost does not inflate the always-loaded subtotal.
- New `summary` (always/onDemand/external/unknown {tokens,count}); the
always-loaded subtotal — "tokens that enter context every turn" — is the headline.
manifest is an environment-aware CLI → SC-6/SC-7 verify it by mode-equivalence,
not byte-equal, and it is not in SC-5. Adding fields in place keeps all snapshots
green with no regen (verified). `total` changes (de-duped) — intended correctness fix.
TOK's load-pattern column (byte-equal SC-6) is deferred to the next chunk (B2).
Tests 996→1008 (deterministic buildManifest unit test + CLI presence checks).
Self-audit A/A, scanner count unchanged at 13.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Foundation chunk of v5.6 "steering-model II" (internal plumbing for B/C;
no command-output change, so --json/--raw/SC-5/6/7 stay byte-stable, count
stays 13).
active-config-reader.mjs:
- deriveLoadPattern(kind,{scoped}) — pure helper mapping each source kind to
loadPattern {always,on-demand,external} + survivesCompaction {yes,no,n/a}
+ derivationConfidence {confirmed,inferred}, traced to the published
loading model (V-rows in docs/v5.5-steering-model-plan.md).
- enumerateRules / enumerateAgents / enumerateOutputStyles — the three
source kinds previously unenumerated (mirror enumerateSkills). Output-style
discovery is direct (not a new file-discovery type) to keep the discovery
surface stable.
- readActiveConfig now exposes rules/agents/outputStyles arrays + totals
counts/subtotals (folded into grandTotal).
yaml-parser.mjs:
- parseSimpleYaml now reads YAML block sequences (paths:\n - a), not just
inline paths:. An empty-valued key with no `- ` items stays null
(backcompat). Resolves a pre-existing RUL false-positive (a block-seq-scoped
rule was misread as unscoped) — fix flows through unchanged RUL code.
Tests +35 (961 -> 996): block-seq parser cases, RUL block-seq regression
(no-misflag + durability-fires), deriveLoadPattern table, three enumerators
(positive+negative). Amended two existing ACR asserts (top-level key shape +
grandTotal sum). self-audit A/A, readmeCheck passed, mismatches []. tests
badge 961+->996+; README testing prose de-staled (635/36 -> 996/56);
CLAUDE.md Foundation note.
B (manifest/tokens render + snapshot regen) and C (CA-OST, count->14)
deferred to their own sessions/GO.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ter3E2JSi1Khgmuf2kady8
Bundles the two v5.5.0 features already on main:
- A (f3aadb5): RUL/CML compaction-durability findings (both LOW) — a
large (>50-line) path-scoped rule and a nested CLAUDE.md are not
re-injected after compaction.
- E (f75ed56): PLH flags plugin-agent hooks/mcpServers/permissionMode
frontmatter Claude Code ignores for plugin subagents (permissionMode
MEDIUM false-security, rest LOW).
Additive — scanner count stays 13, --json/--raw byte-stable. Suite 961
pass. self-audit A/A (config 93, plugin 100), readmeCheck passed,
mismatches []. Version synced: plugin.json 5.4.1->5.5.0, README
version badge + version-history row, CHANGELOG [5.5.0] section.
Foundation (active-config-reader enumeration) + B deferred to v5.6.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ter3E2JSi1Khgmuf2kady8
A (RUL+CML durability) and E (PLH plugin-agent dead-config) implemented
and committed. Foundation moved to v5.6 (A/E are additive and don't
consume it). STATE + plan updated; v5.5.0 release-cut is a separate GO.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ter3E2JSi1Khgmuf2kady8
Per the official "what survives compaction" model (context-window.md):
only the project-root CLAUDE.md (+ unscoped rules) is re-injected after a
context compaction. Two additive, structural findings (low severity):
- RUL: a large (>50-line) PATH-SCOPED rule — reloads only on a matching
file read, and is not re-injected after compaction, so must-hold rules
can silently drop mid-session.
- CML: a NESTED (subdirectory) CLAUDE.md — not re-injected after
compaction (only the project root is).
Additive (no new scanner, count stays 13); one humanizer entry each;
hermetic temp-fixture tests (positive + negative). Suite 957 -> 961,
SC-5 byte-stable, self-audit A/A.
Known limitation (pre-existing, broader than A): the lightweight
frontmatter parser reads inline `paths:` but not YAML block sequences,
so block-sequence-scoped rules are still seen as unscoped. Deferred.
Part of v5.5.0 "steering-model I". Foundation (active-config-reader
enumeration) deferred to v5.6 with B.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ter3E2JSi1Khgmuf2kady8
Plugin subagents silently ignore `hooks`/`mcpServers`/`permissionMode`
frontmatter — these are honored only for user/project agents in
.claude/agents/ (code.claude.com/docs sub-agents). Setting them in a
plugin agent is dead config; `permissionMode` is MEDIUM because it
implies a restriction Claude Code does not apply (false security).
hooks/mcpServers are LOW.
Additive to PLH's agent-frontmatter loop (no new scanner, count stays
13). One humanizer pattern covers the three field titles. Hermetic
temp-fixture test (positive + negative). Suite 954 -> 957, byte-stable.
Part of v5.5.0 "steering-model I". Foundation (active-config-reader
enumeration) deferred to v5.6 with B — A/E are additive and don't
consume it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ter3E2JSi1Khgmuf2kady8
GO granted to implement v5.5.0 in a fresh session. STATE now opens
directly into the task with ordered, concrete first steps (Foundation
enumeration → A durability → E plugin-agent dead-config), the brief
pointer, and the impl-critical gotchas.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ter3E2JSi1Khgmuf2kady8
Verified 2026-06-20 against hooks.md + the 2.1.169 changelog: the
`post-session` hook in that changelog is a SELF-HOSTED-RUNNER
workspace-lifecycle hook (runs after the session, before the workspace
is deleted), NOT a settings.json hook event. It is absent from
hooks.md's 30-event list (all PascalCase), so config-audit was wrongly
treating a bogus `post-session` settings hook as valid — it now flags it.
Restructured the event test suite accordingly and added a negative test.
Resolved U1/U2 in the v5.5 plan (U1 refuted → removed; U2 `outputStyles`
plugin.json key confirmed → PLH unchanged). Suite stays 954, self-audit A/A.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ter3E2JSi1Khgmuf2kady8
Doc-grounded plan for the five new-functionality items greenlit after
checking the "seven steering mechanisms" framing against live CC docs
(2026-06-19): A durability/compaction findings, B load-pattern token
accounting, C output-style scanner, D mechanism-fit heuristic, E
agent-listing cost + plugin-agent dead-config.
Includes a full verification log (CONFIRMED/REFUTED/UNVERIFIED per claim
+ source), dependency graph, phased rollout (v5.5/v5.6/v5.7), testable
acceptance criteria, and open decisions. PLAN only — each feature awaits
its own GO. No code.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ter3E2JSi1Khgmuf2kady8
HKV: add Setup, UserPromptExpansion, PostToolBatch to VALID_EVENTS,
verified live against code.claude.com/docs/en/hooks.md (2026-06-19). A
valid hook using one of these was wrongly flagged "will never fire" — a
user could delete a working hook. Made the "(N total)" hint dynamic so
it can't drift again. Flagged the unverified kebab 'post-session' in a
comment (an existing test depends on it; follow-up check needed).
RUL: reword the globs finding. Only `paths:` is documented; whether CC
ever read `globs` is unverified, so the old "deprecated/legacy" framing
overclaimed (Verifiseringsplikt). New wording steers to the documented
`paths:` field. Updated the coupled fix-engine title match and the
humanizer entry (which also carried the "field was renamed" overclaim).
Suite 950 -> 954 (badge bumped). self-audit A/A, scanner count 13. No
version bump — these land in the pending v5.4.1 patch.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ter3E2JSi1Khgmuf2kady8