refactor(ms-ai-architect): R13b — nøytraliser de mekaniske persona-forekomstene utenfor headinger, etter at ordrens egen korrigerte bøttegrense ble målt usann
Ordren ba meg behandle sitt eget premiss som et premiss: «PM-ens 87/45 er korrigert
til 85/47 ved maaling ... Behandle ogsaa 85/47 som et premiss og re-maal det foer du
bygger.» Re-målt over de 389 ref-filene holder 85/47 heller ikke — og feilen ligger i
KLASSIFIKATOREN, ikke i tellingen. Summen 132 står; fordelingen er 62/70.
TRE FUNN, alle pinnet i test:
1. FJORTEN bold-etiketter er DIALOG-ATTRIBUSJONER, ikke etiketter. Dialog-bøtta
matchet bare kursivformen `*Cosmo:*`. Fetformen står foran sitert tale —
`**Naar kunden sier:** "..."` / `**Cosmo svarer:** "..."` — så å slette den
etterlater replikken uten taler. Redaksjonelt, ikke mekanisk.
2. TI tabellceller er PROVENIENSPÅSTANDER i kildekvalitets-kolonnen. Celleposisjon
deler de 26 tabellforekomstene i tre klasser, ikke én: 12 radetiketter (kolonne 0),
4 kolonneoverskrifter, 10 proveniensverdier (`Moenstre er Cosmo-design`,
`Raadgivende innhold basert paa Cosmo-persona`). Å skrive om en av dem er å avgi en
ny påstand om hvor innholdet kommer fra, og den siste har ikke noe slettemål i det
hele tatt. Seks av ordrens ni sammensetninger ligger helt inne i denne klassen —
ordrens egen advarsel traff, målingen lokaliserte den.
3. Lekkasje ANDRE veien, +1 mekanisk: `- **For arkitekten (Cosmo):** ...` i
reasoning-models-o1-o3-optimization.md:549 er nøyaktig målklassen, men et
linjestart-forankret nett ser den ikke bak `- ` og bokførte den som prosa.
Operatøren ratifiserte 2026-09-15 alt. A: kjør de 62, hold dialog og proveniens for
R14. Begge tilbakeholdte klasser henger på #R14-persona-ramme som aldri er besvart;
de 62 gjør ikke det, fordi R13 ALT har kjørt `For Cosmo` -> `For arkitekten` over 401
headinger — dette gjør bare etiketter og tabellrader konsistente med en beslutning
som allerede er utført.
UTFØRT: 62 forekomster i 50 filer (ordren sa 47) — 46 etiketter i 16 ratifiserte
varianter, 16 celler i 4. Diffen er 62 fjernet = 62 lagt til, ren in-place-erstatning;
hver endret linje klassifisert, ANNET = 0. Ingen måltekst innfører et ord kilden ikke
hadde, utover R13s sanksjonerte `arkitekten` (samme invariant-test, gjenbrukt).
GATEN, DEKOMPONERT I TRE KLAUSULER, hver validert BEGGE veier før den ble konsumert:
R1 REFERENT mekaniske sites 62 -> 0, produkt 451 uendret.
Kjent-pos: `**For Cosmo:**` OG `| Cosmos raad |` (genitiven er den
et bold-only nett mister) feller begge. Kjent-neg:
`**Cosmos DB-anbefaling:**` — ser ut som persona, er produkt —
og `| Azure Cosmos DB |` passerer urørt.
R2 REKKEVIDDE 0 umålte varianter, 0 utenfor rekkevidde. Kjent-pos: en ukjent
etikettform rapporteres som umålt OG transformen KASTER, den
hopper ikke stille over. Kjent-neg: dialog + proveniens bokføres
utenfor scope og er byte-identiske.
R3 HVA SOM STÅR 16 redaksjonelle etiketter + 10 celler uendret, heading/TOC
fortsatt 0 (R13 ikke regradert), prosa 132 -> 70. Kjent-pos: et
fjernet tegn i `Cosmos DB` feller produkt-differansen. Kjent-neg:
alle kun-produkt-filer byte-identiske gjennom transformen.
Gaten ble kjørt FØR transformen og felte R1 (exit 1) — et nullresultat som aldri er
tvunget til det andre svaret er ingen måling. R13b har egen baseline-fil;
cosmo-gate-baseline.json er et referansepunkt-artefakt (personaHeadings 401) og er
IKKE re-emittert. Ny .gitignore-negasjon (6 entries), aldri `git add -f`.
Målt underveis: 0 av de 62 målinjene ligger i en kodeblokk, så fence-agnostisk
transform er trygg her — samme konklusjon R13 nådde for headinger. Nøyaktig 1 linje
bærer begge klasser; dens `Cosmo-persona` står igjen, som den skal.
Suite 1134/1134 (1120 + 14 nye), validate-plugin 250/0/0, transform idempotent,
R13-driveren fortsatt no-op. RX-OPS1 adversarial-scan kjørt MANUELT på de 56 stagede
filene (OK, exit 0) — `core.hooksPath` skygger repoets pre-commit, fiksen eies av
`.claude`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
06713ed7b8
commit
1c9af82daa
56 changed files with 793 additions and 63 deletions
|
|
@ -26,11 +26,17 @@ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|||
import { join, dirname, relative } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { globSync } from 'node:fs';
|
||||
import { classifyCosmo, findDeadAnchors } from './lib/cosmo-persona.mjs';
|
||||
import {
|
||||
classifyCosmo, findDeadAnchors, classifyPersonaSites, neutralizeLabels,
|
||||
} from './lib/cosmo-persona.mjs';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const PLUGIN_ROOT = join(__dirname, '..', '..');
|
||||
const BASELINE_PATH = join(__dirname, 'data', 'cosmo-gate-baseline.json');
|
||||
// R13b lives in its OWN baseline file. The R13 one records pre-transform reference points
|
||||
// (personaHeadings 401) that regenerating would destroy — never build over a reference-point
|
||||
// artefact, so R13b adds a file rather than re-emitting that one.
|
||||
const LABELS_BASELINE_PATH = join(__dirname, 'data', 'cosmo-labels-baseline.json');
|
||||
|
||||
/** Every reference file in the corpus, as repo-relative paths, sorted. */
|
||||
function refFiles() {
|
||||
|
|
@ -121,6 +127,8 @@ function check() {
|
|||
if (now.persona !== 0) fails.push(`G3: ${f} har faatt ${now.persona} persona-forekomst(er)`);
|
||||
}
|
||||
|
||||
const labelLines = checkLabels(fails);
|
||||
|
||||
console.log('=== R13 persona-gate (ratifisert 2026-09-12, alt. A) ===');
|
||||
console.log(`ref-filer : ${m.files.length} (baseline ${b.refFiles})`);
|
||||
console.log(`G1 persona paa heading-linjer : ${m.totals.heading} (baseline ${b.personaHeadings})`);
|
||||
|
|
@ -131,6 +139,8 @@ function check() {
|
|||
+ `(${exempt.size} unntatt, ${newDead.length} nye, ${healed.length} forsvunnet)`);
|
||||
console.log(`G3 produkt-forekomster : ${m.totals.product} (baseline ${b.productOccurrences})`);
|
||||
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);
|
||||
if (fails.length) {
|
||||
console.log('\nGATE FAILED');
|
||||
for (const f of fails) console.log(` x ${f}`);
|
||||
|
|
@ -178,11 +188,190 @@ function validateNet() {
|
|||
else console.log(`ok KJENT-NEGATIV: ${f} — 0 persona, ${c.product} produkt`);
|
||||
}
|
||||
|
||||
console.log('--- R13b klausulene, begge veier ---');
|
||||
bad += validateLabelsNet();
|
||||
|
||||
console.log(bad === 0 ? '\nNETTET VALIDERT BEGGE VEIER' : `\nNETT-VALIDERING FEILET (${bad})`);
|
||||
process.exit(bad === 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
|
||||
// ================================ R13b: the three decomposed clauses ========================
|
||||
//
|
||||
// R1 REFERENT — has the search string more than one referent? Zero persona in
|
||||
// MECHANICAL bold labels and table cells, while product stays untouched.
|
||||
// R2 REKKEVIDDE — does the operation reach every occurrence it claims? An in-class site
|
||||
// in neither ratified table must be REPORTED, never silently skipped.
|
||||
// R3 HVA SOM STAAR — the 70 editorial occurrences unchanged in count AND content, product
|
||||
// 451 unchanged, R13's headings and TOC still zero (not regraded).
|
||||
//
|
||||
// Each clause carries BOTH a known-positive and a known-negative in --validate-net. A gate
|
||||
// never forced to the other answer is not a measurement.
|
||||
|
||||
/** Per-corpus measurement of the R13b sites. */
|
||||
function measureLabels() {
|
||||
const files = refFiles();
|
||||
const t = {
|
||||
mechLabel: 0, mechCell: 0, edLabel: 0, edCell: 0, prose: 0, product: 0,
|
||||
unmapped: [], mechFiles: [], unreachable: [],
|
||||
};
|
||||
for (const rel of files) {
|
||||
const src = readFileSync(join(PLUGIN_ROOT, rel), 'utf8');
|
||||
const s = classifyPersonaSites(src);
|
||||
t.mechLabel += s.label.mechanical;
|
||||
t.mechCell += s.table.mechanical;
|
||||
t.edLabel += s.label.editorial;
|
||||
t.edCell += s.table.editorial;
|
||||
for (const u of s.unmapped) t.unmapped.push(`${rel}: ${u}`);
|
||||
const c = classifyCosmo(src);
|
||||
t.prose += c.persona.prose;
|
||||
t.product += c.product;
|
||||
// R2: a site the tables know but the transform does not actually rewrite is out of reach.
|
||||
if (s.label.mechanical + s.table.mechanical > 0) {
|
||||
t.mechFiles.push(rel);
|
||||
let after;
|
||||
try { after = neutralizeLabels(src, { relPath: rel }).content; } catch (e) {
|
||||
t.unreachable.push(`${rel}: ${e.message}`); continue;
|
||||
}
|
||||
const a = classifyPersonaSites(after);
|
||||
const left = a.label.mechanical + a.table.mechanical;
|
||||
if (left) t.unreachable.push(`${rel}: ${left} site(r) utenfor transformens rekkevidde`);
|
||||
}
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
function emitLabelsBaseline() {
|
||||
const t = measureLabels();
|
||||
const baseline = {
|
||||
_comment: 'Ratified R13b label/cell baseline (operator 2026-09-15, alt. A). Measured BEFORE '
|
||||
+ 'the label transform. R1 drives mechanical -> 0; R3 pins editorial and product as '
|
||||
+ 'immovable. The 85/47 split the dispatching order carried was re-measured to 62/70 — '
|
||||
+ 'the classifier was wrong, not the count. Regenerate ONLY on a ratified scope change.',
|
||||
measured: new Date().toISOString().slice(0, 10),
|
||||
refFiles: refFiles().length,
|
||||
mechanicalLabels: t.mechLabel,
|
||||
mechanicalCells: t.mechCell,
|
||||
mechanicalTotal: t.mechLabel + t.mechCell,
|
||||
mechanicalFiles: t.mechFiles.length,
|
||||
editorialLabels: t.edLabel,
|
||||
editorialCells: t.edCell,
|
||||
editorialTotal: 70,
|
||||
personaProseBefore: t.prose,
|
||||
personaProseAfter: t.prose - (t.mechLabel + t.mechCell),
|
||||
productOccurrences: t.product,
|
||||
mixedLines: [
|
||||
'skills/ms-ai-advisor/references/prompt-engineering/reasoning-models-o1-o3-optimization.md:549',
|
||||
],
|
||||
};
|
||||
writeFileSync(LABELS_BASELINE_PATH, `${JSON.stringify(baseline, null, 2)}\n`, 'utf8');
|
||||
console.log(`R13b-baseline skrevet: ${relative(PLUGIN_ROOT, LABELS_BASELINE_PATH)}`);
|
||||
console.log(JSON.stringify(baseline, null, 2));
|
||||
}
|
||||
|
||||
/** Evaluate R1-R3, pushing onto the shared fails list; returns the printable lines. */
|
||||
function checkLabels(fails) {
|
||||
if (!existsSync(LABELS_BASELINE_PATH)) return ['(R13b: ingen baseline — klausulene hoppes over)'];
|
||||
const b = JSON.parse(readFileSync(LABELS_BASELINE_PATH, 'utf8'));
|
||||
const t = measureLabels();
|
||||
const mech = t.mechLabel + t.mechCell;
|
||||
|
||||
// --- R1 REFERENT ---
|
||||
if (mech !== 0) {
|
||||
fails.push(`R1: ${mech} mekanisk(e) persona-site(r) staar igjen `
|
||||
+ `(${t.mechLabel} etiketter, ${t.mechCell} celler; baseline ${b.mechanicalTotal})`);
|
||||
}
|
||||
if (t.product !== b.productOccurrences) {
|
||||
fails.push(`R1/R3: produkt ${t.product} != baseline ${b.productOccurrences}`);
|
||||
}
|
||||
|
||||
// --- R2 REKKEVIDDE ---
|
||||
if (t.unmapped.length) {
|
||||
fails.push(`R2: ${t.unmapped.length} persona-site(r) i INGEN ratifisert tabell — `
|
||||
+ `umaalt variant: ${t.unmapped.slice(0, 5).join(' | ')}`);
|
||||
}
|
||||
if (t.unreachable.length) {
|
||||
fails.push(`R2: ${t.unreachable.length} site(r) utenfor transformens rekkevidde: `
|
||||
+ `${t.unreachable.slice(0, 5).join(' | ')}`);
|
||||
}
|
||||
|
||||
// --- 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})`);
|
||||
}
|
||||
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}`);
|
||||
}
|
||||
|
||||
const done = mech === 0;
|
||||
return [
|
||||
`R1 mekaniske sites (etikett+celle) : ${mech} (baseline ${b.mechanicalTotal} `
|
||||
+ `= ${b.mechanicalLabels}+${b.mechanicalCells} i ${b.mechanicalFiles} filer)`,
|
||||
`R2 umaalte varianter / utenfor rekkevidde : ${t.unmapped.length} / ${t.unreachable.length}`,
|
||||
`R3 redaksjonelt holdt for R14 : ${t.edLabel} etiketter + ${t.edCell} celler `
|
||||
+ `(baseline ${b.editorialLabels} + ${b.editorialCells})`,
|
||||
` persona i proesa/tabell : ${t.prose} (${b.personaProseBefore} foer R13b, `
|
||||
+ `${b.personaProseAfter} etter)${done ? ' — R13b UTFOERT' : ' — R13b IKKE kjoert'}`,
|
||||
];
|
||||
}
|
||||
|
||||
/** Force each R13b clause to the other answer. */
|
||||
function validateLabelsNet() {
|
||||
let bad = 0;
|
||||
const ok = (msg) => console.log(`ok ${msg}`);
|
||||
const no = (msg) => { console.log(`x ${msg}`); bad++; };
|
||||
|
||||
// R1 known-positive — BOTH forms, because the genitive table variant is precisely what a
|
||||
// bold-only net misses.
|
||||
const victim = refFiles()[0];
|
||||
const base = readFileSync(join(PLUGIN_ROOT, victim), 'utf8');
|
||||
const inj = classifyPersonaSites(`${base}\n**For Cosmo:** x\n\n| A | Cosmos råd |\n`);
|
||||
const pre = classifyPersonaSites(base);
|
||||
if (inj.label.mechanical !== pre.label.mechanical + 1) no('R1 KJENT-POSITIV: injisert "**For Cosmo:**" ble IKKE sett');
|
||||
else ok('R1 KJENT-POSITIV: injisert "**For Cosmo:**" felte R1');
|
||||
if (inj.table.mechanical !== pre.table.mechanical + 1) no('R1 KJENT-POSITIV: injisert "| Cosmos råd |" (genitiv) ble IKKE sett');
|
||||
else ok('R1 KJENT-POSITIV: injisert "| Cosmos råd |" (genitiv) felte R1');
|
||||
|
||||
// R1 known-negative — the second case is the dangerous one: it LOOKS like a persona label.
|
||||
const prod = classifyPersonaSites('**Cosmos DB-anbefaling:** bruk autoscale\n| Azure Cosmos DB | 8000 NOK |\n');
|
||||
if (prod.label.mechanical + prod.table.mechanical !== 0 || prod.unmapped.length) {
|
||||
no(`R1 KJENT-NEGATIV: produkt feilklassifisert (${JSON.stringify(prod)})`);
|
||||
} else ok('R1 KJENT-NEGATIV: "**Cosmos DB-anbefaling:**" og "| Azure Cosmos DB |" passerer urort');
|
||||
|
||||
// R2 known-positive — an in-class site the tables do not know must be REPORTED.
|
||||
const hole = classifyPersonaSites('**Cosmo-hjørnet:** noe tekst\n');
|
||||
if (hole.unmapped.length !== 1) no('R2 KJENT-POSITIV: ukjent etikettform ble IKKE rapportert');
|
||||
else ok('R2 KJENT-POSITIV: ukjent etikettform "**Cosmo-hjørnet:**" rapportert som umaalt');
|
||||
let threw = false;
|
||||
try { neutralizeLabels('**Cosmo-hjørnet:** noe tekst\n'); } catch { threw = true; }
|
||||
if (!threw) no('R2 KJENT-POSITIV: transformen hoppet STILLE over en ukjent form'); else ok('R2 KJENT-POSITIV: transformen KASTER paa ukjent form, hopper ikke stille over');
|
||||
|
||||
// R2 known-negative — the editorial classes must be reported OUT of scope and left alone.
|
||||
const ed = '**Cosmo svarer:**\n| A | B | Cosmo-syntese av verified sources |\n';
|
||||
const edC = classifyPersonaSites(ed);
|
||||
if (edC.label.editorial !== 1 || edC.table.editorial !== 1) no('R2 KJENT-NEGATIV: redaksjonell form ikke bokfoert som utenfor scope');
|
||||
else if (neutralizeLabels(ed).content !== ed) no('R2 KJENT-NEGATIV: redaksjonell form ble ENDRET');
|
||||
else ok('R2 KJENT-NEGATIV: dialog + proveniens bokfoert utenfor scope OG byte-identiske');
|
||||
|
||||
// R3 known-positive — break one product occurrence and the product clause must fell.
|
||||
const broken = classifyCosmo('| Azure Cosmo DB | 8000 |\n');
|
||||
const intact = classifyCosmo('| Azure Cosmos DB | 8000 |\n');
|
||||
if (!(intact.product === 1 && broken.product === 0)) no(`R3 KJENT-POSITIV: fjernet tegn i "Cosmos DB" felte IKKE produkt-differansen (${intact.product} -> ${broken.product})`);
|
||||
else ok('R3 KJENT-POSITIV: fjernet tegn i "Cosmos DB" felte produkt-differansen (1 -> 0)');
|
||||
|
||||
// R3 known-negative — the product-only files must be byte-identical through the transform.
|
||||
for (const f of productOnlyFiles(measure())) {
|
||||
const src = readFileSync(join(PLUGIN_ROOT, f), 'utf8');
|
||||
if (neutralizeLabels(src, { relPath: f }).content !== src) no(`R3 KJENT-NEGATIV: kun-produkt-fila ${f} ble endret`);
|
||||
else ok(`R3 KJENT-NEGATIV: ${f} byte-identisk gjennom transformen`);
|
||||
}
|
||||
return bad;
|
||||
}
|
||||
|
||||
const arg = process.argv[2];
|
||||
if (arg === '--emit-baseline') emitBaseline();
|
||||
else if (arg === '--emit-labels-baseline') emitLabelsBaseline();
|
||||
else if (arg === '--validate-net') validateNet();
|
||||
else check();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue