Reach-splitten (in/out-of-network) er native i LinkedIns post-analytics siden juni 2026, men vises som PROSENT og finnes ikke i CSV-eksporten. Planen antok to manuelle antall; verifiseringen viste prosent, så modellen er ett felt — outOfNetworkPct — og in-network er komplementet. - parseOptionalPercent: egen parser, ikke parseOptionalCount. Komma er desimal (36,5 -> 36.5, aldri 365), og verdi >100 avvises: i én kolonne kan ikke et absolutt antall skilles fra en andel, så svaret er unknown, ikke en gjetning. Blank/ikke-numerisk/negativ -> unknown; ekte 0 beholdes. - Ett lagret halvpart, kryssjekket: In-network godtas og lagres som komplement; et transkribert par som ikke summerer til ~100 (±1 avrunding) forkastes som unknown i stedet for å bli halvveis trodd. - weightedOutOfNetworkPct: impressions-vektet roll-up (avgOutOfNetworkPct, uke + måned). Flatt snitt lar en 50-visnings-post slå en på 10 000; poster uten avlesning ekskluderes, og null vekt gir undefined — aldri 0, aldri NaN. - Reach inngår ALDRI i engagementRate (distribusjon != engasjement). Rapporten leser den som akvisisjon (ut) vs resonans (inn), og sier «ikke ført for denne perioden» framfor å estimere. En reach-innsikt går inn i N15s do-next-kanal. - Step 7c (A2-F11): rapporten tilbyr diff mot brukerens engagement-patterns.md med eksplisitt go — aldri stille skriving, aldri inn i den shippede malen. - Boundary-map (E#9): dwell eksplisitt umålbar, saves partner-gated, reach native men CSV-eksport uverifisert. - Reach-frie importer er byte-identiske med før, på skjerm og på disk. TDD: rødt bevist først (10 feilende), analytics 119 -> 144 tester, tsc ren. test-runner 232 -> 247 (Section 16w, gulv 213 -> 228). Alle suiter grønne. CHANGELOG: N15-oppføringen manglet og er backfilt sammen med N16. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QxvWAjte7vPcF79QeSRvRJ
86 KiB
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
Added — demand-sweep «innenfra og ut» (N7.5 / MR-F9)
demand-spotteragent — the demand-side twin oftrend-spotter. Discovery finds what happened; the sweep translates it into the reader's problem via three passes after discovery, before drafting: demand-sweep (verbatim questions from where readers ask) → pain-point map → vocabulary translation (the reader's words, not the field's jargon). Inherits the session model; agents 19 → 20.- Tier 5 demand sources — a new tier in the source-list convention (
config/trends-sources.template.md): practitioner forums, GitHub issues on spec repos, procurement data, regulator guidance, channel comments — the inverse of the Tier 1–4 supply sources. Known blind spots stated honestly (YouTube JS-rendered → API; Reddit approximate; HN/GitHub = ground truth; an honest null is a finding). demandsignal on the trend record — the rankable twin of the verbatimsaturationtext:strength(etterspørsel: strong/moderate/thin) +answered(is it already answered). Additive-optional; store schema stays v4 (no migration). Absent ⇒ unmeasured (the honest null the sweep may return instead of inventing demand).- Arc (åre) output contract §4 —
scripts/trends/src/arc.ts: groups demand-swept records into veins (relatedIds transitive closure), ranks each vein's questions onetterspørsel × kan-svare × ikke-besvart, and classifies an honest market verdict distinguishing a supply-gap (many ask, none answer — write here) from a demand-gap (few ask, but the sender can answer — high value, low audience) from saturated (skip). Pure + deterministic. arcsCLI verb +/linkedin:trends --demandmode — render the §4 arc map (node --import tsx src/cli.ts arcs), reachable via the command's new demand mode (delegates todemand-spotter, keeps commands at 30). The morning brief also shows the per-candidate demand signal alongside the existing reader fields.- Trends suite 276 → 300; test-runner 139 → 140;
tsc --noEmitclean.
Added — voice chain: bind voice BEFORE prose (N8 / C-1, C-2, C-3, A2-F1, A2-F2)
chronicle-voice-profileartifact (C-1/A2-F1) —voice-trainernow distils a dedicated long-form voice profile (${DATA}/profile/chronicle-voice-profile.md) from the published gold corpus only (ingest/published/,provenance=published), one section per(language, format)pair present in the corpus — never from the English short-formauthentic-voice-samples.mdthe engine itself calls degrading for chronicle voice, never fromprovenance=ai-draft./linkedin:newsletterbinds long-form drafting (Step 0 / 3a / 3b) to THAT profile as the primary reference, with an explicit, never-silent fallback to the short-form corpus when it is absent.- Drift log is READ, not just written (C-2) — Step 3a gains a Pass 0: read
chronicle-voice-drift-log.mdas a negative list of the recurring AI-tells/driftsvoice-scrubberlogged on prior editions, so drafting avoids them BEFORE the scrub (the log was append-only, never consumed); Step 3b re-reads it during expansion. - Review language resolved + persisted at Step 1 (C-3) — the calibration now resolves the review
language(detect/ask) and persists the top-leveledition-state.languageat the Step 1.5 checkpoint, making the template's "Resolved at Step 1" doc-string true. Previously the default"en"was never set, solanguage-reviewer's Norwegian checks andvoice-scrubber's gold standard silently never fired even for a Norwegian edition; Step 6.5 already consumed the field, so resolving it closes the axis end-to-end. - Auto-gull — the gold corpus grows on its own (A2-F2) — Step 8 lock now appends the locked edition to
ingest/published/taggedprovenance=published, so the corpus the chronicle profile is distilled from grows one edition at a time (the author's own locked editions, never the engine's drafts). No manual curation. - Domain-general throughout: languages/formats come from the user's own corpus, never hardcoded. test-runner 140 → 146 (Section 16o: 6 unconditional voice-chain greps + self-test; anti-erosion floor 123 → 129).
Added — randsone gates: gate the feed-facing surfaces (N9 / C-4, C-6, C-7, C-9)
- Randsone fact + language gate (C-4/C-9) —
/linkedin:newsletterStep 9 (post-lock) now runs a scopedfact-reviewer(F1 verifiable claims + F3 number attribution) +language-reviewer(L1 repetition + L2 anglicisms/register) pass over the four feed-facing surfaces the reader actually meets — the delingstekst hook, the samle post, the carousel slide text, and the edition title — before the hook-conversion binary. Until now only the hook's click-worth was judged; its truth and language, the samle post, the carousel slides and the title reached the feed ungated. Placed at Step 9 because that is the first phase where all four surfaces exist (carousel is authored in Step 7.5; delingstekst/samle from Step 8), and Step 9 already operates on post-lock distribution copy — so the gate revises distribution surfaces + re-renders, never the locked body; a defect tracing into the locked body/canonical title is a pivot. - Edition title-strength gate (C-9) — the edition's own title now faces a strength gate at Step 9 (binding on the distribution title, advisory on the locked canonical title), not only the hook's two lines.
- Deterministic post-factcheck fact-recheck (C-6) — a new invariant after Step 5: any later fold-in (5.5/6/6.5/7) whose diff touches a sentence with numbers, names, or quotes fires a mandatory scoped
fact-reviewermini-recheck of exactly those sentences — a deterministic trigger, not a judgment call, replacing the old "re-run if it felt substantive" discretion and closing the pivot-heuristic blind spot (a swapped statistic under the +20 % threshold never trips/linkedin:pivot, but does trip this). - WebFetch on both fact agents (C-7) —
fact-checkerandfact-reviewergainWebFetch: quote precision (F2) and exact-figure attribution (F3) require the verbatim source text in context, not a search snippet. - test-runner 146 → 154 (Section 16p: 8 unconditional randsone-gate greps + self-test; anti-erosion floor 129 → 137). Domain-general; no KTG-specific values hardcoded.
Added — series-level protection: make the retread visible before prose (N11 / C-5)
scripts/editions/— series distillate (new package) — each locked edition leaves behind the narrative units it spent (anecdotes told, arguments argued, hooks used) in<serie>/linkedin/series-distillate.json, besideedition-state.jsonin the series root — series-scoped state where the series' other state already lives, so it travels with the series and needs no slug→path map.distil-append(Step 8 lock) folds an edition in; a re-lock after/linkedin:pivotreplaces its entry rather than duplicating it.- Step 2.5 destillat-sjekk — the check runs BEFORE prose — the skeleton gate now compares the proposed skeleton against everything the series has published and folds any finding into the annotation page the operator already reads, while changing course is still cheap. Advisory, never blocking (exit 0 either way): a deliberate callback is a legitimate move — an unnoticed retread is not. Until now every one of the long-form gate agents saw exactly one edition, so "the reader has heard this before" was structurally invisible; at series cadence it is the fastest-growing defect class.
- Deterministic comparison, AI extraction — the units are distilled from locked prose by the command layer, but the store, the similarity and the verdict are plain code: character-trigram Jaccard, chosen because the plugin is language-general and inflection (migrere / migreringen) breaks word-token overlap. Calibrated against real paraphrase pairs — retellings 0.44–0.55, unrelated 0.04–0.06, same-topic-different-story 0.24 — so the default threshold sits in the gap at 0.40 (word-Jaccard scored a shortened hook paraphrase at 0.11 and would have let it through). Units are compared within their kind; an argument re-used as a hook is a new move.
- Specifics-bank usage log (
usedIn) — the material grain of the same defect:recordUsage+ therecord-usageCLI verb stamp «used in edition NN» on the specifics an edition actually consumed (read from the bound slot-map viaboundSpecificIds, soabstrakt/eksternslots stamp nothing). Recorded at lock, so it means published rather than merely considered, and idempotent under a pivot re-lock. The bank's dedupe deliberately encourages re-surfacing the same material; this log is what makes that re-use visible at the next Step 1.5 instead of silent. Additive-optional — schema stays v1, existing banks load unchanged. - Suites: new
editions27/0; specifics-bank 28 → 45; test-runner 163 → 173 (Section 16r: 9 unconditional greps + non-vacuity self-test; anti-erosion floor 146 → 155).tsc --noEmitclean in both packages. Domain-general throughout.
Added — editions register + phase telemetry: make the production line measurable (N12 / A1-11, A1-12)
- Editions register (
scripts/editions, new module) — one row per edition in production in${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/editions/register.json: series, edition, title, series path, current phase, the one next action, and slot.register-listanswers "what is in flight, and where does each edition stand?" without opening a single series folder — until now that answer required opening everyedition-state.jsonby hand, which is why nobody had it. Data-dir placement (M0), because the register spans all series (the distillate is per-series and stays in the series root). articles.NN.phaseLog— lead time becomes a measured number — every phase transition appends{phase, completedAt}to the edition-state. Additive-optional (default[]), so schemaVersion stays 1 and pre-N12 editions load unchanged; their absence reads as "not measured", never as zero. Per-article rather than top-level, mirroringarticles.NN.phase: lead time is a property of an edition.- One call per transition, both writes —
/linkedin:newslettergains a phase-transition protocol defined once next to the resumption table, and each of the 16 canonical phases invokes it:register-upsert --edition-state <path>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 (register-complete) and prints the measured lead time. - The register is a MIRROR, never a source of truth — deterministic resumption still reads
edition-state.jsonand only that. Delete the register and the next transition rebuilds the row; a failedregister-upsertis reported plainly and never stops the pipeline.startedAtis the one value that cannot be recovered, so it never moves: re-upserting a completed edition reactivates the same row (what/linkedin:pivotdoes), keeping the clock on real elapsed production time. - Deterministic, no clock in the core — every mutation takes
nowas an argument (--at/--nowat the CLI edge), as the distillate takeslockedAt. Idempotent where a re-run is legitimate (a repeated transition logs once; completing twice keeps the firstcompletedAt), 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 rather than defaulted: a row naming the wrong phase is worse than no row. - Suites: editions 27 → 72; test-runner 173 → 184 (Section 16s: 11 unconditional greps incl. a 16-phase coverage sweep + non-vacuity self-test; anti-erosion floor 155 → 166).
tsc --noEmitclean. Domain-general throughout.
Added — publishing slots + vacancy warning + WIP roll-up (N13 / A1-10, D-5)
- Publishing slots are operator config (
profile/publishing-slots.json) — a recurring weekly grid ({day, time, label}) in the per-user data dir, with a schema + opt-in template atconfig/publishing-slots.template.json. The plugin writes it for nobody and ships no publishing times: while the file is absent every slot surface stays silent, exactly like the trend and brain nudges. It lives in the data dir rather than the state file because the state file is machine-written (rollover, pruning, post tracking) and its frontmatter reader does not parse lists — user config does not belong in a file the plugin rewrites. - Vacancy at session start — the SessionStart hook now names the next publishing slot nothing covers, how many are open in the next 14 days, and — when the gap is ≤3 days — how to fill it. Coverage counts both the short-form queue (status
scheduled/published; a cancelled entry frees the slot again) and editions-register rows whose slot claims that date, so long-form and short-form can never be booked on top of each other. A day carrying two slots needs two posts to be full. - Work in progress is surfaced, not searched for —
## Editions in Flightat session start, plus the same roll-up in the/linkedinrouter and/linkedin:calendar: series, edition, current phase, the one next action, claimed slot, and days in flight. The N12 register made this measurable; N13 makes it visible without asking. hooks/scripts/slots.mjs— one implementation, four surfaces — zero-dep (a SessionStart hook must not spawn tsx), pure core (dates in as strings, no clock), and imported by the commands the same wayqueue-manager.mjsalready is. The hook's vacancy warning and the slot/linkedin:newsletterdefaults to therefore cannot disagree./linkedin:newsletterStep 10 defaults to the next open slot — scheduling is one confirmation instead of an interview, and the transition now writes--slotonto the register row so a scheduled edition stops reading as an open slot. Unconfigured grid or a full horizon ⇒ the old ask, unchanged.- D-5: the shipped schedule is marked for what it is —
references/scheduling-strategy.mdnow carries the same epistemic standard as the algorithm reference (confidence: low / directional, practitioner heuristic, no primary source) and defers to the operator's own grid, tested against their own analytics. The tables are a starting point, not a finding about anyone's audience. - Suites: hooks 140 → 174 (25 slot-core + 9 session-start tests); test-runner 184 → 197 (Section 16t: 13 unconditional greps incl. a de-niche check that no publishing time is hardcoded in the slot path and no operator grid is committed, + non-vacuity self-test; anti-erosion floor 166 → 179).
Added — the fold-in loop: a correction made once is enforced from then on (N14 / A2-F6, A2-F7, A2-F8, A2-F9, C-10)
- Capture is finally wired (A2-F7) — the
articles.NN.foldIns[]queue and the promote→ratify machinery have shipped since fix #1, but nothing ever wrote to the queue and no step ever emptied it. All four steps where an operator's judgment enters the text — skeleton annotations (2.5), spine annotations (3a), editorial flags (5.5), cold-review flags (6.5) — now append the correction near-verbatim with its trigger,decision: "pending". Capture is unconditional and deliberately dumb: deciding at capture time whether a correction "deserves" a rule is exactly how the queue stays empty and the loop dies. Edition-specific fixes (a wrong number) are explicitly not rules. - New Step 11 — retro (A2-F8), ≤5 min, AFTER scheduling — three fixed jobs, one operator pass. (1) Promote: every pending fold-in gets an explicit JA/NEI; mechanical → an atomic
scripts/contract-gatepromotion (rule + contract row + manifest row) that only counts when--ratifyis green, judgment → the operator's language-rules file; NEI marks the rowrejectedand keeps it. (2) Effort summary: read straight from the measuredarticles.NN.phaseLog(N12) — transitions, recurred phases (where the rounds actually went), lead time — never re-estimated, and "not measured" when the log is absent. (3) One process-friction question, answered back to the operator: the plugin never writes into the operator's own notes or register. It runs after delivery so nothing about shipping waits on it, and it is the last thing the edition does: the pipeline is not complete until the loop is closed. Phases 18 → 19; the resumption table routesscheduling→ Step 11 andretro→ complete, so an aborted session cannot drop the queue. articles.NN.retroin edition-state — additive-optional (defaultnull), so schemaVersion stays 1 and pre-N14 editions load unchanged.- Background headless review (A2-F6) —
/linkedin:headless-review --backgroundruns the cold package in a background agent with its own context window that writes the consolidated report to disk; the drafting session then reads the file. Maximum independence used to cost a manual session switch plus pasting the report back — the seam is gone, and the isolation is identical (the background agent gets only the cold-contract inputs). The inline fan-out remains the explicit fallback; the isolation is never silently downgraded. - Language rules accumulate in a user file (C-10) —
${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/language-rules/<lang>.md(opt-in, templateconfig/language-rules.template.md) holds the judgment-class corrections Step 11 promoted. It is read twice: bylanguage-reviewerat Step 6.5 (catch) and by Step 4 while the draft is still being cleaned (prevent) — a rule that only ever catches is worth half a rule. The shipped ban-list (longform-quality-rules.mdrule 3) stays the baseline and is never edited by the loop; the user file extends it, and its Deliberate exceptions section is honoured. Absent file ⇒ baseline alone, silently. references/fold-in-loop.md(A2-F9) — the capture → classify → promote → enforce loop documented domain-generally and in-tree, so an adopter with no external writing contract has the whole loop (that absence "is not a gap" — the same mirror ruleeditorial-revieweruses). Names the other four accumulation silos (specifics-bank, brain, voice drift log, A/B learnings) that Step 11 surfaces together, because a retro that empties one queue teaches the operator the others are empty too. references 28 → 29.- Suites: test-runner 197 → 217 (Section 16u: 18 unconditional greps + a non-vacuity self-test on the capture predicate; the phase-coverage sweep now spans 17 phases; anti-erosion floor 179 → 198). All nine suites green.
Added — the do-next contract: a measurement changes the next piece (N15 / A2-F10, A2-F12, A2-F13, A2-F14)
recordDoNext()instate-updater.mjs— one channel from measurement to creation./linkedin:report,/linkedin:analyze, an/linkedin:ab-testAdopt verdict and thepost-feedback-monitor's 48h learning each persist 2–3 directives (imperative + evidence pointer, tagged with their source) to## Do-Next Directivesin the state file;post/quick/batch/createStep 0 andnewsletterStep 1 read them before drafting. Before this every analytics surface ended in chat (grep do.nextacross the repo returned 0) and the next drafting session — a fresh context — never saw any of it.- Lifetime is replace-by-source + a 60-day age floor — each writer supersedes only its own rows, so a new weekly report never evicts an A/B Adopt decision, and the section stays a live steering signal instead of a growing backlog. Deterministic, no AI in the write;
--record-do-nextexposes it as a CLI verb. - Previous-edition calibration (A2-F14) —
newsletterStep 1 matches the previous edition to its measured numbers through the queue id (drafts/queue.json→analytics/posts/*.jsonby publish date, disambiguated on the hook preview), with an explicit honest-miss branch: an unmeasured edition is stated as unmeasured, never estimated. - test-runner 213 → 232 (Section 16v: 15 unconditional do-next greps + self-test; anti-erosion floor 198 → 213). Two real bugs found by the tests themselves: a section regex missing
/m(old rows were never superseded or pruned) and fixtures dated 7 months before their owntoday, which had made the replace-by-source test falsely green.
Added — measure-truth: reach split + honest boundary map (N16 / D-3, A2-F11, E#9)
- Out-of-network reach as an optional manual column (D-3) — LinkedIn split a post's impressions into in-network and out-of-network in June 2026 (post analytics → Discovery, under the impressions count): the first native number that says whether a post acquired audience or only resonated with the audience it already had. It is shown as a percentage split and is absent from the CSV export (whether it will ever be exported is unverified), so it follows the saves pattern — add an
Out-of-network(orIn-network) column with the percentage read off that panel and/linkedin:importingests it. - A separate percent parser, deliberately not the count parser —
parseOptionalPercent()reads a comma as a decimal mark (36,5→ 36.5;parseOptionalCount's US-thousands rule would have said 365) and refuses any value above 100: in one column an absolute impression count and a share are indistinguishable, so the honest answer is unknown rather than a guess. Blank, non-numeric and negative stay unknown; a genuine0is kept (nothing left the network). - One stored half, cross-checked — only
outOfNetworkPctis stored, because the two halves describe one split and keeping both would let a record contradict itself. AnIn-networkcell is stored as its complement, and a transcribed pair that does not sum to ~100 (one point of rounding slack, since the UI rounds each half independently) is discarded as unknown instead of half-trusted. - Impressions-weighted roll-ups —
weightedOutOfNetworkPct()feeds an optionalavgOutOfNetworkPcton the weekly and monthly summaries. Weighted, because a flat mean lets a 50-impression post at 90 % outvote a 10,000-impression one at 20 %; posts without a reading are excluded rather than folded in as 0; zero total weight yieldsundefined, never 0 or NaN. Reach-free imports stay byte-identical to pre-N16 output, on screen and on disk. - Not part of the engagement rate — reach is distribution, not engagement, so it never enters
engagementRate(which stays comparable to every historical import)./linkedin:reportrenders the split as acquisition (out) vs resonance (in), states coverage honestly, and says "not entered for this period" rather than estimating an unentered split. A reach reading routes into the N15 do-next channel so it shapes the next post instead of being admired in the report. - Operator-gated baseline updates (A2-F11) — new
/linkedin:reportStep 7c: the report now offers a diff against the user'saudience-insights/engagement-patterns.mdbaseline (the fileanalytics-interpreter,strategy-advisorand/linkedin:audittreat as tracked truth, and which nothing in the pipeline used to write, so it went stale silently). Old → new with the evidence, an explicit go viaAskUserQuestion, changes labelled provisional when they rest on 1–2 posts, an append to the section's update log — and never a silent write, never into the plugin's shipped template. - Boundary map brought current (E#9) — the README boundaries now state all three honestly: dwell explicitly unmeasurable (internal to LinkedIn, no count to transcribe, no API), saves partner-gated (
POST_SAVEon/memberCreatorPostAnalyticsfrom v202604), and reach native-but-not-exported with export status unverified. - analytics suite 119 → 144 (25 new tests: percent contract, either-half resolution, split cross-check, weighted roll-up, backward-compat); test-runner 232 → 247 (Section 16w: 15 unconditional greps + self-test; anti-erosion floor 213 → 228);
tscclean.
Built feedback (awaiting consumer-side proof)
- MR-F9 (demand-sweep «innenfra og ut») — built; the (a)/(b)/(c) evidence gate is a runtime demonstration, proven consumer-side (plugin agents don't resolve in the dev repo).
[0.7.0] - 2026-07-23
Trend engine → production bridge. The discovery pass becomes a first-class command, gains a reader-side proposal + actionability layer, and is wired directly into the long-form pipeline: a trend now flows id-traceably from discovery to a scheduled edition, and a candidate with no formulable reader-grip can no longer inflate the top band.
Added — trend discovery command (N5)
/linkedin:trends— trend-discovery pass: delegates totrend-spotter(default long-form mode), persists to the trend store + a dated morning brief, triages per candidate (act/skip). Commands 29 → 30. (a8e3cac)trend-spottermodel-pin removed — the agent now inherits the session model instead of being pinned tosonnet, so a discovery pass runs at the session's tier. (a8e3cac)
Added — trend proposal + reader-side layer (N6)
- Proposal fields on the trend record —
angle/targetLevel/rationale/relatedIds, plus the reader-sideactionability(is a reader-grip formulated?) /verdict(BÆRENDE/STØTTE/NYHET) andreaderQuestion/painPoint/saturation. All additive-optional; store schema stays v4 (no migration). (65b6033) - Candidate lifecycle —
new → selected → acted | skipped; aselectverb +--idsbatch triage (partial hit = exit 0, all-miss = exit 2). (65b6033) - Band-distribution calibration (MR-F6) — the scoring SSOT now states the expected distribution (Immediate ≥8 is the exception, target ≤~3 of 20), checkable across runs against the persisted composite. (
bace49f)
Added — trend→newsletter bridge + actionability gate (N7)
- Trend→newsletter bridge (MR-F3) —
/linkedin:newsletterStep 1 accepts a trend id and pre-fills the brief (angle / target-level / key-points / source-URLs) from the stored candidate;articles.NN.sourceTrendIdrecords the provenance (additive-optional, no edition-state schema bump); Step 10 auto-acts the source trend on scheduling so the discovery→production loop closes deterministically; Step 2 gains an external fact-package intake path (kilde-så-draft with finished research).grep -ci trend commands/newsletter.md: 0 → 18. (1adb7f4) - F7 band-cap gate (MR-F7) — a candidate whose reader-grip is explicitly not formulated (
actionability.formulated=false) is capped to at mostHighregardless of composite. Pure and deterministic; it overrides the composite→band derivation downward only (the real composite is preserved), wired into the capture path so the persisted priority is gated. (1adb7f4)
Landed feedback
- MR-F3 (trend→newsletter bridge) — landed @
1adb7f4.
[0.6.0] - 2026-07-17
Catch-up release. Everything since the v0.5.3 tag ships here (28 commits, git log --oneline v0.5.3..HEAD is the source of truth), and the sections below also document work that shipped inside earlier tags but was never changelogged (see the catch-up note at the end).
Added — figure pipeline (MR-F4/MR-F8)
render/build-figur.mjs— coded data figures: SVG/HTML → PNG via headless Chrome; three targets (article 1200px wide with content-driven height / carousel 1080×1350 / single 1200×1200); brand tokens from the user data dir'sprofile/brand-tokens.jsonwith neutral defaults (token-seam); Chrome-hang watchdog. Standalone CLI + importable module; 33 tests. (c4434ed)/linkedin:newsletterStep 7.5 — three figure routes with the coded route PRIMARY for data figures, plus newreferences/figure-design-guidelines.md(reference docs 27 → 28). (1a67bd2)render/build-html.mjsparser reconciliation — multi-line blockquotes (blank>line = new<p>in the same quote),[text](url)links with an http/https/mailto scheme-whitelist, and the**[FIGUR N — «…»]**marker → inline SVG fromfigurer/figN*.svgwith figcaption (fallback: plain blockquote) + CSS for all three. Byte-identical parser parity with the upstream engine proven by fixture; the import-safemain()CLI-guard is kept. 7 new tests. (8151029)
Added — research engine RE-R3a–e
- R3a — persist the relevance score on the store record + rank the morning brief on it. (
e169c78) - R3b — trend lifecycle: re-score on re-capture, status, seen-log. (
b185db9) - R3c — autonomous trigger: scheduler + headless entry. (
3276e44) - R3d — temporal overlay: first-mover + saturation. (
2a8459c) - R3e — brief history + day-over-day diff (frontmatter + "Nytt siden sist"). (
5b51b4b)
Added — OKF Stage 1
- The brain store emits an OKF-compatible bundle (
brain/as bundle,ingest/excluded; title/description + typed pending-diff). (9e95222,e9e183e)
Fixed
- Oppgave 1 fix-pass (from independent cold-review): clipboard heredoc, report refs, calendar queue fields. (
882f6ee) - Prune-regex no-op in state-updater + date-independent calendar fixtures (flake). (
776d728)
Docs
- Independent cold-review complete — 29/29 surfaces (R2a Create emitters, R2b Create orchestrators, R3 Engage, R4 Measure, R5 Grow + Router;
docs/hardening/review*.md). (5474df5…4fd038a) - Truth-pass (this release): README maturity note + badges (hardening 29/29 + cold-review 29/29; what remains for 1.0.0 is a GUI); CLAUDE.md maturity line + Architecture entries for specifics-bank and contract-gate; GR-model correction in
references/algorithm-signals-reference.md— LinkedIn's ranking model has an official primary-source name, the Generative Recommender (GR), announced 2026-03-12 on LinkedIn's engineering blog (the earlier "likely fabricated" flag rejected a genuine primary source and is retracted in a correction note); saves-API rationale updated (Marketing API v202604 exposesPOST_SAVEon/memberCreatorPostAnalytics, partner-gated — manual entry remains the right UX); hardening queue table caught up through S31a/b/c; second-brain architecture header caught up through SB-S3a–e. Plus maturity-status correction (001d76c), CLAUDE.md trim (−2,266 always-loaded tokens,9567689), ingestion-guard adoption plan (d67552e).
Catch-up note — work that shipped inside earlier tags, never changelogged
- Inside v0.5.1: Fix #1 — contract-gate (
scripts/contract-gate/, deterministic §B/§C1 rule-gate,/linkedin:newsletterStep 4.5) and Fix #2 — specifics-bank / lived-specifics (scripts/specifics-bank/store + per-edition binding + Step 1.5 elicitation, slices 1–3). Together these took the newsletter pipeline 16 → 18 phases. - Inside v0.5.3: SB-S3a–e (profile.md reader-wiring, supersede arm, cross-silo id-threading, operations.md ops centre, content-history retirement + read-side reconcile) and RE-R1–R2b (item-schema + triage-scorer as tested code; item→store capture bridge with lossless schema v1→v2 migration; dated morning-brief artifact + session-start surfacing).
[0.5.3] - 2026-06-24
Changed — registration hygiene: agent fasit fixtures moved out of agents/
Stops Claude Code from registering 6 spurious agents. The six long-form-review fasit fixtures lived in agents/fixtures/*-cases.md; because Claude Code scans agents/ recursively, each was registered as a junk fixtures:* agent (fixtures:content-reviewer-cases, …). They are test fixtures, not capabilities. Count-neutral (no real agent/command/reference/skill change; ls agents/*.md still 19) — pure namespace hygiene.
- Moved the 6 fasit fixtures
agents/fixtures/*-cases.md→tests/fixtures/*-cases.md, and their 6 lint testsagents/__tests__/*-fixture.test.mjs→tests/*-fixture.test.mjs(co-located; each test's relative fixture path retargeted../fixtures/→./fixtures/).agents/fixtures/andagents/__tests__/are now removed. - Retargeted the 5 agent fasit-refs (
content/language/persona/editorial/fact-reviewer) from${CLAUDE_PLUGIN_ROOT}/agents/fixtures/…to${CLAUDE_PLUGIN_ROOT}/tests/fixtures/…. - Tests. Fixture-lint 35/35 at the new path (
node --test 'tests/*.test.mjs'); render 20/20, hooks 139/139 unaffected; gatescripts/test-runner.shgreen (counts + version-consistency). Reinstall/reload required for the 6 spurious agents to disappear from the registry.
[0.5.2] - 2026-06-23
Added — SB-S2: the evolution loop (second-brain arc)
The compounding mechanism. An operator-invoked, operator-gated consolidation pass that reads the published gold signal + tributary deltas and proposes a diff to the two-layer brain/profile.md — never a silent overwrite. Non-breaking (additive minor): count-neutral (no command/agent/reference/skill); voice-trainer unchanged; one session-start hook edit (zero-dep).
- Deterministic engine (
scripts/brain/src/consolidate.ts):proposeDiff/applyDiff(pure) — add / reject-ai-draft(model-collapse guard, in code) / evidence-bump / promote at N=3 / conflict→keep-both with distinct ids (primarymintEntityId, altmintContentId— no duplicate-id corruption; no supersede, that's S3) / decay-flag at 90d (dynamic only; static decay-exempt).applyDiffround-trips through the SB-S0 grammar; re-running is idempotent. brain consolidateCLI:--gather(reads published bodies since last run + the profile, for the invoking session to extract candidates — no new agent),--propose --candidates <json>(validates shape + single-line; writesbrain/pending-diff.{md,json}; never touches profile.md),--apply --diff <json> --confirm(the ONLY path that writes profile.md; refuses without--confirm; recordsbrain/consolidation-state.json).- Session-start (
hooks/scripts/session-start.mjs, zero-dep edit): unconditional scaffold-ensure (fresh-install path) + a consolidation-due nudge (counts published records + reads the sidecarlast_runviagetDataRoot; noprofile.mdparse) + a brain-init nudge. - Operator scope: journal-capture deferred · the invoking session does the fuzzy extraction (no new agent) · motor-only (no
profile.mdreader until SB-S3). Docs:docs/second-brain/consolidation-loop.md. - Tests. brain suite 63 → 82 (
consolidate12 +consolidate-cli7); hook suite 131 → 136 (newsession-start-brain-consolidation5); gatescripts/test-runner.sh93/0/0 (BRAIN_TESTS_FLOOR63→82; assertion floor unchanged at 78 — no new gate section). Light-Voyage-hardened (brief-reviewer REVISE, plan-critic REPLAN, scope-guardian ALIGNED — all folded).
[0.5.1] - 2026-06-23
Added — SB-S1: ingest + the published gold signal (second-brain arc)
The second slice of the second-brain arc. Captures the user's actual published posts into ingest/published/ tagged provenance=published — the gold signal that voice/profile learning is allowed to learn from — and wires the learning surface to that signal only, never to AI-drafted content (the model-collapse guard). Non-breaking (additive minor): no command/agent/reference/skill count changes; a shipped agent's learning behaviour changes (voice-trainer).
- Ingest data layer (
scripts/brain/src/ingest.ts): aPublishedRecordfile-per-post grammar (fixed 5-line header +---sentinel + verbatim body, no YAML) with aparse ∘ serializeidentity; a content-hash idmintContentId = sha256(VERBATIM body)[:12]— byte-identity dedup, so two structurally-different posts never collide and a differing body is never silently dropped. brainCLI growsingest --file <path>/ingest --scan-inbox(top-level*.md, skips dotfiles, non-destructive, idempotent, create-on-demand) andpublished list(surfacesprovenanceso ai-draft leakage is visible). The existinginitis preserved.- Published-only invariant wired + gate-enforced.
agents/voice-trainer.mdreadsingest/published/as its primary gold source, keepsvoice-samples/as a human tributary (not reshaped), forbidsprovenance=ai-draft, and fences the auto-append trap. A new structure-lint section (Brain Published-Only Invariant) enforces the wiring with exact-literal greps + a non-vacuity self-test. - Tests. brain suite 34 → 63 (
ingest14 +publish9 +cli6); gatescripts/test-runner.sh90 → 93/0/0 (BRAIN_TESTS_FLOOR34→63, assertion floor 75→78). No automaticbrain/profile.mdmutation (the consolidation loop is SB-S2); no cross-silo threading (SB-S3); no connector (SB-S4).
[0.5.0] - 2026-06-18
Added — M0: per-user external data dir (architecture workstream)
User data now lives outside the plugin. The first of the four v1.0.0 workstreams (architecture) is complete: every class of user data — voice profile, analytics, drafts, plans, A/B tests, audience insights, frameworks, case studies, network/repurposing trackers, user profile — now resolves under ${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/ instead of the plugin tree. Non-breaking (minor bump): an automatic, idempotent, atomic migration runs once on session-start (copy → fsync → byte-verify → rename; external is canonical, never clobbered), and the deprecated ANALYTICS_ROOT / STATE_FILE / PLUGIN_ROOT aliases are still honored for one minor version.
- Resolver seam per runtime (twins).
hooks/scripts/data-root.mjs(getDataRoot(), zero-dep.mjs) andscripts/analytics/.../storage.ts(getDataRoot(), TS viatsx) resolve the same default + override semantics, guarded by a twin-consistency test — the runtime split (zero-dep hooks vstsxanalytics) forbids one shared module. - Migration tool.
hooks/scripts/migrate-data.mjsis the single source of truth for external destinations (MOVE_FILES+COPY_FILES);personalization-score.mjsmirrors it. Idempotent re-runs reportalready-migrated. - D2 — scaffold/instance split. Every COPY-class data file (
examples/high-engagement-posts,audience-insights/{demographics,engagement-patterns},templates/my-post-templates) ships a read-only*-template.mdseed in-plugin; the canonical instance lives external with in-plugin scaffold fallback. - D3 — path convention.
references/data-path-convention.mddefines the inline${LINKEDIN_STUDIO_DATA:-…}/token (generalizing the proven${LTL_SERIES_ROOT:-…}newsletter pattern); ~168 prose references across commands, agents, skills, hook prompts, and references repointed to it. Style-A${CLAUDE_PLUGIN_ROOT}is preserved for shipped read-only assets (templates, checklists, references, the analytics CLI). - Privacy. Closed a real voice-sample leak in the in-plugin
examplesscaffold (scrubbed to a generic seed; git-history scrub tracked as a pre-publish follow-up).
Changed
- Version declarations bumped to
0.5.0across.claude-plugin/plugin.json,README.md(badge), andCLAUDE.md(header). - Structure lint (
scripts/test-runner.sh) extended 74 → 81 checks:EXPECT_REFS25→26 (+ exactly-+1 delta), a no-bare-in-plugin-data-path guard (R1), a no-ANALYTICS_ROOT-pin guard (M3 — caught 5 surviving pins in the analytics agent + skill), an SC2 working-tree dry-run, and an assertion-count anti-erosion floor — each with a non-vacuity self-test. No functionality regressed: hooks 128/0, analytics 119/0 + tsc clean, structure 81/0.
[0.4.0] - 2026-05-31
Re-baselined
Honest version reset: 4.1.0 → 0.4.0. The project was previously numbered 1.0.0–4.1.0, but those were pre-release iterations. The honest maturity is v0.4.0: user data still lives inside the plugin tree (defended only by .gitignore), no command has yet passed the hardening quality-gate, command testing is incomplete, and there is no GUI. This is a deliberate marketplace downgrade — existing installs will not auto-pull a lower number. The path to v1.0.0 is four workstreams: architecture (move all user data out of the plugin into a per-user data dir — the M0 migration), hardening (every command through the quality-gate), command testing (each command run against real/isolated ground truth), and a GUI.
Changed
- Version declarations reset to
0.4.0across.claude-plugin/plugin.json,README.md(badge + version-history),CLAUDE.md(header), and the root marketplaceREADME.mdentry. No functionality changed — no code, command, agent, hook, or state-shape change. All prior changelog entries below are preserved as the development history.
[4.1.0] - 2026-05-30
Summary
Journey layer over the command surface (Voyage S14). Step 14a's cold, independent command-rationalization (docs/remediation/command-rationalization.md) audited all 27 commands under the same subsumption discipline as the agent-overlap study and found zero redundancy — the surface was over-grown in count, not in duplication (the two prior consolidations already removed the genuine overlaps). So instead of cutting, this release organizes: the 27 atomic commands are kept as the execution tier, and a journey layer is added on top. Additive and minor — no command removed, renamed, or behavior-changed; the two new commands register on reload. Design contract: docs/remediation/journey-layer-design.md.
Added
/linkedin:create— Create front-door. One guided "what do you want to make?" entry that routes to the command owning the format (post/quick/react/carousel/video/multiplatform/batch/newsletter). Delegates only — no drafting logic of its own./linkedin:measure— Measure front-door. One guided "how am I doing?" entry that routes to the right analytics command (import/report/analyze/audit/ab-test). Delegates only. Commands 27 → 29.
Changed
- Router re-tiered into five journeys (
commands/linkedin.md) — Start · Create · Engage · Measure · Grow, each headed by a front-door (onboarding/strategyelevated as the Start/Grow front-doors;create/measurenew; Engage is acalendar+firsthourrelay), with the atomic commands nested as the execution tier. Absorbs the planned router-tiering UX step. - Honesty nits from 14a fixed — the router now lists
/linkedin:firsthour(was agent-only under Post-Publish);calendar's publish-action first-hour block cross-links to/linkedin:firsthourfor the full worked sprint plan. EXPECT_COMMANDSinscripts/test-runner.sh27 → 29; CLAUDE.md / README rosters + counts updated in lockstep.
Fixed
- 14a deliverable correction — the cold review's first pass omitted
multiplatform(covered 26/27) and raised acompetitive1K-gating inconsistency that did not survive verification (CLAUDE.md:64, README:222, the router, and the command body all leavecompetitiveungated). Both corrected incommand-rationalization.md; net finding unchanged (keep 27, 0 merge, 0 cut).
Compatibility
- Minor / additive. No command removed, renamed, or behavior-changed; the 27 existing commands, all 19 agents, and all state shapes are unchanged. The two new commands (
create,measure) register when the plugin command set is rebuilt at session start — reload required to see them.
Added — within 4.1.0 (refinement sessions, no surface/count/version change)
- Manual per-post saves in analytics (Voyage S16). Lifts the original v4.0.0 Non-Goal:
PostMetricsgains an optionalsavesfield, ingested when the user adds aSavescolumn to the CSV with the count read off native LinkedIn post analytics (count-only, ~Sept 2025; absent from the export, no self-serve API). The parser (scripts/analytics/src/parsers/csv-parser.ts) reads it when present; weekly/monthly summaries gain an optionaltotalSaves; the CLI (import/report) surfaces saves per-post and as a total. Backward-compatible — a missing column or blank cell leaves saves unknown (never coerced to 0), saves is not folded intoengagementRate(which stays comparable to older imports), and saves-free data round-trips byte-identical. Dwell stays explicitly unmeasurable — no dwell field or surface was added. This refines the v4.0.0 "the plugin cannot read those signals" wording: the plugin still cannot auto-track saves, but it now ingests a manually-entered count. Built location-agnostically through the existinggetAnalyticsRoot()seam so the planned data-dir migration (UI brief §9b/M0) relocates it in one place. NewRankableMetrictype fixes the trend/alert index access that the optional field would otherwise widen tonumber | undefined. - Onboarding tool-grant fix (S16-pre).
commands/onboarding.mdPhase 2 saves voice/user-profile files but its frontmatter omittedWrite; addedWritetoallowed-tools(matchingfirst-post.md). Closes a pre-existing tool-contract gap surfaced by the S15 review.
[4.0.0] - 2026-05-30
Summary
Audit-remediation release (Voyage Phase 0–3). A critical self-review (docs/critical-review-2026-05-29.local.md) found the plugin had drifted in three ways: (1) overclaiming — surfaces promised tracking, analytics, and review independence the plugin could not actually deliver; (2) dormant capability — eleven agents shipped in agents/ were never invoked by any command; (3) structural rot — the structure lint validated a layout the plugin had outgrown, an algorithm-signal claim contradicted itself across files, and an unpublishable model brand/date was baked into user-facing copy. This release is the systematic fix: every claim is made honest or removed, every orphan agent is wired, and the lint is rebuilt to guard the real layout plus version/count/stat consistency. Major version marks the scope of the remediation (every user-facing claim re-examined) and the reinstall/reload required for the newly-wired agents to register; it consolidates — but does not repeat — the v3.0.0 identity break (slug, agent namespace, state-file path). No content-pipeline behavior is removed; the short-form and long-form engines are unchanged except where a gate was added.
Added
/linkedin:firsthour— post-publish first-hour / reply-loop sprint command wiring the previously-orphanedengagement-coachagent: a timestamped target list, draft comments, and a timeline, persisted to state (recordFirstHourPlan), handing off topost-feedback-monitor. Commands 26 → 27.- All 11 orphaned agents wired (case-by-case: 9 in the wiring pass, 2 via the new gates/command) — the agent set is now fully reachable from a command, with no deletions. Agents stay 19.
- Short-form de-AI gate — the short-form content commands run
differentiation-checker+ the voice-guardian before output, the short-form mirror of the long-form de-AI discipline. - Video quality gate —
/linkedin:videoenforces captions + aspect-ratio guidance (4:5 / 1:1 + captions) and drops the hard 9:16 mandate. - Version-consistency grep in the structure lint (
scripts/test-runner.sh) — theplugin.jsonversion must match the README badge, the pluginCLAUDE.mdheader, and the CHANGELOG top entry; theplugin.jsondescription is now also covered by the algorithm-stat-consistency scan.
Changed
- Structure lint rebuilt (
scripts/test-runner.sh) — dynamic registration counts derived fromls(agents/commands/refs/skills), frontmatter shape, hook-drift (compile-hooks.py --check), and an algorithm-stat-consistency grep that forbids the unpublishable model brand/date and competing magnitudes from returning. Replaces a dead validator that asserted an outgrown layout. - Algorithm signals reconciled to one sourced statement —
references/algorithm-signals-reference.mdis the single source of truth (per-claim Source + Confidence); every citer cites rather than restates. The 2026 relevance-ranking model is referenced without a name or a date (the unpublishable brand/date removed everywhere, including the root README and the marketplace manifest). post-feedback-monitorpromoted to Opus (Opus-default for human-facing reasoning).- Newsletter distribution, profile-SEO, and outreach surfaces made honest — they describe what the plugin produces (drafts, recommendations, queues) versus what the operator does manually, with no implied automation.
- Long-form review language is configurable; render output de-branded; series path parameterized (no hard-coded author or series).
- Counts reconciled to the
ls-derived source of truth: 27 commands · 19 agents · 6 skills · 9 hooks · 25 reference docs · 16 newsletter phases. README badges + intro, root README, and the marketplace catalog brought into sync. - Long-form review-pass overlap measured (
docs/remediation/overlap-measurement.md) across the seven long-form review gates against in-repo fixtures: every gate has ≥ 1 unique catch and the real overlaps are justified → no gate trimmed (the review stack stays seven).
Fixed
- Analytics CLI fresh-clone crash —
report.md/import.mdsurface thenpm installat point-of-use, andgetAnalyticsRoot()is anchored on the.claude-plugin/plugin.jsonmarker instead of a build-layout-relative depth (latent correctness bug). - No false metric claims — saves/dwell wording is honest (the plugin cannot read those signals), and the A/B significance claim is downgraded to directional.
Security / Privacy
- Voice-profile leak closed — the tracked
authentic-voice-samples.mdis now a PII-free placeholder carrying a<!-- VOICE_PLACEHOLDER -->sentinel; the author's real profile moved to a gitignored.local.md;personalization-score.mjsscores the placeholder 0 voice points via the sentinel (both voice writers replace-not-append). The author name is scrubbed fromplugin.json(theLICENSEMIT copyright holder is the intentional exception). Per a documented decision, git history is not rewritten — the historical voice file is attributed open-source authorship, not a leaked secret.
Compatibility
- Breaking — reinstall / reload required. The eleven newly-wired agents register only when the plugin agent set is rebuilt at session start; the v3.0.0 slug / agent-namespace (
linkedin-studio:<agent>) / state-path (~/.claude/linkedin-studio.local.md) break is consolidated here. Existing editions and analytics data are unaffected (state shapes are additive; posts/streak/history preserved). The/linkedin:*command namespace is unchanged.
[3.1.0] - 2026-05-29
Summary
An adversarial review package becomes part of the long-form pipeline (Endring 9). The Del 4 production run (Maskinrommet, 2026-05-29 — the Security Champions pivot, v8 → v11) shipped a high-quality article, but its quality assurance leaned on one good editor + KTG's availability to read it several times — it did not scale. The root cause: the editor and the persona sweep ran in the same session as drafting, sharing the conversation history (which versions passed, what was deliberately cut, which flags had been raised). They were therefore not adversarial — they carried framing-bias. Three concrete symptoms: (1) the persona resonance sweep effectively judged an early version, not the one that shipped; (2) editor-approval was single-source; (3) the fact-check was post-hoc relative to the late pivot, so the pivot could build on an unverified premise. v3.1.0 answers KTG's explicit question — how do I start sessions with no context from the main session, to review both content and language? — and makes per-artifact personas a first-class input.
Added
/linkedin:headless-reviewcommand — runs the cold adversarial review package on a FROZEN draft. Designed to be invoked in a fresh session for maximum isolation (the parent then has no drafting transcript); reconstructs everything from disk (frozen draft + writing contract + personas). Flags--draft,--type {content|language|fact|persona-resonance|persona-conversion|all},--persona,--article,--output.- Three new headless review archetypes (all Opus, each with an explicit cardinal context-isolation block that refuses drafting-session framing as "context pollution"):
content-reviewer(color maroon, Read+Grep) — argument integrity: C1 logical holes · C2 unsupported assumptions · C3 argument-level contradiction · C4 missing concretization · C5 unanswered «what about X?». ≤8 flags BLOCK/REWORK/NICE.language-reviewer(navy, Read+Grep) — Norwegian language: L1 verbatim repetition · L2 anglicisms · L3 stiff bureaucratic register · L4 language-level self-contradiction · L5 clang/rhythm. ≤10 flags. Deliberate cold re-take ofeditorial-reviewer's prose axis.fact-reviewer(gold, Read+WebSearch) — cold re-verification on the frozen/pivoted version: F1 verifiable claims · F2 quote precision · F3 number attribution · F4 source quality. Carries overfact-checker's 5-dimension scoring + 🔴/🟡/🟢 sort + contradiction sweep + post-cutoff mandate, adds a pivot-risk subsection. Deliberate redundancy withfact-checkerto catch a pivot premise that arrived after Step 5.
- Step 6.5 (headless-review) in
/linkedin:newsletter— fans the package out in parallel after the in-session persona sweep (Step 6), on a frozen draft snapshot, BEFORE lock; consolidated report surfaced viaSendUserFile; converged flags (two independent cold reviewers agreeing) marked as the strongest signal. Pipeline 15 → 16 phases. /linkedin:pivotcommand + pivot-detection gate — a pivot re-opens the pipeline so the cleared gates (fact-check 5 → editorial 5.5 → persona 6 → headless 6.5) re-run on the changed version before lock. Heuristic: a draft that drifted > 20 % in word count OR gained > 2 sections since Step 6 cleared triggers the gate (enforced as a Step 8 lock precondition). Worked example: the Del 4 v8→v11 run (+42 %, 2 new sections) would have fired the gate and forced the re-sweep.- Per-artifact personas —
articles.NN.personasinedition-state.json: one or more readers configurable per edition, resolved in Step 1 in order (edition-state →<serie>/linkedin/personas.mdper-series file → pluginpersonas.local.md/template → interactive definition). Feeds both the Step 6 sweep and the Step 6.5 package.config/personas.template.mddocuments the resolution order. - Three fasit fixtures + three structural lint tests for the new agents (Del 4 / Security Champions worked cases), mirroring the
editorial-reviewerfixture discipline. All 35 agent-fixture assertions green.
Changed
config/edition-state.template.json— additive: per-articlepersonas[],pivots[],headlessReviewobject; newheadless-reviewphase string (16 phases total);personaSweep.resonance.wordCountrecorded at Step 6 as the pivot-detection baseline.commands/newsletter.md— Step 0/1 persona resolution reworked to per-artifact; new Step 6.5; Step 8 lock preconditions add the headless gate + pivot-detection gate; pipeline + resumption tables updated (persona-sweep-prelock→ resume at Step 6.5; newheadless-review→ Step 7).- Counts: 24 → 26 commands; 16 → 19 agents; 15 → 16 newsletter phases.
Compatibility
Backward-compatible: every state-shape change is additive (existing editions resume by currentPhase; persona-sweep-prelock now resumes at Step 6.5 — an intended deterministic improvement). Reload required before the three new agents resolve (the plugin agent set is built at session start). No new runtime code beyond the agents/commands/fixtures; the render pipeline, hooks, and short-form surface are untouched.
[3.0.0] - 2026-05-29
Summary
Plugin renamed linkedin-thought-leadership → linkedin-studio ("LinkedIn Thought Leadership" → LinkedIn Studio). The old display title read as pompous; the new name is plain and matches how the plugin already describes itself ("LinkedIn content engine"). This is a breaking change — the marketplace slug, the agent namespace (linkedin-studio:<agent>), and the runtime state-file path all change — so it bumps to a major version. Functionality is byte-for-byte identical to v2.4.0; this release is pure identity.
Changed (breaking)
- Slug / directory / manifests:
plugins/linkedin-thought-leadership/→plugins/linkedin-studio/;plugin.jsonand rootmarketplace.jsonname/sourceupdated. Reinstall required. - Agent namespace: commands invoke plugin agents as
linkedin-studio:<name>(waslinkedin-thought-leadership:<name>). Functional change incommands/newsletter.md; docs updated to match. - Runtime state path:
~/.claude/linkedin-thought-leadership.local.md→~/.claude/linkedin-studio.local.md. Hardcoded inhooks/scripts/{state-updater,session-start,posting-reminder,user-prompt-context}.mjs,hooks/prompts/topic-rotation-gate.md, andconfig/state-file.template.md. Existing state migrated in place (post metrics, streak, content history preserved). - Catch-all skill
skills/linkedin-thought-leadership/→skills/linkedin-studio/(frontmattername: linkedin-studio); the five functional skills (linkedin-analytics,-content-creation,-networking,-strategy,-voice) are unchanged.
Not changed (explicit non-deltas)
- Command namespace
/linkedin:*— set per-command in frontmatter (name: linkedin:post), already independent of the plugin slug. Every command (/linkedin:post,/linkedin:newsletter, …) is invoked exactly as before. 24 commands, 16 agents — counts unchanged. - All hooks, scripts, renderers, agent contracts, content — bit-for-bit identical to v2.4.0.
- History preserved:
config-auditv5.0.0 test snapshots and thedocs/build artifacts retain the old slug as point-in-time records and were intentionally not rewritten.
[2.4.0] - 2026-05-29
Summary
An editor's craft gate becomes an explicit pipeline phase in /linkedin:newsletter. The Del 4 production run (Maskinrommet, 2026-05-28) exposed a gap: the persona resonance sweep returned 15 flags across three personas and every persona reported PASS / ready-to-publish — yet the editor (KTG) found eight fresh editorial points on first reading, and only ~25 % overlapped anything the personas had touched. The other six — a missing theory anchor (SDT), a broken series-title link, a stranded small-business addressee, verbatim repetitions, em-dash over-density, an internal contradiction — were craft and narrative-architecture blind spots no agent measured. persona-reviewer measures reader response (does it land?); it does not measure prose craft or narrative architecture (is it well-made?). Two different roles; only one existed. A persona PASS was mis-reporting "ready for the editor's reading", costing an extra editorial round per article. v2.4.0 adds Step 5.5 — Editorial review, between fact-check (Step 5) and the persona sweep (Step 6), and a new editorial-reviewer agent (Opus) that mirrors the Maskinrommet writing-contract §C2. Pipeline 14 → 15 phases; 15 → 16 agents. Backward-compatible: the only state-shape change is additive (editorialReview), and existing editions resume by currentPhase (factcheck-sweep now resumes at Step 5.5 instead of Step 6 — an intended deterministic improvement). Doc/orchestration-only for the pipeline wiring; the new agent + its fasit fixture + structural lint test are the only new files.
Added
agents/editorial-reviewer.md(new, Opus, orange) — an editor, not a reader. Judges two axes: prosa-håndverk (P1 em-dash density · P2 verbatim repetition · P3 postulated numbers without source/hedge · P4 internal contradiction · P5 versal-tic — mostly grep-able) and narrativ-arkitektur (A1 concrete instantiation · A2 theory-anchored hypotheses · A3 series-title symmetry · A4 equally-usable action per addressee · A5 un-overloaded conclusion — evaluative). Returns ≤10 flags as direction (never rewritten copy — the jury judges, the writer writes), each with a quote/line-ref and a severity: BLOCK / REWORK / NICE. ToolsRead+Grep. The checklist is the operationalized mirror of the Maskinrommet skrivekontrakt §C2 (bidirectional mirror rule: §C2 is the source of truth). New agent — requires a session reload before it is invokable.- Step 5.5 — Editorial review in
commands/newsletter.md, between Step 5 (fact-check) and Step 6 (persona sweep). Runs a single foregroundeditorial-reviewerTaskcall, writes the report to<serie>/NN-editorial-review.md, surfaces it to the operator viaSendUserFile(the Endring-5 / Step-7.5 operator-gate pattern), folds approved flags in by tightening (rule 6) → v(n+1), and optionally re-runs the agent on the cleaned version. Why before the persona sweep: the personas measure response — if the prose is locally messy the persona flags become noise; clean the craft first so Step 6 measures what it was built to measure. editorial-reviewphase string +articles.NN.editorialReviewschema +_doc.editorialReviewnote inconfig/edition-state.template.json_doc.phases(14 → 15 phases). Additive;editorialReview: nulluntil Step 5.5 runs. The craft companion tofactcheckLog(truth) andpersonaSweep(response).agents/fixtures/editorial-reviewer-cases.md(new) — fasit fixture: the Del 4 v5 gold standard. KTG's eight editorial points mapped to the two axes + severities (3 BLOCK / 5 REWORK), with the persona-overlap column showing 6/8 were editorial-only blind spots. The calibration target for acceptance-criterion #8 (a live run needs a session reload + Maskinrommet read access; until then the fixture is the gold-standard of record).agents/__tests__/editorial-reviewer-fixture.test.mjs(new) — structural lint mirroring the persona-reviewer / fact-checker fixture tests: asserts both axes, all ten checks (P1–P5 + A1–A5), the three severities, the eight Del 4 cases, the §C2 tie, the direction-not-copy boundary, and the blind-spot rationale. 7 tests, green.
Changed
/linkedin:newsletterpipeline overview — 14 → 15 phases; the overview table, build-status note, and reference-file list reflect Step 5.5.- Resumption table in
commands/newsletter.md—currentPhase: "factcheck-sweep"now resumes at Step 5.5 (was Step 6); neweditorial-reviewrow resumes at Step 6. (Spec saidcurrentPhase: "fact-check"; the canonical key in this plugin isfactcheck-sweep— wired to the real key.) - Step 5 hand-off + ordering note — fact-check now hands off to Step 5.5; the BEFORE-lock note names editorial review (5.5) and the persona sweep (6) as the two gates the fact-check precedes.
- Step 6 intro — now reads on the "editorially-cleaned, fact-checked draft"; the persona sweep judges response, not craft (that was 5.5).
- README, CLAUDE.md, root README, root CLAUDE.md, plugin.json version + descriptions.
Not changed (explicit non-deltas)
persona-reviewercontract — bit-for-bit unchanged. Editorial review is supplementary: one agent measures craft (5.5), one measures response (6). The role boundary is sharp —editorial-reviewernever flags "this won't resonate",persona-reviewernever flags em-dash density.- Steps 0–5, 6 body, 7, 7.5, 8–10 — contract unchanged apart from the Step 5→5.5→6 wiring and the Step 6 intro line.
- Renderers, hooks, scripts, command count (24) — all unchanged. No new
.mjsruntime code (the only new code is the fixture lint test).
[2.3.0] - 2026-05-28
Summary
Visual assets become an explicit pipeline phase in /linkedin:newsletter. Until now images (cover + inline figures) were produced ad-hoc outside the 13-phase pipeline and referenced manually from edition-config.json + linkedin/NN/cover.png — even though a cover is mandatory (KTG cover-directive 27.05: «TLDR on top + at least one figure per article») and must coordinate with the text. v2.3.0 adds Step 7.5 — Visual assets, between annotation (Step 7) and lock (Step 8), so the cover is generated, operator-gated, and approved before lock — render/build-linkedin.mjs picks up linkedin/NN/cover.png at lock, so generating images after lock would force a re-render and break the lock. Pipeline 13 → 14 phases. Doc/orchestration-only — no new code; mcp-image is the default generation route but the interface stays pluggable (external cover-raw.png accepted), and the carousel branch reuses the existing render/build-carousel.mjs. Backward-compatible: the only state-shape change is additive (visualAssets), and existing editions resume by currentPhase (annotation now resumes at Step 7.5 instead of Step 8 — an intended deterministic improvement).
Added
- Step 7.5 — Visual assets in
commands/newsletter.md(between Step 7 annotation and Step 8 lock). Decides image needs from the article type (method-heavy → 1–2 inline figures, diagnosis-heavy → cover only), writes a per-image brief, generates via two routes (defaultmcp__mcp-image__generate_image→cover-v<N>-kandidat.png; external →cover-raw.png), runs the operator-gate (candidates surfaced viaSendUserFile, approval copied to the fixedcover.pngname — the same render+annotate pattern as Steps 2.5/3a), and records credit + caption. Explicit carousel branch (format: "carousel"): render the deck viarender/build-carousel.mjsinstead of cover+inline. visual-assetsphase string +articles.NN.visualAssetsschema inconfig/edition-state.template.json_doc.phases(13 → 14 phases) —{ format, cover: { brief, route, candidates[], approved, status }, figures: [ { id, brief, placement, status } ], carousel }. Additive; defaultformat: "standard".config/image-credit-caption.template.md(new) — cover motif + credit + caption table, modelled on the established Seres-serienimage-credit-caption.md. Honest-about-AI credit per the verification duty; documents the cover/figure naming convention.- Naming convention documented in
commands/newsletter.mdStep 7.5 —cover.png(approved, fixed) /cover-v<N>-kandidat.png(attempts) /cover-raw.png(external pre-edit source) /fig<N>.png(inline). Consistent with existing series use.
Changed
/linkedin:newsletterpipeline overview — 13 → 14 phases; the overview table, build-status note, and reference-file list reflect Step 7.5.- Resumption table in
commands/newsletter.md—currentPhase: "annotation"now resumes at Step 7.5 (was Step 8); newvisual-assetsrow resumes at Step 8.persona-sweep-prelockflows Step 7 → Step 7.5. - Step 8 lock preconditions now require the gated
visualAssets(approvedcover.pngforstandardformat, or approvedcarousel.pdfforcarouselformat) alongside the existing fact-check (no open 🔴) and primær-JA gates. - README, CLAUDE.md, root README, root CLAUDE.md, plugin.json version + descriptions.
Not changed (explicit non-deltas)
- Steps 0–7, 8 body, 9, 10 in
/linkedin:newsletter— contract unchanged apart from the Step 7→7.5→8 wiring and the additive Step 8 precondition. - Renderers —
render/build-linkedin.mjs,build-html.mjs,build-carousel.mjsuntouched (Step 7.5 calls them; no code change).build-linkedin.mjsstill readscover.pngby fixed name and does not embedfig<N>.png(figures are referenced in the draft and uploaded manually) — Step 7.5 documents this actual behavior rather than overstating it. - Hooks, scripts, command count (24), agent count (15) — all unchanged.
[2.2.0] - 2026-05-28
Summary
Longform gates hardened with the lessons from the second /linkedin:newsletter production run (Seres-serien). A chronicle built as a model/name catalog passed persona review (its flags were read as notes, not stop-signs) and nearly shipped; the rewrite was stronger but introduced fresh factual errors. v2.2.0 closes six concrete weaknesses: the persona gate becomes blocking with an explicit hard-fail list, fact-check is made orthogonal to narrative strength (more polish → more verification) with a post-cutoff web-search mandate, a new Norwegian-chronicle de-AI voice-scrubber is added and wired into Step 4, operator gates become render+annotate rounds, and per-edition production state is reconciled with the global STATE.md continuity system (no more edition-HANDOVER.md). 14 → 15 agents; commands unchanged (24). Backward-compatible — the only state-shape change is additive.
Added
agents/voice-scrubber.md(new, Opus) — aggressive de-AI scrubber + voice-drift corrector for long-form Norwegian chronicle drafts. Pass 1 strips objective AI-tells («la meg være ærlig», reflex rule-of-three, em-dash-spam, self-referential overhead, modell-/navne-katalog); Pass 2 corrects drift toward the chronicle voice; Pass 3 appends to a chronicle-voice-drift-log so it sharpens over editions. Calibration rule (cardinal): gold standard = the approved Norwegian editions, NEVERassets/voice-samples/authentic-voice-samples.md(English short-form, forbids the em-dash). New agent — requires a session reload before it is invokable.- De-AI / voice scrub sub-pass in
commands/newsletter.mdStep 4 — fans outvoice-scrubber(foreground, namespaced) with the draft + approved-Norwegian-edition paths as the gold standard. - Blocking hard-fail list in
agents/persona-reviewer.md+config/personas.template.md— primær «mistet meg» / doesn't own the action / sjargong-mur / modell-/navne-katalog → BLOCK regardless of other axes. «JA med store forbehold» = NEI. - Post-cutoff fact-check mandate + high-frequency-error checklist in
agents/fact-checker.md— claims dated after the model's knowledge cutoff MUST be web-searched; explicit checks for person titles, org-varying "standards", over-credited studies, source scope, and founding/release years. Fact-check declared orthogonal to narrative strength. - Render+annotate operator gates in
commands/newsletter.mdSteps 2.5 + 3a — HTML annotation viarender/build-html.mjs→file://link is the primary operator-review flow;AskUserQuestionbecomes a receipt + fallback. - Avoid-patterns — modell-/navne-katalog, completeness-over-reader-action, self-referential overhead openings added to
references/longform-quality-rules.md(rules 1 + 3) andconfig/user-profile.template.md. personaSweep.skeleton+immutableRulesfields inconfig/edition-state.template.json(additive).
Changed
- Edition production state reconciled with STATE.md (ONE-system).
commands/newsletter.mdStep 0 now reads<serie>/STATE.md(auto-injected by the session-start hook); every phase writes narrative status to<serie>/STATE.md(overwrite) and machine state (fact-check log, persona verdicts, immutable rules) toedition-state.json. AllHANDOVER §4/§5/§6references replaced. agents/fact-checker.mdprinciple 3 strengthened to make web search mandatory for post-cutoff claims.- README, CLAUDE.md, root README, plugin.json version + descriptions.
Removed
config/edition-HANDOVER.template.md— deleted. The plugin no longer ships or requires a separate handover mechanism;<serie>/STATE.md+edition-state.jsoncarry its content per the global continuity rule.
[2.1.0] - 2026-05-28
Summary
Skeleton gate before prose in /linkedin:newsletter. Two new pipeline phases (Step 2.5 — Skeleton + section pitch; Step 3a — Spine prose) split the old Step 3 into pre-prose / spine-prose / full-prose stages, each with an operator-gate. Adds a third persona-reviewer mode (skjelett) that judges the five-line skeleton + section pitches BEFORE prose is written. Empirically motivated by the Seres-serien Del 3 + Del 4 production: spine errors caught post-prose cost ~1 day; the same error caught at the skeleton stage costs 5–15 minutes. Backward-compatible: existing editions stop at currentPhase: "research" and now resume at Step 2.5 instead of Step 3 — an intended deterministic improvement, never a contract break.
Added
- Step 2.5 — Skeleton + section pitch in
commands/newsletter.md. Writes<serie>/NN-skjelett.mdwith the five-line spine (premiss / problem / anbefaling / gevinst / vei videre) + one-line section pitches. Operator-gate (JA / REVIDER / NEI) AND parallel persona-skjelett-sweep must both return JA before the pipeline advances. Encodes the Maskinrommet writing-contract §A discipline into the pipeline itself. - Step 3a — Spine prose in
commands/newsletter.md. One paragraph per section against the gated skeleton, ~20–30 % of final edition length. Operator-gate on whether the axis lands now that there is prose on it. Cheap second checkpoint before full expansion. - Step 3b — Full prose expansion in
commands/newsletter.md. Splits the old Step 3 (Draft) into spine prose (3a) and full prose expansion (3b). 3b owns the existing draft-cursor logic for multi-session expansion; 3a is short enough to restart on interruption. persona-reviewerskjelett-mode (third mode alongsideresonansandkonverter). Five spine axes (Premiss / Problem / Anbefaling / Gevinst / Vei videre) scored HOLDER / TVILER / MANGLER, ≤3 direction-only flags, per-pitch section-pay-in check, gate ladder PASS / REWORK / BLOCK. Caller passesmode: skjelett.skeleton-pitch+spine-prosephase strings inconfig/edition-state.template.json_doc.phases— 11 → 13 phases. Resumption table incommands/newsletter.mdextended with deterministic rows for both new phases.- Rule 8 — Skjelett før prosa in
references/longform-quality-rules.md. Documents the skeleton-before-prose pre-condition that all other rules implicitly rely on, with the same five-slot format the pipeline enforces.
Changed
/linkedin:newsletterpipeline overview — 11 → 13 phases; pipeline tables incommands/newsletter.mdandCLAUDE.mdreflect the new ordering (0, 1, 2, 2.5, 3a, 3b, 4–10).- Resumption table in
commands/newsletter.md—currentPhase: "research"now resumes at Step 2.5 (was Step 3). Two new rows added forskeleton-pitch(→ Step 3a) andspine-prose(→ Step 3b). Draft-cursor note clarifies that the cursor applies only to Step 3b. agents/persona-reviewer.mddescription, principles, and anti-patterns — extended to cover the third mode (skjelett). Existing resonans + konverter modes unchanged in contract.CLAUDE.mdheader + persona-reviewer row + newsletter command row — reflect v2.1.0 surface.
Not changed (explicit non-deltas)
- Step 1, Step 2, Steps 4–10 in
/linkedin:newsletter— bit-for-bit unchanged in contract. - Renderers —
render/build-html.mjsandrender/build-linkedin.mjsuntouched; both still consumeNN-utkast.md(3a writes the spine state, 3b overwrites with the full state, but onlycurrentPhase: "draft"triggers rendering). - Hooks, scripts, command count (24), agent count (14) — all unchanged.
[2.0.0] - 2026-05-28
Summary
Full-spectrum LinkedIn content engine — short-form feed posts AND long-form newsletter editions in one cohesive surface, with net-fewer commands and net-stronger pipeline. Built across 21 Voyage sessions (S1..S20+S1a) with 1 step = 1 session discipline. Locked decisions A–H in docs/voyage-build-brief.md §3.
Added
/linkedin:newsletter— long-form orchestrator command. Multi-session pipeline: load → calibrate → research fan-out → draft → consistency/quality → fact-check sweep → persona sweep → annotate → lock → delivery → hook-gate → schedule. Maintainededition-state.jsonacross sessions. Supports newsletter editions, essays, and series articles/linkedin:outreach— outreach orchestrator (absorbed/linkedin:collaband/linkedin:speaking). Covers collaborations, partner pitches, and CFPs/speaking opportunities in one surfaceagents/fact-checker.md(Opus, brown) — verifies every factual claim in long-form drafts against primary sources. Outputs 🟢/🔴/🟡 verdicts per claim. Runs BEFORE lockagents/persona-reviewer.md(Opus, olive) — evaluates reader-persona resonance + hook-conversion gate. Two modes: per-persona deep review, multi-persona scoreboard. Runs BEFORE lockrender/pipeline migrated in-plugin —build-html.mjs,build-pdf.mjs,build-linkedin.mjs,build-carousel.mjs. Self-hosted fonts (Newsreader, Inter, JetBrains Mono) under OFL-1.1 withrender/OFL.txt. WeasyPrint degradation: missing binary → skip-signal, not throwconfig/personas.template.md— reader persona library. Knowledge level, time-pressure, resonance criteria per persona. Consumed bypersona-reviewerconfig/edition-state.template.json— schema for long-form edition state across sessionsreferences/longform-quality-rules.md— quality bar specific to long-form (different from short-form rules)- Router gating —
/linkedin:monetizeand/linkedin:outreachsurface "unlocks at ~1K followers" guidance and point sub-1K users at/linkedin:strategyfirst docs/agents-capability-matrix.md— single source of truth for which agent owns which capability. Pipeline diagram + intent table + model tier table
Changed
- Agent merges (16 → 14):
performance-reporter→analytics-interpreter(interpret + report modes, same data sources, mode-selector by trigger phrase)comment-strategist→engagement-coach(5x5x5 + first-hour + CEA method + target scoring + daily routine + comment quality scorecard; upgraded haiku → sonnet since the agent now handles deeper work)content-tracker→ absorbed bystate-updater.mjs+analytics-interpreterpersonalization-scorer→ absorbed bypersonalization-score.mjs(deterministic, no AI)
- Command merges (27 → 24):
/linkedin:templates→ mode in/linkedin:quick(8 post-type templates)/linkedin:publish→ action in/linkedin:calendar(mark scheduled posts as published)/linkedin:authority→ absorbed into/linkedin:strategy(canon for authority building, trajectory dedup)/linkedin:collab+/linkedin:speaking→/linkedin:outreach
/linkedin:importStep 6 analysis — delegated to/linkedin:report(both consume the sametrendsCLI; no more duplicated analysis pipeline)commands/linkedin.mdrouter — newsletter row added, removed-command rows pruned, gating-rule paragraph for monetize/outreach- All 6 skill catalogs reconciled —
linkedin-content-creation,linkedin-analytics,linkedin-strategy,linkedin-networking,linkedin-thought-leadership,linkedin-voiceall reflect the v2.0.0 command/agent set
Removed
commands/templates.md(absorbed intocommands/quick.md)commands/publish.md(absorbed intocommands/calendar.md)commands/authority.md(absorbed intocommands/strategy.md)commands/collab.md(absorbed intocommands/outreach.md)commands/speaking.md(absorbed intocommands/outreach.md)agents/content-tracker.mdagents/personalization-scorer.mdagents/performance-reporter.mdagents/comment-strategist.md
Fixed
references/glossary.md"Authority Score" entry — corrected stale ref tocommands/authority.md(removed) →commands/strategy.md(canon)scripts/test-runner.shEXPECTED_AGENTSlist — reconciled to 14 agents
Migration notes
- Plugin remains fully backward-compatible from a user-perspective: removed commands now route to their absorbing command via
commands/linkedin.md - v1.x users who had
commands/templates.mdetc. in muscle memory will be auto-redirected by the router
[1.2.0] - 2026-04-11
Summary
Friction reduction release. Fewer interactive steps, auto-clipboard, deterministic state management, and progressive onboarding.
Added
clipboard-helper.mjs— cross-platform clipboard utility (macOSpbcopy, Linuxxclip/xsel, WSLclip.exe). All 8 content commands auto-copy output to clipboardstate-updater.mjs— deterministic state mutations:updatePostTracking,pruneContentHistory,updateFollowerCount. Pure functions with 19 tests. No AI involvement in state updatesical-generator.mjs— RFC 5545 calendar file generation for batch scheduling. VALARM reminders, VTIMEZONE support, line folding, special character escaping. 16 tests- MCP image carousel pipeline —
/linkedin:carouselgenerates professional slide images via mcp-image (1080x1350, 3:4 ratio) with text overlays. Mermaid Chart and text-based fallbacks - Progressive onboarding — personalization score hidden until 3+ posts; voice guardian suppressed until 5+ voice samples; reasonable defaults in state template
- iCal integration in batch —
/linkedin:batchgenerates.icsfile importable into macOS Calendar, Google Calendar, and Outlook - Auto-prune content history — session-start dynamically imports
pruneContentHistoryto remove entries older than 90 days
Changed
- Reduced interactive steps — angle, format, and post type inferred from context. Max 2 questions per post (down from 4-6) in
post,quick,react,pipeline - State management — Stop hook and 8 commands now reference
state-updater.mjsfor deterministic writes instead of AI-driven YAML editing - State file template — default expertise area changed from domain-specific to
"general"for better new-user experience
[1.1.0] - 2026-04-08
Summary
Q2 2026 feature release. 9 improvements across onboarding, content quality, and analytics pipeline.
Added
/linkedin:onboarding— multi-step onboarding wizard: profile → setup → first-post as one guided flow/linkedin:carousel— structured multi-slide carousel generator with 5 templates and design specs- Voice drift scoring — 6-dimension rubric (sentence structure, word choice, openings, storytelling, tone, formatting) with AUTHENTIC/CAUTION/ALERT/REWRITE verdicts in voice-guardian hook
- Industry angle variants — 48 concrete variants (6 industries × 8 angles) in thought-leadership-angles reference
- Multi-URL comparison —
/linkedin:reactnow supports 2-3 URL synthesis with contrarian and pattern analysis angles - Day-of-week heatmap —
heatmapCLI command andHeatmapReporttype in analytics pipeline - Month-over-month reports —
report --month YYYY-MMCLI command with MoM deltas, weekly breakdown, top performers - Automated week-rollover — session-start hook now writes
posts_this_week: 0and updatescurrent_weekon ISO week change - Collected Post Samples — Stop hook passively accumulates published posts in voice-samples file for drift scoring
Changed
- README Quick Start — replaced 4-step manual flow with single
/linkedin:onboardingentry point /linkedin:report— Step 2 now offers report type choice (weekly/monthly/heatmap)/linkedin:post— Step 2 shows industry-specific angles when user-profile has industry set; Step 3 redirects to carousel when appropriate/linkedinrouter — added onboarding and carousel to menus and direct routing- Command count — 25 → 27 (onboarding, carousel)
[1.0.0] - 2026-04-07
Summary
Public release for open-source marketplace. All runtime bugs fixed, documentation aligned, agent model tiering implemented.
Fixed
- Agent model assignments — all 16 agents corrected from opus to proper tiering (12 Sonnet, 4 Haiku)
- Queue manager references — 10 stale
queue-manager.shreferences replaced withqueue-manager.mjsNode.js invocations - Quick-import references — 2 stale
quick-import.shreferences updated to.mjs - Personalization score import bug — standalone execution block now guarded to prevent stdout contamination on import
- Regex anchor — invalid
\ZJavaScript regex replaced with$in user-prompt-context.mjs - Agent color mismatches — 8 agent frontmatter colors unified with CLAUDE.md documentation
- Version inconsistency — unified from 3 conflicting versions (0.6.0/1.7.0/2.0.1) to 1.0.0
Added
- plugin.json — added
license,repository,keywordsfields for marketplace compliance - README — attribution note, "What This Plugin Does Not Cover" section, Node.js 18+ prerequisite, hooks badge
- CONTRIBUTING.md — replaced GitHub PR template with solo-project boilerplate
- Quality scorecard — added "Voice Authenticity" criterion (total now /81)
- Commands —
/linkedin:reactand/linkedin:first-postadded to README command tables - agents/README.md — updated from 14 to 16 agents, added personalization-scorer and post-feedback-monitor
- SKILL.md — added 5 missing commands to router command table
Changed
- CLAUDE.md — compacted from 237 to 90 lines, removed duplicated content
- All hooks — 100% Node.js (.mjs), no bash dependencies (cross-platform: macOS/Linux/Windows)
- Error handling — added JSON.parse guards in queue-manager.mjs and analytics storage.ts
Removed
- Skill version fields — removed non-standard
version:from all 6 SKILL.md frontmatter - Development artifacts — removed internal evaluation note from collab.md
- Orphaned files — deleted outdated docs/commands-reference.md
- BACKLOG.md and DEVELOPMENT-LOG.md — gitignored (internal development files)
[0.6.0] - 2026-02-07
Note
First formal version. Previously unversioned.
What exists today
- 20 commands covering full content lifecycle
- 15 specialized agents
- 8 hooks for workflow automation
- Analytics system with CSV import
- Profile/topic-relevance optimization
- Content matrix system (40+ post ideas from single topic)
- Personalization engine
- 20 reference documents for LinkedIn best practices
- Full content pipeline from ideation to post-publish monitoring