Ordre 20260912T193441Z-7358817909. Steg 1 var ikke transformen, men å rette
roadmapens R13-gate og få den ratifisert. Gaten `grep -rl "Cosmo"
skills/*/references -> 0` var usann på to uavhengige måter:
1. Ordren fanget den første: 451 av forekomstene er Azure Cosmos DB, ekte
produktinnhold. Diskriminatoren er ikke bokstaven «s» — `Cosmos <norsk
substantiv>` er genitiv av personaen (`### Cosmos tonalitet`), mens
`Cosmos DB`/`CosmosClient`/`cosmos_ru` er produkt.
2. Denne økten fant den andre: 132 persona-forekomster ligger i prosa,
tabeller, dialog-replikker og proveniens-linjer. Heading-nøytralisering
kan ikke nå dem, så «0 persona» er uoppnåelig også under den ratifiserte
formen. Operatøren ratifiserte alternativ A: gaten speiler formen, og de
132 bokføres til R13b/R14.
Tre korreksjoner av premisser som sto i ordren og STATE:
«ca 320 produkt» -> 451 (case-sensitivt nett manglet 327 lowercase
TOC-ankre + 99 identifikatorer; sann nevner 1 638)
«169 headinger» -> 401. 169 var `^## For Cosmo`-prefikset (168) og var
internt inkonsistent med sin egen topp-variant (204)
«417 matcher ingen
populasjon» -> 417 er cosmo-headinger utenfor kodefences; briefens
nevner var reell hele tiden
Fence-bevissthet er målt skadelig, ikke nødvendig: begge toggle-regler er
gale på dette korpuset (naiv toggle skjuler en ekte heading i
chain-of-thought-prompting.md, CommonMark-regelen ubalanserer
service-level-documentation-dr.md). Fence-agnostisk deteksjon finner 401
heading-linjer i nøyaktig de samme 40 variantene som fence-bevisst finner
400 i — ingen kodeblokk-linje er byte-identisk til en persona-heading. Derfor
nøkles transformen på 40 enumererte heading-tekster og ignorerer fences. En
ukjent variant kaster; en slug-kollisjon kaster. Ingenting auto-fikses.
TOC-en regenereres ikke, den rettes kirurgisk: alle 327 persona-lenker hadde
lenketekst lik én av de 40 heading-tekstene og anker lik slugify av den
(327/327, 0 avvik), så heading og TOC-entry skrives i samme operasjon og
ingen mellomtilstand etterlater en død lenke.
Ratifisert målform: `For Cosmo`, `For Cosmo Skyberg` og `For arkitekten
(Cosmo)` konvergerer på `For arkitekten`. To filer kolliderte og er adjudisert
ved å lese dem, ikke ved regel.
Verifisering (alle 7 kriterier fra ordren):
G1 persona på heading-linjer 401 -> 0
G2 døde fragmentlenker 1 -> 1 (pre-eksisterende, unntatt)
G3 produkt-forekomster 451 -> 451; `Cosmos DB|Azure Cosmos` 308 = 308
de 3 kun-produkt-filene byte-identiske
nettet validert begge veier injisert persona feller G1; genitiv feller G1;
produkt-heading og de 3 filene passerer
hele diffen 802 heading-linjer + 654 TOC-linjer, ANNET = 0
linjeantall 728 lagt til = 728 slettet
suite 1120/1120 (1097 + 23 nye)
validate-plugin 250 PASS / 0 FAIL
stikkprøve 10 filer, alle 5 skills, inkl. de 3 mest
produkt-tunge (26/20/19) — kun heading+TOC
Utenfor scope, urørt: de 4 SKILL.md, de 23 commands, CLAUDE.md, README.md,
NOTICE.md, docs/ (alt R14).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
327 lines
14 KiB
JavaScript
327 lines
14 KiB
JavaScript
// test-cosmo-persona.test.mjs — R13 Cosmo del 1: persona-classification + heading
|
|
// neutralisation in the reference corpus. The gate this pins is the RATIFIED one
|
|
// (operator 2026-09-12, alternative A), NOT the roadmap's `grep -rl "Cosmo" -> 0`,
|
|
// which was measured false twice over: it would delete 451 Azure Cosmos DB
|
|
// occurrences, and it is unreachable under a heading-only scope because 132 persona
|
|
// occurrences live in prose, tables and code blocks that heading neutralisation
|
|
// cannot reach.
|
|
//
|
|
// The three clauses (measured baselines 2026-09-12 over skills/*/references, 389 files):
|
|
// G1 persona occurrences on markdown heading lines 401 -> 0 (374 files, 40 variants)
|
|
// G2 every fragment link resolves to a real heading 1 dead -> 1 dead
|
|
// (agent-framework.md#pattern-3-human-in-the-loop is pre-existing and exempt)
|
|
// G3 product occurrences unchanged 451 -> 451
|
|
//
|
|
// WHY exact-string keying and NOT fence-awareness: measured, both toggle rules are
|
|
// wrong on this corpus. A naive ``` toggle leaves chain-of-thought-prompting.md
|
|
// unbalanced (19 markers) and hides the real heading at line 403; the CommonMark
|
|
// info-string rule instead unbalances service-level-documentation-dr.md (nested
|
|
// ```markdown examples). Measured resolution: fence-agnostic detection finds 401
|
|
// heading-like persona lines in EXACTLY the same 40 variants as fence-aware
|
|
// detection finds 400 — no code-block line is byte-identical to a persona heading.
|
|
// So the transform keys on the 40 enumerated heading texts and ignores fences, which
|
|
// is both correct and auditable. An unmapped persona heading THROWS: content is never
|
|
// auto-rewritten by pattern.
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
isPersonaOccurrence,
|
|
classifyCosmo,
|
|
HEADING_MAP,
|
|
FILE_HEADING_OVERRIDES,
|
|
neutralizeContent,
|
|
slugifyHeading,
|
|
findDeadAnchors,
|
|
} from '../../scripts/kb-update/lib/cosmo-persona.mjs';
|
|
|
|
// ---------------------------------------------------------------- classification
|
|
|
|
test('isPersonaOccurrence — bare Cosmo is persona', () => {
|
|
assert.equal(isPersonaOccurrence('Cosmo anbefaler X'), true);
|
|
assert.equal(isPersonaOccurrence("Cosmo's Cheat Sheet"), true);
|
|
assert.equal(isPersonaOccurrence('Cosmo-tilnærming'), true);
|
|
assert.equal(isPersonaOccurrence('Cosmo Skyberg, arkitekt'), true);
|
|
});
|
|
|
|
test('isPersonaOccurrence — lowercase anchor fragment is persona', () => {
|
|
// The 327 TOC anchors: `](#for-arkitekten-cosmo)` — persona, and invisible to a
|
|
// case-sensitive net. This is why the roadmap gate could not even see them.
|
|
assert.equal(isPersonaOccurrence('cosmo)'), true);
|
|
assert.equal(isPersonaOccurrence('cosmo-skyberg)'), true);
|
|
});
|
|
|
|
test('isPersonaOccurrence — Azure Cosmos DB and SDK identifiers are product', () => {
|
|
assert.equal(isPersonaOccurrence('Cosmos DB for NoSQL'), false);
|
|
assert.equal(isPersonaOccurrence('Cosmos-DB"'), false);
|
|
assert.equal(isPersonaOccurrence('CosmosClient(connectionString)'), false);
|
|
assert.equal(isPersonaOccurrence('CosmosAIGraph'), false);
|
|
assert.equal(isPersonaOccurrence('cosmosdb show'), false);
|
|
assert.equal(isPersonaOccurrence('cosmos_primary_monthly'), false);
|
|
assert.equal(isPersonaOccurrence('Cosmos;'), false); // Microsoft.Azure.Cosmos;
|
|
assert.equal(isPersonaOccurrence('cosmos-db/continuous-backup-restore)'), false);
|
|
});
|
|
|
|
test('isPersonaOccurrence — Norwegian genitive "Cosmos <substantiv>" is persona', () => {
|
|
// The discriminator is NOT the letter s: 9 measured occurrences are the persona's
|
|
// genitive, e.g. `### Cosmos tonalitet`.
|
|
assert.equal(isPersonaOccurrence('Cosmos tonalitet'), true);
|
|
assert.equal(isPersonaOccurrence('Cosmos veiledningsstrategi'), true);
|
|
assert.equal(isPersonaOccurrence('Cosmos råd |'), true);
|
|
assert.equal(isPersonaOccurrence('Cosmos anbefalinger'), true);
|
|
assert.equal(isPersonaOccurrence('Cosmos erfaringsbaserte anbefalinger)'), true);
|
|
});
|
|
|
|
test('isPersonaOccurrence — a function word after Cosmos keeps it product', () => {
|
|
// "Redis for hot, Cosmos for warm data" — product prose, not a genitive.
|
|
assert.equal(isPersonaOccurrence('Cosmos for warm data)'), false);
|
|
assert.equal(isPersonaOccurrence('Cosmos og Kusto'), false);
|
|
assert.equal(isPersonaOccurrence('Cosmos i DR-region'), false);
|
|
});
|
|
|
|
test('classifyCosmo — buckets persona by syntactic location and counts product', () => {
|
|
const doc = [
|
|
'# Tittel',
|
|
'',
|
|
'- [For Cosmo](#for-cosmo)', // TOC line: 2 persona (text + anchor)
|
|
'',
|
|
'## For Cosmo', // heading: 1 persona
|
|
'',
|
|
'**For Cosmo:** bruk denne.', // prose: 1 persona
|
|
'',
|
|
'### Azure Cosmos DB for NoSQL', // heading: product only
|
|
'',
|
|
'Bruk `CosmosClient` mot Cosmos DB.', // prose: 2 product
|
|
'',
|
|
].join('\n');
|
|
const c = classifyCosmo(doc);
|
|
assert.equal(c.persona.heading, 1);
|
|
assert.equal(c.persona.toc, 2);
|
|
assert.equal(c.persona.prose, 1);
|
|
assert.equal(c.product, 3); // Azure Cosmos (heading) + CosmosClient + Cosmos DB (prose)
|
|
assert.equal(c.personaTotal, 4);
|
|
});
|
|
|
|
// ---------------------------------------------------------------- the mapping table
|
|
|
|
test('HEADING_MAP covers all 40 measured persona heading variants', () => {
|
|
assert.equal(Object.keys(HEADING_MAP).length, 40);
|
|
});
|
|
|
|
test('HEADING_MAP — every neutral target is free of persona', () => {
|
|
for (const [from, to] of Object.entries(HEADING_MAP)) {
|
|
assert.equal(
|
|
classifyCosmo(`## ${to}`).personaTotal, 0,
|
|
`target for "${from}" still carries persona: "${to}"`,
|
|
);
|
|
}
|
|
});
|
|
|
|
test('HEADING_MAP — no target invents a word absent from its source', () => {
|
|
// The order forbids new or changed Cosmo content beyond the neutralisation itself.
|
|
// Enforced word-by-word: every word of a neutral target must already occur in the
|
|
// source heading, EXCEPT the two sanctioned substitutions for the persona token —
|
|
// "arkitekten" (the role Cosmo played) and "å" (when a name was the sentence's
|
|
// subject, e.g. "Spørsmål Cosmo bør stille" -> "Spørsmål å stille"). Dropping words
|
|
// is allowed; introducing any other word is not.
|
|
const SANCTIONED = new Set(['arkitekten', 'å']);
|
|
const words = (s) => s.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean);
|
|
for (const [from, to] of Object.entries(HEADING_MAP)) {
|
|
const src = new Set(words(from));
|
|
for (const w of words(to)) {
|
|
assert.ok(
|
|
src.has(w) || SANCTIONED.has(w),
|
|
`target invents the word "${w}": "${from}" -> "${to}"`,
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
test('FILE_HEADING_OVERRIDES resolves both measured in-file slug collisions', () => {
|
|
// Swept over all 389 files: exactly two documents where the ratified mapping would
|
|
// make two headings share one slug. One collides with a sibling persona heading, the
|
|
// other with the document's own H1 title.
|
|
const collision1 = 'skills/ms-ai-engineering/references/mlops-genaiops/feedback-loops-continuous-improvement.md';
|
|
const collision2 = 'skills/ms-ai-governance/references/monitoring-observability/application-insights-llm-monitoring.md';
|
|
assert.equal(FILE_HEADING_OVERRIDES[collision1]['For Cosmo'], 'Nøkkelpunkter for rådgivning');
|
|
assert.equal(
|
|
FILE_HEADING_OVERRIDES[collision2]['For Cosmo Skyberg: Application Insights for LLM Monitoring'],
|
|
'For arkitekten',
|
|
);
|
|
assert.equal(Object.keys(FILE_HEADING_OVERRIDES).length, 2);
|
|
});
|
|
|
|
test('FILE_HEADING_OVERRIDES only ever redirects a variant HEADING_MAP already knows', () => {
|
|
// An override is a collision remedy, never a back door for an unmeasured heading:
|
|
// every key must already be in the shared table, and every target persona-free.
|
|
for (const [rel, map] of Object.entries(FILE_HEADING_OVERRIDES)) {
|
|
for (const [from, to] of Object.entries(map)) {
|
|
assert.ok(HEADING_MAP[from] !== undefined, `${rel}: "${from}" is not a known variant`);
|
|
assert.notEqual(to.trim(), '', `${rel}: empty target for "${from}"`);
|
|
assert.equal(classifyCosmo(`## ${to}`).personaTotal, 0, `${rel}: target keeps persona: "${to}"`);
|
|
}
|
|
}
|
|
});
|
|
|
|
// ---------------------------------------------------------------- slug parity
|
|
|
|
test('slugifyHeading matches transform.mjs slug semantics (æøå survive)', () => {
|
|
assert.equal(slugifyHeading('For arkitekten (Cosmo)'), 'for-arkitekten-cosmo');
|
|
assert.equal(slugifyHeading('Spørsmål å stille kunden'), 'spørsmål-å-stille-kunden');
|
|
assert.equal(slugifyHeading('For Cosmo: Beslutningsveiledning'), 'for-cosmo-beslutningsveiledning');
|
|
});
|
|
|
|
// ---------------------------------------------------------------- the transform
|
|
|
|
test('neutralizeContent rewrites the heading AND its TOC entry and anchor together', () => {
|
|
const before = [
|
|
'# Tittel',
|
|
'',
|
|
'## Innhold',
|
|
'',
|
|
'- [Introduksjon](#introduksjon)',
|
|
'- [For arkitekten (Cosmo)](#for-arkitekten-cosmo)',
|
|
'',
|
|
'## Introduksjon',
|
|
'',
|
|
'## For arkitekten (Cosmo)',
|
|
'',
|
|
'Tekst.',
|
|
'',
|
|
].join('\n');
|
|
const { content, changes } = neutralizeContent(before, { relPath: 'x.md' });
|
|
assert.match(content, /^## For arkitekten$/m);
|
|
assert.doesNotMatch(content, /Cosmo/i);
|
|
assert.match(content, /^- \[For arkitekten\]\(#for-arkitekten\)$/m);
|
|
assert.equal(changes.length, 2, 'one heading + one TOC entry');
|
|
});
|
|
|
|
test('neutralizeContent leaves product headings and product prose byte-identical', () => {
|
|
const before = [
|
|
'# Tittel',
|
|
'',
|
|
'### Azure Cosmos DB for GraphRAG',
|
|
'',
|
|
'Bruk `CosmosClient` mot Cosmos DB; se `cosmos_primary_monthly`.',
|
|
'',
|
|
'### Cosmos DB Autoscale',
|
|
'',
|
|
].join('\n');
|
|
const { content, changes } = neutralizeContent(before, { relPath: 'x.md' });
|
|
assert.equal(content, before);
|
|
assert.equal(changes.length, 0);
|
|
});
|
|
|
|
test('neutralizeContent preserves the product occurrence count exactly', () => {
|
|
const before = [
|
|
'## For Cosmo',
|
|
'',
|
|
'Azure Cosmos DB med `CosmosClient` og cosmosdb-CLI.',
|
|
'',
|
|
].join('\n');
|
|
const productBefore = classifyCosmo(before).product;
|
|
const { content } = neutralizeContent(before, { relPath: 'x.md' });
|
|
assert.equal(classifyCosmo(content).product, productBefore);
|
|
assert.equal(classifyCosmo(content).personaTotal, 0);
|
|
});
|
|
|
|
test('neutralizeContent rewrites a heading that a fence toggle would hide', () => {
|
|
// The chain-of-thought-prompting.md case: an unbalanced fence means a naive
|
|
// toggle believes line 4 is inside a code block. Exact-string keying must not care.
|
|
const before = [
|
|
'```python',
|
|
'x = 1',
|
|
'```python', // stray opener — leaves a naive toggle "inside" forever
|
|
'',
|
|
'## For arkitekten (Cosmo)',
|
|
'',
|
|
].join('\n');
|
|
const { content } = neutralizeContent(before, { relPath: 'x.md' });
|
|
assert.match(content, /^## For arkitekten$/m);
|
|
});
|
|
|
|
test('neutralizeContent is idempotent — a second run changes nothing', () => {
|
|
const before = [
|
|
'- [For Cosmo](#for-cosmo)',
|
|
'',
|
|
'## For Cosmo',
|
|
'',
|
|
].join('\n');
|
|
const once = neutralizeContent(before, { relPath: 'x.md' }).content;
|
|
const twice = neutralizeContent(once, { relPath: 'x.md' });
|
|
assert.equal(twice.content, once);
|
|
assert.equal(twice.changes.length, 0);
|
|
});
|
|
|
|
test('neutralizeContent THROWS on an in-file slug collision', () => {
|
|
// Two persona headings mapping to the same neutral text would produce a duplicate
|
|
// heading and an ambiguous anchor. Fail loud; never write blind.
|
|
const before = [
|
|
'## For arkitekten (Cosmo)',
|
|
'',
|
|
'## For Cosmo',
|
|
'',
|
|
].join('\n');
|
|
assert.throws(
|
|
() => neutralizeContent(before, { relPath: 'x.md' }),
|
|
/kollisjon/i,
|
|
);
|
|
});
|
|
|
|
test('neutralizeContent THROWS on an unmapped persona heading', () => {
|
|
// Content is never auto-rewritten by pattern: an unseen variant is an operator
|
|
// decision, not a silent guess.
|
|
const before = ['## For Cosmo: en helt ny variant ingen har målt', ''].join('\n');
|
|
assert.throws(
|
|
() => neutralizeContent(before, { relPath: 'x.md' }),
|
|
/ukjent persona-heading/i,
|
|
);
|
|
});
|
|
|
|
test('neutralizeContent does not touch persona in prose, tables or dialogue', () => {
|
|
// Explicitly OUT of the ratified R13 scope — the 132 residual occurrences booked
|
|
// for R13b/R14. A transform that reached them would exceed its mandate.
|
|
const before = [
|
|
'**For Cosmo:** bruk denne modellen.',
|
|
'',
|
|
'| For arkitekten (Cosmo) | Baseline |',
|
|
'',
|
|
'*Cosmo:* "La oss starte med risikoprofilen."',
|
|
'',
|
|
].join('\n');
|
|
const { content, changes } = neutralizeContent(before, { relPath: 'x.md' });
|
|
assert.equal(content, before);
|
|
assert.equal(changes.length, 0);
|
|
});
|
|
|
|
// ---------------------------------------------------------------- G2: dead anchors
|
|
|
|
test('findDeadAnchors reports a fragment link with no matching heading', () => {
|
|
const doc = [
|
|
'- [Finnes](#finnes)',
|
|
'- [Finnes ikke](#finnes-ikke)',
|
|
'',
|
|
'## Finnes',
|
|
'',
|
|
].join('\n');
|
|
const dead = findDeadAnchors(doc);
|
|
assert.equal(dead.length, 1);
|
|
assert.equal(dead[0].anchor, 'finnes-ikke');
|
|
});
|
|
|
|
test('findDeadAnchors returns empty when every anchor resolves', () => {
|
|
const doc = ['- [Spørsmål å stille](#spørsmål-å-stille)', '', '## Spørsmål å stille', ''].join('\n');
|
|
assert.deepEqual(findDeadAnchors(doc), []);
|
|
});
|
|
|
|
test('a neutralized document has no dead anchors', () => {
|
|
// The whole point of rewriting heading and TOC entry in one operation.
|
|
const before = [
|
|
'- [For Cosmo: Quick Reference Card](#for-cosmo-quick-reference-card)',
|
|
'',
|
|
'## For Cosmo: Quick Reference Card',
|
|
'',
|
|
].join('\n');
|
|
const { content } = neutralizeContent(before, { relPath: 'x.md' });
|
|
assert.deepEqual(findDeadAnchors(content), []);
|
|
assert.match(content, /^- \[Quick Reference Card\]\(#quick-reference-card\)$/m);
|
|
});
|