Two new pipeline phases gate the spine before any prose is written: - Step 2.5 — Skeleton + section pitch: writes <serie>/NN-skjelett.md with the five-line spine (premiss / problem / anbefaling / gevinst / vei videre) + one-line section pitches. Operator-gate (JA / REVIDER / NEI) AND parallel persona-skjelett-sweep must both return JA before the pipeline can advance. - Step 3a — Spine prose: one paragraph per section against the gated skeleton, ~20-30% of final edition length. Operator-gate on whether the axis lands now that there is prose on it. Old Step 3 (Draft) split into 3a (spine) and 3b (full expansion); 3b owns the multi-session draft-cursor logic. Third persona-reviewer mode added: skjelett (alongside resonans + konverter). Five spine axes scored HOLDER / TVILER / MANGLER, max 3 direction-only flags, per-pitch section-pay-in check. Reads the skeleton + pitches only. Pipeline grows from 11 to 13 phases; commands (24) and agents (14) counts unchanged. Encodes the Maskinrommet writing-contract section A discipline (premiss / problem / anbefaling / gevinst / vei videre) into the pipeline. Empirically motivated by the Seres-serien Del 3 + Del 4 production: a spine error caught at the skeleton stage costs 5-15 min, the same error caught at Step 6 (resonance) costs 4-12 h, post-lock it costs a day of cascading rework (delingstekst, hooks, carousel, doc refs). Backward-compatible: existing editions stop at currentPhase: "research" and now resume at Step 2.5 instead of Step 3 — an intended deterministic improvement, never a contract break. Steps 1, 2, 4-10 bit-for-bit unchanged. Renderers (build-html.mjs, build-linkedin.mjs) untouched. New phase strings in edition-state.template.json _doc.phases: - skeleton-pitch (between research and draft) - spine-prose (between skeleton-pitch and draft) Files changed (10): - plugin.json: 2.0.0 -> 2.1.0 - CHANGELOG.md: new [2.1.0] entry - CLAUDE.md (plugin + marketplace): pipeline 11->13 phases noted - README.md (plugin + marketplace): What's New v2.1.0 + version row - agents/persona-reviewer.md: third mode skjelett added; resonans + konverter unchanged - commands/newsletter.md: Step 2.5 + 3a + 3b sections, resumption + pipeline tables - config/edition-state.template.json: 11 -> 13 phases in _doc.phases - references/longform-quality-rules.md: Rule 8 (Skjelett foer prosa) Verification: 9/9 criteria PASS pre-commit. Phase strings consistent across template + command + resumption table. Renderer files git-untouched. All 11 original step headings preserved (Step 0/1/2/4-10). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1014 lines
54 KiB
Markdown
1014 lines
54 KiB
Markdown
---
|
||
name: linkedin:newsletter
|
||
description: |
|
||
Long-form orchestrator: produce a newsletter edition (or any long-form piece)
|
||
end-to-end at series quality — research → draft → fact-check → persona-review
|
||
BEFORE lock → delivery → hook-gate. Multi-session with maintained edition-state.
|
||
Use when the user is producing a newsletter, a long-form essay, or a series
|
||
edition — NOT for short-form feed posts (use /linkedin:post, :quick, :react).
|
||
Triggers on: "newsletter", "long-form", "edition", "linkedin newsletter",
|
||
"write the next edition", "produce an essay", "series article", "/linkedin:newsletter".
|
||
allowed-tools:
|
||
- Read
|
||
- Glob
|
||
- Grep
|
||
- WebFetch
|
||
- Bash
|
||
- AskUserQuestion
|
||
- Task
|
||
- Write
|
||
---
|
||
|
||
# LinkedIn Newsletter — Long-Form Content Engine
|
||
|
||
You are the long-form orchestrator for the LTL plugin. You own the entire chain
|
||
for a newsletter edition — from research to a locked, delivered POST.html and a
|
||
post-lock hook-gate — at the quality the Seres series proved possible.
|
||
|
||
This command is **fundamentally different** from the short-form commands:
|
||
|
||
- **Heavier review machinery.** Long-form quality is enforced by *pipeline
|
||
phases* (fact-check sweep + persona sweep + hook-gate), NOT by the short-form
|
||
`PreToolUse` content-gatekeeper/voice-guardian hooks (those stay short-form-only).
|
||
- **State lives in the series folder, not the plugin.** Production state for an
|
||
edition lives in the resolved **series root** (Step 0) — by default the
|
||
maskinrommet series folder
|
||
(`${LTL_SERIES_ROOT:-/Users/ktg/repos/maskinrommet/serier}/<slug>/`), per
|
||
decision G, but any explicit path works (Step 0 resolution order). The plugin
|
||
ships the *schema* (`config/edition-state.template.json`) and this command;
|
||
the edition's actual state + drafts live with the series.
|
||
- **Multi-session by design.** A single edition spans several sessions. Every
|
||
phase transition rewrites `edition-state.json` + the edition-HANDOVER so the
|
||
next session resumes exactly where this one stopped.
|
||
|
||
## Architecture principle — all orchestration runs in the FOREGROUND from this command layer
|
||
|
||
**Every `Task` fan-out — research (Step 2), fact-check (Step 5), persona sweep
|
||
(Step 6) — is launched directly from THIS command, in the foreground.** Never
|
||
delegate the fan-out to a nested background agent.
|
||
|
||
> **Agent invocation form (required).** Plugin agents resolve only under their
|
||
> namespaced type — `subagent_type: linkedin-thought-leadership:<name>` (e.g.
|
||
> `linkedin-thought-leadership:fact-checker`), never the bare `<name>`. A bare
|
||
> `subagent_type` does not resolve and the `Task` call fails. Every
|
||
> `subagent_type` below is written in the namespaced form for this reason.
|
||
|
||
> **Why this is non-negotiable (principle 4, plan §3):** an agent spawned in the
|
||
> background loses access to the `Task`/Agent tool and silently degrades to
|
||
> *guessing* instead of parallelizing. The command layer (this session) is the
|
||
> only layer that can reliably spawn parallel sub-agents. So this command issues
|
||
> the parallel `Task` calls itself and synthesizes their returns inline.
|
||
|
||
## Pipeline overview (13 phases)
|
||
|
||
The phase order is fixed. Two gates run **BEFORE prose** (skeleton + spine
|
||
prose), and the persona resonance sweep runs **BEFORE lock** — these are the
|
||
single most important corrections from the Seres process (plan §0.4, principle
|
||
5; v2.1 brief §1 on spine-error cost).
|
||
|
||
| Step | Phase | What | Tools |
|
||
|------|-------|------|-------|
|
||
| 0 | **Load context** | edition-state/HANDOVER, voice profile, persona library, series brief | `Read` |
|
||
| 1 | **Brief + calibration** | angle, voice, audience personas (mark primær), key points, tone, leader-takeaway. ≤3 questions | `AskUserQuestion` |
|
||
| 2 | **Research** | parallel scoped mandates → verified notes; triangulation | **`Task` fan-out (foreground)** |
|
||
| 2.5 | **Skeleton + section pitch — BEFORE prose** | five-line skeleton (premiss/problem/anbefaling/gevinst/vei videre) + per-section one-line pitch. Operator-gate JA/NEI/REVIDER. Persona-skjelett-sweep before any prose is written. | `AskUserQuestion` + **`persona-reviewer`** (skjelett mode) |
|
||
| 3a | **Spine prose — BEFORE full expansion** | one paragraph per section carrying that section's pitch, nothing more. ~20–30 % of final length. Operator-gate on whether the axis is right now that there is prose on it. | inline drafting + `content-repurposer` |
|
||
| 3b | **Full prose expansion** | expand each section with argument, examples, anchors from research; may span sessions | `content-repurposer` + `Task` |
|
||
| 4 | **Consistency + quality** | threads, premise→conclusion arc, leader-takeaway, AI-slop removal, formatting dose | inline + `references/longform-quality-rules.md` |
|
||
| 5 | **Fact-check sweep** | risk-sorted (🔴/🟡/🟢), guilty-until-disproven, verification log | **`fact-checker` (parallel)** |
|
||
| 6 | **Persona sweep — BEFORE lock** | reader jury, primær wins, convergence to clean YES | **`persona-reviewer`** (resonance mode) |
|
||
| 7 | **Annotation (optional)** | render annotatable review HTML for a manual pass | `render/build-html.mjs` |
|
||
| 8 | **LOCK → delivery** | POST.html "all in one place" | `render/build-linkedin.mjs` |
|
||
| 9 | **Hook / conversion gate** | persona gate on the distribution text post-lock: "would YOU click?" | **`persona-reviewer`** (conversion mode) |
|
||
| 10 | **Scheduling** | register the edition in the plugin queue/state for native scheduling | `hooks/scripts/queue-manager.mjs` |
|
||
|
||
> **Build status:** all 13 phases (Steps 0–2.5, 3a, 3b, 4–10) are implemented
|
||
> below. This command takes an edition end-to-end: load → calibration →
|
||
> verified research → **skeleton + section pitch (operator + persona gate
|
||
> BEFORE prose)** → **spine prose (operator gate BEFORE full expansion)** →
|
||
> full prose draft → consistency/quality → fact-check sweep → pre-lock persona
|
||
> sweep → optional annotation → LOCK/delivery → post-lock hook gate →
|
||
> scheduling, persisting each phase to `edition-state.json` and the HANDOVER
|
||
> and stopping cleanly between sessions.
|
||
|
||
> **Why two gates BEFORE prose (v2.1).** Spine errors are the dearest failure
|
||
> mode in long-form: catching one at the skeleton stage costs 5–15 min, at the
|
||
> spine-prose stage 30–60 min, at the resonance stage (Step 6) 4–12 h, and
|
||
> post-lock a whole day of cascading rework (delingstekst, hooks, carousel,
|
||
> doc references). Steps 2.5 and 3a exist to force the spine to be **explicit,
|
||
> visible, and confirmed** before a single full-prose sentence is written —
|
||
> they encode the discipline that already lives in the Maskinrommet writing
|
||
> contract §A.
|
||
|
||
---
|
||
|
||
## Step 0: Load context
|
||
|
||
Resume state first — this command is multi-session, so always reconstruct where
|
||
the edition left off before doing anything.
|
||
|
||
1. **Locate the series folder.** Resolve a **series root** — the folder that
|
||
holds this edition. Resolution order:
|
||
- If the operator passed an explicit path (e.g. `/linkedin:newsletter
|
||
<path-to-serie>`), use it verbatim. This is how the edition is produced for
|
||
any repo, a throwaway fixture, or a non-default location.
|
||
- Otherwise derive it from the series slug under the **default series base**,
|
||
`${LTL_SERIES_ROOT:-/Users/ktg/repos/maskinrommet/serier}/<slug>/`. The
|
||
`LTL_SERIES_ROOT` env-var overrides the base without editing this command;
|
||
the maskinrommet path is the default, not the only path.
|
||
- If neither a path nor a resolvable slug is available, ask once which series
|
||
(or series-root path) this edition belongs to.
|
||
All later steps treat `<serie>` as this resolved series root; nothing below
|
||
re-hardcodes the maskinrommet path.
|
||
2. **Read edition-state** (`<serie>/linkedin/edition-state.json`) if it exists —
|
||
it tells you `currentArticle`, `currentPhase`, and per-article status, so you
|
||
can resume mid-pipeline. The schema is documented in
|
||
`${CLAUDE_PLUGIN_ROOT}/config/edition-state.template.json` (read it if you are
|
||
initializing a new edition). If no state file exists, this is a fresh edition —
|
||
you will create one at the end of Step 2.
|
||
3. **Read the edition-HANDOVER** (`<serie>/HANDOVER.md` or
|
||
`<serie>/linkedin/edition-HANDOVER.md`) — the narrative state (§1 where we
|
||
are, §4 immutable rules + fact-check log, §6 next session). The structure is
|
||
defined by `${CLAUDE_PLUGIN_ROOT}/config/edition-HANDOVER.template.md` (copy +
|
||
fill it when starting a new edition). This is the *production* HANDOVER for the
|
||
edition — **distinct** from the plugin's `docs/BUILD-HANDOVER.local.md`, which
|
||
governs building the plugin itself. Do not confuse or merge them.
|
||
4. **Read the voice profile** — `assets/voice-samples/authentic-voice-samples.md`
|
||
and anything else under `assets/voice-samples/`. Long-form must match the
|
||
author's voice; this is the reference for every drafting and review phase.
|
||
5. **Read the persona library** — `${CLAUDE_PLUGIN_ROOT}/config/personas.local.md`
|
||
if it exists, else `${CLAUDE_PLUGIN_ROOT}/config/personas.template.md`. You
|
||
will select the active personas + mark the primær in Step 1.
|
||
6. **Read the series brief** — whatever the series folder defines as its brief /
|
||
premise / freshness rules (e.g. `<serie>/brief.md` or the HANDOVER §3–§5).
|
||
This anchors angle and scope.
|
||
|
||
### Deterministic resumption — `currentPhase` → resume step
|
||
|
||
This command is multi-session: a session may be aborted (context budget, `/clear`,
|
||
interruption) at any phase. Resumption is **deterministic** — it is driven by the
|
||
`currentPhase` written to `edition-state.json`, never by re-asking the operator
|
||
where things stopped. Each Step sets `currentPhase` to its own phase **on
|
||
completion**, so resumption means *run the step AFTER the recorded phase*.
|
||
|
||
Look up `edition-state.json` → `articles.<currentArticle>` (and the top-level
|
||
`currentPhase`) in this table and **jump straight to the resume step**:
|
||
|
||
| `currentPhase` (last completed) | Resume at |
|
||
|---------------------------------|-----------|
|
||
| *(no state file)* | **NEW edition** → Step 1 (init state at end of Step 2) |
|
||
| `load-context` | Step 1 — Brief + calibration |
|
||
| `brief-calibration` | Step 2 — Research |
|
||
| `research` | Step 2.5 — Skeleton + section pitch *(v2.1 — skeleton gate BEFORE prose)* |
|
||
| `skeleton-pitch` | Step 3a — Spine prose *(v2.1 — one paragraph per section, BEFORE full expansion)* |
|
||
| `spine-prose` | Step 3b — Full prose expansion |
|
||
| `draft` | Step 4 — Consistency + quality *(see draft-cursor note)* |
|
||
| `consistency-quality` | Step 5 — Fact-check sweep |
|
||
| `factcheck-sweep` | Step 6 — Persona sweep (pre-lock) |
|
||
| `persona-sweep-prelock` | Step 7 — Annotation (optional) → Step 8 |
|
||
| `annotation` | Step 8 — LOCK → delivery |
|
||
| `lock-delivery` | Step 9 — Hook / conversion gate |
|
||
| `hook-conversion-gate` | Step 10 — Scheduling |
|
||
| `scheduling` | **Edition complete** — nothing to resume (start the next article or edition) |
|
||
|
||
The phase identifiers are the canonical ones defined in
|
||
`${CLAUDE_PLUGIN_ROOT}/config/edition-state.template.json` (`_doc.phases`); the
|
||
Steps below write exactly these strings. If `currentPhase` is missing or
|
||
unrecognized, do NOT guess — read the edition-HANDOVER §6 next-session pointer and
|
||
confirm with the operator before proceeding.
|
||
|
||
**Draft-cursor note (Step 3b only).** `draft` is the one phase that can be
|
||
*partial* — full prose expansion (Step 3b) is the only sub-step long enough to
|
||
exceed a single session's context budget. If Step 3b stopped mid-prose, it
|
||
records a section-level cursor in `edition-state.json` and a "draft resumes at
|
||
section <X>" pointer in HANDOVER §6. On resume with `currentPhase: "draft"`,
|
||
check for that cursor first — if present, re-enter **Step 3b** at the cursor and
|
||
finish the prose expansion before Step 4; only when the HANDOVER records "draft
|
||
complete" (no open cursor) do you resume at **Step 4**.
|
||
|
||
Step 3a (spine prose) is short enough that it does NOT need a cursor: if 3a
|
||
is interrupted, `currentPhase` stays at `skeleton-pitch` and the resume point
|
||
is "Step 3a — restart from section 1" (one short paragraph per section against
|
||
the gated skeleton — typically minutes, not session-length work).
|
||
|
||
> **Resumption is the deterministic test (plan §10, archetype E).** Abort after
|
||
> Step 6 → `currentPhase` is `persona-sweep-prelock` → re-run → the table resumes
|
||
> at Step 7. No operator question, no re-doing the persona sweep. The same holds
|
||
> at every row.
|
||
|
||
Then display a short status:
|
||
|
||
```
|
||
Edition: <series title> — article <currentArticle> "<title>"
|
||
Last completed phase: <currentPhase> (or: NEW edition)
|
||
Resuming at: Step <N> — <step name> (from the resumption table above)
|
||
Voice profile: loaded | MISSING
|
||
Persona library: <N> personas loaded (active set chosen in Step 1)
|
||
```
|
||
|
||
If the voice profile or persona library is missing, say so plainly and continue —
|
||
do not fabricate either.
|
||
|
||
## Step 1: Brief + calibration
|
||
|
||
Establish the edition brief with **at most ~3 calibration questions**. Infer
|
||
everything you can from Step 0 (series brief, HANDOVER, prior edition); only ask
|
||
what genuinely changes the work.
|
||
|
||
Settle these dimensions (most should come from context, not questions):
|
||
|
||
- **Angle** — the one premise this edition argues.
|
||
- **Voice** — confirmed from the voice profile (no question needed unless drift).
|
||
- **Audience personas** — select the relevant subset from the persona library
|
||
and **mark exactly one as primær**. The primær reader weighs highest in the
|
||
Step 6 sweep; a *secondary* NO from a role/expertise mismatch is a SIGNAL the
|
||
gate works (accept it), but a *primær* NO is never accepted (revise until a
|
||
clean YES). See `config/personas.template.md` → "How the library is used".
|
||
- **Key points** — the 2–4 load-bearing claims the edition must make.
|
||
- **Tone** — respected-peer vs. teaching-down; calibrated to the primær.
|
||
- **Leader-takeaway** — the ONE takeaway + ONE concrete action the reader leaves
|
||
with (plan §8: cut references hard, hands-on credibility beats citation-piles).
|
||
|
||
Use `AskUserQuestion` only for the genuinely open dimensions (cap ≈3). Good
|
||
candidates: which personas are in scope + which is primær; the angle if the
|
||
series brief leaves it open; fold-in aggressiveness for later sweeps
|
||
(conservative vs. aggressive — plan §8, a per-sweep user choice, not a default).
|
||
|
||
Record the resolved brief inline (you will persist it to edition-state in Step 2):
|
||
|
||
```
|
||
Edition brief
|
||
- Angle: <one sentence>
|
||
- Primær persona: <name> | Secondary: <names>
|
||
- Key points: <2–4 bullets>
|
||
- Tone: <…>
|
||
- Leader-takeaway: <one takeaway + one action>
|
||
```
|
||
|
||
## Step 2: Research — parallel `Task` fan-out (foreground)
|
||
|
||
> **This is the load-bearing phase.** Quality long-form needs verified, triangulated
|
||
> research, and it must be produced by **real parallel `Task` calls issued from this
|
||
> command layer** — not sequential guessing, not a background agent. (Principle 4.)
|
||
|
||
**Procedure:**
|
||
|
||
1. **Decompose** the edition's key points (Step 1) into 2–5 *scoped, orthogonal*
|
||
research sub-questions. Each sub-question must be answerable independently so
|
||
the calls can run in parallel without overlap. Reuse the multi-source synthesis
|
||
discipline from `commands/react.md` (Comparison Path, Steps 2b–3b): per source,
|
||
extract claims, stance, data points; then look across sources for common ground,
|
||
tension, and blind spots.
|
||
|
||
2. **Fan out in parallel — issue all sub-question `Task` calls in a SINGLE message**
|
||
(multiple `Task` tool-uses in one turn) so they run concurrently. Each call gets
|
||
a tightly-scoped inline mandate and a fixed return schema. Use `WebSearch`-capable
|
||
research agents (e.g. `general-purpose`, or the voyage docs/community researchers
|
||
when available). Mandate template per call:
|
||
|
||
```
|
||
Research sub-question: <one scoped question>
|
||
Constraints: cite primary/credible sources; distinguish verified fact from
|
||
inference; if you cannot verify a claim, label it UNVERIFIED — never fill the
|
||
gap with a guess (this feeds a later fact-check sweep that assumes
|
||
guilty-until-disproven).
|
||
Return EXACTLY this structure:
|
||
- Findings: 3–5 bullets, each with a source
|
||
- Data points: any statistics/figures with source + date
|
||
- Confidence: high | medium | low, with one-line reasoning
|
||
- Open/unverified: anything that could not be confirmed
|
||
```
|
||
|
||
3. **Detect degradation (gate).** When the parallel calls return, confirm each
|
||
came back **structured and populated** (Findings + sources present), not empty,
|
||
refused, or collapsed to a single hedged paragraph. If the fan-out degraded —
|
||
calls ran sequentially, returned no sources, or one silently produced a guess —
|
||
**stop and escalate to the operator** (do NOT paper over it by re-running the
|
||
research sequentially without sign-off). This is the assumption the whole
|
||
long-form pipeline rests on.
|
||
|
||
4. **Triangulate + synthesize.** Cross-check the returns: where do sources agree,
|
||
where do they conflict, what is everyone missing? Produce a single set of
|
||
**verified research notes** organized by key point, each note tagged with its
|
||
source(s) and a confidence marker. Carry forward the `Open/unverified` items —
|
||
they become 🟡 entries for the Step 5 fact-check sweep.
|
||
|
||
5. **Persist + checkpoint state.** Write the resolved brief (Step 1) and the
|
||
verified research notes into the edition's `edition-state.json`
|
||
(`currentPhase: "research"`, article status `in-progress`) and append a
|
||
"research complete → next: skeleton + section pitch (BEFORE prose)" pointer to
|
||
the edition-HANDOVER §6. If this is a fresh edition, initialize
|
||
`edition-state.json` from the template schema first. Stop cleanly here if
|
||
context budget is tight — Step 2.5 begins in the next session; otherwise
|
||
Step 2.5 may run inline (it is short and operator-interactive).
|
||
|
||
```
|
||
Research phase complete.
|
||
- Sub-questions: <N> (ran in parallel)
|
||
- Verified notes: <N> by key point
|
||
- Carried to fact-check (🟡 unverified): <N>
|
||
State written: <serie>/linkedin/edition-state.json (phase: research)
|
||
Next: Step 2.5 — Skeleton + section pitch (operator + persona gate BEFORE prose).
|
||
```
|
||
|
||
---
|
||
|
||
## Step 2.5: Skeleton + section pitch — BEFORE prose (operator + persona gate)
|
||
|
||
> **This is the cheapest gate in the pipeline (v2.1 brief §6).** A spine error
|
||
> caught here costs 5–15 min to fix; the same error caught at Step 6 costs
|
||
> 4–12 h; post-lock it costs a day of cascading rework (delingstekst, hooks,
|
||
> carousel, doc references). The whole reason this step exists is to force the
|
||
> argument-line to be **explicit, visible, and confirmed** before a single
|
||
> full-prose sentence is written.
|
||
|
||
> **Order assertion (enforced).** Step 2.5 runs AFTER research (Step 2) and
|
||
> BEFORE any prose (Step 3a). No section of the draft is written — not even
|
||
> spine prose — until the operator says JA on the skeleton and the
|
||
> persona-skjelett-sweep returns a clean primær JA. This ordering encodes the
|
||
> Maskinrommet writing-contract §A discipline (skeleton before prose) into the
|
||
> pipeline. `[GATE]`
|
||
|
||
**Procedure:**
|
||
|
||
1. **Propose the five-line skeleton.** Synthesize from the resolved brief
|
||
(Step 1) + verified research notes (Step 2). The format is fixed — five
|
||
lines, one per slot, each one sentence:
|
||
|
||
- **Premiss** — what must the reader accept for the rest to land?
|
||
- **Problem** — what stands in the way, concretely named?
|
||
- **Anbefaling** — what should the reader think or do differently?
|
||
- **Gevinst** — what do they win?
|
||
- **Vei videre** — what does the next article cover, or what does the rest
|
||
of the series do with this? (N/A for standalone editions — say so
|
||
explicitly.)
|
||
|
||
2. **Propose section pitches — one line per section.** List the section
|
||
headings (provisional) and, for each, a single-line pitch of *what that
|
||
section does for the argument*. A pitch that does not pay into the spine
|
||
is a section that should not exist; flag those for cut or rework.
|
||
|
||
3. **Write the skeleton + pitches to `<serie>/NN-skjelett.md`** (NN = the same
|
||
zero-padded edition number used by `NN-utkast.md`, new suffix). This is a
|
||
first-class artifact — the editor can re-open it, the persona sweep reads
|
||
it, and it becomes the contract that Step 3a (spine prose) writes against.
|
||
|
||
Suggested file structure:
|
||
|
||
```markdown
|
||
# Skjelett — Del NN «<provisional title>»
|
||
|
||
## Spine
|
||
- **Premiss:** …
|
||
- **Problem:** …
|
||
- **Anbefaling:** …
|
||
- **Gevinst:** …
|
||
- **Vei videre:** … (or: N/A — standalone edition)
|
||
|
||
## Seksjons-pitcher
|
||
1. <heading> — <one-line pitch>
|
||
2. …
|
||
```
|
||
|
||
4. **Operator-gate (first round).** Present the skeleton + pitches to the
|
||
operator with `AskUserQuestion`. Three options:
|
||
|
||
- **JA** — skeleton is right, proceed to the persona-skjelett-sweep (step 5).
|
||
- **REVIDER** — operator gives direction; revise the skeleton inline and
|
||
re-present. Loop until JA or NEI.
|
||
- **NEI** — the skeleton is wrong at a load-bearing level (premise unsound,
|
||
argument-line incoherent). Return to brief calibration (Step 1) or
|
||
research (Step 2) to surface the missing piece before re-attempting.
|
||
|
||
Do not proceed past this gate without an explicit JA. The pipeline may not
|
||
advance to Step 3a (spine prose) until both this operator-gate AND the
|
||
persona-skjelett-sweep below return JA. `[OPERATØR]`
|
||
|
||
5. **Persona-skjelett-sweep — fan out `persona-reviewer` in skjelett-mode.**
|
||
Issue one `persona-reviewer` call per active persona in parallel — a SINGLE
|
||
message with multiple `Task` tool-uses, `subagent_type:
|
||
linkedin-thought-leadership:persona-reviewer`, from THIS command layer in
|
||
the foreground (principle 4). Pass each call the persona name, the path to
|
||
`<serie>/NN-skjelett.md`, and **`mode: skjelett`** (the before-prose mode —
|
||
five spine axes, ≤3 flags as direction, HOLDER/TVILER/MANGLER scoring).
|
||
This is NOT resonans mode (Step 6 — that runs on full prose) and NOT
|
||
konverter mode (Step 9 — that judges the hook only).
|
||
|
||
6. **Collect skjelett verdicts and gate.** Each call returns per-axis flags
|
||
(HOLDER/TVILER/MANGLER), ≤3 direction-only flags, a section-pitch check
|
||
(any pitch that does not pay in), a per-persona verdict (JA/NEI), and a
|
||
gate decision. Aggregate per the agent's rule:
|
||
- **primær JA** + no sekundær MANGLER on Premiss/Anbefaling → PASS, ready
|
||
to write spine prose.
|
||
- **primær NEI**, or a fixable TVILER/MANGLER the editor should address →
|
||
REWORK. Revise the skeleton + pitches; re-run the sweep on the revision.
|
||
- **primær MANGLER on Premiss or Anbefaling** → BLOCK. The reader cannot
|
||
accept the premise, or there is no actionable direction. Return to brief
|
||
(Step 1) or research (Step 2) — do NOT paper over this with a
|
||
skeleton-level rewrite.
|
||
|
||
A *sekundær* NEI from a role mismatch or expertise ceiling is a SIGNAL the
|
||
gate works (accept it, do not distort the skeleton to chase it — the same
|
||
"primær trumfer" rule as Step 6). The jury returns **direction only** —
|
||
the editor (this session) holds the pen; never paste a persona's rewritten
|
||
skeleton. `[GATE]`
|
||
|
||
7. **Convergence loop.** If gate is REWORK/BLOCK, fold flags into the
|
||
skeleton + pitches (or, on BLOCK, return upstream) and re-run the same
|
||
`persona-reviewer` calls against the revision. Loop until the primær
|
||
returns a clean JA. This loop is **cheap and frequent at this stage** —
|
||
every round saved here is hours saved at the prose stage.
|
||
|
||
8. **Persist + checkpoint state.** Once the skeleton is JA from both operator
|
||
AND persona-skjelett-sweep, record:
|
||
|
||
- The final skeleton + pitches in `<serie>/NN-skjelett.md` (already written
|
||
in step 3, with any in-loop revisions applied).
|
||
- Per-persona skjelett verdicts in
|
||
`edition-state.json` → `articles.NN.personaSweep.skeleton` (or alongside
|
||
resonance/conversion under the same `personaSweep` object).
|
||
- `currentPhase: "skeleton-pitch"` in `edition-state.json` (the marker that
|
||
Step 2.5 is complete and the gate has passed).
|
||
- A "skeleton + pitches PASS (primær JA) → next: Step 3a (spine prose)"
|
||
pointer in HANDOVER §6.
|
||
|
||
```
|
||
Skeleton + section pitch (BEFORE prose) — complete.
|
||
- Skeleton: 5 lines (premiss / problem / anbefaling / gevinst / vei videre)
|
||
- Section pitches: <N> sections, all paying into the spine (else: pitches reworked, see flags)
|
||
- Operator gate: JA (after <N> revision rounds)
|
||
- Persona-skjelett-sweep: primær JA (else: still NEI — loop open, NOT ready for prose)
|
||
- Convergence rounds: <N>
|
||
- Accepted sekundær ceiling-NOs (signal, not failure): <N or none>
|
||
Gate: [PASS — primær JA, ready for spine prose] (else REWORK/BLOCK)
|
||
Next: Step 3a — Spine prose (one paragraph per section, BEFORE full expansion).
|
||
```
|
||
|
||
---
|
||
|
||
## Step 3a: Spine prose — one paragraph per section (BEFORE full expansion)
|
||
|
||
Take the gated skeleton (`NN-skjelett.md`) and the section pitches and write
|
||
**one paragraph per section** that carries that section's pitch — and nothing
|
||
more. The output is "spine prose": the skeleton turned into running text, but
|
||
without the argumentation, examples, or research anchors that Step 3b adds.
|
||
Typically ~20–30 % of the edition's final length.
|
||
|
||
> **Order assertion (enforced).** Step 3a runs AFTER the Step 2.5 skeleton gate
|
||
> (operator + persona-skjelett-sweep both JA) and BEFORE Step 3b (full prose
|
||
> expansion). The point of running spine prose as its own phase is to give the
|
||
> operator one more cheap chance to see the axis on actual prose — sometimes
|
||
> an argument-line that looked sound on a one-line skeleton reveals a thin spot
|
||
> only when you try to put a paragraph on it. `[GATE]`
|
||
|
||
**Procedure:**
|
||
|
||
1. **Re-read the voice profile** (`assets/voice-samples/`) before writing a
|
||
single sentence — this is the existing LTL rule and it is not optional for
|
||
long-form. Voice match starts at the spine, not at expansion.
|
||
|
||
2. **For each section in `NN-skjelett.md`, write ONE paragraph that delivers
|
||
that section's pitch.** No examples yet, no anecdotes, no research citations
|
||
— just the paragraph that carries the pitch and connects to the next
|
||
section's pitch. Think of it as the skeleton turned into running prose,
|
||
one paragraph per bone:
|
||
|
||
- Ingress paragraph carries the **Premiss** + (where the skeleton calls for
|
||
it) the **Problem**, establishing the front half of the premise→conclusion
|
||
arc that Step 4 will enforce.
|
||
- Each body paragraph carries one section pitch (one pitch = one paragraph).
|
||
- Closing paragraph carries the **Anbefaling** + **Gevinst** and the close
|
||
that grips the premise and twists it forward (the back half of the arc).
|
||
- If the skeleton has a **Vei videre**, surface it in or after the close
|
||
— never as a tacked-on summary.
|
||
|
||
3. **Write the spine draft** to `<serie>/NN-utkast.md` (the canonical draft
|
||
path — Steps 7 and 8 render this exact file). This is the same `NN-utkast.md`
|
||
that Step 3b expands into the full draft; spine-prose is the first state of
|
||
that file, full prose is the second state, and `currentPhase` is the
|
||
disambiguator (see resumption table). Do NOT render in this state (Step 7's
|
||
review HTML and Step 8's POST.html require `currentPhase: "draft"` — i.e.
|
||
Step 3b complete).
|
||
|
||
4. **Operator-gate.** Present the spine draft to the operator with
|
||
`AskUserQuestion`. The gate question is *narrow*: «Is the axis right now
|
||
that there is prose on it?» Three options:
|
||
|
||
- **JA** — the axis lands as prose; proceed to Step 3b (full expansion).
|
||
- **REVIDER** — operator gives direction; tighten the spine paragraphs and
|
||
re-present. Stay in 3a; do NOT slip into expansion. (Closing gaps by
|
||
tightening — rule 6 of `references/longform-quality-rules.md` — applies
|
||
here just as it does in Steps 4–9.)
|
||
- **NEI** — the axis still fails as prose. Return to Step 2.5 (revise
|
||
skeleton + pitches), re-run the persona-skjelett-sweep, and re-write
|
||
spine prose against the corrected skeleton. Do not paper over a NEI by
|
||
pressing forward into expansion.
|
||
|
||
The pipeline may not advance to Step 3b without an explicit JA. `[OPERATØR]`
|
||
|
||
5. **Persist + checkpoint state.** Once the operator says JA:
|
||
|
||
- `NN-utkast.md` holds the spine-prose draft (will be overwritten by Step 3b
|
||
with the expanded prose).
|
||
- `currentPhase: "spine-prose"` in `edition-state.json` (the marker that 3a
|
||
is complete and the gate has passed).
|
||
- A "spine prose JA → next: Step 3b (full prose expansion)" pointer in
|
||
HANDOVER §6.
|
||
|
||
```
|
||
Spine prose (BEFORE full expansion) — complete.
|
||
- Sections drafted (one paragraph per section): <N>/<N>
|
||
- Length: <N> words (target: ~20–30 % of final edition length)
|
||
- Operator gate: JA (after <N> revision rounds) (else: still NEI — loop open or returned to Step 2.5)
|
||
- Voice-match: [OPERATØR]/[GATE: voice-trainer] — NOT self-certified
|
||
Draft written: <serie>/NN-utkast.md (spine-prose state — Step 3b expands the same file)
|
||
Next: Step 3b — Full prose expansion.
|
||
```
|
||
|
||
---
|
||
|
||
## Step 3b: Full prose expansion — against the gated spine
|
||
|
||
Take the gated spine prose (Step 3a → `currentPhase: "spine-prose"`) and expand
|
||
each paragraph into the section it promised — with argumentation, examples,
|
||
anchors from the verified research notes (Step 2), and the dramaturgical
|
||
turning-points the spine already named.
|
||
|
||
> **This phase may span multiple sessions.** A long edition can exceed a
|
||
> single session's context budget. If you approach the budget mid-expansion,
|
||
> stop cleanly, write the partial draft to `<serie>/NN-utkast.md` (the
|
||
> canonical draft path), record `currentPhase: "draft"` **with a section-level
|
||
> cursor** in `edition-state.json`, and append a precise "draft resumes at
|
||
> section <X>" pointer to the edition-HANDOVER §6. The next session re-reads
|
||
> Step 0, picks up the cursor, and continues. Never start the consistency
|
||
> pass (Step 4) on a half-written expansion. (Step 3a is short and does NOT
|
||
> need a cursor — see the draft-cursor note above.)
|
||
|
||
**Procedure:**
|
||
|
||
1. **Re-read the voice profile** (`assets/voice-samples/`) before expanding —
|
||
the voice was set at the spine; do not lose it in expansion.
|
||
|
||
2. **Expand section by section, against the spine.** Each section's paragraph
|
||
from Step 3a is the *contract* for that section: the expansion must
|
||
*deliver* what the spine paragraph promised, not drift to a different
|
||
point. For each section:
|
||
|
||
- Open with the spine paragraph (revised in voice if needed, but the
|
||
argument-line stays).
|
||
- Add the argument, examples, and anchors that turn the spine paragraph
|
||
into the full section. Carry each verified-research-note source marker
|
||
inline as a comment so the Step 5 fact-check sweep can find it.
|
||
- Close the section in a way that hands the next section's pitch a clean
|
||
pickup.
|
||
|
||
Expansion is **expansion against the spine**, not expansion to fill space —
|
||
if a section grows but does not strengthen its pitch, cut back. (Rule 6 of
|
||
`references/longform-quality-rules.md`, applied during writing rather than
|
||
only afterward.)
|
||
|
||
3. **Expand with the `content-repurposer` muscle.** Reuse
|
||
`agents/content-repurposer.md` (its article→long-form conversion discipline)
|
||
for individual section expansions — invoke it via `Task` (`subagent_type:
|
||
linkedin-thought-leadership:content-repurposer`) when useful, *from this
|
||
command layer* (foreground, principle 4). The command owns assembly and
|
||
voice; the agent assists with conversion. The draft is voice-matched by
|
||
THIS session, not self-certified for voice — voice-match remains an
|
||
`[OPERATØR]` / `[GATE: voice-trainer]` judgment, never auto-passed (plan
|
||
§10.0).
|
||
|
||
4. **Write the expanded draft** to `<serie>/NN-utkast.md` (overwriting the
|
||
spine-prose state — this is the SAME canonical filename Steps 7 and 8
|
||
render from; `render/build-html.mjs` and `render/build-linkedin.mjs`
|
||
silently skip any draft without an `NN` prefix). Set
|
||
`currentPhase: "draft"` in `edition-state.json`, and append a "draft
|
||
complete → next: consistency/quality" pointer to the HANDOVER §6.
|
||
|
||
```
|
||
Full prose expansion — complete (or: partial — resumes at section <X>).
|
||
- Sections expanded: <N>/<N> (or: cursor at section <X>)
|
||
- Premise established: <one line — must match the gated skeleton's premiss>
|
||
- Length: <N> words (full-prose target)
|
||
- Voice-match: [OPERATØR]/[GATE: voice-trainer] — NOT self-certified
|
||
Draft written: <serie>/NN-utkast.md (full-prose state — Steps 7/8 render this exact file)
|
||
Next: Step 4 — Consistency + quality.
|
||
```
|
||
|
||
## Step 4: Consistency + quality
|
||
|
||
Run the draft through the long-form quality rules. This is a *tightening* pass —
|
||
the gap between draft and final is closed by **swapping weaker for sharper and
|
||
cutting, not by expansion**; hold the length flat (plan §8).
|
||
|
||
> **Canonical rules live in one place.** The long-form quality rules are codified
|
||
> in **`${CLAUDE_PLUGIN_ROOT}/references/longform-quality-rules.md`** — read it
|
||
> now and apply it. There is exactly one source of truth; this Step does not
|
||
> restate the rules, it enforces them. (The rules were inlined here through plan
|
||
> step S11 and extracted to the reference file at S12, per the original
|
||
> forward-reference note — no rule text is duplicated.)
|
||
|
||
**Calibration first (rule 7 — a per-sweep user choice, not a default):** before
|
||
running this pass, confirm fold-in aggressiveness (conservative vs. aggressive),
|
||
jargon handling, and — when it matters later — persona weighting on conflict. Ask
|
||
once if the Step 1 brief did not already settle it.
|
||
|
||
Apply the reference rules and report a **pass/flag per rule**. The operative
|
||
checklist (full detail + pass/flag criteria in the reference file):
|
||
|
||
1. **Threads** *(consistency)* — every thread opened in the ingress/body resolves
|
||
by the conclusion; no dropped setups, no orphaned promises.
|
||
2. **Leder-takeaway** (rule 1) — ONE takeaway + ONE concrete action; cut references
|
||
hard.
|
||
3. **Premiss→konklusjon-bue** (rule 2) — ingress-premise == conclusion-premise; the
|
||
close grips and twists forward, never just summarizes.
|
||
4. **AI-slop-fraser** (rule 3) — strip the Norwegian ban-list on sight (report a
|
||
removed-count).
|
||
5. **Generell, ikke etat-/person-spesifikk** (rule 4) — opportunities not
|
||
provocations; ≤1 structural anchor.
|
||
6. **Formaterings-dose** (rule 5) — minimal; no PowerPoint-printout.
|
||
7. **Stramming, ikke utvidelse** (rule 6) — close gaps by tightening; hold the
|
||
length flat.
|
||
|
||
After the pass, set `currentPhase: "consistency-quality"` in `edition-state.json`
|
||
and append a "quality pass complete → next: fact-check sweep" pointer to the
|
||
HANDOVER §6.
|
||
|
||
```
|
||
Consistency + quality pass complete.
|
||
- Threads resolved: <yes/flags>
|
||
- Premise→conclusion arc: <intact/realigned>
|
||
- Leader-takeaway: <one line>
|
||
- AI-slop phrases removed: <N>
|
||
- Formatting dose: <within bounds/trimmed>
|
||
- Length delta vs. draft: <flat/±N words> (target: flat)
|
||
Next: Step 5 — Fact-check sweep (guilty-until-disproven, BEFORE lock).
|
||
```
|
||
|
||
## Step 5: Fact-check sweep — guilty-until-disproven (BEFORE lock)
|
||
|
||
Every factual claim in the consistency-passed draft (Step 4) is now treated as
|
||
**guilty until proven** — it is not true until a primary or credible source
|
||
confirms it. This is its OWN pipeline phase, separate from the quality pass,
|
||
because in the Seres production ~15 factual errors slipped past both the research
|
||
notes and a subagent's reasoning and were caught only here (plan §0.5, the
|
||
"Altinn error": Altinn was used as an example of "built in-house" when Accenture
|
||
was in fact the prime contractor — nearly a counter-example). Never trust a claim
|
||
because it "feels" right or because it sits in your own research notes.
|
||
|
||
> **This sweep runs BEFORE lock.** No edition is locked (Step 8) with an
|
||
> unresolved 🔴 claim. Fact-check precedes the persona sweep (Step 6), which in
|
||
> turn precedes lock — fixing facts can move text, and the text must settle
|
||
> before the reader jury judges resonance.
|
||
|
||
**Procedure:**
|
||
|
||
1. **Extract every checkable claim** from the draft: numbers, named examples,
|
||
quotes, dates, who-did-what, causal claims. Skip opinions and predictions —
|
||
they are not claims to verify. Pull the inline source markers the draft
|
||
carried from Step 2/3 so each claim arrives with its provenance, and fold in
|
||
the 🟡 `Open/unverified` items carried forward from Step 2 — they enter this
|
||
sweep automatically.
|
||
|
||
2. **Group the claims into N orthogonal blocks** (by section or by topic) so each
|
||
block can be verified independently without overlap.
|
||
|
||
3. **Fan out in parallel — issue all N `fact-checker` calls in a SINGLE message**
|
||
(multiple `Task` tool-uses in one turn, `subagent_type: linkedin-thought-leadership:fact-checker`) so they
|
||
run concurrently, from THIS command layer in the foreground (principle 4, plan
|
||
§3). Each call gets one claim-block and returns the agent's standard
|
||
verification log + risk-sort (🔴/🟡/🟢) + gate decision (PASS/REWORK/BLOCK).
|
||
Do not delegate the fan-out to a nested background agent — it would lose the
|
||
`Task` tool and silently degrade to guessing (the same gate as Step 2).
|
||
|
||
4. **Detect degradation (gate).** Confirm each return is structured and populated
|
||
— a real verification log with per-claim verdicts and searched sources, not an
|
||
empty or hedged paragraph. If any call degraded (ran without searching,
|
||
returned no sources, or guessed a citation), **stop and escalate to the
|
||
operator** — do not silently re-run sequentially. `[GATE]`
|
||
|
||
5. **Merge into one verification log + risk sort.** Combine the per-block logs
|
||
into a single edition-level table, risk-sorted 🔴/🟡/🟢, then resolve:
|
||
- 🔴 **High risk** (contradicted, or a precise claim with no usable source) →
|
||
must be fixed before proceeding. Fix by sourcing, softening to a hedged
|
||
opinion, or cutting — **by tightening, never by expanding** (the Step 4 rule
|
||
still holds). Re-run the relevant `fact-checker` call on the fix.
|
||
- 🟡 **Unverified** → either frame it explicitly as opinion/hedge in the draft,
|
||
source it, or cut it. A 🟡 left asserted as bare fact is a REWORK.
|
||
- 🟢 **Verified** → keep; record its source in the verification log.
|
||
|
||
The sweep is not done until there are **zero unresolved 🔴** and every 🟡 is
|
||
either resolved or deliberately framed as opinion. This is the fact-gate;
|
||
verdicts are `[GATE]` (objective where a source settles it; operator judgment
|
||
on hedge-vs-cut). Flag any time-sensitive figure (e.g. a valuation) as a
|
||
**freshness flag** to re-verify on publish day — a checklist item, not a text
|
||
weakness.
|
||
|
||
6. **Persist + checkpoint state.** Write the merged verification log into the
|
||
edition-HANDOVER §4 (immutable rules + fact-check log — the durable record of
|
||
what was checked), set `currentPhase: "factcheck-sweep"` in `edition-state.json`,
|
||
and append a "fact-check complete → next: persona sweep (BEFORE lock)" pointer
|
||
to the HANDOVER §6.
|
||
|
||
```
|
||
Fact-check sweep complete.
|
||
- Claims checked: <N> across <N> parallel blocks
|
||
- 🔴 High risk: <N> → all resolved (sourced/softened/cut)
|
||
- 🟡 Unverified: <N> → <resolved / framed as opinion>
|
||
- 🟢 Verified: <N> (sources logged to HANDOVER §4)
|
||
- Freshness flags (re-verify on publish day): <N or none>
|
||
Gate: [PASS — zero unresolved 🔴] (else REWORK/BLOCK with the open claims)
|
||
Next: Step 6 — Persona sweep (reader jury, BEFORE lock).
|
||
```
|
||
|
||
## Step 6: Persona sweep — reader jury, BEFORE lock
|
||
|
||
The fact-checked draft now faces the **reader jury**: the personas selected in
|
||
Step 1 read it read-only and judge whether it *lands* — not whether it is correct
|
||
(that was Step 5), not whether it is original. This is the **single most
|
||
important ordering rule in the whole pipeline.** In the Seres production this
|
||
sweep was originally run *after* the texts were locked (Step 8), which forced
|
||
reopening locked texts — the biggest single process error of the series (plan
|
||
§0.4). **It runs here, FØR lås / before lock, without exception.**
|
||
|
||
> **Order assertion (enforced).** This persona sweep (Step 6) precedes lock
|
||
> (Step 8). The pipeline may NOT proceed to Step 8 until the primær persona
|
||
> returns a clean JA from this sweep. A wiring that locked first and reviewed
|
||
> after would reproduce the exact Seres failure — do not do it. `[GATE]`
|
||
|
||
**Procedure:**
|
||
|
||
1. **Load the active personas** chosen in Step 1, with exactly one marked
|
||
**primær**. Each persona's five fields (rolle, avkobler, overbeviser,
|
||
ekspertise, sjargong) come from `config/personas.local.md` (or the template).
|
||
|
||
2. **Fan out one `persona-reviewer` call per persona, in parallel** — issue them
|
||
in a SINGLE message (multiple `Task` tool-uses, `subagent_type:
|
||
linkedin-thought-leadership:persona-reviewer`), from THIS command layer in the
|
||
foreground (principle 4).
|
||
Pass each call its persona name and **`mode: resonans`** (the before-lock mode
|
||
— all six axes, ≤5 flags as direction). This is NOT conversion mode, which is
|
||
the post-lock hook-gate in Step 9. One persona per run — never mix two.
|
||
|
||
3. **Collect verdicts and gate.** Each call returns per-axis flags
|
||
(LØST/DELVIS/IKKE), ≤5 direction-only flags, a per-persona verdict (JA/NEI),
|
||
and a gate decision. Aggregate per the agent's rule:
|
||
- **primær JA** + no real (non-ceiling) sekundær IKKE → PASS, ready to lock.
|
||
- **primær NEI**, or a fixable DELVIS/IKKE → REWORK.
|
||
- **primær NEI on Krok or Leder-takeaway** → BLOCK (the reader never starts,
|
||
or leaves with nothing to do) — must be reworked before lock.
|
||
A *sekundær* NEI from a role mismatch or expertise ceiling is a SIGNAL the gate
|
||
works — accept it; do not distort the text to chase it (plan §0.5, "primær
|
||
trumfer"). The jury returns **direction only** — the editor (this session)
|
||
holds the pen; never paste a persona's rewritten copy. `[GATE]`
|
||
|
||
4. **Convergence loop.** If the gate is REWORK/BLOCK, fold the flags into the
|
||
draft **by tightening** (the Step 4 rule holds — close the gap, hold the length
|
||
flat), then **re-run the same `persona-reviewer` calls** against the revised
|
||
draft. Each round re-judges every prior flag as LØST / DELVIS / IKKE. Loop
|
||
until the primær returns a clean JA (in Seres this took 2 rounds). Re-run only
|
||
the personas whose verdicts are still open.
|
||
|
||
5. **Persist + checkpoint state.** Record the final per-persona verdicts and the
|
||
resolved flags in the edition-HANDOVER §5 (method / persona calibration), set
|
||
`currentPhase: "persona-sweep-prelock"` in `edition-state.json`, and append a "persona sweep
|
||
PASS (primær JA) → next: lock/delivery" pointer to the HANDOVER §6.
|
||
|
||
```
|
||
Persona sweep complete (BEFORE lock).
|
||
- Personas run: <N> (primær: <name>)
|
||
- Convergence rounds: <N>
|
||
- primær verdict: JA (else: still NEI — loop open, NOT ready to lock)
|
||
- Accepted sekundær ceiling-NOs (signal, not failure): <N or none>
|
||
Gate: [PASS — primær JA, ready to lock] (else REWORK/BLOCK)
|
||
Next: Step 7 — Annotation (optional), then Step 8 — LOCK → delivery.
|
||
```
|
||
|
||
---
|
||
|
||
## Step 7: Annotation — optional annotatable review HTML
|
||
|
||
Before locking, you may render the draft as a self-contained, annotatable HTML
|
||
page for one last manual read in the browser (the same pencil-toggle annotation
|
||
surface the render scripts ship). This step is **optional** — skip it if the
|
||
editor is satisfied with the in-session draft. It does not gate lock.
|
||
|
||
**Procedure:**
|
||
|
||
1. **Confirm the draft path.** The consistency- and persona-passed draft from
|
||
Steps 4–6 is the `NN-utkast.md` (NN = zero-padded edition number) in the
|
||
series folder, with YAML front matter (`title`, etc.).
|
||
|
||
2. **Render the review HTML.** `render/build-html.mjs` writes to `./review/`
|
||
relative to the *current working directory*, so run it **with cwd = the
|
||
series folder** (not the plugin). Pass the draft file:
|
||
|
||
```bash
|
||
cd <serie-mappe> && node "${CLAUDE_PLUGIN_ROOT}/render/build-html.mjs" NN-utkast.md
|
||
```
|
||
|
||
**Check the exit code (N3 — do not assume success).** As of S14/F7 the exit
|
||
code is authoritative: `build-html.mjs` exits **non-zero when zero HTML files
|
||
were produced** (e.g. a typo'd/missing filename — it prints `Fant ikke:` then
|
||
`Ingen HTML produsert …`), and exits 0 only when at least one file was written.
|
||
Still confirm the expected output file exists — verify
|
||
`<serie-mappe>/review/NN-utkast.html` is present, not just exit 0. Report the
|
||
stderr and do NOT advance the phase if the file is missing.
|
||
|
||
3. **Hand off the link.** On success the script prints `Skrev <path> (<KB>)`.
|
||
Surface `<serie-mappe>/review/NN-utkast.html` as a `file://` link for the
|
||
editor's manual annotation pass. Fold any resulting edits back **by
|
||
tightening** (the Step 4 rule still holds) — then, if the edit was
|
||
substantive, re-run the affected sweep (Step 5 or 6) before proceeding.
|
||
|
||
4. **Persist.** Set `currentPhase: "annotation"` in `edition-state.json` and
|
||
append an "annotation rendered (optional) → next: lock/delivery" pointer to
|
||
the HANDOVER §6. If skipped, note "annotation skipped" and move on.
|
||
|
||
```
|
||
Annotation (optional).
|
||
- Rendered: <serie-mappe>/review/NN-utkast.html (or: skipped)
|
||
- build-html exit: 0 (else: non-zero — review HTML NOT produced, see stderr)
|
||
Next: Step 8 — LOCK → delivery.
|
||
```
|
||
|
||
---
|
||
|
||
## Step 8: LOCK → delivery — POST.html "all in one place"
|
||
|
||
This is the **lock**. Only enter it once the Step 6 persona sweep returned a
|
||
clean primær JA and the Step 5 fact-check has no unresolved 🔴. Locking
|
||
produces the editor's single delivery artifact — `POST.html`, the
|
||
"all-in-one-place" page that carries the edition text plus its distribution
|
||
(delingstekst) copy, ready to paste into LinkedIn.
|
||
|
||
> **Order assertion (enforced).** Lock (Step 8) runs AFTER the pre-lock persona
|
||
> sweep (Step 6) and BEFORE the hook/conversion gate (Step 9). Reversing lock
|
||
> and the pre-lock sweep reproduces the exact Seres failure (reopening locked
|
||
> texts) — see Step 6. The post-lock hook-gate (Step 9) judges only the
|
||
> distribution hook and never reopens the locked body. `[GATE]`
|
||
|
||
**Procedure:**
|
||
|
||
1. **Confirm lock preconditions.** In `edition-state.json`: the article's
|
||
`factcheckLog` has no open 🔴 and `personaSweep.resonance` recorded a primær
|
||
JA. If either is missing, STOP — return to Step 5/6. Do not lock past an
|
||
open gate.
|
||
|
||
2. **Confirm the delivery inputs in the series folder.**
|
||
`render/build-linkedin.mjs` reads, relative to cwd (`<serie>/linkedin/`):
|
||
- `linkedin/edition-config.json` — calendar, freshness, cover credit/caption.
|
||
Template + schema: `${CLAUDE_PLUGIN_ROOT}/config/edition-config.template.json`.
|
||
- `linkedin/edition-delingstekst.md` — the per-edition distribution text (and
|
||
the `samle` post). Template: `${CLAUDE_PLUGIN_ROOT}/config/edition-delingstekst.template.md`,
|
||
whose header documents the exact `## Del N —` / `## Samle` section grammar the
|
||
renderer parses.
|
||
|
||
Both inputs are **optional and graceful** (renderer degrades, does not throw):
|
||
a missing or malformed `edition-config.json` falls back to empty defaults, and a
|
||
missing `edition-delingstekst.md` yields no distribution copy while the article
|
||
`POST.html` still builds. Provide both for a complete delivery — the
|
||
distribution hook is what Step 9 gates.
|
||
|
||
3. **Render POST.html.** Run with **cwd = the series folder** (the script
|
||
resolves `linkedin/` from cwd and writes `linkedin/NN/POST.html`). The draft
|
||
filename MUST keep its two-digit `NN` prefix or the script skips it
|
||
(`↷ hopper over … (ikke NN-prefiks)`):
|
||
|
||
```bash
|
||
cd <serie-mappe> && node "${CLAUDE_PLUGIN_ROOT}/render/build-linkedin.mjs" NN-utkast.md
|
||
```
|
||
|
||
**Check the exit code (N3 — do not assume success).** Confirm exit 0 AND
|
||
that `linkedin/NN/POST.html` now exists (the `✓ linkedin/NN/POST.html`
|
||
line). A skip-warning with exit 0 but no NN file means the prefix was wrong —
|
||
treat as failure, rename, re-run. Surface any throw (missing config /
|
||
delingstekst) with its stderr.
|
||
|
||
4. **Lock the article.** Set `locked: true`, `status: "locked"`, and
|
||
`currentPhase: "lock-delivery"` in `edition-state.json`. Surface
|
||
`<serie-mappe>/linkedin/NN/POST.html` as a `file://` link. From here the
|
||
body is frozen — Step 9 may only adjust the distribution hook, never the
|
||
locked edition text.
|
||
|
||
```
|
||
LOCK → delivery.
|
||
- Preconditions: factcheck 🔴 = none, persona resonans primær = JA (else: STOP)
|
||
- Delivered: <serie-mappe>/linkedin/NN/POST.html
|
||
- build-linkedin exit: 0 + POST.html present (else: non-zero / skip — NOT locked)
|
||
- Article status: locked
|
||
Next: Step 9 — Hook / conversion gate (post-lock).
|
||
```
|
||
|
||
---
|
||
|
||
## Step 9: Hook / conversion gate — "would YOU click?" (AFTER lock)
|
||
|
||
The locked edition still has to earn the click. The **distribution text** — the
|
||
delingstekst hook that fronts the post in the feed — now faces a final binary
|
||
gate: would the primær persona, scrolling, actually stop and open this? This is
|
||
the post-lock conversion sweep, distinct from the pre-lock resonance sweep
|
||
(Step 6): it judges the **hook only**, binary, never the body.
|
||
|
||
> **Order assertion (enforced).** This hook-gate (Step 9) runs AFTER lock
|
||
> (Step 8) — it operates on the distribution text of an already-locked edition
|
||
> and must never reopen the locked body. If the hook fails, you revise the
|
||
> *delingstekst* (and re-render via Step 8's `build-linkedin.mjs`), not the
|
||
> edition text. This ordering is the inverse of the resonance sweep, which runs
|
||
> BEFORE lock — keeping the two apart is what fixed the Seres process. `[GATE]`
|
||
|
||
**Procedure:**
|
||
|
||
1. **Take the distribution hook** — the first two lines of the edition's entry
|
||
in `linkedin/edition-delingstekst.md` (and the `samle` hook, if shipping the
|
||
collected post). This is what the reader sees before "…see more".
|
||
|
||
2. **Run `persona-reviewer` in conversion mode** (`subagent_type:
|
||
linkedin-thought-leadership:persona-reviewer`) for the **primær** persona
|
||
only, from THIS command layer in the foreground. Pass
|
||
**`mode: konverter`** (the after-lock, hook-only mode — NOT resonans). The
|
||
agent returns a single binary verdict, **JA / NEI**, on «would YOU click?» —
|
||
no axis scoring, no flags, no rewritten copy (principle: the jury judges,
|
||
the editor writes).
|
||
|
||
3. **Gate on the binary.**
|
||
- **JA** → the hook converts. Proceed to Step 10.
|
||
- **NEI** → the hook does not earn the click. Revise the **delingstekst hook
|
||
only** (sharpen the krok by tightening — close the gap, hold the body
|
||
frozen), **re-render POST.html** via Step 8's `build-linkedin.mjs` (the
|
||
body stays locked; only the distribution copy changed), and re-run the
|
||
conversion check. Loop until JA. `[GATE]`
|
||
|
||
4. **Persist.** Record the conversion verdict in
|
||
`edition-state.json` → `articles.NN.personaSweep.conversion` (and HANDOVER §5),
|
||
and set `currentPhase: "hook-conversion-gate"`.
|
||
|
||
```
|
||
Hook / conversion gate (post-lock).
|
||
- Primær persona: <name> mode: konverter (hook only)
|
||
- Verdict: JA — hook converts (else: NEI — revise delingstekst hook, re-render, re-check)
|
||
- Body: untouched (locked in Step 8)
|
||
Gate: [PASS — JA] (else loop on the distribution hook)
|
||
Next: Step 10 — Scheduling.
|
||
```
|
||
|
||
---
|
||
|
||
## Step 10: Scheduling — register the edition in the plugin queue
|
||
|
||
The locked, conversion-passed edition is ready to ship. Register it in the
|
||
plugin's native scheduling queue so it shows up in `/linkedin:calendar`
|
||
(both for viewing and for the publish action) and the posting-time
|
||
reminders — the same queue the short-form pipeline uses. The edition is
|
||
now a first-class scheduled post.
|
||
|
||
**Procedure:**
|
||
|
||
1. **Pick the slot.** Confirm the target `scheduled_date` (YYYY-MM-DD) and
|
||
`scheduled_time` from the edition-config calendar / the series brief. If the
|
||
slot is unset, ask once (Step 1's calibration may already have settled it).
|
||
|
||
2. **Register via `queue-manager.mjs`.** Add one queue entry for the edition,
|
||
reusing the short-form queue contract — `queueAdd(id, draftPath, schedDate,
|
||
schedTime, pillar, format, hookPreview, charCount)`:
|
||
- `id` — stable edition id (e.g. `<series-slug>-NN`)
|
||
- `draftPath` — the locked `linkedin/NN/POST.html`
|
||
- `format` — `newsletter` (so calendar/reporting can distinguish long-form)
|
||
- `hookPreview` — the conversion-passed distribution hook (first ~80 chars)
|
||
|
||
```bash
|
||
node -e 'import("'"${CLAUDE_PLUGIN_ROOT}"'/hooks/scripts/queue-manager.mjs").then(q => q.queueAdd("<series-slug>-NN","<serie-mappe>/linkedin/NN/POST.html","YYYY-MM-DD","HH:MM","<pillar>","newsletter","<hook ~80c>",<charCount>))'
|
||
```
|
||
|
||
The function appends to `assets/drafts/queue.json` with `status:
|
||
"scheduled"` and returns the new entry.
|
||
|
||
3. **Persist + close the edition.** Set the article's `status: "scheduled"`,
|
||
`scheduled: "<YYYY-MM-DD HH:MM>"`, and `currentPhase: "scheduling"` in
|
||
`edition-state.json`. Append a closing "edition scheduled → mark live via
|
||
`/linkedin:calendar` on <date>" pointer to the HANDOVER §6. The pipeline is
|
||
complete.
|
||
|
||
```
|
||
Scheduling.
|
||
- Queue entry: <series-slug>-NN → assets/drafts/queue.json (status: scheduled)
|
||
- Slot: YYYY-MM-DD HH:MM format: newsletter
|
||
- Article status: scheduled
|
||
Edition complete. Visible in /linkedin:calendar; mark live via /linkedin:calendar (publish action).
|
||
```
|
||
|
||
---
|
||
|
||
## Reference Files
|
||
|
||
- `${CLAUDE_PLUGIN_ROOT}/config/edition-state.template.json` — edition-state schema (13 phases including v2.1 skeleton + spine-prose gates)
|
||
- `${CLAUDE_PLUGIN_ROOT}/config/edition-config.template.json` — static delivery metadata schema (calendar, freshness, credit, captions) — Step 8
|
||
- `${CLAUDE_PLUGIN_ROOT}/config/edition-delingstekst.template.md` — distribution-copy grammar (`## Del N —` / `## Samle`) — Steps 8/9
|
||
- `${CLAUDE_PLUGIN_ROOT}/config/edition-HANDOVER.template.md` — narrative production-state structure (§1–§6) — Step 0
|
||
- `${CLAUDE_PLUGIN_ROOT}/config/personas.template.md` — reusable reader personas + "primær trumfer" rule
|
||
- `${CLAUDE_PLUGIN_ROOT}/agents/fact-checker.md` — Step 5 fact-check agent (risk-sorted, guilty-until-disproven)
|
||
- `${CLAUDE_PLUGIN_ROOT}/agents/persona-reviewer.md` — Step 2.5/6/9 reader jury (skeleton + resonance + conversion modes)
|
||
- `${CLAUDE_PLUGIN_ROOT}/commands/react.md` — multi-source synthesis discipline (reused in Step 2)
|
||
- `${CLAUDE_PLUGIN_ROOT}/assets/voice-samples/authentic-voice-samples.md` — voice matching
|
||
- `${CLAUDE_PLUGIN_ROOT}/references/longform-quality-rules.md` — canonical long-form rules (Steps 2.5, 3a, 3b, 4–9 all reference)
|
||
- `${CLAUDE_PLUGIN_ROOT}/render/build-linkedin.mjs` — POST.html delivery (Step 8)
|
||
- `${CLAUDE_PLUGIN_ROOT}/render/build-html.mjs` — annotatable review renderer (Step 7)
|