From 7ebd28cb0b74eb5cb79934b4e8cdcf44ea881880 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Thu, 28 May 2026 22:49:19 +0200 Subject: [PATCH] =?UTF-8?q?feat(linkedin):=20v2.3.0=20=E2=80=94=20Step=207?= =?UTF-8?q?.5=20visual-assets=20phase=20in=20/linkedin:newsletter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Endring 7 from the change-spec: make visual assets an explicit pipeline phase. New Step 7.5 (visual-assets) between annotation (Step 7) and lock (Step 8): cover (+ optional inline figures) or carousel deck, generated and operator-gated BEFORE lock so build-linkedin.mjs picks up cover.png at lock without a post-lock re-render. Pipeline 13 → 14 phases. - commands/newsletter.md: Step 7.5 section, pipeline overview + build-status, resumption table (annotation → 7.5; new visual-assets → 8), Step 8 precondition, reference-file list. - config/edition-state.template.json: visual-assets phase + additive articles.NN.visualAssets schema (format / cover / figures / carousel). - config/image-credit-caption.template.md (new): motif + credit + caption table, honest-about-AI credit, naming convention. - Two generation routes, no lock-in: default mcp-image (cover-v-kandidat.png) or external cover-raw.png. Operator-gate via SendUserFile → cp to cover.png. Carousel branch reuses render/build-carousel.mjs. - Doc/orchestration-only — no new code. Commands (24) + agents (15) unchanged. - Version sync 2.2.0 → 2.3.0 across plugin.json, CHANGELOG, README, CLAUDE.md, root README + root CLAUDE.md. Correction: spec claimed build-linkedin.mjs handles fig1-4; verified it does NOT — it embeds only cover.png by fixed name; figures are referenced in the draft markdown and uploaded manually. Step 7.5 documents actual behavior. All 8 acceptance criteria met. JSON valid (14 phases); 20/20 render tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 2 +- README.md | 9 +- .../.claude-plugin/plugin.json | 4 +- .../linkedin-thought-leadership/CHANGELOG.md | 22 ++ plugins/linkedin-thought-leadership/CLAUDE.md | 6 +- plugins/linkedin-thought-leadership/README.md | 21 +- .../commands/newsletter.md | 190 ++++++++++++++++-- .../config/edition-state.template.json | 16 +- .../config/image-credit-caption.template.md | 63 ++++++ 9 files changed, 304 insertions(+), 29 deletions(-) create mode 100644 plugins/linkedin-thought-leadership/config/image-credit-caption.template.md diff --git a/CLAUDE.md b/CLAUDE.md index a84c54f..2bbb150 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,7 +9,7 @@ plugins/ ai-psychosis/ v1.0.0 — Interaction awareness (sycophancy, reinforcement loops) config-audit/ v3.1.0 — Configuration intelligence (health, opportunities, auto-fix, whats-active) graceful-handoff/ v2.1.0 — Auto-trigger handoff via Stop hook (skill + JSON pipeline + 4-step model-aware context resolution) - linkedin-thought-leadership/ v2.2.0 — Full-spectrum LinkedIn content engine (short-form feed + long-form newsletter). v2.0.0 consolidated surface (27→24 commands, 16→14 agents) + added `/linkedin:newsletter` orchestrator with fact-check + persona-sweep gates BEFORE lock. v2.1.0 added skeleton-gate BEFORE prose (Step 2.5 + Step 3a) + third `persona-reviewer` mode (`skjelett`); pipeline 11→13 phases. v2.2.0 hardens the longform gates (2nd production run): blocking persona hard-fails (mistet meg / doesn't own action / sjargong-mur / modell-navne-katalog → BLOCK), fact-check post-cutoff web-search mandate + orthogonal-to-narrative rule, new `voice-scrubber` agent (Opus, de-AI + Norwegian-chronicle voice; gold standard = approved Norwegian editions NOT English post corpus), render+annotate operator gates (2.5/3a), and edition-state reconciled with STATE.md (`edition-HANDOVER.md` deleted). Agents 14→15; commands unchanged (24). Render pipeline self-hosted (OFL-1.1 fonts). + linkedin-thought-leadership/ v2.3.0 — Full-spectrum LinkedIn content engine (short-form feed + long-form newsletter). v2.0.0 consolidated surface (27→24 commands, 16→14 agents) + added `/linkedin:newsletter` orchestrator with fact-check + persona-sweep gates BEFORE lock. v2.1.0 added skeleton-gate BEFORE prose (Step 2.5 + Step 3a) + third `persona-reviewer` mode (`skjelett`); pipeline 11→13 phases. v2.2.0 hardened the longform gates (2nd production run): blocking persona hard-fails, fact-check post-cutoff web-search mandate + orthogonal-to-narrative rule, new `voice-scrubber` agent (Opus, de-AI + Norwegian-chronicle voice; gold standard = approved Norwegian editions NOT English post corpus), render+annotate operator gates (2.5/3a), edition-state reconciled with STATE.md (`edition-HANDOVER.md` deleted); agents 14→15. v2.3.0 adds **Step 7.5 (visual-assets)** to `/linkedin:newsletter` — cover (+ inline figures) or carousel deck, generated (default mcp-image; external `cover-raw.png` accepted) + operator-gated via `SendUserFile` BEFORE lock so `build-linkedin.mjs` picks up `cover.png` without a post-lock re-render; pipeline 13→14 phases, new `config/image-credit-caption.template.md`, additive `visualAssets` state, doc/orchestration-only (no new code). Commands unchanged (24); agents 15. Render pipeline self-hosted (OFL-1.1 fonts). llm-security/ v7.7.2 — Security scanning, auditing, threat modeling. HTML report output for all 18 skill commands (render-report CLI + canonical ESM module mirrored bit-identical into the playground). v7.7.2 translated the remaining Norwegian surface text in the playground UI, the canonical renderer, the agent prompts, and the README/CLAUDE.md state sections to English. v7.7.1 stripped the playground to the catalog as the only routable surface. ms-ai-architect/ v1.15.0 — Microsoft AI architecture (Cosmo Skyberg persona) + manual KB-refresh slash command + v3 project-view (sidebar med 17 artifacts + main + import-modal overlay, v2-surface fjernet i v1.15.0) okr/ v1.0.0 — OKR guidance for Norwegian public sector diff --git a/README.md b/README.md index 22f97df..be66f0f 100644 --- a/README.md +++ b/README.md @@ -206,19 +206,20 @@ Key commands: `/architect`, `/architect:ros`, `/architect:security`, `/architect --- -### [LinkedIn Thought Leadership](plugins/linkedin-thought-leadership/) `v2.2.0` +### [LinkedIn Thought Leadership](plugins/linkedin-thought-leadership/) `v2.3.0` Build authentic LinkedIn authority through algorithmic understanding, strategic consistency, and AI-assisted content creation. -v2.2.0 **hardens the longform gates** with the lessons from the second `/linkedin:newsletter` production run (Seres-serien): the persona gate is now **blocking with an explicit hard-fail list** (primær «mistet meg» / doesn't own the action / sjargong-mur / modell-/navne-katalog → BLOCK; «JA med store forbehold» = NEI); fact-check is **orthogonal to narrative strength** with a **post-cutoff web-search mandate** + high-frequency-error checklist; a new **`voice-scrubber`** agent (Opus) does de-AI scrub + Norwegian-chronicle voice-drift correction (gold standard = approved Norwegian editions, NOT the English post corpus); operator gates become **render+annotate** rounds; and per-edition state is **reconciled with the global STATE.md** continuity system (`edition-HANDOVER.md` removed). Agents 14 → 15; commands unchanged (24). v2.1.0's skeleton-gate-before-prose and v2.0.0's full-spectrum surface are preserved. Updated for the January 2026 360Brew algorithm change, which validates your creator profile before distributing content. +v2.3.0 makes **visual assets an explicit pipeline phase** in `/linkedin:newsletter`: new **Step 7.5 (visual-assets)** between annotation (Step 7) and lock (Step 8). The cover (+ optional inline figures) or a carousel deck is generated (default `mcp-image`; external `cover-raw.png` accepted) and operator-gated via `SendUserFile` **before lock**, so `render/build-linkedin.mjs` picks up `cover.png` + the edition-config credit/caption at lock without a post-lock re-render. Pipeline 13 → 14 phases; new `config/image-credit-caption.template.md` + additive `visualAssets` state; doc/orchestration-only (no new code). v2.2.0 **hardened the longform gates** with the lessons from the second production run (Seres-serien): the persona gate is **blocking with an explicit hard-fail list** (primær «mistet meg» / doesn't own the action / sjargong-mur / modell-/navne-katalog → BLOCK); fact-check is **orthogonal to narrative strength** with a **post-cutoff web-search mandate**; a **`voice-scrubber`** agent (Opus) does de-AI scrub + Norwegian-chronicle voice-drift correction; operator gates became **render+annotate** rounds; and per-edition state was **reconciled with the global STATE.md** continuity system (`edition-HANDOVER.md` removed). Commands unchanged (24); 15 agents. v2.1.0's skeleton-gate-before-prose and v2.0.0's full-spectrum surface are preserved. Updated for the January 2026 360Brew algorithm change, which validates your creator profile before distributing content. +- **Visual-assets gate BEFORE lock (v2.3)** — Step 7.5 decides image needs from the article type (method-heavy → 1–2 inline figures; diagnosis-heavy → cover only), briefs each image, generates via mcp-image or an external `cover-raw.png`, surfaces candidates with `SendUserFile`, and copies the approved one to the fixed `cover.png` name. Explicit `format: "carousel"` branch reusing `build-carousel.mjs`. - **Blocking persona hard-fails (v2.2)** — `persona-reviewer` + `personas.template.md` make primær «mistet meg», doesn't-own-the-action, sjargong-mur, and modell-/navne-katalog BLOCK-level rewrites, not annotations. The bar is the primær reader's *clean* JA. - **Fact-check orthogonal to polish (v2.2)** — `fact-checker` web-searches every post-cutoff claim (never confirms from memory) and runs an explicit checklist for person titles, org-varying "standards", over-credited studies, source scope, and founding/release years. - **New `voice-scrubber` agent (v2.2, Opus)** — aggressive de-AI scrub (Pass 1) + Norwegian-chronicle voice-drift correction (Pass 2), calibrated to the **approved Norwegian editions** rather than the English post corpus; wired into Step 4. *New agent — requires a session reload before invokable.* - **Render+annotate operator gates (v2.2)** — Steps 2.5/3a surface an annotatable `file://` review page (`build-html.mjs`) as the primary operator-review flow; `AskUserQuestion` becomes a receipt + fallback. - **STATE.md-reconciled edition state (v2.2)** — narrative state in `/STATE.md` (auto-injected, overwritten each phase), machine state in `edition-state.json`; no separate `edition-HANDOVER.md`. - **Skeleton gate BEFORE prose (v2.1)** — `/linkedin:newsletter` Step 2.5 writes a five-line spine (premiss / problem / anbefaling / gevinst / vei videre) + one-line section pitches to `/NN-skjelett.md`. Operator-gate AND parallel persona-skjelett-sweep must both return JA before the pipeline can advance. Step 3a follows with spine prose (one paragraph per section) and its own operator-gate. Encodes the Maskinrommet writing-contract §A discipline into the pipeline. -- **Long-form `/linkedin:newsletter` orchestrator** — multi-session 13-phase pipeline (research → **skeleton + persona-skjelett-sweep** → **spine prose** → full prose draft → **de-AI/voice scrub** → fact-check sweep → persona sweep → lock → delivery → hook-gate) with maintained edition-state. Newsletter editions, essays, series articles +- **Long-form `/linkedin:newsletter` orchestrator** — multi-session 14-phase pipeline (research → **skeleton + persona-skjelett-sweep** → **spine prose** → full prose draft → **de-AI/voice scrub** → fact-check sweep → persona sweep → annotation → **visual assets (cover/figures or carousel, BEFORE lock)** → lock → delivery → hook-gate) with maintained edition-state. Newsletter editions, essays, series articles - **Three longform-quality gate agent modes** — `fact-checker` (Opus, verifies every claim against primary sources) and `persona-reviewer` (Opus) with three modes: **`skjelett`** (Step 2.5, before prose), **`resonans`** (Step 6, before lock), **`konverter`** (Step 9, after lock) - **Render pipeline in-plugin** — `build-html.mjs`, `build-pdf.mjs`, `build-linkedin.mjs`, `build-carousel.mjs` with self-hosted Newsreader/Inter/JetBrains Mono under OFL-1.1 - **Guided onboarding** — `/linkedin:onboarding` walks new users through profile → setup → first post in one flow @@ -233,7 +234,7 @@ v2.2.0 **hardens the longform gates** with the lessons from the second `/linkedi Key commands: `/linkedin:onboarding`, `/linkedin:post`, `/linkedin:quick`, `/linkedin:newsletter`, `/linkedin:carousel`, `/linkedin:react`, `/linkedin:report` -14 specialized agents · 24 commands · 6 skills · 9 hooks · 24 reference docs +15 specialized agents · 24 commands · 6 skills · 9 hooks · 24 reference docs → [Full documentation](plugins/linkedin-thought-leadership/README.md) diff --git a/plugins/linkedin-thought-leadership/.claude-plugin/plugin.json b/plugins/linkedin-thought-leadership/.claude-plugin/plugin.json index 2c52a83..c501492 100644 --- a/plugins/linkedin-thought-leadership/.claude-plugin/plugin.json +++ b/plugins/linkedin-thought-leadership/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "linkedin-thought-leadership", - "version": "2.2.0", - "description": "Full-spectrum LinkedIn content engine — feed posts, carousels, video scripts, and long-form newsletter editions — with the January 2026 360Brew algorithm baked in. v2.2 hardens the longform gates: blocking persona hard-fails, post-cutoff fact-check mandate, a Norwegian-chronicle de-AI voice-scrubber, render+annotate operator gates, and STATE.md-reconciled edition state.", + "version": "2.3.0", + "description": "Full-spectrum LinkedIn content engine — feed posts, carousels, video scripts, and long-form newsletter editions — with the January 2026 360Brew algorithm baked in. v2.3 adds Step 7.5 (visual assets) to /linkedin:newsletter: cover + inline figures or carousel deck, generated and operator-gated BEFORE lock (pipeline 13→14 phases). v2.2 hardened the longform gates: blocking persona hard-fails, post-cutoff fact-check mandate, a Norwegian-chronicle de-AI voice-scrubber, render+annotate operator gates, and STATE.md-reconciled edition state.", "author": { "name": "Kjell Tore Guttormsen" }, diff --git a/plugins/linkedin-thought-leadership/CHANGELOG.md b/plugins/linkedin-thought-leadership/CHANGELOG.md index 4c69be6..1ddb9de 100644 --- a/plugins/linkedin-thought-leadership/CHANGELOG.md +++ b/plugins/linkedin-thought-leadership/CHANGELOG.md @@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.3.0] - 2026-05-28 + +### Summary +Visual assets become an explicit pipeline phase in `/linkedin:newsletter`. Until now images (cover + inline figures) were produced ad-hoc *outside* the 13-phase pipeline and referenced manually from `edition-config.json` + `linkedin/NN/cover.png` — even though a cover is mandatory (KTG cover-directive 27.05: «TLDR on top + at least one figure per article») and must coordinate with the text. v2.3.0 adds **Step 7.5 — Visual assets**, between annotation (Step 7) and lock (Step 8), so the cover is generated, operator-gated, and approved *before* lock — `render/build-linkedin.mjs` picks up `linkedin/NN/cover.png` at lock, so generating images after lock would force a re-render and break the lock. Pipeline 13 → 14 phases. Doc/orchestration-only — no new code; mcp-image is the default generation route but the interface stays pluggable (external `cover-raw.png` accepted), and the carousel branch reuses the existing `render/build-carousel.mjs`. Backward-compatible: the only state-shape change is additive (`visualAssets`), and existing editions resume by `currentPhase` (`annotation` now resumes at Step 7.5 instead of Step 8 — an intended deterministic improvement). + +### Added +- **Step 7.5 — Visual assets** in `commands/newsletter.md` (between Step 7 annotation and Step 8 lock). Decides image needs from the article type (method-heavy → 1–2 inline figures, diagnosis-heavy → cover only), writes a per-image brief, generates via two routes (default `mcp__mcp-image__generate_image` → `cover-v-kandidat.png`; external → `cover-raw.png`), runs the operator-gate (candidates surfaced via `SendUserFile`, approval copied to the fixed `cover.png` name — the same render+annotate pattern as Steps 2.5/3a), and records credit + caption. Explicit **carousel branch** (`format: "carousel"`): render the deck via `render/build-carousel.mjs` instead of cover+inline. +- **`visual-assets` phase string** + **`articles.NN.visualAssets` schema** in `config/edition-state.template.json` `_doc.phases` (13 → 14 phases) — `{ format, cover: { brief, route, candidates[], approved, status }, figures: [ { id, brief, placement, status } ], carousel }`. Additive; default `format: "standard"`. +- **`config/image-credit-caption.template.md`** (new) — cover motif + credit + caption table, modelled on the established Seres-serien `image-credit-caption.md`. Honest-about-AI credit per the verification duty; documents the cover/figure naming convention. +- **Naming convention documented** in `commands/newsletter.md` Step 7.5 — `cover.png` (approved, fixed) / `cover-v-kandidat.png` (attempts) / `cover-raw.png` (external pre-edit source) / `fig.png` (inline). Consistent with existing series use. + +### Changed +- **`/linkedin:newsletter` pipeline overview** — 13 → 14 phases; the overview table, build-status note, and reference-file list reflect Step 7.5. +- **Resumption table** in `commands/newsletter.md` — `currentPhase: "annotation"` now resumes at **Step 7.5** (was Step 8); new `visual-assets` row resumes at Step 8. `persona-sweep-prelock` flows Step 7 → Step 7.5. +- **Step 8 lock preconditions** now require the gated `visualAssets` (approved `cover.png` for `standard` format, or approved `carousel.pdf` for `carousel` format) alongside the existing fact-check (no open 🔴) and primær-JA gates. +- README, CLAUDE.md, root README, root CLAUDE.md, plugin.json version + descriptions. + +### Not changed (explicit non-deltas) +- **Steps 0–7, 8 body, 9, 10** in `/linkedin:newsletter` — contract unchanged apart from the Step 7→7.5→8 wiring and the additive Step 8 precondition. +- **Renderers** — `render/build-linkedin.mjs`, `build-html.mjs`, `build-carousel.mjs` untouched (Step 7.5 *calls* them; no code change). `build-linkedin.mjs` still reads `cover.png` by fixed name and does not embed `fig.png` (figures are referenced in the draft and uploaded manually) — Step 7.5 documents this actual behavior rather than overstating it. +- **Hooks, scripts, command count (24), agent count (15)** — all unchanged. + ## [2.2.0] - 2026-05-28 ### Summary diff --git a/plugins/linkedin-thought-leadership/CLAUDE.md b/plugins/linkedin-thought-leadership/CLAUDE.md index e1795bf..0ff05dc 100644 --- a/plugins/linkedin-thought-leadership/CLAUDE.md +++ b/plugins/linkedin-thought-leadership/CLAUDE.md @@ -1,6 +1,6 @@ -# LinkedIn Thought Leadership Plugin (v2.2.0) +# LinkedIn Thought Leadership Plugin (v2.3.0) -Full-spectrum LinkedIn content engine — short-form feed posts, carousels, video scripts, and long-form newsletter editions — with the January 2026 360Brew algorithm baked in. v2.0.0 consolidated the surface (27 commands → 24, 16 agents → 14) while adding the long-form `/linkedin:newsletter` orchestrator + two longform-quality gate agents (`fact-checker`, `persona-reviewer`). v2.1.0 added two gates BEFORE prose (Step 2.5 skeleton + Step 3a spine prose) + a third `persona-reviewer` mode (`skjelett`). **v2.2.0** hardens the longform gates with the lessons from the second production run (Seres-serien): the persona gate is now **blocking with an explicit hard-fail list** (primær «mistet meg» / doesn't own the action / sjargong-mur / modell-/navne-katalog → BLOCK; «JA med store forbehold» = NEI); fact-check is declared **orthogonal to narrative strength** with a **post-cutoff web-search mandate** + high-frequency-error checklist; a new **`voice-scrubber`** agent (Opus) does de-AI scrub + Norwegian-chronicle voice-drift correction (gold standard = approved Norwegian editions, NOT the English post corpus) wired into Step 4; operator gates become **render+annotate** rounds (Steps 2.5/3a); and per-edition production state is **reconciled with the global STATE.md** continuity system — `edition-HANDOVER.md` is gone. Agents 14 → 15; commands unchanged (24). +Full-spectrum LinkedIn content engine — short-form feed posts, carousels, video scripts, and long-form newsletter editions — with the January 2026 360Brew algorithm baked in. v2.0.0 consolidated the surface (27 commands → 24, 16 agents → 14) while adding the long-form `/linkedin:newsletter` orchestrator + two longform-quality gate agents (`fact-checker`, `persona-reviewer`). v2.1.0 added two gates BEFORE prose (Step 2.5 skeleton + Step 3a spine prose) + a third `persona-reviewer` mode (`skjelett`). v2.2.0 hardened the longform gates with the lessons from the second production run (Seres-serien): blocking persona hard-fails, a post-cutoff fact-check mandate, a `voice-scrubber` agent, render+annotate operator gates, and STATE.md-reconciled edition state. **v2.3.0** makes **visual assets an explicit pipeline phase** — new **Step 7.5 (visual-assets)** between annotation (Step 7) and lock (Step 8): cover (+ optional inline figures) or a carousel deck, generated (default `mcp-image`, external `cover-raw.png` accepted) and **operator-gated BEFORE lock** so `render/build-linkedin.mjs` picks up `cover.png` at lock without a post-lock re-render. Pipeline 13 → 14 phases; new `config/image-credit-caption.template.md`; additive `visualAssets` state. Doc/orchestration-only — no new code. Agents unchanged (15); commands unchanged (24). ## Architecture @@ -46,7 +46,7 @@ All content commands (post, quick, react, pipeline, first-post, video, multiplat | `/linkedin:post` | Full post creation (10-15 min) | | `/linkedin:quick` | 5-minute quick post (3-line formula) + 8 post-type templates | | `/linkedin:pipeline` | Full end-to-end content pipeline | -| `/linkedin:newsletter` | Long-form orchestrator: newsletter edition / essay / series article — multi-session 13-phase pipeline with **skeleton + spine-prose gates BEFORE prose (v2.1)** and fact-check + persona-sweep BEFORE lock | +| `/linkedin:newsletter` | Long-form orchestrator: newsletter edition / essay / series article — multi-session 14-phase pipeline with **skeleton + spine-prose gates BEFORE prose (v2.1)**, fact-check + persona-sweep BEFORE lock, and **visual-assets gate BEFORE lock (Step 7.5, v2.3)** | | `/linkedin:batch` | Create a full week of content | | `/linkedin:calendar` | View/manage post scheduling queue + publish action (mark scheduled posts as published) | | `/linkedin:carousel` | Structured multi-slide carousel generator | diff --git a/plugins/linkedin-thought-leadership/README.md b/plugins/linkedin-thought-leadership/README.md index 298a330..ab2724d 100644 --- a/plugins/linkedin-thought-leadership/README.md +++ b/plugins/linkedin-thought-leadership/README.md @@ -6,7 +6,7 @@ *AI-generated: all code produced by Claude Code through dialog-driven development. [Full disclosure →](../../README.md#ai-generated-code-disclosure)* -![Version](https://img.shields.io/badge/version-2.2.0-blue) +![Version](https://img.shields.io/badge/version-2.3.0-blue) ![Platform](https://img.shields.io/badge/platform-Claude_Code_Plugin-purple) ![Commands](https://img.shields.io/badge/commands-24-green) ![Agents](https://img.shields.io/badge/agents-15-orange) @@ -14,12 +14,13 @@ ![Reference Docs](https://img.shields.io/badge/reference_docs-24-teal) ![License](https://img.shields.io/badge/license-MIT-lightgrey) -A comprehensive Claude Code plugin that turns LinkedIn from a chore into a full-spectrum content engine — short-form feed posts, carousels, video scripts, and long-form newsletter editions. v2.0.0 consolidated the surface (27 → 24 commands, 16 → 14 agents) and added `/linkedin:newsletter` as a multi-session long-form orchestrator with fact-check + persona-sweep gates BEFORE lock. **v2.1.0** adds two more gates BEFORE prose to `/linkedin:newsletter` — a skeleton gate (Step 2.5) and a spine-prose gate (Step 3a) — encoding the Maskinrommet writing-contract §A discipline (premiss / problem / anbefaling / gevinst / vei videre) into the pipeline itself, so spine errors get caught in minutes at the skeleton stage instead of hours at the resonance stage or a full day post-lock. **v2.2.0** hardens the longform gates with the lessons from the next production run: blocking persona hard-fails (primær «mistet meg» / doesn't own the action / jargon wall / model-name catalog → BLOCK), a post-cutoff fact-check mandate, a new Norwegian-chronicle de-AI voice-scrubber agent, render+annotate operator gates, and edition state reconciled with the global STATE.md continuity system. 24 slash commands, 15 specialized agents, 9 automated hooks, and a 24-document knowledge base grounded in LinkedIn's actual algorithm signals. Updated for the January 2026 **360Brew** algorithm change, where LinkedIn now validates your profile before distributing content. +A comprehensive Claude Code plugin that turns LinkedIn from a chore into a full-spectrum content engine — short-form feed posts, carousels, video scripts, and long-form newsletter editions. v2.0.0 consolidated the surface (27 → 24 commands, 16 → 14 agents) and added `/linkedin:newsletter` as a multi-session long-form orchestrator with fact-check + persona-sweep gates BEFORE lock. **v2.1.0** adds two more gates BEFORE prose to `/linkedin:newsletter` — a skeleton gate (Step 2.5) and a spine-prose gate (Step 3a) — encoding the Maskinrommet writing-contract §A discipline (premiss / problem / anbefaling / gevinst / vei videre) into the pipeline itself, so spine errors get caught in minutes at the skeleton stage instead of hours at the resonance stage or a full day post-lock. v2.2.0 hardened the longform gates with the lessons from the next production run: blocking persona hard-fails (primær «mistet meg» / doesn't own the action / jargon wall / model-name catalog → BLOCK), a post-cutoff fact-check mandate, a new Norwegian-chronicle de-AI voice-scrubber agent, render+annotate operator gates, and edition state reconciled with the global STATE.md continuity system. **v2.3.0** makes visual assets an explicit pipeline phase — new **Step 7.5 (visual-assets)** between annotation and lock: the cover (+ optional inline figures) or a carousel deck is generated and operator-gated *before* lock, so the renderer picks up `cover.png` without a post-lock re-render (pipeline 13 → 14 phases). 24 slash commands, 15 specialized agents, 9 automated hooks, and a 24-document knowledge base grounded in LinkedIn's actual algorithm signals. Updated for the January 2026 **360Brew** algorithm change, where LinkedIn now validates your profile before distributing content. --- ## Table of Contents +- [What's New in v2.3.0](#whats-new-in-v230) - [What's New in v2.2.0](#whats-new-in-v220) - [What's New in v2.1.0](#whats-new-in-v210) - [What's New in v2.0.0](#whats-new-in-v200) @@ -41,6 +42,21 @@ A comprehensive Claude Code plugin that turns LinkedIn from a chore into a full- --- +## What's New in v2.3.0 + +**Visual assets become an explicit pipeline phase.** Until now, images (cover + inline figures) were produced ad-hoc *outside* the `/linkedin:newsletter` pipeline and referenced by hand — even though a cover is mandatory and has to coordinate with the text. v2.3.0 adds **Step 7.5 — Visual assets** between annotation (Step 7) and lock (Step 8), so visuals are resolved *before* the edition locks. + +- **New Step 7.5 — Visual assets** (`commands/newsletter.md`). Decides image needs from the article type (method-heavy → 1–2 inline figures; diagnosis-heavy → cover only), writes a per-image brief, generates, runs an operator-gate, and records credit + caption. It runs **before lock on purpose**: `render/build-linkedin.mjs` picks up `linkedin/NN/cover.png` + the edition-config credit/caption at lock, so generating images after lock would force a re-render and break the lock. +- **Two generation routes, no lock-in** — default `mcp__mcp-image__generate_image` (Nano Banana Pro) writing `cover-v-kandidat.png`, or an external route (DALL·E / Midjourney / photographer) via a `cover-raw.png` the operator drops in. The interface is pluggable (path-in / path-out); mcp-image is the default, not a hard dependency. +- **Operator-gate = the Step 2.5/3a pattern, for images** — every candidate is surfaced via `SendUserFile` for side-by-side comparison; on approval the chosen candidate is copied to the fixed `cover.png` name that the renderer reads. +- **Explicit carousel branch** — `format: "carousel"` editions render a typografisk slide-deck via the existing `render/build-carousel.mjs` instead of cover+inline. +- **New `config/image-credit-caption.template.md`** — cover motif + credit + caption table (honest-about-AI credit per the verification duty), modelled on the established series convention. Documents the `cover.png` / `cover-v-kandidat.png` / `cover-raw.png` / `fig.png` naming. +- **`visual-assets` phase + additive `visualAssets` state** in `config/edition-state.template.json`. Pipeline grows 13 → 14 phases; resumption stays deterministic (`annotation` now resumes at Step 7.5). + +Doc/orchestration-only — **no new code**. Commands (24) and agents (15) unchanged. Backward-compatible: the only state-shape change is additive. + +--- + ## What's New in v2.2.0 **Longform gates hardened** — the second production run (Seres-serien) surfaced six concrete weaknesses; v2.2.0 closes all of them. A chronicle built as a model/name catalog passed review (flags were read as notes, not stop-signs) and nearly shipped; the rewrite was better but introduced fresh factual errors. The gates now make both failure modes blocking. @@ -562,6 +578,7 @@ Scheduled posts are tracked in `assets/drafts/queue.json`: | Version | Date | Highlights | |---------|------|-----------| +| **2.3.0** | 2026-05-28 | Visual assets as an explicit pipeline phase. New **Step 7.5 — Visual assets** in `/linkedin:newsletter` (between annotation and lock): cover (+ optional inline figures) or carousel deck, generated (default `mcp-image`; external `cover-raw.png` accepted) and operator-gated via `SendUserFile` BEFORE lock so `build-linkedin.mjs` picks up `cover.png` without a post-lock re-render. Explicit `format: "carousel"` branch reusing `build-carousel.mjs`. New `config/image-credit-caption.template.md`; additive `visualAssets` state + naming convention (`cover.png` / `cover-v-kandidat.png` / `cover-raw.png` / `fig.png`). Pipeline 13 → 14 phases. Doc/orchestration-only (no new code); commands (24) + agents (15) unchanged. | | **2.2.0** | 2026-05-28 | Longform gates hardened (2nd production run). Persona gate blocking with explicit hard-fail list (primær mistet meg / doesn't own action / sjargong-mur / modell-navne-katalog → BLOCK; «JA med forbehold» = NEI). Fact-check post-cutoff web-search mandate + high-frequency-error checklist. New `voice-scrubber` agent (Opus): de-AI scrub + Norwegian-chronicle voice-drift, gold standard = approved Norwegian editions (NOT the English post corpus). Render+annotate operator gates (Steps 2.5/3a). Edition state reconciled with STATE.md (ONE-system); `edition-HANDOVER.template.md` deleted. 14 → 15 agents; commands unchanged (24). | | **2.1.0** | 2026-05-28 | Skeleton gate BEFORE prose in `/linkedin:newsletter`. New Step 2.5 (skeleton + section pitch, operator-gate + persona-skjelett-sweep) and Step 3a (spine prose, operator-gate) split the old Step 3 into pre-prose stages with their own gates. New `persona-reviewer` mode (`skjelett`). Pipeline grows 11 → 13 phases; commands and agents unchanged in count (24, 14). Encodes the Maskinrommet writing-contract §A discipline (premiss / problem / anbefaling / gevinst / vei videre) into the pipeline. Empirically motivated by the Seres-serien Del 3 + Del 4 spine-rework cost. | | **2.0.0** | 2026-05-28 | Full-spectrum content engine. `/linkedin:newsletter` long-form orchestrator with multi-session edition-state, fact-check + persona-sweep gates BEFORE lock. New agents: `fact-checker`, `persona-reviewer` (both Opus). Render pipeline migrated in-plugin with self-hosted OFL-1.1 fonts. Net-fewer surface: 27 → 24 commands (5 removed, 2 added), 16 → 14 agents (4 merged, 2 added). Router gating on monetize/outreach (unlocks at ~1K). `/linkedin:import` delegates analysis to `/linkedin:report`. | diff --git a/plugins/linkedin-thought-leadership/commands/newsletter.md b/plugins/linkedin-thought-leadership/commands/newsletter.md index be65d0d..d44352d 100644 --- a/plugins/linkedin-thought-leadership/commands/newsletter.md +++ b/plugins/linkedin-thought-leadership/commands/newsletter.md @@ -71,7 +71,7 @@ delegate the fan-out to a nested background agent. > 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) +## Pipeline overview (14 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 @@ -90,17 +90,19 @@ single most important corrections from the Seres process (plan §0.4, principle | 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` | +| 7.5 | **Visual assets — BEFORE lock** | cover (+ optional inline figures) or carousel deck: behov → per-image brief → generate (mcp-image default / external `cover-raw.png`) → operator-gate (`SendUserFile`) → approve to `cover.png` → credit/caption. Runs before lock so the renderer picks the cover up. | `mcp__mcp-image__generate_image` + `SendUserFile` + (carousel) `render/build-carousel.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` (machine) and +> **Build status:** all 14 phases (Steps 0–2.5, 3a, 3b, 4–7, 7.5, 8–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 → **visual assets (cover/figures +> or carousel, operator-gated BEFORE lock)** → LOCK/delivery → post-lock hook +> gate → scheduling, persisting each phase to `edition-state.json` (machine) and > `/STATE.md` (narrative) and stopping cleanly between sessions. > **Why two gates BEFORE prose (v2.1).** Spine errors are the dearest failure @@ -181,8 +183,9 @@ Look up `edition-state.json` → `articles.` (and the top-level | `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 | +| `persona-sweep-prelock` | Step 7 — Annotation (optional) → Step 7.5 | +| `annotation` | Step 7.5 — Visual assets *(cover/figures or carousel deck, BEFORE lock)* | +| `visual-assets` | 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) | @@ -883,13 +886,164 @@ editor is satisfied with the in-session draft. It does not gate lock. substantive, re-run the affected sweep (Step 5 or 6) before proceeding. 4. **Persist.** Set `currentPhase: "annotation"` in `edition-state.json` and - write an "annotation rendered (optional) → next: lock/delivery" line to + write an "annotation rendered (optional) → next: visual assets" line to `/STATE.md` (overwrite). If skipped, note "annotation skipped" and move on. ``` Annotation (optional). - Rendered: /review/NN-utkast.html (or: skipped) - build-html exit: 0 (else: non-zero — review HTML NOT produced, see stderr) +Next: Step 7.5 — Visual assets (cover/figures or carousel, BEFORE lock). +``` + +--- + +## Step 7.5: Visual assets — cover (+ inline figures) or carousel deck, BEFORE lock + +The edition needs at least a **cover** (mandatory per the KTG cover-directive: +TLDR on top + at least one figure per article) and, for method-heavy editions, +one or two inline figures. This is a real pipeline phase — not an ad-hoc step +outside the pipeline — because the cover and its credit/caption coordinate with +the text, and because Step 8's renderer **picks them up**. + +> **Why BEFORE lock (enforced).** `render/build-linkedin.mjs` (Step 8) reads +> `linkedin/NN/cover.png` (fixed filename) and the edition-config +> `coverCredit` + `captions[NN]` when it builds `POST.html`. If images were +> generated *after* lock, `POST.html` would have to be re-rendered — so the +> edition would no longer be locked in practice. Visual assets are therefore +> resolved here, between the pre-lock persona sweep (Step 6) / optional +> annotation (Step 7) and the lock (Step 8). `[GATE]` + +> **What the renderer does and does NOT do.** `build-linkedin.mjs` embeds the +> **cover** by *filename reference* in the POST.html cover field (`linkedin/NN/cover.png` +> + credit + caption) — it does not inline the image bytes, and it does **not** +> embed `fig.png` at all. Inline figures are referenced in the draft markdown +> (`![alt](linkedin/NN/figN.png)`) for the author's reference and **uploaded +> manually** in the LinkedIn editor. So "visual assets" here means: produce and +> approve the image *files* + record their credit/caption, not auto-embed them. + +**Format branch (decide first).** An edition is either `standard` (cover + +optional inline figures) or `carousel` (a typografisk slide-deck instead of +cover+inline). It is `carousel` when its `NN` is in `edition-config.json` +→ `carousel` (the list of editions that ship a document post), or when the +operator declares carousel format for it. Branch accordingly: + +- **`standard`** → run steps 1–5 below (cover, optional figures, credit/caption). +- **`carousel`** → skip cover/figures; jump to **step 6 (carousel branch)** below. + +**Procedure (`standard` format):** + +1. **Decide image needs from the article type.** Use a light heuristic on the + article's skeleton (Step 2.5) + writing contract, or just ask the operator: + - **Cover** — always mandatory: one hero illustration for the edition. + - **Inline figures** — article-dependent. *Method-heavy* articles (a model + diagram, a relationship map, before/after) usually want 1–2 figures; + *diagnosis-heavy* articles often need only the cover. Propose a count and + let the operator confirm or override. + +2. **Write a brief per image.** For each image (cover + any figures): a short + text brief — motif, mood, format, aspect ratio (cover target is **1920×1080**, + per the renderer's cover block). Generate a first proposal from the skeleton + + writing contract; the operator overrides freely. Record each per-image brief + in `edition-state.json` → `articles.NN.visualAssets.cover.brief` and + `…figures[].brief`. + +3. **Generate — two routes, no lock-in.** The interface is pluggable (path-in / + path-out); `mcp-image` is the default, not a hard dependency: + - **Default route — `mcp__mcp-image__generate_image`** (Nano Banana Pro / + Gemini 3 Pro Image). Write candidates to + `linkedin/NN/cover-v-kandidat.png` (and `fig-kandidat.png` for + figures). Candidate naming lets several attempts sit side by side without + overwriting an approved file. Record route `"mcp-image"`. + - **External route** — DALL·E, Midjourney, a photographer, a hand-built SVG. + The plugin accepts a `linkedin/NN/cover-raw.png` the operator drops in; no + tool is mandated. Record route `"external"`. (The raw file may then be + cropped/retouched into a candidate, or approved directly.) + +4. **Operator-gate (render + approve — the Step 2.5/3a pattern, for images).** + Surface **every candidate** to the operator with `SendUserFile` (the image + equivalent of the render+annotate gate the write deliverables use): + 1. Collect the candidate paths (`cover-v-kandidat.png`, any external + `cover-raw.png`). + 2. `SendUserFile` them with a one-line caption tying each to its brief, so + the operator can compare side by side. + 3. The operator either **approves one** or **asks for more attempts** (loop + back to step 3 — generate the next `cover-v-kandidat.png`). + 4. On approval, **copy the approved candidate to the fixed name** that + `build-linkedin.mjs` reads — `cover.png`: + ```bash + cd && cp "linkedin/NN/cover-v-kandidat.png" "linkedin/NN/cover.png" + ``` + (Same pattern for figures: approved → `linkedin/NN/fig.png`.) Confirm + `linkedin/NN/cover.png` exists before advancing. + + Do not advance to Step 8 without an approved `cover.png`. `[OPERATØR]` + +5. **Credit + caption (prep for Step 8).** Read + `/linkedin/image-credit-caption.md` (template: + `${CLAUDE_PLUGIN_ROOT}/config/image-credit-caption.template.md` — copy it in + for a new series) and add/update the row for this edition: **motif** (one + line) + **caption** (one line, encoding the article's signal). The credit + must be **honest about AI generation** when the image is AI-made + (verification duty). Then fold these into `/linkedin/edition-config.json`: + - `coverCredit` — the global cover credit line (one value for the edition/series). + - `captions[NN]` — this article's cover caption / alt text. + These are exactly the fields `build-linkedin.mjs` already reads in Step 8. + +**Procedure (`carousel` format — branch):** + +6. **Render the carousel deck instead of cover+inline.** A carousel edition's + visual asset is the slide-deck, not a hero cover. Author the slides in + `/linkedin/NN/carousel.md` (the `## SLIDE N — …` grammar + `build-carousel.mjs` parses), then render with the plugin-owned renderer + (cwd = series folder): + ```bash + cd && node "${CLAUDE_PLUGIN_ROOT}/render/build-carousel.mjs" linkedin/NN/carousel.md + ``` + `build-carousel.mjs` writes `linkedin/NN/carousel.html` always and + `linkedin/NN/carousel.pdf` when `weasyprint` is on PATH (it degrades with an + install hint instead of throwing — check the output and surface the hint if + the PDF was skipped). Surface the rendered deck (`carousel.pdf`, else + `carousel.html`) to the operator via `SendUserFile` for the same approve / + regenerate gate as step 4. Record this under + `articles.NN.visualAssets.carousel = { source, pdf, status }` and set + `format: "carousel"`. No `cover.png` is required for a pure carousel edition + (its `POST.html` carousel block references `linkedin/NN/carousel.pdf`); a + carousel edition that *also* posts a feed cover runs both branches. `[OPERATØR]` + +**Naming convention (documented — consistent with existing series use):** + +| File | Meaning | +|------|---------| +| `cover.png` | **Approved, fixed name** — the only cover filename `build-linkedin.mjs` reads. | +| `cover-v-kandidat.png` | Generation attempts (mcp-image or post-processed). Several may coexist. | +| `cover-raw.png` | Optional external pre-edit source (DALL·E / Midjourney / photographer). | +| `fig.png` | Inline figure (`fig1.png`, `fig2.png`, …), referenced from the draft markdown, uploaded manually. | +| `carousel.md` / `carousel.pdf` | Carousel deck source + rendered PDF (carousel-format editions). | + +Descriptive variant suffixes (e.g. `cover-foto-kandidat-v2.png`) are fine for +parallel exploration as long as the **approved** image always lands at the fixed +`cover.png` name. + +**Persist + checkpoint state.** Once the cover is approved (or, for carousel, +the deck is approved) and credit/caption are recorded: + +- Set `articles.NN.visualAssets` (format, cover.status `approved`, + cover.approved `"cover.png"`, candidates list, figures, carousel) in + `edition-state.json`. +- Set `currentPhase: "visual-assets"` in `edition-state.json` (the marker that + Step 7.5 is complete and the gate has passed). +- Write a "visual assets approved (cover/figures or carousel) → next: + lock/delivery" line to `/STATE.md` (overwrite). + +``` +Visual assets (BEFORE lock). +- Format: standard (cover + figures) (or: carousel deck) +- Cover: linkedin/NN/cover.png approved (after candidates) (or: N/A — carousel) +- Figures: approved → linkedin/NN/figN.png (or: none) +- Carousel deck: linkedin/NN/carousel.pdf rendered + approved (or: N/A — standard) +- Route: mcp-image | external Credit/caption: recorded in image-credit-caption.md + edition-config.json +- Operator gate: approved (candidates surfaced via SendUserFile) [OPERATØR] Next: Step 8 — LOCK → delivery. ``` @@ -912,9 +1066,11 @@ produces the editor's single delivery artifact — `POST.html`, the **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. + `factcheckLog` has no open 🔴, `personaSweep.resonance` recorded a primær JA, + and `visualAssets` is gated — for `standard` format the approved + `linkedin/NN/cover.png` exists (Step 7.5); for `carousel` format the approved + `carousel.pdf`/`carousel.html` exists. If any is missing, STOP — return to the + relevant step (5/6/7.5). Do not lock past an open gate. 2. **Confirm the delivery inputs in the series folder.** `render/build-linkedin.mjs` reads, relative to cwd (`/linkedin/`): @@ -1062,8 +1218,9 @@ Edition complete. Visible in /linkedin:calendar; mark live via /linkedin:calenda ## 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-state.template.json` — edition-state schema (14 phases including v2.1 skeleton + spine-prose gates and v2.3 visual-assets) - `${CLAUDE_PLUGIN_ROOT}/config/edition-config.template.json` — static delivery metadata schema (calendar, freshness, credit, captions) — Step 8 +- `${CLAUDE_PLUGIN_ROOT}/config/image-credit-caption.template.md` — cover motif + credit + caption table (honest-about-AI credit) — Step 7.5 - `${CLAUDE_PLUGIN_ROOT}/config/edition-delingstekst.template.md` — distribution-copy grammar (`## Del N —` / `## Samle`) — Steps 8/9 - `${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) @@ -1072,5 +1229,6 @@ Edition complete. Visible in /linkedin:calendar; mark live via /linkedin:calenda - `${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-linkedin.mjs` — POST.html delivery; reads `linkedin/NN/cover.png` + credit/caption (Step 8) - `${CLAUDE_PLUGIN_ROOT}/render/build-html.mjs` — annotatable review renderer (Step 7) +- `${CLAUDE_PLUGIN_ROOT}/render/build-carousel.mjs` — carousel deck renderer (`## SLIDE N —` → PDF via weasyprint) — Step 7.5 carousel branch diff --git a/plugins/linkedin-thought-leadership/config/edition-state.template.json b/plugins/linkedin-thought-leadership/config/edition-state.template.json index 1323b25..2d25f1f 100644 --- a/plugins/linkedin-thought-leadership/config/edition-state.template.json +++ b/plugins/linkedin-thought-leadership/config/edition-state.template.json @@ -15,11 +15,13 @@ "factcheck-sweep — risk-sorted, guilty-until-disproven, verification log (Step 5)", "persona-sweep-prelock — reader jury, primary wins, convergence to clean YES (Step 6)", "annotation — optional annotatable review HTML for a manual pass (Step 7)", + "visual-assets — cover (+ optional inline figures) or carousel deck: brief → generate → operator-gate → approve, BEFORE lock so build-linkedin.mjs picks them up (Step 7.5)", "lock-delivery — LOCK → POST.html all-in-one-place deliverable (Step 8)", "hook-conversion-gate — persona gate on distribution text post-lock: would YOU click? (Step 9)", "scheduling — register edition in plugin queue/state for native LinkedIn scheduling (Step 10)" ], - "articleStatusValues": ["pending", "in-progress", "locked", "scheduled"] + "articleStatusValues": ["pending", "in-progress", "locked", "scheduled"], + "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]." }, "schemaVersion": 1, "series": { @@ -41,6 +43,18 @@ "resonance": null, "conversion": null }, + "visualAssets": { + "format": "standard", + "cover": { + "brief": null, + "route": null, + "candidates": [], + "approved": null, + "status": "pending" + }, + "figures": [], + "carousel": null + }, "locked": false, "scheduled": null } diff --git a/plugins/linkedin-thought-leadership/config/image-credit-caption.template.md b/plugins/linkedin-thought-leadership/config/image-credit-caption.template.md new file mode 100644 index 0000000..ea75301 --- /dev/null +++ b/plugins/linkedin-thought-leadership/config/image-credit-caption.template.md @@ -0,0 +1,63 @@ +# Bilde-credit + caption — cover per edition + +> **TEMPLATE.** Copy this to `/linkedin/image-credit-caption.md` and fill it +> in per series. `/linkedin:newsletter` Step 7.5 (visual-assets phase) reads it and +> updates the row for the edition in production; the values flow into +> `/linkedin/edition-config.json` → `coverCredit` + `captions[NN]`, which +> `render/build-linkedin.mjs` reads when it builds `POST.html` (Step 8). This file +> is the human-readable source of truth for *motif + credit + caption*; the JSON is +> the machine copy the renderer consumes. + +LinkedIn-editoren har et **«Add credit and caption»**-felt under hvert bilde. Fyll +inn per cover. Caption = én kort linje som koder artikkelens signal (det leseren +skal sitte igjen med), ikke en bildebeskrivelse. + +> Format i editoren: ofte ett felt. Lim «Caption — Credit» eller bruk feltene hver +> for seg om de finnes. + +## Verifiseringsplikt — credit skal være ærlig + +Er coveret **KI-generert** (Nano Banana Pro / Gemini / DALL·E / Midjourney) → +credit MÅ si det. Aldri la et AI-bilde framstå som foto eller egenprodusert +illustrasjon. Eksempel-credit for AI-cover: + +**Felles credit (alle editions):** `Illustrasjon generert med ` — f.eks. +`Illustrasjon generert med Google Gemini (Nano Banana Pro)`. + +Er coveret et ekte foto eller en håndlaget figur → bytt til den ærlige creditten +(`Foto: `, `Egenprodusert figur`). Avvik fra felles-creditten føres under. + +**Per-edition credit-avvik:** _(list any edition whose credit differs from the +felles-credit, with the reason — e.g. «Del 3: Egenprodusert figur (kodet SVG)». +None by default.)_ + +## Motiv + caption per edition + +| Del | Cover (motiv) | Caption | +|-----|---------------|---------| +| 01 | __ | __ | +| 02 | _…_ | _…_ | +| samle | __ | __ | + +## Naming-konvensjon (cover-filer) + +- `cover.png` — **godkjent, fast navn**. Det eneste filnavnet `build-linkedin.mjs` + leser. Operator-gaten i Step 7.5 kopierer den godkjente kandidaten hit. +- `cover-v-kandidat.png` — genererings-forsøk (mcp-image eller etterbehandlet). + Flere kan ligge side om side uten å overskrive den godkjente. +- `cover-raw.png` — valgfri ekstern pre-edit-kilde (DALL·E / Midjourney / fotograf). +- `fig.png` — inline-figur (`fig1.png`, `fig2.png`, …), referert fra utkast-markdown + med `![alt](linkedin/NN/figN.png)` og **lastet opp manuelt** i editoren + (`build-linkedin.mjs` embedder ikke figurer). + +## Carousel-utgaver + +Carousel-editions (typografisk deck via `render/build-carousel.mjs`) har som regel +**ingen foto-cover** → ingen bilde-credit nødvendig. Slide-kilden er +`linkedin/NN/carousel.md`, rendret til `linkedin/NN/carousel.pdf`. En carousel-edition +som *også* legger en feed-cover trenger likevel en rad over. + +## Samle-post + +Ev. Maskinrommet-/serie-badge (egen asset) → ingen credit. Lenken til serien ligger i +første kommentar, ikke i bildet.