feat(linkedin): v2.3.0 — Step 7.5 visual-assets phase in /linkedin:newsletter
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<N>-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) <noreply@anthropic.com>
This commit is contained in:
parent
4ed9717627
commit
7ebd28cb0b
9 changed files with 304 additions and 29 deletions
|
|
@ -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
|
||||
> `<serie>/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.<currentArticle>` (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
|
||||
`<serie>/STATE.md` (overwrite). 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 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<N>.png` at all. Inline figures are referenced in the draft markdown
|
||||
> (``) 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<N>-kandidat.png` (and `fig<N>-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<N>-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<N+1>-kandidat.png`).
|
||||
4. On approval, **copy the approved candidate to the fixed name** that
|
||||
`build-linkedin.mjs` reads — `cover.png`:
|
||||
```bash
|
||||
cd <serie-mappe> && cp "linkedin/NN/cover-v<N>-kandidat.png" "linkedin/NN/cover.png"
|
||||
```
|
||||
(Same pattern for figures: approved → `linkedin/NN/fig<N>.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
|
||||
`<serie>/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 `<serie>/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
|
||||
`<serie>/linkedin/NN/carousel.md` (the `## SLIDE N — …` grammar
|
||||
`build-carousel.mjs` parses), then render with the plugin-owned renderer
|
||||
(cwd = series folder):
|
||||
```bash
|
||||
cd <serie-mappe> && 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<N>-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<N>.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 `<serie>/STATE.md` (overwrite).
|
||||
|
||||
```
|
||||
Visual assets (BEFORE lock).
|
||||
- Format: standard (cover + <N> figures) (or: carousel deck)
|
||||
- Cover: linkedin/NN/cover.png approved (after <N> candidates) (or: N/A — carousel)
|
||||
- Figures: <N> 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 (`<serie>/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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue