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>
126 lines
6 KiB
JavaScript
126 lines
6 KiB
JavaScript
#!/usr/bin/env node
|
|
// neutralize-cosmo-labels.mjs — R13b: apply the ratified neutralisation to the MECHANICAL
|
|
// persona sites that sit OUTSIDE headings — bold labels and table cells — in one per-file
|
|
// operation, behind the same plan-then-write discipline as R13's heading driver.
|
|
//
|
|
// SCOPE (operator-ratified 2026-09-15, alternative A; measured, not inherited). IN: the 62
|
|
// mechanical occurrences — 46 bold labels in 16 ratified variants, 16 table cells in 4.
|
|
// OUT, and deliberately so: the 70 editorial ones, which R14 owns. The dispatching order
|
|
// carried "MEKANISK 85 / REDAKSJONELT 47" and asked for that premise to be re-measured before
|
|
// anything was built; re-measured, it is wrong in the CLASSIFIER, not the count — 14 bold
|
|
// labels are dialogue speaker attributions standing in front of quoted speech, 10 table cells
|
|
// are provenance claims in the source-quality column, and one list-item label hidden behind a
|
|
// `- ` marker leaks the other way INTO scope. Full derivation: lib/cosmo-persona.mjs.
|
|
//
|
|
// Also out, unchanged from R13: the 4 SKILL.md, the 23 commands, CLAUDE.md / README / NOTICE,
|
|
// and docs/ — all R14 — and R13's own headings and TOC, which are green and are not regraded.
|
|
//
|
|
// INVARIANTS asserted per file BEFORE any write (a throw aborts the whole run and writes
|
|
// nothing):
|
|
// - zero MECHANICAL label/cell sites remain
|
|
// - the editorial counts are untouched — if one moved, the scope has been breached
|
|
// - product occurrences unchanged (the 451 Azure Cosmos DB mentions)
|
|
// - persona on headings and in TOC entries still zero (R13 not regraded)
|
|
// - line count unchanged; no NEW dead fragment link
|
|
// - only lines the transform claims to have touched differ from the original
|
|
// Idempotent: a re-run matches no key and is a no-op, so an interrupted run is re-runnable.
|
|
//
|
|
// Usage: node scripts/kb-update/neutralize-cosmo-labels.mjs [--dry]
|
|
import { readFileSync, realpathSync, globSync } from 'node:fs';
|
|
import { join, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { atomicWriteSync } from './lib/atomic-write.mjs';
|
|
import { classifyCosmo, classifyPersonaSites, findDeadAnchors, neutralizeLabels } from './lib/cosmo-persona.mjs';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const PLUGIN_ROOT = join(__dirname, '..', '..');
|
|
|
|
/** Throws on the first violation, so the run aborts before a single byte is written. */
|
|
function assertInvariant(rel, before, after, changes) {
|
|
const sb = classifyPersonaSites(before);
|
|
const sa = classifyPersonaSites(after);
|
|
const left = sa.label.mechanical + sa.table.mechanical;
|
|
if (left !== 0) throw new Error(`${rel}: ${left} mekanisk(e) persona-site(r) staar igjen`);
|
|
if (sa.unmapped.length) throw new Error(`${rel}: ukjent persona-site: ${sa.unmapped.join(', ')}`);
|
|
if (sa.label.editorial !== sb.label.editorial || sa.table.editorial !== sb.table.editorial) {
|
|
throw new Error(`${rel}: REDAKSJONELT scope roert — etiketter `
|
|
+ `${sb.label.editorial} -> ${sa.label.editorial}, celler `
|
|
+ `${sb.table.editorial} -> ${sa.table.editorial}`);
|
|
}
|
|
|
|
const b = classifyCosmo(before);
|
|
const a = classifyCosmo(after);
|
|
if (a.product !== b.product) throw new Error(`${rel}: produkt endret ${b.product} -> ${a.product}`);
|
|
if (a.persona.heading !== 0 || a.persona.toc !== 0) {
|
|
throw new Error(`${rel}: R13 regradert — heading ${a.persona.heading}, toc ${a.persona.toc}`);
|
|
}
|
|
|
|
const lb = before.split('\n');
|
|
const la = after.split('\n');
|
|
if (lb.length !== la.length) throw new Error(`${rel}: linjeantall endret ${lb.length} -> ${la.length}`);
|
|
|
|
const deadBefore = new Set(findDeadAnchors(before).map((d) => d.anchor));
|
|
const newDead = findDeadAnchors(after).map((d) => d.anchor).filter((x) => !deadBefore.has(x));
|
|
if (newDead.length) throw new Error(`${rel}: nye doede ankre: ${newDead.join(', ')}`);
|
|
|
|
const touched = new Set(changes.map((c) => c.line));
|
|
for (let i = 0; i < lb.length; i++) {
|
|
if (lb[i] === la[i]) continue;
|
|
if (!touched.has(i + 1)) {
|
|
throw new Error(`${rel}:${i + 1}: linje endret uten aa staa i endringslista\n`
|
|
+ ` foer: ${lb[i]}\n etter: ${la[i]}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function main() {
|
|
const dry = process.argv.includes('--dry');
|
|
const files = globSync('skills/*/references/**/*.md', { cwd: PLUGIN_ROOT }).sort();
|
|
const planned = [];
|
|
let labelChanges = 0;
|
|
let cellChanges = 0;
|
|
let heldBack = 0;
|
|
|
|
for (const rel of files) {
|
|
const before = readFileSync(join(PLUGIN_ROOT, rel), 'utf8');
|
|
heldBack += (() => { const s = classifyPersonaSites(before); return s.label.editorial + s.table.editorial; })();
|
|
const { content: after, changes } = neutralizeLabels(before, { relPath: rel });
|
|
if (after === before) continue;
|
|
assertInvariant(rel, before, after, changes);
|
|
labelChanges += changes.filter((c) => c.kind === 'label').length;
|
|
cellChanges += changes.filter((c) => c.kind === 'cell').length;
|
|
planned.push({ rel, out: after, changes });
|
|
}
|
|
|
|
console.log(`ref-filer: ${files.length} | aa endre: ${planned.length} | urørt: ${files.length - planned.length}`);
|
|
console.log(`etikett-endringer: ${labelChanges} | tabellcelle-endringer: ${cellChanges} `
|
|
+ `| SUM ${labelChanges + cellChanges}`);
|
|
console.log(`redaksjonelle sites holdt tilbake til R14: ${heldBack}`);
|
|
|
|
if (dry) {
|
|
const byTarget = new Map();
|
|
for (const p of planned) {
|
|
for (const c of p.changes) {
|
|
const k = `${c.kind.padEnd(5)} ${c.from} -> ${c.to}`;
|
|
byTarget.set(k, (byTarget.get(k) || 0) + 1);
|
|
}
|
|
}
|
|
console.log('\n(dry run — ingen skriving)\n--- mapping som vil brukes ---');
|
|
for (const [k, v] of [...byTarget.entries()].sort((x, y) => y[1] - x[1])) {
|
|
console.log(`${String(v).padStart(4)} ${k}`);
|
|
}
|
|
return;
|
|
}
|
|
|
|
for (const p of planned) atomicWriteSync(join(PLUGIN_ROOT, p.rel), p.out);
|
|
console.log(`\nSkrev ${planned.length} filer.`);
|
|
}
|
|
|
|
const isMain = (() => {
|
|
try {
|
|
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
|
} catch {
|
|
return false;
|
|
}
|
|
})();
|
|
if (isMain) main();
|