feat(ms-ai-architect): Spor 1 — insertHeaderFields + surgical normalizeStaleVerified (3 dialekter, pipe-safe, ---aware, TDD) [skip-docs]

This commit is contained in:
Kjell Tore Guttormsen 2026-07-04 08:54:21 +02:00
commit 6b58afda3b
2 changed files with 338 additions and 0 deletions

View file

@ -50,6 +50,14 @@ const MS_LEARN_RE = /\b(?:learn|docs)\.microsoft\.com/i;
const DATE_RE = /^\d{4}-\d{2}(?:-\d{2})?$/;
// The top-of-file region the kb-headers parsers scan. MIRRORS kb-headers.mjs:6
// (HEADER_REGION_BYTES) — kept local rather than imported so this lib's only
// kb-headers dependency stays the read-only parser functions (same discipline as
// TOC_MIN_LINES mirroring eval.mjs). insertHeaderFields / normalizeStaleVerified use it
// to keep the fields they insert — and the stale Verified they strip — inside the window
// those parsers actually read.
const HEADER_REGION_BYTES = 500;
// A reference file longer than this many lines should carry a table of contents
// (best-practices §Structure). MIRRORS eval.mjs N4_LARGE_FILE_LINES — kept in sync
// behaviourally by test-transform's checkN4 parity assertion, NOT by importing across
@ -257,6 +265,122 @@ export function insertToc(content) {
return [...before, ...block, ...lines.slice(idx)].join('\n');
}
// A bold-label meta line, e.g. **Category:** / **Kategori:** / **Status:** /
// **Sist oppdatert:** / **Confidence:** — and a pipe-delimited meta row, which also
// begins with a bold label. The non-greedy label body excludes `*` so it stops at the
// first `:**`. Language-agnostic by construction: it keys on the bold-label shape, not
// on a fixed label vocabulary.
const RE_META_LINE = /^\s*\*\*[^*\n]+?:\*\*/;
/**
* Backfill the Port-1 `**Type:**` (and, for a reference, `**Source:**`) header field(s)
* into an EXISTING legacy KB file that lacks them the Spor 1 migration primitive for
* the ~327 non-advisor files. Unlike buildKbHeader (which emits a COMPLETE new header),
* this inserts ONLY the missing field(s), placing them immediately after the last line of
* the first contiguous run of bold-label meta lines (RE_META_LINE covers the
* **Category:** / **Kategori:** / pipe dialects). It never inserts before the first `## `
* section or past the 500-byte scan window, so the fields land where kb-headers.* read
* them; the body is byte-identical.
*
* Per-field idempotent: skips `**Type:**` when parseTypeHeader is already set, skips
* `**Source:**` when parseSourceHeader is already set. A non-reference type NEVER receives
* a `**Source:**` (mirrors buildKbHeader's out-of-correctness-scope rule) Type only.
*
* @param {string} content full existing file content
* @param {{type?: string, source?: string}} meta resolved type + (reference) authority URL
* @returns {string} content with the missing header field(s) inserted, or unchanged
* @throws if the type is not one of VALID_TYPES
*/
export function insertHeaderFields(content, meta) {
const s = String(content ?? '');
const m = meta ?? {};
const type = m.type ?? DEFAULT_TYPE;
if (!VALID_TYPES.includes(type)) {
throw new Error(`insertHeaderFields: invalid type '${type}' (expected ${VALID_TYPES.join('|')})`);
}
const isReference = type === 'reference';
const src = m.source;
const hasSource = src !== undefined && src !== null && String(src).trim() !== '';
const needType = parseTypeHeader(s) === null;
const needSource = isReference && hasSource && parseSourceHeader(s) === null;
if (!needType && !needSource) return s;
const toInsert = [];
if (needType) toInsert.push(`**Type:** ${type}`);
if (needSource) toInsert.push(`**Source:** ${String(src).trim()}`);
const lines = s.split('\n');
let anchorIdx = -1; // last line of the first contiguous meta run
let titleIdx = -1; // fallback anchor for the "no meta line" dialect
let cum = 0;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (/^##\s/.test(line) || /^---\s*$/.test(line)) break; // reached body / rule
if (titleIdx === -1 && /^#\s+\S/.test(line)) titleIdx = i;
if (RE_META_LINE.test(line)) {
anchorIdx = i; // extend the contiguous meta run
} else if (anchorIdx !== -1) {
break; // first non-meta line after the run — the run is over (contiguous only)
}
cum += line.length + 1;
if (cum > HEADER_REGION_BYTES) break; // never place fields past the scan window
}
// "None" dialect (no bold-label meta): anchor on the title line so the fields still
// land in the header region, before the first section. (-1 → very top if no title.)
if (anchorIdx === -1) anchorIdx = titleIdx;
lines.splice(anchorIdx + 1, 0, ...toInsert);
return lines.join('\n');
}
/**
* Surgically strip a STALE `**Verified:**` value from a legacy KB file's header the
* bounded Spor 1 carve-out for the 14 files carrying `**Verified:** MCP <date>`. That
* non-date value is read by parseVerifiedHeader as a bogus "verified" stamp, which drops
* the file out of the full-pass worklist AND marks it falsely verified; stripping it is
* the OPPOSITE of stamping verified. Acts ONLY on the first header-region `**Verified:**`
* (the exact occurrence parseVerifiedHeader reads) and ONLY when its value is not a clean
* YYYY-MM(-DD) date. It never touches anything at or below the first `---` rule, so a
* body-duplicate survives (removing body content is NOT part of the blessed byte-identity
* carve-out). On a pipe-delimited meta row it removes only the `**Verified:** <value>`
* token plus one adjacent ` | ` separator, preserving the sibling tokens
* (**Sist oppdatert:** etc.); on a standalone line it drops the whole line. Idempotent.
*
* @param {string} content full existing file content
* @returns {string} content with the stale header Verified removed, or unchanged
*/
export function normalizeStaleVerified(content) {
const s = String(content ?? '');
const head = s.slice(0, HEADER_REGION_BYTES);
const m = /\*\*Verified:\*\*\s*(\S+)/i.exec(head);
if (!m) return s; // no header Verified
if (DATE_RE.test(m[1])) return s; // clean date → leave untouched
const matchStart = m.index; // index of "**Verified:**" in s
// Guard: never act at or below the first `---` rule — body content is off-limits.
const rule = /^---\s*$/m.exec(s);
if (rule && matchStart >= rule.index) return s; // defensive (parser scans header, but be sure)
const lineStart = s.lastIndexOf('\n', matchStart - 1) + 1; // 0 if no preceding newline
let lineEnd = s.indexOf('\n', matchStart);
if (lineEnd === -1) lineEnd = s.length;
const line = s.slice(lineStart, lineEnd);
if (!line.includes(' | ')) {
// Standalone `**Verified:** …` line — drop the whole line, including its newline.
const cutEnd = lineEnd < s.length ? lineEnd + 1 : lineEnd;
return s.slice(0, lineStart) + s.slice(cutEnd);
}
// Pipe row: remove only the `**Verified:** <value>` token + one adjacent ` | `.
const nextSepRel = s.slice(matchStart, lineEnd).indexOf(' | ');
const tokenEnd = nextSepRel === -1 ? lineEnd : matchStart + nextSepRel;
if (s.slice(matchStart - 3, matchStart) === ' | ') {
return s.slice(0, matchStart - 3) + s.slice(tokenEnd); // drop the preceding separator
}
if (nextSepRel !== -1) {
return s.slice(0, matchStart) + s.slice(tokenEnd + 3); // drop the following separator
}
return s.slice(0, matchStart) + s.slice(tokenEnd); // degenerate — token only
}
const RE_TITLE = /^#\s+\S/m;
const RE_LAST_UPDATED = /\*\*Last updated:\*\*\s*[\d-]+/i;
const RE_STATUS = /\*\*Status:\*\*\s*\S/i;