feat(config-audit): cache-prefix stability scanner CPS (v5 N3) [skip-docs]
New CPS scanner walks CLAUDE.md cascade and flags volatile content
between lines 31 and 150 — the cache-prefix window beyond TOK Pattern
A's top-30 territory. Volatile content anywhere in the cached prefix
forces a fresh cache write from that line down on every turn.
Volatile-pattern set extends TOK Pattern A with:
- shell-exec lines (! prefix) — common in CLAUDE.md to inject git/date
- ${VAR} substitutions — vary per-shell, defeat cache reuse
Severity: medium per finding. Skips lines 1-30 to avoid duplicating
Pattern A's range; CPS' value is in the 31-150 zone.
Wired into scan-orchestrator + scoring SCANNER_AREA_MAP. CPS shares
the "Token Efficiency" area with TOK; scoreByArea now deduplicates by
area name and combines counts across scanners contributing to the
same area, so the 9-area scorecard contract holds.
Fixtures volatile-mid-section/{volatile-line-60, volatile-line-200}
verify both positive (line 60) and out-of-window (line 200) cases.
[skip-docs] reason: v5 plan fences off README/CLAUDE.md badge updates
to Session 5; Forgejo pre-commit-docs-gate hook requires this tag.
Tests: 604 → 611 (+7).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
0420b8cc4a
commit
65087e624f
6 changed files with 517 additions and 9 deletions
218
plugins/config-audit/tests/fixtures/volatile-mid-section/volatile-line-200/CLAUDE.md
vendored
Normal file
218
plugins/config-audit/tests/fixtures/volatile-mid-section/volatile-line-200/CLAUDE.md
vendored
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
# Project
|
||||
|
||||
Stable preamble.
|
||||
Body line 4.
|
||||
Body line 5.
|
||||
Body line 6.
|
||||
Body line 7.
|
||||
Body line 8.
|
||||
Body line 9.
|
||||
Body line 10.
|
||||
Body line 11.
|
||||
Body line 12.
|
||||
Body line 13.
|
||||
Body line 14.
|
||||
Body line 15.
|
||||
Body line 16.
|
||||
Body line 17.
|
||||
Body line 18.
|
||||
Body line 19.
|
||||
Body line 20.
|
||||
Body line 21.
|
||||
Body line 22.
|
||||
Body line 23.
|
||||
Body line 24.
|
||||
Body line 25.
|
||||
Body line 26.
|
||||
Body line 27.
|
||||
Body line 28.
|
||||
Body line 29.
|
||||
Body line 30.
|
||||
Body line 31.
|
||||
Body line 32.
|
||||
Body line 33.
|
||||
Body line 34.
|
||||
Body line 35.
|
||||
Body line 36.
|
||||
Body line 37.
|
||||
Body line 38.
|
||||
Body line 39.
|
||||
Body line 40.
|
||||
Body line 41.
|
||||
Body line 42.
|
||||
Body line 43.
|
||||
Body line 44.
|
||||
Body line 45.
|
||||
Body line 46.
|
||||
Body line 47.
|
||||
Body line 48.
|
||||
Body line 49.
|
||||
Body line 50.
|
||||
Body line 51.
|
||||
Body line 52.
|
||||
Body line 53.
|
||||
Body line 54.
|
||||
Body line 55.
|
||||
Body line 56.
|
||||
Body line 57.
|
||||
Body line 58.
|
||||
Body line 59.
|
||||
Body line 60.
|
||||
Body line 61.
|
||||
Body line 62.
|
||||
Body line 63.
|
||||
Body line 64.
|
||||
Body line 65.
|
||||
Body line 66.
|
||||
Body line 67.
|
||||
Body line 68.
|
||||
Body line 69.
|
||||
Body line 70.
|
||||
Body line 71.
|
||||
Body line 72.
|
||||
Body line 73.
|
||||
Body line 74.
|
||||
Body line 75.
|
||||
Body line 76.
|
||||
Body line 77.
|
||||
Body line 78.
|
||||
Body line 79.
|
||||
Body line 80.
|
||||
Body line 81.
|
||||
Body line 82.
|
||||
Body line 83.
|
||||
Body line 84.
|
||||
Body line 85.
|
||||
Body line 86.
|
||||
Body line 87.
|
||||
Body line 88.
|
||||
Body line 89.
|
||||
Body line 90.
|
||||
Body line 91.
|
||||
Body line 92.
|
||||
Body line 93.
|
||||
Body line 94.
|
||||
Body line 95.
|
||||
Body line 96.
|
||||
Body line 97.
|
||||
Body line 98.
|
||||
Body line 99.
|
||||
Body line 100.
|
||||
Body line 101.
|
||||
Body line 102.
|
||||
Body line 103.
|
||||
Body line 104.
|
||||
Body line 105.
|
||||
Body line 106.
|
||||
Body line 107.
|
||||
Body line 108.
|
||||
Body line 109.
|
||||
Body line 110.
|
||||
Body line 111.
|
||||
Body line 112.
|
||||
Body line 113.
|
||||
Body line 114.
|
||||
Body line 115.
|
||||
Body line 116.
|
||||
Body line 117.
|
||||
Body line 118.
|
||||
Body line 119.
|
||||
Body line 120.
|
||||
Body line 121.
|
||||
Body line 122.
|
||||
Body line 123.
|
||||
Body line 124.
|
||||
Body line 125.
|
||||
Body line 126.
|
||||
Body line 127.
|
||||
Body line 128.
|
||||
Body line 129.
|
||||
Body line 130.
|
||||
Body line 131.
|
||||
Body line 132.
|
||||
Body line 133.
|
||||
Body line 134.
|
||||
Body line 135.
|
||||
Body line 136.
|
||||
Body line 137.
|
||||
Body line 138.
|
||||
Body line 139.
|
||||
Body line 140.
|
||||
Body line 141.
|
||||
Body line 142.
|
||||
Body line 143.
|
||||
Body line 144.
|
||||
Body line 145.
|
||||
Body line 146.
|
||||
Body line 147.
|
||||
Body line 148.
|
||||
Body line 149.
|
||||
Body line 150.
|
||||
Body line 151.
|
||||
Body line 152.
|
||||
Body line 153.
|
||||
Body line 154.
|
||||
Body line 155.
|
||||
Body line 156.
|
||||
Body line 157.
|
||||
Body line 158.
|
||||
Body line 159.
|
||||
Body line 160.
|
||||
Body line 161.
|
||||
Body line 162.
|
||||
Body line 163.
|
||||
Body line 164.
|
||||
Body line 165.
|
||||
Body line 166.
|
||||
Body line 167.
|
||||
Body line 168.
|
||||
Body line 169.
|
||||
Body line 170.
|
||||
Body line 171.
|
||||
Body line 172.
|
||||
Body line 173.
|
||||
Body line 174.
|
||||
Body line 175.
|
||||
Body line 176.
|
||||
Body line 177.
|
||||
Body line 178.
|
||||
Body line 179.
|
||||
Body line 180.
|
||||
Body line 181.
|
||||
Body line 182.
|
||||
Body line 183.
|
||||
Body line 184.
|
||||
Body line 185.
|
||||
Body line 186.
|
||||
Body line 187.
|
||||
Body line 188.
|
||||
Body line 189.
|
||||
Body line 190.
|
||||
Body line 191.
|
||||
Body line 192.
|
||||
Body line 193.
|
||||
Body line 194.
|
||||
Body line 195.
|
||||
Body line 196.
|
||||
Body line 197.
|
||||
Body line 198.
|
||||
[2026-04-15] Inline date in body — not above cache.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
79
plugins/config-audit/tests/fixtures/volatile-mid-section/volatile-line-60/CLAUDE.md
vendored
Normal file
79
plugins/config-audit/tests/fixtures/volatile-mid-section/volatile-line-60/CLAUDE.md
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# Project
|
||||
|
||||
Stable preamble.
|
||||
Body line 4.
|
||||
Body line 5.
|
||||
Body line 6.
|
||||
Body line 7.
|
||||
Body line 8.
|
||||
Body line 9.
|
||||
Body line 10.
|
||||
Body line 11.
|
||||
Body line 12.
|
||||
Body line 13.
|
||||
Body line 14.
|
||||
Body line 15.
|
||||
Body line 16.
|
||||
Body line 17.
|
||||
Body line 18.
|
||||
Body line 19.
|
||||
Body line 20.
|
||||
Body line 21.
|
||||
Body line 22.
|
||||
Body line 23.
|
||||
Body line 24.
|
||||
Body line 25.
|
||||
Body line 26.
|
||||
Body line 27.
|
||||
Body line 28.
|
||||
Body line 29.
|
||||
Body line 30.
|
||||
Body line 31.
|
||||
Body line 32.
|
||||
Body line 33.
|
||||
Body line 34.
|
||||
Body line 35.
|
||||
Body line 36.
|
||||
Body line 37.
|
||||
Body line 38.
|
||||
Body line 39.
|
||||
Body line 40.
|
||||
Body line 41.
|
||||
Body line 42.
|
||||
Body line 43.
|
||||
Body line 44.
|
||||
Body line 45.
|
||||
Body line 46.
|
||||
Body line 47.
|
||||
Body line 48.
|
||||
Body line 49.
|
||||
Body line 50.
|
||||
Body line 51.
|
||||
Body line 52.
|
||||
Body line 53.
|
||||
Body line 54.
|
||||
Body line 55.
|
||||
Body line 56.
|
||||
Body line 57.
|
||||
Body line 58.
|
||||
Body line 59.
|
||||
!git log -5 # volatile shell-exec at line 60
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
More body.
|
||||
79
plugins/config-audit/tests/scanners/cache-prefix.test.mjs
Normal file
79
plugins/config-audit/tests/scanners/cache-prefix.test.mjs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { resetCounter } from '../../scanners/lib/output.mjs';
|
||||
import { scan } from '../../scanners/cache-prefix-scanner.mjs';
|
||||
import { discoverConfigFiles } from '../../scanners/lib/file-discovery.mjs';
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
const FIXTURES = resolve(__dirname, '../fixtures');
|
||||
|
||||
async function runScanner(fixtureName) {
|
||||
resetCounter();
|
||||
const path = resolve(FIXTURES, fixtureName);
|
||||
const discovery = await discoverConfigFiles(path);
|
||||
return scan(path, discovery);
|
||||
}
|
||||
|
||||
describe('CPS scanner — basic structure', () => {
|
||||
it('reports scanner prefix CPS', async () => {
|
||||
const result = await runScanner('volatile-mid-section/volatile-line-60');
|
||||
assert.equal(result.scanner, 'CPS');
|
||||
});
|
||||
|
||||
it('finding IDs match CA-CPS-NNN pattern', async () => {
|
||||
const result = await runScanner('volatile-mid-section/volatile-line-60');
|
||||
for (const f of result.findings) {
|
||||
assert.match(f.id, /^CA-CPS-\d{3}$/);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('CPS scanner — volatile content within cached prefix', () => {
|
||||
it('flags !git log at line 60 (medium severity)', async () => {
|
||||
const result = await runScanner('volatile-mid-section/volatile-line-60');
|
||||
const f = result.findings.find(x => /volatile content inside cached prefix/i.test(x.title || ''));
|
||||
assert.ok(f, `expected volatile-prefix finding; got: ${result.findings.map(x => x.title).join(' | ')}`);
|
||||
assert.equal(f.severity, 'medium', `expected medium, got ${f.severity}`);
|
||||
assert.match(String(f.evidence || ''), /line 60/);
|
||||
assert.match(String(f.evidence || ''), /shell-exec/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CPS scanner — volatile content beyond cache window', () => {
|
||||
it('does NOT flag volatility at line 200+ (outside 150-line window)', async () => {
|
||||
const result = await runScanner('volatile-mid-section/volatile-line-200');
|
||||
const f = result.findings.find(x => /volatile content inside cached prefix/i.test(x.title || ''));
|
||||
assert.equal(f, undefined,
|
||||
`expected no finding for line-200 fixture; got: ${f?.title}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CPS scanner — does not duplicate TOK Pattern A territory', () => {
|
||||
it('volatility at lines 1–30 is left for TOK Pattern A (no CPS finding)', async () => {
|
||||
// The opus-47/cache-breaking fixture has volatile content at the very top.
|
||||
// CPS skips lines 1–30 to avoid duplicating Pattern A's territory.
|
||||
const result = await runScanner('opus-47/cache-breaking');
|
||||
const f = result.findings.find(x => /volatile content inside cached prefix/i.test(x.title || ''));
|
||||
assert.equal(f, undefined,
|
||||
`expected no CPS finding when volatility is only in lines 1–30 (Pattern A's range)`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CPS scanner — orchestrator wiring', () => {
|
||||
it('CPS appears in scan-orchestrator scanner list', async () => {
|
||||
const orch = await import('../../scanners/scan-orchestrator.mjs');
|
||||
const path = resolve(FIXTURES, 'volatile-mid-section/volatile-line-60');
|
||||
const env = await orch.runAllScanners(path, { filterFixtures: false });
|
||||
const cps = env.scanners.find(r => r.scanner === 'CPS');
|
||||
assert.ok(cps, `expected CPS in orchestrator results; got: ${env.scanners.map(r => r.scanner).join(', ')}`);
|
||||
});
|
||||
|
||||
it('CPS findings carry the token-efficiency category', async () => {
|
||||
const result = await runScanner('volatile-mid-section/volatile-line-60');
|
||||
const f = result.findings.find(x => /volatile content inside cached prefix/i.test(x.title || ''));
|
||||
assert.ok(f);
|
||||
assert.equal(f.category, 'token-efficiency');
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue