feat(ms-ai-architect): Spor 1 — insertHeaderFields + surgical normalizeStaleVerified (3 dialekter, pipe-safe, ---aware, TDD) [skip-docs]
This commit is contained in:
parent
f8e420c773
commit
6b58afda3b
2 changed files with 338 additions and 0 deletions
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue