import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { join } from 'node:path'; import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { assessSkillListingBudget, envFlag, isBundledSkillsDisabled, DESCRIPTION_CAP, AGGREGATE_BUDGET_TOKENS, CONTEXT_WINDOW_ANCHOR, LARGE_CONTEXT_WINDOW, LARGE_CONTEXT_BUDGET_TOKENS, withCommas, BUDGET_CALIBRATION_NOTE, } from '../../scanners/lib/skill-listing-budget.mjs'; // This lib is the single source of truth for the skill-listing budget that both // the SKL scanner (diagnoses overflow) and the GAP scanner (recommends the // disableBundledSkills lever) consume. The constants and aggregate math here // must match what SKL shipped in v5.2.0 — these tests pin that contract. describe('skill-listing-budget — constants', () => { it('DESCRIPTION_CAP is the verified 1536-char listing cap (CC 2.1.105)', () => { assert.equal(DESCRIPTION_CAP, 1536); }); it('AGGREGATE_BUDGET_TOKENS is 2% of the conservative 200k anchor', () => { assert.equal(CONTEXT_WINDOW_ANCHOR, 200_000); assert.equal(AGGREGATE_BUDGET_TOKENS, 4000); }); it('LARGE_CONTEXT_BUDGET_TOKENS is 2% of the 1M window', () => { assert.equal(LARGE_CONTEXT_WINDOW, 1_000_000); assert.equal(LARGE_CONTEXT_BUDGET_TOKENS, 20000); }); }); describe('withCommas', () => { it('inserts thousands separators', () => { assert.equal(withCommas(1_000_000), '1,000,000'); assert.equal(withCommas(4000), '4,000'); assert.equal(withCommas(999), '999'); assert.equal(withCommas(0), '0'); }); }); describe('BUDGET_CALIBRATION_NOTE', () => { it('discloses the 1M-context scaling and flags this as an estimate, not telemetry', () => { assert.match(BUDGET_CALIBRATION_NOTE, /1,000,000/); assert.match(BUDGET_CALIBRATION_NOTE, /20,000/); assert.match(BUDGET_CALIBRATION_NOTE, /estimate/i); }); }); describe('assessSkillListingBudget — aggregate math', () => { it('sums each description length capped at the 1536 cap', () => { // 1000 + 1000 + min(5000,1536) = 3536 const r = assessSkillListingBudget([1000, 1000, 5000]); assert.equal(r.aggregateChars, 3536); }); it('scanned counts every entry, including empty descriptions', () => { const r = assessSkillListingBudget([0, 0, 100]); assert.equal(r.scanned, 3); assert.equal(r.aggregateChars, 100); }); it('aggregateTokens mirrors estimateTokens markdown (ceil chars/4)', () => { const r = assessSkillListingBudget([16]); // 16 chars -> 4 tokens assert.equal(r.aggregateTokens, 4); }); it('is NOT over budget exactly at the budget (16×1000 chars -> 4000 tok)', () => { const r = assessSkillListingBudget(Array(16).fill(1000)); assert.equal(r.aggregateChars, 16000); assert.equal(r.aggregateTokens, 4000); assert.equal(r.overBudget, false); assert.equal(r.overBy, 0); }); it('is over budget one skill past the budget (17×1000 chars -> 4250 tok)', () => { const r = assessSkillListingBudget(Array(17).fill(1000)); assert.equal(r.aggregateTokens, 4250); assert.equal(r.overBudget, true); assert.equal(r.overBy, 250); }); it('exposes budgetTokens and handles the empty case', () => { const r = assessSkillListingBudget([]); assert.equal(r.budgetTokens, AGGREGATE_BUDGET_TOKENS); assert.equal(r.scanned, 0); assert.equal(r.aggregateChars, 0); assert.equal(r.aggregateTokens, 0); assert.equal(r.overBudget, false); assert.equal(r.overBy, 0); }); it('treats negative or non-finite lengths as zero contribution', () => { const r = assessSkillListingBudget([-5, NaN, 100]); assert.equal(r.aggregateChars, 100); assert.equal(r.scanned, 3); }); }); describe('envFlag', () => { it('treats 1/true/yes/on as set', () => { for (const v of ['1', 'true', 'TRUE', 'yes', 'on', ' 1 ']) { assert.equal(envFlag(v), true, `expected ${JSON.stringify(v)} → true`); } }); it('treats null/empty/0/false/no/off as un-set', () => { for (const v of [undefined, null, '', '0', 'false', 'no', 'off', ' ']) { assert.equal(envFlag(v), false, `expected ${JSON.stringify(v)} → false`); } }); }); describe('isBundledSkillsDisabled — lever cascade', () => { async function withHome(fn) { const home = await mkdtemp(join(tmpdir(), 'config-audit-lever-home-')); const originalHome = process.env.HOME; const originalEnv = process.env.CLAUDE_CODE_DISABLE_BUNDLED_SKILLS; process.env.HOME = home; delete process.env.CLAUDE_CODE_DISABLE_BUNDLED_SKILLS; try { return await fn(home); } finally { process.env.HOME = originalHome; if (originalEnv === undefined) delete process.env.CLAUDE_CODE_DISABLE_BUNDLED_SKILLS; else process.env.CLAUDE_CODE_DISABLE_BUNDLED_SKILLS = originalEnv; await rm(home, { recursive: true, force: true }); } } async function writeSettings(dir, obj) { await mkdir(dir, { recursive: true }); await writeFile(join(dir, 'settings.json'), JSON.stringify(obj)); } it('is false on a clean HOME with no env and no settings', async () => { await withHome(async () => { assert.equal(await isBundledSkillsDisabled(), false); }); }); it('is true when the env var is set', async () => { await withHome(async () => { process.env.CLAUDE_CODE_DISABLE_BUNDLED_SKILLS = '1'; assert.equal(await isBundledSkillsDisabled(), true); }); }); it('is true when user ~/.claude/settings.json sets it (the location discovery misses)', async () => { await withHome(async (home) => { await writeSettings(join(home, '.claude'), { disableBundledSkills: true }); assert.equal(await isBundledSkillsDisabled(), true); }); }); it('is true when project .claude/settings.json sets it', async () => { await withHome(async () => { const project = await mkdtemp(join(tmpdir(), 'config-audit-lever-proj-')); try { await writeSettings(join(project, '.claude'), { disableBundledSkills: true }); assert.equal(await isBundledSkillsDisabled(project), true); } finally { await rm(project, { recursive: true, force: true }); } }); }); it('is false when the setting is present but not strictly true', async () => { await withHome(async (home) => { await writeSettings(join(home, '.claude'), { disableBundledSkills: false }); assert.equal(await isBundledSkillsDisabled(), false); }); }); });