diff --git a/plugins/config-audit/scanners/lib/scoring.mjs b/plugins/config-audit/scanners/lib/scoring.mjs index 338a3cb..4b579ac 100644 --- a/plugins/config-audit/scanners/lib/scoring.mjs +++ b/plugins/config-audit/scanners/lib/scoring.mjs @@ -150,12 +150,21 @@ const SCANNER_AREA_MAP = { IMP: 'Imports', CNF: 'Conflicts', GAP: 'Feature Coverage', + TOK: 'Token Efficiency', }; +/** + * Slugify an area name into a stable id. + * Example: "Token Efficiency" → "token_efficiency", "CLAUDE.md" → "claude_md". + */ +function slugify(name) { + return String(name).toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, ''); +} + /** * Score per config area from scanner results. * @param {object[]} scannerResults - Array of scanner result objects from envelope.scanners - * @returns {{ areas: Array<{ name: string, grade: string, score: number, findingCount: number }>, overallGrade: string }} + * @returns {{ areas: Array<{ id: string, name: string, grade: string, score: number, findingCount: number }>, overallGrade: string }} */ export function scoreByArea(scannerResults) { const areas = []; @@ -178,7 +187,7 @@ export function scoreByArea(scannerResults) { } const grade = gradeFromPassRate(score); - areas.push({ name, grade, score, findingCount }); + areas.push({ id: slugify(name), name, grade, score, findingCount }); } // Overall grade: quality areas only (exclude GAP — feature coverage is informational, not a quality issue) @@ -262,7 +271,7 @@ export function generateScorecard(areaScores, utilization, maturity, segment, ac /** * Generate a v3 health-focused terminal scorecard. - * Shows only the 7 quality areas — no utilization, maturity, or segment. + * Shows only the quality areas (currently 8) — no utilization, maturity, or segment. * @param {{ areas: Array<{ name: string, grade: string, score: number }>, overallGrade: string }} areaScores * @param {number} opportunityCount - Number of GAP findings (shown as opportunity count) * @returns {string} diff --git a/plugins/config-audit/tests/lib/scoring.test.mjs b/plugins/config-audit/tests/lib/scoring.test.mjs index 8eb590d..df2127b 100644 --- a/plugins/config-audit/tests/lib/scoring.test.mjs +++ b/plugins/config-audit/tests/lib/scoring.test.mjs @@ -265,11 +265,11 @@ describe('determineSegment', () => { // scoreByArea // ======================================== describe('scoreByArea', () => { - it('returns areas for all 8 scanners', () => { - const scanners = ['CML', 'SET', 'HKV', 'RUL', 'MCP', 'IMP', 'CNF', 'GAP'] + it('returns areas for all 9 scanners', () => { + const scanners = ['CML', 'SET', 'HKV', 'RUL', 'MCP', 'IMP', 'CNF', 'GAP', 'TOK'] .map(s => makeScannerResult(s, 0)); const result = scoreByArea(scanners); - assert.equal(result.areas.length, 8); + assert.equal(result.areas.length, 9); }); it('zero findings → A grade', () => { diff --git a/plugins/config-audit/tests/scanners/posture.test.mjs b/plugins/config-audit/tests/scanners/posture.test.mjs index e3f3756..c809fc7 100644 --- a/plugins/config-audit/tests/scanners/posture.test.mjs +++ b/plugins/config-audit/tests/scanners/posture.test.mjs @@ -45,9 +45,10 @@ describe('posture.mjs CLI — healthy project', () => { assert.ok(result.segment.segment.length > 0); }); - it('returns 8 area scores', () => { - assert.equal(result.areas.length, 8); + it('returns 9 area scores', () => { + assert.equal(result.areas.length, 9); for (const area of result.areas) { + assert.ok('id' in area); assert.ok('name' in area); assert.ok('grade' in area); assert.ok('score' in area); @@ -55,6 +56,11 @@ describe('posture.mjs CLI — healthy project', () => { } }); + it('exposes a token_efficiency area id', () => { + const te = result.areas.find(a => a.id === 'token_efficiency'); + assert.ok(te, 'token_efficiency id present'); + }); + it('returns overallGrade', () => { assert.ok(['A', 'B', 'C', 'D', 'F'].includes(result.overallGrade)); });