#!/usr/bin/env node /** * CLI for the §B/§C1 mechanical binding-gate. * * node --import tsx src/cli.ts [--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 [--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();