refactor(ms-ai-architect): R14 — persona ut av plugin-flaten, og de seks stedene som produserte den

Nøytral arkitekt-ramme (ratifisert 15.09) i SKILL.md-ene, 23 kommandoer, 70 redaksjonelle
forekomster i ref-korpuset, README/CLAUDE.md/NOTICE og A11Y-rapportens proveniens-linje.
Hver av de 70 avgjort i kontekst: dialog fikk ny taler, proveniens ny opphavspåstand.

Ordren navnga fem produsent-steder; den live sjette manglet. `PROMPT_TEMPLATE` settes i
generate-skills.sh:27 og leses ALDRI — prompten er en heredoc i samme script. Å rette bare
prompt-template.md ville latt generatoren re-minte personaen ved neste KB-kjøring.

Ny G4-klausul i check-cosmo-gate.mjs: persona i leveranseflaten = 0, derivasjonsregisteret
pinnet per fil PÅ TALL (et filnavn-unntak er blindt for hva fila senere inneholder).
Registeret utvidet med README.md 3 — versjonstabellens rader er historikk, ikke leveranse.
Den ene genitiv-formede produktlinja adjudiseres på cosmos-db-URL-en i samme rad; regelen
er målt lukket (8 tvetydige linjer totalt, 33 URL-linjer, 2 persona-klassifiserte, begge
produkt) og hvitvasker ikke bar `Cosmo`. classifyCosmo urørt — baselinene hviler på den.
R3 godtar nå R14-utført (0) men ingenting imellom: et halvveis sveip felles fortsatt.

Bevis: gate PASSED + NETTET VALIDERT BEGGE VEIER · 1137/1137 · validate-plugin 250/0/0 ·
begge --dry-drivere 0 · Layer B 45/45 OK (kjent-positiv feller, exit 1).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-15 23:15:27 +02:00
commit ced0c5f46d
80 changed files with 400 additions and 152 deletions

View file

@ -28,7 +28,9 @@ import { fileURLToPath } from 'node:url';
import { globSync } from 'node:fs';
import {
classifyCosmo, findDeadAnchors, classifyPersonaSites, neutralizeLabels,
personaInDeliveryContent, DERIVATION_REGISTER,
} from './lib/cosmo-persona.mjs';
import { execFileSync } from 'node:child_process';
const __dirname = dirname(fileURLToPath(import.meta.url));
const PLUGIN_ROOT = join(__dirname, '..', '..');
@ -128,6 +130,7 @@ function check() {
}
const labelLines = checkLabels(fails);
const deliveryLines = checkDelivery(fails);
console.log('=== R13 persona-gate (ratifisert 2026-09-12, alt. A) ===');
console.log(`ref-filer : ${m.files.length} (baseline ${b.refFiles})`);
@ -141,6 +144,8 @@ function check() {
console.log(` kun-produkt-filer urørt : ${Object.keys(b.productOnlyFileCounts).length}`);
console.log('--- R13b etikett/celle-gate (ratifisert 2026-09-15, alt. A) ---');
for (const l of labelLines) console.log(l);
console.log('--- R14 leveranseflate-gate (ratifisert 2026-09-15) ---');
for (const l of deliveryLines) console.log(l);
if (fails.length) {
console.log('\nGATE FAILED');
for (const f of fails) console.log(` x ${f}`);
@ -190,6 +195,8 @@ function validateNet() {
console.log('--- R13b klausulene, begge veier ---');
bad += validateLabelsNet();
console.log('--- R14 G4, begge veier ---');
bad += validateDeliveryNet();
console.log(bad === 0 ? '\nNETTET VALIDERT BEGGE VEIER' : `\nNETT-VALIDERING FEILET (${bad})`);
process.exit(bad === 0 ? 0 : 1);
@ -296,13 +303,20 @@ function checkLabels(fails) {
}
// --- R3 HVA SOM SKAL STAA ---
if (t.edLabel !== b.editorialLabels || t.edCell !== b.editorialCells) {
fails.push(`R3: REDAKSJONELT scope roert — etiketter ${t.edLabel} `
+ `(baseline ${b.editorialLabels}), celler ${t.edCell} (baseline ${b.editorialCells})`);
// R3 vernet R14s scope mens R13b kjoerte: de 70 redaksjonelle skulle staa UROERT. R14 er
// det scopet, saa naar R14 er utfoert er 0 den rette verdien -- ikke et brudd. Klausulen
// godtar derfor to tilstander og INGENTING imellom: enten holdt (16+10, R14 ikke kjoert)
// eller tomt (0+0, R14 utfoert). Et halvveis sveip er nettopp det R3 skal felle.
const edHeld = t.edLabel === b.editorialLabels && t.edCell === b.editorialCells;
const edDone = t.edLabel === 0 && t.edCell === 0;
if (!edHeld && !edDone) {
fails.push(`R3: REDAKSJONELT scope halvveis roert — etiketter ${t.edLabel} `
+ `(holdt ${b.editorialLabels}, utfoert 0), celler ${t.edCell} `
+ `(holdt ${b.editorialCells}, utfoert 0)`);
}
if (t.prose !== b.personaProseAfter && t.prose !== b.personaProseBefore) {
fails.push(`R3: persona i proesa/tabell ${t.prose} er hverken foer-verdien `
+ `${b.personaProseBefore} eller etter-verdien ${b.personaProseAfter}`);
if (![b.personaProseBefore, b.personaProseAfter, 0].includes(t.prose)) {
fails.push(`R3: persona i proesa/tabell ${t.prose} er ingen av de tre lovlige `
+ `tilstandene (${b.personaProseBefore} foer R13b, ${b.personaProseAfter} etter, 0 etter R14)`);
}
const done = mech === 0;
@ -370,6 +384,102 @@ function validateLabelsNet() {
return bad;
}
// ================================ R14: G4 leveranseflaten ==================================
//
// G4 persona i leveranseflaten = 0, med derivasjonsregisteret pinnet PER FIL PAA TALL.
//
// Skopet er `git ls-files '*.md'` (tracked), ikke arbeidstreet: den udekomponerte roadmap-
// gaten talte med 1146 gitignorerte `.kb-backup/`-filer og 9 LOCAL-ONLY-filer som aldri
// leveres. Ref-korpuset trekkes fra -- G1/G3/R1/R3 eier det allerede.
/** Tracked markdown, som repo-relative stier. */
function trackedMarkdown() {
return execFileSync('git', ['ls-files', '*.md'], { cwd: PLUGIN_ROOT, encoding: 'utf8' })
.trim().split('\n').filter(Boolean);
}
const inCorpus = (f) => /^skills\/[^/]+\/references\//.test(f);
/** Leveranseflatens persona + registerets faktiske tall. */
function measureDelivery() {
let surface = 0;
const offenders = [];
const register = {};
for (const rel of trackedMarkdown()) {
if (inCorpus(rel)) continue;
const n = personaInDeliveryContent(readFileSync(join(PLUGIN_ROOT, rel), 'utf8'));
if (rel in DERIVATION_REGISTER) { register[rel] = n; continue; }
if (n > 0) { surface += n; offenders.push(`${rel} (${n})`); }
}
return { surface, offenders, register };
}
function checkDelivery(fails) {
const d = measureDelivery();
if (d.surface !== 0) {
fails.push(`G4: ${d.surface} persona-forekomst(er) i leveranseflaten: `
+ `${d.offenders.slice(0, 10).join(', ')}`);
}
// Registeret pinnes paa TALL. Et filnavn-unntak er blindt for hva fila senere kommer til
// aa inneholde; et tall-pin feller i det en ny persona-linje legges til.
for (const [rel, want] of Object.entries(DERIVATION_REGISTER)) {
const got = d.register[rel];
if (got === undefined) { fails.push(`G4: registerfila ${rel} finnes ikke lenger`); continue; }
if (got !== want) fails.push(`G4: ${rel} har ${got} persona, pinnet ${want}`);
}
const regTotal = Object.values(DERIVATION_REGISTER).reduce((a, b) => a + b, 0);
return [
`G4 persona i leveranseflaten : ${d.surface}`,
` derivasjonsregister (tall-pinnet): ${regTotal} i `
+ `${Object.keys(DERIVATION_REGISTER).length} filer`,
];
}
/** Force G4 to the other answer. */
function validateDeliveryNet() {
let bad = 0;
const ok = (m) => console.log(`ok ${m}`);
const no = (m) => { console.log(`x ${m}`); bad++; };
// Kjent-positiv: hver form persona faktisk tok i leveranseflaten.
for (const [label, text] of [
['prosa', 'Du er Cosmo Skyberg i en anskaffelsesrolle.\n'],
['heading', '# Cosmo Skyberg - Microsoft AI Solution Architect\n'],
['norsk genitiv', 'Legg til Cosmos helhetsvurdering:\n'],
['produsent-prompt', '8. For arkitekten (Cosmo) — 5-8 noekkelspoersmaal\n'],
]) {
if (personaInDeliveryContent(text) !== 1) no(`G4 KJENT-POSITIV: ${label} ble IKKE sett`);
else ok(`G4 KJENT-POSITIV: ${label} felte G4`);
}
// Kjent-negativ: produktet, inkludert den adjudiserte genitiv-formede raden.
for (const [label, text] of [
['Azure Cosmos DB', '| Azure Cosmos DB | 8000 NOK |\n'],
['CosmosClient', 'Bruk `CosmosClient` mot cosmos_ru-kvoten.\n'],
['adjudisert URL-rad',
'| Cosmos multi-region writes | learn.microsoft.com/azure/cosmos-db/distribute-data-globally |\n'],
]) {
if (personaInDeliveryContent(text) !== 0) no(`G4 KJENT-NEGATIV: ${label} feilklassifisert som persona`);
else ok(`G4 KJENT-NEGATIV: ${label} passerer urort`);
}
// URL-regelen skal ikke bli en hvitvaskingskanal: bare genitiv-formen unnskyldes.
if (personaInDeliveryContent('Cosmo anbefaler learn.microsoft.com/azure/cosmos-db/ som kilde.\n') !== 1) {
no('G4 KJENT-POSITIV: bar `Cosmo` ble hvitvasket av en cosmos-db-URL paa samme linje');
} else ok('G4 KJENT-POSITIV: bar `Cosmo` hvitvaskes IKKE av en cosmos-db-URL');
// Registeret er tall-pinnet, ikke filnavn-pinnet: en ny persona-linje i en unntatt fil
// maa flytte tallet. Maalt paa fila selv, ikke paastaatt.
const reg = Object.keys(DERIVATION_REGISTER)[0];
const src = readFileSync(join(PLUGIN_ROOT, reg), 'utf8');
const before = personaInDeliveryContent(src);
const after = personaInDeliveryContent(`${src}\nDu er Cosmo Skyberg.\n`);
if (after !== before + 1) no(`G4 KJENT-POSITIV: injisert persona i unntatt fil ${reg} flyttet ikke tallet`);
else ok(`G4 KJENT-POSITIV: injisert persona i ${reg} flyttet tallet ${before} -> ${after} (tall-pin, ikke filnavn-hopp)`);
return bad;
}
const arg = process.argv[2];
if (arg === '--emit-baseline') emitBaseline();
else if (arg === '--emit-labels-baseline') emitLabelsBaseline();