refactor(linkedin)!: rename plugin linkedin-thought-leadership → linkedin-studio (v3.0.0)

BREAKING CHANGE: the marketplace slug, the agent namespace
(linkedin-studio:<agent>), and the runtime state-file path
(~/.claude/linkedin-studio.local.md) all change. Reinstall required;
existing state migrated in place (post metrics, streak, history preserved).
The /linkedin:* commands are unchanged — the command namespace is set
per-command in frontmatter and was always independent of the plugin slug.
Functionality is byte-identical to v2.4.0; this release is pure identity.

- dir + manifests: plugins/linkedin-studio + plugin.json + root marketplace.json
- agent namespace updated in commands/newsletter.md (only functional invoker)
- state path updated in 4 hook scripts + topic-rotation prompt + state template
- catch-all skill dir renamed skills/linkedin-studio (5 functional skills unchanged)
- docs + version bump to 3.0.0 across README badge, CHANGELOG, root README/CLAUDE.md
- historical records (CHANGELOG past entries, docs/ build artifacts,
  config-audit v5.0.0 snapshots) intentionally retain the old slug

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-05-29 11:32:02 +02:00
commit b6bb61246b
196 changed files with 164 additions and 138 deletions

View file

@ -0,0 +1,164 @@
# Agent Capability Matrix
14 specialized agents for LinkedIn thought leadership. Each agent has a focused responsibility, defined model, and unique color for visual identification.
## Quick Reference
| Agent | Model | Color | Primary Responsibility |
|-------|-------|-------|----------------------|
| content-optimizer | Sonnet | Blue | Optimize posts against algorithm signals |
| strategy-advisor | Sonnet | Green | Growth strategy and phase-specific guidance |
| analytics-interpreter | Sonnet | Yellow | Pattern discovery + weekly/monthly performance reports (interpret/report modes) |
| engagement-coach | Sonnet | Magenta | 5x5x5 + first-hour tactics + CEA commenting + target selection |
| content-planner | Sonnet | Cyan | Weekly/monthly content calendars |
| network-builder | Sonnet | Teal | Strategic networking and outreach |
| content-repurposer | Sonnet | Purple | Format conversion and evergreen refresh |
| trend-spotter | Sonnet | White | Trending topics and opportunity scoring |
| voice-trainer | Sonnet | Pink | Voice profile building and drift detection |
| differentiation-checker | Sonnet | Gray | Originality scoring and commodity detection |
| video-scripter | Sonnet | Violet | Video script creation with pacing and visual cues |
| post-feedback-monitor | Haiku | Lime | Post-publish 48h monitoring and real-time interventions |
| fact-checker | Opus | Brown | Factual-claim verification against primary/credible sources (longform) |
| persona-reviewer | Opus | Olive | Reader-persona resonance + hook-conversion gate (longform) |
## Capability Matrix
Capabilities mapped across agents. **P** = Primary, **S** = Secondary/Supporting.
| Capability | optimizer | strategy | analytics | engage | planner | network | repurpose | trends | voice | diff-check | video | post-monitor | fact-check | persona-rev |
|-----------|:---------:|:--------:|:---------:|:------:|:-------:|:-------:|:---------:|:------:|:-----:|:----------:|:-----:|:------------:|:----------:|:-----------:|
| Post optimization | **P** | | | | | | | | | | | | | |
| Hook analysis | **P** | | | | | | | | | | S | | | S |
| Algorithm alignment | **P** | | | S | | | | | | | S | S | | |
| Growth strategy | | **P** | | | S | | | | | | | | | |
| Phase assessment | | **P** | | | | | | | | | | | | |
| Trajectory analysis | | **P** | S | | | | | | | | | | | |
| Audience analysis | | S | **P** | | | | | | | | | | | |
| Pattern discovery | | | **P** | | | | | | | | | | | |
| Performance reports | | | **P** | | | | | | | | | | | |
| Content DNA | | | **P** | | | | | | S | | | | | |
| Engagement coaching | | | | **P** | | S | | | | | | | | |
| 5x5x5 method | | | | **P** | | S | | | | | | | | |
| Comment strategy | | | | **P** | | | | | | | | | | |
| CEA method | | | | **P** | | | | | | | | | | |
| Target identification | | | | **P** | | S | | | | | | | | |
| Content planning | | | | | **P** | | | S | | | | | | |
| Mix enforcement | | | | | **P** | | | | | | | | | |
| Gap analysis | | | | | **P** | | | | | | | | | |
| Network building | | | | S | | **P** | | | | | | | | |
| Connection scoring | | | | | | **P** | | | | | | | | |
| DM templates | | | | | | **P** | | | | | | | | |
| Format conversion | | | | | | | **P** | | | | S | | | |
| Evergreen scoring | | | | | | | **P** | | | | | | | |
| Content lifecycle | | | | | S | | **P** | | | | | | | |
| Trend scanning | | | | | S | | | **P** | | | | | | |
| First-mover assessment | | | | | | | | **P** | | | | | | |
| Angle mapping | | | | | S | | S | **P** | | | | | | |
| Voice profiling | | | | | | | | | **P** | | | | | |
| Drift detection | | | | | | | | | **P** | | | | | |
| Quarterly audit | | | | | | | | | **P** | | | | | |
| Originality scoring | | | | | | | | | | **P** | | | | |
| Commodity detection | | | | | | | | | | **P** | | | | |
| Differentiation | | | | | | | | | | **P** | | | | |
| Video scripting | | | | | | | S | | | | **P** | | | |
| Script pacing | | | | | | | | | | | **P** | | | |
| Visual cue notation | | | | | | | | | | | **P** | | | |
| Post-publish monitoring | | | | | | | | | | | | **P** | | |
| Velocity analysis | | | | | | | | | | | | **P** | | |
| Factual verification | | | | | | | | | | | | | **P** | |
| Primary-source check | | | | | | | | | | | | | **P** | |
| Persona resonance | | | | | | | | | | | | | | **P** |
| Hook-conversion gate | | | | | | | | | | | | | | **P** |
## Content Pipeline
How agents collaborate in the end-to-end content lifecycle:
```
┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ trend-spotter│───▸│ content-planner │───▸│ diff-checker │
│ (find topics)│ │ (plan + schedule) │ │ (originality │
└─────────────┘ └──────────────────┘ │ gate ≥51/100) │
│ └────────┬────────┘
│ │
┌──────▼──────┐ ┌───────┴────────┐
│voice-trainer│ │ FORMAT SPLIT │
│(voice check)│ └──┬──────────┬──┘
└──────┬──────┘ │ │
│ ┌───────▼───┐ ┌────▼─────────┐
│ │video- │ │content- │
└────────────▸│scripter │ │optimizer │
│(scripts) │ │(text posts) │
└───────┬───┘ └──────┬───────┘
│ │
└─────┬──────┘
┌────────────────────────────┤
│ │
┌──────▼────────────┐ ┌────────▼───────┐
│analytics- │ │ [PUBLISH] │
│interpreter │ └────────┬───────┘
│(interpret/report) │ │
└───────────────────┘ ┌────────▼───────┐
│engagement-coach│
│(5x5x5 + first │
│ hour + CEA │
│ commenting) │
└────────────────┘
```
### Longform Quality Gates (newsletter)
For longform editions, two additional Opus agents run BEFORE lock:
```
draft ─▸ fact-checker ─▸ persona-reviewer ─▸ LOCK ─▸ delivery
(primary-source (resonance +
verification) hook-conversion
gate)
```
### Parallel Support Agents
These agents operate independently and feed into the pipeline at multiple points:
```
strategy-advisor ──────▸ Macro-level planning and phase guidance
analytics-interpreter ─▸ Pattern discovery + periodic reports feeding back into planning
network-builder ───────▸ Relationship building amplifying content reach
content-repurposer ────▸ Post-publish: extends content lifecycle
```
## Which Agent Do I Need?
| Scenario | Agent | Command |
|----------|-------|---------|
| "I want to write a post" | content-optimizer | `/linkedin:post` |
| "What should I post about?" | content-planner, trend-spotter | `/linkedin:pipeline` |
| "Make this post better" | content-optimizer | `/linkedin:post` |
| "Is this original enough?" | differentiation-checker | `/linkedin:pipeline` |
| "Plan my week's content" | content-planner | `/linkedin:batch` |
| "Am I on track this week?" | — | `/linkedin:calendar` |
| "How did I do this week?" | analytics-interpreter (report mode) | `/linkedin:report` |
| "Analyze my LinkedIn data" | analytics-interpreter (interpret mode) | `/linkedin:analyze` |
| "What's my LinkedIn strategy?" | strategy-advisor | `/linkedin:strategy` |
| "Help me engage more" | engagement-coach | `/linkedin:strategy` |
| "Who should I comment on?" | engagement-coach | `/linkedin:strategy` |
| "Build my network" | network-builder | `/linkedin:strategy` |
| "Does this sound like me?" | voice-trainer | `/linkedin:post` |
| "Repurpose my best post" | content-repurposer | `/linkedin:pipeline` |
| "What's trending in my field?" | trend-spotter | `/linkedin:pipeline` |
| "Audit my content strategy" | analytics-interpreter, strategy-advisor | `/linkedin:audit` |
| "How do I monetize?" | strategy-advisor | `/linkedin:monetize` |
| "Create a video script" | video-scripter | `/linkedin:video` |
| "Turn this post into a video" | video-scripter, content-repurposer | `/linkedin:video` |
| "Script a talking head video" | video-scripter | `/linkedin:video` |
| "Verify facts in this draft" | fact-checker | `/linkedin:newsletter` (longform) |
| "Will this land with my readers?" | persona-reviewer | `/linkedin:newsletter` (longform) |
## Model Selection Rationale
| Model | Agents | Why |
|-------|--------|-----|
| **Opus** | 2 agents (fact-checker, persona-reviewer) | Longform judgment: factual verification, reader-persona resonance |
| **Sonnet** | 11 agents | Complex reasoning: optimization, strategy, analysis, scoring, scripting, comment targeting |
| **Haiku** | 1 agent (post-feedback-monitor) | Lighter task: post monitoring with anomaly detection |

View file

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

View file

@ -0,0 +1,71 @@
# Brief — LTL-plugin oppgradering: produksjonskvalitet og -hastighet
> **Status:** Feltkunnskaps-brief fra Maskinrommet (Seres-serien, 2627.05.2026). Mater den 21-sesjoners oppgraderingen — komplementær til `brief-fullspektrum-innholdsmotor.md`. Dette er *kravgrunnlaget* (hvorfor + hva + suksesskriterier), ikke sesjonsplanen.
>
> **Kildeartefakter (i Maskinrommet-innholdsrepoet, ikke her):** `serier/silvija-seres-motsvar/HANDOVER.md §7`, `~/.claude/learnings/global-learnings.md` (2026-05-27), og `linkedin-plugin-endringsspec.md` (taktisk endringsliste — denne briefen er det strategiske laget over den).
---
## 1. Problem / nordstjerne
Å produsere **én kort artikkel** som er **100 % korrekt** *og* **treffer primærpersonaen** tok i praksis timer (Seres-serien Del 2). Det er ikke bærekraftig ved 37 artikler/uke.
**Nordstjerne:** en kort artikkel fra idé til publiseringsklar på **~30 min**, uten å ofre korrekthet eller persona-treff. Målet er å *front-loade og systematisere* verifisering og dømmekraft — ikke å skippe dem.
## 2. Evidensgrunnlag (hva som faktisk gikk galt)
Del 2 ble først skrevet som en **modell-katalog** (det forfatteren fant interessant), passerte review, og var nær publisering før den ble stoppet og skrevet helt om. Tidstyvene, kategorisert:
| Tidstyv | Type | Adresseres av |
|---|---|---|
| Dårlig utkast måtte skrives helt om | Skulle vært fanget | Blokkerende persona-gate (mål 1) |
| Persona-gaten flagget tung friksjon, men ble lest som notat | Prosess-svikt | Gate som stryk/bestått (mål 1) |
| Trippelsjekk av mange ferske påstander som egen sluttfase | Gjentakende | Påstands-ledger + verifisering ved skriving (mål 3) |
| Flere gale påstander i en *bedre* tekst (titler, «standarder», studie-funn, scope, årstall) | Gjentakende | Verifiseringsdisiplin (mål 3) |
| AI-slop / overhead / katalog-ras → mange ordpuss-runder | Gjentakende | Voice «unngå»-mønstre + mal (mål 2, 4) |
| Build ↔ kilde i utakt (footgun) | Repo-spesifikt | Allerede fikset i innholdsrepoet (ikke plugin) |
**Tre tverklærdommer (skal styre designet):**
1. Persona-review må **blokkere**, ikke annotere.
2. Skriv for **leseren**, ikke forfatteren — ett konkret case > en katalog.
3. **Sterkere narrativ ≠ riktigere fakta** — verifiser mer, ikke mindre.
## 3. Mål — hva den oppgraderte pluginen skal levere
1. **Blokkerende persona-gate.** 3 personaer (A IT-dir, B KI-leder, **C linjeleder = primær, trumfer**) leser KUN teksten. Returnerer **BESTÅTT/STRYK**, ikke kommentarer. ⛔ Hard fail = C «mistet meg», C eier ikke handlingen, sjargong-mur, eller modell-/navne-katalog. «JA med forbehold» = NEI. Kjøres ett pass på nær-ferdig utkast.
2. **Voice «unngå»-mønstre i profilen.** Katalog-ras, fullstendighet-over-handling, selvrefererende overhead-åpninger, «ikke bare X, men Y», unødig tre-listing, påklistret oppsummering, hedging. Mål: utkastet treffer persona C på 1.2. forsøk, ikke 5.
3. **Påstands-ledger + verifiseringsdisiplin.** Hver faktapåstand føres med kilde + dato *mens* teksten skrives. Påstander datert etter modellens kunnskapsgrense **må websøkes**. Fast sjekkliste for hyppige feiltyper: persontitler (sluttet/byttet rolle), «standarder» som varierer per virksomhet, studier tilskrevet for sterke funn, kilde-scope (konkludert vs. utenfor scope), start-/utgivelsesår.
4. **Artikkel-skjelett / mal.** Led med leserens problem; ett konkret (helst norsk) etterprøvbart case framfor en liste; lande på leser-eid handling; skill eksplisitt «dette eier du» vs. «dette ber du IT/fag om».
## 4. Eierskap (hvem gjør hva)
- **LTL-pluginen:** strategi, voice, hooks/caption, **persona-gate**, **påstands-ledger**, mal — alt som er innholds-/kvalitetsarbeid.
- **Voyage (trek*):** orkestrering av lengre/fler-sesjons-løp der det trengs.
- **Innholdsrepo (Maskinrommet):** produksjon/rendering (POST.html/PDF/carousel via `tools/`-scriptene). Render holdes utenfor pluginen.
## 5. Ikke-mål (scope-grenser)
- Ikke fjern menneskelig dømmekraft eller verifisering — målet er fart *med* korrekthet.
- Rendering/typografi forblir i innholdsrepoet.
- Analytics forblir CSV-basert (jf. eksisterende v1.3.0-beslutning — posting-only API).
- Denne briefen lager ikke 21-sesjonsplanen; den gir kravene planen skal innfri.
## 6. Suksesskriterier (målbare)
- [ ] Tid idé→publiseringsklar for en kort artikkel: **~30 min** (ned fra timer).
- [ ] Persona C gir **ekte JA** på 1.2. pass (ikke «JA med forbehold»).
- [ ] **Null** uverifiserte påstander datert etter kunnskapsgrensen ved publisering.
- [ ] **Null** gale person-titler / falske «standard»-påstander / overdrevne studie-funn (de fem feiltypene i mål 3).
- [ ] Persona-gaten **blokkerer** dokumentert (stryk → omskriv før godkjenning), ikke bare annoterer.
## 7. Åpne spørsmål / research-plan (start her i sesjon 1)
- Kartlegg gjeldende plugin-struktur (v1.2.0: ~27 kommandoer, 16 agenter, 9 hooks, 6 skills) — hvor hører gate/ledger/mal hjemme (ny agent? skill-steg i `pipeline`/`post`? hook?).
- Hvordan formaliseres en *blokkerende* verdikt-retur i arbeidsflyten (agent → strukturert PASS/FAIL som stopper neste steg)?
- Ledger-format: nytt asset (`assets/claims/…`)? Knyttet til `differentiation-checker`/research?
- Sekvensering mot eksisterende v1.3.0-plan (posting-API) — uavhengig spor eller felles release?
- Verifiser at voice-profilen (`config/user-profile.local.md`) har en «unngå»-seksjon å utvide.
---
*Skrevet fra produksjonserfaringen i Maskinrommet. Plugin-endringene utføres i LTL-repoet, ikke her.*

View file

@ -0,0 +1,402 @@
# Integration Test Guide: LinkedIn Studio Plugin
Manual integration testing scenarios for commands, agents, and hooks in the plugin.
## Prerequisites
Before testing, ensure:
- [ ] `~/.claude/linkedin-studio.local.md` exists (create from `config/state-file.template.md`)
- [ ] Voice samples exist in `assets/voice-samples/authentic-voice-samples.md`
- [ ] Quality scorecard exists at `assets/checklists/quality-scorecard.md`
- [ ] Plugin is installed: appears in Claude Code's skill/command list
## /linkedin:pipeline — End-to-End Tests
### Test 1: Full Pipeline — Idea to Post
**Goal:** Execute the complete 8-step pipeline from ideation to publish-ready post.
**Steps:**
1. Run `/linkedin:pipeline`
2. Verify Step 0 loads: state file read, status displayed (posts/week, streak)
3. Choose "Generate ideas for me" when prompted
4. Verify 3 topic suggestions appear, drawn from `thought-leadership-angles.md`
5. Select a topic → verify angle selection (2-3 options)
6. Choose format → verify draft follows structure (hook/context/insight/implication/CTA)
7. Verify optimization checks run:
- Hook: 110-140 chars
- Total: 1,200-1,800 chars
- No external links in body
- No corporate buzzwords
8. Verify scheduling recommendation mentions CET times
9. Verify 5x5x5 guidance is provided
10. Verify copy-paste ready output with character count and hashtags
11. Verify first-hour monitoring plan is shown
12. Verify 48-hour check-in reminder appears
**Expected outcome:** A complete, publish-ready post with all quality checks passed.
**Hooks that fire:**
- `SessionStart` → loads state
- `UserPromptSubmit` → injects context
- `PreToolUse (Write)` → quality gate + voice guardian (if draft is written to file)
- `PostToolUse (Write)` → alternative hooks + posting time suggestion
- `Stop` → state update + pre-publish reminders
### Test 2: Pipeline with Existing Topic
**Goal:** User provides their own topic, skipping ideation.
**Steps:**
1. Run `/linkedin:pipeline`
2. Choose "I have an idea already"
3. Provide topic: "Why AI agents will replace workflows in 2026"
4. Verify the topic is used directly (no override)
5. Verify angle suggestions are relevant to the provided topic
6. Complete the remaining steps
**Expected outcome:** Post is created on the user's topic, not a generated one.
### Test 3: Pipeline with State File Missing
**Goal:** Graceful handling when state file doesn't exist.
**Steps:**
1. Temporarily rename `~/.claude/linkedin-studio.local.md`
2. Run `/linkedin:pipeline`
3. Verify: no crash, reasonable fallback (e.g., "No posting data found. Starting fresh.")
4. Complete the pipeline
5. Verify: state file is created after pipeline completes
**Expected outcome:** Pipeline works without state file, creates one at the end.
### Test 4: Pipeline — Draft Save Option
**Goal:** Verify "Save as draft for later" works.
**Steps:**
1. Run `/linkedin:pipeline`
2. Create a post
3. At scheduling step, choose "Save as draft for later"
4. Verify: no posting reminders (5x5x5, first-hour) are shown for drafts
5. Verify: state file is NOT updated with post date (it's a draft, not published)
**Expected outcome:** Draft is saved without publishing-related actions.
---
## /linkedin:batch — End-to-End Tests
### Test 5: Full Batch — 3 Posts from One Theme
**Goal:** Create 3 posts from a single theme with varying angles and formats.
**Steps:**
1. Run `/linkedin:batch`
2. Verify Step 0 loads: state file, check for existing weekly plan
3. Choose "One main theme"
4. Provide theme: "The future of AI in public sector"
5. Verify batch plan shows 3 posts with:
- Different angles (not repetitive)
- Mixed formats (not all the same)
- Different target days
6. Approve the plan
7. Verify each post:
- Follows structure (hook 110-140 chars, 1,200-1,800 total)
- Has unique angle
- Quick quality check passes
8. Verify posts are saved to `assets/drafts/week-[WXX]/`
9. Verify filenames follow pattern: `[day]-[topic-slug].md`
10. Verify YAML frontmatter in each file (planned_date, pillar, angle, format, status)
11. Verify summary shows content mix and pillar coverage
12. Approve all drafts
13. Verify posting schedule with recommended times
**Expected outcome:** 3 distinct posts saved in correct directory with proper metadata.
### Test 6: Batch — Content Pillar Mode
**Goal:** Batch using existing content pillar.
**Steps:**
1. Run `/linkedin:batch`
2. Choose "Content pillar"
3. Select from user's defined pillars in skill file
4. Verify posts are created around that pillar
5. Verify angle variety (not same perspective repeated)
**Expected outcome:** All posts align with chosen pillar but explore different angles.
### Test 7: Batch — Revision Flow
**Goal:** Verify post revision during batch creation.
**Steps:**
1. Run `/linkedin:batch` and create 3 posts
2. At review step, choose "Revise a specific post"
3. Ask for post #2 to be revised (e.g., "Make the hook more provocative")
4. Verify: only post #2 is changed, others remain intact
5. Verify: summary updates to reflect the revised post
**Expected outcome:** Individual post revision works without affecting other batch posts.
### Test 8: Batch — Drafts Directory Creation
**Goal:** Verify `assets/drafts/` directory is created when it doesn't exist.
**Steps:**
1. Ensure `assets/drafts/` does not exist
2. Run `/linkedin:batch` and complete the workflow
3. Verify: `assets/drafts/week-[WXX]/` directory is created
4. Verify: all posts are saved correctly
**Expected outcome:** Directory is created automatically, posts are saved.
---
## Cross-Command Integration Tests
### Test 9: Pipeline After Batch
**Goal:** Pipeline uses batch-created drafts.
**Steps:**
1. First run `/linkedin:batch` to create 3 drafts
2. Then run `/linkedin:pipeline`
3. At ideation, choose "Use a planned topic"
4. Verify: pipeline picks up a draft from the batch
5. Complete pipeline with the batch draft
6. Verify: state file is updated after publishing
**Expected outcome:** Pipeline can consume batch-created drafts seamlessly.
### Test 10: Batch Respects Weekly State
**Goal:** Batch adjusts recommendations based on current posting state.
**Steps:**
1. Set state file to show 2 posts already published this week
2. Run `/linkedin:batch` with goal of 3 posts/week
3. Verify: batch suggests creating only 1 post (3 - 2 = 1 remaining)
4. Or if configurable, verify batch mentions current progress
**Expected outcome:** Batch is aware of weekly posting status.
---
## Hook Integration Tests
### Test 11: Quality Gate Fires on Post Draft
**Goal:** Verify PreToolUse quality gate hook catches issues.
**Steps:**
1. During pipeline or batch, intentionally create a post with:
- Hook over 140 chars
- External link in body
- Corporate buzzword ("leverage")
2. Verify: quality gate flags ALL issues
3. Verify: issues are described specifically (not generic warnings)
**Expected outcome:** Quality gate catches all three violations with specific feedback.
### Test 12: Voice Guardian Detects AI Patterns
**Goal:** Verify voice guardian hook catches AI-sounding content.
**Steps:**
1. During pipeline, create a post that starts with "In today's rapidly evolving landscape..."
2. Verify: voice guardian flags the AI pattern
3. Verify: specific rewrite suggestions are provided
4. Verify: voice samples are referenced for comparison (if they exist)
**Expected outcome:** Voice guardian identifies AI patterns and suggests authentic alternatives.
### Test 13: Stop Hook Updates State
**Goal:** Verify session-end state update works correctly.
**Steps:**
1. Run `/linkedin:pipeline` and create a post
2. Note the topic and hook
3. End the session (or let Stop hook fire)
4. Read `~/.claude/linkedin-studio.local.md`
5. Verify:
- `last_post_date` = today
- `last_post_topic` = the topic used
- `posts_this_week` incremented
- `current_streak` updated correctly
- Recent Posts section has new entry
**Expected outcome:** State file accurately reflects the session's output.
### Test 14: PostToolUse Generates Alternative Hooks
**Goal:** Verify post-creation automation fires.
**Steps:**
1. During pipeline or batch, write a post draft
2. Verify: 3 alternative hooks are generated
3. Verify: each alternative has character count shown
4. Verify: optimal posting time is suggested
5. Verify: 5x5x5 reminder appears
**Expected outcome:** Post-creation automation provides actionable suggestions.
---
## Agent Tests
### Test 15: Post-Feedback Monitor — Basic Monitoring
**Command:** Trigger `post-feedback-monitor` agent
**Steps:**
1. Say "How is my latest post doing?"
2. Agent should load algorithm-signals-reference and engagement-frameworks
3. Agent should ask which post to monitor
4. Provide sample metrics: 500 impressions, 15 reactions, 3 comments, 1 repost
5. Agent should identify the current phase and provide benchmarks
**Expected:** Structured output with metrics snapshot, velocity score, anomaly detection, and recommended actions
**Validates:** Agent file loads correctly, context loading works, output format matches spec
### Test 16: Post-Feedback Monitor — Anomaly Detection
**Command:** Trigger `post-feedback-monitor` agent
**Steps:**
1. Say "My post has 2000 impressions but only 5 reactions"
2. Agent should detect "Impression-Engagement Gap" anomaly
3. Agent should provide specific intervention recommendations
**Expected:** Anomaly correctly identified with cause analysis and action plan
**Validates:** Anomaly detection framework, intervention playbook
### Test 17: Post-Feedback Monitor — Golden Hour
**Command:** Trigger `post-feedback-monitor` agent
**Steps:**
1. Say "I just posted 30 minutes ago, what should I do?"
2. Agent should activate Golden Hour protocol
3. Agent should provide time-sensitive action items
**Expected:** Golden Hour specific advice (reply within 5 min, DM connections, first comment strategy)
**Validates:** Phase detection, time-sensitive interventions
---
## Command Tests
### Test 18: A/B Test — Design New Test
**Command:** `/linkedin:ab-test`
**Steps:**
1. Run the command
2. Select "Design a new A/B test"
3. Choose "Hook/Opening line" as the variable
4. Follow the guided workflow
**Expected:** Complete test plan with hypothesis, variants, execution schedule, success criteria
**Validates:** Command loads, AskUserQuestion flow works, reference file loads, test plan file created
### Test 19: A/B Test — Analyze Results
**Command:** `/linkedin:ab-test`
**Steps:**
1. First create a test plan (Test 18) and manually create a test file with sample data
2. Run `/linkedin:ab-test` and select "Analyze test results"
3. Select the test to analyze
**Expected:** Results comparison table, significance assessment (20% rule), verdict, recommended next steps
**Validates:** File scanning, data analysis, result formatting
### Test 20: Enhanced Report — Trends & Alerts
**Command:** `/linkedin:report`
**Steps:**
1. Ensure at least 4 weeks of imported data exists
2. Run `/linkedin:report` for the current week
3. Verify trend analysis section appears after main report
4. Verify alert detection section appears
**Expected:** 4-week trend table, trend interpretation, performance alerts, algorithm alerts
**Validates:** Trend CLI integration, alert thresholds, formatting
### Test 21: Enhanced Import — Anomaly Detection
**Command:** `/linkedin:import`
**Steps:**
1. Ensure baseline data exists (previous imports)
2. Import a new CSV export
3. After import, verify anomaly detection runs
**Expected:** Breakout posts flagged, patterns detected, intelligent next steps offered
**Validates:** Anomaly detection rules, baseline comparison, conditional suggestions
### Test 22: Enhanced Report — Markdown Export
**Command:** `/linkedin:report`
**Steps:**
1. Run `/linkedin:report` for any week with data
2. Select "Export as Markdown" from options
3. Verify file is saved to `assets/analytics/weekly-reports/YYYY-WXX-report.md`
**Expected:** Clean markdown file with all sections (metrics, trends, alerts, top performers, recommendations)
**Validates:** Export template, file creation, gitignore compliance
---
## Cross-Command Integration Tests
### Test 23: Router — New Commands Accessible
**Command:** `/linkedin`
**Steps:**
1. Run `/linkedin`
2. Verify A/B test appears in command menu
3. Verify post-feedback-monitor appears in agent suggestions
4. Say "I want to A/B test my hooks" — should route to `/linkedin:ab-test`
5. Say "How is my post doing?" — should route to `post-feedback-monitor`
**Expected:** All new commands and agents are accessible through the router
**Validates:** Router updates, intent matching
### Test 24: Collaboration — Multi-Author Workflow
**Command:** `/linkedin:collab`
**Steps:**
1. Run `/linkedin:collab` and complete readiness check
2. Navigate to multi-author content coordination section
3. Verify co-creation workflow templates are available
4. Verify collaboration tracking section exists
**Expected:** Multi-author workflow with 5 phases, shared draft guidelines, collaboration pipeline board
**Validates:** New collab command sections (Step 7 and Step 8)
---
## Known Limitations
1. **No automated testing:** These commands are conversational — they require human interaction at AskUserQuestion steps. Testing must be manual.
2. **State file format:** State file uses YAML frontmatter. Any malformed YAML will cause parsing issues. Always validate format after manual edits.
3. **Draft directory:** `assets/drafts/` and `assets/plans/` are created at runtime. They don't exist in the base plugin directory and won't appear until first use.
4. **Hook ordering:** PreToolUse has two hooks (quality gate + voice guardian). Both fire on every Write/Edit of content files. If one blocks, the user must fix the issue before proceeding.
5. **Content vs. config detection:** All prompt-based hooks include logic to skip non-content files. This relies on heuristic pattern matching (checking for `.local.md`, `.json`, script extensions, etc.). Edge cases may exist.
6. **Agent testing:** Agents (Tests 15-17) are triggered conversationally, not via slash commands. They require natural language input and cannot be invoked deterministically. Test by using the trigger phrases documented in the agent frontmatter.
7. **Structure validation:** Use `scripts/test-runner.sh` to validate file existence, frontmatter format, and router completeness. This is automated and complements the manual integration tests above.
## Test Results Log
Record results here when tests are executed:
| Test | Date | Result | Notes |
|------|------|--------|-------|
| 1 | | | |
| 2 | | | |
| 3 | | | |
| 4 | | | |
| 5 | | | |
| 6 | | | |
| 7 | | | |
| 8 | | | |
| 9 | | | |
| 10 | | | |
| 11 | | | |
| 12 | | | |
| 13 | | | |
| 14 | | | |
| 15 | | | |
| 16 | | | |
| 17 | | | |
| 18 | | | |
| 19 | | | |
| 20 | | | |
| 21 | | | |
| 22 | | | |
| 23 | | | |
| 24 | | | |

View file

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

View file

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

View file

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

View file

@ -0,0 +1,261 @@
# Dogfood S13 — friction log (`/linkedin:newsletter` end-to-end)
**Step 14 (fasit S13) deliverable.** A real end-to-end dogfood of the long-form
pipeline against a **throwaway fixture** (operator decision 2026-05-27: throwaway
fixture in `docs/review/` scope, synthetic topic, no cross-repo write to
maskinrommet). The fixture series lives at
`docs/review/dogfood-serie/` (gitignored — throwaway, not committed). This log is
the only committed artifact.
This is a **design-friction** log, not a content-quality review. Per plan Step 14
"On failure: escalate — capture friction in the log, do not force a green check."
The pipeline's deterministic backbone (render scripts, edition-state, queue) was
**executed for real**; the gate layer (fact-check, persona sweep) is **BLOCKED**
(see F1/F2) and its verdicts were **not fabricated**.
---
## Order-proof (the headline assertion this step must record)
The persona sweep is wired to run **FØR lås / before lock**, exactly as fasit §0.4
mandates. Verified structurally (gate execution itself is blocked by F1/F2, so the
proof is at the wiring level, not a live JA):
1. `config/edition-state.template.json``_doc.phases` orders the phases
`consistency-quality → factcheck-sweep → persona-sweep-prelock → annotation →
lock-delivery`. `persona-sweep-prelock` (Step 6) precedes `lock-delivery`
(Step 8) in the canonical phase list.
2. `commands/newsletter.md` Step 6 and Step 8 each carry an explicit
**"Order assertion (enforced)"** block stating the pipeline may not reach lock
until the primær persona returns a clean JA from the pre-lock sweep.
3. The fixture `linkedin/edition-HANDOVER.md` physically records §5 persona-sweep
(BEFORE lock) **above** §lås, and `edition-state.json` `currentPhase` never
advances to `lock-delivery` because the persona JA precondition is unmet.
So the before-lock ordering holds in the wiring. What the dogfood could **not** do
is execute the gate — because of F1/F2 below.
---
## Friction points (numbered; ordered by severity)
### F1 — [BLOCKER] Agents invoked by bare name; harness requires namespace
**What:** `commands/newsletter.md` issues every `Task` fan-out by **bare agent
name** — `subagent_type: fact-checker` (Step 5), `persona-reviewer` (Steps 6, 9),
and `content-repurposer` (Step 3). The Claude Code harness registers plugin agents
**namespaced** as `linkedin-thought-leadership:<name>`. A bare name does not
resolve.
**Evidence:** Live test this session — `Task(subagent_type: "content-repurposer")`
returned `Agent type 'content-repurposer' not found`, with the available list
showing only `linkedin-thought-leadership:content-repurposer`. Same failure for
`fact-checker` and `persona-reviewer`.
**Impact:** Steps 3, 5, 6, 9 — the entire gate/draft fan-out machinery — fail as
written. This is the core of the long-form pipeline.
**Implicates:** `commands/newsletter.md` (lines ~299, 398399, 467469, 638) →
**Step 15 fix:** namespace all `subagent_type` references to
`linkedin-thought-leadership:<name>` (or confirm the intended invocation form and
align the command to it).
### F2 — [BLOCKER / ENV] `fact-checker` + `persona-reviewer` not registered this session
**What:** Even namespaced, neither `fact-checker` nor `persona-reviewer` appears in
the harness's available-agents list, while every other LTL agent (incl.
`content-repurposer`) does. The two agents were added in S4/S5 (commits
`be03d44`, `1faffac`) after the running session's plugin agent registry was built.
**Evidence:** Available list this session contains
`linkedin-thought-leadership:content-repurposer` etc. but **not** `…:fact-checker`
or `…:persona-reviewer`. Their frontmatter is well-formed and structurally
identical to `content-repurposer` (verified `name`/`description`/`model`/`color`/
`tools`) — so this is **not** a frontmatter defect; it is a registry/reload gap.
**Impact:** Compounds F1 — even after namespacing, Steps 5/6/9 cannot run until the
session reloads the plugin agent set.
**Implicates:** environment (Claude Code reload to register new agents) +
**doc fix:** note in `CLAUDE.md`/`README.md` that adding a plugin agent requires a
session reload before it is invokable. Not a code defect in the agent files.
### F3 — [MAJOR] No template for 3 of the 4 series-folder input artifacts
**What:** The pipeline depends on four artifacts in the series folder:
`edition-state.json`, `edition-config.json`, `edition-delingstekst.md`, and the
`edition-HANDOVER.md`. Only `edition-state.json` ships a template
(`config/edition-state.template.json`). The formats of `edition-config.json` and
`edition-delingstekst.md` are discoverable **only** by reading the render scripts.
**Evidence:** To run the dogfood I had to reverse-engineer both formats from
`render/build-linkedin.mjs` (`loadEditionConfig` shape; `parseDelingstekst`
section grammar `## Del N —` / `## Samle`, `**Første kommentar:**`, `#hashtag`
line). A new operator has no shipped reference.
**Impact:** Step 8 says "confirm the delivery inputs exist" with **no generation
path** — a fresh edition cannot produce these from anything the plugin ships.
**Implicates:** `config/` (add `edition-config.template.json` +
`edition-delingstekst.template.md` + an `edition-HANDOVER.template.md`) and
`commands/newsletter.md` Step 8 (point at the templates) → **Step 15 fix.**
### F4 — [MAJOR] Draft filename/location is inconsistent across steps
**What:** Step 3 says write the draft to `<serie>/linkedin/<article>.draft.md`.
Steps 7 and 8 expect `NN-utkast.md` (two-digit NN prefix) in the **series root**.
`build-linkedin.mjs` **silently skips** any file without an `NN` prefix
(`↷ hopper over … (ikke NN-prefiks)`).
**Evidence:** `render/build-linkedin.mjs` line 357 regex `^(\d{2})`; the command
text uses two different paths/names for the same draft.
**Impact:** Following Step 3 literally produces a file (`*.draft.md`, inside
`linkedin/`) that Step 8 then silently skips — a green exit with no POST.html.
**Implicates:** `commands/newsletter.md` (reconcile Step 3 vs Steps 7/8 on draft
filename + location) → **Step 15 fix.**
### F5 — [MAJOR] Series root hardcoded to maskinrommet
**What:** Step 0 resolves the series folder under
`/Users/ktg/repos/maskinrommet/serier/<slug>/` with no parameter to point
elsewhere. Dogfooding (or any other repo / a throwaway fixture) requires a manual
mental override of the documented path.
**Evidence:** Step 0.1 and the architecture note both hardcode the maskinrommet
path; the dogfood had to invent `docs/review/dogfood-serie/` off-spec.
**Impact:** The command is coupled to one specific external repo. Operator must
deviate from the written procedure for any other location.
**Implicates:** `commands/newsletter.md` Step 0 (accept/derive a series-root arg;
keep maskinrommet as default, not the only path) → **Step 15 fix (or deferred with
operator note if maskinrommet-only is intentional).**
### F6 — [MINOR] `build-linkedin.mjs` carries Seres-specific hardcoding
**What:** `CAROUSEL = new Set(["03","06"])` and the **unconditional** samle-post
build are Seres-series assumptions baked into a script that S2 "generalized."
**Evidence:** Dogfood produced `linkedin/samle/POST.html` for a single-edition
fixture that has no series to summarize; the carousel set is dead for any series
whose carousel editions aren't 03/06.
**Impact:** Low for correctness (samle is harmless extra output), but it is
generalization debt — the script still assumes the Seres shape.
**Implicates:** `render/build-linkedin.mjs` (lines 63, 364370) → likely **defer**
to a later generalization pass; note for operator.
### F7 — [MINOR] `build-html.mjs` returns exit 0 on a missing input file
**What:** A missing input arg prints `Fant ikke: <path>` to stderr and `continue`s;
the loop completes with **exit 0** and no HTML produced.
**Evidence:** `render/build-html.mjs` lines 10451048 (`continue`, no `process.exit`,
no error accumulation).
**Impact:** Step 7 already flags this (N3), but the script's silent exit-0 is a
footgun: a typo'd filename looks like success. The command must check that the
expected output file exists, not just the exit code.
**Implicates:** `render/build-html.mjs` (exit non-zero if zero files written) +/or
`commands/newsletter.md` Step 7 (verify output file, not just exit) → **Step 15
fix candidate.**
### F8 — [MINOR] Fatal-vs-graceful asymmetry on missing delivery inputs (Step 8)
**What:** Step 8 says "if either file is absent the script throws on read." In fact
`edition-config.json` is **graceful** (`loadEditionConfig` falls back to empty
defaults), while `edition-delingstekst.md` is **fatal**`parseDelingstekst()`
runs unconditionally at the top of `main()` and ENOENT-throws **before any
POST.html is written**, including the article POST that does not depend on it.
**Evidence:** `render/build-linkedin.mjs` lines 4559 (graceful config) vs 180
(`fs.readFileSync(DELINGSTEKST_FILE)` with no try) called at line 349.
**Impact:** The command's description of the failure mode is inaccurate, and a
missing delingstekst kills the whole build rather than degrading.
**Implicates:** `render/build-linkedin.mjs` (guard `parseDelingstekst` like config)
+ `commands/newsletter.md` Step 8 (correct the "either file throws" wording) →
**Step 15 fix candidate.**
### F9 — [MINOR] `agents/README.md` registered as an agent
**What:** The harness available list includes `linkedin-thought-leadership:README`
— a non-agent README in `agents/` is being picked up as an invokable agent.
**Evidence:** Available-agents list this session contains `…:README`.
**Impact:** Cosmetic/registry noise; not harmful but pollutes the agent namespace.
**Implicates:** `agents/README.md` (relocate, or ensure it lacks agent frontmatter)
→ likely **defer** / doc-pass.
---
## What ran clean (no friction)
- **Step 7 annotation**`build-html.mjs 01-utkast.md` (cwd = serie-mappe) → wrote
`review/01-utkast.html` (30.4 KB), exit 0. ✅
- **Step 8 render**`build-linkedin.mjs 01-utkast.md``linkedin/01/POST.html`
+ `linkedin/samle/POST.html`, exit 0; body survived (headings/lists/strong
present in POST.html). ✅ (Render works; the *lock gate* is blocked, not the
renderer.)
- **cwd contract** — running both scripts from the series folder resolved
`linkedin/` and `review/` correctly. The command's "cd to the series folder"
instruction is right; the footgun is only if you forget (then output lands under
the plugin).
- **edition-state schema** — the template's phase list and article-status values
were sufficient to represent the walk; resumption table in Step 0 is coherent.
## Friction summary for Step 15 (revert/fix targets)
| # | Severity | Implicated file | Step 15 disposition | Status |
|---|----------|-----------------|---------------------|--------|
| F1 | BLOCKER | `commands/newsletter.md` | namespace agent calls | ✅ |
| F2 | BLOCKER/env | env + `CLAUDE.md`/`README.md` | reload + document | ✅ (doc) / 🔶 (reload = operator) |
| F3 | MAJOR | `config/` + `commands/newsletter.md` | add 3 templates | ✅ |
| F4 | MAJOR | `commands/newsletter.md` | reconcile draft path/name | ✅ |
| F5 | MAJOR | `commands/newsletter.md` | de-hardcode series root | ✅ |
| F6 | MINOR | `render/build-linkedin.mjs` | config-derive carousel; fix samle comment | ✅ |
| F7 | MINOR | `render/build-html.mjs` (+ Step 7) | exit non-zero on no output | ✅ |
| F8 | MINOR | `render/build-linkedin.mjs` (+ Step 8) | guard delingstekst + fix wording | ✅ |
| F9 | MINOR | `agents/README.md` | relocate out of `agents/` | ✅ (relocated) / 🔶 (de-register on reload) |
**Headline:** the long-form pipeline's deterministic backbone is sound, but its
**gate layer is currently un-runnable** (F1 + F2). Step 15 must close F1 (a concrete
one-line-per-call edit) and F2 (reload + a doc note) before the pre-lock persona
sweep can actually execute — the order is correctly wired, it just cannot fire yet.
---
## Step 15 (S14) — re-test outcomes
All nine friction points were closed (operator elected to fix F6F9 rather than
defer). Each was re-tested with a concrete check, not a self-asserted "fixed."
- **F1 — ✅ closed.** All four `Task` call sites in `commands/newsletter.md`
(content-repurposer Step 3, fact-checker Step 5, persona-reviewer Steps 6 + 9)
now use the namespaced `subagent_type: linkedin-thought-leadership:<name>`, plus a
canonical "Agent invocation form (required)" note near the foreground principle.
**Check:** `grep -nE 'subagent_type: (fact-checker|persona-reviewer|content-repurposer)' commands/newsletter.md`
→ zero bare names; `grep -nE 'linkedin-thought-leadership:(fact-checker|persona-reviewer|content-repurposer)'`
→ all 4 sites namespaced.
- **F2 — ✅ doc / 🔶 reload.** Documented in `CLAUDE.md` (Agents section: invocation
form + reload requirement) and `README.md` (Agent Architecture note). The
environmental half — registering a newly-added agent — inherently requires a
Claude Code **session reload**; that is an operator action, not a code change.
Confirmed F2 persists across `/clear`: `fact-checker`/`persona-reviewer` were
still absent from this fresh session's agent registry (only the 15 older agents +
README appeared), proving it is a reload gap, not a per-session fluke.
- **F3 — ✅ closed.** Added `config/edition-config.template.json`,
`config/edition-delingstekst.template.md`, `config/edition-HANDOVER.template.md`
(formats reverse-engineered from the render scripts, now shipped as reference).
Wired into `newsletter.md` Step 0 (HANDOVER), Step 8 (config + delingstekst), and
the Reference Files footer. **Check:** all three exist under `config/`;
`edition-config.template.json` parses as valid JSON.
- **F4 — ✅ closed.** Step 3 now writes the canonical `<serie>/NN-utkast.md` in the
series root (the exact file Steps 7/8 render), with an explicit "do NOT write
`linkedin/<article>.draft.md`" warning. **Check:** no `.draft.md`/`<article>` path
refs remain except the intentional anti-pattern warning.
- **F5 — ✅ closed.** Step 0 resolves a **series root** via an order (explicit path
arg → `${LTL_SERIES_ROOT:-…/maskinrommet/serier}/<slug>/` → ask once); maskinrommet
is the default, not the only path. The architecture preamble was aligned to match.
- **F6 — ✅ closed.** `CAROUSEL = new Set(["03","06"])` removed; carousel editions are
now config-derived (`config.carousel`, a list of NN strings) via `loadEditionConfig`
+ `EMPTY_CONFIG` + the new config template. Misleading "samle bygges alltid" comment
corrected (build has always been gated on `shareMap.samle`). **Check:** `grep -n
CAROUSEL render/build-linkedin.mjs` → none; 20/20 render tests pass (one assertion
updated to include the `carousel: []` default).
- **F7 — ✅ closed.** `build-html.mjs main()` now counts files written, prints
`Ingen HTML produsert …` and the CLI guard exits non-zero when zero files are
produced (no more silent exit-0 on a typo'd filename). Step 7 wording updated to
rely on the exit code AND verify the output file. **Re-tested live:** missing input
→ exit 1; valid input → exit 0 + `review/NN-utkast.html` written.
- **F8 — ✅ closed.** `parseDelingstekst()` wrapped in try/catch returning `{}` on
ENOENT, matching `loadEditionConfig`'s fail-soft contract; Step 8 wording corrected
("both inputs optional and graceful," not "either file throws"). **Re-tested live:**
no delingstekst → `build-linkedin.mjs` exit 0 + `linkedin/01/POST.html` still built.
- **F9 — ✅ relocated / 🔶 de-register on reload.** `git mv agents/README.md
docs/agents-capability-matrix.md` — the only reliable fix, since the file registers
by filename (it had no frontmatter yet still appeared as
`linkedin-thought-leadership:README`). The stale registration clears on the next
Claude Code reload (env, same as F2). **Check:** no README in `agents/`;
`docs/agents-capability-matrix.md` present.
### Finding (out of Step 15 scope — recorded, not actioned)
`plan.md` Steps 16, 17, 18 hard-code `agents/README.md` as an explicit `grep` search
path (plan.md:635, 727, 849). After F9's relocation that path no longer exists. The
explicit arg is **redundant** with the recursive `agents/` search those greps already
include, so dropping it (or repointing to `docs/agents-capability-matrix.md`) is
safe — but `plan.md` is outside Step 15's Files list (Hard Rule 2), so this is logged
for the operator/those steps rather than edited here. **Action for Steps 1618:** drop
the dangling `agents/README.md` arg from their Verify greps, or repoint to the new path.

View file

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