linkedin-studio/CLAUDE.md
Kjell Tore Guttormsen f0532dce3f feat(linkedin-studio): N14 — foldIns-fangst + Step 11 retro + background-headless + språkregel-akkumulering [skip-docs]
Lukker sløyfen pluginen manglet: en rettelse operatøren gjør i utgave N
håndheves i N+1 i stedet for å bli gjenoppdaget. Maskineriet fantes
(foldIns-skjema + promote→ratify siden fix #1); det som manglet var wiring —
ingenting skrev til køen og ingen fase tømte den.

- Fangst (A2-F7): Steps 2.5/3a/5.5/6.5 appender rettelsen ordrett med trigger,
  decision "pending". Ubetinget og bevisst dum — å avgjøre ved fangst om noe
  "fortjener" en regel er nettopp slik køen holder seg tom og sløyfen dør.
- Step 11 retro (A2-F8), ≤5 min ETTER scheduling: promoter køen med eksplisitt
  JA/NEI (mekanisk → atomisk contract-gate-promotering, teller kun med grønn
  --ratify; dømmekraft → operatørens språkregelfil; NEI → rejected, beholdes),
  effort-oppsummering fra MÅLT phaseLog (aldri re-estimert), og ÉN
  friksjons-spørring som besvares tilbake til operatøren. Faser 18 → 19;
  resumption-tabellen ruter scheduling → Step 11, retro → complete.
- articles.NN.retro: additiv-valgfri (default null), schemaVersion forblir 1.
- Background-headless (A2-F6): --background kjører pakken i en bakgrunnsagent
  som skriver rapporten til disk; drafting-sesjonen leser fila. Samme isolasjon
  som fersk sesjon, uten copy-paste-sømmen. Inline fan-out = eksplisitt fallback.
- Språkregler (C-10): ${DATA}/language-rules/<lang>.md (opt-in, template).
  Leses TO ganger — av language-reviewer (fanger) og av Step 4 (forebygger).
  Shippet banliste forblir baseline; brukerfila utvider.
- references/fold-in-loop.md (A2-F9): loopen dokumentert domene-generelt in-tree,
  så en adopter uten ekstern skrivekontrakt har hele sløyfen. Refs 28 → 29.

Suiter (alle grønne): test-runner 197 → 217 (Section 16u: 18 ubetingede greps +
non-vacuity self-test; fase-sveip 16 → 17 faser; floor 179 → 198) · hooks 174 ·
trends 300 · brain 134 · editions 72 · specifics-bank 45 · contract-gate 33 ·
tests 35 · render 60.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QxvWAjte7vPcF79QeSRvRJ
2026-07-25 12:39:16 +02:00

118 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# LinkedIn Studio Plugin (v0.7.0)
Full-spectrum LinkedIn content engine — short-form feed posts, carousels, video scripts, and long-form newsletter editions — with the 2026 relevance-ranking model baked in. Maturity v0.7.0: M0 (per-user data dir `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/`, idempotent session-start migration — see `references/data-path-convention.md`) complete; the 29 pre-0.7.0 command surfaces all through the interactive quality-gate (`docs/hardening/log.md`) AND through independent cold-review (29/29, R2aR5 — `docs/hardening/review*.md`); `/linkedin:trends` (shipped in 0.7.0) is not yet through the hardening gate. Remaining for v1.0.0: a GUI. Version history → `CHANGELOG.md`.
## Architecture
- **State file:** `~/.claude/linkedin-studio.local.md` (YAML frontmatter, auto-initialized from `config/state-file.template.md`)
- **State updater:** `hooks/scripts/state-updater.mjs` — deterministic state mutations (post tracking, streak, content history pruning). Pure functions, tested, no AI involvement
- **Clipboard helper:** `hooks/scripts/clipboard-helper.mjs` — cross-platform clipboard (macOS `pbcopy`, Linux `xclip`/`xsel`, WSL `clip.exe`). All content commands auto-copy
- **iCal generator:** `hooks/scripts/ical-generator.mjs` — RFC 5545 calendar files for batch scheduling. Standalone CLI + importable module
- **Figure renderer:** `render/build-figur.mjs` — coded data figures (SVG/HTML → PNG via headless Chrome); three targets (article/carousel/single); brand tokens from the user data dir's `profile/brand-tokens.json`, neutral defaults otherwise (see `references/figure-design-guidelines.md`). Standalone CLI + importable module
- **Post queue:** `assets/drafts/queue.json` (managed by `hooks/scripts/queue-manager.mjs`)
- **Analytics:** CLI `scripts/analytics/` (TypeScript, needs `tsx` + `npm install`); data `assets/analytics/` (gitignored)
- **Specifics-bank:** `scripts/specifics-bank/` (TypeScript, needs `tsx` + `npm install`) — deterministic, topic-tagged store of the operator's lived specifics (real numbers, named cases, held opinions) in the per-user data dir; elicited/bound by `/linkedin:newsletter` Step 1.5 so drafts draw from real inventory, never invented filler. Consumption is logged back at Step 8 lock (`record-usage``usedIn`), so cross-edition re-use of the same material is visible rather than silently encouraged by the dedupe
- **Series distillate:** `scripts/editions/` (TypeScript, needs `tsx` + `npm install`) — series-level memory: each locked edition's spent narrative units (anecdotes/arguments/hooks) in `<serie>/linkedin/series-distillate.json`; written at `/linkedin:newsletter` Step 8 (`distil-append`), checked against the next skeleton at Step 2.5 (`distil-check`, advisory). Deterministic character-trigram similarity — AI extracts, code compares
- **Editions register:** `scripts/editions/` (same package) — one row per edition in production in the per-user data dir (`editions/register.json`): series, edition, path, current phase, next action, slot, `startedAt`/`completedAt`. Each of the 17 canonical `/linkedin:newsletter` phase transitions runs `register-upsert --edition-state <path>`, which appends `articles.NN.phaseLog` (lead-time telemetry, additive — schemaVersion stays 1) **and** mirrors the row in one call; Step 10 runs `register-complete`. `register-list` is the work-in-progress view. The register is a **mirror** — deterministic resumption reads `edition-state.json` only, and a lost register is rebuilt by the next transition
- **Publishing slots:** `hooks/scripts/slots.mjs` — the weekly production grid. Slots are **operator config** in the per-user data dir (`profile/publishing-slots.json`, schema + opt-in template in `config/publishing-slots.template.json`); absent config ⇒ every slot surface stays silent, and the plugin hardcodes no publishing times. Coverage of a slot counts the short-form queue (`scheduled`/`published`) **and** editions-register rows claiming that date, so long-form and short-form cannot be double-booked. Zero-dep (SessionStart may not spawn tsx) and imported identically by the hook and by the commands (`queue-manager.mjs` pattern), so session-start's vacancy warning and `/linkedin:newsletter` Step 10's slot default are the same computation
- **Contract-gate:** `scripts/contract-gate/` (TypeScript, needs `tsx` + `npm install`) — deterministic §B/§C1 rule-gate on the full draft (`/linkedin:newsletter` Step 4.5, before the AI sweeps); ratifies `rules.ts` against the edition's §E-manifest, then gates with BLOCK/WARN
- **Fold-in loop:** capture → classify → promote → enforce, documented domain-generally in `references/fold-in-loop.md`. The four `/linkedin:newsletter` steps where operator judgment enters the text (2.5, 3a, 5.5, 6.5) append corrections to `articles.NN.foldIns[]` (`decision: "pending"`); **Step 11 (retro)** empties the queue with an explicit operator JA/NEI — mechanical → an atomic contract-gate promotion (only counts when `--ratify` is green), judgment → the operator's opt-in language-rules file (`${DATA}/language-rules/<lang>.md`, template `config/language-rules.template.md`), which Step 4 reads while drafting (prevent) and `language-reviewer` reads at Step 6.5 (catch). Rejected rows are kept, never deleted. Step 11 also reports effort from the **measured** `phaseLog` and asks one friction question whose answer goes back to the operator — the plugin never writes their register
- **Analytics metrics (S16):** parsed CSV columns + an optional, manually-entered `saves` count (count-only in native LinkedIn analytics since ~Sept 2025, no CSV export; the Marketing API exposes `POST_SAVE` on `/memberCreatorPostAnalytics` from v202604, but access is partner-gated — so manual entry remains the right UX). `parseOptionalCount()`: blank / non-numeric / negative → `undefined` (`unknown`, never 0), a genuine `0` is kept; saves surfaced per-post + as `totalSaves`, but **not** folded into `engagementRate`. `dwell` stays **explicitly unmeasurable** (internal to LinkedIn, no export/API). All analytics I/O routes through the `getAnalyticsRoot()` seam (M0 per-user data-dir).
## Hooks
9 hooks across 7 events. All Node.js (.mjs). PreToolUse/PostToolUse hooks use parameterized `content-gatekeeper.mjs` with `isLinkedInContent()` check.
| Event | Purpose |
|-------|---------|
| `SessionStart` | Load state, REMEMBER.md, milestone tracker, editions in flight + publishing-slot vacancy |
| `PreToolUse` (Write\|Edit) | Content quality gate, voice guardian, topic rotation gate |
| `Stop` | State update, pre-publish reminders, content history |
| `UserPromptSubmit` | LinkedIn context enrichment (three-tier matching) |
| `PostToolUse` (Write) | Post-creation automation (5x5x5, posting time) |
| `PreCompact` | Preserve LinkedIn context during compaction |
| `Notification` | Posting reminders (rate-limited 30min) |
**Session markers:** `/tmp/linkedin-hooks/session-active` (Stop hook gating, 12h staleness). **Shared modules:** `linkedin-content-filter.mjs`, `queue-manager.mjs`, `personalization-score.mjs`, `state-updater.mjs`, `clipboard-helper.mjs`, `slots.mjs`.
**State updates:** post tracking, streak, and content history are handled deterministically by `state-updater.mjs` (Stop hook + commands); entries older than 90 days are auto-pruned at session start.
**Hook editing:** edit `hooks/hooks.template.json` + `hooks/prompts/*.md`, then run `python3 hooks/scripts/compile-hooks.py`. Do not edit `hooks.json` directly. Prompts load at runtime via gatekeeper scripts; the compile step is only needed when adding `type: prompt` hooks.
## Commands (30)
All content commands auto-copy output to clipboard via `clipboard-helper.mjs`; interactive steps are minimized (angle/format/post-type inferred from context, max 2 questions per post). Organized into five journeys (Start · Create · Engage · Measure · Grow): `create`/`measure` are guided creation/analytics front-doors, `onboarding`/`strategy` the Start/Grow front-doors, and the 28 atomic commands are the execution tier.
| Command | Purpose |
|---------|---------|
| `/linkedin` | Router — status line + five-journey command menu |
| `/linkedin:create` | Create front-door — routes to the command owning the format (post/quick/react/carousel/video/multiplatform/batch/newsletter); delegates only |
| `/linkedin:measure` | Measure front-door — routes to the analytics command (import/report/analyze/audit/ab-test); delegates only |
| `/linkedin:onboarding` | Multi-step onboarding wizard (profile → setup → first-post); Start-journey front-door |
| `/linkedin:first-post` | First-post accelerator (10 min) |
| `/linkedin:setup` | Guided personalization setup |
| `/linkedin:react` | URL-to-post pipeline |
| `/linkedin:post` | Full post creation (10-15 min) |
| `/linkedin:quick` | 5-minute quick post (3-line formula) + 8 post-type templates |
| `/linkedin:pipeline` | Full end-to-end content pipeline |
| `/linkedin:trends` | Trend discovery pass — delegates to `trend-spotter` (default long-form mode), persists to trend store + dated morning brief, triage per id (act/skip) |
| `/linkedin:newsletter` | Long-form orchestrator (newsletter / essay / series article) — multi-session 19-phase pipeline with gates before prose (skeleton + spine), fact-check, editorial-review (Step 5.5), persona sweep, headless adversarial review (Step 6.5), and visual-assets — all before lock; closes with the Step 11 retro that promotes this edition's corrections so the next one starts with them enforced |
| `/linkedin:headless-review` | Cold adversarial review package — 3 headless archetypes (`content-reviewer`, `language-reviewer`, `fact-reviewer`) + `persona-reviewer` on a FROZEN draft, no drafting-session context; operator-gated report (Step 6.5 standalone surface). `--background` runs the package in a background agent that writes the report to disk — fresh-session isolation without the paste-back seam |
| `/linkedin:pivot` | Re-open a long-form edition after a late substantive change so cleared gates re-run before lock (pivot heuristic: >20% word-count or >2 new sections) |
| `/linkedin:batch` | Create a full week of content |
| `/linkedin:calendar` | View/manage post scheduling queue + publish action |
| `/linkedin:firsthour` | Post-publish first-hour / reply-loop sprint — delegates to `engagement-coach`, persists the plan (`recordFirstHourPlan`), hands off to `post-feedback-monitor` |
| `/linkedin:carousel` | Structured multi-slide carousel generator |
| `/linkedin:video` | Video script generator (30s-2min) |
| `/linkedin:multiplatform` | Adapt content for other platforms (long-form → `/linkedin:newsletter`) |
| `/linkedin:analyze` | Content/performance analysis |
| `/linkedin:audit` | Periodic content strategy audit |
| `/linkedin:import` | Import CSV export → structured JSON (delegates analysis to `/linkedin:report`) |
| `/linkedin:report` | Generate weekly performance report |
| `/linkedin:ab-test` | Design and manage A/B content tests |
| `/linkedin:strategy` | Growth strategy + authority building; Grow-journey front-door |
| `/linkedin:competitive` | Competitive analysis of niche |
| `/linkedin:monetize` | Monetization strategy and funnels (unlocks at ~1K followers) |
| `/linkedin:outreach` | Outreach orchestrator — collaborations + speaking (unlocks at ~1K followers) |
| `/linkedin:profile` | Profile/topic-relevance optimization |
## Agents (20)
| Agent | Model | Color | Responsibility |
|-------|-------|-------|----------------|
| `content-optimizer` | Sonnet | Blue | Optimize existing posts |
| `strategy-advisor` | Sonnet | Green | Growth recommendations |
| `analytics-interpreter` | Sonnet | Yellow | Audience pattern analysis + weekly/monthly performance reports (interpret/report modes) |
| `engagement-coach` | Sonnet | Magenta | 5x5x5 + first-hour tactics + CEA commenting + target selection |
| `content-planner` | Sonnet | Cyan | Content audit + weekly/monthly plans |
| `network-builder` | Sonnet | Teal | Strategic networking + outreach |
| `content-repurposer` | Sonnet | Purple | Format conversion + evergreen refresh |
| `trend-spotter` | (inherits session) | White | Trending topics + opportunity scores |
| `demand-spotter` | (inherits session) | Silver | Demand-sweep «innenfra og ut» (N7.5): reader's verbatim questions (Tier-5 sources) → pain-point map → vocabulary translation → §4 arc map with honest market verdict (supply-gap/demand-gap/saturated) |
| `voice-trainer` | Sonnet | Pink | Voice profile building + drift detection |
| `differentiation-checker` | Sonnet | Gray | Originality scoring + commodity detection |
| `post-feedback-monitor` | Opus | Lime | Post-publish 48h monitoring |
| `video-scripter` | Sonnet | Violet | Video script creation with pacing |
| `fact-checker` | Opus | Brown | Factual-claim verification against primary sources + post-cutoff web-search mandate (longform) |
| `editorial-reviewer` | Opus | Orange | Editor's craft gate (Step 5.5, before persona sweep): prosa-håndverk + narrativ-arkitektur, ≤10 flags BLOCK/REWORK/NICE, operator-gated via `SendUserFile` (longform) |
| `persona-reviewer` | Opus | Olive | Reader-persona gate — skeleton (before prose) + resonance (before lock) + hook-conversion (after lock), blocking hard-fail list (longform) |
| `voice-scrubber` | Opus | Red | De-AI scrub + Norwegian-chronicle voice-drift correction (longform) |
| `content-reviewer` | Opus | Maroon | Cold/headless argument-integrity review on a frozen draft (Step 6.5): logical holes, unsupported assumptions, contradiction, missing concretization, unanswered objection; ≤8 flags (longform) |
| `language-reviewer` | Opus | Navy | Cold/headless Norwegian-language review on a frozen draft (Step 6.5): repetition, anglicisms, stiff register, self-contradiction, rhythm; ≤10 flags (longform) |
| `fact-reviewer` | Opus | Gold | Cold/headless re-verification on the frozen/pivoted version (Step 6.5, web search): verifiable claims, quote precision, number attribution, source quality + pivot-risk (longform) |
**Rule:** Always read `assets/voice-samples/` before generating content.
**Invocation form:** commands invoke plugin agents by their **namespaced** type — `subagent_type: linkedin-studio:<name>`, never the bare `<name>` (a bare type does not resolve and the `Task` call fails).
**Reload requirement:** a NEW agent file under `agents/` registers only after a Claude Code **session reload** (the plugin agent set is built at session start). Reload before invoking a freshly-added agent.
## Content Quality Rules
1. Hook: 110-140 characters (mobile cutoff)
2. Post length: 1,200-1,800 chars (standard), 150-500 chars (quick)
3. No external links in post body (correlate with lower reach; see `references/algorithm-signals-reference.md`)
4. No corporate buzzwords: leverage, synergy, paradigm shift, thought leader, disruptive, value proposition, ecosystem, holistic approach
5. Topic must align with user's 5 core expertise areas (topic-relevance signal)
6. Topic rotation: no back-to-back same pillar, no pillar >50% in 14 days (warn-only)
7. Progressive onboarding: personalization score hidden until 3+ posts; voice guardian suppressed until 5+ voice samples