feat(ms-ai-architect): Spor 3 Port 2 — create-time-guard (born-verified contract) [skip-docs]
Type-aware create-guard for reference-filer (Spor 3 Port 1/Port 2): - kb-headers.mjs: parseTypeHeader/parseVerifiedHeader/parseVerifiedByHeader (samme top-500-byte bold-label-skann som parseSourceHeader). - transform.mjs: buildKbHeader er type-aware (reference krever source+verified+verified_by; non-reference kaster pa MS-Learn-source) + emitterer Type/Verified/Verified by; validateKbFile type-aware; stampVerifiedMeta = fodt-verifisert-gate (stempler KUN ved bestatt judge-verdikt, ellers kaster). - validate-kb-file.mjs (ny): kjorbar gate generatorer kaller for commit (exit != 0 ved kontraktbrudd). - generate-skills.md + kb-update.md + transform-prompt.md: wiret til fodt-verifisert kontrakt (header-felt + judge-steg + create-guard-gate). Suite 586/586 (33 nye + 1 invariant). Plugin-validering 239/0/0. Utenfor scope (flagget): generate-skills.sh legacy (sonnet/Cosmo/no-source), Cosmo-persona i generatorene (S-Cosmo), korpus-migrering av 306 filer (Spor 1).
This commit is contained in:
parent
0ded2f9413
commit
5a0ba1fc9e
10 changed files with 621 additions and 54 deletions
69
scripts/kb-update/validate-kb-file.mjs
Normal file
69
scripts/kb-update/validate-kb-file.mjs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#!/usr/bin/env node
|
||||
// validate-kb-file.mjs — the executable create-time-guard (Spor 3 Port 2).
|
||||
//
|
||||
// The gate any generator (generate-skills, kb-update) calls AFTER writing a candidate
|
||||
// KB file and BEFORE committing it. It wraps transform.validateKbFile over one or more
|
||||
// paths and exits NON-ZERO if any file violates the contract — so a regenerated file
|
||||
// that lost its Source, is not born-verified, or dropped its TOC can never ship. This
|
||||
// is what makes "create-guard MUST be in place before Spor 1 corpus-fixing" enforceable:
|
||||
// the generator physically cannot re-introduce the drift the corpus pass is removing.
|
||||
//
|
||||
// Usage:
|
||||
// node scripts/kb-update/validate-kb-file.mjs <file.md> [<file2.md> ...]
|
||||
//
|
||||
// Exit code: 0 = all files valid; 1 = at least one violation (or usage error).
|
||||
|
||||
import { readFileSync, realpathSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { validateKbFile } from './lib/transform.mjs';
|
||||
|
||||
/**
|
||||
* Validate a batch of candidate KB files. Pure + dependency-injected (readFile) so it is
|
||||
* unit-testable without disk. A path that cannot be read is reported as invalid (with a
|
||||
* `read-error` marker) rather than throwing — one unreadable file must not crash the gate.
|
||||
*
|
||||
* @param {string[]} paths
|
||||
* @param {(p: string) => string} [readFile] — reader (defaults to readFileSync utf8)
|
||||
* @returns {{ok: boolean, results: Array<{path: string, valid: boolean, missing: string[]}>}}
|
||||
*/
|
||||
export function validatePaths(paths, readFile = (p) => readFileSync(p, 'utf8')) {
|
||||
const results = (paths ?? []).map((path) => {
|
||||
let content;
|
||||
try {
|
||||
content = readFile(path);
|
||||
} catch (err) {
|
||||
return { path, valid: false, missing: [`read-error: ${err.message}`] };
|
||||
}
|
||||
const { valid, missing } = validateKbFile(content);
|
||||
return { path, valid, missing };
|
||||
});
|
||||
return { ok: results.every((r) => r.valid), results };
|
||||
}
|
||||
|
||||
function main(argv) {
|
||||
const paths = argv.slice(2);
|
||||
if (paths.length === 0) {
|
||||
process.stderr.write('usage: validate-kb-file.mjs <file.md> [<file2.md> ...]\n');
|
||||
process.exit(1);
|
||||
}
|
||||
const { ok, results } = validatePaths(paths);
|
||||
for (const r of results) {
|
||||
if (r.valid) {
|
||||
process.stdout.write(`OK ${r.path}\n`);
|
||||
} else {
|
||||
process.stdout.write(`FAIL ${r.path} — missing: ${r.missing.join(', ')}\n`);
|
||||
}
|
||||
}
|
||||
process.exit(ok ? 0 : 1);
|
||||
}
|
||||
|
||||
// Robust direct-invocation check (mirrors detect-courses.mjs): only run main when this
|
||||
// file is the entrypoint, not when imported by a test.
|
||||
const isMain = (() => {
|
||||
try {
|
||||
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
if (isMain) main(process.argv);
|
||||
Loading…
Add table
Add a link
Reference in a new issue