Slice 2 av fix #1 («rettelser fester seg»): binder rules.ts til den menneskelige sannhetskilden (skrivekontrakt §E), så en korreksjon gjort én gang håndheves for alltid i stedet for å gjenoppdages hver utgave (diagnose-gap 2). - ratify.ts: importerer RULES direkte (ingen skjør tekst-parsing), krever en bijeksjon mot §E gate-bindings-manifestet (id ↔ ref ↔ severity) + dangling- §-anker-sjekk. CLI: `--ratify [kontrakt]` / `npm run ratify`. Sti: arg > env MASKINROMMET_CONTRACT > default (relativt til modulen). - edition-state.template: foldIns[] fangst-kø (per-artikkel proveniens, pending|promoted|rejected, mekanisk→rules.ts / dømmekraft→§C2). - 12 nye tester (33 totalt), tsc rent. Live-kontrakt-integrasjonstest ratify-er det ekte §E-manifestet rent (10 regler bundet 1:1). Companion: skrivekontrakt §E (JA-promoter-prosedyre + manifest) i maskinrommet. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
95 lines
3.4 KiB
JavaScript
95 lines
3.4 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* CLI for the §B/§C1 mechanical binding-gate.
|
|
*
|
|
* node --import tsx src/cli.ts <utkast.md> [--json]
|
|
*
|
|
* Exit code: 0 if the gate passes (no BLOCK violations — handoff permitted),
|
|
* 1 if BLOCKED, 2 on usage error. WARN violations never fail the gate; they are
|
|
* surfaced for KTG's judgment. The text output is the "kvittering" the writing
|
|
* contract asks for when a draft is delivered.
|
|
*/
|
|
|
|
import { runGateFile } from "./gate.js";
|
|
import { ratifyFile, defaultContractPath } from "./ratify.js";
|
|
import type { RatifyResult } from "./ratify.js";
|
|
import type { GateResult, Violation } from "./types.js";
|
|
|
|
function printViolation(v: Violation): void {
|
|
console.log(` [${v.ref}] ${v.ruleId}`);
|
|
if (v.observed !== undefined) {
|
|
const t = v.threshold !== undefined ? ` (terskel: ${v.threshold})` : "";
|
|
console.log(` observert: ${v.observed}${t}`);
|
|
}
|
|
console.log(` → ${v.message}`);
|
|
const shown = v.matches.slice(0, 5);
|
|
for (const m of shown) console.log(` · L${m.line}: ${m.excerpt}`);
|
|
if (v.matches.length > shown.length) {
|
|
console.log(` · … +${v.matches.length - shown.length} til`);
|
|
}
|
|
}
|
|
|
|
function report(r: GateResult): void {
|
|
console.log("§B/§C1 regelsjekk-gate");
|
|
console.log(`Utkast: ${r.draftPath ?? "(stdin)"} · ${r.wordCount} ord\n`);
|
|
if (r.blocks.length) {
|
|
console.log(`BLOCK (${r.blocks.length}) — må fikses før handoff:`);
|
|
r.blocks.forEach(printViolation);
|
|
console.log("");
|
|
}
|
|
if (r.warns.length) {
|
|
console.log(`WARN (${r.warns.length}) — krever din dom (blokkerer ikke):`);
|
|
r.warns.forEach(printViolation);
|
|
console.log("");
|
|
}
|
|
if (r.passed) {
|
|
console.log(`✓ Gate passert — 0 block, ${r.warns.length} warn til vurdering.`);
|
|
} else {
|
|
console.log(
|
|
`✗ BLOKKERT — ${r.blocks.length} block, ${r.warns.length} warn. Fiks block-ene og kjør på nytt.`,
|
|
);
|
|
}
|
|
}
|
|
|
|
function reportRatify(r: RatifyResult): void {
|
|
console.log("Gate-bindings-ratify (rules.ts ↔ §E-manifest)");
|
|
console.log(
|
|
`Kontrakt: ${r.contractPath ?? "(ukjent)"} · ${r.ruleCount} regler / ${r.manifestCount} manifest-rader\n`,
|
|
);
|
|
if (r.issues.length) {
|
|
console.log(`DRIFT (${r.issues.length}):`);
|
|
for (const i of r.issues) console.log(` [${i.kind}] ${i.id}\n → ${i.detail}`);
|
|
console.log("");
|
|
console.log("✗ Ute av sync — rules.ts og §E-manifestet stemmer ikke. Rett før promotering teller.");
|
|
} else {
|
|
console.log(`✓ I sync — ${r.ruleCount} regler bundet 1:1 til §E-manifestet (ref + severity stemmer).`);
|
|
}
|
|
}
|
|
|
|
function runRatify(args: string[], jsonOut: boolean): never {
|
|
const argPath = args.find((a) => !a.startsWith("--"));
|
|
const path = argPath ?? process.env.MASKINROMMET_CONTRACT ?? defaultContractPath();
|
|
const result = ratifyFile(path);
|
|
if (jsonOut) console.log(JSON.stringify(result, null, 2));
|
|
else reportRatify(result);
|
|
process.exit(result.ok ? 0 : 1);
|
|
}
|
|
|
|
function main(): void {
|
|
const args = process.argv.slice(2);
|
|
const jsonOut = args.includes("--json");
|
|
if (args.includes("--ratify")) {
|
|
runRatify(args, jsonOut);
|
|
}
|
|
const path = args.find((a) => !a.startsWith("--"));
|
|
if (!path) {
|
|
console.error("Bruk: contract-gate <utkast.md> [--json] | contract-gate --ratify [kontrakt.md] [--json]");
|
|
process.exit(2);
|
|
}
|
|
const result = runGateFile(path);
|
|
if (jsonOut) console.log(JSON.stringify(result, null, 2));
|
|
else report(result);
|
|
process.exit(result.passed ? 0 : 1);
|
|
}
|
|
|
|
main();
|