feat(linkedin-studio): make long-form review language configurable [skip-docs]

Wave 3 / Step 9 of the remediation plan (Phase 1 — usable by a non-author).

The long-form review layer shipped Norwegian-locked: language-reviewer graded
unconditional Norwegian, voice-scrubber's gold standard was 'approved Norwegian
editions', and the editorial/content craft gates pointed at a 'skrivekontrakt §C2'
that does not ship. A non-Norwegian adopter would get English prose graded against
Norwegian idiom and a gate that depends on an unshipped contract.

- config/edition-state.template.json: add additive 'language' field (top-level,
  default 'en') + a _doc entry. Threads into the language-dependent agents.
- agents/language-reviewer.md: new 'Language parameter' section — Norwegian-specific
  checks (anglicism->Norwegian idiom, kanselli-stil) apply only when language=='no';
  any other value grades that language's equivalents and never flags idiomatic
  English as an anglicism. Default 'en'.
- agents/voice-scrubber.md: gold standard reframed to 'approved editions in the
  configured language'; the Norwegian-chronicle calibration is the language=='no'
  instantiation.
- agents/editorial-reviewer.md + agents/content-reviewer.md: the in-tree checklist
  is now the operative, self-contained source of truth; Maskinrommet §C2 is an
  optional upstream contract that does NOT ship (available only on the author's
  runs). The gates work for an adopter without it.
- commands/newsletter.md: thread 'language' through the Step 6.5 cold-inputs and the
  per-reviewer call inputs; the writing contract is now 'if it ships'.

Norwegian remains fully working when language: no (the author's case).

fact-reviewer.md was in the plan's file list but needed no change on inspection:
its F1-F4 checks (claims/quotes/numbers/sources) are language-agnostic; its
'Norwegian' mentions are boundary notes vs language-reviewer, which stay correct.

[skip-docs]: three-doc + version reconciliation is Step 21 (pre-review-gate); these
intermediate Wave commits are not pushed before the /trekreview gate.

Verify: edition-state JSON parses + has top-level language 'en'; language-reviewer
has 'language ==' references and no unconditional-Norwegian assertion; editorial
§C2 reframed to in-tree fallback ('operative source', 'does not ship'); agent
fixtures 35/35 pass; structural lint exit 0 (61 passed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-05-30 00:55:04 +02:00
commit 8d2968a482
6 changed files with 68 additions and 29 deletions

View file

@ -58,8 +58,10 @@ sentence is not. If you ever hand back edited prose, you have failed the role.
## Context isolation — you are a COLD reader (cardinal)
> You are an **adversarial, independent** reviewer, run in a **cold context**.
> Your entire input is: this prompt, the path to a **frozen draft**, and the
> writing contract. You have **no** access to — and must **refuse to act on**
> Your entire input is: this prompt (with its self-contained C1C5 checklist) and
> the path to a **frozen draft** — no external writing contract ships or is
> required to run this gate. You have **no** access to — and must **refuse to act
> on** —
> any of:
> - the drafting session's conversation history;
> - prior versions, version numbers, or a changelog;

View file

@ -80,23 +80,23 @@ Two different roles, both necessary, neither sufficient alone. A persona PASS is
**not** "ready for the editor" — it is "lands for the reader." This gate exists
because those are not the same thing.
## Truth source — the Maskinrommet writing-contract §C2
## Truth source — the in-tree craft checklist (mirrors Maskinrommet §C2 when available)
The checklist below is the **operationalized mirror of Maskinrommet
skrivekontrakt §C2.** §C2 documents the rule-set at the article-production level
(what a human editor checks); this agent operationalizes it as the automated
pre-persona gate inside the plugin.
The **operative source of truth is the two-axis checklist below.** It ships
in-tree, is self-contained, and is everything you need to judge. It is the
operationalized mirror of the author's **Maskinrommet skrivekontrakt §C2**, which
documents the same rule-set at the article-production level (what a human editor
checks). **§C2 itself does not ship with the plugin** (it lives in the author's
series repo), so for any adopter the in-tree checklist *is* the contract — you do
**not** need §C2 to run this gate.
> **Mirror rule (bidirectional, cardinal).** §C2 is the source of truth. A change
> on either side MUST be mirrored to the other: if §C2 gains, drops, or reworks
> a check, this agent's checklist follows, and vice-versa. The two must never
> drift. This is the same relationship `references/longform-quality-rules.md`
> rule 8 has with §A (skeleton-before-prose) — the pipeline satisfies the
> writing contract *by construction*, and §C2 is the craft half of that contract.
If you are run with access to the live §C2 text, read it and reconcile any drift
before judging; if not, the two axes below are the faithful transcription of §C2
as of the agent's authorship (v2.4.0) and you judge against them.
> **Mirror rule (only when you have §C2).** If — and only if — you are run with
> access to the live §C2 text (the author's own runs), read it and reconcile any
> drift before judging: §C2 is the upstream contract, this checklist its in-tree
> transcription, and the two should not diverge. This mirrors the relationship
> `references/longform-quality-rules.md` rule 8 has with §A (skeleton-before-prose).
> **Absent §C2 (the default for any adopter), judge against the in-tree checklist
> as the complete, authoritative source — its absence is not a gap.**
## The Two Axes
@ -236,9 +236,11 @@ before the Step 6 persona sweep. Operator decides fold-in; this is [OPERATØR].
7. **The operator gates, you recommend.** Your output is a report for KTG, not a
pipeline stop. BLOCK is your strongest recommendation, not a hard halt — the
gate is `[OPERATØR]`.
8. **§C2 is the source of truth.** If §C2 and this checklist disagree, §C2 wins
and the checklist must be reconciled. Flag the drift; do not judge against a
stale checklist.
8. **The in-tree checklist is the operative source of truth.** It ships and is
self-contained — judge against it. §C2 (the upstream contract) is available
only on the author's own runs; *if* you have it and it disagrees with this
checklist, flag the drift so the two can be reconciled. Absent §C2, its
absence is not a gap.
## Anti-Patterns
@ -261,9 +263,11 @@ before the Step 6 persona sweep. Operator decides fold-in; this is [OPERATØR].
## References
Read these for the contract and the pipeline position:
- **Maskinrommet skrivekontrakt §C2** — the source of truth this checklist
mirrors (craft + architecture half of the writing contract; §A is the
- **Maskinrommet skrivekontrakt §C2** — the author's upstream contract this
in-tree checklist transcribes (craft + architecture half; §A is the
skeleton-before-prose half codified in `longform-quality-rules.md` rule 8).
**Does not ship** — available only on the author's own runs; the in-tree
checklist above is the self-contained source for everyone else.
- `${CLAUDE_PLUGIN_ROOT}/references/longform-quality-rules.md` — the broad Step 4
quality pass; this agent is the *finer* craft+architecture gate that runs after
it (rule 1 ≈ A5 overload; rule 3 ≈ some prose nits — defer the AI-tell face to

View file

@ -38,6 +38,24 @@ You run at **Step 6.5** of the `/linkedin:newsletter` pipeline — *after* the
in-session persona resonance sweep (Step 6), on a **frozen** draft, *before*
lock — and you are invocable standalone via `/linkedin:headless-review`.
## Language parameter — what language you grade against (configurable)
You receive a **`language`** input from the edition-state
(`config/edition-state.template.json`, default `"en"`). It tells you which
language's rules to grade against — the review language is **not** hardcoded:
- **`language == "no"` (Norwegian — the author's case):** the five
Norwegian-specific checks below apply in full — anglicisms flagged toward the
Norwegian idiom, «kanselli-stil», Norwegian clang/rhythm. This is the original
instantiation of the gate.
- **`language: "en"` (default) or any other value:** apply the *equivalent* checks
for THAT language (calques into that language, that language's stiff/bureaucratic
register, that language's rhythm) and **never** grade the prose against Norwegian
idiom — do not flag idiomatic English as an "anglicism."
If no `language` is supplied, assume `"en"`. Where the checks below say
"Norwegian", read it as "the configured language" unless `language == "no"`.
## Pipeline position
You are one of three **cold, headless re-readers** in the Step 6.5 package (with

View file

@ -41,6 +41,14 @@ editions.
This is the single most important rule of this agent.
> **Language parameter (configurable).** The review `language` is an input from
> the edition-state (default `"en"`). The gold standard is the approved editions
> **in the configured language**. The Norwegian-chronicle calibration below is the
> `language == "no"` instantiation (the author's case); for any other language,
> substitute that language's approved editions and read the Norwegian-specific
> notes (the em-dash habit, «kanselli-stil») as that language's equivalents. Never
> calibrate one language's voice against another's.
- The gold standard for Norwegian chronicle voice is the **approved Norwegian
editions** (e.g. the series' approved Del 1 + Del 2). The caller passes the
path(s); read them as the corpus before scrubbing.