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

15 KiB
Raw Blame History

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-usageusedIn), 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