linkedin-studio/CLAUDE.md
Kjell Tore Guttormsen 657539ef09 feat(linkedin-studio): N12 — editions-register CLI + phaseLog-telemetri (TDD) [skip-docs]
Two things were structurally invisible: WHICH editions are in flight (readable
only by opening every series folder's edition-state.json by hand) and HOW LONG an
edition takes (not recorded anywhere). At two editions a week both are
load-bearing — a production line cannot be dimensioned on numbers never collected.

Register (scripts/editions, new module beside distillate.ts): one row per edition
in ${LINKEDIN_STUDIO_DATA}/editions/register.json — series, edition, title, series
path, current phase, next action, slot, startedAt/completedAt. Data-dir placement
(M0) because the register spans ALL series; the distillate is per-series and stays
in the series root. Verbs: register-upsert / register-list / register-complete.

phaseLog: articles.NN.phaseLog[{phase, completedAt}] in edition-state, additive so
schemaVersion stays 1 — pre-N12 editions load unchanged and their absence reads as
"not measured", never as zero. Per-article, mirroring articles.NN.phase: lead time
is a property of an edition.

One call per transition, both writes: newsletter.md gains a phase-transition
protocol defined once beside the resumption table, and all 16 canonical phases
invoke it. register-upsert appends the phase-log entry AND mirrors the register
row. Deliberately one command, not two — telemetry the command layer must remember
to write separately is incomplete inside a week, and an incomplete log measures
nothing. Step 10 closes the row and prints the measured lead time.

Mirror discipline: resumption still reads edition-state.json and only that. Delete
the register and the next transition rebuilds it; a failed upsert is reported, never
a reason to stop the pipeline. startedAt is the one unrecoverable value, so it never
moves — re-upserting a completed edition reactivates the same row (what
/linkedin:pivot does), keeping the clock on real elapsed production time.

Deterministic: no clock in the core (now passed in, --at/--now at the edge, as the
distillate takes lockedAt). Idempotent where a re-run is legitimate (repeated
transition logs once; completing twice keeps the first completedAt), not where it is
real work (a phase recurring after another one is logged again — a pivot back through
cleared gates is production time that happened). Missing facts are refused at the
edge, not defaulted: a row naming the wrong phase is worse than no row.

TDD (Iron Law): all three test files written first and verified red before any
implementation existed (register.test.ts + editionState.test.ts failed on missing
modules, cli-register.test.ts on "unknown command: register-upsert").

Suites: editions 27 -> 72 (floor raised) · test-runner 173 -> 184 (Section 16s: 11
unconditional greps incl. a 16-phase coverage sweep + non-vacuity self-test;
anti-erosion floor 155 -> 166) · trends 300/0 · brain 134/0 · specifics-bank 45/0 ·
hooks 140/0 · tests 35/0 · render 60/0. tsc --noEmit clean.

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

116 lines
13 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 16 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
- **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
- **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 |
| `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`.
**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 18-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 |
| `/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) |
| `/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