diff --git a/plugins/linkedin-thought-leadership/.gitignore b/plugins/linkedin-thought-leadership/.gitignore index 027bab4..6d6197e 100644 --- a/plugins/linkedin-thought-leadership/.gitignore +++ b/plugins/linkedin-thought-leadership/.gitignore @@ -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/ diff --git a/plugins/linkedin-thought-leadership/docs/brief-fullspektrum-innholdsmotor.md b/plugins/linkedin-thought-leadership/docs/brief-fullspektrum-innholdsmotor.md new file mode 100644 index 0000000..fb56042 --- /dev/null +++ b/plugins/linkedin-thought-leadership/docs/brief-fullspektrum-innholdsmotor.md @@ -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`. diff --git a/plugins/linkedin-thought-leadership/docs/plan-fullspektrum-innholdsmotor.md b/plugins/linkedin-thought-leadership/docs/plan-fullspektrum-innholdsmotor.md new file mode 100644 index 0000000..aed4fe9 --- /dev/null +++ b/plugins/linkedin-thought-leadership/docs/plan-fullspektrum-innholdsmotor.md @@ -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//` (é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//` (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 ~25–30 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//`), 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 ` 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 (2–4) 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 2–3. 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 1–3 | +| **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 0–2 (§5.1). | Antakelse-test 4 (research-fan-out kjører) | +| **S8** | 2 | newsletter.md Step 3–4 (utkast + konsistens/kvalitet). | Utkast genereres voice-matchet | +| **S9** | 2 | newsletter.md Step 5–6 (faktasjekk + persona-sweep). | Begge agenter kalles parallelt; sweep FØR lås | +| **S10** | 2 | newsletter.md Step 7–10 (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: ]` 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 && node /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 1–3 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 (S7–S10, 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, S15–S20):** (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 "" commands/ agents/ skills/ hooks/ README.md` gir kun tilsiktede treff; (e) router (`linkedin.md`) oppdatert. +- **G — Dogfood (S13–S14):** (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 1–3 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 0–2 kjører på dummy-serie; antakelse 4 (parallell fan-out) | +| S8 | E | Step 3–4 produserer utkast-fil; `[OPERATØR]`/`[GATE: voice-trainer]` for voice-match — ikke selv-sertifisert | +| S9 | E | Step 5–6: `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 7–10: 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//linkedin/edition-config.json` — ny per-serie config diff --git a/plugins/linkedin-thought-leadership/docs/voyage-build-brief.md b/plugins/linkedin-thought-leadership/docs/voyage-build-brief.md new file mode 100644 index 0000000..ac9ccbc --- /dev/null +++ b/plugins/linkedin-thought-leadership/docs/voyage-build-brief.md @@ -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 1–3); 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 S1–S20 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). diff --git a/plugins/linkedin-thought-leadership/docs/voyage-build/brief.md b/plugins/linkedin-thought-leadership/docs/voyage-build/brief.md new file mode 100644 index 0000000..ac9ccbc --- /dev/null +++ b/plugins/linkedin-thought-leadership/docs/voyage-build/brief.md @@ -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 1–3); 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 S1–S20 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). diff --git a/plugins/linkedin-thought-leadership/docs/voyage-build/plan.md b/plugins/linkedin-thought-leadership/docs/voyage-build/plan.md new file mode 100644 index 0000000..b643dcc --- /dev/null +++ b/plugins/linkedin-thought-leadership/docs/voyage-build/plan.md @@ -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 (S1–S20) 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 A–H (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// — 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 ` 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:`, `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 34–50: `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 3–5 +> 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 |` → ``), all heading levels `#`–`####` (today only `##`/`###`), inline `` `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 `
`/``/`
`; `#`→`

` … `####`→`

`; backtick span → ``; 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 /render/build-html.mjs ` 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: "" + - path: render/build-html.mjs + pattern: "" + - path: render/__tests__/build-html.test.mjs + pattern: "
" + ``` + > **Manifest note (blocker fix):** the three `
`/`` + > 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 `
`/`` 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:34–50) 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 (16–21), 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 0–2 (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 0–2 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 0–2 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 3–4 (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 3–4 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 3–4 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 5–6 (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 5–6 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 7–10 (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 7–10 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 8–13 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 -- `); 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 (3–5 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 `). +- **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 /render/build-pdf.mjs ` 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 1–7: 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 8–13: 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 14–15: 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 16–21: 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 2–3 (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 1–7 (sequential --fg) +- **Wave 2:** Sessions 8–13 (after Wave 1) +- **Wave 3:** Sessions 14–15 (after Wave 2) +- **Wave 4:** Sessions 16–21 (after Wave 1; Session 21 last overall — closes v2.0.0) + +### Grouping rules applied + +- Steps sharing files → adjacent sessions (newsletter Steps 8–13 all touch `newsletter.md`, done strictly in order) +- Render (Steps 1–3) 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 S1–S20 (+S1a) mapped; all decisions A–H 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 | R1–R5 + N1–N3 + 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 S1–S20 (+S1a) mapped 1:1; decisions A–H 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 `
`, `` — 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 8–11 hide 2–4 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 14–15 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 "32–50"; actual 34–50 | minor | Citations corrected to 34–50 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 |