feat(ms-ai-architect): Spor D steg 2 — score-skill CLI (--json/--gate/--skill) (TDD)
Byggekloss for både SessionStart-surfacing og lifecycle-gate (D3). - eval.mjs: ekstraher buildReport() (eksportert, testbar) ut av main() — samler hele korpus-rapporten (deterministisk + K10 + merget judge-cache). - lib/skill-score.mjs: formatScoreReport() (ren) — sortert forbedringsrapport med score, status, floored/provisional/ujudget-tags og ⚑gulv-markering. - score-skill.mjs (NY CLI): buildReport → scoreReport → render/json/gate. --skill scorer hele korpuset (K10 trenger alle søsken) men rapporterer/gater kun den valgte → inkrementell enkelt-skill-scoring for D3 lifecycle-gate. - docs/development.md: ny «Skill-kvalitetsscore (Spor D)»-seksjon. Live baseline: advisor 91, governance/security 96 (OK); engineering + infrastructure 89 (floored av K10-gulv) UNDER mål. Gate exit=1. Tester: kb-eval 134→142 (+8). validate 239, kb-update 316 uendret grønt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e87289d929
commit
c1a09062d4
5 changed files with 249 additions and 7 deletions
72
scripts/kb-eval/score-skill.mjs
Normal file
72
scripts/kb-eval/score-skill.mjs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#!/usr/bin/env node
|
||||
// score-skill.mjs — Skill-quality score CLI (Spor D, step 2).
|
||||
//
|
||||
// Runs the eval pipeline (buildReport: deterministic K2/K3/K5/K6/refCount/N1-N5
|
||||
// + cross-skill K10 + merged operator-gated judge cache), scores every skill via
|
||||
// the PURE lib (scoreSkill/scoreReport), and reports a 0-100 quality score with
|
||||
// a sorted improvement list. The corpus invariant is: every skill stays >=90 %.
|
||||
//
|
||||
// Usage:
|
||||
// node scripts/kb-eval/score-skill.mjs # human summary
|
||||
// node scripts/kb-eval/score-skill.mjs --json # machine output (scoreReport)
|
||||
// node scripts/kb-eval/score-skill.mjs --gate 90 # non-zero exit if any skill < 90
|
||||
// node scripts/kb-eval/score-skill.mjs --skill <name> # scope output to one skill
|
||||
// # (still scores the full corpus
|
||||
// # so K10 sibling-overlap is correct)
|
||||
//
|
||||
// K10 requires every sibling description, so even single-skill (incremental)
|
||||
// scoring evaluates the whole corpus and then filters the reported set — this is
|
||||
// the building block for the lifecycle-gate in apply-skill-op.mjs.
|
||||
//
|
||||
// Zero dependencies. Pure scoring lives in lib/skill-score.mjs.
|
||||
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { buildReport } from './eval.mjs';
|
||||
import { scoreReport, formatScoreReport, TARGET } from './lib/skill-score.mjs';
|
||||
|
||||
function argValue(args, flag) {
|
||||
const i = args.indexOf(flag);
|
||||
return i !== -1 && i + 1 < args.length ? args[i + 1] : null;
|
||||
}
|
||||
|
||||
function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const jsonOut = args.includes('--json');
|
||||
const gateRaw = argValue(args, '--gate');
|
||||
const gate = gateRaw !== null ? Number(gateRaw) : null;
|
||||
const skillFilter = argValue(args, '--skill');
|
||||
|
||||
const target = Number.isFinite(gate) ? gate : TARGET;
|
||||
const full = scoreReport(buildReport(), { target });
|
||||
|
||||
// Scope the reported/gated set to one skill if requested (scoring already ran
|
||||
// over the whole corpus, so K10 is computed correctly).
|
||||
let scored = full.scored;
|
||||
if (skillFilter) {
|
||||
scored = scored.filter((s) => s.name === skillFilter);
|
||||
if (scored.length === 0) {
|
||||
process.stderr.write(`score-skill: ukjent skill '${skillFilter}'. Tilgjengelig: ${full.scored.map((s) => s.name).join(', ')}\n`);
|
||||
process.exitCode = 2;
|
||||
return;
|
||||
}
|
||||
}
|
||||
const below = scored.filter((s) => !s.meetsTarget);
|
||||
const result = { target, scored, below };
|
||||
|
||||
if (jsonOut) {
|
||||
process.stdout.write(JSON.stringify(result) + '\n');
|
||||
} else {
|
||||
process.stdout.write(formatScoreReport(result) + '\n');
|
||||
if (gate !== null) {
|
||||
process.stdout.write(below.length === 0
|
||||
? `✓ Gate ≥${target}: alle ${scored.length} skills består.\n`
|
||||
: `✗ Gate ≥${target}: ${below.length} skill(s) under mål — ${below.map((s) => s.name).join(', ')}.\n`);
|
||||
}
|
||||
}
|
||||
|
||||
if (gate !== null && below.length > 0) process.exitCode = 1;
|
||||
}
|
||||
|
||||
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
||||
main();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue