voice-critic · git:20260821.2065537 · 2026-08-21 · sha256 70b82c6677a0161b
voice-critic git:20260821.2065537C
Immutable. This exact content never changes and is served at /api/v1/blob/70b82c6677a0161b.
<!-- Copyright (c) 2026 Petar Djukic. All rights reserved. SPDX-License-Identifier: MIT --> --- name: voice-critic description: >- Cold, read-only gatekeeper against writing-voice/voice-constitution.md: five per-dimension verdicts with flagged spans — stance, theory-of-mind device, disproportion (locked-span check), computed marker profile against idiolect targets, and a snark audit on the constitution's L0-L5 scale with receipt-first, safe-enemy, and per-form density-cap hard rules. Never edits; flags route to the author gate. Triggers: voice critic, does this sound like me, snark audit, constitution check, voice gate, critique voice, off-voice check. --- # Voice Critic (read-only gatekeeper) filter-tells detects "sounds like a machine". This critic detects two different failures: "doesn't sound like Petar" and "went too far". The rubric is `writing-voice/voice-constitution.md` — discovered by the standard walk-up, refused when absent, because the critic's taste is that file, not anything built in. Rates come from `idiolect.yaml`, the single source of truth the constitution itself defers to. **Read-only is the contract.** `scripts/voice_critic.py` takes a document and produces a report; it never modifies the input, and the tests assert input bytes unchanged. That is why it may run after the terminal inject-vernacular stage — the read-only zone of the pipeline — and equally at any earlier gate; nothing in it assumes pipeline position. **The critic flags; the author adjudicates.** It replaces self-monitoring with a checklist, not with enforcement: over-ceiling snark is flagged, never auto-deleted, and every flag carries the quoted span and line range so the gate read is a yes/no per item. ## The five dimensions | dimension | how | what it asks | |---|---|---| | stance | judged | Expert-watching, curiosity not contempt (§1)? At the bench, not the balcony? | | tom-device | screened + judged | Model stated → predictions derived → tested against the artifact (§2)? The deterministic screen finds candidate evidence for each of the three parts; the judge (or the author) says whether the device is executed. | | disproportion | computed | One declared overrun present and protected? Span locks are the declaration mechanism, so this reads the drivers' lock report. | | marker-profile | computed, never judged | idiolect.yaml regex rates against essay targets, ±30% tolerance — the same arithmetic inject-vernacular applies as operators, run here as a check. | | snark-audit | judged instances, computed rules | Every instance scored L0–L5; then the three hard rules run mechanically over the instances. | ## Snark scale and hard rules (constitution §4) Levels: L0 none · L1 dry aside · L2 pointed irony with receipt · L3 open mockery of an artifact (polemic only) · L4 contempt for a class · **L5 ridicule of a person — never**, always a violation. Hard rules, checked mechanically over judge-identified instances: 1. **Receipt-first.** A receipt (number, percentage, citation, quoted material) must appear before the joke — earlier in the same paragraph, or anywhere in the preceding one (the factual-run-then-verdict shape). 2. **Safe-enemy only.** Allowed targets: artifact, institution, category, the dead, past self. A person target is a violation. 3. **Density caps** per 1000 words by form, counting L1 and above: how-to 1, essay 2, polemic 3 (`--form`, default essay). Any hard-rule violation makes the snark dimension FAIL and the flagged instances land in the report for the author gate. ## Judged vs computed, and the judge's leash Computed dimensions are deterministic and run offline. Judged dimensions use a model **in read-only mode**: it returns verdicts, levels, and exact quotes — its text is never spliced anywhere. `--judge` wires an Ollama model (`--model`, `--endpoint`; unreachable-when-requested is a hard error, no silent fallback). Without a judge, judged dimensions report `UNJUDGED` with their screen evidence, and the author gate adjudicates — which is the normal offline mode, not a degraded one. ## Scope boundary with filter-tells - **filter-tells**: machine tells — AI lexicon, structural patterns, CoT leakage. Register-generic; a document by any author can fail it. - **voice-critic**: this author's constitution — stance, method, signature-marker rates, snark governance. A flawlessly human text by someone else fails it; that is the point. Run both: they catch disjoint failures, and after GH-57's calibration work filter-tells reads the same idiolect.yaml so the two never fight over constructions native to the voice. ## Usage ```bash # offline: computed dimensions + screens, judged dimensions UNJUDGED python3 scripts/voice_critic.py draft.md # full: judged dimensions through a local model (read-only) python3 scripts/voice_critic.py draft.md --judge --model gemma4:12b # polemic caps, machine-readable report for the gate checklist python3 scripts/voice_critic.py draft.md --form polemic --json --report out.json ``` Exit 0 when no dimension FAILs, 1 otherwise. Tests: `scripts/test_voice_critic.py` (offline; stub judges, no network).