import { describe, it, beforeEach } 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/token-hotspots.mjs'; import { discoverConfigFiles } from '../../scanners/lib/file-discovery.mjs'; const __dirname = fileURLToPath(new URL('.', import.meta.url)); const FIXTURES = resolve(__dirname, '../fixtures'); async function fixtureDiscovery(name) { return discoverConfigFiles(resolve(FIXTURES, name)); } async function runScanner(fixtureName) { resetCounter(); const path = resolve(FIXTURES, fixtureName); const discovery = await fixtureDiscovery(fixtureName); return scan(path, discovery); } describe('TOK scanner — healthy-project', () => { let result; beforeEach(async () => { result = await runScanner('healthy-project'); }); it('returns status ok', () => { assert.equal(result.status, 'ok'); }); it('reports scanner prefix TOK', () => { assert.equal(result.scanner, 'TOK'); }); it('finding IDs match CA-TOK-NNN pattern', () => { for (const f of result.findings) { assert.match(f.id, /^CA-TOK-\d{3}$/); } }); it('exposes total_estimated_tokens as a number', () => { assert.equal(typeof result.total_estimated_tokens, 'number'); assert.ok(result.total_estimated_tokens >= 0); }); }); describe('TOK scanner — opus-47/cache-breaking', () => { let result; beforeEach(async () => { result = await runScanner('opus-47/cache-breaking'); }); it('flags CA-TOK-001 (cache-breaking volatile top)', () => { const f = result.findings.find(x => x.id === 'CA-TOK-001'); assert.ok(f, 'expected a CA-TOK-001 finding for cache-breaking fixture'); }); it('CA-TOK-001 severity is high (v5 F7 recalibration)', () => { const f = result.findings.find(x => x.id === 'CA-TOK-001'); assert.equal(f.severity, 'high', `expected high after F7, got ${f.severity}`); }); }); describe('TOK scanner — opus-47/redundant-tools', () => { let result; beforeEach(async () => { result = await runScanner('opus-47/redundant-tools'); }); it('emits at least one CA-TOK-002 finding (redundant tool/permission)', () => { const has002 = result.findings.some(f => /^CA-TOK-002$/.test(f.id) || f.title?.toLowerCase().includes('redundant')); assert.ok(has002, 'expected a CA-TOK-002 finding for redundant-tools fixture'); }); }); describe('TOK scanner — opus-47/deep-imports', () => { let result; beforeEach(async () => { result = await runScanner('opus-47/deep-imports'); }); it('emits at least one CA-TOK-003 finding (deep @import chain)', () => { const has003 = result.findings.some(f => /^CA-TOK-003$/.test(f.id) || f.title?.toLowerCase().includes('import')); assert.ok(has003, 'expected a CA-TOK-003 finding for deep-imports fixture'); }); }); describe('TOK scanner — opus-47/sonnet-era (v5 F5: Pattern D removed)', () => { let result; beforeEach(async () => { result = await runScanner('opus-47/sonnet-era'); }); it('emits zero findings (no Pattern D / CA-TOK-004 anymore)', () => { assert.equal(result.findings.length, 0, `expected 0 findings on sonnet-era after F5, got: ${result.findings.map(f => f.id).join(', ')}`); }); it('never emits CA-TOK-004 (removed in v5)', () => { assert.ok(result.findings.every(f => f.id !== 'CA-TOK-004'), 'expected no CA-TOK-004; removed in v5 F5'); }); }); describe('TOK scanner — marketplace scale ordering', () => { it('total_estimated_tokens strictly increases across small → medium → large', async () => { const small = await runScanner('marketplace-small'); const medium = await runScanner('marketplace-medium'); const large = await runScanner('marketplace-large'); assert.ok(small.total_estimated_tokens < medium.total_estimated_tokens, `expected small (${small.total_estimated_tokens}) < medium (${medium.total_estimated_tokens})`); assert.ok(medium.total_estimated_tokens < large.total_estimated_tokens, `expected medium (${medium.total_estimated_tokens}) < large (${large.total_estimated_tokens})`); }); }); describe('TOK scanner — readActiveConfig integration (v5 F1)', () => { let result; beforeEach(async () => { result = await runScanner('tok-active-config'); }); it('exposes activeConfig summary on the result (proves readActiveConfig was called)', () => { assert.ok(result.activeConfig, 'expected result.activeConfig to be set'); assert.equal(typeof result.activeConfig.claudeMdEstimatedTokens, 'number'); assert.ok(result.activeConfig.claudeMdEstimatedTokens > 0, `expected claudeMd cascade > 0 tokens, got ${result.activeConfig.claudeMdEstimatedTokens}`); }); it('hotspots include at least one MCP-source entry', () => { const hasMcp = result.hotspots.some(h => /mcp/i.test(h.source)); assert.ok(hasMcp, `expected hotspots to include an MCP source; got: ${result.hotspots.map(h => h.source).join(', ')}`); }); it('total_estimated_tokens exceeds the minimal sonnet-era baseline', async () => { // sonnet-era has no .mcp.json — the activeConfig MCP entries from this // fixture should push its total above sonnet-era's even when both fixtures // share the user's ambient cascade/plugin state. const baseline = await runScanner('opus-47/sonnet-era'); assert.ok(result.total_estimated_tokens > baseline.total_estimated_tokens, `expected ${result.total_estimated_tokens} > ${baseline.total_estimated_tokens}`); }); }); describe('TOK scanner — hotspots contract', () => { let result; beforeEach(async () => { result = await runScanner('marketplace-large'); }); it('every finding has a non-empty recommendation', () => { for (const f of result.findings) { assert.ok(f.recommendation, `finding ${f.id} missing recommendation`); assert.ok(String(f.recommendation).length > 0, `finding ${f.id} has empty recommendation`); } }); it('exposes a hotspots array of length 3–10', () => { assert.ok(Array.isArray(result.hotspots), 'expected result.hotspots to be an array'); assert.ok(result.hotspots.length >= 3, `expected ≥3 hotspots, got ${result.hotspots.length}`); assert.ok(result.hotspots.length <= 10, `expected ≤10 hotspots, got ${result.hotspots.length}`); }); it('every hotspot exposes source/estimated_tokens/rank/recommendations', () => { for (const h of result.hotspots) { assert.ok(typeof h.source === 'string' && h.source.length > 0, 'hotspot.source missing'); assert.equal(typeof h.estimated_tokens, 'number', 'hotspot.estimated_tokens not a number'); assert.equal(typeof h.rank, 'number', 'hotspot.rank not a number'); assert.ok(Array.isArray(h.recommendations), 'hotspot.recommendations not an array'); assert.ok(h.recommendations.length >= 1 && h.recommendations.length <= 3, `hotspot.recommendations length should be 1–3, got ${h.recommendations.length}`); } }); it('every hotspot.source is unique (v5 F4: no padding)', () => { const sources = result.hotspots.map(h => h.source); const unique = new Set(sources); assert.equal(unique.size, sources.length, `expected unique sources; got duplicates in: ${sources.join(', ')}`); }); it('hotspots.length never exceeds HOTSPOTS_MAX (10)', () => { assert.ok(result.hotspots.length <= 10, `expected ≤10 hotspots, got ${result.hotspots.length}`); }); }); describe('TOK scanner — M2 skill description > 500 chars (v5)', () => { it('flags skill with bloated description (low severity)', async () => { const result = await runScanner('skill-bloated'); const f = result.findings.find(x => /skill description/i.test(x.title || '')); assert.ok(f, `expected skill-description finding; got: ${result.findings.map(x => x.title).join(' | ')}`); assert.equal(f.severity, 'low', `expected low, got ${f.severity}`); assert.match(f.evidence || '', /bloated/); }); it('does NOT flag tight description (under 500 chars)', async () => { const result = await runScanner('skill-tight'); const f = result.findings.find(x => /skill description/i.test(x.title || '')); assert.equal(f, undefined, `expected no skill-description finding; got: ${f?.title}`); }); }); 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 — N1 MCP tool-schema budget (v5 CA-TOK-005)', () => { // readActiveConfig pulls in ambient ~/.claude.json plugin MCP servers; tests // filter to the fixture's own server name (budget-srv-) to avoid // user-state leakage. Findings identified by title (not exact ID) — TOK IDs // are sequential per scan. const findFixtureBudget = (result, count) => result.findings.find(f => /MCP tool-schema budget/i.test(f.title || '') && (f.title || '').includes(`budget-srv-${count}`) ); it('14 tools → no budget finding (under 20-tool floor)', async () => { const result = await runScanner('mcp-budget/14-tools'); const f = findFixtureBudget(result, 14); assert.equal(f, undefined, `expected no budget finding for budget-srv-14 under 20 tools; got: ${f?.title}`); }); it('25 tools → low severity', async () => { const result = await runScanner('mcp-budget/25-tools'); const f = findFixtureBudget(result, 25); assert.ok(f, `expected budget finding for budget-srv-25; got: ${result.findings.map(x => x.title).join(' | ')}`); assert.equal(f.severity, 'low', `expected low for 25 tools, got ${f.severity}`); }); it('60 tools → medium severity', async () => { const result = await runScanner('mcp-budget/60-tools'); const f = findFixtureBudget(result, 60); assert.ok(f, `expected budget finding for budget-srv-60`); assert.equal(f.severity, 'medium', `expected medium for 60 tools, got ${f.severity}`); }); it('120 tools → high severity', async () => { const result = await runScanner('mcp-budget/120-tools'); const f = findFixtureBudget(result, 120); assert.ok(f, `expected budget finding for budget-srv-120`); assert.equal(f.severity, 'high', `expected high for 120 tools, got ${f.severity}`); }); it('unknown toolCount → low severity with "unknown" in evidence', async () => { const result = await runScanner('mcp-budget/unknown-tools'); const f = findFixtureBudget(result, 'unknown'); assert.ok(f, `expected budget finding for budget-srv-unknown`); assert.equal(f.severity, 'low', `expected low for unknown toolCount, got ${f.severity}`); assert.match(String(f.evidence || ''), /unknown/i, `expected "unknown" in evidence, got: ${f.evidence}`); }); it('finding ID matches CA-TOK-NNN format', async () => { const result = await runScanner('mcp-budget/120-tools'); const f = findFixtureBudget(result, 120); assert.ok(f); assert.match(f.id, /^CA-TOK-\d{3}$/); }); it('finding evidence carries calibration_note', async () => { const result = await runScanner('mcp-budget/60-tools'); const f = findFixtureBudget(result, 60); assert.ok(f); assert.match(String(f.evidence || ''), /severity reflects estimated tokens\/turn/i); }); }); describe('TOK scanner — F7 severity recalibration (v5)', () => { // Findings identified by title pattern, not finding ID — TOK IDs are // sequential per scan run, not semantic per pattern (output.mjs:31). const SEVERITY_TABLE = [ { fixture: 'opus-47/cache-breaking', pattern: 'A', titleMatch: /cache-breaking volatile/i, expected: 'high' }, { fixture: 'opus-47/redundant-tools', pattern: 'B', titleMatch: /redundant permission/i, expected: 'medium' }, { fixture: 'opus-47/deep-imports', pattern: 'C', titleMatch: /deep @import chain/i, expected: 'low' }, ]; for (const { fixture, pattern, titleMatch, expected } of SEVERITY_TABLE) { it(`Pattern ${pattern} (${fixture}) has severity ${expected}`, async () => { const result = await runScanner(fixture); const f = result.findings.find(x => titleMatch.test(x.title || '')); assert.ok(f, `expected a finding matching ${titleMatch} in ${fixture}; got: ${result.findings.map(x => x.title).join(' | ')}`); assert.equal(f.severity, expected, `expected ${expected}, got ${f.severity}`); }); it(`Pattern ${pattern} (${fixture}) carries calibration_note evidence`, async () => { const result = await runScanner(fixture); const f = result.findings.find(x => titleMatch.test(x.title || '')); assert.ok(f, `expected a finding matching ${titleMatch} in ${fixture}`); const evidence = String(f.evidence || ''); assert.ok(/severity reflects estimated tokens\/turn/i.test(evidence), `expected calibration_note phrase in evidence, got: ${evidence}`); }); } });