feat(cps): scan @imported files for volatile cached-prefix content (v5.10 B6) [skip-docs]

CPS originally inspected only files discovery classifies as claude-md, but a
CLAUDE.md can pull arbitrary files into the cached prefix via @import — and
those targets (e.g. @shared/conventions.md) are usually not claude-md in
discovery, so their inlined content was never scanned. Neither TOK Pattern A
(top-30 of cascade files) nor the in-file CPS scan reaches past the importing
file, so volatility inside an imported file was invisible.

B6 closes the gap: for each @import whose import site sits within the
cached-prefix window (imp.line <= CACHED_PREFIX_LINES), CPS resolves the path
(resolveImportPath, mirroring import-resolver/token-hotspots semantics), reads
the target, and runs findVolatileLines over its first 150 lines. A hit emits a
distinct medium finding — "Volatile content in @imported file breaks cached
prefix" — keyed on the resolved file, evidence naming the importer.

Scope boundaries (deliberate):
- One hop only; imports-of-imports stay with IMP (deep-chain owner).
- No lines-1-30 skip for imported content — that exclusion is root-file-specific
  to avoid Pattern A overlap, which never reaches imported files.
- No double-reporting: an import resolving to a discovered claude-md is skipped
  (own iteration); a reportedImports set dedupes a target imported by several
  CLAUDE.md files.

Dropped from B6 (per plan verdict): confident behavioral cache-buster detection
(opusplan/model-switch is runtime, not static config) and jq-transcript
automation. "No overstated behavioral finding ships" — even the permitted
opusplan info-advisory was left out; the @import extension is the whole of B6.

Byte-stability: the in-file finding keeps the same condition + byte-identical
evidence/description (continue-skip refactored to if-emit, behaviour-preserving);
new findings fire only on a volatile import, which no frozen v5.0.0 fixture has.
docs: README + scanner-internals CPS rows + full B6 note; CLAUDE.md kept lean
([skip-docs]). Suite 1254 -> 1257 green; snapshots + SC-5 untouched.
Version/badges/CHANGELOG wait for the v5.10 release cut.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-06-23 20:45:12 +02:00
commit fa1ddd963a
8 changed files with 183 additions and 26 deletions

View file

@ -19,7 +19,7 @@ Scanner CLI: `node scanners/scan-orchestrator.mjs <path> [--global] [--full-mach
| `conflict-detector.mjs` | CNF | Settings conflicts, permission contradictions, hook duplicates |
| `feature-gap-scanner.mjs` | GAP | 25 feature checks across 4 tiers — shown as opportunities, not grades |
| `token-hotspots.mjs` | TOK | Cache-breaking volatile content, redundant tool permissions, deep import chains, oversized cascade, bloated SKILL.md descriptions, MCP tool-schema budget, MCP tool-schema deferral (CA-TOK-006), stale plugin-cache disk-cleanup (prompt-cache patterns) |
| `cache-prefix-scanner.mjs` | CPS | Volatile content in lines 31150 of CLAUDE.md cascade (beyond Pattern A's top-30 window) |
| `cache-prefix-scanner.mjs` | CPS | Volatile content in lines 31150 of CLAUDE.md cascade (beyond Pattern A's top-30 window); plus volatile content inside `@import`-ed files (v5.10 B6, one hop) |
| `disabled-in-schema-scanner.mjs` | DIS | Dead/ineffective permission entries (low). (1) Tools in BOTH `permissions.deny` AND `permissions.allow` — deny wins; dominance is param-aware and treats the `Tool(*)` deny-all glob as equivalent to a bare deny (covers a bare allow). (2) Unanchored allow wildcards (`*`, `B*`, `mcp__*`) that Claude Code silently skips — CC accepts allow globs only after a literal glob-free `mcp__<server>__` prefix. Predicates shared with CNF live in `lib/permission-rules.mjs` |
| `collision-scanner.mjs` | COL | Cross-plugin skill name collisions (low); user-vs-plugin overlaps (medium); `details.namespaces` payload |
| `skill-listing-scanner.mjs` | SKL | (1) `CA-SKL-001` (medium): active skill descriptions over the verified 1,536-char listing cap (CC 2.1.105) → silently truncated in the model's skill listing. (2) `CA-SKL-002` (low): sum of active descriptions (each counted up to the cap) over the listing budget (~2% of context, CC 2.1.32), anchored on a conservative 200k window with a calibration note that the budget scales 5× on 1M-context models — leads with the measured sum, an estimate not telemetry. HOME-scoped (all user + plugin skills). Remediation surfaces `disableBundledSkills` / `skillOverrides` / trim. Distinct lens from TOK pattern F (project-local 500-char bloat heuristic) |
@ -219,6 +219,41 @@ returns ≥1 chatty hook — surfaces the documented **filter-before-Claude-read
grep ERROR and return only matches instead of a 10,000-line log). No chatty hook → silent (opportunity,
not noise — same contract as the cliOverMcp / bundledSkills levers).
### cache-prefix-scanner — @import extension (v5.10 B6)
CPS originally scanned only the files discovery classifies as `claude-md`. But a CLAUDE.md can pull
arbitrary files into context with `@import` directives, and those targets are usually *not* `claude-md`
in discovery (e.g. `@shared/conventions.md`) — so their content was inlined into the cached prefix yet
never inspected. Neither TOK Pattern A (top-30 of cascade files) nor the in-file CPS scan reaches past
the importing file, so volatility in an imported file was invisible.
B6 closes the gap: for each `@import` whose **import site** sits within the cached-prefix window
(`imp.line ≤ CACHED_PREFIX_LINES`), CPS resolves the path (`resolveImportPath`, mirroring
import-resolver/token-hotspots semantics), reads the target, and runs `findVolatileLines` over its first
150 lines. A hit emits a distinct medium finding — *"Volatile content in @imported file breaks cached
prefix"* — keyed on the resolved file (so the fix points at the right place), with evidence naming the
importer (`imported by <file> (@<path> at line N)`).
**Scope boundaries (deliberate):**
- **One hop only.** Imports-of-imports are not followed — IMP owns deep-chain analysis. The verified win
is the direct import; transitive resolution adds cycle/depth complexity for marginal coverage.
- **No lines-130 skip for imported content.** That exclusion exists only to avoid duplicating TOK
Pattern A's territory in the *root* file; Pattern A never reads imported files, so all of the imported
prefix counts.
- **No double-reporting.** An import resolving to a file that is itself a discovered `claude-md` is
skipped (it gets its own in-file iteration); a `reportedImports` set dedupes a target imported by
several CLAUDE.md files.
**Byte-stability.** The in-file finding is emitted under exactly the same condition and with byte-identical
evidence/description as before (the `continue`-skip was refactored to an `if`-emit — behaviour-preserving).
New findings fire only when a discovered CLAUDE.md imports a volatile file, which no frozen v5.0.0 fixture
does — snapshots and SC-5 verified untouched by the full suite.
**Dropped from B6 (per plan `verdict`).** Confident behavioral cache-buster detection (opusplan /
model-switch is a *runtime* behaviour, not static config a scanner can reliably flag) and jq-transcript
automation. "No overstated behavioral finding ships" — so even the permitted opusplan *info*-advisory was
left out; the verified @import extension is the whole of B6.
### CML scanner — context-window-scaled char budget
Beyond the line-count checks (200/500 lines, both MEDIUM), the CML scanner mirrors