diff --git a/plugins/linkedin-studio/agents/content-reviewer.md b/plugins/linkedin-studio/agents/content-reviewer.md index 060c2d6..b598994 100644 --- a/plugins/linkedin-studio/agents/content-reviewer.md +++ b/plugins/linkedin-studio/agents/content-reviewer.md @@ -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 C1–C5 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; diff --git a/plugins/linkedin-studio/agents/editorial-reviewer.md b/plugins/linkedin-studio/agents/editorial-reviewer.md index d6284c7..33f6e79 100644 --- a/plugins/linkedin-studio/agents/editorial-reviewer.md +++ b/plugins/linkedin-studio/agents/editorial-reviewer.md @@ -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 diff --git a/plugins/linkedin-studio/agents/language-reviewer.md b/plugins/linkedin-studio/agents/language-reviewer.md index eca517a..8a511ac 100644 --- a/plugins/linkedin-studio/agents/language-reviewer.md +++ b/plugins/linkedin-studio/agents/language-reviewer.md @@ -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 diff --git a/plugins/linkedin-studio/agents/voice-scrubber.md b/plugins/linkedin-studio/agents/voice-scrubber.md index 339ae32..c0bbca0 100644 --- a/plugins/linkedin-studio/agents/voice-scrubber.md +++ b/plugins/linkedin-studio/agents/voice-scrubber.md @@ -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. diff --git a/plugins/linkedin-studio/commands/newsletter.md b/plugins/linkedin-studio/commands/newsletter.md index e7d4345..7fbdb45 100644 --- a/plugins/linkedin-studio/commands/newsletter.md +++ b/plugins/linkedin-studio/commands/newsletter.md @@ -1041,21 +1041,26 @@ command runs — see `commands/headless-review.md` for the full cold contract): ``` Record the frozen path; pass *that* path (not the live draft) to every reviewer. -2. **Resolve the cold inputs.** The writing contract (`/../../docs/skrivekontrakt.md` - → plugin mirror → `references/longform-quality-rules.md`) and the active +2. **Resolve the cold inputs.** The **review language** (`edition-state.language`, + default `en` — tells `language-reviewer` and `voice-scrubber` which language's + rules to grade against; Norwegian-specific checks fire only when `language: no`), + the writing contract *if it ships* (`/../../docs/skrivekontrakt.md` → + plugin mirror → `references/longform-quality-rules.md`; absent for an adopter, + the craft agents' in-tree checklists are self-contained), and the active personas (`articles.NN.personas`, primær identified). Nothing else. 3. **Fan out the five archetypes in parallel** — issue them in a SINGLE message (multiple `Task` tool-uses) from THIS command layer in the foreground (principle 4), `subagent_type` namespaced: - `linkedin-studio:content-reviewer` — argument integrity (C1–C5) - - `linkedin-studio:language-reviewer` — Norwegian language (L1–L5) + - `linkedin-studio:language-reviewer` — language quality (L1–L5; grades against `edition-state.language`, Norwegian-specific rules when `language: no`) - `linkedin-studio:fact-reviewer` — cold re-verification (F1–F4, 🔴/🟡/🟢, incl. pivot premises) - `linkedin-studio:persona-reviewer` `mode: resonans` — **one call per active persona** - `linkedin-studio:persona-reviewer` `mode: konverter` — **primær only** (hook) Each call's prompt carries ONLY the cold-contract inputs (frozen draft path, - contract path, persona for the persona modes) + the instruction to ignore any + `language`, contract path if it ships, persona for the persona modes) + the + instruction to ignore any framing about prior versions / cuts / pivots. **Never** paste history or summarize "what we changed" into a reviewer prompt — that is the context pollution the package exists to eliminate. diff --git a/plugins/linkedin-studio/config/edition-state.template.json b/plugins/linkedin-studio/config/edition-state.template.json index 7354ce4..799b070 100644 --- a/plugins/linkedin-studio/config/edition-state.template.json +++ b/plugins/linkedin-studio/config/edition-state.template.json @@ -27,13 +27,15 @@ "visualAssets": "Per-article visual-asset record written by Step 7.5 (visual-assets phase). Runs BEFORE lock because render/build-linkedin.mjs picks up linkedin/NN/cover.png + the edition-config credit/caption when it builds POST.html — generating images after lock would force a re-render. Shape: { format: \"standard\" | \"carousel\"; cover: { brief, route, candidates[], approved, status }; figures: [ { id, brief, placement, status } ]; carousel: null | { source, pdf, status } }. format \"standard\" = cover + optional inline figures (cover.png is mandatory per the KTG cover-directive); format \"carousel\" = typografisk deck via render/build-carousel.mjs instead of cover+inline (cover/figures stay empty). route: \"mcp-image\" (default, via mcp__mcp-image__generate_image) | \"external\" (DALL·E / Midjourney / photographer → linkedin/NN/cover-raw.png). status ladder: pending → briefed → generated → approved. candidates[] holds the cover-v-kandidat.png attempts; approved is the fixed approved name (\"cover.png\") once the operator-gate passes. figures[].id = \"fig1\"..; placement = section reference in NN-utkast.md (figures are referenced in the draft via ![alt](linkedin/NN/figN.png) and uploaded manually in the LinkedIn editor — build-linkedin.mjs does NOT embed them). Naming convention: cover.png (approved, fixed — what build-linkedin.mjs reads) | cover-v-kandidat.png (attempts) | cover-raw.png (optional external pre-edit source) | fig.png (inline). credit + caption are recorded in /linkedin/image-credit-caption.md and flow into edition-config.json coverCredit + captions[NN].", "personas": "Per-article resolved reader-persona set (input config), written/confirmed in Step 1. This makes personas configurable PER ARTIFACT, not just from one global plugin library: Step 1 resolves them in order — (1) already present here → use as-is; (2) /linkedin/personas.md (per-series file) → load; (3) plugin config/personas.local.md (or personas.template.md) library → select a subset; (4) none/insufficient → DEFINE interactively via AskUserQuestion. Exactly one entry has tier \"primær\" (the rest \"sekundær\"); «primær trumfer» on conflict. This set feeds BOTH the in-session sweep (Step 6) and the headless package (Step 6.5 / persona-reviewer). Each entry: { name, tier: \"primær\" | \"sekundær\", rolle, avkobler, overbeviser, ekspertise, sjargong, source: \"edition-state\" | \"series-file\" | \"plugin-library\" | \"interactive\" }. Default []: resolved on first Step 1.", "headlessReview": "Per-article headless-review record written by Step 6.5 (headless-review phase). Runs AFTER the in-session persona sweep (Step 6) and BEFORE lock (Step 8), on a FROZEN snapshot of the publish-ready (or pivoted) draft, fanned out from the command layer (foreground) or invoked standalone via /linkedin:headless-review in a fresh/cold session. Five archetypes judge independently with NO drafting-session context: content-reviewer (argument integrity), language-reviewer (Norwegian language), fact-reviewer (cold re-verification incl. claims a late pivot bolted on), persona-reviewer mode=resonans (per active persona), persona-reviewer mode=konverter (primær, hook only). The consolidated report is surfaced to the operator via SendUserFile; the operator decides which flags fold in. Shape: { frozenDraft, reviewers: { content, language, fact, personaResonance, personaConversion } (each { reportPath, summary, status }), consolidatedReport, foldedIn, waived, status }. status ladder: pending → run → folded. null until Step 6.5 runs. This is the adversarial-independence companion to the in-session gates (editorialReview, personaSweep, factcheckLog) — deliberately redundant: a cold reader catches what the framing-biased in-session pass missed.", - "pivots": "Per-article pivot log (Endring 9c). A pivot is a substantive change to a draft AFTER a gate had already cleared — e.g. a new argument anchor / section added late (the Del 4 Security Champions case: +~530 words, 2 new sections, +42 %). Each /linkedin:pivot invocation appends one entry and moves currentPhase back so the cleared gates (Steps 5–6.5) re-run on the pivoted version before lock. Heuristic (documented, checked at the Step 8 lock precondition): if the current draft's word count differs > 20 % from the version that last cleared Step 6, OR it has > 2 new sections, a pivot-reopen is suggested/required. Each entry: { timestamp, reason, fromPhase, toPhase, wordCountBefore, wordCountAfter, deltaPct, newSections, gatesToRerun: [phase…] }. Default []." + "pivots": "Per-article pivot log (Endring 9c). A pivot is a substantive change to a draft AFTER a gate had already cleared — e.g. a new argument anchor / section added late (the Del 4 Security Champions case: +~530 words, 2 new sections, +42 %). Each /linkedin:pivot invocation appends one entry and moves currentPhase back so the cleared gates (Steps 5–6.5) re-run on the pivoted version before lock. Heuristic (documented, checked at the Step 8 lock precondition): if the current draft's word count differs > 20 % from the version that last cleared Step 6, OR it has > 2 new sections, a pivot-reopen is suggested/required. Each entry: { timestamp, reason, fromPhase, toPhase, wordCountBefore, wordCountAfter, deltaPct, newSections, gatesToRerun: [phase…] }. Default [].", + "language": "Review language for this series/edition (additive, default \"en\"). Threads into the long-form review agents so they grade against THIS language's rules: language-reviewer applies Norwegian-specific checks (anglicism→Norwegian idiom, «kanselli-stil») only when language == \"no\"; voice-scrubber's gold standard is the approved editions IN this language; any other value → the agents apply that language's equivalents and never grade prose against Norwegian idiom. \"no\" = Norwegian (the author's case). Resolved at Step 1 / load-context and passed to the language-dependent agents." }, "schemaVersion": 1, "series": { "slug": "", "title": "" }, + "language": "en", "currentArticle": "01", "currentPhase": "load-context", "updatedAt": "",