feat(config-audit): TOK flags CLAUDE.md cascade > 10k tokens (v5 M4) [skip-docs]
- New Pattern E in TOK: emits medium finding when activeConfig.claudeMd.estimatedTokens > 10_000 - Uses cascade tokens, file count, and calibration note as evidence - New fixtures: large-cascade (37k bytes / 14475 cascade tokens) + small-cascade (5k baseline) 572 → 574 tests, all green.
This commit is contained in:
parent
9330124f5c
commit
25ca6139b4
4 changed files with 1073 additions and 0 deletions
|
|
@ -44,6 +44,10 @@ const VOLATILE_PATTERNS = [
|
||||||
|
|
||||||
const MAX_IMPORT_DEPTH = 2;
|
const MAX_IMPORT_DEPTH = 2;
|
||||||
|
|
||||||
|
// v5 M4: cascades above this contribute >10k tokens to every turn even before
|
||||||
|
// any tool description loads. Heuristic for "context budget under pressure".
|
||||||
|
const CASCADE_TOKEN_THRESHOLD = 10_000;
|
||||||
|
|
||||||
const HOTSPOTS_MAX = 10;
|
const HOTSPOTS_MAX = 10;
|
||||||
|
|
||||||
// v5 F7: shared evidence note appended to every TOK pattern finding.
|
// v5 F7: shared evidence note appended to every TOK pattern finding.
|
||||||
|
|
@ -330,6 +334,29 @@ export async function scan(targetPath, discovery) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Pattern E: CLAUDE.md cascade > CASCADE_TOKEN_THRESHOLD (v5 M4) ──
|
||||||
|
if (activeConfig?.claudeMd?.estimatedTokens > CASCADE_TOKEN_THRESHOLD) {
|
||||||
|
const cascadeTokens = activeConfig.claudeMd.estimatedTokens;
|
||||||
|
const fileCount = activeConfig.claudeMd.files?.length ?? 0;
|
||||||
|
findings.push(finding({
|
||||||
|
scanner: SCANNER,
|
||||||
|
severity: SEVERITY.medium,
|
||||||
|
title: 'CLAUDE.md cascade exceeds 10k tokens per turn',
|
||||||
|
description:
|
||||||
|
`The active CLAUDE.md cascade for this repo (${fileCount} files: managed + user + ` +
|
||||||
|
`ancestors + project + @imports) totals ~${cascadeTokens} tokens. Every turn loads this ` +
|
||||||
|
'whole prefix; budget pressure compounds with tool schemas and MCP servers.',
|
||||||
|
file: activeConfig.claudeMd.files?.find(f => f.scope === 'project')?.path || null,
|
||||||
|
evidence:
|
||||||
|
`cascade_tokens=${cascadeTokens}; threshold=${CASCADE_TOKEN_THRESHOLD}; ` +
|
||||||
|
`files=${fileCount} — ${CALIBRATION_NOTE}`,
|
||||||
|
recommendation:
|
||||||
|
'Trim the user/project CLAUDE.md, push reference material into @imports that load ' +
|
||||||
|
'on-demand, or move long sections to skills. Aim for <10k tokens in the cascade.',
|
||||||
|
category: 'token-efficiency',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
// ── Hotspots ranking ──
|
// ── Hotspots ranking ──
|
||||||
const hotspots = await buildHotspots(discovery, targetPath, activeConfig);
|
const hotspots = await buildHotspots(discovery, targetPath, activeConfig);
|
||||||
|
|
||||||
|
|
|
||||||
1024
plugins/config-audit/tests/fixtures/large-cascade/CLAUDE.md
vendored
Normal file
1024
plugins/config-audit/tests/fixtures/large-cascade/CLAUDE.md
vendored
Normal file
File diff suppressed because it is too large
Load diff
5
plugins/config-audit/tests/fixtures/small-cascade/CLAUDE.md
vendored
Normal file
5
plugins/config-audit/tests/fixtures/small-cascade/CLAUDE.md
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Small Cascade Fixture
|
||||||
|
|
||||||
|
Minimal CLAUDE.md so the cascade stays below the 10k token threshold even
|
||||||
|
when added to the ambient user/project cascade picked up by readActiveConfig.
|
||||||
|
|
||||||
|
|
@ -189,6 +189,23 @@ describe('TOK scanner — hotspots contract', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('TOK scanner — M4 cascade > 10k tokens (v5)', () => {
|
||||||
|
it('flags CLAUDE.md cascade > 10k tokens with medium severity', async () => {
|
||||||
|
const result = await runScanner('large-cascade');
|
||||||
|
const f = result.findings.find(x => /cascade/i.test(x.title || ''));
|
||||||
|
assert.ok(f, `expected cascade finding; got: ${result.findings.map(x => x.title).join(' | ')}`);
|
||||||
|
assert.equal(f.severity, 'medium', `expected medium, got ${f.severity}`);
|
||||||
|
assert.match(f.title, /CLAUDE\.md cascade/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does NOT flag small cascade (< 10k tokens)', async () => {
|
||||||
|
const result = await runScanner('small-cascade');
|
||||||
|
const f = result.findings.find(x => /cascade/i.test(x.title || ''));
|
||||||
|
assert.equal(f, undefined,
|
||||||
|
`expected no cascade finding for small fixture; got: ${f?.title}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('TOK scanner — F7 severity recalibration (v5)', () => {
|
describe('TOK scanner — F7 severity recalibration (v5)', () => {
|
||||||
// Findings identified by title pattern, not finding ID — TOK IDs are
|
// Findings identified by title pattern, not finding ID — TOK IDs are
|
||||||
// sequential per scan run, not semantic per pattern (output.mjs:31).
|
// sequential per scan run, not semantic per pattern (output.mjs:31).
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue