docs(linkedin): Voyage executable plan for v2.0.0 build (21 sessions)

Production plan for lifting LTL v1.2.0 -> v2.0.0 (full-spectrum content
engine + /linkedin:newsletter + render migration + net-fewer commands/agents).

- docs/voyage-build/{brief.md,plan.md}: Voyage project dir. plan.md = 21 steps
  (S1..S20+S1a), each with a per-step Manifest. Validator: valid, 0 warnings.
- docs/{brief,plan}-fullspektrum-innholdsmotor.md + voyage-build-brief.md:
  the directional brief, hardened fasit (authoritative WHAT), and Voyage input.
- Built via /trekplan: confirmatory swarm verified the fasit against real files
  (13 corrections folded in); scope-guardian ALIGNED; plan-critic REPLAN ->
  revised (3 blockers + 5/6 major + 4/5 minor closed).
- plan.html (annotation surface) left untracked: regenerable via annotate.mjs.

Next: /trekexecute --fg --project docs/voyage-build (fresh session) -> S1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-05-26 21:38:52 +02:00
commit 098726d397
6 changed files with 1748 additions and 0 deletions

View file

@ -43,6 +43,9 @@ assets/analytics/content-history.md
BACKLOG.md
docs/DEVELOPMENT-LOG.md
# Generated annotation/review artifacts (regenerable; annotations live in browser localStorage)
docs/review/
# Node.js
scripts/analytics/node_modules/
scripts/analytics/build/

View file

@ -0,0 +1,160 @@
# Brief — LTL som fullspektrum LinkedIn-innholdsmotor (idé → publisering)
> **Til:** linkedin-thought-leadership-pluginens utviklingsrepo.
> **Skrevet:** 2026-05-26, etter produksjon av den første kronikkserien (Seres-serien, 6 deler).
> **Type:** retningsbrief — beslutningsgrunnlag før planlegging/bygging. Selvstendig (kan leses uten annen kontekst).
---
## 1. Hva vi skal oppnå
Løft LTL fra en kortform-fokusert plugin til **den komplette motoren for ALT LinkedIn-innhold,
fra idé til publisering** — inkludert **nyhetsbrev/langform**, som i dag er pluginens svakeste område.
Én plugin skal eie hele kjeden: **idé → research → utkast → faktasjekk → review → hook/distribusjon →
planlegging → publisering → analyse** — for alle formater: posts, carousels, reaksjoner, video, og
**nyhetsbrev-editions**.
Begrunnelsen: scopet er LinkedIn. Da hører nyhetsbrev hjemme i LTL (det ER et LinkedIn-native format),
sømmene mot en separat plugin koster mer enn de gir, og brukeren bruker uansett LTL for alt LinkedIn.
---
## 2. Kontekst: hvor dette innholdet lages
**`~/repos/maskinrommet`** er det private, lokale repoet der **ALT innhold produseres**. Der brukes
**LTL-pluginen til all innholdsproduksjon**. Arbeidsdelingen:
- **maskinrommet** = arbeidsbenken (innhold, serier, og en delt `tools/`-mappe med deterministiske
render-skript: `build-linkedin.mjs` → POST.html, `build-carousel.mjs`, `build-html.mjs` annoterbar
HTML, `build-pdf.mjs` avis-PDF).
- **LTL** = verktøyet/hjernen som driver produksjonen i det repoet.
- **dette LTL-repoet** = der selve plugin-utviklingen skjer. Erfaringene kommer fra maskinrommet;
endringene gjøres her.
Render-skriptene i `maskinrommet/tools/` er den mekaniske utføreren som LTL-pipelinen *kaller*.
«LTL eier prosessen» krever ikke at LTL *hoster* render-skriptet — se åpen beslutning C.
---
## 3. Live-status (baseline å bygge for)
Nyhetsbrevet **Maskinrommet** er etablert på LinkedIn:
- **Første post ute**, **30 abonnenter**, **6 nyhetsbrev i pipen** (Seres-serien, ferdig produsert).
- Publisering er manuell (LinkedIn har ingen API for newsletter/long-form), men kan native-planlegges.
Dette er en reell, kjørende kadens — ikke en hypotese. Forbedringene skal støtte å produsere de neste
seriene raskere og med samme kvalitet.
---
## 4. Erfaringsgrunnlag: hva en kronikk faktisk krever
Seres-serien (6 kronikker, ~10 sesjoner) avdekket at langform-produksjon ikke er «skriving» — det er en
**research- og adversarial-review-pipeline**. Den faktiske flyten som ga kvalitet:
1. **Brief** — vinkel, tenkt stemme, målgruppe-personaer (med primær), nøkkelpoeng, tone, leder-takeaway.
2. **Research** — flere parallelle, avgrensede mandater → verifiserte notater.
3. **Faktasjekk-sweep** — risikosortert (🔴/🟡/🟢), parallelle WebSearch-agenter, «hver påstand skyldig
til motbevist». Fanget ~13 feil — flere som egne research-filer hadde bommet på.
4. **Skriving** — dramaturgisk rekkefølge, multi-sesjon med vedlikeholdt HANDOVER.
5. **Konsistens + kvalitet** — på tvers av tekstene (gjentakelser, tone, premiss→konklusjon-bue).
6. **Persona-/audience-sweep FØR lås** — 3 definerte leser-juryer leser read-only, primær trumfer.
(«Lander poenget?»). Kjørt til konvergens (LØST/DELVIS/IKKE per flagg).
7. **Hook-/konverterings-gate** — egen persona-gate på distribusjons-teksten: «ville DU klikket?»,
hold tilbake leveransen (tall/case/grep), ikke konklusjonen.
8. **Lås → leveranse** — POST.html «alt på ett sted» (dato, hook, hashtags, første kommentar,
cover-caption, brødtekst som rik tekst).
**De tre suksessfaktorene:** front-loadet kontekst, skreddersydd annoteringsverktøy, vedlikeholdt
single-source HANDOVER før hver kontekst-reset.
**Største prosessfeil å unngå:** persona-sweep ble kjørt ETTER lås → måtte åpne låste tekster. Den
generaliserte malen MÅ ha persona-sweep FØR lås.
---
## 5. Byggeprinsipp: løft Voyage-mønstrene, ikke fork dem
Denne pipelinen mapper nesten 1:1 på **Voyage**-pluginen (trekbrief → trekresearch → trekplan →
trekexecute → trekreview, med parallelle agenter + adversarielle reviewere + multi-sesjon).
**Men IKKE fork Voyage og IKKE reimplementer den blindt.** Voyage er kode-spesifikk (`file:line`,
kode-reviewere, RULE_CATALOGUE). Løft i stedet **mønstrene** inn i LTL som et nytt **langform-/
nyhetsbrev-spor** ved siden av de eksisterende kortform-kommandoene:
- faset pipeline med parallelle research-agenter
- faktasjekk-sweep som eget steg
- multi-persona adversarial jury (leser-personaer erstatter kode-reviewere; primær trumfer)
- multi-sesjons-kontinuitet (HANDOVER-mønster)
**Delte agenter, variabel intensitet:** research- og faktasjekk-agentene skal kunne kalles på *lav*
intensitet fra en kortform-post som siterer ett tall, og *full* sweep fra et nyhetsbrev. Voice og
hook-gate deles på tvers — aldri to systemer.
Voyage er **referanse/inspirasjon**, ikke en avhengighet å vedlikeholde.
---
## 6. Hva LTL allerede har (ikke bygg på nytt)
- Skrive-workflows for kortform: `/linkedin:post` (vinkel→draft→kvalitet→refinement),
`/linkedin:pipeline` (idé→draft→optimer→planlegg→engasjer→publiser→analyse), `batch`, `react`,
`quick`, `carousel`, `video`, `templates`.
- Voice-system (`config/user-profile.local.md`, voice-samples, voice-trainer-agent).
- Hook-/optimaliserings-støtte (content-optimizer, differentiation-checker).
- Planlegging/sporing/analyse (`calendar`, `publish`, `import`, `report`; queue.json; state-fil).
- 16 agenter, fler-stegs-kommandoer, REMEMBER-kontinuitet — arkitekturen *kan* være vert for et tyngre
pipeline-spor.
---
## 7. Gapet å fylle (LTLs svake punkt = langform/nyhetsbrev)
1. **Langform-/nyhetsbrev-pipeline** som eget kommandospor (idé→publisering for editions, ikke bare posts).
2. **Research-orkestrering** — parallelle mandater, verifiserte notater.
3. **Faktasjekk-sweep** — risikosortert, kildekritisk, verifiseringslogg, «skyldig til motbevist».
4. **Multi-persona adversarial review FØR lås** — konfigurerbare leser-juryer, primær trumfer,
konvergens-loop til rent JA.
5. **Multi-sesjons-kontinuitet** — HANDOVER-mønster for produksjon som spenner flere økter.
6. **Nyhetsbrev-leveranse** — edition-format (POST.html-stil «alt på ett sted»), delingstekst-system,
ferskvare-flagg for tidssensitive tall, native planlegging.
7. **Annoteringssteg** — integrer annoterbar review-HTML i flyten (render bor i maskinrommet/tools).
---
## 8. Åpne beslutninger (landes før bygging i dette repoet)
- **A. Pipeline som nye kommandoer vs. utvidelse av `pipeline`.** Eget `/linkedin:longform`/`:newsletter`-
spor, eller utvid eksisterende `pipeline` med en «long-form»-modus?
- **B. Agent-deling.** Bygges research/faktasjekk/persona-jury som nye delte agenter brukt på variabel
intensitet av både kort- og langform? (Anbefalt.)
- **C. Render-eierskap.** Forblir `build-linkedin.mjs`/`build-carousel.mjs` i `maskinrommet/tools/`
(LTL kaller dem), eller flyttes LinkedIn-render inn i pluginen? (Anbefalt: bli i tools/ — render-familien
deler fonts/identitet; pluginen holdes lean.)
- **D. Personasett.** Defineres leser-personaene per prosjekt (fra målgruppen) eller som gjenbrukbare
profiler i config?
- **E. Faktasjekk-omfang.** Eget steg, eller integrert i research + review?
---
## 8b. Merknad: brukeren trenger onboarding i pluginen
Brukeren kjenner ikke pluginen sin godt ennå og vil «ta ut potensialet». Tilby tidlig i
LTL-sesjonen: **`/linkedin`** (oversikt over alle kommandoer) + **`/linkedin:setup`**
(personaliserings-score + fyll inn voice-samples/case/rammeverk/demografi/profil). Personalisering
er nøkkelen — voice-matching, differensiering og hook-gate avhenger av ekte innmatede data.
## 9. Referanser (i `~/repos/maskinrommet` og memory)
- **Plan (supersedes-kandidat):** `~/repos/maskinrommet/planer/2026-05-26-kronikk-voyage-companion.md`
— skrevet før denne retningen (foreslo separat companion). Behold som historikk; *denne briefen* er
gjeldende retning.
- **Erfaringskatalog:** produsert i sesjon 2026-05-26 (16 prosessfaser, suksessfaktorer, friksjon, verktøy).
- **HANDOVER:** `~/repos/maskinrommet/serier/silvija-seres-motsvar/HANDOVER.md` (§3 leveranse, §4 regler, §5 metode).
- **Memory (`~/.claude/projects/-Users-ktg-repos-svv/memory/`):**
`project_kronikk_produksjonsprosess.md` (Voyage-mapping, persona-sweep-FØR-lås, kalibrering),
`project_maskinrommet_newsletter.md`, `project_kronikk_faktasjekk_sweep.md`,
`feedback_dokumentprosjekt_suksessfaktorer.md`, `feedback_hook_post_persona_gate.md`,
`feedback_persona_audience_sweep.md`, `feedback_use_linkedin_plugin.md`,
`feedback_post_html_single_sheet.md`.

View file

@ -0,0 +1,473 @@
# Plan — LTL som fullspektrum LinkedIn-innholdsmotor
> **Type:** Renoverings- og byggeplan for `linkedin-thought-leadership`-pluginen («LTL»).
> **Skrevet:** 2026-05-26. **Status:** Til godkjenning før bygging. **Versjonsmål:** v1.2.0 → v2.0.0.
> **Følger av:** [brief-fullspektrum-innholdsmotor.md](./brief-fullspektrum-innholdsmotor.md) (samme mappe).
>
> **LES §0 FØRST.** Denne planen utføres sesjon-for-sesjon. Hver sesjon starter med tom kontekst (etter `/clear`) og har KUN denne fila + `BUILD-HANDOVER.local.md` + kildene §0 peker til. Derfor er alle navn, stier og begreper definert eksplisitt i §0 — ingenting forutsettes kjent.
---
## 0. Orientering for en ny sesjon
Denne seksjonen gjør planen selvstendig. Hvis et begrep brukes senere uten forklaring, slå det opp her.
### 0.1 Repoer og absolutte stier
| Det | Hva | Absolutt sti | Rolle her |
|-----|-----|--------------|-----------|
| **LTL-pluginen** | Claude Code-plugin for LinkedIn thought leadership. v1.2.0. 27 kommandoer, 16 agenter, 9 hooks, 6 skills. | `/Users/ktg/.claude/plugins/marketplaces/ktg-plugin-marketplace/plugins/linkedin-thought-leadership/` | **DETTE er repoet vi bygger i.** All plugin-endring skjer her. |
| **Marketplace-rot** | Open-source plugin-marketplace (flere plugins + `shared/`). Distribueres via Forgejo: `git.fromaitochitta.com/open/ktg-plugin-marketplace` (aldri GitHub). | `/Users/ktg/.claude/plugins/marketplaces/ktg-plugin-marketplace/` | Rot-`README.md` må oppdateres ved feature-endring (doc-plikt). |
| **maskinrommet** | Privat, lokalt repo (INGEN git-remote) der operatøren produserer alt LinkedIn-innhold. Inneholder `tools/` (4 render-skript + `fonts/`) og `serier/<slug>/` (én innholdsserie per mappe). | `/Users/ktg/repos/maskinrommet/` | **Annet repo enn pluginen.** Skriving hit krever eksplisitt instruks (cross-repo). Vi leser render-skriptene herfra i S1. |
| **Voyage-pluginen** | En annen plugin i samme marketplace. Implementerer en kontrakt-drevet, multi-sesjons pipeline for KODE-prosjekter: kommandoene `/trekbrief``/trekresearch``/trekplan``/trekexecute``/trekreview`, med parallelle spesialist-agenter og adversarielle reviewere. | `/Users/ktg/.claude/plugins/marketplaces/ktg-plugin-marketplace/plugins/voyage/` | **Referanse/inspirasjon, IKKE avhengighet.** Vi løfter *mønsteret* (faset pipeline, parallelle agenter, adversariell review før lås, multi-sesjon), men kopierer ALDRI koden — Voyage er kode-spesifikk (`file:line`, kode-reviewere, RULE_CATALOGUE). Les den for mønster-inspirasjon. |
| **svv-memory** | Memory-filer skrevet under produksjonen av Seres-serien (se 0.2). Kilden til prosess-erfaringen denne planen generaliserer. | `/Users/ktg/.claude/projects/-Users-ktg-repos-svv/memory/` | Les ved behov for dyp prosess-kontekst (se nøkkelfiler i 0.4). |
### 0.2 Hva «Seres-serien» er
Operatøren (Kjell Tore Guttormsen) produserte en kronikkserie på 6 deler — internt kalt «Seres-serien» eller «silvija-seres-motsvar» — i ~10 arbeidsøkter i mai 2026. Den utgjør de første utgavene av LinkedIn-nyhetsbrevet «Maskinrommet» (0.3). Serien ligger i `/Users/ktg/repos/maskinrommet/serier/silvija-seres-motsvar/`. Produksjonen avdekket en research- og review-tung prosess (0.4) som denne planen generaliserer til en gjenbrukbar pipeline. **«Seres-erfaringen»** = lærdommene derfra. Når planen sier «Seres beviste/avdekket X», betyr det «erfaringen fra denne serien viste X», dokumentert i svv-memory (0.1).
### 0.3 «Maskinrommet»-nyhetsbrevet
Operatørens ukentlige LinkedIn-nyhetsbrev, etablert 2026-05-25 (URL: `linkedin.com/newsletters/maskinrommet-7464605936645509120`). Status ved planskriving: ~30 abonnenter, 6 editions (Seres-serien) ferdig produsert og klare for utrulling. **LinkedIn-fakta** (verifisert): det finnes INGEN API for nyhetsbrev/long-form — publisering skjer ved manuell innliming av rik tekst i LinkedIns editor. Men LinkedIn har **native planlegging** (dato + klokkeslett i 15-min-intervaller), så hele utrullingen kan forhåndsplanlegges. Cover-bilde: 1920×1080. En **edition** = én utgave = én kronikk/artikkel + distribusjonspakke (hook-tekst, hashtags, første kommentar, cover).
### 0.4 De 16 prosessfasene (Seres-erfaringen, kondensert)
Dette er den faktiske flyten som ga kvalitet i Seres-produksjonen. Den nye nyhetsbrev-kommandoen (§5) er en kondensering av denne. Kilde: `…/svv/memory/project_kronikk_produksjonsprosess.md`.
1. **Kalibrerings-intervju** — tid/målgruppe/mål; ~3 spørsmål før skriving.
2. **Front-loadet kontekst** — brief + research + sitatbank lest FØR skriving (suksessfaktor).
3. **Research + verifiseringsplikt** — hver påstand mot primærkilde.
4. **Serie-overgripende regler** etableres (stil/term/tone-låser).
5. **Utkast** via Opus-subagent — én artikkel om gangen, med leserekkefølge-kontekst.
6. **Kritisk-leser-simulering** — «fortjener dette publisering?» → konkret kritikk, ikke skryt.
7. **Gap-tabell + gap-lukking** — hvert gap lukkes med *tightening/bytte, ikke utvidelse*; lengde flat.
8. **Annoterings-loop** — render annoterbar review-HTML (verktøyet `build-html.mjs`, 0.6), marker → kommenter → eksporter.
9. **Konsistens-pass** — tråder, pronomen-ordning, kryss-referanser, tall, dramaturgi.
10. **Kvalitets-sweep** — ett klart leder-poeng + én konkret handling per tekst; premiss→konklusjon-bue (0.5).
11. **Faktasjekk-sweep** — EGET steg; parallelle agenter; alle påstander «skyldig til motbevist» (0.5).
12. **Lesbarhets-/formaterings-sweep** — MINIMALT (0.5, formaterings-dose).
13. **Persona-/audience-sweep** — leser-jury tester om teksten LANDER (0.5).
14. **Verifiserings-loop** — kjør personaene PÅ NYTT mot oppdatert tekst til rent JA (0.5, konvergens-loop).
15. **LÅS → leveranse** — POST.html (0.5) og/eller avis-PDF.
16. **Distribusjons-/hook-gate** — konverterings-gate på distribusjonsteksten, ETTER lås (0.5).
**Den enkeltstående største prosessfeilen i Seres:** persona-sweepen (fase 13) ble opprinnelig kjørt ETTER at tekstene var låst (fase 15), noe som tvang gjenåpning av låste tekster. Den generaliserte malen MÅ derfor ha persona-sweep FØR lås. (Dette er grunnen til rekkefølgen i prinsipp 5, §3.)
Nøkkelfiler i svv-memory for dypere kontekst: `project_kronikk_produksjonsprosess.md` (fasene + Voyage-mapping), `project_kronikk_faktasjekk_sweep.md`, `feedback_persona_audience_sweep.md`, `feedback_hook_post_persona_gate.md`, `feedback_post_html_single_sheet.md`, `project_maskinrommet_newsletter.md`.
### 0.5 Ordliste (begreper brukt senere)
- **Edition** — én utgave av nyhetsbrevet (0.3).
- **Faktasjekk-sweep** — eget steg der hver faktapåstand (tall, navngitte eksempler, sitater, datoer, hvem-gjorde-hva) verifiseres mot primærkilde, etter prinsippet «**skyldig til motbevist**» (antas feil til den er verifisert; aldri fyll hull med gjetninger). I Seres ble ~15 feil fanget.
- **«Altinn-feilen»** — det konkrete funnet som beviste at faktasjekk må være et eget steg: i et utkast ble Altinn brukt som eksempel på «bygd i eget hus / internt eierskap», men Accenture var i realiteten hovedleverandør — altså nær et mot-eksempel. Verken research-filene eller en subagents resonnement fanget det; operatørens egen hukommelse gjorde. Lærdom: vær kritisk til ALT, også det som «føles» riktig og det som står i egne research-notater.
- **Persona-/audience-sweep** — en adversariell **leser-jury**: navngitte leser-personaer (definert fra målgruppen) leser den ferdige teksten *read-only* og dømmer om den LANDER (ikke om den er «riktig»). Personaene skriver ALDRI tekst — de gir retning; redaktøren (hovedkonteksten) holder pennen. Dette er kronikk-ekvivalenten til Voyages adversarielle kode-reviewere.
- **«Primær trumfer»** — én persona er utpekt som primærleser. Ved konflikt mellom personaer vekter primær høyest. En *sekundær*-NEI på grunn av rolle-mismatch eller ekspertise-tak («dette vet jeg alt om fra før») er et SIGNAL om at gaten virker, ikke en svikt — godta den, ikke forvreng primærteksten for å jage den. En *primær*-NEI godtas derimot ikke.
- **Konvergens-loop (LØST / DELVIS / IKKE)** — etter at jury-flagg er foldet inn i teksten, kjøres personaene PÅ NYTT for å bekrefte at endringene faktisk landet (ikke bare at teksten ble endret). Hvert tidligere flagg dømmes LØST / DELVIS LØST / IKKE LØST, til rent JA fra primær. I Seres tok dette 2 runder.
- **Hook-/konverterings-gate** — en EGEN persona-gate, men på **distribusjonsteksten** (feed-hooken / «Tell your network»-teksten), spisset for konvertering: binær JA/NEI på «ville DU klikket videre?» — ikke «er den god». Kjøres ETTER lås (teksten bak er ferdig). **«Hold tilbake leveransen, ikke konklusjonen»**: en hook som gir bort tallet/caset/listen/grepet i feeden får ros, men ingen klikk; hold igjen beviset og la et åpent spørsmål peke inn i artikkelen. Konklusjonen kan stå.
- **POST.html «alt på ett sted»** — ett selvforsynt publiseringsark per edition, generert av `build-linkedin.mjs` (0.6). Inneholder i publiseringsrekkefølge: dato + klokkeslett (+ ev. ferskvare-banner), tittel/SEO, cover-filnavn + credit + caption, delingstekst inkl. hashtags, første kommentar, ev. carousel-referanse, og brødteksten som rik tekst klar til innliming. Formålet: legg inn én edition i én operasjon uten å hoppe mellom filer.
- **Ferskvare-flagg** — markering i POST.html av tids-sensitive tall (f.eks. en selskaps-verdsettelse) som MÅ re-verifiseres på publiseringsdagen. En publiseringsdag-sjekkliste, ikke en tekstsvakhet.
- **voice-profil / voice-samples** — operatørens skrivestemme. Lagret i `config/user-profile.local.md` + `assets/voice-samples/` i pluginen. **Leses ALLTID før innholdsproduksjon** (eksisterende LTL-regel).
- **360Brew** — LinkedIns rangerings-/anbefalingsmodell (oppdatering jan. 2026). LTL optimaliserer innhold mot dens signaler.
- **5x5x5** — eksisterende LTL-engasjementstaktikk (kommenter/engasjer i et mønster rundt egen post). Ikke relevant for langform.
- **CEA** — kommenteringsmetode i `comment-strategist`-agenten. Ikke relevant for langform.
- **PreToolUse-gate / content-gatekeeper / voice-guardian** — eksisterende LTL-hooks som gir rådgivende kvalitets-/stemme-advarsler når kortform-innhold skrives. Kalibrert for feed-poster.
### 0.6 Render-skriptene (i `maskinrommet/tools/`, flyttes til pluginen i S1)
Fire zero-/lav-avhengighets Node-skript. Felles invariant: kjøres ALLTID med `cwd` = serie-mappa (output skrives relativt til `process.cwd()`); fonts lastes via skriptets `__dirname`. Kontrakt:
| Skript | Kall | Output | Avhengighet |
|--------|------|--------|-------------|
| `build-html.mjs` | `node …/build-html.mjs utkast/NN-*.md` | `review/NN-*.html` — selvstendig annoterbar HTML (marker → intent → kommentar → localStorage → eksport). **Dette er annoteringsverktøyet** (beslutning H, §7.5). | Zero-dep |
| `build-linkedin.mjs` | `node …/build-linkedin.mjs utkast/0*.md` | `linkedin/NN/POST.html` + `linkedin/samle/POST.html` (0.5). Leser også `linkedin/edition-delingstekst.md`. **NB: har i dag hardkodet Seres-kalender/captions/ferskvare** — generaliseres i S2. | Zero-dep |
| `build-carousel.mjs` | `node …/build-carousel.mjs linkedin/NN/carousel.md` | `carousel.pdf` (1080×1350) | Krever `weasyprint` på PATH |
| `build-pdf.mjs` | `node …/build-pdf.mjs utkast/NN-*.md` | `pdf/NN-*.pdf` (avis-A4) | Krever `weasyprint` på PATH |
### 0.7 «Auditen» (gjennomført i sesjon S0, frosset inn i §4)
Før denne planen ble alle 27 kommandoene og 16 agentene i LTL lest og vurdert for overlapp, redundans og langform-relevans (sesjon S0, 2026-05-26). Konklusjonene er **frosset inn i §4 og §6** — en ny sesjon skal stole på dem og trenger IKKE kjøre auditen på nytt. Vil du verifisere ett enkelt konsoliderings-grep, les den aktuelle kommando-/agent-fila direkte (alle ligger i `commands/` og `agents/` i pluginen).
---
## 1. Sammendrag
LTL skal eie **hele kjeden for ALT LinkedIn-innhold** — fra kortform-post til nyhetsbrev-edition (0.3) — med samme kvalitetsnivå som Seres-erfaringen (0.2) viste er mulig. Pluginens svakeste område i dag er langform/nyhetsbrev: det finnes kun som referansestoff (`references/newsletter-strategy-guide.md`) og en nedstrøms-adaptasjonssnutt i `commands/multiplatform.md`, ikke som en førsteklasses idé→leveranse-flyt.
**Kjerneinnsikten fra auditen (0.7):** Vi kan legge til full langform-kapabilitet **og gjøre pluginen enklere samtidig**. Langform krever +1 kommando og +2 agenter, men auditen avdekket nok reell redundans til at vi netto **reduserer** overflaten gjennom konsolidering (§4). Resultatet: en plugin med færre, klarere kommandoer/agenter — pluss en ny tung kapabilitet.
**Den nye kapabiliteten** løfter mønstrene fra Voyage-pluginen (0.1) — faset pipeline, parallelle research-agenter, adversariell review, multi-sesjon — inn i LTL uten å kopiere Voyages kode. Leser-personaer (0.5) erstatter Voyages kode-reviewere; faktasjekk (0.5) blir et eget steg; persona-sweep kjøres FØR lås (0.4).
---
## 2. Beslutninger landet
Alle åpne beslutninger fra briefen (§8 der) pluss tre som dukket opp under planlegging. Disse er LÅST — en ny sesjon endrer dem ikke uten eksplisitt instruks fra operatøren.
| # | Beslutning | Valg | Kilde |
|---|-----------|------|-------|
| **A** | Kommando-struktur | **Én ny orkestrator-kommando** `/linkedin:newsletter` med interne faser + multi-sesjons-resumption. IKKE en suite med fem fase-kommandoer. IKKE en utvidelse av `commands/pipeline.md` (feil artefakt-kontrakt: pipeline er låst til feed-post-format). | Audit (0.7) + operatør |
| **B** | Agent-deling | **Bare langform nå.** De eksisterende kortform-kommandoene røres ikke. Å dele de nye agentene inn i kortform på «variabel intensitet» utsettes til et eget, senere spor. | Operatør |
| **C** | Render-eierskap | **Ship alle 4 render-skript (0.6) + fonts i pluginen** (under `render/`). Generaliser `build-linkedin.mjs`. maskinrommet blir konsument. Da får alle som laster ned LTL fra Forgejo hele produksjons-pipelinen. | Operatør |
| **D** | Persona-sett | **Hybrid:** et gjenbrukbart persona-bibliotek i `config/`, der relevante personaer velges og justeres per prosjekt; primær (0.5) merkes per prosjekt. | Operatør |
| **E** | Faktasjekk-omfang | **Eget steg** (ikke integrert i research eller review). «Altinn-feilen» (0.5) beviste at research-notatene bommer; faktasjekk må være en dedikert sweep. | Seres-erfaring (0.2) |
| **F** | Renovering | **Konsolider redundans i samme runde** som langform bygges, slik at netto kommando-/agent-antall går NED (§4). | Audit (0.7) + operatør |
| **G** | Produksjons-state | Edition-state/HANDOVER for en pågående nyhetsbrev-produksjon bor i **serie-mappa** (i maskinrommet), ikke i pluginens state-fil. Pluginen er en stateless motor; editions registreres i plugin-state KUN for kalender/scheduling. | Arkitektur (separasjon) |
| **H** | Annoterings-renderer | `build-html.mjs` (0.6) generaliseres til en **førsteklasses plugin-kapabilitet for ALLE artefakter** (plan/brief/post/edition), ikke bare kronikker: tabeller, alle overskriftsnivåer, inline-kode, generisk frontmatter. Reference-impl ble produsert i sesjon S0b (§7.5). | Operatør |
---
## 3. Arkitektur-prinsipper
1. **Pluginen er motoren, maskinrommet er arbeidsbenken.** Innhold + produksjons-state for en serie bor i `/Users/ktg/repos/maskinrommet/serier/<slug>/` (0.1). Pluginen (0.1) leverer kommandoen, agentene, persona-biblioteket og render-skriptene. Ett unntak fra den tidligere modellen: render-skriptene flyttes INN i pluginen (beslutning C), men kjøres alltid med `cwd` = serie-mappa (0.6).
2. **Gjenbruk mønstre, ikke kode.** Kopier *formen* på det som finnes: LTL-kommando-malen (YAML-frontmatter + nummererte `Step 0..N` + en `## Reference Files`-seksjon — se f.eks. `commands/pipeline.md`), agent-frontmatter-stilen (se f.eks. `agents/differentiation-checker.md`), hook-kompileringen (`hooks/hooks.template.json` + `hooks/prompts/*.md` + `python3 hooks/scripts/compile-hooks.py`), og det deterministiske state-mønsteret (`hooks/scripts/state-updater.mjs` + `queue-manager.mjs`). Fra Voyage-pluginen (0.1) løftes pipeline-*mønsteret* — ikke koden.
3. **Langform-kvalitet håndheves av pipeline-fasene, ikke av PreToolUse-gaten.** De eksisterende kortform-hookene (content-gatekeeper / voice-guardian, 0.5) er kalibrert for feed-poster og forblir kortform-only (beslutning B). Langform får sin egen, tyngre review-maskineri: faktasjekk-sweep + persona-sweep + hook-gate (0.5). Langform-utkast trenger derfor IKKE ligge under `assets/drafts/` (der kortform-gatene fyrer) — de bor i serie-mappa i maskinrommet.
4. **All agent-orkestrering skjer i forgrunn fra kommando-laget.** Research-, faktasjekk- og persona-fan-out gjøres via `Task`-kall fra selve kommandoen — ALDRI fra en nestet bakgrunns-agent. (Erfaring: en agent som spawnes i bakgrunn mister tilgang til `Task`/Agent-verktøyet og degraderer stille til gjetning i stedet for å parallellisere.)
5. **Persona-sweep FØR lås.** Dette adresserer den største prosessfeilen i Seres (0.4). Den faste rekkefølgen er: utkast → konsistens → kvalitet → faktasjekk → persona-sweep → fold inn → LÅS → leveranse → hook-gate.
6. **Forenkling er en førsteklasses leveranse**, ikke en bivirkning. Hvert nytt element måles mot om det øker eller senker total kompleksitet i pluginen.
---
## 4. Del 1 — Renovering (konsolidering)
Auditen (0.7) fant reell redundans. Grepene under reduserer overflaten og fjerner konkurrenter til den nye nyhetsbrev-kommandoen. Alle er **korreksjon-i-scope** (slå sammen / deleger eksisterende kapabilitet) — ingen ny funksjonalitet. Stol på funnene; vil du etterprøve ett grep, les de navngitte filene i `commands/` eller `agents/`.
### 4.1 Kommando-konsolidering (27 → ~23)
| Grep | Kommandoer (filer i `commands/`) | Begrunnelse | Risiko |
|------|-----------|-------------|--------|
| **SLÅ SAMMEN** | `templates.md` → en modus i `quick.md` | Begge bygger på de samme 8 posttypene, samme hooks-bank og samme tegnmål. Klareste redundansen i katalogen. | Lav |
| **SLÅ SAMMEN** | `publish.md` → en handling i `calendar.md` | Begge leser samme kø (`queue.json`), viser overlappende lister; `calendar` ruter allerede til `publish`. | Lav |
| **SLÅ SAMMEN** | `collab.md` + `speaking.md` → ny `outreach.md` | Strukturell tvilling: samme outreach-/pitch-paradigme og samme pipeline-tabell. Fjerner ~2530 KB duplisert tekst. | Middels |
| **ABSORBER** | `authority.md` → en seksjon i `strategy.md` | `authority` har ingen unik kjerne; den er sammensatt av biter fra strategy/audit/profile/multiplatform. | Lav |
| **DEDUPLISER** | «trajectory»-logikk → bo kun i `strategy.md`; `audit.md` refererer dit | Identisk STATUS-tabell vedlikeholdes i dag to steder. | Lav |
| **KANON** | `profile.md` blir kanonkilde for profil-alignment; `audit.md`/`analyze.md` peker dit | Samme profil-sjekk er re-implementert 4 steder. | Lav |
| **TRIM** | analyse-delen (Step 6) i `import.md` → deleger til `report.md` | To rapport-generatorer kjører samme `trends`-CLI. | Lav |
| **RECONCILE** | Flytt newsletter/blog-stien UT av `multiplatform.md` | Unngå to inngangsdører til langform når `/linkedin:newsletter` finnes. **Må skje sammen med Del 2 (S11).** | Middels |
| **GATE** | I routeren `linkedin.md`: vis `monetize`/`outreach`/`collab` som «låses opp ved ~1K følgere» | Disse er aspirasjonelle for operatørens nåværende nivå (~30 følgere); skjuler kompleksitet uten å slette filer. | Lav |
### 4.2 Agent-konsolidering (16 → ~12, deretter +2 langform = ~14)
| Grep | Agenter (filer i `agents/`) | Resultat |
|------|---------|----------|
| **SLÅ SAMMEN** | `analytics-interpreter` + `performance-reporter` | 1 `analytics`-agent med to moduser (tolk / rapporter). Identiske datakilder. |
| **SLÅ SAMMEN** | `engagement-coach` + `comment-strategist` | 1 `engagement`-agent (5x5x5 + first-hour + CEA-kommentering). |
| **AVVIKLE → script** | `content-tracker` | Ren deterministisk plan-vs-kø-diff; hører hjemme i `calendar`/`state-updater.mjs`, ikke en LLM-agent. |
| **AVVIKLE → script** | `personalization-scorer` | Ren placeholder-deteksjon; `hooks/scripts/personalization-score.mjs` finnes allerede. |
| **VURDER** | `video-scripter``content-repurposer` | Marginal sammenslåing; la stå hvis dybden forsvarer egen fil (avgjøres i S19). |
**Nettoeffekt:** De 2 nye langform-agentene (`fact-checker`, `persona-reviewer`, §6) **finansieres** ved å avvikle de 2 deterministiske agentene. Pluss de to sammenslåingene → fra 16 mot ~14 agenter med klarere ansvarslinjer.
---
## 5. Del 2 — Langform-kapabiliteten (`/linkedin:newsletter`)
Én ny kommando-fil (`commands/newsletter.md`), med interne faser og multi-sesjons-resumption. Følger den eksisterende LTL-kommando-malen (prinsipp 2, §3). Re-kjøring av kommandoen oppdager edition-state (5.2) og fortsetter der forrige økt slapp.
### 5.1 Faser (kondensering av de 16 fasene i 0.4)
| Step | Fase | Hva | Agenter/verktøy |
|------|------|-----|-----------------|
| 0 | **Load context** | Les edition-state/HANDOVER (5.2) for resumption, voice-profil (0.5), persona-bibliotek (6.1), serie-brief | Read |
| 1 | **Brief + kalibrering** | Vinkel, stemme, målgruppe-personaer (merk primær, 0.5), nøkkelpoeng, tone, leder-takeaway. Maks ~3 kalibrerings-spørsmål | AskUserQuestion |
| 2 | **Research** | Parallelle, avgrensede mandater → verifiserte notater. Triangulering | **Task-fan-out** i forgrunn (prinsipp 4, §3) |
| 3 | **Utkast** | Dramaturgisk rekkefølge, voice-matchet. Kan spenne flere sesjoner med vedlikeholdt HANDOVER | `content-repurposer` (utvidet) + Task |
| 4 | **Konsistens + kvalitet** | Tråder, premiss→konklusjon-bue, leder-takeaway, AI-slop-fjerning, formaterings-dose (alt i §8) | inline + `references/longform-quality-rules.md` |
| 5 | **Faktasjekk-sweep** | Risikosortert (🔴/🟡/🟢), «skyldig til motbevist», verifiseringslogg (0.5) | **`fact-checker`** (ny, parallell — 6.2) |
| 6 | **Persona-sweep — FØR lås** | Leser-jury, primær trumfer, konvergens-loop til rent JA (0.5) | **`persona-reviewer`** (ny, kjøres én gang per persona — 6.3) |
| 7 | **Annotering (valgfritt)** | Render annoterbar review-HTML for et manuelt pass | `render/build-html.mjs` (§7.5) |
| 8 | **LÅS → leveranse** | POST.html «alt på ett sted» (0.5) | `render/build-linkedin.mjs` (0.6) |
| 9 | **Hook-/konverterings-gate** | Persona-gate på distribusjonsteksten: «ville DU klikket?» — etter lås (0.5) | **`persona-reviewer`** i konverterings-modus (6.3) |
| 10 | **Planlegging** | Registrer edition i pluginens kø/state for native scheduling (0.3) | `hooks/scripts/queue-manager.mjs` |
### 5.2 Edition-state / HANDOVER (beslutning G)
Produksjons-state for en pågående edition bor i serie-mappa (`/Users/ktg/repos/maskinrommet/serier/<slug>/`), ikke i pluginen. Følger HANDOVER-mønsteret fra Seres (referansefil: `/Users/ktg/repos/maskinrommet/serier/silvija-seres-motsvar/HANDOVER.md`):
- **§1 Hvor vi er nå** — status + låst artikkel-tabell
- **§2 Publiseringskalender** — datoer
- **§3 Leveransen** — POST.html-kontrakt + filkart over serie-mappa
- **§4 Ufravikelige regler** — stil-/fakta-låser + faktasjekk-logg
- **§5 Metode** — persona-kalibrering, gjenbrukbar prosess
- **§6 Neste sesjon** — peker på neste handling
Kommandoen leser denne i Step 0 og oppdaterer den ved hver fase-overgang. Et lett `edition-state.json` (gjeldende fase + per-artikkel-status) kan komplettere for deterministisk resumption.
**Merk skillet mellom to HANDOVER-er:** (a) `docs/BUILD-HANDOVER.local.md` i pluginen styrer *byggingen av selve pluginen* (§9.2); (b) edition-HANDOVER i serie-mappa styrer *produksjonen av en nyhetsbrev-utgave*. De er ikke samme fil og blandes ikke.
### 5.3 Skill-plassering
Ingen ny skill opprettes. Langform legges som trigger/innhold i den eksisterende skillen `skills/linkedin-content-creation/SKILL.md` (som allerede dekker post/quick/batch/pipeline/templates/multiplatform).
---
## 6. Del 3 — Delte byggeklosser
### 6.1 Persona-bibliotek (beslutning D — hybrid)
- **`config/personas.template.md`** (+ en aktiv `config/personas.local.md`, gitignored via `*.local.md`): gjenbrukbare leser-profiler. Frø-personaene fra Seres (0.2): IT-divisjonsdirektør, KI-seksjonsleder, og linjeleder (primær).
- Per persona dokumenteres: rolle, hva som kobler dem av, hva som overbeviser dem, ekspertise-nivå, sjargong-toleranse.
- **Per-prosjekt-override:** `/linkedin:newsletter` velger relevante personaer fra biblioteket i Step 1 og merker primær i edition-briefen. (Om «primær trumfer», sekundær-NEI som signal osv., se 0.5.)
### 6.2 `fact-checker`-agent (ny — `agents/fact-checker.md`)
- **Modell:** Opus (verifiserings-resonnering; tillit-kritisk). **Tools:** `Read`, `WebSearch`.
- **Mandat:** Gitt en bolk faktapåstander → verifiser hver mot primær-/troverdig kilde, etter «skyldig til motbevist» (0.5). Aldri fyll hull med gjetninger — flagg uverifisert eksplisitt. Returner en verifiseringslogg + risikosortering 🔴/🟡/🟢.
- **Orkestrering:** `/linkedin:newsletter` (Step 5) lister alle påstander og fan-outer N parallelle `fact-checker`-kall (én per bolk) i forgrunn (prinsipp 4, §3), og samler loggene.
- **Prompt-arketype:** kopier gate-strukturen fra `agents/differentiation-checker.md` (søk → vurder → gate-utfall; «søk før du dømmer» er ikke-forhandlbart) — men endre mandatet fra *originalitet* til *faktuell korrekthet*. (Bygg en ny fil; ikke utvid differentiation-checker — de to svarer på ortogonale spørsmål.)
### 6.3 `persona-reviewer`-agent (ny, parameterisert — `agents/persona-reviewer.md`)
- **Modell:** Opus (nyansert leser-simulering). **Tools:** `Read`.
- **Mandat:** Les én persona-definisjon (fra 6.1) + teksten → døm på 6 akser: (1) holder hooken? (2) resonans — angår dette MEG? (3) tone — respektert vs. belært? (4) troverdighet — avvist som hype? (5) leder-takeaway + konkret handling? (6) lengde/driv? Returner topp-5 flagg med **retning, ikke ferdig omskriving**. Juryen skriver ALDRI tekst (0.5).
- **To moduser i SAMME agent-fil** (parameter i kallet):
- **Resonans-modus** (Step 6, FØR lås): «lander poenget for denne leseren?»
- **Konverterings-modus** (Step 9, etter lås): binær JA/NEI på «ville DU klikket?» — kun på hook/distribusjonsteksten, ikke artikkelen bak (0.5).
- **Konvergens-loop** (0.5): kommandoen kjører agenten på nytt mot oppdatert tekst og lar hver persona dømme LØST/DELVIS/IKKE per tidligere flagg, til rent JA fra primær.
### 6.4 Research-fan-out (ingen ny agent)
`/linkedin:newsletter` (Step 2) spawner parallelle `Task`-kall med inline-mandater (ett per delspørsmål), samler og triangulerer i forgrunn (prinsipp 4, §3). Ingen egen research-agent opprettes. Gjenbruk URL-fetch/multi-kilde-syntese-disiplinen som allerede finnes i `commands/react.md` der det er relevant.
---
## 7. Del 4 — Render-migrering (beslutning C)
### 7.1 Flytt render inn i pluginen
- Opprett mappen `render/` i pluginen og legg inn alle 4 skriptene (0.6) + `fonts/` + en `OFL.txt` (fontene Inter/JetBrains Mono/Source Serif 4/Newsreader er OFL-lisensierte og kan redistribueres — `shared/playground-design-system` i marketplace-en self-hoster allerede tre av dem, så praksisen er etablert).
- **cwd-modellen bevares:** kommandoen kjører `node ${CLAUDE_PLUGIN_ROOT}/render/build-linkedin.mjs <input>` med `cwd` = serie-mappa. Output følger `cwd`, fonts følger `__dirname` (pluginens `render/fonts/`). (`${CLAUDE_PLUGIN_ROOT}` peker til plugin-install-mappa og brukes allerede i eksisterende kommandoer, f.eks. for `clipboard-helper.mjs`.)
### 7.2 Generaliser `build-linkedin.mjs`
Skriptet har i dag hardkodet Seres-spesifikk kalender, captions og ferskvare-flagg. Generaliser det til å lese en **edition-config** (f.eks. `linkedin/edition-config.json` i serie-mappa) for disse verdiene. Endringen gjøres i pluginen (skriptet bor nå der); maskinrommet leverer config-fila per serie.
### 7.3 maskinrommet blir konsument
- `maskinrommet/tools/`-kopiene fjernes; maskinrommet kaller pluginens render-skript i stedet.
- **Dette er et eget arbeidsspor i maskinrommet-repoet** (`/Users/ktg/repos/maskinrommet/` — et ANNET repo, 0.1) og krever eksplisitt instruks fra operatøren når vi kommer dit. Planen beskriver det; selve utførelsen der gjøres separat. Pluginen fungerer uansett alene.
### 7.4 Graceful degradation (weasyprint)
- `build-html` + `build-linkedin` er zero-dep og virker alltid.
- `build-pdf` + `build-carousel` krever `weasyprint` (eksternt Python-verktøy, kan ikke bundles) på PATH → detekter, og gi en tydelig installasjons-instruks hvis det mangler. Pipelinen stopper aldri på manglende weasyprint; den hopper over PDF-stegene med en advarsel.
### 7.5 Annoterings-renderer som førsteklasses kapabilitet (beslutning H)
`build-html.mjs` (0.6) ER annoteringsverktøyet (marker → Endre/Legg til/Fjern/Avklar/Risiko → kommentar → sidepanel → localStorage → eksport av annoterings-markdown). I dag er det kronikk-spesifikt: kun `##`/`###`-overskrifter, ingen tabell-støtte, ingen inline-kode, og frontmatter forutsetter kronikk-felter. Sesjon S0b beviste mangelen — for å annotere *denne planen* (full av tabeller) måtte rendereren utvides.
- **Generaliser** `render/build-html.mjs` til å rendre et hvilket som helst markdown-artefakt: tabeller, alle overskriftsnivåer (`#``####`), inline `` `kode` ``, og generisk frontmatter/tittel. Selve annoterings-motoren (CSS + klient-JS) er allerede artefakt-uavhengig og gjenbrukes ordrett.
- **Bruksområde:** planer, briefer, ferdige poster, nyhetsbrev-editions, carousel-utkast — alt operatøren eller en Forgejo-nedlaster vil ha tilbakemelding på.
- **Reference-implementasjon (S0b):** en engangs-generator som henter motoren ut av `build-html.mjs` ved kjøring og legger til tabell-/overskrift-/kode-parsing. Lå i `/private/tmp/claude-ltl-review/gen.mjs` i den sesjonen (kan være slettet — den er ikke kilden, kun et bevis på at det virker). **Plugin-versjonen skal embedde motoren** (CSS + klient-JS) direkte i `render/build-html.mjs` — ingen runtime-avhengighet til maskinrommet eller til en temp-fil.
- **Integrasjon:** Step 7 i nyhetsbrev-pipelinen (§5.1) kaller denne. Genererte review-filer legges i `docs/review/` (gitignored — annoteringene lever uansett i nettleserens localStorage, ikke i fila).
- **Zero-dep** — virker alltid, ingen weasyprint.
---
## 8. Kvalitetsregler for langform
Disse stammer fra Seres-erfaringen (0.2) og skal kodes inn i `references/longform-quality-rules.md` + i `/linkedin:newsletter` og agentene:
- **Leder-takeaway:** hver tekst skal lande ÉN klar takeaway + én konkret handling. Beskjær referanser hardt; hands-on-troverdighet slår sitat-dynge.
- **Premiss→konklusjon-bue:** etabler ett klart premiss tidlig (ingress + første avsnitt); la avslutningen GRIPE premisset konkret og vri det framover (retning + ett håndfast grep), ikke bare oppsummere.
- **AI-slop-fraser (forbudt):** «her må jeg være ærlig» / «for å være ærlig»; «ikke bare X, men Y»; unødig tre-listing; «i en stadig mer kompleks verden»; påklistrede oppsummeringssetninger.
- **Generell, ikke etat-/person-spesifikk:** ingen personlige etat-anekdoter; presenter muligheter, ikke provokasjoner. Maks én strukturell forankrings-referanse per tekst (ikke gjentatt kritikk av en navngitt person).
- **Formaterings-dose (minimal):** fet = maks ett poeng per bolk; korte lister (24) kun der teksten allerede ramser opp — aldri gjør bærende resonnement til kulepunkter; tabeller sparsomt. «Ingen artikkel skal ligne en PowerPoint-utskrift.»
- **Gap lukkes med tightening/bytte, ikke utvidelse** — bytt svakere mot skarpere, hold lengden flat.
- **Kalibrering per sweep er et brukervalg, ikke default:** før hver faktasjekk-/persona-sweep avklares fold-inn-aggressivitet (konservativ vs. aggressiv), sjargong-håndtering, og persona-vekting ved konflikt.
---
## 9. Fasing og sesjons-dekomponering
### 9.1 Faser
Kritisk sti er langform-kapabiliteten (operatøren har 6 editions i pipen og vil produsere neste serie raskere). Konsolidering som ikke blokkerer langform kommer etterpå.
| Fase | Innhold | Avhengighet | Risiko |
|------|---------|-------------|--------|
| **1 — Fundament** | Render-migrering (§7) + persona-bibliotek (§6.1) + `fact-checker` + `persona-reviewer` (§6.2/§6.3) + edition-state-skjema (§5.2). Finansier agentene ved å avvikle `content-tracker` + `personalization-scorer` (§4.2). | — | Middels |
| **2 — Kapabiliteten** | `commands/newsletter.md` med alle faser (§5) + reconcile av newsletter-stien ut av `multiplatform.md` (§4.1). | Fase 1 | Middels |
| **3 — Dogfood** | Produser en ekte edition gjennom pipelinen. Åpne review-HTML i nettleser og gå gjennom kjerne-flytene. Fang og fiks friksjon FØR release. | Fase 2 | Lav |
| **4 — Renovering** | Resterende kommando-/agent-konsolidering (§4): templates→quick, publish→calendar, collab+speaking→outreach, authority→strategy, analytics/engagement-merge, router-gating. | Uavhengig | Middels |
Fase 4 kan kjøres parallelt med eller etter Fase 23. Hver konsolidering er en egen liten, testbar endring. v2.0.0 markerer fullført Fase 4.
### 9.2 Sesjons-dekomponering
Arbeidet spenner mange sesjoner. Hver sesjon = ÉN sammenhengende, testbar leveranse, holdt **innenfor 35% kontekst** (godt før compact). Én oppgave per sesjon — aldri start neste før HANDOVER (§9.3) er oppdatert. «S0» og «S0b» under er allerede gjort (planlegging); S1 er første bygge-sesjon.
| Sesjon | Fase | Leveranse | Verifisering (se §10) |
|--------|------|-----------|--------------|
| **S1** | 1 | Opprett `render/`; kopier de 4 skriptene + `fonts/` + OFL fra `/Users/ktg/repos/maskinrommet/tools/` INN i pluginen. (Ikke rør maskinrommet.) | Antakelse-test 13 |
| **S1a** | 1 | Generaliser annoterings-rendereren `render/build-html.mjs` (beslutning H): tabeller, `#``####`, inline-kode, generisk artefakt. Embed motoren. | Rendrer denne planen + en post + en brief rent; tabeller intakt |
| **S2** | 1 | Generaliser `render/build-linkedin.mjs` → leser `edition-config.json` (§7.2). | Antakelse-test 5 |
| **S3** | 1 | `config/personas.template.md` + frø-personaene (§6.1). | Fil finnes, 3 personaer, primær merket |
| **S4** | 1 | `agents/fact-checker.md` (§6.2) + test på prøvepåstander. | Returnerer verifiseringslogg + 🔴/🟡/🟢 |
| **S5** | 1 | `agents/persona-reviewer.md` (§6.3, 2 moduser) + test. | Returnerer 6-akse-flagg / JA-NEI |
| **S6** | 1 | Edition-state-skjema (§5.2) + avvikle `content-tracker` + `personalization-scorer` (§4.2). | `ls agents/` ned med 2; funksjon bevart |
| **S7** | 2 | `commands/newsletter.md` skjelett — Step 02 (§5.1). | Antakelse-test 4 (research-fan-out kjører) |
| **S8** | 2 | newsletter.md Step 34 (utkast + konsistens/kvalitet). | Utkast genereres voice-matchet |
| **S9** | 2 | newsletter.md Step 56 (faktasjekk + persona-sweep). | Begge agenter kalles parallelt; sweep FØR lås |
| **S10** | 2 | newsletter.md Step 710 (annotering, lås/leveranse, hook-gate, planlegging). | POST.html produseres; hook-gate etter lås |
| **S11** | 2 | Reconcile newsletter-sti ut av `multiplatform.md` (§4.1) + skill-trigger (§5.3) + router-rad i `linkedin.md`. | Kun ÉN inngang til newsletter |
| **S12** | 2 | `references/longform-quality-rules.md` (§8) + resumption-wiring (Step 0 leser edition-state). | Avbryt/gjenoppta-test |
| **S13** | 3 | Dogfood: produser en ekte edition ende-til-ende; logg friksjon. | Edition i serie-mappa; review-HTML i nettleser |
| **S14** | 3 | Fiks dogfood-friksjon. | Friksjonsliste lukket |
| **S15** | 4 | `templates.md` → modus i `quick.md` (§4.1). | quick dekker begge; templates fjernet |
| **S16** | 4 | `publish.md` → handling i `calendar.md` (§4.1). | calendar dekker publish |
| **S17** | 4 | `collab.md` + `speaking.md` → ny `outreach.md` (§4.1). | outreach dekker begge |
| **S18** | 4 | `authority.md``strategy.md` + trajectory-dedup + `profile.md` kanon (§4.1). | strategy dekker authority |
| **S19** | 4 | Agent-merge: analytics (2→1) + engagement (2→1) (§4.2). | `ls agents/` ned med 2 |
| **S20** | 4 | `import.md`-trim + router-gating (§4.1) + sluttdoc-pass → **v2.0.0**. | `ls commands/` verifisert ned; alle 3 doc-nivåer oppdatert |
Tabellen er veiledende. Hvis en sesjon nærmer seg 35% kontekst før leveransen er ferdig: splitt den, oppdater HANDOVER med delvis status, og la neste sesjon fullføre.
### 9.3 Bygge-protokoll (multi-sesjon)
**Single source of truth:** `docs/BUILD-HANDOVER.local.md` i pluginen (gitignored). Holder: hvor vi er nå, hva forrige sesjon gjorde/verifiserte, NESTE SESJON-oppgaven, ufravikelige regler, og en sesjons-logg. (Forveksles ikke med edition-HANDOVER i serie-mappa, §5.2.)
**Kontekst-budsjett:** Hver sesjon holdes innenfor 35% kontekst (før compact). Nærmer du deg: avslutt rent, ikke start nytt steg.
**Siste handling i HVER sesjon (ufravikelig):** Oppdater `BUILD-HANDOVER.local.md` — status, hva ble gjort og verifisert, og en presis NESTE SESJON-oppgave. Skriv aldri «gå til X» med mindre X eksisterer og er testet.
**Modell:** Opus 4.7 på alt. Ikke degrader til en mindre modell — vent heller på tilgjengelighet.
**Fast resume-kommando** (operatøren limer inn etter hver `/clear`, identisk hver gang):
```
Fortsett byggingen av LTL fullspektrum-innholdsmotor. Les
docs/BUILD-HANDOVER.local.md (single source of truth) og
docs/plan-fullspektrum-innholdsmotor.md. Utfør oppgaven merket
«NESTE SESJON» i HANDOVER, og BARE den. Hold deg innenfor 35%
kontekst. Siste handling: oppdater BUILD-HANDOVER.local.md.
Ikke push uten at jeg ber om det.
```
**Disiplin:** Én oppgave per sesjon. Test før HANDOVER oppdateres. Ikke utvid scope utover NESTE SESJON-oppgaven. Cross-repo-arbeid (maskinrommet, §7.3) krever eksplisitt instruks før utførelse.
---
## 10. Verifisering — hvordan en sesjon vet at den er ferdig
Dette er kjernen i at planen holder uten drift: hver sesjon har en **Definition of Done (DoD)** som er binær og kjørbar, og som sesjonen MÅ evaluere på seg selv før den oppdaterer HANDOVER.
### 10.0 Prinsipp: hva «ferdig» betyr
«Ferdig» = hvert DoD-punkt for sesjonen er objektivt bekreftet. Tre typer punkter, etter hvordan de bekreftes:
- **Deterministisk** — bekreftes med en kommando (filen finnes, `wc -l` stemmer, `node --test` grønt, `grep` gir forventet treff). Claude kjører kommandoen og leser utfallet. Ingen skjønn.
- **Kjent-svar-fixture** — for agenter/skript der output er strukturert: lag FØRST en liten fixture med fasit (jf. husregelen «ingen produksjonskode uten en feilende test først»), kjør, og sammenlign output mot fasit. Claude evaluerer mot fasiten, ikke mot egen magefølelse.
- **Subjektiv kvalitet — SELV-SERTIFISERES ALDRI.** Om en tekst «lander», treffer voice, er original eller prosakvalitet er skjønn. Claude skal ALDRI sette grønn hake på dette selv. Det rutes til én av to:
- en **gate-mekanisme** som produserer et eksplisitt verdikt (f.eks. `persona-reviewer` returnerer rent JA fra primær; `voice-trainer` bekrefter voice-match; `fact-checker` returnerer 0 åpne 🔴), eller
- **operatøren**, via den annoterbare review-HTML-en (§7.5).
- Et DoD-punkt av denne typen merkes `[GATE: <hvem>]` eller `[OPERATØR]` og regnes som uoppfylt til gaten/operatøren har gitt verdikt. En sesjon er ikke ferdig fordi Claude «mener» kvaliteten er god.
Hvis et DoD-punkt ikke kan bekreftes: sesjonen er IKKE ferdig. Skriv den faktiske statusen i HANDOVER (hva som gjenstår), ikke en grønn hake.
### 10.1 Nøkkelantakelser (test umiddelbart i sesjonen som rører dem)
1. **Render kjørbar fra pluginen med riktig cwd.** `cd <serie-mappe> && node <plugin>/render/build-linkedin.mjs utkast/01-*.md``linkedin/01/POST.html` + `linkedin/samle/POST.html` finnes i serie-mappa. *(Deterministisk.)*
2. **Bundlede fonts resolver via `__dirname`.** `build-pdf` produserer PDF med Newsreader/Inter fra plugin-lokasjonen, ikke fallback. *(Deterministisk: inspiser PDF-metadata / visuell sjekk.)*
3. **`${CLAUDE_PLUGIN_ROOT}` eksponert i kommando-Bash.** Et Step som ekko-er stien resolver til plugin-install-mappa. *(Deterministisk.)*
4. **Task-fan-out i forgrunn beholder Task-verktøyet.** Kommandoen spawner 2+ parallelle research-`Task`-kall og får strukturerte svar (ikke degradert). *(Deterministisk: tell parallelle kall + svar.)*
5. **Generalisert `build-linkedin` leser edition-config.** Endre `edition-config.json` → POST.html-output endres uten kode-endring. *(Deterministisk: diff to kjøringer.)*
### 10.2 DoD-arketyper
Hver sesjon arver én arketype + sine egne spesifikke verdier (§10.3).
- **A — Render-flytting (S1):** (a) `ls render/` viser 4 skript + `fonts/` + `OFL.txt`; (b) nøkkelantakelse 13 grønne; (c) zero-dep-skriptene kjører uten `npm install`.
- **B — Skript-generalisering (S1a, S2):** (a) ny `node --test`-fil skrevet FØRST, feiler før endring; (b) etter endring: testen grønn; (c) **regresjon**: en kjent input gir uendret output for det gamle bruksmønsteret (diff = tom).
- **C — Template/config-fil (S3, S6-skjema):** (a) filen finnes; (b) påkrevde felter til stede (`grep`); (c) parser uten feil (`node -e` som leser den).
- **D — Ny agent (S4, S5):** (a) fil finnes med gyldig frontmatter (`model: opus`, `tools`, `description` — sjekk med `grep`/parse); (b) **kjent-svar-fixture skrevet FØRST** (f.eks. 3 påstander: én sann/én falsk/én uverifiserbar → forventet 🟢/🔴/🟡); (c) agent-kjøring mot fixturen matcher fasit-formen og -dommen; (d) `[GATE]` agenten skriver ALDRI om tekst selv (verifiser at output er flagg/retning, ikke ny copy).
- **E — Kommando-steg (S7S10, S12):** (a) steget kjører på en dummy-serie-fixture og produserer det spesifiserte artefaktet (fil finnes / forventet form); (b) resumption der relevant: avbryt → re-kjør → fortsetter fra riktig steg via edition-state (deterministisk); (c) subjektive steg (utkast, persona-sweep) er `[GATE]`/`[OPERATØR]`, ikke selv-sertifisert.
- **F — Konsolidering (S11, S15S20):** (a) **kapabilitets-sjekkliste**: hver enkeltfunksjon i forgjenger-fila er enumerert og verifisert til stede i målet (punkt for punkt); (b) forgjenger-fil fjernet; (c) `ls commands/ | wc -l` / `ls agents/ | wc -l` viser forventet, lavere tall; (d) **ingen daudlenker**: `grep -rn "<gammelt-navn>" commands/ agents/ skills/ hooks/ README.md` gir kun tilsiktede treff; (e) router (`linkedin.md`) oppdatert.
- **G — Dogfood (S13S14):** (a) en ekte edition produsert ende-til-ende (filer i serie-mappa); (b) rekkefølge-bevis: edition-HANDOVER viser persona-sweep FØR lås; (c) friksjon logget i HANDOVER; (d) for S14: hvert lukket friksjonspunkt re-testet med en konkret sjekk, ikke bare «fikset».
### 10.3 DoD per sesjon (konkret)
| Sesjon | Arketype | Spesifikt «suksess ser slik ut» |
|--------|----------|-------------------------------|
| S1 | A | 4 skript + fonts + OFL i `render/`; antakelse 13 grønne |
| S1a | B | `node --test` for tabell/`#``####`/inline-kode grønn; rendrer denne planen med 9 tabeller intakt; gammel kronikk rendrer uendret (regresjon) |
| S2 | B | Test for edition-config-lesing grønn; antakelse 5 (diff to configs); gammel Seres-input gir uendret POST.html om config matcher dagens hardkoding (regresjon) |
| S3 | C | `personas.template.md` finnes; 3 personaer med alle felt (rolle/avkobler/overbeviser/ekspertise/sjargong); primær merket; parser OK |
| S4 | D | `fact-checker.md` + fixture med 3 påstander → output 🟢/🔴/🟡 matcher fasit; flagger uverifiserbar som 🟡 (ikke gjetter) |
| S5 | D | `persona-reviewer.md` + fixture: returnerer ≤5 flagg på 6 akser med retning; INGEN omskrevet copy; begge moduser gir riktig form |
| S6 | C+F | edition-state-skjema parser; `content-tracker`+`personalization-scorer` fjernet; `ls agents/`=14→ (forventet); funksjon dekket av script (kjør scriptet) |
| S7 | E | newsletter.md Step 02 kjører på dummy-serie; antakelse 4 (parallell fan-out) |
| S8 | E | Step 34 produserer utkast-fil; `[OPERATØR]`/`[GATE: voice-trainer]` for voice-match — ikke selv-sertifisert |
| S9 | E | Step 56: `fact-checker` + `persona-reviewer` kalles parallelt; sweep skjer FØR lås (rekkefølge-assert); `[GATE]` rent JA fra primær kreves for å gå videre |
| S10 | E | Step 710: POST.html produsert av render; hook-gate kjører ETTER lås (rekkefølge-assert); edition registrert i kø |
| S11 | F | Newsletter-sti fjernet fra `multiplatform.md` (kun én inngang — `grep`); skill-trigger lagt til; router-rad lagt til |
| S12 | C+E | `longform-quality-rules.md` finnes m/ alle regler i §8; resumption: avbryt etter Step 6 → re-kjør → fortsetter fra Step 7 |
| S13 | G | Ekte edition i serie-mappa; edition-HANDOVER viser sweep-før-lås; review-HTML åpnet; friksjon logget |
| S14 | G | Hvert friksjonspunkt fra S13 re-testet med konkret sjekk; restliste tom eller eksplisitt deferert |
| S15 | F | `quick.md` dekker alle 8 templates-typer (sjekkliste); `templates.md` fjernet; `ls commands/` ned 1; ingen daudlenker |
| S16 | F | `calendar.md` dekker publish-handlingen (sjekkliste); `publish.md` fjernet; ned 1; ingen daudlenker |
| S17 | F | `outreach.md` dekker collab+speaking (sjekkliste m/ hver funksjon); begge forgjengere fjernet; ned 1 netto; ingen daudlenker |
| S18 | F | `strategy.md` dekker authority + trajectory; `audit`/`analyze` peker til `profile` som kanon; `authority.md` fjernet; ingen daudlenker |
| S19 | F | analytics (2→1) + engagement (2→1); `ls agents/` ned 2; hver modus i den slåtte agenten dekker forgjengernes funksjoner (sjekkliste) |
| S20 | F | `import.md` analyse delegert til `report`; router-gating på plass; **v2.0.0** i alle versjons-referanser (`grep`); alle 3 doc-nivåer oppdatert |
### 10.4 Selv-evaluerings-protokoll (kjøres ved hver sesjonsslutt)
1. Hent sesjonens DoD fra §10.3 (+ arketypen i §10.2).
2. Gå gjennom hvert punkt og kjør sjekken. Skriv utfall: ✅ (bekreftet med kommando/fixture), 🔶 (`[GATE]`/`[OPERATØR]` — venter på verdikt), eller ❌ (feilet/ikke gjort).
3. **Ærlighetsregel:** Aldri ✅ på et punkt du ikke faktisk har kjørt. Aldri ✅ på subjektiv kvalitet — den er 🔶 til gate/operatør har dømt.
4. Skriv resultatet i HANDOVER-ens sesjons-logg, og sett NESTE SESJON. Er noe ❌/🔶 som blokkerer: NESTE SESJON er å lukke det, ikke å gå videre.
5. Bare når alle DoD-punkter er ✅ (eller bevisst 🔶-deferert med operatørens viten) regnes sesjonen som ferdig.
### 10.5 Doc-plikt (obligatorisk ved push)
Enhver feature-endring oppdaterer i SAMME commit: plugin-`README.md`, plugin-`CLAUDE.md`, og rot-`README.md` (`/Users/ktg/.claude/plugins/marketplaces/ktg-plugin-marketplace/README.md`).
---
## 11. Risikoer og åpne spørsmål
| # | Risiko / spørsmål | Håndtering |
|---|-------------------|------------|
| R1 | **Cross-repo render-migrering** berører maskinrommet (§7.3, annet repo). | Krev eksplisitt instruks før endring der. Pluginen virker alene uansett. |
| R2 | **weasyprint kan ikke bundles.** | Graceful degradation (§7.4). Zero-dep-skriptene dekker kjerne-leveransen (POST.html). |
| R3 | **Font-vekt** øker plugin-størrelsen. | Verifiser total størrelse i S1; OFL-lisens vedlegges. Akseptabelt for kapabiliteten. |
| R4 | **Konsolidering kan bryte eksisterende arbeidsflyt** (sammenslåtte kommandoer). | Hver merge er egen testbar endring med kapabilitets-sjekkliste; router oppdateres. |
| R5 | **Opus-kostnad** ved mange parallelle fact-checker/persona-kall. | Forventet og akseptert (operatørens preferanse: Opus på alt; tillit-kritisk arbeid). Eskalér til operatør hvis volumet blir et problem. |
| Q1 | `edition-config.json` som JSON eller frontmatter-md? | Avklares i S2 (JSON anbefales for deterministisk parsing). |
| Q2 | Skal `video-scripter` absorberes i `content-repurposer`? | Marginal; avgjøres i S19 etter nærlesning. |
| Q3 | Konsolidering av de eksisterende kommandoene utover §4 (større refaktor)? | Utenfor denne planens scope. Noteres som mulig fremtidig spor. |
---
## 12. Filmanifest
Alle stier relativt til pluginen `/Users/ktg/.claude/plugins/marketplaces/ktg-plugin-marketplace/plugins/linkedin-thought-leadership/` med mindre annet er angitt.
**Nye filer:**
- `commands/newsletter.md` — orkestrator-kommandoen (§5)
- `commands/outreach.md` — fra sammenslåing av collab + speaking (§4.1)
- `agents/fact-checker.md` — ny agent, Opus (§6.2)
- `agents/persona-reviewer.md` — ny agent, Opus, 2 moduser (§6.3)
- `config/personas.template.md` — persona-bibliotek (§6.1)
- `render/build-linkedin.mjs`, `render/build-carousel.mjs`, `render/build-html.mjs`, `render/build-pdf.mjs` — flyttet fra maskinrommet (§7); `build-linkedin` generalisert (§7.2); `build-html` generalisert til artefakt-annoterings-renderer (§7.5)
- `render/fonts/` + `render/OFL.txt`
- `references/longform-quality-rules.md` — kodede kvalitetsregler (§8)
**Endrede filer:**
- `commands/linkedin.md` (router) — ny rad for newsletter + nivå-gating av aspirasjonelle kommandoer (§4.1)
- `commands/quick.md`, `calendar.md`, `strategy.md`, `import.md`, `multiplatform.md`, `profile.md`, `audit.md`, `analyze.md` — konsolidering (§4.1)
- `agents/analytics-interpreter.md` (+ avvikle `performance-reporter`), `agents/engagement-coach.md` (+ avvikle `comment-strategist`) — sammenslåing (§4.2)
- `skills/linkedin-content-creation/SKILL.md` — langform-trigger (§5.3)
- `hooks/hooks.template.json` + kjør `python3 hooks/scripts/compile-hooks.py` — ev. SessionStart-resumption for editions
- `.gitignore``docs/review/` lagt til (genererte annoterings-artefakter) ✓ gjort i S0b
- `README.md`, `CLAUDE.md` (plugin) + rot-`README.md` — doc-plikt ved hver push
**Fjernede filer (konsolidering, §4):**
- `agents/content-tracker.md`, `agents/personalization-scorer.md` (→ deterministisk script)
- `agents/performance-reporter.md`, `agents/comment-strategist.md` (slått inn i analytics/engagement)
- `commands/templates.md`, `commands/publish.md`, `commands/authority.md` (slått inn i quick/calendar/strategy)
- `commands/collab.md`, `commands/speaking.md` (→ `commands/outreach.md`)
**Cross-repo (eget spor i `/Users/ktg/repos/maskinrommet/`, krever eksplisitt instruks — §7.3):**
- `maskinrommet/tools/` — kopiene fjernes; maskinrommet kaller pluginens render
- `maskinrommet/serier/<slug>/linkedin/edition-config.json` — ny per-serie config

View file

@ -0,0 +1,82 @@
# Bygge-brief (Voyage-input) — LTL fullspektrum-innholdsmotor → v2.0.0
> **Formål:** Input til `/trekplan` (Voyage-pluginen). Dette er byggingen av LTL-pluginen, drevet som et Voyage-prosjekt: `/trekplan` produserer en kjørbar plan med per-steg Manifests, `/trekexecute --fg` + `/trekcontinue` driver sesjonene, `/trekreview` er release-gate.
> **Detaljert referanse:** [`plan-fullspektrum-innholdsmotor.md`](./plan-fullspektrum-innholdsmotor.md) (samme mappe) — den hardnede planen med §0 orientering, §4 renovering, §5 langform, §6 byggeklosser, §7 render, §10 DoD. **Les den; den er fasit for hva som skal bygges.**
> **Opprinnelig retningsbrief:** [`brief-fullspektrum-innholdsmotor.md`](./brief-fullspektrum-innholdsmotor.md).
---
## 1. Oppgave
Løft `linkedin-thought-leadership`-pluginen («LTL», v1.2.0) til **v2.0.0**: en fullspektrum-motor for ALT LinkedIn-innhold — fra kortform-post til nyhetsbrev-edition — samtidig som den totale kommando-/agent-overflaten **reduseres** gjennom konsolidering.
Tre arbeidskropper:
1. **Renovering** — konsolider reell redundans (27→~23 kommandoer, 16→~14 agenter). Plan §4.
2. **Langform-kapabilitet** — én ny kommando `/linkedin:newsletter` med faset, multi-sesjons pipeline (research → utkast → faktasjekk → persona-review FØR lås → leveranse → hook-gate). Plan §5§6.
3. **Render + annotering inn i pluginen** — flytt 4 render-skript + fonts; generaliser `build-linkedin`; generaliser `build-html` til artefakt-annoterings-renderer. Plan §7.
## 2. Orientering (kritisk — repoer og stier)
- **LTL-pluginen (bygg her):** `/Users/ktg/.claude/plugins/marketplaces/ktg-plugin-marketplace/plugins/linkedin-thought-leadership/`. Plugin-i-monorepo: steg-stier er relative til denne mappa (sett Execution Strategy `cwd:` deretter).
- **Marketplace-rot:** `/Users/ktg/.claude/plugins/marketplaces/ktg-plugin-marketplace/` (Forgejo, aldri GitHub).
- **maskinrommet (annet repo):** `/Users/ktg/repos/maskinrommet/` — render-skriptene kopieres HERFRA inn i pluginen. Skriving TIL maskinrommet krever eksplisitt instruks (cross-repo, eget spor).
- **Voyage:** `/Users/ktg/.claude/plugins/marketplaces/ktg-plugin-marketplace/plugins/voyage/` — harness for denne byggingen.
- **svv-memory (prosess-erfaring):** `/Users/ktg/.claude/projects/-Users-ktg-repos-svv/memory/`.
- **Seres-serien** (kilden til langform-prosessen): `/Users/ktg/repos/maskinrommet/serier/silvija-seres-motsvar/`.
Full ordliste (Seres, persona-sweep, hook-gate, faktasjekk «skyldig til motbevist», POST.html, Altinn-feilen, «største prosessfeil») står i **plan §0** — les den før planlegging.
## 3. Låste beslutninger (constraints — IKKE åpne for re-litigering)
- **A:** Én ny orkestrator-kommando `/linkedin:newsletter`, ikke en suite, ikke utvidelse av `pipeline`.
- **B:** Bare langform nå — eksisterende kortform-kommandoer røres ikke.
- **C:** Ship alle 4 render-skript + fonts i pluginen (`render/`); generaliser `build-linkedin`; maskinrommet blir konsument.
- **D:** Hybrid persona-bibliotek i `config/`, override per prosjekt; primær merkes.
- **E:** Faktasjekk er et eget steg.
- **F:** Konsolider redundans i samme runde (netto færre kommandoer/agenter).
- **G:** Edition-produksjons-state bor i serie-mappa (maskinrommet), ikke i plugin-state.
- **H:** `build-html` generaliseres til artefakt-annoterings-renderer (tabeller, alle overskrifter, inline-kode).
## 4. Forskning er allerede gjort (ikke gjenta fra null)
`/trekplan`-utforskningen kan være **bekreftende, ikke fra-scratch**. Følgende er ferdig kartlagt og frosset i planen:
- **Inventar-audit** av alle 27 kommandoer + 16 agenter (overlapp/redundans/langform-relevans) → plan §4, §6.
- **Render-kontraktene** (4 skript, cwd-modell, weasyprint-avhengighet, build-linkedin hardkoding) → plan §0.6, §7.
- **Prosess-erfaringen** (16 faser, persona-sweep-før-lås, faktasjekk-sweep) → plan §0.4, svv-memory.
- **Arkitektur-mønstre** i LTL (kommando-mal, agent-frontmatter, hook-kompilering, state-updater) → plan §3.
`/trekplan` bør verifisere disse mot faktiske filer der det er billig, ikke re-derivere.
## 5. Scope og fasing
Fire faser (plan §9.1). Kritisk sti = langform (fase 13); renovering (fase 4) kan følge etter.
- **Fase 1 — Fundament:** render-migrering + annoterings-generalisering + persona-bibliotek + `fact-checker` + `persona-reviewer` + edition-state-skjema; finansier de 2 nye agentene ved å avvikle `content-tracker` + `personalization-scorer`.
- **Fase 2 — Kapabilitet:** `commands/newsletter.md` (10 steg) + reconcile newsletter-sti ut av `multiplatform`.
- **Fase 3 — Dogfood:** produser en ekte edition ende-til-ende; fiks friksjon.
- **Fase 4 — Renovering:** templates→quick, publish→calendar, collab+speaking→outreach, authority→strategy, analytics/engagement-merge, router-gating → v2.0.0.
Sesjons-dekomponeringen S1S20 i plan §9.2 er forslag til Execution Strategy. Hver sesjon ≤ 35% kontekst.
**Utenfor scope:** variabel-intensitet-deling av agenter inn i kortform (utsatt, beslutning B); konsolidering utover §4; cross-repo-endringer i maskinrommet (eget spor, krever instruks).
## 6. Suksesskriterier (per-steg DoD → Manifests)
Plan §10 definerer DoD per sesjon. Oversett disse til Voyage per-steg **Manifests** (`expected_paths`, `min_file_count`, `must_contain`, `commit_message_pattern`, `forbidden_paths`):
- **Deterministisk** (Voyage `Verify:` + Manifest + Phase 7.5-audit): filer finnes, `ls | wc -l` stemmer, `node --test` grønt, `grep` gir forventet, render kjører fra serie-mappa, edition-config-bytte endrer output.
- **Kjent-svar-fixture** (skriv FØR implementasjon, jf. husregel «ingen produksjonskode uten feilende test først»): `fact-checker` mot 3 påstander (sann/falsk/uverifiserbar → 🟢/🔴/🟡); `persona-reviewer` mot test-tekst (≤5 flagg, 6 akser, INGEN omskrevet copy).
- **Subjektiv kvalitet — ALDRI selv-sertifisert.** Voice-match, om teksten «lander», prosakvalitet rutes til (a) gate-agent med eksplisitt verdikt (`persona-reviewer` rent JA fra primær; `voice-trainer`-bekreftelse) eller (b) operatør via annoterbar review-HTML. Et slikt steg er ikke «ferdig» på Claudes eget skjønn. (Voyage Phase 7.5-audit er sikkerhetsnettet mot hallusinert «ferdig».)
## 7. Ufravikelige regler
- **Modell:** Opus 4.7 på alt (Voyage `profile: premium`). Ikke degrader.
- **Kjøremodus:** `/trekexecute --fg` (sekvensielt, abonnement). IKKE parallell `claude -p` (API-billing). `/trekcontinue` per fersk sesjon.
- **Doc-plikt:** hver feature-endring som pushes oppdaterer plugin-README + plugin-CLAUDE + rot-README i samme commit.
- **Cross-repo:** ingen skriving til `/Users/ktg/repos/maskinrommet/` uten eksplisitt instruks.
- **Ingen push** uten eksplisitt instruks.
- **Hooks (bash 3.2 / Node):** alle nye hooks i Node `.mjs`; rediger `hooks/hooks.template.json` + `hooks/prompts/*.md` → kjør `python3 hooks/scripts/compile-hooks.py`.
- **Redaksjonell kvalitet på .md-prompter** (kommandoer/agenter) verifiseres av operatør via annoterings-HTML, ikke mekanisk.
## 8. Åpne spørsmål (avklares i plan, ikke blokkerende)
- `edition-config.json` JSON vs. frontmatter (anbefalt JSON).
- `video-scripter` absorberes i `content-repurposer`? (besluttes sent).

View file

@ -0,0 +1,82 @@
# Bygge-brief (Voyage-input) — LTL fullspektrum-innholdsmotor → v2.0.0
> **Formål:** Input til `/trekplan` (Voyage-pluginen). Dette er byggingen av LTL-pluginen, drevet som et Voyage-prosjekt: `/trekplan` produserer en kjørbar plan med per-steg Manifests, `/trekexecute --fg` + `/trekcontinue` driver sesjonene, `/trekreview` er release-gate.
> **Detaljert referanse:** [`plan-fullspektrum-innholdsmotor.md`](./plan-fullspektrum-innholdsmotor.md) (samme mappe) — den hardnede planen med §0 orientering, §4 renovering, §5 langform, §6 byggeklosser, §7 render, §10 DoD. **Les den; den er fasit for hva som skal bygges.**
> **Opprinnelig retningsbrief:** [`brief-fullspektrum-innholdsmotor.md`](./brief-fullspektrum-innholdsmotor.md).
---
## 1. Oppgave
Løft `linkedin-thought-leadership`-pluginen («LTL», v1.2.0) til **v2.0.0**: en fullspektrum-motor for ALT LinkedIn-innhold — fra kortform-post til nyhetsbrev-edition — samtidig som den totale kommando-/agent-overflaten **reduseres** gjennom konsolidering.
Tre arbeidskropper:
1. **Renovering** — konsolider reell redundans (27→~23 kommandoer, 16→~14 agenter). Plan §4.
2. **Langform-kapabilitet** — én ny kommando `/linkedin:newsletter` med faset, multi-sesjons pipeline (research → utkast → faktasjekk → persona-review FØR lås → leveranse → hook-gate). Plan §5§6.
3. **Render + annotering inn i pluginen** — flytt 4 render-skript + fonts; generaliser `build-linkedin`; generaliser `build-html` til artefakt-annoterings-renderer. Plan §7.
## 2. Orientering (kritisk — repoer og stier)
- **LTL-pluginen (bygg her):** `/Users/ktg/.claude/plugins/marketplaces/ktg-plugin-marketplace/plugins/linkedin-thought-leadership/`. Plugin-i-monorepo: steg-stier er relative til denne mappa (sett Execution Strategy `cwd:` deretter).
- **Marketplace-rot:** `/Users/ktg/.claude/plugins/marketplaces/ktg-plugin-marketplace/` (Forgejo, aldri GitHub).
- **maskinrommet (annet repo):** `/Users/ktg/repos/maskinrommet/` — render-skriptene kopieres HERFRA inn i pluginen. Skriving TIL maskinrommet krever eksplisitt instruks (cross-repo, eget spor).
- **Voyage:** `/Users/ktg/.claude/plugins/marketplaces/ktg-plugin-marketplace/plugins/voyage/` — harness for denne byggingen.
- **svv-memory (prosess-erfaring):** `/Users/ktg/.claude/projects/-Users-ktg-repos-svv/memory/`.
- **Seres-serien** (kilden til langform-prosessen): `/Users/ktg/repos/maskinrommet/serier/silvija-seres-motsvar/`.
Full ordliste (Seres, persona-sweep, hook-gate, faktasjekk «skyldig til motbevist», POST.html, Altinn-feilen, «største prosessfeil») står i **plan §0** — les den før planlegging.
## 3. Låste beslutninger (constraints — IKKE åpne for re-litigering)
- **A:** Én ny orkestrator-kommando `/linkedin:newsletter`, ikke en suite, ikke utvidelse av `pipeline`.
- **B:** Bare langform nå — eksisterende kortform-kommandoer røres ikke.
- **C:** Ship alle 4 render-skript + fonts i pluginen (`render/`); generaliser `build-linkedin`; maskinrommet blir konsument.
- **D:** Hybrid persona-bibliotek i `config/`, override per prosjekt; primær merkes.
- **E:** Faktasjekk er et eget steg.
- **F:** Konsolider redundans i samme runde (netto færre kommandoer/agenter).
- **G:** Edition-produksjons-state bor i serie-mappa (maskinrommet), ikke i plugin-state.
- **H:** `build-html` generaliseres til artefakt-annoterings-renderer (tabeller, alle overskrifter, inline-kode).
## 4. Forskning er allerede gjort (ikke gjenta fra null)
`/trekplan`-utforskningen kan være **bekreftende, ikke fra-scratch**. Følgende er ferdig kartlagt og frosset i planen:
- **Inventar-audit** av alle 27 kommandoer + 16 agenter (overlapp/redundans/langform-relevans) → plan §4, §6.
- **Render-kontraktene** (4 skript, cwd-modell, weasyprint-avhengighet, build-linkedin hardkoding) → plan §0.6, §7.
- **Prosess-erfaringen** (16 faser, persona-sweep-før-lås, faktasjekk-sweep) → plan §0.4, svv-memory.
- **Arkitektur-mønstre** i LTL (kommando-mal, agent-frontmatter, hook-kompilering, state-updater) → plan §3.
`/trekplan` bør verifisere disse mot faktiske filer der det er billig, ikke re-derivere.
## 5. Scope og fasing
Fire faser (plan §9.1). Kritisk sti = langform (fase 13); renovering (fase 4) kan følge etter.
- **Fase 1 — Fundament:** render-migrering + annoterings-generalisering + persona-bibliotek + `fact-checker` + `persona-reviewer` + edition-state-skjema; finansier de 2 nye agentene ved å avvikle `content-tracker` + `personalization-scorer`.
- **Fase 2 — Kapabilitet:** `commands/newsletter.md` (10 steg) + reconcile newsletter-sti ut av `multiplatform`.
- **Fase 3 — Dogfood:** produser en ekte edition ende-til-ende; fiks friksjon.
- **Fase 4 — Renovering:** templates→quick, publish→calendar, collab+speaking→outreach, authority→strategy, analytics/engagement-merge, router-gating → v2.0.0.
Sesjons-dekomponeringen S1S20 i plan §9.2 er forslag til Execution Strategy. Hver sesjon ≤ 35% kontekst.
**Utenfor scope:** variabel-intensitet-deling av agenter inn i kortform (utsatt, beslutning B); konsolidering utover §4; cross-repo-endringer i maskinrommet (eget spor, krever instruks).
## 6. Suksesskriterier (per-steg DoD → Manifests)
Plan §10 definerer DoD per sesjon. Oversett disse til Voyage per-steg **Manifests** (`expected_paths`, `min_file_count`, `must_contain`, `commit_message_pattern`, `forbidden_paths`):
- **Deterministisk** (Voyage `Verify:` + Manifest + Phase 7.5-audit): filer finnes, `ls | wc -l` stemmer, `node --test` grønt, `grep` gir forventet, render kjører fra serie-mappa, edition-config-bytte endrer output.
- **Kjent-svar-fixture** (skriv FØR implementasjon, jf. husregel «ingen produksjonskode uten feilende test først»): `fact-checker` mot 3 påstander (sann/falsk/uverifiserbar → 🟢/🔴/🟡); `persona-reviewer` mot test-tekst (≤5 flagg, 6 akser, INGEN omskrevet copy).
- **Subjektiv kvalitet — ALDRI selv-sertifisert.** Voice-match, om teksten «lander», prosakvalitet rutes til (a) gate-agent med eksplisitt verdikt (`persona-reviewer` rent JA fra primær; `voice-trainer`-bekreftelse) eller (b) operatør via annoterbar review-HTML. Et slikt steg er ikke «ferdig» på Claudes eget skjønn. (Voyage Phase 7.5-audit er sikkerhetsnettet mot hallusinert «ferdig».)
## 7. Ufravikelige regler
- **Modell:** Opus 4.7 på alt (Voyage `profile: premium`). Ikke degrader.
- **Kjøremodus:** `/trekexecute --fg` (sekvensielt, abonnement). IKKE parallell `claude -p` (API-billing). `/trekcontinue` per fersk sesjon.
- **Doc-plikt:** hver feature-endring som pushes oppdaterer plugin-README + plugin-CLAUDE + rot-README i samme commit.
- **Cross-repo:** ingen skriving til `/Users/ktg/repos/maskinrommet/` uten eksplisitt instruks.
- **Ingen push** uten eksplisitt instruks.
- **Hooks (bash 3.2 / Node):** alle nye hooks i Node `.mjs`; rediger `hooks/hooks.template.json` + `hooks/prompts/*.md` → kjør `python3 hooks/scripts/compile-hooks.py`.
- **Redaksjonell kvalitet på .md-prompter** (kommandoer/agenter) verifiseres av operatør via annoterings-HTML, ikke mekanisk.
## 8. Åpne spørsmål (avklares i plan, ikke blokkerende)
- `edition-config.json` JSON vs. frontmatter (anbefalt JSON).
- `video-scripter` absorberes i `content-repurposer`? (besluttes sent).

View file

@ -0,0 +1,948 @@
---
task: "Lift linkedin-thought-leadership plugin v1.2.0 → v2.0.0 — full-spectrum content engine + newsletter, net-fewer commands/agents"
slug: ltl-v2-fullspektrum
project_dir: docs/voyage-build
created: 2026-05-26
plan_version: 1.7
profile: premium
phase_models:
plan: opus
execute: opus
profile_source: state-mandate
---
# LTL v2.0.0 — Full-Spectrum LinkedIn Content Engine
> **Plan quality: A** (90/100) — APPROVE_WITH_NOTES (revised after adversarial review: 3 blockers + 5 major + 4 minor resolved; see Revisions)
>
> Generated by trekplan v5.1.1 on 2026-05-26 — `plan_version: 1.7`
>
> **This is the Voyage-executable plan.** The authoritative human spec is
> [`../plan-fullspektrum-innholdsmotor.md`](../plan-fullspektrum-innholdsmotor.md)
> (the "fasit"). This file translates that spec's 20 sessions (S1S20) into
> Voyage `### Step N:` steps with per-step Manifests. Where the two differ,
> the corrections in this plan (verified against actual files by the
> exploration swarm) win — they are noted inline.
## Context
LTL must own the entire chain for ALL LinkedIn content — from short-form post
to newsletter edition — at the quality the "Seres series" production proved
possible, **while reducing** the total command/agent surface through
consolidation. Three work-bodies (brief §1):
1. **Renovation** — consolidate real redundancy (27→~23 commands, 16→~14 agents).
2. **Long-form capability** — one new `/linkedin:newsletter` command with a
phased, multi-session pipeline (research → draft → fact-check → persona-review
BEFORE lock → delivery → hook-gate).
3. **Render + annotation into the plugin** — move 4 render scripts + fonts;
generalize `build-linkedin`; generalize `build-html` into an artifact
annotation renderer.
Decisions AH (fasit §2) are LOCKED and not re-litigated here. The model is
Opus 4.7 on everything; execution is `/trekexecute --fg` (subscription),
`/trekcontinue` per fresh session.
## Architecture Diagram
```mermaid
graph TD
subgraph "Plugin = engine"
CMD["/linkedin:newsletter (new)"]
FC["agents/fact-checker (new)"]
PR["agents/persona-reviewer (new)"]
PERS["config/personas.template.md (new)"]
RENDER["render/ — build-html, build-linkedin, build-pdf, build-carousel + fonts"]
QRULES["references/longform-quality-rules.md (new)"]
CMD --> FC
CMD --> PR
CMD --> PERS
CMD --> RENDER
CMD --> QRULES
end
subgraph "Consolidation (net fewer)"
Q["templates→quick"]
C["publish→calendar"]
O["collab+speaking→outreach"]
A["authority→strategy"]
AN["analytics merge / engagement merge"]
RETIRE["retire content-tracker + personalization-scorer → scripts"]
end
subgraph "maskinrommet = workbench (other repo, read-only here)"
SERIE["serier/<slug>/ — content + edition-state + edition-config.json"]
end
RENDER -. "cwd = serie-mappe" .-> SERIE
```
## Codebase Analysis
- **Tech stack:** Claude Code plugin. Commands + agents are `.md` files
(YAML frontmatter + Markdown system-prompt bodies). Hooks are Node.js `.mjs`
compiled from `hooks/hooks.template.json` via `hooks/scripts/compile-hooks.py`.
Tests use the built-in `node:test` + `node:assert/strict`. Zero npm deps in
hooks/scripts. Node v25.8.2 (so `node --test <dir>` is broken — glob form only).
- **Size:** 166 source files (medium). 27 commands, 16 agents (+ `agents/README.md`),
9 hooks, 6 skills.
- **Command template** (`commands/pipeline.md`, `commands/react.md`): frontmatter
is `name: linkedin:<verb>`, `description: |` block ending in a `Triggers on:`
line, `allowed-tools:` YAML bullet list (only tools actually used). Body: H1 +
"You are a…" persona line + `## Step 0: Load Context` onward + closing
`## Reference Files` bullet list using `${CLAUDE_PLUGIN_ROOT}/...` paths.
- **Agent frontmatter** (`agents/differentiation-checker.md`,
`agents/content-repurposer.md`): `name:` bare kebab-case, `description: |`
block ending in `Triggers on:`, `model:` bare keyword, `color:` bare word,
`tools: ["Read", ...]` JSON inline array. **Existing agents use `model: sonnet`,
but the fasit (§6.2/§6.3) + KTG global rule mandate `model: opus` for the two
new agents.**
- **Reuse targets (verified):** `hooks/scripts/state-updater.mjs` (export
functions + CLI guard `if (import.meta.url === \`file://${process.argv[1]}\`)`
at line 227 — the canonical export/guard pattern), `queue-manager.mjs`
(`PLUGIN_ROOT` env + `__dirname`), `personalization-score.mjs`
(`calculateScore(pluginRoot)`), `clipboard-helper.mjs`, `compile-hooks.py`,
`skills/linkedin-content-creation/SKILL.md`, `references/newsletter-strategy-guide.md`.
- **${CLAUDE_PLUGIN_ROOT}** is injected by Claude Code into command-prompt Bash
(proven at runtime: `pipeline.md:108,114,137,188`, `calendar.md:25`). Scripts
do NOT read it internally — they self-resolve via `__dirname`/env. (Correction
to fasit wording.)
- **Render scripts** (`/Users/ktg/repos/maskinrommet/tools/`, read-only here):
all 4 are zero-npm-dep (Node builtins only). build-html.mjs (963 lines),
build-linkedin.mjs (364 lines, hardcoded Seres calendar/captions/freshness at
lines 3450: `CALENDAR`:34, `FRESHNESS`:44, `COVER_CREDIT`:49, `CAPTIONS`:50),
build-pdf.mjs (345), build-carousel.mjs (268).
## Research Sources
No external web research was needed — the brief declares research complete
(brief §4) and the four exploration agents (task-finder, convention-scanner,
risk-assessor, test-strategist) confirmed the spec against actual files. Their
material corrections are folded into the steps below and the Assumptions table.
## Corrections folded in from confirmatory exploration
These are verified deviations from the fasit. Each is a correction-in-scope
(implements what the fasit already mandates, against the real files):
1. **Fonts:** only `build-pdf.mjs` + `build-carousel.mjs` consume fonts (via
`file://` URLs from `__dirname/fonts`, not base64). `build-html.mjs` +
`build-linkedin.mjs` use system-font stacks and need no fonts. Fonts dir =
1.5 MB, 8 .ttf (Inter 400/600/700 + Newsreader 400/400i/600/600i/700).
2. **No OFL license file exists** anywhere in maskinrommet. `render/OFL.txt`
must be **authored/sourced** (Inter + Newsreader are OFL-1.1), not copied.
3. **weasyprint graceful degradation is NOT implemented**`build-pdf.mjs:339`
and `build-carousel.mjs:262` hard-fail (`execFileSync` + `process.exit(1)`)
on missing weasyprint. §7.4 degradation must be **written** during migration
(Step 1), not assumed present. weasyprint IS installed on this machine (67.0)
so the degradation path needs a forced-PATH-miss test.
4. **Render scripts are not importable** — no `export`, no CLI guard, `main()`
called unconditionally. The first production change for the generalized
scripts (Steps 2, 3) is to add `export` + the CLI guard (copy
`state-updater.mjs:227`). The failing import-test drives this naturally.
5. **Agent "known-answer fixtures" cannot be `node:test`** (agents are .md
prompts, no importable function, no deterministic LLM output). Split: (a) an
automated `node:test` lints the fixture file's structure; (b) the actual
accuracy comparison is an `[OPERATØR]`/`[GATE]` manual check — consistent
with fasit §10.0 "subjective quality is never self-certified."
6. **Dead-link blast radius is larger than the fasit's "Lav" labels** (N1, High):
`publish` alone has 21 route-refs incl. 9 inside hook scripts
(`session-start.mjs`, `posting-reminder.mjs`, `user-prompt-context.mjs`) that
emit runtime guidance and break silently. The §10.2-F grep target set must add
`agents/README.md` + `CLAUDE.md`. Treat every consolidation merge as Medium.
7. **Skill catalogs duplicated across 6 skill dirs** (N2): the langform trigger
+ catalog updates (Steps 11, 21) must sweep all 6 `skills/*/SKILL.md`, not
just `linkedin-content-creation`.
8. **`engagement-coach.md:24`** has a live "defer to the comment-strategist
agent" cross-ref. Since comment-strategist merges INTO engagement-coach
(Step 20), that line must be rewritten, not just deleted.
9. **No existing parallel Task fan-out** to copy — the Step 8 fan-out
(fasit assumption 4) is the single highest-uncertainty checkpoint; it is a
pure runtime assumption with no static precedent.
## Implementation Plan
> **Mapping:** one Voyage step = one fasit session. Each fasit session is
> already sized to ≤35 % context as a single testable deliverable, so 1 step =
> 1 `/trekcontinue` session is the correct granularity (not the usual 35
> finer steps). The fasit session id (S1, S1a, …) is in each step title.
> Internal TDD sub-steps live in **Changes** / **Test first**; the Manifest
> encodes the session's binary Definition-of-Done (fasit §10.3).
### Step 1: S1 — Migrate render scripts + fonts into the plugin
- **Files:** `render/build-html.mjs`, `render/build-linkedin.mjs`, `render/build-pdf.mjs`, `render/build-carousel.mjs`, `render/fonts/` (8 .ttf), `render/OFL.txt` (all new)
- **Changes:** Create `render/`. Copy the 4 scripts + `fonts/` (8 .ttf, 1.5 MB) FROM `/Users/ktg/repos/maskinrommet/tools/` (read-only source; do NOT modify maskinrommet). **Author `render/OFL.txt`** — the SIL Open Font License 1.1 text covering Inter + Newsreader (no license file exists at source; correction #2). **Add weasyprint graceful degradation** to `render/build-pdf.mjs` + `render/build-carousel.mjs` (correction #3): before `execFileSync("weasyprint", …)`, detect weasyprint on PATH; if absent, print a clear install instruction and skip the PDF step with a non-fatal warning instead of `process.exit(1)`. (codebase analysis)
- **Reuses:** font-resolution pattern already in the scripts (`const FONT_DIR = path.join(__dirname, "fonts")`, build-pdf.mjs:154).
- **Test first:**
- File: `render/__tests__/weasyprint-degradation.test.mjs` (new)
- Verifies: when `weasyprint` is not resolvable, the degradation helper returns a skip-signal (not a throw) and emits an install hint
- Pattern: `hooks/scripts/__tests__/state-updater.test.mjs` (node:test + assert/strict, pure-function call)
- **Verify:** `ls render/ && ls render/fonts/*.ttf | wc -l && node --test 'render/__tests__/*.test.mjs'` → expected: 4 `.mjs` + `fonts/` + `OFL.txt`; **8** .ttf (Inter-400/600/700, Newsreader-400/400i/600/600i/700); tests pass
- **On failure:** revert — `git checkout -- render/ && rm -rf render/`
- **Checkpoint:** `git commit -m "feat(linkedin): migrate render scripts + fonts into plugin (S1)"`
- **Manifest:**
```yaml
manifest:
expected_paths:
- render/build-html.mjs
- render/build-linkedin.mjs
- render/build-pdf.mjs
- render/build-carousel.mjs
- render/OFL.txt
- render/__tests__/weasyprint-degradation.test.mjs
- render/fonts/Inter-400.ttf
- render/fonts/Newsreader-400.ttf
min_file_count: 8
commit_message_pattern: "^feat\\(linkedin\\): migrate render scripts"
bash_syntax_check: []
forbidden_paths:
- /Users/ktg/repos/maskinrommet/tools/build-html.mjs
- /Users/ktg/repos/maskinrommet/tools/build-linkedin.mjs
must_contain:
- path: render/build-pdf.mjs
pattern: "weasyprint"
- path: render/build-carousel.mjs
pattern: "weasyprint"
- path: render/OFL.txt
pattern: "SIL Open Font License"
```
### Step 2: S1a — Generalize the annotation renderer (build-html.mjs)
- **Files:** `render/build-html.mjs`, `render/__tests__/build-html.test.mjs` (new)
- **Changes:** First make the script importable (correction #4): add `export` to `markdownToHtml`, `inline`, and the table/heading helpers, and wrap the CLI body in the guard `if (import.meta.url === \`file://${process.argv[1]}\`)` (copy `state-updater.mjs:227`). Then generalize the markdown→HTML engine (beslutning H): support tables (`| a | b |` → `<table>`), all heading levels `#``####` (today only `##`/`###`), inline `` `code` `` → `<code>` (today `inline()` only does `**bold**`/`*italic*`), and generic frontmatter/title. The annotation engine (CSS + client-JS: mark → Endre/Legg til/Fjern/Avklar/Risiko → sidebar → localStorage → export) is artifact-agnostic and embedded verbatim — no runtime dependency on maskinrommet or a temp file. (codebase analysis)
- **Reuses:** `state-updater.mjs:227` CLI-guard pattern; the existing annotation engine inside the source `build-html.mjs`.
- **Test first:**
- File: `render/__tests__/build-html.test.mjs` (new)
- Verifies: `markdownToHtml` converts a `| a | b |` block to `<table>`/`<tr>`/`<td>`; `#``<h1>``####``<h4>`; backtick span → `<code>`; empty input → no table; malformed row tolerated
- Pattern: `hooks/scripts/__tests__/state-updater.test.mjs`
- **Verify:** `node --test 'render/__tests__/*.test.mjs'` → expected: pass; then `cd /tmp && node <plugin>/render/build-html.mjs <a-table-heavy-md>` renders all tables (manual visual = `[OPERATØR]`)
- **On failure:** revert — `git checkout -- render/build-html.mjs render/__tests__/build-html.test.mjs`
- **Checkpoint:** `git commit -m "feat(linkedin): generalize build-html annotation renderer — tables, headings, inline code (S1a)"`
- **Manifest:**
```yaml
manifest:
expected_paths:
- render/build-html.mjs
- render/__tests__/build-html.test.mjs
min_file_count: 2
commit_message_pattern: "^feat\\(linkedin\\): generalize build-html"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: render/build-html.mjs
pattern: "export"
- path: render/build-html.mjs
pattern: "import.meta.url"
- path: render/build-html.mjs
pattern: "<table>"
- path: render/build-html.mjs
pattern: "<h4"
- path: render/build-html.mjs
pattern: "<code>"
- path: render/__tests__/build-html.test.mjs
pattern: "<table>"
```
> **Manifest note (blocker fix):** the three `<table>`/`<h4`/`<code>`
> patterns on the production `build-html.mjs` are the real generalization
> predicate — a no-op that adds only `export` + the CLI guard but leaves the
> markdown engine untouched will NOT emit `<table>`/`<h4`/`<code>` and fails
> the Manifest. The `node --test` in Verify is the second gate.
### Step 3: S2 — Generalize build-linkedin.mjs to read edition-config.json
- **Files:** `render/build-linkedin.mjs`, `render/__tests__/build-linkedin.test.mjs` (new), `render/__tests__/fixtures/edition-config.json` (new)
- **Changes:** First add `export` + CLI guard (correction #4). Replace the hardcoded `CALENDAR`/`FRESHNESS`/`CAPTIONS`/`COVER_CREDIT` constants (build-linkedin.mjs:3450) with a read of `linkedin/edition-config.json` from `process.cwd()` (the serie-mappe). Resolve Q1 in favour of JSON (deterministic parsing). Provide a sensible default/empty-config path so a missing config degrades gracefully. (codebase analysis)
- **Reuses:** `state-updater.mjs:227` guard; analytics fixture-dir convention (`scripts/analytics/tests/fixtures/`).
- **Test first:**
- File: `render/__tests__/build-linkedin.test.mjs` (new) + `fixtures/edition-config.json`
- Verifies: changing values in the fixture config changes POST.html output (no code change) — fasit assumption 5; regression: a config matching the old hardcoded Seres values yields the previous output
- Pattern: `scripts/analytics/tests/csv-parser.test.ts` (file-fixture loading)
- **Verify:** `node --test 'render/__tests__/*.test.mjs'` → expected: pass (diff of two configs differs; regression config matches baseline)
- **On failure:** revert — `git checkout -- render/build-linkedin.mjs render/__tests__/build-linkedin.test.mjs render/__tests__/fixtures/`
- **Checkpoint:** `git commit -m "feat(linkedin): generalize build-linkedin to read edition-config.json (S2)"`
- **Manifest:**
```yaml
manifest:
expected_paths:
- render/build-linkedin.mjs
- render/__tests__/build-linkedin.test.mjs
- render/__tests__/fixtures/edition-config.json
min_file_count: 3
commit_message_pattern: "^feat\\(linkedin\\): generalize build-linkedin"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: render/build-linkedin.mjs
pattern: "edition-config"
- path: render/build-linkedin.mjs
pattern: "import.meta.url"
```
### Step 4: S3 — Persona library (config/personas.template.md)
- **Files:** `config/personas.template.md` (new)
- **Changes:** Create the reusable reader-persona library (beslutning D, fasit §6.1) with the 3 Seres seed personas: IT division director, AI-section lead, and line manager (primary). Per persona document: role, what disconnects them, what convinces them, expertise level, jargon tolerance. Mark the primary explicitly. Active overrides live in a gitignored `config/personas.local.md` (per `*.local.md`). (codebase analysis)
- **Reuses:** template style of `config/state-file.template.md`, `config/user-profile.template.md`.
- **Test first:** *(config file — structural check, not node:test)* DoD archetype C: file exists, required fields present (grep), parses.
- **Verify:** `test -f config/personas.template.md && grep -c -E 'rolle|avkobler|overbeviser|ekspertise|sjargong' config/personas.template.md && grep -ci 'primær' config/personas.template.md` → expected: file present; field markers present for 3 personas; primary marked
- **On failure:** revert — `git checkout -- config/personas.template.md`
- **Checkpoint:** `git commit -m "feat(linkedin): add reusable persona library template (S3)"`
- **Manifest:**
```yaml
manifest:
expected_paths:
- config/personas.template.md
min_file_count: 1
commit_message_pattern: "^feat\\(linkedin\\): add reusable persona library"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: config/personas.template.md
pattern: "[Pp]rimær"
```
### Step 5: S4 — fact-checker agent (agents/fact-checker.md)
- **Files:** `agents/fact-checker.md` (new), `agents/fixtures/fact-checker-cases.md` (new), `agents/__tests__/fact-checker-fixture.test.mjs` (new)
- **Changes:** New Opus agent, `tools: ["Read", "WebSearch"]` (fasit §6.2). Mandate: given a block of factual claims, verify each against a primary/credible source under "guilty until proven" (aldri fyll hull med gjetninger; flag unverifiable explicitly); return a verification log + risk-sort 🔴/🟡/🟢. Copy the gate structure from `agents/differentiation-checker.md` (Mission → numbered search process with literal example queries → scored dimensions + verdict-threshold table → PASS/REWORK/BLOCK gate → fenced Output Format → Key Principles + Anti-Patterns) but change the mandate from originality to factual correctness. **Do not extend differentiation-checker** — orthogonal questions. Per correction #5: write a fasit fixture (3 claims: one true→🟢, one false→🔴, one unverifiable→🟡) + a node:test that lints the fixture's structure; the accuracy comparison is `[OPERATØR]`/`[GATE]`. (codebase analysis)
- **Reuses:** `agents/differentiation-checker.md` gate-prompt form; `state-updater.test.mjs` test shape.
- **Test first:**
- File: `agents/__tests__/fact-checker-fixture.test.mjs` (new)
- Verifies: the fixture file has exactly 3 cases, each with exactly one of 🟢/🔴/🟡 and a non-empty fasit field
- Pattern: `hooks/scripts/__tests__/state-updater.test.mjs`
- **Verify:** `node --test 'agents/__tests__/*.test.mjs' && grep -E '^model: opus' agents/fact-checker.md` → expected: fixture-lint passes; frontmatter has `model: opus`. Accuracy run against fixture = `[GATE: fact-checker output matches fasit form + verdicts]`
- **On failure:** revert — `git checkout -- agents/fact-checker.md agents/fixtures/ agents/__tests__/fact-checker-fixture.test.mjs`
- **Checkpoint:** `git commit -m "feat(linkedin): add fact-checker agent + fixture (S4)"`
- **Manifest:**
```yaml
manifest:
expected_paths:
- agents/fact-checker.md
- agents/fixtures/fact-checker-cases.md
- agents/__tests__/fact-checker-fixture.test.mjs
min_file_count: 3
commit_message_pattern: "^feat\\(linkedin\\): add fact-checker agent"
bash_syntax_check: []
forbidden_paths:
- agents/differentiation-checker.md
must_contain:
- path: agents/fact-checker.md
pattern: "model: opus"
- path: agents/fact-checker.md
pattern: "WebSearch"
```
### Step 6: S5 — persona-reviewer agent (agents/persona-reviewer.md, 2 modes)
- **Files:** `agents/persona-reviewer.md` (new), `agents/fixtures/persona-reviewer-cases.md` (new), `agents/__tests__/persona-reviewer-fixture.test.mjs` (new)
- **Changes:** New Opus agent, `tools: ["Read"]` (fasit §6.3). Mandate: read one persona definition (from Step 4's library) + the text → judge on 6 axes (hook holds? resonance? tone? credibility? leader-takeaway + concrete action? length/drive?). Return top-5 flags as **direction, not rewritten copy** (the jury NEVER writes text). Two modes in the same file (parameter in the call): resonance-mode (Step 8 of newsletter, BEFORE lock) and conversion-mode (Step 11, after lock — binary YES/NO "would YOU click?" on the hook only). Convergence-loop: re-run per persona judging LØST/DELVIS/IKKE until clean YES from primary. Per correction #5: fixture + structural lint test; accuracy = manual gate. (codebase analysis)
- **Reuses:** `agents/differentiation-checker.md` form; `state-updater.test.mjs` test shape.
- **Test first:**
- File: `agents/__tests__/persona-reviewer-fixture.test.mjs` (new)
- Verifies: fixture has a persona def + sample text + the 6 axis labels; both modes documented
- Pattern: `hooks/scripts/__tests__/state-updater.test.mjs`
- **Verify:** `node --test 'agents/__tests__/*.test.mjs' && grep -E '^model: opus' agents/persona-reviewer.md && grep -Eci 'resonans|konverter' agents/persona-reviewer.md` → expected: lint passes; opus; both modes present (BSD-grep-safe `-E`). Output-shape (≤5 flags, 6 axes, NO rewritten copy) = `[GATE]`
- **On failure:** revert — `git checkout -- agents/persona-reviewer.md agents/fixtures/persona-reviewer-cases.md agents/__tests__/persona-reviewer-fixture.test.mjs`
- **Checkpoint:** `git commit -m "feat(linkedin): add persona-reviewer agent (2 modes) + fixture (S5)"`
- **Manifest:**
```yaml
manifest:
expected_paths:
- agents/persona-reviewer.md
- agents/fixtures/persona-reviewer-cases.md
- agents/__tests__/persona-reviewer-fixture.test.mjs
min_file_count: 3
commit_message_pattern: "^feat\\(linkedin\\): add persona-reviewer agent"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: agents/persona-reviewer.md
pattern: "model: opus"
```
### Step 7: S6 — Edition-state schema + retire content-tracker & personalization-scorer
- **Files:** `config/edition-state.template.json` (new), remove `agents/content-tracker.md`, remove `agents/personalization-scorer.md`, `agents/README.md` (update), `CLAUDE.md` (update agent table)
- **Changes:** Define the edition-state schema (fasit §5.2) — current phase + per-article status — as a documented JSON template (beslutment G: production state lives in the serie-mappe, this is the schema the plugin defines). Retire the 2 deterministic agents (fasit §4.2): their function is already covered by `hooks/scripts/personalization-score.mjs` (placeholder detection) and `state-updater.mjs`/`calendar` (plan-vs-queue diff) — verify the script path covers it, then delete the agent files. Update `agents/README.md` (flow diagrams + Haiku table reference them — N1) and `CLAUDE.md` agent count. (codebase analysis)
- **Reuses:** `hooks/scripts/personalization-score.mjs` `calculateScore()`; `state-updater.mjs`.
- **Test first:** *(schema file — archetype C+F)* parse the JSON template; enumerate the retired agents' capabilities and confirm each is covered by an existing script (run `node -e` on personalization-score.mjs).
- **Verify:** `node -e "JSON.parse(require('fs').readFileSync('config/edition-state.template.json','utf8'))" && ! test -f agents/content-tracker.md && ! test -f agents/personalization-scorer.md && ! grep -rn 'content-tracker\|personalization-scorer' agents/ README.md CLAUDE.md skills/ && ls agents/*.md | grep -v README | wc -l` → expected: JSON parses; both agents gone; **zero stray refs**; agent count = 16 (14 + 2 new fact-checker/persona-reviewer)
- **On failure:** revert — `git checkout -- agents/ config/edition-state.template.json CLAUDE.md`
- **Checkpoint:** `git commit -m "refactor(linkedin): edition-state schema + retire 2 deterministic agents to scripts (S6)"`
- **Manifest:**
```yaml
manifest:
expected_paths:
- config/edition-state.template.json
min_file_count: 1
commit_message_pattern: "^refactor\\(linkedin\\): edition-state schema"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: config/edition-state.template.json
pattern: "phase"
```
> **Manifest-schema limit (acknowledged):** `must_contain` is positive-match
> only, so the two agent *deletions* + capability-parity claim (Assumption 3)
> cannot be encoded as a Manifest predicate. For this and every consolidation
> step (1621), the **`! test -f` + dead-link grep in Verify is the binding
> completion predicate** — trekexecute must treat Verify as a gate here, not
> just the Manifest. Archetype F (fasit §10.2) governs.
### Step 8: S7 — newsletter.md skeleton, Step 02 (load, calibrate, research fan-out)
- **Files:** `commands/newsletter.md` (new)
- **Changes:** Create the orchestrator command following the LTL command template (`name: linkedin:newsletter`, `description: |` + Triggers, `allowed-tools:` incl. `Task`, `AskUserQuestion`, `Read`, `Bash`). Implement Step 0 (load edition-state/HANDOVER, voice-profile, persona library, serie-brief), Step 1 (brief + calibration, ≤3 questions, mark primary persona), Step 2 (parallel research **Task fan-out in foreground** — correction #9: highest-uncertainty checkpoint; no existing pattern to copy). Principle 4: fan-out from the command layer, never from a nested background agent. (codebase analysis)
- **Reuses:** `commands/pipeline.md` step structure + `${CLAUDE_PLUGIN_ROOT}` Bash form; `commands/react.md` multi-source synthesis discipline.
- **Test first:** *(command prose — archetype E)* run Step 02 against a dummy serie fixture; fasit assumption 4: confirm 2+ parallel research Task calls return structured (not degraded) results.
- **Verify:** `grep -E '^name: linkedin:newsletter' commands/newsletter.md && grep -c 'Step [012]' commands/newsletter.md` → expected: frontmatter correct; Steps 02 present. Parallel-fan-out behavior = `[GATE: assumption-4 runtime test — count parallel calls + structured replies]`
- **On failure:** escalate — if parallel Task fan-out degrades, stop and report (this is the load-bearing assumption); do not paper over with sequential calls without operator sign-off
- **Checkpoint:** `git commit -m "feat(linkedin): newsletter command skeleton Step 0-2 (S7)"`
- **Manifest:**
```yaml
manifest:
expected_paths:
- commands/newsletter.md
min_file_count: 1
commit_message_pattern: "^feat\\(linkedin\\): newsletter command skeleton"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: commands/newsletter.md
pattern: "name: linkedin:newsletter"
- path: commands/newsletter.md
pattern: "Step 0"
- path: commands/newsletter.md
pattern: "Step 2"
- path: commands/newsletter.md
pattern: "[Pp]arallel"
```
> **Manifest note:** the `Step 0`/`Step 2`/`parallel` patterns force all three
> phases + the fan-out wiring to be present (a single-string no-op fails). The
> *runtime* behavior — that foreground `Task` fan-out keeps the Task tool and
> returns non-degraded results (Assumption 1, the highest-uncertainty
> checkpoint) — is the `[GATE]` in Verify and the `escalate` On-failure; it
> cannot be encoded statically.
### Step 9: S8 — newsletter.md Step 34 (draft + consistency/quality)
- **Files:** `commands/newsletter.md` (edit)
- **Changes:** Add Step 3 (draft in dramaturgical order, voice-matched, may span sessions with maintained HANDOVER — use `content-repurposer` extended + Task) and Step 4 (consistency + quality: threads, premise→conclusion arc, leader-takeaway, AI-slop removal, minimal formatting-dose). **Forward-reference fix (major):** `references/longform-quality-rules.md` is not authored until Step 13. So Step 4 **inlines the fasit §8 rules directly in `newsletter.md` here**; Step 13 then EXTRACTS them to `references/longform-quality-rules.md` and replaces the inline block with a pointer. No dangling reference at any point. (codebase analysis)
- **Reuses:** `agents/content-repurposer.md`; voice-samples (always read before content — existing LTL rule); fasit §8 rule text (inlined now, extracted in Step 13).
- **Test first:** *(archetype E)* Step 34 produce a draft file on the dummy serie; voice-match is `[OPERATØR]`/`[GATE: voice-trainer]` — NOT self-certified (fasit §10.0).
- **Verify:** `grep -c 'Step 3' commands/newsletter.md && grep -c 'Step 4' commands/newsletter.md && grep -ci 'AI-slop\|premiss' commands/newsletter.md` → expected: Steps 34 present; §8 rules inlined. Draft quality = `[OPERATØR]`/`[GATE]`
- **On failure:** revert — `git checkout -- commands/newsletter.md`
- **Checkpoint:** `git commit -m "feat(linkedin): newsletter Step 3-4 draft + consistency (S8)"`
- **Manifest:**
```yaml
manifest:
expected_paths:
- commands/newsletter.md
min_file_count: 1
commit_message_pattern: "^feat\\(linkedin\\): newsletter Step 3-4"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: commands/newsletter.md
pattern: "content-repurposer"
- path: commands/newsletter.md
pattern: "Step 3"
- path: commands/newsletter.md
pattern: "Step 4"
- path: commands/newsletter.md
pattern: "AI-slop"
```
### Step 10: S9 — newsletter.md Step 56 (fact-check sweep + persona sweep BEFORE lock)
- **Files:** `commands/newsletter.md` (edit)
- **Changes:** Add Step 5 (fact-check sweep: risk-sorted 🔴/🟡/🟢, "guilty until proven", verification log — fan-out N parallel `fact-checker` calls in foreground) and Step 6 (persona sweep BEFORE lock: reader-jury via `persona-reviewer` resonance-mode, primary trumps, convergence-loop to clean YES). This is the fix for the single biggest Seres process error (fasit §0.4 / principle 5). Order assertion: sweep precedes lock. (codebase analysis)
- **Reuses:** `agents/fact-checker.md` (Step 5), `agents/persona-reviewer.md` (Step 6).
- **Test first:** *(archetype E)* both agents invoked in parallel; order-assert: persona-sweep step appears before the lock step; `[GATE]` clean-YES-from-primary required to proceed.
- **Verify:** `grep -n 'Step 5\|Step 6\|fact-checker\|persona-reviewer\|FØR lås\|before lock' commands/newsletter.md` → expected: Steps 56 present, both agents referenced, sweep ordered before lock. Verdicts = `[GATE]`
- **On failure:** revert — `git checkout -- commands/newsletter.md`
- **Checkpoint:** `git commit -m "feat(linkedin): newsletter Step 5-6 fact-check + persona sweep before lock (S9)"`
- **Manifest:**
```yaml
manifest:
expected_paths:
- commands/newsletter.md
min_file_count: 1
commit_message_pattern: "^feat\\(linkedin\\): newsletter Step 5-6"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: commands/newsletter.md
pattern: "fact-checker"
- path: commands/newsletter.md
pattern: "persona-reviewer"
- path: commands/newsletter.md
pattern: "Step 5"
- path: commands/newsletter.md
pattern: "Step 6"
```
> **Order assertion** (persona-sweep Step 6 BEFORE lock — the single biggest
> Seres process error, fasit §0.4): `must_contain` proves both phases exist;
> the *ordering* (Step 6 precedes the Step 8 lock) is asserted by the grep in
> Verify + is a `[GATE]`. A wiring that placed the sweep after lock would pass
> `must_contain` but fail the Verify order-assert.
### Step 11: S10 — newsletter.md Step 710 (annotate, lock/delivery, hook-gate, schedule)
- **Files:** `commands/newsletter.md` (edit)
- **Changes:** Add Step 7 (optional annotation: `render/build-html.mjs` → review HTML in `docs/review/`), Step 8 (LOCK → delivery: POST.html via `render/build-linkedin.mjs`, cwd = serie-mappe), Step 9 (hook/conversion gate: `persona-reviewer` conversion-mode on the distribution text, AFTER lock — order assertion), Step 10 (register edition in the queue via `queue-manager.mjs` for native scheduling). Correction (N3): when shelling out to render scripts, check exit codes — don't assume success. (codebase analysis)
- **Reuses:** `render/build-html.mjs`, `render/build-linkedin.mjs`, `hooks/scripts/queue-manager.mjs`.
- **Test first:** *(archetype E)* Step 8 produces POST.html on the dummy serie; order-assert: hook-gate (Step 9) runs AFTER lock (Step 8); edition registered in queue.
- **Verify:** `grep -n 'Step 7\|Step 8\|Step 9\|Step 10\|POST.html\|build-linkedin\|queue-manager' commands/newsletter.md` → expected: Steps 710 present; render + queue wired; hook-gate after lock
- **On failure:** revert — `git checkout -- commands/newsletter.md`
- **Checkpoint:** `git commit -m "feat(linkedin): newsletter Step 7-10 lock, delivery, hook-gate, schedule (S10)"`
- **Manifest:**
```yaml
manifest:
expected_paths:
- commands/newsletter.md
min_file_count: 1
commit_message_pattern: "^feat\\(linkedin\\): newsletter Step 7-10"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: commands/newsletter.md
pattern: "build-linkedin"
- path: commands/newsletter.md
pattern: "queue-manager"
- path: commands/newsletter.md
pattern: "Step 7"
- path: commands/newsletter.md
pattern: "Step 9"
- path: commands/newsletter.md
pattern: "Step 10"
```
> **Order assertion** (hook-gate Step 9 AFTER lock Step 8): `must_contain`
> proves all four phases exist; ordering is the Verify grep + `[GATE]`.
### Step 12: S11 — Reconcile newsletter path out of multiplatform + skill trigger + router row
- **Files:** `commands/multiplatform.md` (edit), `commands/linkedin.md` (edit), `skills/linkedin-content-creation/SKILL.md` (edit) + the other 5 `skills/*/SKILL.md` catalogs (edit — correction #7)
- **Changes:** Remove the newsletter/blog adaptation path from `multiplatform.md` so there is exactly ONE entry to long-form (fasit §4.1). Add the langform trigger to `skills/linkedin-content-creation/SKILL.md` (fasit §5.3) AND sweep the catalog tables in all 6 `skills/*/SKILL.md` (N2). Add a router row for `newsletter` in `commands/linkedin.md`. (codebase analysis)
- **Reuses:** existing router-row format in `linkedin.md`; skill trigger format.
- **Test first:** *(archetype F)* grep proves only one newsletter entry; router row present; no dead newsletter path in multiplatform.
- **Verify:** `grep -Eci 'Step.*newsletter|newsletter (pipeline|workflow|edition)' commands/multiplatform.md && grep -c 'newsletter' commands/linkedin.md` → expected: **0** multi-step newsletter section in multiplatform (a one-line pointer is allowed, any wording); router row present in linkedin.md. (Robust: does not depend on exact pointer phrasing — only that no *pipeline/section* survives.)
- **On failure:** revert — `git checkout -- commands/multiplatform.md commands/linkedin.md skills/`
- **Checkpoint:** `git commit -m "refactor(linkedin): single newsletter entry + skill trigger + router row (S11)"`
- **Manifest:**
```yaml
manifest:
expected_paths:
- commands/linkedin.md
- commands/multiplatform.md
min_file_count: 2
commit_message_pattern: "^refactor\\(linkedin\\): single newsletter entry"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: commands/linkedin.md
pattern: "newsletter"
```
### Step 13: S12 — longform-quality-rules.md + resumption wiring
- **Files:** `references/longform-quality-rules.md` (new), `commands/newsletter.md` (edit — Step 0 reads edition-state for resumption)
- **Changes:** Codify the fasit §8 quality rules (leader-takeaway, premise→conclusion arc, forbidden AI-slop phrases, generic-not-agency-specific, minimal formatting-dose, gap-closing by tightening not expansion, per-sweep calibration). Wire resumption: Step 0 reads edition-state and continues from the right step (abort → re-run → resumes). (codebase analysis)
- **Reuses:** edition-state schema (Step 7); fasit §8 content.
- **Test first:** *(archetype C+E)* file exists with all §8 rules (grep); resumption: abort after Step 6 → re-run → resumes from Step 7 (deterministic via edition-state).
- **Verify:** `test -f references/longform-quality-rules.md && grep -ci 'leder-takeaway\|premiss\|AI-slop\|formaterings-dose' references/longform-quality-rules.md` → expected: file present; rules present. Resumption = deterministic test
- **On failure:** revert — `git checkout -- references/longform-quality-rules.md commands/newsletter.md`
- **Checkpoint:** `git commit -m "feat(linkedin): longform quality rules + edition resumption wiring (S12)"`
- **Manifest:**
```yaml
manifest:
expected_paths:
- references/longform-quality-rules.md
- commands/newsletter.md
min_file_count: 2
commit_message_pattern: "^feat\\(linkedin\\): longform quality rules"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: references/longform-quality-rules.md
pattern: "AI-slop"
```
### Step 14: S13 — Dogfood: produce a real edition end-to-end
> **`[OPERATØR]`-gated session — not pure headless.** This step runs the live
> pipeline with a human in the loop (browser walkthrough, real edition).
> A headless `claude -p` cannot self-certify a real edition's quality.
- **Files:** `docs/voyage-build/dogfood-S13-friction.md` (new — the in-plugin deliverable). Edition content is produced in a maskinrommet serie-mappe (operator-gated, stays in that repo).
- **Changes:** Run `/linkedin:newsletter` end-to-end to produce one real edition (files in the serie-mappe). **Cross-repo write to maskinrommet requires explicit operator instruction** (R1) — confirm before writing there; otherwise dogfood against a throwaway serie fixture inside `docs/review/` scope. Open the review HTML in a browser and walk the core flows (dogfood-UI gate). **Write a structured friction log to `docs/voyage-build/dogfood-S13-friction.md`** recording: each friction point (numbered), an order-proof note (edition-HANDOVER shows persona-sweep BEFORE lock), and which pipeline file each friction implicates (drives Step 15's revert targets). (codebase analysis)
- **Reuses:** the full Step 813 pipeline.
- **Test first:** *(archetype G — operator/manual)* an edition produced end-to-end; order-proof: edition-HANDOVER shows persona-sweep BEFORE lock; review HTML opened.
- **Verify:** `test -f docs/voyage-build/dogfood-S13-friction.md && grep -ci 'sweep.*lås\|before lock\|FØR lås' docs/voyage-build/dogfood-S13-friction.md` → expected: friction log exists; order-proof recorded. Edition quality + UI = `[OPERATØR]`
- **On failure:** escalate — dogfood reveals design friction; capture it in the log, do not force a green check
- **Checkpoint:** `git commit -m "test(linkedin): dogfood newsletter pipeline end-to-end (S13)"` *(edition content stays in maskinrommet; only the friction log is committed plugin-side)*
- **Manifest:**
```yaml
manifest:
expected_paths:
- docs/voyage-build/dogfood-S13-friction.md
min_file_count: 1
commit_message_pattern: "^test\\(linkedin\\): dogfood newsletter"
bash_syntax_check: []
forbidden_paths:
- /Users/ktg/repos/maskinrommet/tools/build-html.mjs
must_contain:
- path: docs/voyage-build/dogfood-S13-friction.md
pattern: "[Ff]riction|[Ff]riksjon"
```
> **Blocker fix:** the friction-log file is now a real, checkable deliverable
> (file must exist + record the order-proof) — the step can no longer pass by
> producing nothing. The edition's subjective quality stays `[OPERATØR]` per
> fasit §10.0.
### Step 15: S14 — Fix dogfood friction
> **`[OPERATØR]`-gated session.** Revert targets come from the Step 14 friction
> log's "implicates file X" notes — that log is the referent for every fix and
> every `git checkout`.
- **Files:** the pipeline files named in `docs/voyage-build/dogfood-S13-friction.md`; `docs/voyage-build/dogfood-S13-friction.md` (update with re-test outcomes)
- **Changes:** Close each friction point from Step 14 with a concrete fix; re-test each with a concrete check (not "fixed"). Update the friction log with per-item status (✅ re-tested / 🔶 deferred). Remaining items either closed or explicitly deferred with operator's knowledge. (codebase analysis)
- **Reuses:** the S13 friction log (names the files to touch + revert).
- **Test first:** *(archetype G)* each closed friction point re-tested with a concrete check; restliste empty or explicitly deferred.
- **Verify:** `grep -c '✅\|🔶' docs/voyage-build/dogfood-S13-friction.md` → expected: every friction item has a ✅ (re-tested) or 🔶 (deferred with operator note) — no silent closures
- **On failure:** revert the specific fix using the file path recorded against that friction item in the log (`git checkout -- <that file>`); if the log does not name the file, escalate
- **Checkpoint:** `git commit -m "fix(linkedin): close dogfood friction (S14)"`
- **Manifest:**
```yaml
manifest:
expected_paths:
- docs/voyage-build/dogfood-S13-friction.md
min_file_count: 1
commit_message_pattern: "^fix\\(linkedin\\): close dogfood friction"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: docs/voyage-build/dogfood-S13-friction.md
pattern: "✅|🔶"
```
### Step 16: S15 — templates.md → mode in quick.md
- **Files:** `commands/quick.md` (edit), remove `commands/templates.md`, `commands/linkedin.md` (router edit)
- **Changes:** Enumerate every one of the 8 template types in `templates.md` and confirm each is covered by a mode in `quick.md` (capability checklist — archetype F). Remove `templates.md`. Grep the expanded target set (`commands/ agents/ skills/ hooks/ README.md CLAUDE.md agents/README.md`) for `templates` route-refs and fix all (N1). (codebase analysis)
- **Reuses:** existing `quick.md` 3-line formula + templates bank.
- **Test first:** *(archetype F)* capability checklist: all 8 types in quick; `templates.md` gone; `ls commands/` down 1; no dead links.
- **Verify:** `! test -f commands/templates.md && grep -rn '/linkedin:templates\|commands/templates' commands/ agents/ skills/ hooks/ README.md CLAUDE.md agents/README.md` → expected: file gone; zero stray route-refs (only intentional)
- **On failure:** revert — `git checkout -- commands/quick.md commands/templates.md commands/linkedin.md`
- **Checkpoint:** `git commit -m "refactor(linkedin): merge templates into quick (S15)"`
- **Manifest:**
```yaml
manifest:
expected_paths:
- commands/quick.md
min_file_count: 1
commit_message_pattern: "^refactor\\(linkedin\\): merge templates into quick"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: commands/quick.md
pattern: "template"
```
### Step 17: S16 — publish.md → action in calendar.md
- **Files:** `commands/calendar.md` (edit), remove `commands/publish.md`, plus the 9 hook-script refs (N1: `session-start.mjs`, `posting-reminder.mjs`, `user-prompt-context.mjs`, `hooks/prompts/state-update-reminder.md`)
- **Changes:** Move the publish action into `calendar.md` (both read `queue.json`; calendar already routes to publish). Remove `publish.md`. **Critical (N1):** `publish` has 21 route-refs, 9 inside hook scripts that emit runtime guidance — update every one or the plugin ships text pointing at a dead command. Re-compile hooks if any `hooks/` source changed. (codebase analysis)
- **Reuses:** `queue-manager.mjs`; existing calendar→publish routing.
- **Test first:** *(archetype F)* capability checklist: calendar covers publish; `publish.md` gone; all 21 refs (incl. 9 hook refs) reconciled.
- **Verify:** `! test -f commands/publish.md && grep -rn '/linkedin:publish\|commands/publish' commands/ agents/ skills/ hooks/ README.md CLAUDE.md` → expected: file gone; zero stray refs; `python3 hooks/scripts/compile-hooks.py --check` clean if hooks touched
- **On failure:** revert — `git checkout -- commands/calendar.md commands/publish.md hooks/`
- **Checkpoint:** `git commit -m "refactor(linkedin): merge publish into calendar — reconcile hook refs (S16)"`
- **Manifest:**
```yaml
manifest:
expected_paths:
- commands/calendar.md
min_file_count: 1
commit_message_pattern: "^refactor\\(linkedin\\): merge publish into calendar"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: commands/calendar.md
pattern: "[Pp]ublish"
```
### Step 18: S17 — collab.md + speaking.md → new outreach.md
- **Files:** `commands/outreach.md` (new), remove `commands/collab.md`, remove `commands/speaking.md`, `commands/linkedin.md` (router edit)
- **Changes:** Create `outreach.md` covering both collab and speaking (structural twins: same outreach/pitch paradigm + pipeline table). Capability checklist: every function of both predecessors present in outreach. Remove both. Reconcile all route-refs (collab 8, speaking 8, incl. `README.md:511` ToS table). (codebase analysis)
- **Reuses:** the shared outreach/pitch structure from both files.
- **Test first:** *(archetype F)* checklist covers collab + speaking; both predecessors gone; net down 1; no dead links.
- **Verify:** `test -f commands/outreach.md && ! test -f commands/collab.md && ! test -f commands/speaking.md && grep -rn '/linkedin:collab\|/linkedin:speaking' commands/ agents/ skills/ hooks/ README.md CLAUDE.md` → expected: outreach present; both gone; zero stray refs
- **On failure:** revert — `git checkout -- commands/`
- **Checkpoint:** `git commit -m "refactor(linkedin): merge collab + speaking into outreach (S17)"`
- **Manifest:**
```yaml
manifest:
expected_paths:
- commands/outreach.md
min_file_count: 1
commit_message_pattern: "^refactor\\(linkedin\\): merge collab \\+ speaking into outreach"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: commands/outreach.md
pattern: "[Ss]peaking"
```
### Step 19: S18 — authority.md → strategy.md + trajectory dedup + profile canon
- **Files:** `commands/strategy.md` (edit), remove `commands/authority.md`, `commands/audit.md` (edit — point to profile/strategy), `commands/analyze.md` (edit — point to profile)
- **Changes:** Absorb `authority.md` into a section of `strategy.md` (authority has no unique core). De-duplicate trajectory logic to live only in `strategy.md`; `audit.md` references it. Make `profile.md` the canonical source for profile-alignment; `audit.md`/`analyze.md` point there. Remove `authority.md`. (codebase analysis)
- **Reuses:** existing strategy phase content; profile-alignment check in `profile.md`.
- **Test first:** *(archetype F)* strategy covers authority + trajectory; audit/analyze point to profile canon; `authority.md` gone; no dead links.
- **Verify:** `! test -f commands/authority.md && grep -rn '/linkedin:authority\|commands/authority' commands/ agents/ skills/ hooks/ README.md CLAUDE.md` → expected: gone; zero stray refs
- **On failure:** revert — `git checkout -- commands/`
- **Checkpoint:** `git commit -m "refactor(linkedin): absorb authority into strategy + profile canon (S18)"`
- **Manifest:**
```yaml
manifest:
expected_paths:
- commands/strategy.md
min_file_count: 1
commit_message_pattern: "^refactor\\(linkedin\\): absorb authority into strategy"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: commands/strategy.md
pattern: "[Aa]uthority"
```
### Step 20: S19 — Agent merges: analytics (2→1) + engagement (2→1)
- **Files:** `agents/analytics-interpreter.md` (edit → analytics, 2 modes), remove `agents/performance-reporter.md`, `agents/engagement-coach.md` (edit → engagement), remove `agents/comment-strategist.md`, `agents/README.md` (edit), `CLAUDE.md` (edit)
- **Changes:** Merge `performance-reporter` into `analytics-interpreter` (one analytics agent, interpret/report modes — identical data sources). Merge `comment-strategist` into `engagement-coach` (5x5x5 + first-hour + CEA). **Rewrite `engagement-coach.md:24`** ("defer to the comment-strategist agent" — correction #8) since the target now lives in-file. Decide Q2 (video-scripter → content-repurposer) here. Reconcile all refs incl. `agents/README.md` flow diagrams + `skills/linkedin-analytics/SKILL.md:40`. (codebase analysis)
- **Reuses:** existing agent bodies (merge, don't rewrite from scratch).
- **Test first:** *(archetype F)* each mode in the merged agent covers predecessors' functions (checklist); `ls agents/` down 2; no dead links; the self-ref at line 24 rewritten.
- **Verify:** `! test -f agents/performance-reporter.md && ! test -f agents/comment-strategist.md && ! grep -n 'comment-strategist agent' agents/engagement-coach.md && grep -rn 'performance-reporter\|comment-strategist' agents/ skills/ README.md CLAUDE.md agents/README.md` → expected: both gone; self-ref rewritten; zero stray refs
- **On failure:** revert — `git checkout -- agents/ CLAUDE.md`
- **Checkpoint:** `git commit -m "refactor(linkedin): merge analytics + engagement agents 2→1 each (S19)"`
- **Manifest:**
```yaml
manifest:
expected_paths:
- agents/analytics-interpreter.md
- agents/engagement-coach.md
min_file_count: 2
commit_message_pattern: "^refactor\\(linkedin\\): merge analytics \\+ engagement"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: agents/engagement-coach.md
pattern: "CEA"
```
### Step 21: S20 — import.md trim + router gating + final doc pass → v2.0.0
- **Files:** `commands/import.md` (edit), `commands/linkedin.md` (router gating edit), `README.md`, `CLAUDE.md`, `.claude-plugin/plugin.json`, `CHANGELOG.md`, marketplace `README.md` + `CLAUDE.md` (version refs)
- **Changes:** Delegate `import.md` Step 6 analysis to `report.md` (two report generators run the same `trends` CLI). Add router gating in `linkedin.md` (show monetize/outreach/collab as "unlocks at ~1K followers"). **Version bump to v2.0.0** — update all 7 in-tree refs (`plugin.json:3`, `CLAUDE.md:1`, `README.md:9/23/42/522`, `CHANGELOG.md:8`) + 2 marketplace refs (`README.md:209`, `CLAUDE.md:12`), and fix the `#whats-new-v120` anchor → `#whats-new-v200`. Final doc pass: all 3 doc levels (plugin README + plugin CLAUDE + root README) reflect v2.0.0 scope (fasit §10.5). (codebase analysis)
- **Reuses:** `report.md` trends CLI; existing version-sync discipline.
- **Test first:** *(archetype F)* command count verified down; all version refs = 2.0.0 (grep); all 3 doc levels updated; router gating present.
- **Changes (count, corrected):** net command count = **24** = 27 today 5 removed (`templates`, `publish`, `authority`, `collab`, `speaking`) + 2 added (`newsletter`, `outreach`). (The fasit's "~23" was approximate; the exact arithmetic is 24. This is the binding number.)
- **Verify:** `test "$(ls commands/*.md | wc -l | tr -d ' ')" = "24" && ! grep -rIn '1\\.2\\.0' .claude-plugin/plugin.json CLAUDE.md README.md CHANGELOG.md` → expected: command count is **exactly 24**; zero `1.2.0` refs remain (all bumped to 2.0.0)
- **On failure:** revert — `git checkout -- commands/ README.md CLAUDE.md .claude-plugin/ CHANGELOG.md`
- **Checkpoint:** `git commit -m "chore(linkedin): v2.0.0 — import trim, router gating, full doc pass (S20)"`
- **Manifest:**
```yaml
manifest:
expected_paths:
- .claude-plugin/plugin.json
- CHANGELOG.md
- README.md
- CLAUDE.md
min_file_count: 4
commit_message_pattern: "^chore\\(linkedin\\): v2\\.0\\.0"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: .claude-plugin/plugin.json
pattern: "2.0.0"
- path: CHANGELOG.md
pattern: "2.0.0"
- path: CLAUDE.md
pattern: "2.0.0"
- path: README.md
pattern: "2.0.0"
```
> **Blocker fix:** the count predicate is now a single exact value (24), tested
> with a string-equality assertion in Verify. The earlier self-contradictory
> "22 / 23 / verify net" is removed. Note: agent net count stays 16 (4 removed,
> 2 added — verified in the end-to-end Verification block).
## Alternatives Considered
| Approach | Pros | Cons | Why rejected |
|----------|------|------|--------------|
| Newsletter as a suite of 5 phase-commands | Each phase independently invocable | Surface explosion; against beslutning A; fragments resumption | Locked decision A: one orchestrator command |
| Extend `commands/pipeline.md` for long-form | Reuse existing pipeline | pipeline is locked to feed-post artifact contract (wrong output shape) | Locked decision A |
| Keep render scripts in maskinrommet, call cross-repo | No migration work | Forgejo downloaders get no render pipeline; cross-repo coupling | Locked decision C: ship render in plugin |
| Finer Voyage steps (35 per session) | Matches Voyage default granularity | Explodes to 60+ steps; fasit already validated session sizing at ≤35 % context | 1 step = 1 session is the right grain here |
| Integrate fact-check into research/review | Fewer agents | "Altinn-feilen" proved research notes miss facts; needs a dedicated sweep | Locked decision E: fact-check is its own step |
## Test Strategy
- **Framework:** `node:test` + `node:assert/strict`, zero deps. Run with the
**glob form** `node --test 'render/__tests__/*.test.mjs'` (Node 25 breaks
`node --test <dir>`).
- **Existing patterns:** `hooks/scripts/__tests__/*.test.mjs` (inline
template-literal fixtures, pure-function calls, `assert.match`/`assert.equal`);
`scripts/analytics/tests/*.test.ts` (file fixtures in `tests/fixtures/`).
- **Clean test-first (render scripts):** the first production change is to add
`export` + the CLI guard, so the failing import-test drives the refactor
(Steps 2, 3). Mirrors `state-updater.mjs:227`.
- **Awkward test-first (agents):** agents are .md prompts — not unit-testable.
Automate a **fixture-schema lint** as `node:test`; route the actual accuracy
comparison to an `[OPERATØR]`/`[GATE]` manual check (corrections #5, fasit §10.0).
### Tests to write
| Type | File | Verifies | Model test |
|------|------|----------|------------|
| Unit | `render/__tests__/weasyprint-degradation.test.mjs` | missing weasyprint → skip-signal, not throw | `state-updater.test.mjs` |
| Unit | `render/__tests__/build-html.test.mjs` | tables, `#``####`, inline code | `state-updater.test.mjs` |
| Unit | `render/__tests__/build-linkedin.test.mjs` | reads edition-config; config diff → output diff | `csv-parser.test.ts` (file fixture) |
| Lint | `agents/__tests__/fact-checker-fixture.test.mjs` | fixture has 3 cases, one each 🟢/🔴/🟡 | `state-updater.test.mjs` |
| Lint | `agents/__tests__/persona-reviewer-fixture.test.mjs` | persona def + 6 axes + both modes | `state-updater.test.mjs` |
## Risks and Mitigations
| Priority | Risk | Location | Impact | Mitigation |
|----------|------|----------|--------|------------|
| High | Dead-link blast radius (N1) — `publish` has 21 refs incl. 9 in hook scripts emitting runtime guidance | `session-start.mjs:253,258,333,336,383`, `posting-reminder.mjs:94,95`, `user-prompt-context.mjs:46` | Ships text pointing at a removed command | Step 17 grep target set incl. `agents/README.md` + `CLAUDE.md`; recompile hooks; treat every merge as Medium |
| High | Parallel Task fan-out (fasit assumption 4) has NO existing precedent to copy | `commands/newsletter.md` Step 2 | If it degrades, the whole research phase falls back to guessing | Step 8 = highest-uncertainty checkpoint; escalate on degrade, don't paper over |
| Medium | weasyprint hard-fails; degradation not implemented (N/3) | `build-pdf.mjs:339`, `build-carousel.mjs:262` | PDF steps abort on machines without weasyprint | Write degradation in Step 1; force-PATH-miss test |
| Medium | No OFL license file at source (correction #2) | `render/OFL.txt` | License-compliance gap redistributing OFL fonts | Author OFL-1.1 text in Step 1 |
| Medium | Skill catalogs duplicated across 6 dirs (N2) | `skills/*/SKILL.md` | Stale catalogs ship | Steps 12, 21 sweep all 6 |
| Low | Font weight 1.5 MB (R3) | `render/fonts/` | Plugin size | Acceptable; OFL attached |
| Low | Cross-repo render migration (R1) | maskinrommet | Out of plugin scope | Explicit operator instruction before any maskinrommet write |
| Low | Opus cost on many parallel agent calls (R5) | Steps 10, 14 | Cost | Expected/accepted; escalate if volume spikes |
## Assumptions
| # | Assumption | Why unverifiable | Impact if wrong |
|---|-----------|-----------------|-----------------|
| 1 | Foreground Task fan-out from a command keeps the Task tool (vs background agents losing it) | Pure runtime behavior; no static precedent in repo | Step 8/10 research + sweeps degrade to sequential/guessing |
| 2 | `${CLAUDE_PLUGIN_ROOT}` resolves in command Bash | Proven at runtime in existing commands, but env-injected | Render/script invocations fail to resolve |
| 3 | Retired agents' function is fully covered by existing scripts | personalization-score.mjs + state-updater exist, but coverage parity is judgment | A capability silently lost in Step 7 |
| 4 | maskinrommet write for dogfood (Step 14) gets explicit operator OK | Cross-repo; operator-gated | Dogfood blocked or done against a fixture only |
## Verification
End-to-end / cross-step checks (per-step Manifests run automatically during execution):
- [ ] `ls render/ && ls render/fonts/*.ttf | wc -l && node --test 'render/__tests__/*.test.mjs'` → 4 scripts + 8 fonts + OFL.txt; all render tests pass
- [ ] **Antakelse 2 (fonts resolve via `__dirname`, not fallback):** `cd /tmp && node <plugin>/render/build-pdf.mjs <sample.md>` produces a PDF embedding Newsreader/Inter (inspect PDF metadata / visual) — `[OPERATØR]` visual confirm
- [ ] `node --test 'agents/__tests__/*.test.mjs'` → fixture-lint tests pass
- [ ] `test "$(ls commands/*.md | wc -l | tr -d ' ')" = "24"` → command count is exactly 24 (27 5 removed + 2 added)
- [ ] `ls agents/*.md | grep -v README | wc -l` → 16 (content-tracker, personalization-scorer, performance-reporter, comment-strategist removed; fact-checker, persona-reviewer added)
- [ ] `grep -rn '1\.2\.0' .claude-plugin/plugin.json CLAUDE.md README.md CHANGELOG.md` → zero matches (all bumped to 2.0.0)
- [ ] `grep -rn '/linkedin:templates\|/linkedin:publish\|/linkedin:authority\|/linkedin:collab\|/linkedin:speaking' commands/ agents/ skills/ hooks/ README.md CLAUDE.md agents/README.md` → zero stray route-refs to removed commands
- [ ] `python3 hooks/scripts/compile-hooks.py --check` → clean (no drift after hook-ref edits)
- [ ] `[OPERATØR]` one real edition produced end-to-end with persona-sweep before lock, reviewed in browser
- [ ] All 3 doc levels (plugin README + plugin CLAUDE + root README) reflect v2.0.0
## Estimated Scope
- **Files to create:** ~13 (newsletter.md, outreach.md, fact-checker.md, persona-reviewer.md, personas.template.md, edition-state.template.json, longform-quality-rules.md, OFL.txt, 4 render scripts under render/ + fonts/, plus test/fixture files)
- **Files to modify:** ~15 (multiplatform, linkedin router, quick, calendar, strategy, audit, analyze, import, profile, analytics-interpreter, engagement-coach, 6 skills, agents/README, CLAUDE.md, README.md, plugin.json, CHANGELOG, hook scripts, marketplace docs)
- **Files to remove:** 7 (templates, publish, authority, collab, speaking commands; content-tracker, personalization-scorer, performance-reporter, comment-strategist agents)
- **Complexity:** high (21 sessions, multi-session resumption, cross-repo touchpoint, runtime-assumption checkpoint)
## Execution Strategy
> **Execution is strictly sequential `/trekexecute --fg` (subscription), one
> session per `/trekcontinue`** — per operator constraint (no parallel
> `claude -p`, API billing). Waves below are dependency groupings, not
> parallelism licenses. Each session = exactly one step (each step is a full
> ≤35 %-context fasit session).
### Session 17: Wave 1 — Fundament (Phase 1)
- **Steps:** 1 (S1), 2 (S1a), 3 (S2), 4 (S3), 5 (S4), 6 (S5), 7 (S6)
- **Wave:** 1
- **Depends on:** none (internal: Step 2, 3 depend on Step 1)
- **Scope fence:**
- Touch: `render/`, `config/personas.template.md`, `config/edition-state.template.json`, `agents/fact-checker.md`, `agents/persona-reviewer.md`, `agents/fixtures/`, `agents/__tests__/`, remove content-tracker + personalization-scorer, `agents/README.md`, `CLAUDE.md` (agent table)
- Never touch: `commands/newsletter.md` (Wave 2), any consolidation target (Wave 4)
### Session 813: Wave 2 — Kapabilitet (Phase 2)
- **Steps:** 8 (S7), 9 (S8), 10 (S9), 11 (S10), 12 (S11), 13 (S12)
- **Wave:** 2
- **Depends on:** Wave 1 (needs agents, personas, render, edition-state)
- **Scope fence:**
- Touch: `commands/newsletter.md`, `commands/multiplatform.md`, `commands/linkedin.md`, `skills/*/SKILL.md`, `references/longform-quality-rules.md`
- Never touch: render scripts (frozen after Wave 1), consolidation targets (Wave 4)
### Session 1415: Wave 3 — Dogfood (Phase 3)
- **Steps:** 14 (S13), 15 (S14)
- **Wave:** 3
- **Depends on:** Wave 2 (needs full pipeline) + Wave 1 render
- **Scope fence:**
- Touch: a serie-mappe (maskinrommet — operator-gated) or a `docs/review/` fixture; friction log; whichever pipeline files S14 fixes name
- Never touch: maskinrommet without explicit operator instruction (R1)
### Session 1621: Wave 4 — Renovering (Phase 4)
- **Steps:** 16 (S15), 17 (S16), 18 (S17), 19 (S18), 20 (S19), 21 (S20)
- **Wave:** 4
- **Depends on:** independent of Wave 23 (may run after Wave 1); Step 21 closes v2.0.0 so it runs last
- **Scope fence:**
- Touch: consolidation targets (quick, calendar, outreach, strategy, audit, analyze, import, profile, linkedin router), `agents/analytics-interpreter.md`, `agents/engagement-coach.md`, removed files, all doc levels, version refs, hook scripts (publish refs)
- Never touch: `commands/newsletter.md` internals (frozen after Wave 2)
### Execution Order
- **Wave 1:** Sessions 17 (sequential --fg)
- **Wave 2:** Sessions 813 (after Wave 1)
- **Wave 3:** Sessions 1415 (after Wave 2)
- **Wave 4:** Sessions 1621 (after Wave 1; Session 21 last overall — closes v2.0.0)
### Grouping rules applied
- Steps sharing files → adjacent sessions (newsletter Steps 813 all touch `newsletter.md`, done strictly in order)
- Render (Steps 13) frozen before the newsletter command consumes it
- Consolidation (Wave 4) isolated from langform files to avoid cross-contamination
- 1 step per session (each is a full ≤35 %-context deliverable — see mapping note)
## Plan Quality Score
| Dimension | Weight | Score | Notes |
|-----------|--------|-------|-------|
| Structural integrity | 0.15 | 95 | 21 steps, dependency-ordered, waves match fasit phases |
| Step quality | 0.20 | 92 | each step has Files/Changes/Reuses/Test-first/Verify/On-failure/Checkpoint/Manifest; some Verify cmds approximate (agent gates) |
| Coverage completeness | 0.20 | 95 | every fasit session S1S20 (+S1a) mapped; all decisions AH realized |
| Specification quality | 0.15 | 90 | concrete paths + reuse refs; a few `[OPERATØR]`/`[GATE]` steps are intentionally non-mechanical |
| Risk & pre-mortem | 0.15 | 92 | R1R5 + N1N3 + 4 assumptions; highest-uncertainty checkpoint flagged |
| Headless readiness | 0.10 | 90 | On-failure + Checkpoint per step; multi-session resumption via project dir |
| Manifest quality | 0.05 | 85 | every step has a real predicate after revision; consolidation deletions bind to Verify (schema is positive-match only — acknowledged) |
| **Weighted total** | **1.00** | **90** | **Grade: A** |
**Adversarial review:**
- **Plan critic:** REPLAN → revised. 3 blockers + 6 major + 5 minor found; all blockers + 5/6 major + 4/5 minor addressed (see Revisions). The one major not "fixed" (M2: Manifest can't encode deletions) is an acknowledged schema limitation — bound to the Verify gate instead.
- **Scope guardian:** ALIGNED. 0 scope-creep; all S1S20 (+S1a) mapped 1:1; decisions AH realized, none re-litigated; maskinrommet read-only/operator-gated; decision B (no short-form extension) honored. 2 gaps + 1 dependency issue — all addressed in Revisions.
## Revisions
| # | Finding | Severity | Resolution |
|---|---------|----------|------------|
| 1 | Step 21 command-count predicate self-contradictory (22/23/"verify net"); correct net is 24 | blocker | Verify rewritten to exact string-equality `= "24"`; Changes states the binding number; "~23" noted as approximate. Manifest adds CLAUDE.md + README.md `2.0.0` checks |
| 2 | Step 2 Manifest (string `export`/`import.meta.url`) doesn't prove the table/heading/inline-code generalization (a no-op passes) | blocker | Manifest `must_contain` now greps the production renderer for `<table>`, `<h4`, `<code>` — output markers a no-op cannot emit |
| 3 | Steps 14, 15 empty Manifests + Step 9 single-string Manifest = rubber stamps | blocker | Step 14 now requires `docs/voyage-build/dogfood-S13-friction.md` (with order-proof); Step 15 requires the log updated with ✅/🔶 per item; Step 9 Manifest adds `Step 3`/`Step 4`/`AI-slop` |
| 4 | Newsletter Steps 811 hide 24 phases behind single-string Manifests; order assertions only in grep | major | Each Manifest now requires all phase `Step N` headings present; order (sweep-before-lock, hook-after-lock) bound to Verify grep + `[GATE]` with explicit notes |
| 5 | Step 7 deletions + capability-parity not verified by Manifest | major | Acknowledged schema limit (positive-match only); Verify `! test -f` + dead-link grep made the binding predicate; archetype-F note added |
| 6 | Step 9 forward-references `longform-quality-rules.md` created in Step 13 | major | Step 9 now inlines the §8 rules in newsletter.md; Step 13 extracts them to the reference file + leaves a pointer. No dangling reference at any point |
| 7 | Step 15 On-failure ("revert the specific fix") had no referent | major | On-failure now reverts using the file path recorded against each friction item in the S13 log; escalate if unnamed. Steps 1415 marked `[OPERATØR]`-gated (not pure headless) |
| 8 | Step 8 fan-out Manifest only checked the command name | major | Manifest adds `Step 0`/`Step 2`/`parallel`; runtime fan-out behavior remains the `[GATE]` + escalate On-failure (cannot be static) |
| 9 | Step 12 grep depended on exact pointer wording `see /linkedin:newsletter` | major | Verify rewritten to assert no multi-step newsletter *section* survives (`Step.*newsletter` count = 0); a one-line pointer of any wording is allowed |
| 10 | build-linkedin constants cited "3250"; actual 3450 | minor | Citations corrected to 3450 with per-constant line refs (CALENDAR:34, FRESHNESS:44, COVER_CREDIT:49, CAPTIONS:50) |
| 11 | Step 1 `min_file_count: 6` undercounts; fonts absent from Manifest | minor | Added `render/fonts/Inter-400.ttf` + `render/fonts/Newsreader-400.ttf` to expected_paths; `min_file_count: 8`; Verify asserts 8 .ttf; build-carousel weasyprint added to must_contain |
| 12 | Step 6 Verify `grep -ci 'modus\|mode'``\|` not portable on BSD grep (darwin) | minor | Rewritten to `grep -Eci 'resonans\|konverter'` (BSD-safe `-E`) |
| 13 | Scope gap: antakelse 2 (PDF fonts resolve via `__dirname`, not fallback) not asserted | minor | Added to end-to-end Verification as an `[OPERATØR]` PDF-metadata check |