Adds scripts/contract-gate/ — a deterministic, AI-free gate that enforces the mechanical half of the Maskinrommet writing contract (§B red flags + §C1 regelsjekk) BEFORE operator handoff, so the same mechanical correction never reaches KTG twice. First slice of fix #1 (binding + accumulating); target re-scoped to close all 4 diagnosis gaps = sustainable newsletter workflow. - Structured ruleset as DATA (src/rules.ts) so accumulation (slice 2) appends. - 3 BLOCK (modell-strawman, series-thesis "er ikke modellen", _underscore_) + 7 WARN (judgment-needed). Block only on provable, ~zero-FP matches. - Code/frontmatter masking; line-accurate reporting; CLI receipt + exit code. - 21/21 tests (node:test), tsc clean. - Validated on 6 real editions: 0 BLOCK on published Del 2-5; caught a real Rule-10 violation in Del 6 (WIP) automatically. Versal-tic uses an emphasis-word denylist after real-data FPs (NAV/DFØ/NPM). Finding: the plugin already mirrors §A + §C2; §C1 was the unhomed gap this fills. Plugin structure lint unchanged (81/0/0); no new agents/commands/refs/skills.
141 lines
5.4 KiB
TypeScript
141 lines
5.4 KiB
TypeScript
/**
|
||
* Seed ruleset for the §B/§C1 mechanical binding-gate.
|
||
*
|
||
* Each rule transcribes a mechanically-checkable item from the Maskinrommet
|
||
* writing contract (maskinrommet/docs/skrivekontrakt.md). The classification
|
||
* principle (slice 1): BLOCK only what the gate can PROVE with ~zero false
|
||
* positives (exact forbidden phrases, markdown syntax); WARN on grep-flags that
|
||
* still need human/AI judgment (a word that *might* be a metaphor, a bare
|
||
* "modell" that *might* be neutral). Judgment-only / external-data rules
|
||
* (research/ traceability, term definitions, narrative architecture, the full
|
||
* AI-tell list) are intentionally NOT here — they stay with voice-scrubber
|
||
* (tier 2) and editorial-reviewer (tier 3).
|
||
*
|
||
* Deferred to a later slice (documented gaps, not silent omissions):
|
||
* - §C2 "postulerte tall" (numbers without hedge/source) — needs a
|
||
* hedge-proximity heuristic with non-trivial false-positive risk.
|
||
* - §C2 "ordrette gjentakelser" (any n-gram repeated >2×) — needs n-gram
|
||
* analysis; the contract's named phrases are edition-specific.
|
||
*/
|
||
|
||
import type { Rule } from "./types.js";
|
||
|
||
export const RULES: Rule[] = [
|
||
// ── BLOCK: provable, ~zero false positives ─────────────────────────────
|
||
{
|
||
id: "B3-modell-referansepunkt",
|
||
ref: "§B-3 / §C1",
|
||
severity: "block",
|
||
kind: "literal",
|
||
patterns: [
|
||
"det er ikke modellen som",
|
||
"ikke en bedre modell",
|
||
"modellen er god nok",
|
||
"uten å bytte modell",
|
||
],
|
||
message:
|
||
"Modell-stråmann (regel 3, KTGs mest gjentatte korreksjon): ingen påstand om at modellen er problemet/for svak. Skriv om til vår evne, vilje og kompetanse til å hente ut potensialet.",
|
||
},
|
||
{
|
||
id: "B10-serie-tese",
|
||
ref: "§B-10 / §C1",
|
||
severity: "block",
|
||
kind: "literal",
|
||
patterns: ["er ikke modellen"],
|
||
message:
|
||
"Serie-tesen / meta-tese (regel 10, korollar til regel 3): «… er ikke modellen» er den tause linsen, aldri en setning vi leverer. Positiv konkret landing («menneskene … er den knappe ressursen») er OK — den abstrakte meta-tesen er ikke.",
|
||
},
|
||
{
|
||
id: "understrek-emfase",
|
||
ref: "§C1 / hygiene",
|
||
severity: "block",
|
||
kind: "regex",
|
||
// Markdown underscore-emphasis: opening _ at a boundary, closing _ before
|
||
// whitespace/punctuation. snake_case (underscores between word chars) is
|
||
// NOT matched, so identifiers in prose don't false-positive.
|
||
patterns: ["(^|[\\s(])_[^_\\n]+_(?=[\\s).,!?:;]|$)"],
|
||
message:
|
||
"Emfase med _understrek_ rendrer rått (hygiene). Bruk **fet** (eller *kursiv* med stjerne).",
|
||
},
|
||
|
||
// ── WARN: grep-flag, needs human/AI judgment ───────────────────────────
|
||
{
|
||
id: "B2-metafor",
|
||
ref: "§B-2",
|
||
severity: "warn",
|
||
kind: "regex",
|
||
patterns: ["\\b(tak|lodd|reise|landskap)\\b"],
|
||
message:
|
||
"Mulig metafor som bærer argumentet (regel 2). Bekreft at bruken er konkret/nøytral, ikke utvidet bildespråk («et usynlig tak» → «KI-en når ikke dataene»).",
|
||
},
|
||
{
|
||
id: "B3-modell-bar",
|
||
ref: "§B-3",
|
||
severity: "warn",
|
||
kind: "count",
|
||
threshold: 0,
|
||
patterns: ["\\bmodell"],
|
||
message:
|
||
"«modell» nevnt — sjekk at HVERT treff er nøytral faktabruk («en KI-modell er trent på …»), ikke modellen brukt som referansepunkt/stråmann (regel 3).",
|
||
},
|
||
{
|
||
id: "KI-en-subjekt",
|
||
ref: "hygiene ★",
|
||
severity: "warn",
|
||
kind: "count",
|
||
threshold: 3,
|
||
patterns: ["\\bKI-en\\b"],
|
||
message:
|
||
"«KI-en» gjentatt som handlende subjekt er dårlig norsk (språk-hygiene). Varier: «KI», «verktøyet», eller omskriv («data KI har tilgang til»).",
|
||
},
|
||
{
|
||
id: "B9-PS",
|
||
ref: "§B-9 / §C1",
|
||
severity: "warn",
|
||
kind: "regex",
|
||
patterns: ["(^|\\n)\\s*P\\.?\\s?S\\.?\\b"],
|
||
message:
|
||
"PS/etter-closing-blokk (regel 9). Sjekk at den ikke åpner nytt begrep/rammeverk etter handlingskallet — leseren skal lukke på ett konkret handlingsvalg.",
|
||
},
|
||
{
|
||
id: "C2-tankestrek-tetthet",
|
||
ref: "§C2",
|
||
severity: "warn",
|
||
kind: "density",
|
||
threshold: 1,
|
||
per: 50,
|
||
patterns: ["\\u2014"], // em dash —
|
||
message:
|
||
"Tankestrek-tetthet over ~1 per 50 ord (§C2). Bytt halvparten med komma eller punktum; innskudd-formelen «X — Y — Z» bør være sjelden.",
|
||
},
|
||
{
|
||
id: "C2-versal-tic",
|
||
ref: "§C2",
|
||
severity: "warn",
|
||
kind: "regex",
|
||
// Case-sensitive, Unicode-aware (handles MÅ/NÅ/SÅ). DENYLIST, not allowlist:
|
||
// flag only UPPERCASE forms of known Norwegian emphasis words — NOT every
|
||
// 2+-uppercase run. Acronyms (NPM, DFØ, NAV) are not emphasis words, so they
|
||
// never flag. (Real-data finding on Del 4/5: an acronym allowlist can't keep
|
||
// up; the emphasis-word set is small and stable.)
|
||
flags: "gu",
|
||
patterns: [
|
||
"(?<!\\p{L})(OG|IKKE|IKKJE|MEN|MÅ|SKAL|ALDRI|ALLTID|ALLE|INGEN|BARE|KUN|NÅ|SÅ|HELE|ENHVER)(?!\\p{L})",
|
||
],
|
||
message:
|
||
"Versal-tic (§C2): emfase-ord i VERSALER midt i prosa («OG», «IKKE», «MEN») bryter kronikk-stilen. Bruk *kursiv* eller omformuler.",
|
||
},
|
||
{
|
||
id: "B8-ai-tells",
|
||
ref: "§B-8",
|
||
severity: "warn",
|
||
kind: "regex",
|
||
patterns: [
|
||
"la meg være ærlig",
|
||
"i en verden der",
|
||
"det handler ikke om .{1,40}? det handler om",
|
||
],
|
||
message:
|
||
"AI-tell (regel 8). Fjern/omformuler. Full liste: auto-memory `no-ai-tell-phrases`.",
|
||
},
|
||
];
|