feat(config-audit): wire TOK into posture scorecard as 8th quality area (Token Efficiency)

This commit is contained in:
Kjell Tore Guttormsen 2026-04-19 22:45:12 +02:00
commit 4b385bf456
3 changed files with 23 additions and 8 deletions

View file

@ -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}

View file

@ -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', () => {

View file

@ -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));
});