docs(linkedin-studio): Voyage remediation setup — brief + research + plan (Phase 0-3)
Audit-remediation Voyage project authored end-to-end this session: - brief.md (reviewer PROCEED; validator pass) — full Phase 0-3 scope, phased, with success criteria refined by research - research/01-03 — high-effort external swarm + Gemini (Topic 1); reconciled the external bar and corrected several audit feature-premises (no publishable model name/date; saves UI-visible not API-pullable; auto-publish possible-not-built; 9:16 not mandatory; newsletter notifications deduplicated not triple; CLI crash = missing npm install, depth-bug latent) - plan.md (21 steps, 7 sessions, 5 waves; validator pass; A- 88/100) — plan-critic REVISE (3 blockers + majors) addressed; scope-guardian ALIGNED; gemini Pass-2 folded in 2 blind spots (git-history decision; lint stat-grep sequencing) Execution is future sessions (one wave each) via /trekexecute, /trekreview as the release gate. Audit report stays local until the article ships. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
90fcc1069d
commit
a61b818578
6 changed files with 2082 additions and 0 deletions
4
plugins/linkedin-studio/.gitignore
vendored
4
plugins/linkedin-studio/.gitignore
vendored
|
|
@ -45,6 +45,10 @@ docs/DEVELOPMENT-LOG.md
|
||||||
|
|
||||||
# Generated annotation/review artifacts (regenerable; annotations live in browser localStorage)
|
# Generated annotation/review artifacts (regenerable; annotations live in browser localStorage)
|
||||||
docs/review/
|
docs/review/
|
||||||
|
docs/**/*.html
|
||||||
|
# Voyage executor bookmarks (local continuity, not tracked)
|
||||||
|
docs/**/.session-state.local.json
|
||||||
|
*.local.json
|
||||||
|
|
||||||
# Node.js
|
# Node.js
|
||||||
scripts/analytics/node_modules/
|
scripts/analytics/node_modules/
|
||||||
|
|
|
||||||
417
plugins/linkedin-studio/docs/remediation/brief.md
Normal file
417
plugins/linkedin-studio/docs/remediation/brief.md
Normal file
|
|
@ -0,0 +1,417 @@
|
||||||
|
---
|
||||||
|
type: trekbrief
|
||||||
|
brief_version: "2.1"
|
||||||
|
created: 2026-05-29
|
||||||
|
task: "Remediate linkedin-studio from the baseline audit — correctness, honesty, generalization, and the highest-leverage 2026 coverage gaps (full Phase 0–3 roadmap, phased)"
|
||||||
|
slug: remediation
|
||||||
|
project_dir: docs/remediation/
|
||||||
|
research_topics: 3
|
||||||
|
research_status: complete
|
||||||
|
auto_research: false
|
||||||
|
interview_turns: 3
|
||||||
|
source: interview
|
||||||
|
phase_signals:
|
||||||
|
- phase: research
|
||||||
|
effort: high
|
||||||
|
model: opus
|
||||||
|
- phase: plan
|
||||||
|
effort: high
|
||||||
|
model: opus
|
||||||
|
- phase: execute
|
||||||
|
effort: high
|
||||||
|
model: opus
|
||||||
|
- phase: review
|
||||||
|
effort: high
|
||||||
|
model: opus
|
||||||
|
---
|
||||||
|
|
||||||
|
# Task: linkedin-studio baseline-audit remediation
|
||||||
|
|
||||||
|
> Generated by `/trekbrief` on 2026-05-29.
|
||||||
|
> This brief is the contract between requirements and planning. `/trekplan`
|
||||||
|
> reads it to produce the implementation plan. Every decision in the plan must
|
||||||
|
> trace back to content in this brief.
|
||||||
|
>
|
||||||
|
> **Source of record:** `docs/critical-review-2026-05-29.local.md` (the baseline
|
||||||
|
> audit — Workflow `wf_8623b3ea-682`, 28 agents + Gemini Deep Research
|
||||||
|
> triangulation), including its **Operator correction (2026-05-29)** which is
|
||||||
|
> primary-source and supersedes the cold read. Section references below
|
||||||
|
> (§3, §3b, §4, §5, §7, §8, §9, §10) point into that file.
|
||||||
|
|
||||||
|
## Intent
|
||||||
|
|
||||||
|
The baseline audit was a cold, hostile read of the plugin repo with no operator
|
||||||
|
input. It found a set of **file-reproducible correctness and honesty defects**
|
||||||
|
that survive a second hostile pass: the analytics CLI crashes on first use on a
|
||||||
|
fresh clone (`ERR_MODULE_NOT_FOUND`); the algorithm "facts" contradict
|
||||||
|
themselves across files (a comment is "15x more reach" on one line and "5x" ten
|
||||||
|
lines later; carousel is "6.6%, highest of all formats" in two files and "1.92%"
|
||||||
|
in a third; the external-link penalty is "40-50%" in eight files and "25-40%" in
|
||||||
|
ten); the author's **real** voice profile ships committed and is read
|
||||||
|
unconditionally, so a fresh adopter who skips setup writes in the author's voice
|
||||||
|
and is told "Voice ✓ Done"; the only structural lint is dead and always fails
|
||||||
|
identically whether the plugin is healthy or gutted; the A/B output claims
|
||||||
|
statistical significance organic personal posts essentially never reach; the
|
||||||
|
analytics data model measures network-graph metrics while the strategy layer
|
||||||
|
tells the user to optimize saves/dwell it structurally cannot read; and 11 of 19
|
||||||
|
agents are never invoked by any command. On top of the correctness layer, the
|
||||||
|
plugin presents algorithm knowledge with a precision the evidence does not
|
||||||
|
support, and ships its flagship long-form engine as **bespoke disguised as
|
||||||
|
general** — hardcoded Norwegian, a maintainer-private absolute series path, and a
|
||||||
|
"skrivekontrakt §C2" that does not ship.
|
||||||
|
|
||||||
|
The operator's correction **refutes the audit's single hardest finding**: the
|
||||||
|
long-form pipeline HAS run end-to-end — two editions shipped via
|
||||||
|
`/linkedin:newsletter`, with artifacts living in a separate series repo
|
||||||
|
(`maskinrommet/serier`), so in-repo archaeology saw nothing. So this work is
|
||||||
|
**not** "prove the pipeline runs." It is: fix what is file-reproducibly broken,
|
||||||
|
make the plugin honest about what it knows and what it cannot do, and make it
|
||||||
|
usable by someone who is not the author. The stakes are trust — the operator
|
||||||
|
writes long-form regularly from now on, intends to share the plugin actively, and
|
||||||
|
will publish a Maskinrommet article about it. Every algorithm claim that ships
|
||||||
|
becomes a public claim, so correctness and honesty are load-bearing, not cosmetic.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
linkedin-studio passes a does-it-work bar on a fresh clone and is honest about
|
||||||
|
its boundaries, while keeping its differentiators. Concretely, the end state is:
|
||||||
|
the analytics CLI runs from any working directory on a fresh clone; the
|
||||||
|
structural lint reflects the real layout and fails on drift; there is **one**
|
||||||
|
source-anchored algorithm-signal statement that every command and agent cites,
|
||||||
|
with no intra-file or cross-file contradictions and no unsourced numeric
|
||||||
|
precision (the deployed-model name, the January-2026 date, and the −40-60% figure
|
||||||
|
are downgraded to exactly what current sources support); a generic placeholder
|
||||||
|
voice profile ships while the author's real one is gitignored; the plugin is
|
||||||
|
de-Norwegian-locked with a parameterized series path and documented default; the
|
||||||
|
plugin honestly discloses its boundaries (no self-serve analytics API for
|
||||||
|
personal profiles, no auto-publish, dwell is not exportable) up front; the
|
||||||
|
highest-leverage 2026 coverage gaps are closed with **wired, tracked** surfaces
|
||||||
|
(first-hour/reply loop, short-form de-AI gate, video 9:16 enforcement,
|
||||||
|
profile-SEO, newsletter distribution, outreach pipeline state); the long-form
|
||||||
|
stack is **kept** and trimmed for quality only where review-pass overlap is
|
||||||
|
**measured**, not assumed; and the 11 orphan agents are resolved case-by-case
|
||||||
|
(wired to a command or deleted). The README's "the version that ships is the
|
||||||
|
version that's actually been independently reviewed" claim is removed and
|
||||||
|
replaced with an honest framing. Delivered **phased** per §9: Phase 0
|
||||||
|
(correctness + honesty) → Phase 1 (usable by a non-author) → Phase 2 (coverage
|
||||||
|
gaps) → Phase 3 (long-form earn / redundancy measurement).
|
||||||
|
|
||||||
|
## Non-Goals
|
||||||
|
|
||||||
|
- **Not** rebuilding the plugin or rewriting the long-form engine. Fork-1 decision
|
||||||
|
is **KEEP** the long-form stack; trim only where it measurably improves quality.
|
||||||
|
- **Not** proving the long-form pipeline runs end-to-end — refuted by the operator
|
||||||
|
correction; it has shipped two editions. The audit's "never run" framing, the §2
|
||||||
|
"Long-form stack: never executed" row, and teardown spine A's "accretion without
|
||||||
|
dogfooding" premise are **dropped** and must not anchor any work.
|
||||||
|
- **Not** adding a manual-entry feature for saves/dwell. The saves/dwell decision
|
||||||
|
is the **honesty-fix only** — downgrade the claims; do not build a measurement
|
||||||
|
surface for them (operator decision, 2026-05-29).
|
||||||
|
- **Not** building LinkedIn auto-publish, an analytics-API integration, or any
|
||||||
|
paid/remote service. Boundaries are to be **disclosed**, not engineered away.
|
||||||
|
- **Not** any enterprise feature (web dashboard, fleet policy, ticketing) — this is
|
||||||
|
a solo project; those are fork-and-own.
|
||||||
|
- **Not** changing the `/linkedin:*` command invocation surface for short-form
|
||||||
|
unless a fix requires it; the short-form feed engine is the part that works.
|
||||||
|
- **Not** writing the Maskinrommet article in this work — that is downstream of a
|
||||||
|
clean, honest plugin.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- **Opus on everything** — all phases, all subagents, all loops (standing
|
||||||
|
operator default; overrides any model-tiering table). Voyage = Opus always.
|
||||||
|
- **No hidden costs** — any `/trekplan` run or Workflow with many agents MUST be
|
||||||
|
cost-warned with explicit operator yes before it runs. The operator is present.
|
||||||
|
- **Verification duty (the article will publish)** — every external claim that
|
||||||
|
ships is verified against a current primary or credible source before it is
|
||||||
|
written; gaps are marked "Not verified" or omitted, never filled with a guess;
|
||||||
|
no sales language in technical text. Reproduce the audit's key external findings
|
||||||
|
first-hand (§10 items 2–4), do not inherit them from the report.
|
||||||
|
- **Three-doc rule** — any feature change pushed to Forgejo updates all three doc
|
||||||
|
levels in the same change: plugin `README.md`, plugin `CLAUDE.md`, root
|
||||||
|
`README.md`.
|
||||||
|
- **Version sync** — on any version bump, grep the old version and update every
|
||||||
|
reference (package/manifest, README badges, CHANGELOG, CLAUDE.md, SKILL.md,
|
||||||
|
STATE.md counts).
|
||||||
|
- **Hook editing** — edit `hooks/hooks.template.json` + `hooks/prompts/*.md`, then
|
||||||
|
run `python3 hooks/scripts/compile-hooks.py`; never edit `hooks.json` directly.
|
||||||
|
- **bash 3.2 + Node-only hooks** — all shell is bash-3.2-compatible; hooks are
|
||||||
|
Node `.mjs`, cross-platform, zero npm dependencies.
|
||||||
|
- **Cross-repo `maskinrommet/`** — writing there requires an explicit instruction;
|
||||||
|
this work touches only the plugin repo.
|
||||||
|
- **Audit report stays local** — `docs/critical-review-2026-05-29.local.md` is not
|
||||||
|
committed until the article is out.
|
||||||
|
|
||||||
|
## Preferences
|
||||||
|
|
||||||
|
- Phased delivery following the §9 roadmap (Phase 0 → 1 → 2 → 3); one phase is a
|
||||||
|
natural plan-step boundary, executed one Voyage session at a time.
|
||||||
|
- Per execution step, choose the engine deliberately: inline · `Agent` · `Workflow`
|
||||||
|
(tightly-scoped fan-out for heavy steps only).
|
||||||
|
- Fix the **substrate first**: `references/algorithm-signals-reference.md` is the
|
||||||
|
file the contradictions propagate from — correct it once and the agents/commands
|
||||||
|
inherit the fix.
|
||||||
|
- Reframe "ritual vs craft" in the long-form critique as **trim-for-quality**, not
|
||||||
|
justify-existence (the stack is exercised machinery).
|
||||||
|
- Honesty-reframe of the README "independently reviewed" claim happens **in this
|
||||||
|
plan**, not as an out-of-band edit now (operator-confirmed).
|
||||||
|
- Generalize cleanly: parameterize the series path via env-var/config with a
|
||||||
|
documented, non-private default; keep all voice profiles (author's and any
|
||||||
|
adopter's) local-only and gitignored.
|
||||||
|
|
||||||
|
## Non-Functional Requirements
|
||||||
|
|
||||||
|
- **Zero new npm dependencies** in hooks/scanners/scripts (Node built-ins + `node:test`).
|
||||||
|
- **Fresh-clone clean** — analytics CLI and lint both succeed on a clone with no
|
||||||
|
prior `npm install` state assumed (CLI surfaces the install step as first-class).
|
||||||
|
- **No PII in committed files** — the shipped voice profile contains no real name,
|
||||||
|
avoid-list, or identifying vocabulary; ownership-neutral placeholder markers only.
|
||||||
|
- **Backward-compatible state** — any state-file change to
|
||||||
|
`~/.claude/linkedin-studio.local.md` is additive; existing editions/queues keep working.
|
||||||
|
- **Cross-platform** — clipboard, hooks, and any new scripts run on macOS + Linux + WSL.
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
*Each is falsifiable by a command or a specific observation. Grouped by phase.*
|
||||||
|
|
||||||
|
**Phase 0 — correctness + honesty**
|
||||||
|
|
||||||
|
- Analytics CLI runs from any CWD on a fresh clone: from a directory other than
|
||||||
|
the plugin root, `/linkedin:report`'s underlying CLI invocation exits 0 (no
|
||||||
|
`ERR_MODULE_NOT_FOUND`) — verified by running the documented invocation after a
|
||||||
|
clean checkout with the install step performed as instructed.
|
||||||
|
- One magnitude per algorithm effect: `grep -rn` for the comment multiplier,
|
||||||
|
carousel engagement %, and link-penalty % across `references/` + `agents/` +
|
||||||
|
`commands/` returns a **single** value (or one labelled range) per effect — no
|
||||||
|
"15x vs 5x", no "6.6% vs 1.92%", no "40-50% vs 25-40%".
|
||||||
|
- `references/algorithm-signals-reference.md` carries a **per-claim source +
|
||||||
|
confidence column**; every agent/command that states a signal cites it rather
|
||||||
|
than restating a bare number.
|
||||||
|
- The "360Brew, January 2026" / "−40-60% before anyone sees it" claims are
|
||||||
|
downgraded to sourced direction only: no asserted deployed-model name, no
|
||||||
|
asserted Jan-2026 switch date, no unsourced reach figure (README + `commands/profile.md`).
|
||||||
|
- `assets/voice-samples/authentic-voice-samples.md` contains **no real PII**
|
||||||
|
(no author name, no real avoid-list); a placeholder is detected by
|
||||||
|
ownership-neutral markers; the real profile lives at a gitignored `*.local.md`;
|
||||||
|
`setup.md` **overwrites** rather than merges.
|
||||||
|
- `scripts/test-runner.sh` exits **0** on the healthy repo, globs the real
|
||||||
|
`agents/` layout, derives `EXPECTED_AGENTS` from `ls agents/` with a
|
||||||
|
length-equality assertion, and **fails (non-zero)** when an agent file is
|
||||||
|
added or removed without registration (provable by a temporary add/remove).
|
||||||
|
- `commands/ab-test.md` has **no** literal `Significant? Yes/No` column; confidence
|
||||||
|
is capped at "directional" below ~50 samples per variant; the "20% significance
|
||||||
|
rule" wording is gone.
|
||||||
|
- Saves/dwell claims are downgraded to honest wording **[refined by research/02]**:
|
||||||
|
no report or strategy surface tells the user to optimize a signal the data model
|
||||||
|
cannot populate. Accurate framing: saves ARE visible in native LinkedIn post
|
||||||
|
analytics (since ~Sept 2025, count-only) but there is **no self-serve API** to pull
|
||||||
|
them, so the tool does not auto-track them (point the user to the native number);
|
||||||
|
dwell is internal-only for organic posts. The `report.md` "Saves (10x weight) …
|
||||||
|
highest-impact" line is reframed accordingly.
|
||||||
|
|
||||||
|
**Phase 1 — usable by a non-author**
|
||||||
|
|
||||||
|
- The series root is read from an env-var/config with a **documented** default
|
||||||
|
that is **not** a maintainer-private absolute path (`grep -rn '/Users/ktg'` over
|
||||||
|
shipped — non-`*.local.*` — files returns 0 hits in long-form config/commands).
|
||||||
|
- The Norwegian-language review layer is gated/parameterized so it does not grade
|
||||||
|
English prose against Norwegian rules for a non-Norwegian adopter (language is a
|
||||||
|
configurable input, not a hardcoded lock).
|
||||||
|
- README + the relevant commands state the boundaries up front **[refined by
|
||||||
|
research/02]**, each as plain prose, not buried and **dated ("as of 2026-05")**:
|
||||||
|
(a) post-level analytics API for personal profiles EXISTS but is partner-gated
|
||||||
|
(vetted Community Management API + verified org + Page) — not self-serve; CSV is the
|
||||||
|
practical floor; (b) auto-publish to a personal profile IS technically possible
|
||||||
|
self-serve (`w_member_social`) but is **deliberately not built** — a design + ToS
|
||||||
|
choice, not an API impossibility (do NOT write "cannot auto-publish"); (c) dwell not
|
||||||
|
exportable for organic posts. The calendar/queue/"publish" wording is reconciled so
|
||||||
|
it never implies the tool auto-posts.
|
||||||
|
- Discoverability counts reconcile: the auto-activating router skill
|
||||||
|
`skills/linkedin-studio/SKILL.md` no longer calls it the "thought leadership plugin",
|
||||||
|
lists the **real** agent count, and routes to `newsletter`/`headless-review`/
|
||||||
|
`pivot`/`react`; onboarding's "25 commands" and the pillar-count disagreement
|
||||||
|
(`setup.md` 5 vs `onboarding.md` 3–5) are aligned to the real numbers.
|
||||||
|
- README contains **no** "the version that ships is the version that's actually
|
||||||
|
been independently reviewed" string; an honest framing replaces it.
|
||||||
|
|
||||||
|
**Phase 2 — highest-leverage coverage gaps**
|
||||||
|
|
||||||
|
- A wired first-hour/reply surface exists, invoked by a command (not orphan prose),
|
||||||
|
with tracked state: a target list + draft comments + a timestamped first-hour
|
||||||
|
plan persisted in plugin state.
|
||||||
|
- A short-form de-AI / differentiation gate fires on short-form creation
|
||||||
|
(post/quick/react/carousel/video) — provable by a hook/gate or an agent the
|
||||||
|
command actually invokes (`grep -rl 'subagent_type: linkedin-studio:differentiation-checker' commands/` ≥ 1, or an equivalent wired gate).
|
||||||
|
- Each of the 11 orphan agents is **either** invoked by ≥1 command
|
||||||
|
(`grep -rl 'subagent_type: linkedin-studio:<name>' commands/` ≥ 1) **or** deleted;
|
||||||
|
the agent count in CLAUDE.md / README / SKILL.md equals `ls agents/*.md | wc -l`.
|
||||||
|
- Video gate is a **quality gate, not a reach-push [refined by research/03]**: MP4
|
||||||
|
default (warn-only on MOV/AVI) + within-limits; **captions enforced/strongly
|
||||||
|
recommended** (SRT or native auto-captions); aspect ratio is **guidance, not a hard
|
||||||
|
gate — 4:5 / 1:1 preferred for broad distribution, 9:16 mobile-only opt-in** (the
|
||||||
|
"4:5 deprioritized" self-contradiction is fixed toward "4:5 preferred"); **no
|
||||||
|
"3-second hook" rule** (replaced by "front-load value for muted autoplay") and **no
|
||||||
|
"video maximizes reach" copy** (per-video reach is declining; documents out-engage
|
||||||
|
video).
|
||||||
|
- Profile, newsletter distribution, and outreach gain the §5 surfaces **[newsletter
|
||||||
|
refined by research/03]** — each at least a wired command surface, not prose-only:
|
||||||
|
profile-SEO fields; **honest** newsletter distribution (bypasses organic feed ranking
|
||||||
|
via **one deduplicated** notification per subscriber per edition — NOT "triple
|
||||||
|
notification"; one-time launch-blast + a **~1–2K follower floor**; realistic
|
||||||
|
cold-start floors of 0–100 subs in months 1–3; disclose non-export / no-canonical /
|
||||||
|
no-read-analytics / per-subscriber decay); outreach tracked contact/pipeline state.
|
||||||
|
|
||||||
|
**Phase 3 — long-form earn / redundancy measurement**
|
||||||
|
|
||||||
|
- `/linkedin:newsletter` shows a "multi-session, multi-gate, ~N-hour" expectation
|
||||||
|
banner at the top.
|
||||||
|
- Long-form review-pass overlap is **measured** (a recorded comparison of what each
|
||||||
|
reviewer/gate actually catches) and the redundancy is trimmed where the measurement
|
||||||
|
does not justify it — with the measurement committed as evidence, not an assertion.
|
||||||
|
The measurement source is an **in-repo fixture edition** by default; reading a
|
||||||
|
shipped edition from `maskinrommet/serier` requires an explicit operator instruction
|
||||||
|
per the cross-repo constraint, so the plan must not assume that read.
|
||||||
|
|
||||||
|
**Cross-cutting (every phase)**
|
||||||
|
|
||||||
|
- All three doc levels updated in the same change (plugin README, plugin CLAUDE.md,
|
||||||
|
root README); `grep` for the prior version string returns 0 stale hits after any bump.
|
||||||
|
- The plugin's own lint (`scripts/test-runner.sh`, rebuilt) and any `node --test`
|
||||||
|
suites pass on the final state.
|
||||||
|
|
||||||
|
## Research Plan
|
||||||
|
|
||||||
|
*The internal/file-level fixes (analytics-CLI crash, dead lint, voice-leak,
|
||||||
|
orphan-agent wiring, A/B significance, doc counts) need **no** external research —
|
||||||
|
they are reproducible from the repo. The fixes that touch **external claims** do:
|
||||||
|
the audit's external bar is explicitly "thin / re-check before publishing" (§10),
|
||||||
|
and the operator's verification duty (the article publishes) mandates first-hand
|
||||||
|
sourcing. Three topics, each feeding specific plan steps.*
|
||||||
|
|
||||||
|
### Topic 1: Canonical 2026 LinkedIn algorithm signal statement
|
||||||
|
|
||||||
|
- **Why this matters:** This is the substrate fix. Phase-0 steps that reconcile the
|
||||||
|
contradictory stats (comment multiplier, carousel %, link penalty), reframe the
|
||||||
|
first-comment advice, downgrade the "360Brew / Jan-2026 / −40-60%" premise, and
|
||||||
|
widen the "golden hour" depend on knowing what current defensible sources actually
|
||||||
|
support — magnitude, direction, and confidence per signal. The audit (§3/§3b) shows
|
||||||
|
the existing numbers are a mix of vendor-blog estimates and self-contradiction; the
|
||||||
|
reconciled statement every command/agent will cite cannot be authored without this.
|
||||||
|
- **Research question:** "What does the 2026 LinkedIn feed-ranking system actually
|
||||||
|
reward — for the comment-vs-reaction weighting, document/carousel engagement rate,
|
||||||
|
external-link reach effect and the current first-comment-workaround status, the
|
||||||
|
early-engagement ('golden hour') window incl. delayed/evergreen reinjection, and
|
||||||
|
the deployed ranking model's verifiable name and deployment date — with a primary
|
||||||
|
or credible source and a confidence level for each claim?"
|
||||||
|
- **Suggested invocation:** `/trekresearch --project docs/remediation/ --external "What does the 2026 LinkedIn feed-ranking system actually reward — comment-vs-reaction weighting, document/carousel engagement rate, external-link reach effect and first-comment status, the early-engagement window incl. delayed reinjection, and the deployed ranking model's verifiable name and date — with a source and confidence per claim?"`
|
||||||
|
- **Required for plan steps:** Phase-0 "reconcile algorithm stats to one sourced
|
||||||
|
statement", "reframe external-link penalty", "downgrade 360Brew premise"; the
|
||||||
|
per-claim source/confidence column in `algorithm-signals-reference.md`.
|
||||||
|
- **Confidence needed:** high
|
||||||
|
- **Estimated cost:** deep — with contrarian + Gemini triangulation (the audit
|
||||||
|
already shows vendor-blog noise and two passes disagreeing on the model name).
|
||||||
|
- **Scope hint:** external
|
||||||
|
|
||||||
|
### Topic 2: Personal-profile analytics + auto-publish boundaries (2026)
|
||||||
|
|
||||||
|
- **Why this matters:** Phase-1 "state the boundaries honestly" and Phase-2 "saves
|
||||||
|
honesty-fix" need the current, verifiable status of three things the plugin makes
|
||||||
|
architectural claims about: whether a personal profile can self-serve a Member Post
|
||||||
|
Analytics API (§3b #11 marks the plugin's "individuals cannot → CSV only" premise
|
||||||
|
as *outdated*, not merely a constraint), whether per-post saves are visible in the
|
||||||
|
LinkedIn UI (the audit cites Sept-2025), and what auto-publish is/isn't possible for
|
||||||
|
a personal profile via API. Wrong boundary statements would replace one false claim
|
||||||
|
with another.
|
||||||
|
- **Research question:** "As of 2026, can a personal LinkedIn profile self-serve
|
||||||
|
post-level analytics via an API (Member Post Analytics or partner platforms), are
|
||||||
|
per-post saves visible in the native UI, and can a personal profile auto-publish
|
||||||
|
posts via any API — and what are the exact access constraints for a solo user
|
||||||
|
without partner/company-page access?"
|
||||||
|
- **Suggested invocation:** `/trekresearch --project docs/remediation/ --external "As of 2026, can a personal LinkedIn profile self-serve post-level analytics via an API, are per-post saves visible in the native UI, and can a personal profile auto-publish via any API — with the exact constraints for a solo user without partner/company-page access?"`
|
||||||
|
- **Required for plan steps:** Phase-1 "honest boundary statements in README +
|
||||||
|
commands"; Phase-2 "saves/dwell honesty-fix wording"; the §5 scheduling-boundary
|
||||||
|
disclosure.
|
||||||
|
- **Confidence needed:** high
|
||||||
|
- **Estimated cost:** standard — agent swarm.
|
||||||
|
- **Scope hint:** external
|
||||||
|
|
||||||
|
### Topic 3: Coverage-gap feature specs — video, de-AI signal, newsletter distribution
|
||||||
|
|
||||||
|
- **Why this matters:** Phase-2 builds new **wired** surfaces, so they need current
|
||||||
|
specs, not prose. Three inputs: the hard requirements for short-form video that
|
||||||
|
LinkedIn actually rewards in 2026 (aspect ratio, resolution, hook timing,
|
||||||
|
caption/SRT), what concretely triggers the templated-AI / engagement-bait down-rank
|
||||||
|
the de-AI gate must guard against, and the real newsletter-distribution mechanics
|
||||||
|
(the triple-notification leverage, cadence discipline, realistic cold-start numbers)
|
||||||
|
the long-form stack currently omits.
|
||||||
|
- **Research question:** "For LinkedIn in 2026, what are the hard short-form video
|
||||||
|
requirements the algorithm rewards (aspect ratio, resolution, hook timing,
|
||||||
|
captions), what specifically triggers the templated-AI / engagement-bait down-rank
|
||||||
|
signal, and what are the newsletter-distribution mechanics (notification leverage,
|
||||||
|
cadence, realistic cold-start subscriber numbers) a creator should follow?"
|
||||||
|
- **Suggested invocation:** `/trekresearch --project docs/remediation/ --external "For LinkedIn in 2026: hard short-form video requirements the algorithm rewards (aspect ratio, resolution, hook timing, captions); what triggers the templated-AI/engagement-bait down-rank; and newsletter-distribution mechanics (notification leverage, cadence, realistic cold-start numbers)?"`
|
||||||
|
- **Required for plan steps:** Phase-2 "video 9:16 enforcement", "short-form de-AI
|
||||||
|
gate", "newsletter distribution surface".
|
||||||
|
- **Confidence needed:** medium
|
||||||
|
- **Estimated cost:** standard — agent swarm.
|
||||||
|
- **Scope hint:** external
|
||||||
|
|
||||||
|
## Open Questions / Assumptions
|
||||||
|
|
||||||
|
- **[ASSUMPTION]** The plan will order work as the §9 phases (0→1→2→3), one phase
|
||||||
|
per Voyage session, with `/trekreview` as the final release gate. To be confirmed
|
||||||
|
when `/trekplan` produces the step list.
|
||||||
|
- **[ASSUMPTION]** Orphan-agent case-by-case decisions (wire vs delete) are made
|
||||||
|
**in the plan phase**, not now; the operator answered "case by case".
|
||||||
|
- **[ASSUMPTION]** Versioning shape (single major bump vs phased minor releases) is a
|
||||||
|
plan-phase decision; not pre-committed here.
|
||||||
|
- **[OPEN — narrow, from the operator correction]** Whether the v3.1 headless
|
||||||
|
cold-review layer (Step 6.5) co-ran in a *shipped* edition. Moot for the README
|
||||||
|
(the "independently reviewed" claim is removed per fork-4), but worth noting so the
|
||||||
|
honesty-reframe wording is accurate.
|
||||||
|
- **[ASSUMPTION]** Research Topic 1's "deep" cost (contrarian + Gemini) will be
|
||||||
|
cost-warned before it runs, per the no-hidden-costs rule.
|
||||||
|
|
||||||
|
## Prior Attempts
|
||||||
|
|
||||||
|
The baseline audit itself is the prior work: a cold adversarial Workflow
|
||||||
|
(`wf_8623b3ea-682`, 28 agents) plus an independent Gemini Deep Research
|
||||||
|
triangulation pass, delivered as `docs/critical-review-2026-05-29.local.md`. After
|
||||||
|
delivery the operator supplied first-hand corrections that refuted the audit's
|
||||||
|
flagship "never run" finding and locked four scope decisions (keep long-form +
|
||||||
|
trim for quality; regular use confirmed; generalize the plugin; remove the README
|
||||||
|
"independently reviewed" claim). No remediation code has been written yet — this
|
||||||
|
brief is the first step of the fix. The plugin reached v3.1.0 via six versions in
|
||||||
|
~48 hours (the audit's "accretion" meta-finding), which is *why* the correctness
|
||||||
|
and honesty pass is needed before further feature accretion.
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
|
||||||
|
- **Created:** 2026-05-29
|
||||||
|
- **Interview turns:** 3 (scope boundary, orphan-agent disposition, saves/dwell disposition — the four headline forks were pre-locked by the operator correction)
|
||||||
|
- **Auto-research opted in:** no
|
||||||
|
- **Source:** trekbrief interview (driven by the locked operator corrections; no full re-interview per the operating model)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How to continue
|
||||||
|
|
||||||
|
Manual (default):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run each research topic (order does not matter):
|
||||||
|
/trekresearch --project docs/remediation/ --external "What does the 2026 LinkedIn feed-ranking system actually reward — comment-vs-reaction weighting, document/carousel engagement rate, external-link reach effect and first-comment status, the early-engagement window incl. delayed reinjection, and the deployed ranking model's verifiable name and date — with a source and confidence per claim?"
|
||||||
|
/trekresearch --project docs/remediation/ --external "As of 2026, can a personal LinkedIn profile self-serve post-level analytics via an API, are per-post saves visible in the native UI, and can a personal profile auto-publish via any API — with the exact constraints for a solo user without partner/company-page access?"
|
||||||
|
/trekresearch --project docs/remediation/ --external "For LinkedIn in 2026: hard short-form video requirements the algorithm rewards (aspect ratio, resolution, hook timing, captions); what triggers the templated-AI/engagement-bait down-rank; and newsletter-distribution mechanics (notification leverage, cadence, realistic cold-start numbers)?"
|
||||||
|
|
||||||
|
# Then plan:
|
||||||
|
/trekplan --project docs/remediation/
|
||||||
|
|
||||||
|
# Then execute:
|
||||||
|
/trekexecute --project docs/remediation/
|
||||||
|
```
|
||||||
|
|
||||||
|
Auto (opt-in during `/trekbrief`): research and planning run automatically; only
|
||||||
|
execution is manual. **Not used here** — per the operating model, `/trekplan` is
|
||||||
|
invoked separately in the foreground after operator approval, with an explicit
|
||||||
|
cost warning.
|
||||||
908
plugins/linkedin-studio/docs/remediation/plan.md
Normal file
908
plugins/linkedin-studio/docs/remediation/plan.md
Normal file
|
|
@ -0,0 +1,908 @@
|
||||||
|
# LinkedIn Studio — Baseline-Audit Remediation
|
||||||
|
|
||||||
|
> **Plan quality: A−** (88/100, post-revision) — APPROVE_WITH_NOTES
|
||||||
|
> (plan-critic: REVISE → 3 blockers + actionable majors fixed; scope-guardian: ALIGNED)
|
||||||
|
>
|
||||||
|
> Generated by trekplan v5.1.1 on 2026-05-29
|
||||||
|
> plan_version: 1.7
|
||||||
|
> Project: `docs/remediation/` · Brief: `docs/remediation/brief.md` · Research: `research/01..03`
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The baseline audit (`docs/critical-review-2026-05-29.local.md`, cold hostile read + Gemini
|
||||||
|
triangulation) found file-reproducible **correctness and honesty defects** in linkedin-studio,
|
||||||
|
and a flagship engine **shipped bespoke-as-general** (Norwegian-locked, private series path,
|
||||||
|
non-shipping contract). The operator's correction refuted the audit's "long-form never ran"
|
||||||
|
premise (two editions shipped via `/linkedin:newsletter`), so this is **not** "prove the
|
||||||
|
pipeline runs." It is: fix what is file-reproducibly broken, make the plugin honest about what
|
||||||
|
it knows and what it cannot do, and make it usable by someone who is not the author. The stakes
|
||||||
|
are trust — the operator writes long-form regularly, will share the plugin actively, and will
|
||||||
|
publish a Maskinrommet article about it, so every algorithm claim that ships is a public claim.
|
||||||
|
|
||||||
|
Three research briefs (run cold, this session) reconciled the external bar and **corrected
|
||||||
|
several of the audit's own feature premises**: there is no publishable name/date for the
|
||||||
|
deployed ranking model; the engagement order is saves > shares > quality-comments > reactions
|
||||||
|
with dwell + topic-relevance the only officially-named signals; documents (~7%) lead video
|
||||||
|
(declining); the link effect is correlational (LinkedIn denies intent) and the first-comment
|
||||||
|
"fix" is contested; **auto-publish IS self-serve-possible** (so "cannot auto-publish" would be
|
||||||
|
a new false claim); **saves ARE visible in the native UI** (so "untrackable" is stale); a hard
|
||||||
|
**9:16 video gate is wrong** (4:5/1:1 preferred; captions are the enforceable spec); and the
|
||||||
|
newsletter **"triple-notification" is deduplicated** (the honest benefit is "bypasses feed
|
||||||
|
ranking", with a ~1–2K follower floor). Delivered phased per audit §9.
|
||||||
|
|
||||||
|
## Architecture Diagram
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
subgraph "Phase 0 — guard + substrate + leaks"
|
||||||
|
LINT[test-runner.sh rebuilt<br/>dynamic counts + drift greps]
|
||||||
|
SUB[algorithm-signals-reference.md<br/>canonical + source/confidence]
|
||||||
|
CLI[analytics getAnalyticsRoot<br/>anchor on .claude-plugin/]
|
||||||
|
VOICE[voice placeholder + sentinel<br/>real → gitignored .local.md]
|
||||||
|
SUB --> CITERS[~40 citing files reconciled]
|
||||||
|
LINT -.guards.-> CITERS
|
||||||
|
LINT -.guards.-> VOICE
|
||||||
|
end
|
||||||
|
subgraph "Phase 1 — usable by non-author"
|
||||||
|
PATH[series path default + de-brand render]
|
||||||
|
LANG[language-lock → configurable]
|
||||||
|
BOUND[honest dated boundaries]
|
||||||
|
COUNTS1[discoverability counts + SKILL rename]
|
||||||
|
end
|
||||||
|
subgraph "Phase 2 — coverage gaps"
|
||||||
|
ORPH[11 orphans wire/delete]
|
||||||
|
DEAI[de-AI: wire differentiation-checker<br/>+ extend voice-guardian]
|
||||||
|
VID[video gate: captions + aspect guidance]
|
||||||
|
ENG[first-hour/reply loop + state]
|
||||||
|
DIST[honest newsletter distribution]
|
||||||
|
ORPH --> DEAI
|
||||||
|
ORPH --> ENG
|
||||||
|
end
|
||||||
|
subgraph "Phase 3 — long-form earn"
|
||||||
|
BANNER[newsletter time/effort banner]
|
||||||
|
OVERLAP[review-pass overlap measured + trimmed]
|
||||||
|
end
|
||||||
|
FINAL[version bump + counts + three-doc + CHANGELOG]
|
||||||
|
CITERS --> FINAL
|
||||||
|
COUNTS1 --> FINAL
|
||||||
|
DIST --> FINAL
|
||||||
|
OVERLAP --> FINAL
|
||||||
|
LINT -.asserts.-> FINAL
|
||||||
|
```
|
||||||
|
|
||||||
|
## Codebase Analysis
|
||||||
|
|
||||||
|
- **Tech stack:** Markdown-first Claude Code plugin (commands/agents/references as `.md` with
|
||||||
|
YAML frontmatter) + Node.js ESM (`.mjs`) hooks (zero npm deps, `node:test`) + one Python
|
||||||
|
hook-compiler + one bash 3.2 structural lint + a TypeScript analytics CLI (`scripts/analytics/`,
|
||||||
|
run via `tsx`). 214 source files (medium).
|
||||||
|
- **Key patterns:** commands invoke agents ONLY via `Task` with `subagent_type:
|
||||||
|
linkedin-studio:<name>` (namespaced; bare fails); hooks compiled from
|
||||||
|
`hooks.template.json` + `prompts/*.md` via `compile-hooks.py` (never edit `hooks.json`);
|
||||||
|
content gates are advisory (always exit 0) via `content-gatekeeper.mjs` + `isLinkedInContent`;
|
||||||
|
reference docs cite algorithm facts by path but **restate numbers inline** (so a substrate fix
|
||||||
|
only propagates if citers are converted to cite-not-restate); state mutations via pure
|
||||||
|
functions in `state-updater.mjs`; agents Sonnet except the 6 Opus long-form gates + 1 Haiku
|
||||||
|
(`post-feedback-monitor`, the lone deviation).
|
||||||
|
- **Relevant files:** `scripts/test-runner.sh` (dead lint); `references/algorithm-signals-reference.md`
|
||||||
|
(the substrate, cited by 16 files); `scripts/analytics/src/utils/storage.ts:17-22`
|
||||||
|
(`getAnalyticsRoot` depth bug); `assets/voice-samples/authentic-voice-samples.md` +
|
||||||
|
`hooks/scripts/personalization-score.mjs:23` + `.claude-plugin/plugin.json:6` (voice/PII);
|
||||||
|
`commands/ab-test.md:301-317`; `commands/report.md`/`import.md` (CLI invocation);
|
||||||
|
`config/edition-state.template.json:4` + `commands/newsletter.md:36,138` + `render/build-linkedin.mjs:350`
|
||||||
|
+ `render/build-carousel.mjs` (series path + brand); `agents/language-reviewer.md` (lang lock);
|
||||||
|
the 11 orphan agents; `commands/video.md` + `references/linkedin-formats.md` (aspect contradiction);
|
||||||
|
`skills/*/SKILL.md` + `commands/onboarding.md`/`setup.md`/`linkedin.md` (counts); `README.md:120,128`.
|
||||||
|
- **Reusable code:** `hooks/scripts/state-updater.mjs` (add pure mutation fns for new tracked
|
||||||
|
state); `hooks/prompts/voice-guardian.md` (already has AI-pattern detection — extend, don't
|
||||||
|
duplicate); `agents/differentiation-checker.md` (the orphan to wire as the de-AI gate);
|
||||||
|
`agents/__tests__/*-fixture.test.mjs` (shape-test pattern); `scripts/analytics/tests/storage.test.ts`
|
||||||
|
(tmpdir + env-override pattern for the CWD-fix test); `agents/content-reviewer.md` (Opus
|
||||||
|
gate-agent template, if any new agent is needed); `compile-hooks.py --check` (drift guard to
|
||||||
|
wire into the lint).
|
||||||
|
- **Recent git activity:** solo project; six versions in ~48h (the audit's accretion finding) —
|
||||||
|
v3.1.0 added the cold-review trio. No CI runs the lint.
|
||||||
|
|
||||||
|
## Research Sources
|
||||||
|
|
||||||
|
| Topic | Source(s) | Key findings | Confidence |
|
||||||
|
|-------|-----------|--------------|------------|
|
||||||
|
| Algorithm signals (research/01) | LinkedIn Eng blog, arXiv 2501.16450, Socialinsider 1.3M, Ordinal 900K, Entrepreneur (Lorenzetti), van der Blom 1.8M + Gemini | No publishable model name/date; order saves>shares>comments>reactions; dwell+topic-relevance the only named signals; docs ~7% top, video declining; link ~38% correlational, intent disputed; golden window 60–90 min; AI-slop down-rank officially confirmed | high (direction) / medium (magnitude) |
|
||||||
|
| Analytics + publish boundaries (research/02) | Microsoft Learn (li-lms-2026-05), LinkedIn Help, Marcus Noble, GitHub #35 | Analytics API exists but partner-gated (not self-serve); **auto-publish IS self-serve via `w_member_social`** (clipboard-stop is a choice/ToS, not a wall); **saves visible in UI since ~Sept 2025** + API POST_SAVE (gated); dwell internal-only for organic | high |
|
||||||
|
| Coverage-gap specs (research/03) | LinkedIn Help, Socialinsider, aspectratiocalculator, LinkedIn newsletter FAQ, The Science Marketer | 9:16 not a clean win (4:5/1:1 preferred, desktop crop); captions the enforceable spec; "3-sec hook" folklore; video reach −36% YoY; newsletter notifications **deduplicated** (not triple); one-time launch invite → ~1–2K floor; non-exportable lock-in | high (mechanics) / medium (cold-start) |
|
||||||
|
|
||||||
|
## Implementation Plan
|
||||||
|
|
||||||
|
> **Ordering rationale (from risk-assessor):** the lint is rebuilt FIRST because it is the
|
||||||
|
> tripwire every later step relies on (it already fails 29 checks against the real layout, so
|
||||||
|
> nothing is guarded today). The substrate stat file is reconciled SECOND so all citers inherit
|
||||||
|
> one source. Voice-leak + placeholder-detection are sequenced TOGETHER (coupled criticals).
|
||||||
|
> The version/counts/three-doc reconciliation is LAST so it captures everything.
|
||||||
|
|
||||||
|
### Step 1: Rebuild the dead structural lint
|
||||||
|
|
||||||
|
- **Files:** `scripts/test-runner.sh`
|
||||||
|
- **Changes:** Replace the stale hardcoded `EXPECTED_AGENTS` (14), `EXPECTED_COMMANDS` (lists 4 deleted commands, uses wrong `commands/linkedin:NAME.md` layout), the `skills/<name>.md` path check (real: `skills/<name>/SKILL.md`), the `personalization-scorer` reference, the fabricated `auto_discover` plugin.json field, and the missing `docs/DEVELOPMENT-LOG.md` assert. Derive counts dynamically: `AGENTS=$(ls agents/*.md | wc -l)` with a length-equality assert against the CLAUDE.md "Telling" block (19/26/25/6/9); glob `commands/*.md`, `references/*.md`, `skills/*/SKILL.md`; assert each command/agent has `name:`/`description:` frontmatter; call `python3 hooks/scripts/compile-hooks.py --check` for hook drift. bash 3.2-safe (plain arrays, no `declare -A`). **Scope note (gemini Pass-2): this step rebuilds the STRUCTURAL lint only (counts/layout/frontmatter/hook-drift) — all of which are already broken today, so the lint can go green immediately and guard Steps 4–21. The stat-consistency grep is added to the lint in Step 3 (after reconciliation makes it pass — adding it here would fail this step's own Verify, since the contradictions still exist) and the version-consistency grep in Step 21. This avoids the chicken-and-egg the lint-first ordering otherwise risks.**
|
||||||
|
- **Reuses:** existing `pass()/fail()/warn()` + `PLUGIN_ROOT` skeleton in `test-runner.sh`; `compile-hooks.py --check` (already works).
|
||||||
|
- **Test first:**
|
||||||
|
- File: manual red/green (bash validator, not node:test)
|
||||||
|
- Verifies: exits 0 on healthy repo; exits 1 when an agent file is removed
|
||||||
|
- Pattern: the existing section structure in `scripts/test-runner.sh`
|
||||||
|
- **Verify:** `bash scripts/test-runner.sh; echo "exit=$?"` → expected: `exit=0`; then prove the registration guard bites with a self-restoring one-liner: `git mv agents/trend-spotter.md /tmp/x; bash scripts/test-runner.sh; rc=$?; git mv /tmp/x agents/trend-spotter.md; echo "removed-agent exit=$rc"` → `removed-agent exit=1` (restore runs regardless of the lint's `set -e`).
|
||||||
|
- **On failure:** revert — `git checkout -- scripts/test-runner.sh`
|
||||||
|
- **Checkpoint:** `git commit -m "fix(linkedin-studio): rebuild dead structural lint to real v3.1 layout"`
|
||||||
|
- **Manifest:**
|
||||||
|
```yaml
|
||||||
|
manifest:
|
||||||
|
expected_paths:
|
||||||
|
- scripts/test-runner.sh
|
||||||
|
min_file_count: 1
|
||||||
|
commit_message_pattern: "^fix\\(linkedin-studio\\): rebuild dead structural lint"
|
||||||
|
bash_syntax_check:
|
||||||
|
- scripts/test-runner.sh
|
||||||
|
forbidden_paths: []
|
||||||
|
must_contain:
|
||||||
|
- path: scripts/test-runner.sh
|
||||||
|
pattern: "ls agents"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Rebuild the algorithm-signals substrate (canonical, sourced)
|
||||||
|
|
||||||
|
- **Files:** `references/algorithm-signals-reference.md`
|
||||||
|
- **Changes:** Make this the single source of truth. Add a per-claim **Source + Confidence** column to the signal tables (house style: keep the `| Signal | Weight | … |` table genre, widen rows; keep the closing `*Sources:*` footer as bibliography). Apply research/01: (a) engagement order saves > shares > quality-comments > reactions, comment ≈ 2x like (medium), drop the bare "15x/5x" framing → state as ordering + sourced; (b) reconcile the intra-file carousel contradiction (`:72` 1.92% PDF vs `:73` 6.60% multi-image) → documents/carousels top format ~7% (Socialinsider, company-page per-impression), note 1.92% was a personal-profile baseline; (c) link effect ~38% correlational 2026 (band ~19–60%), LinkedIn denies intent, value-first > location; (d) golden window 60–90 min + evergreen resurfacing (drop "24–72h" precision); (e) the model: "an LLM relevance-ranking system is live in 2026" — **no name, no date**; (f) drop the "−40-60% off-topic" figure, keep "profile/topic alignment is a ranking input"; (g) dwell + topic-relevance flagged as the only officially-named signals; (h) buzzwords = editorial guidance, not a reach mechanic.
|
||||||
|
- **Reuses:** existing table format; `references/longform-quality-rules.md` provenance-blockquote style; research/01 §Recommendation as the source-of-truth content.
|
||||||
|
- **Test first:**
|
||||||
|
- File: n/a (content doc) — verification is the Step 1 lint's stat-consistency grep
|
||||||
|
- Verifies: no intra-file contradiction; every numeric claim has a Source/Confidence cell
|
||||||
|
- Pattern: research/01 reconciliation table
|
||||||
|
- **Verify:** `grep -nE '1\.92%|15x more reach|January 2026|360Brew|-40-60%' references/algorithm-signals-reference.md` → expected: no matches (all downgraded); `grep -c 'Confidence' references/algorithm-signals-reference.md` → ≥ 1.
|
||||||
|
- **On failure:** revert — `git checkout -- references/algorithm-signals-reference.md`
|
||||||
|
- **Checkpoint:** `git commit -m "fix(linkedin-studio): reconcile algorithm-signals to one sourced statement"`
|
||||||
|
- **Manifest:**
|
||||||
|
```yaml
|
||||||
|
manifest:
|
||||||
|
expected_paths:
|
||||||
|
- references/algorithm-signals-reference.md
|
||||||
|
min_file_count: 1
|
||||||
|
commit_message_pattern: "^fix\\(linkedin-studio\\): reconcile algorithm-signals"
|
||||||
|
bash_syntax_check: []
|
||||||
|
forbidden_paths: []
|
||||||
|
must_contain:
|
||||||
|
- path: references/algorithm-signals-reference.md
|
||||||
|
pattern: "Confidence"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Propagate the reconciled numbers to all citers
|
||||||
|
|
||||||
|
- **Files:** ENUMERATE AT EXECUTION by running the two greps below first, then edit every hit. Known set (from exploration + plan-critic Blocker 2): `commands/carousel.md`, `commands/profile.md`, `commands/react.md`, `commands/analyze.md`, `commands/monetize.md`, `commands/audit.md`, `commands/strategy.md`, `commands/linkedin.md`, `references/linkedin-formats.md`, `references/linkedin-visual-style.md`, `references/glossary.md`, `references/first-comment-strategy.md`, `references/troubleshooting-guide.md`, `references/linkedin-monetization-strategies.md`, `references/engagement-frameworks.md`, `agents/content-optimizer.md`, `hooks/prompts/content-quality-gate.md`, `hooks/prompts/topic-rotation-gate.md`, `CLAUDE.md`, `README.md`, `skills/linkedin-studio/SKILL.md`, `skills/linkedin-analytics/SKILL.md`, `assets/templates/carousel-templates.md`
|
||||||
|
- **Changes:** First enumerate the real citer set: `grep -rlnE '40-50%|25-40%|6\.6%|1\.92%|15x more reach|January 2026|360Brew|-40-60%' references/ commands/ agents/ skills/ hooks/prompts/ CLAUDE.md README.md` — edit EVERY hit (do not work from a stale hand-list; plan-critic Blocker 2 found `audit.md`/`strategy.md`/`linkedin.md`/`topic-rotation-gate.md` were missing). Replace every restated number with the reconciled value AND a citation to `algorithm-signals-reference.md` (cite-not-restate). Carousel 6.6%/1.92% → "documents/carousels top format (~7%); see algorithm-signals-reference". Link penalty 40-50% / 25-40% → one correlational statement. 360Brew / "January 2026" / "−40-60%" → sourced direction only. **Note (plan-critic Blocker 3): `content-quality-gate.md` and `topic-rotation-gate.md` are loaded at RUNTIME by `content-gatekeeper.mjs` — editing their prose does NOT require `compile-hooks.py` and does NOT change `hooks.json`. No recompile in this step.** **Then (gemini Pass-2) add the stat-consistency grep to `scripts/test-runner.sh`** — the external-link penalty % and carousel % must each appear as ONE value across `references/ commands/ skills/ hooks/prompts/` (fail if ≥2 distinct magnitudes; exclude commented lines). Adding it HERE, after reconciliation, means the lint goes green instead of failing its own gate.
|
||||||
|
- **Reuses:** Step 2's canonical file as the cite target; the Step 1 lint skeleton.
|
||||||
|
- **Test first:**
|
||||||
|
- File: n/a — the Step 1 lint stat-consistency grep is the regression check
|
||||||
|
- Verifies: one magnitude per effect across the tree
|
||||||
|
- Pattern: the lint's penalty-% grep
|
||||||
|
- **Verify:** `grep -rnE '40-50%|25-40%|1\.92%' references/ commands/ skills/ hooks/prompts/ CLAUDE.md README.md | grep -v algorithm-signals-reference` → no matches; `grep -rnE 'January 2026|360Brew' commands/ README.md skills/ hooks/prompts/ CLAUDE.md` → no matches (or only inside a sourced "research model, not deployed" note); spot-check 3 random citers actually cite the reference (cite-not-restate, not just a consistent bare number); `bash scripts/test-runner.sh` → exit 0 (the newly-added stat-grep is green).
|
||||||
|
- **On failure:** revert — `git checkout -- <enumerated files> scripts/test-runner.sh`
|
||||||
|
- **Checkpoint:** `git commit -m "fix(linkedin-studio): propagate reconciled algorithm numbers, cite-not-restate"`
|
||||||
|
- **Manifest:**
|
||||||
|
```yaml
|
||||||
|
manifest:
|
||||||
|
expected_paths:
|
||||||
|
- commands/carousel.md
|
||||||
|
- commands/profile.md
|
||||||
|
- commands/strategy.md
|
||||||
|
- commands/audit.md
|
||||||
|
- scripts/test-runner.sh
|
||||||
|
min_file_count: 5
|
||||||
|
commit_message_pattern: "^fix\\(linkedin-studio\\): propagate reconciled algorithm numbers"
|
||||||
|
bash_syntax_check: []
|
||||||
|
forbidden_paths: []
|
||||||
|
must_contain:
|
||||||
|
- path: commands/carousel.md
|
||||||
|
pattern: "algorithm-signals-reference"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Fix the analytics CLI root-resolution + surface install
|
||||||
|
|
||||||
|
- **Files:** `scripts/analytics/src/utils/storage.ts`, `commands/report.md`, `commands/import.md`, `scripts/analytics/src/cli.ts`
|
||||||
|
- **Changes:** **PRIMARY fix (the actual fresh-clone crash):** `tsx` + `csv-parse` are absent on a fresh clone (gitignored `node_modules`), so `node --import tsx` throws `ERR_MODULE_NOT_FOUND` — surface the install at point-of-use in `report.md`/`import.md`: prepend `cd "${CLAUDE_PLUGIN_ROOT}/scripts/analytics" && npm install --silent` (idempotent) before the `node --import tsx` invocation, and fix the troubleshooting line "Verify tsx is available" → the actual install command. Reconcile `cli.ts` usage text (`node build/cli.js` → the real `tsx src/cli.ts` runtime). **SECONDARY fix (latent correctness bug — `getAnalyticsRoot()` depth):** the `../../../../` in storage.ts:17-22 is calibrated for `build/utils/` but runs under tsx from `src/utils/`, so the *fallback* root is wrong. The shipped commands mask it by always passing `ANALYTICS_ROOT="${CLAUDE_PLUGIN_ROOT}/assets/analytics"` (so it is latent, NOT the crash cause — per plan-critic Blocker 1), but it is wrong for any direct/test invocation. Anchor it on the dir containing `.claude-plugin/plugin.json`. Keep the `ANALYTICS_ROOT` override as the test seam.
|
||||||
|
- **Reuses:** existing `ANALYTICS_ROOT` env handling in storage.ts; `scripts/analytics/tests/storage.test.ts` tmpdir/afterEach pattern.
|
||||||
|
- **Test first:**
|
||||||
|
- File: `scripts/analytics/tests/storage-root.test.ts` (new)
|
||||||
|
- Verifies: default root (no env) anchors on the plugin dir, NOT `scripts/analytics/assets`; `ANALYTICS_ROOT` override returns the resolved env path (red against current code, green after fix)
|
||||||
|
- Pattern: `scripts/analytics/tests/storage.test.ts`
|
||||||
|
- **Verify:** install first, then run from a foreign CWD — `cd scripts/analytics && npm install --silent && cd /tmp && node --import tsx "${CLAUDE_PLUGIN_ROOT:-$OLDPWD}/scripts/analytics/src/cli.ts" report 2>&1 | head` → no `ERR_MODULE_NOT_FOUND`; `cd scripts/analytics && npm install --silent && npm test 2>&1 | tail -3` → storage-root test passes.
|
||||||
|
- **On failure:** revert — `git checkout -- scripts/analytics/ commands/report.md commands/import.md`
|
||||||
|
- **Checkpoint:** `git commit -m "fix(linkedin-studio): anchor analytics root on plugin marker + surface npm install"`
|
||||||
|
- **Manifest:**
|
||||||
|
```yaml
|
||||||
|
manifest:
|
||||||
|
expected_paths:
|
||||||
|
- scripts/analytics/src/utils/storage.ts
|
||||||
|
- scripts/analytics/tests/storage-root.test.ts
|
||||||
|
- commands/report.md
|
||||||
|
- commands/import.md
|
||||||
|
min_file_count: 4
|
||||||
|
commit_message_pattern: "^fix\\(linkedin-studio\\): anchor analytics root"
|
||||||
|
bash_syntax_check: []
|
||||||
|
forbidden_paths: []
|
||||||
|
must_contain:
|
||||||
|
- path: scripts/analytics/src/utils/storage.ts
|
||||||
|
pattern: "plugin.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Voice-leak + placeholder-detection (coupled criticals)
|
||||||
|
|
||||||
|
- **Files:** `assets/voice-samples/authentic-voice-samples.template.md` (new), `assets/voice-samples/authentic-voice-samples.md` (→ becomes placeholder), `hooks/scripts/personalization-score.mjs`, `commands/setup.md`, `commands/onboarding.md`, `.claude-plugin/plugin.json`, `.gitignore`, plus the exact-filename readers (`hooks/scripts/user-prompt-context.mjs`, `hooks/prompts/voice-guardian.md`, `hooks/prompts/state-update-reminder.md`)
|
||||||
|
- **Changes:** Ship a PII-free placeholder at `authentic-voice-samples.md` carrying an explicit sentinel (e.g. `<!-- VOICE_PLACEHOLDER -->`); move the author's real profile to a gitignored `authentic-voice-samples.local.md` and add that glob to `.gitignore`; add a `.template.md` for adopters. Fix `personalization-score.mjs:23` to detect the sentinel (not the `[Your Name]` heuristic) so the placeholder scores 0 voice points. **Both voice writers must replace-not-append (plan-critic Major 1):** fix `setup.md:99` (merge→overwrite) AND `commands/onboarding.md:142` (append→overwrite) so a populated profile removes the sentinel — otherwise the sentinel survives below appended content and the score stays 0 after the user fills it in. Also update the stale detection-heuristic table at `setup.md:28` ("Check for `[Your Name]` or <50 lines") to describe the sentinel. Scrub the author name from `.claude-plugin/plugin.json:6` (author field → neutral/org). **PII NFR scope (plan-critic Major 2):** the leak is the voice profile + `plugin.json` author field; the author name in `LICENSE` is the legally-required MIT copyright holder and is an intentional, correct exception (do NOT scrub it). Verify the ~15 readers tolerate the placeholder (most read a dir glob; the exact-filename readers keep working since the filename persists). **Git-history decision (gemini Pass-2 Decision 3): `.gitignore` does NOT remove the already-committed real profile from past commits — it stays retrievable from history.** Decision (proportionate to the threat model): this is the **author's own attributed open-source plugin** — his name is already public by design in `LICENSE` (MIT copyright), the README, and his Forgejo account, so the historical voice file is *attributed authorship, not a leaked secret*. The bug being fixed is the **adopter-default** (a fork inheriting the author's voice + being told "Voice ✓"), which the placeholder+gitignore-going-forward fully resolves. **Do NOT force-push a history rewrite** (`git-filter-repo`) — it is disruptive for an open-source plugin with existing clones/forks and is disproportionate for non-secret attributed content. Record this as a conscious decision in the commit body (so it is a documented choice, not an oversight). *If the real `.local.md` ever contains genuine secrets (it should not — it is styling), that would change the calculus.*
|
||||||
|
- **Reuses:** the `<!-- ... -->` sentinel convention; existing dir-glob reads.
|
||||||
|
- **Test first:**
|
||||||
|
- File: `hooks/scripts/__tests__/personalization-score.test.mjs` (new)
|
||||||
|
- Verifies: placeholder (with sentinel) scores 0 voice points; a real-looking profile (no sentinel, >50 lines) scores the voice points
|
||||||
|
- Pattern: `hooks/scripts/__tests__/state-updater.test.mjs`
|
||||||
|
- **Verify:** `grep -rIn "Kjell Tore" assets/voice-samples/authentic-voice-samples.md .claude-plugin/plugin.json` → no matches; `git check-ignore assets/voice-samples/authentic-voice-samples.local.md` → prints the path; `node --test hooks/scripts/__tests__/personalization-score.test.mjs` → passes.
|
||||||
|
- **On failure:** revert — `git checkout -- assets/voice-samples/ hooks/scripts/personalization-score.mjs commands/setup.md .claude-plugin/plugin.json .gitignore` and restore the real file from the local copy.
|
||||||
|
- **Checkpoint:** `git commit -m "fix(linkedin-studio): ship placeholder voice profile, gitignore real, sentinel detection"`
|
||||||
|
- **Manifest:**
|
||||||
|
```yaml
|
||||||
|
manifest:
|
||||||
|
expected_paths:
|
||||||
|
- assets/voice-samples/authentic-voice-samples.md
|
||||||
|
- assets/voice-samples/authentic-voice-samples.template.md
|
||||||
|
- hooks/scripts/personalization-score.mjs
|
||||||
|
- hooks/scripts/__tests__/personalization-score.test.mjs
|
||||||
|
- .gitignore
|
||||||
|
min_file_count: 5
|
||||||
|
commit_message_pattern: "^fix\\(linkedin-studio\\): ship placeholder voice profile"
|
||||||
|
bash_syntax_check: []
|
||||||
|
forbidden_paths: []
|
||||||
|
must_contain:
|
||||||
|
- path: hooks/scripts/personalization-score.mjs
|
||||||
|
pattern: "VOICE_PLACEHOLDER"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 6: Fix the A/B significance claim
|
||||||
|
|
||||||
|
- **Files:** `commands/ab-test.md`
|
||||||
|
- **Changes:** Remove the literal `Significant? Yes/No` column (`:301-306`); rename to `Directional?`; cap confidence at "directional only" below ~50 conversions/variant; drop the "20% significance rule" wording (`:277,310-313`) and the "3 = Medium, 5+ = High" ladder (`:315-317`) — replace with a plain "organic personal-post volume rarely reaches statistical significance; treat results as directional."
|
||||||
|
- **Reuses:** existing ab-test.md structure.
|
||||||
|
- **Test first:**
|
||||||
|
- File: n/a (content doc)
|
||||||
|
- Verifies: no "Significant?" column, no "20% significance rule"
|
||||||
|
- Pattern: grep check
|
||||||
|
- **Verify:** `grep -nE 'Significant\?|20% significance' commands/ab-test.md` → no matches; `grep -c 'irectional' commands/ab-test.md` → ≥ 1.
|
||||||
|
- **On failure:** revert — `git checkout -- commands/ab-test.md`
|
||||||
|
- **Checkpoint:** `git commit -m "fix(linkedin-studio): downgrade A/B significance claim to directional"`
|
||||||
|
- **Manifest:**
|
||||||
|
```yaml
|
||||||
|
manifest:
|
||||||
|
expected_paths:
|
||||||
|
- commands/ab-test.md
|
||||||
|
min_file_count: 1
|
||||||
|
commit_message_pattern: "^fix\\(linkedin-studio\\): downgrade A/B significance"
|
||||||
|
bash_syntax_check: []
|
||||||
|
forbidden_paths: []
|
||||||
|
must_contain:
|
||||||
|
- path: commands/ab-test.md
|
||||||
|
pattern: "irectional"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 7: Saves/dwell honesty-fix
|
||||||
|
|
||||||
|
- **Files:** `commands/report.md`, `scripts/analytics/src/models/types.ts`, `commands/strategy.md`
|
||||||
|
- **Changes:** Apply research/02 D2/D4. Reframe `report.md:223` ("Saves (10x weight) … highest-impact") → honest dated wording: "saves are visible in your native LinkedIn post analytics (since ~Sept 2025, count-only) but there is no self-serve API to pull them, so this tool does not auto-track them — read them in LinkedIn directly; dwell is internal-only for organic posts." Add a code comment in `types.ts` documenting why `saves`/`dwell` are intentionally absent (no self-serve source). Reconcile any strategy-layer copy that tells the user to "optimize saves" the tool can't read. **No** new metric field, **no** manual-entry feature (operator Q3).
|
||||||
|
- **Reuses:** research/02 D2 wording; existing report.md structure.
|
||||||
|
- **Test first:**
|
||||||
|
- File: n/a (content/comment)
|
||||||
|
- Verifies: no claim the tool measures saves/dwell
|
||||||
|
- Pattern: grep check
|
||||||
|
- **Verify:** `grep -nE 'Saves \(10x|highest-impact signals' commands/report.md` → no matches; `grep -n 'no self-serve API' commands/report.md` → ≥ 1 match.
|
||||||
|
- **On failure:** revert — `git checkout -- commands/report.md scripts/analytics/src/models/types.ts commands/strategy.md`
|
||||||
|
- **Checkpoint:** `git commit -m "fix(linkedin-studio): honest saves/dwell wording, no false tracking claim"`
|
||||||
|
- **Manifest:**
|
||||||
|
```yaml
|
||||||
|
manifest:
|
||||||
|
expected_paths:
|
||||||
|
- commands/report.md
|
||||||
|
- scripts/analytics/src/models/types.ts
|
||||||
|
min_file_count: 2
|
||||||
|
commit_message_pattern: "^fix\\(linkedin-studio\\): honest saves/dwell wording"
|
||||||
|
bash_syntax_check: []
|
||||||
|
forbidden_paths: []
|
||||||
|
must_contain:
|
||||||
|
- path: commands/report.md
|
||||||
|
pattern: "no self-serve API"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 8: Parameterize the series path + de-brand render output
|
||||||
|
|
||||||
|
- **Files:** `config/edition-state.template.json`, `commands/newsletter.md`, `render/build-linkedin.mjs`, `render/build-carousel.mjs`, `config/image-credit-caption.template.md`
|
||||||
|
- **Changes:** Change the `${LTL_SERIES_ROOT:-…}` default from the private `/Users/ktg/repos/maskinrommet/serier` to a documented neutral default (e.g. `${LTL_SERIES_ROOT:-$HOME/linkedin-series}`), and scrub the hardcoded private path from `edition-state.template.json:4` prose. De-brand the render output: make the "Maskinrommet" footer/eyebrow/title strings in `build-linkedin.mjs:350` + `build-carousel.mjs:7,232,282` configurable (env var or config field, defaulting to a neutral/empty brand). Preserve the env-var + explicit-path-arg contract.
|
||||||
|
- **Reuses:** existing `LTL_SERIES_ROOT` env mechanism; config-field pattern.
|
||||||
|
- **Test first:**
|
||||||
|
- File: n/a (config/render) — verification is the Phase-1 grep
|
||||||
|
- Verifies: no `/Users/ktg` in shipped files; brand configurable
|
||||||
|
- Pattern: grep check
|
||||||
|
- **Verify:** `grep -rIn '/Users/ktg' config/ commands/ render/ | grep -v '.local'` → no matches; `grep -rn 'Maskinrommet' render/` → only inside a configurable default, not a hardcoded literal.
|
||||||
|
- **On failure:** revert — `git checkout -- config/ commands/newsletter.md render/`
|
||||||
|
- **Checkpoint:** `git commit -m "feat(linkedin-studio): parameterize series path + de-brand render output"`
|
||||||
|
- **Manifest:**
|
||||||
|
```yaml
|
||||||
|
manifest:
|
||||||
|
expected_paths:
|
||||||
|
- config/edition-state.template.json
|
||||||
|
- commands/newsletter.md
|
||||||
|
- render/build-linkedin.mjs
|
||||||
|
min_file_count: 3
|
||||||
|
commit_message_pattern: "^feat\\(linkedin-studio\\): parameterize series path"
|
||||||
|
bash_syntax_check: []
|
||||||
|
forbidden_paths: []
|
||||||
|
must_contain:
|
||||||
|
- path: commands/newsletter.md
|
||||||
|
pattern: "LTL_SERIES_ROOT"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 9: Parameterize the Norwegian language-lock
|
||||||
|
|
||||||
|
- **Files:** `agents/language-reviewer.md`, `agents/voice-scrubber.md`, `agents/content-reviewer.md`, `agents/fact-reviewer.md`, `agents/editorial-reviewer.md`, `commands/newsletter.md`, `config/edition-state.template.json`
|
||||||
|
- **Changes:** Make the review language a configurable input rather than a hardcoded Norwegian contract. Add a `language` field to edition-state (additive, default e.g. `en`), thread it into the long-form agent prompts so `language-reviewer` grades against the configured language's rules (Norwegian anglicism/kanselli checks fire only when `language: no`), and `voice-scrubber`'s "gold standard" references the configured language's approved editions. Keep Norwegian fully working when configured. **Also resolve the "skrivekontrakt §C2 does not ship" generalization defect (plan-critic minor 2):** make the §C2 reference in the craft-gate agents (`editorial-reviewer`, `content-reviewer`) non-blocking for a non-author — point to the in-tree fallback checklist the audit noted exists, so the gate works without the unshipped Maskinrommet contract.
|
||||||
|
- **Reuses:** the additive edition-state schema; the namespaced agent-invocation contract; the in-tree fallback craft checklist.
|
||||||
|
- **Test first:**
|
||||||
|
- File: n/a (agent prose) — fixture-shape test if a fixture changes
|
||||||
|
- Verifies: agents read a `language` input; Norwegian path intact; §C2 has an in-tree fallback
|
||||||
|
- Pattern: `agents/__tests__/language-reviewer-fixture.test.mjs` if present
|
||||||
|
- **Verify:** `grep -ni 'language' config/edition-state.template.json` → ≥ 1 (positive: the field exists and is threaded — confirm a `language ==`/`language:` reference appears in `agents/language-reviewer.md`); `grep -niE 'unconditional|always Norwegian|norsk draft' agents/language-reviewer.md` → no unconditional-Norwegian assertion remains; `grep -ni 'C2' agents/editorial-reviewer.md` → reference now points to an in-tree fallback, not only the unshipped contract.
|
||||||
|
- **On failure:** revert — `git checkout -- agents/ commands/newsletter.md config/edition-state.template.json`
|
||||||
|
- **Checkpoint:** `git commit -m "feat(linkedin-studio): make long-form review language configurable"`
|
||||||
|
- **Manifest:**
|
||||||
|
```yaml
|
||||||
|
manifest:
|
||||||
|
expected_paths:
|
||||||
|
- agents/language-reviewer.md
|
||||||
|
- config/edition-state.template.json
|
||||||
|
min_file_count: 2
|
||||||
|
commit_message_pattern: "^feat\\(linkedin-studio\\): make long-form review language configurable"
|
||||||
|
bash_syntax_check: []
|
||||||
|
forbidden_paths: []
|
||||||
|
must_contain:
|
||||||
|
- path: config/edition-state.template.json
|
||||||
|
pattern: "language"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 10: Honest, dated boundary statements
|
||||||
|
|
||||||
|
- **Files:** `README.md`, `commands/report.md`, `commands/calendar.md`, `commands/import.md`
|
||||||
|
- **Changes:** Apply research/02. Add a dated ("as of 2026-05") boundaries section: (a) post-level analytics API exists but is partner-gated (verified org + LinkedIn Page) — not self-serve; CSV is the practical floor; (b) **auto-publish to a personal profile is technically possible self-serve but deliberately not built** (OAuth/token overhead + LinkedIn Terms on automated posting) — a choice, NOT "cannot"; (c) dwell internal-only for organic. Reconcile the `calendar.md` "publish action" / queue wording so it never implies the tool auto-posts (it marks a manually-posted item as published).
|
||||||
|
- **Reuses:** research/02 §Recommendation wording.
|
||||||
|
- **Test first:**
|
||||||
|
- File: n/a (docs)
|
||||||
|
- Verifies: no "cannot auto-publish" / no "CSV is the only way"; dated
|
||||||
|
- Pattern: grep check
|
||||||
|
- **Verify:** `grep -niE 'cannot auto-publish|only way to (get|access)' README.md commands/*.md` → no matches; `grep -ni 'as of 2026' README.md` → ≥ 1.
|
||||||
|
- **On failure:** revert — `git checkout -- README.md commands/report.md commands/calendar.md commands/import.md`
|
||||||
|
- **Checkpoint:** `git commit -m "docs(linkedin-studio): honest dated API/auto-publish/analytics boundaries"`
|
||||||
|
- **Manifest:**
|
||||||
|
```yaml
|
||||||
|
manifest:
|
||||||
|
expected_paths:
|
||||||
|
- README.md
|
||||||
|
- commands/calendar.md
|
||||||
|
min_file_count: 2
|
||||||
|
commit_message_pattern: "^docs\\(linkedin-studio\\): honest dated"
|
||||||
|
bash_syntax_check: []
|
||||||
|
forbidden_paths: []
|
||||||
|
must_contain:
|
||||||
|
- path: README.md
|
||||||
|
pattern: "as of 2026"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 11: Remove the README "independently reviewed" claim + honest reframe
|
||||||
|
|
||||||
|
- **Files:** `README.md`
|
||||||
|
- **Changes:** Remove the "the version that ships is the version that's actually been independently reviewed" string (~L128) and the matching pipeline-diagram "independent re-read" line (~L120). Replace with an honest framing: the long-form pipeline runs cold/headless review gates before lock (describe the capability), without claiming every shipped edition was independently re-reviewed. Locate by content, not line number (lines have drifted).
|
||||||
|
- **Reuses:** existing README pipeline section.
|
||||||
|
- **Test first:**
|
||||||
|
- File: n/a (docs)
|
||||||
|
- Verifies: the exact claim string is gone
|
||||||
|
- Pattern: grep check
|
||||||
|
- **Verify:** `grep -ni 'independently reviewed' README.md` → no matches.
|
||||||
|
- **On failure:** revert — `git checkout -- README.md`
|
||||||
|
- **Checkpoint:** `git commit -m "docs(linkedin-studio): remove independently-reviewed claim, honest reframe"`
|
||||||
|
- **Manifest:**
|
||||||
|
```yaml
|
||||||
|
manifest:
|
||||||
|
expected_paths:
|
||||||
|
- README.md
|
||||||
|
min_file_count: 1
|
||||||
|
commit_message_pattern: "^docs\\(linkedin-studio\\): remove independently-reviewed claim"
|
||||||
|
bash_syntax_check: []
|
||||||
|
forbidden_paths: []
|
||||||
|
must_contain: []
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 12: Reconcile discoverability surfaces
|
||||||
|
|
||||||
|
- **Files:** `skills/linkedin-studio/SKILL.md`, `skills/linkedin-analytics/SKILL.md`, `skills/linkedin-content-creation/SKILL.md`, `skills/linkedin-strategy/SKILL.md`, `skills/linkedin-networking/SKILL.md`, `skills/linkedin-voice/SKILL.md`, `commands/onboarding.md`, `commands/setup.md`, `commands/linkedin.md`
|
||||||
|
- **Changes:** In `skills/linkedin-studio/SKILL.md`: rename "thought leadership plugin" → "LinkedIn Studio"; correct the agent table to the real count and route to `newsletter`/`headless-review`/`pivot`/`react`. Fix `onboarding.md` "25 commands" → 26; reconcile the pillar-count disagreement (`onboarding.md` 3-5 vs `setup.md` 5) to one number; add `headless-review`/`pivot` to the `linkedin.md` router tables + option list. (Final exact counts are set in Step 21; here, fix names/routing/contradictions.)
|
||||||
|
- **Reuses:** existing SKILL.md table format.
|
||||||
|
- **Test first:**
|
||||||
|
- File: n/a (docs) — Step 1 lint asserts agent/command counts
|
||||||
|
- Verifies: no "thought leadership"; router lists all commands
|
||||||
|
- Pattern: grep check
|
||||||
|
- **Verify:** `grep -rni 'thought leadership' skills/` → no matches; `grep -nc 'headless-review' commands/linkedin.md` → ≥ 1; pillar count consistent: `grep -rnE 'pillars|expertise areas' commands/onboarding.md commands/setup.md` shows one consistent number.
|
||||||
|
- **On failure:** revert — `git checkout -- skills/ commands/onboarding.md commands/setup.md commands/linkedin.md`
|
||||||
|
- **Checkpoint:** `git commit -m "docs(linkedin-studio): reconcile discoverability surfaces + skill naming"`
|
||||||
|
- **Manifest:**
|
||||||
|
```yaml
|
||||||
|
manifest:
|
||||||
|
expected_paths:
|
||||||
|
- skills/linkedin-studio/SKILL.md
|
||||||
|
- commands/onboarding.md
|
||||||
|
- commands/linkedin.md
|
||||||
|
min_file_count: 3
|
||||||
|
commit_message_pattern: "^docs\\(linkedin-studio\\): reconcile discoverability"
|
||||||
|
bash_syntax_check: []
|
||||||
|
forbidden_paths: []
|
||||||
|
must_contain:
|
||||||
|
- path: commands/linkedin.md
|
||||||
|
pattern: "headless-review"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 13: Resolve the 11 orphan agents (wire-or-delete, case-by-case)
|
||||||
|
|
||||||
|
- **Files:** `commands/video.md`, `commands/post.md`, `commands/ab-test.md`, `commands/calendar.md`, `commands/report.md`, `commands/analyze.md`, `commands/batch.md`, `commands/pipeline.md`, `commands/strategy.md`, `commands/outreach.md`, `commands/setup.md`, plus `CLAUDE.md`
|
||||||
|
- **Changes:** **DEFAULT: wire all 11 (no deletions) — each has a clear home, so the agent count stays 19** (plan-critic Major 3: the dispositions are fixed here, not deferred). Per agent, add `Task` to the command's `allowed-tools` AND a `subagent_type: linkedin-studio:<name>` call. The 11 orphans + disposition:
|
||||||
|
|
||||||
|
| # | Orphan agent | Disposition | Target command |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | `video-scripter` | wire | `video.md` (already says "delegate") |
|
||||||
|
| 2 | `content-optimizer` | wire | `post.md` + `ab-test.md` |
|
||||||
|
| 3 | `analytics-interpreter` | wire | `report.md` + `analyze.md` |
|
||||||
|
| 4 | `content-planner` | wire | `batch.md` + `pipeline.md` (already hold `Task`) |
|
||||||
|
| 5 | `trend-spotter` | wire | `batch.md` + `pipeline.md` |
|
||||||
|
| 6 | `network-builder` | wire | `outreach.md` |
|
||||||
|
| 7 | `strategy-advisor` | wire | `strategy.md` |
|
||||||
|
| 8 | `voice-trainer` | wire | `setup.md` (voice-profile building) |
|
||||||
|
| 9 | `post-feedback-monitor` | wire | `calendar.md` (publish action / 48h monitor) |
|
||||||
|
| 10 | `differentiation-checker` | wire (Step 14) | post/quick/react/carousel/video |
|
||||||
|
| 11 | `engagement-coach` | wire (Step 16) | `firsthour.md` |
|
||||||
|
|
||||||
|
Agents 10–11 are wired in their dedicated steps; this step wires 1–9. If a wiring proves genuinely awkward at execution, the fallback is delete-that-agent (document it + decrement the count in Step 21) — but the default is wire-all → 19 agents. Use the namespaced `linkedin-studio:<name>` form; note the session-reload requirement.
|
||||||
|
- **Reuses:** the namespaced `subagent_type` contract; existing `Task` grants in batch/pipeline.
|
||||||
|
- **Test first:**
|
||||||
|
- File: n/a — Step 1 lint asserts agent count == `ls agents/*.md`
|
||||||
|
- Verifies: each remaining orphan is invoked ≥1; deleted ones removed from counts
|
||||||
|
- Pattern: the grep success-criterion
|
||||||
|
- **Verify:** for each agent NOT deleted: `grep -rl "subagent_type: linkedin-studio:<name>" commands/` → ≥1; `bash scripts/test-runner.sh` → exit 0 (counts consistent).
|
||||||
|
- **On failure:** revert — `git checkout -- commands/ agents/ CLAUDE.md` (and `git restore` any deleted agent)
|
||||||
|
- **Checkpoint:** `git commit -m "refactor(linkedin-studio): wire or delete 11 orphan agents (case-by-case)"`
|
||||||
|
- **Manifest:**
|
||||||
|
```yaml
|
||||||
|
manifest:
|
||||||
|
expected_paths:
|
||||||
|
- commands/video.md
|
||||||
|
- CLAUDE.md
|
||||||
|
min_file_count: 2
|
||||||
|
commit_message_pattern: "^refactor\\(linkedin-studio\\): wire or delete 11 orphan agents"
|
||||||
|
bash_syntax_check: []
|
||||||
|
forbidden_paths: []
|
||||||
|
must_contain:
|
||||||
|
- path: commands/video.md
|
||||||
|
pattern: "subagent_type: linkedin-studio:video-scripter"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 14: Short-form de-AI / differentiation gate
|
||||||
|
|
||||||
|
- **Files:** `commands/post.md`, `commands/quick.md`, `commands/react.md`, `commands/carousel.md`, `commands/video.md`, `hooks/prompts/voice-guardian.md`, `hooks/hooks.json` (regenerated)
|
||||||
|
- **Changes:** Wire the existing orphan `differentiation-checker` into the five short-form creation commands (add `Task` to `allowed-tools` + a `subagent_type: linkedin-studio:differentiation-checker` call). EXTEND (do not duplicate) `hooks/prompts/voice-guardian.md`'s existing AI-pattern section with the LinkedIn-named signals from research/01 D8 + research/03 D4 (personal substance, original thinking, concrete specifics, genuine voice; soft engagement-bait check: block mechanical-response CTAs, allow genuine questions). **No recompile (plan-critic Blocker 3): `voice-guardian.md` is loaded at runtime by `content-gatekeeper.mjs` — extending its prose changes nothing in `hooks.json`; `compile-hooks.py` is only needed when ADDING a new hook entry to the template, which this step does not do.** (No new agent — reuse the orphan; avoids the voice-guardian overlap the architecture-mapper flagged.)
|
||||||
|
- **Reuses:** `agents/differentiation-checker.md` (the orphan); `hooks/prompts/voice-guardian.md` (existing AI detection, runtime-loaded).
|
||||||
|
- **Test first:**
|
||||||
|
- File: `hooks/scripts/__tests__/linkedin-content-filter.test.mjs` (new) — verifies `isLinkedInContent` fires on the short-form content paths the gate guards
|
||||||
|
- Verifies: gate scope correct; differentiation-checker wired
|
||||||
|
- Pattern: `hooks/scripts/__tests__/state-updater.test.mjs`
|
||||||
|
- **Verify:** `grep -rl "subagent_type: linkedin-studio:differentiation-checker" commands/` → ≥1; `node --test hooks/scripts/__tests__/linkedin-content-filter.test.mjs` → passes; `python3 hooks/scripts/compile-hooks.py --check` → no drift.
|
||||||
|
- **On failure:** revert — `git checkout -- commands/ hooks/`
|
||||||
|
- **Checkpoint:** `git commit -m "feat(linkedin-studio): short-form de-AI gate via differentiation-checker + voice-guardian"`
|
||||||
|
- **Manifest:**
|
||||||
|
```yaml
|
||||||
|
manifest:
|
||||||
|
expected_paths:
|
||||||
|
- commands/post.md
|
||||||
|
- hooks/prompts/voice-guardian.md
|
||||||
|
- hooks/scripts/__tests__/linkedin-content-filter.test.mjs
|
||||||
|
min_file_count: 3
|
||||||
|
commit_message_pattern: "^feat\\(linkedin-studio\\): short-form de-AI gate"
|
||||||
|
bash_syntax_check: []
|
||||||
|
forbidden_paths: []
|
||||||
|
must_contain:
|
||||||
|
- path: commands/post.md
|
||||||
|
pattern: "differentiation-checker"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 15: Video quality gate (captions + aspect guidance, not 9:16-mandatory)
|
||||||
|
|
||||||
|
- **Files:** `commands/video.md`, `references/linkedin-formats.md`, `references/algorithm-signals-reference.md`
|
||||||
|
- **Changes:** Apply research/03 D1-D3. In `video.md`: MP4 default (warn-only on MOV/AVI), within official upload limits; **enforce/strongly-recommend captions** (SRT or native auto-captions, labelled best-practice not "required"); aspect ratio as **guidance — 4:5 / 1:1 preferred for broad distribution, 9:16 mobile-only opt-in**. Fix the contradiction in `linkedin-formats.md` (the "4:5 deprioritized" line → "4:5 preferred"; remove "3-second hook" → "front-load value for muted autoplay"). Remove the "Vertical 9:16 gets distribution boost" + any "video maximizes reach" copy in `algorithm-signals-reference.md:75`/`video.md:198`; add a one-line "per-video reach declining; documents out-engage video" note.
|
||||||
|
- **Reuses:** research/03 §Recommendation; the orphan `video-scripter` (wired in Step 13).
|
||||||
|
- **Test first:**
|
||||||
|
- File: n/a (content) — grep check
|
||||||
|
- Verifies: no "9:16 required", no "3-second hook"; captions present
|
||||||
|
- Pattern: grep check
|
||||||
|
- **Verify:** `grep -niE 'must be 9:16|9:16 \(1080|3-second hook' commands/video.md references/linkedin-formats.md` → no matches; `grep -ni 'captions' commands/video.md` → ≥1; `grep -ni 'deprioritized' references/linkedin-formats.md` → no 4:5-deprioritized line.
|
||||||
|
- **On failure:** revert — `git checkout -- commands/video.md references/linkedin-formats.md references/algorithm-signals-reference.md`
|
||||||
|
- **Checkpoint:** `git commit -m "feat(linkedin-studio): video quality gate (captions + aspect guidance, drop 9:16 mandate)"`
|
||||||
|
- **Manifest:**
|
||||||
|
```yaml
|
||||||
|
manifest:
|
||||||
|
expected_paths:
|
||||||
|
- commands/video.md
|
||||||
|
- references/linkedin-formats.md
|
||||||
|
min_file_count: 2
|
||||||
|
commit_message_pattern: "^feat\\(linkedin-studio\\): video quality gate"
|
||||||
|
bash_syntax_check: []
|
||||||
|
forbidden_paths: []
|
||||||
|
must_contain:
|
||||||
|
- path: commands/video.md
|
||||||
|
pattern: "4:5"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 16: First-hour / reply-loop command with tracked state
|
||||||
|
|
||||||
|
- **Files:** `commands/firsthour.md` (new), `config/state-file.template.md`, `hooks/scripts/state-updater.mjs`, `hooks/scripts/__tests__/state-updater.test.mjs`, `CLAUDE.md`
|
||||||
|
- **Changes:** Add a wired first-hour/reply-loop command that invokes `engagement-coach` (+ `post-feedback-monitor`) with tracked state: a target list, draft comments, and a timestamped first-hour plan persisted in `~/.claude/linkedin-studio.local.md`. Add additive scalar fields / a non-`R`-initial section to `config/state-file.template.md` and a pure mutation function in `state-updater.mjs` mirroring `updatePostTracking`. Register the command in CLAUDE.md.
|
||||||
|
- **Reuses:** `commands/react.md` structure; `agents/engagement-coach.md`; `state-updater.mjs` `updatePostTracking` pattern; the additive-state contract.
|
||||||
|
- **Test first:**
|
||||||
|
- File: `hooks/scripts/__tests__/state-updater.test.mjs` (extend) — verifies the new mutation fn is additive (missing field → graceful default)
|
||||||
|
- Verifies: first-hour state round-trips; existing fields untouched
|
||||||
|
- Pattern: existing state-updater tests
|
||||||
|
- **Verify:** `grep -rl "subagent_type: linkedin-studio:engagement-coach" commands/` → ≥1; `node --test hooks/scripts/__tests__/state-updater.test.mjs` → passes; `bash scripts/test-runner.sh` → exit 0 (command count updated).
|
||||||
|
- **On failure:** revert — `git checkout -- commands/firsthour.md config/state-file.template.md hooks/scripts/ CLAUDE.md`
|
||||||
|
- **Checkpoint:** `git commit -m "feat(linkedin-studio): first-hour/reply-loop command with tracked state"`
|
||||||
|
- **Manifest:**
|
||||||
|
```yaml
|
||||||
|
manifest:
|
||||||
|
expected_paths:
|
||||||
|
- commands/firsthour.md
|
||||||
|
- config/state-file.template.md
|
||||||
|
- hooks/scripts/state-updater.mjs
|
||||||
|
min_file_count: 3
|
||||||
|
commit_message_pattern: "^feat\\(linkedin-studio\\): first-hour/reply-loop command"
|
||||||
|
bash_syntax_check: []
|
||||||
|
forbidden_paths: []
|
||||||
|
must_contain:
|
||||||
|
- path: commands/firsthour.md
|
||||||
|
pattern: "engagement-coach"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 17: Honest newsletter-distribution + profile-SEO + outreach pipeline surfaces
|
||||||
|
|
||||||
|
- **Files:** `commands/newsletter.md`, `commands/profile.md`, `commands/outreach.md`, `references/linkedin-growth-playbook-2025-2026.md`
|
||||||
|
- **Changes:** Apply research/03 D5. Newsletter distribution surface = the **honest** version: "bypasses organic feed ranking via ONE deduplicated notification per subscriber per edition (NOT triple)"; one-time launch-blast + a **~1–2K follower floor** (frame: wait until you can spend the blast); realistic cold-start floors (0–100 subs months 1–3); disclose non-export / no-canonical / no-read-analytics / per-subscriber decay. Add profile-SEO fields to `profile.md` (headline-as-search-field, per-section keyword targets). Add tracked contact/pipeline state to `outreach.md` (or reference the state added in Step 16's pattern).
|
||||||
|
- **Reuses:** research/03 D5; the state pattern from Step 16.
|
||||||
|
- **Test first:**
|
||||||
|
- File: n/a (content) — grep check
|
||||||
|
- Verifies: no "triple notification"; follower floor present
|
||||||
|
- Pattern: grep check
|
||||||
|
- **Verify:** `grep -niE 'triple.notification' commands/newsletter.md` → no matches; `grep -niE 'deduplicated|follower floor|1[–-]?2K|bypasses.*feed' commands/newsletter.md` → ≥1; `grep -ni 'headline' commands/profile.md` → ≥1.
|
||||||
|
- **On failure:** revert — `git checkout -- commands/newsletter.md commands/profile.md commands/outreach.md references/linkedin-growth-playbook-2025-2026.md`
|
||||||
|
- **Checkpoint:** `git commit -m "feat(linkedin-studio): honest newsletter distribution + profile-SEO + outreach pipeline"`
|
||||||
|
- **Manifest:**
|
||||||
|
```yaml
|
||||||
|
manifest:
|
||||||
|
expected_paths:
|
||||||
|
- commands/newsletter.md
|
||||||
|
- commands/profile.md
|
||||||
|
min_file_count: 2
|
||||||
|
commit_message_pattern: "^feat\\(linkedin-studio\\): honest newsletter distribution"
|
||||||
|
bash_syntax_check: []
|
||||||
|
forbidden_paths: []
|
||||||
|
must_contain:
|
||||||
|
- path: commands/newsletter.md
|
||||||
|
pattern: "deduplicated"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 18: Promote post-feedback-monitor off Haiku
|
||||||
|
|
||||||
|
- **Files:** `agents/post-feedback-monitor.md`, `CLAUDE.md`
|
||||||
|
- **Changes:** Change `model: haiku` → `model: opus` (the lone non-Opus/Sonnet deviation; human-facing real-time coaching; contradicts the standing Opus-default). Update the model column in the CLAUDE.md agent table.
|
||||||
|
- **Reuses:** existing agent frontmatter.
|
||||||
|
- **Test first:**
|
||||||
|
- File: n/a (frontmatter) — grep check
|
||||||
|
- Verifies: no Haiku agent remains
|
||||||
|
- Pattern: grep check
|
||||||
|
- **Verify:** `grep -rl 'model: haiku' agents/` → no matches; `grep -n 'post-feedback-monitor' CLAUDE.md` shows Opus.
|
||||||
|
- **On failure:** revert — `git checkout -- agents/post-feedback-monitor.md CLAUDE.md`
|
||||||
|
- **Checkpoint:** `git commit -m "fix(linkedin-studio): promote post-feedback-monitor to Opus (Opus-default)"`
|
||||||
|
- **Manifest:**
|
||||||
|
```yaml
|
||||||
|
manifest:
|
||||||
|
expected_paths:
|
||||||
|
- agents/post-feedback-monitor.md
|
||||||
|
- CLAUDE.md
|
||||||
|
min_file_count: 2
|
||||||
|
commit_message_pattern: "^fix\\(linkedin-studio\\): promote post-feedback-monitor to Opus"
|
||||||
|
bash_syntax_check: []
|
||||||
|
forbidden_paths: []
|
||||||
|
must_contain:
|
||||||
|
- path: agents/post-feedback-monitor.md
|
||||||
|
pattern: "model: opus"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 19: Newsletter multi-session/effort banner
|
||||||
|
|
||||||
|
- **Files:** `commands/newsletter.md`
|
||||||
|
- **Changes:** Add a banner at the top of the newsletter command: "This is a multi-session, multi-gate, ~N-hour process (16 phases)." Set realistic expectations before the user starts.
|
||||||
|
- **Reuses:** existing newsletter.md header.
|
||||||
|
- **Test first:**
|
||||||
|
- File: n/a (content) — grep check
|
||||||
|
- Verifies: banner present
|
||||||
|
- Pattern: grep check
|
||||||
|
- **Verify:** `grep -niE 'multi-session|multi-gate|16 phases|~[0-9].*hour' commands/newsletter.md` → ≥1 near the top.
|
||||||
|
- **On failure:** revert — `git checkout -- commands/newsletter.md`
|
||||||
|
- **Checkpoint:** `git commit -m "docs(linkedin-studio): add multi-session/effort banner to newsletter"`
|
||||||
|
- **Manifest:**
|
||||||
|
```yaml
|
||||||
|
manifest:
|
||||||
|
expected_paths:
|
||||||
|
- commands/newsletter.md
|
||||||
|
min_file_count: 1
|
||||||
|
commit_message_pattern: "^docs\\(linkedin-studio\\): add multi-session/effort banner"
|
||||||
|
bash_syntax_check: []
|
||||||
|
forbidden_paths: []
|
||||||
|
must_contain:
|
||||||
|
- path: commands/newsletter.md
|
||||||
|
pattern: "multi-session"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 20: Measure long-form review-pass overlap + trim
|
||||||
|
|
||||||
|
- **Files:** `docs/remediation/overlap-measurement.md` (new, committed evidence), and any agent/command trimmed as a result (e.g. `commands/newsletter.md`, an agent merged/removed)
|
||||||
|
- **Changes:** On an **in-repo fixture edition** (NOT a read of `maskinrommet/serier` — cross-repo needs explicit instruction), run the long-form review agents and record what each catches in a committed comparison table (per the operator's "trim-for-quality, measured-not-assumed" decision). Trim redundancy ONLY where the measurement shows it (merge/remove a gate that catches nothing the others don't); if the measurement justifies the redundancy, record that and keep it. Commit the measurement as evidence.
|
||||||
|
- **Reuses:** the existing `agents/fixtures/*-cases.md` fasit edition as the fixture; the long-form review agents.
|
||||||
|
- **Test first:**
|
||||||
|
- File: n/a (measurement artifact)
|
||||||
|
- Verifies: measurement committed; any trim is justified by it
|
||||||
|
- Pattern: n/a
|
||||||
|
- **Verify:** `test -f docs/remediation/overlap-measurement.md` → exists; the file contains a per-reviewer catch table; if any gate was removed, `bash scripts/test-runner.sh` → exit 0 (counts consistent).
|
||||||
|
- **On failure:** skip — if the fixture is insufficient to measure, record "measurement inconclusive; redundancy retained pending a real edition" and do not trim.
|
||||||
|
- **Checkpoint:** `git commit -m "docs(linkedin-studio): measure long-form review-pass overlap, trim where unjustified"`
|
||||||
|
- **Manifest:**
|
||||||
|
```yaml
|
||||||
|
manifest:
|
||||||
|
expected_paths:
|
||||||
|
- docs/remediation/overlap-measurement.md
|
||||||
|
min_file_count: 1
|
||||||
|
commit_message_pattern: "^docs\\(linkedin-studio\\): measure long-form review-pass overlap"
|
||||||
|
bash_syntax_check: []
|
||||||
|
forbidden_paths: []
|
||||||
|
must_contain:
|
||||||
|
- path: docs/remediation/overlap-measurement.md
|
||||||
|
pattern: "catch"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 21: Version bump + counts + three-doc + CHANGELOG reconciliation
|
||||||
|
|
||||||
|
- **Files:** `.claude-plugin/plugin.json`, `README.md`, `CLAUDE.md`, `CHANGELOG.md`, `../../README.md` (root marketplace), `../../.claude-plugin/marketplace.json`, `STATE.md`
|
||||||
|
- **Changes:** Bump the version → **4.0.0** (breaking: reinstall for new state/wired agents). Counts are now determinable (plan-critic Major 7): Step 13 wires all 11 orphans with **no deletions → 19 agents**; Step 16 adds `firsthour` → **27 commands** (26 + 1); **25 reference docs · 6 skills · 9 hooks · 16 newsletter phases**. (If execution deletes any orphan per Step 13's documented fallback, decrement here — but the default is the fixed set above.) **Always recompute from `ls` at execution as the source of truth** (`ls agents/*.md | wc -l`, etc.) and reconcile to that. Update the three doc levels in the same change (plugin README, plugin CLAUDE.md, root README), the README badges, CHANGELOG (Keep-a-Changelog entry summarizing Phase 0–3), and the marketplace.json. `grep` the old version → 0 stale. The Step 1 lint asserts version + count consistency.
|
||||||
|
- **Reuses:** the version-sync memory (grep old version, update all); the lint's version/count asserts.
|
||||||
|
- **Test first:**
|
||||||
|
- File: n/a — Step 1 lint is the consistency check
|
||||||
|
- Verifies: version consistent everywhere; counts match `ls`
|
||||||
|
- Pattern: the lint version-grep
|
||||||
|
- **Verify:** `grep -rn "3\\.1\\.0" --include=*.json --include=*.md . | grep -v CHANGELOG | grep -v docs/remediation` → no stale hits; `bash scripts/test-runner.sh` → exit 0; counts in README == CLAUDE.md == root README.
|
||||||
|
- **On failure:** revert — `git checkout -- .claude-plugin/ README.md CLAUDE.md CHANGELOG.md ../../README.md ../../.claude-plugin/marketplace.json STATE.md`
|
||||||
|
- **Checkpoint:** `git commit -m "release(linkedin-studio): v4.0.0 — audit remediation (Phase 0-3), counts + three-doc sync"`
|
||||||
|
- **Manifest:**
|
||||||
|
```yaml
|
||||||
|
manifest:
|
||||||
|
expected_paths:
|
||||||
|
- .claude-plugin/plugin.json
|
||||||
|
- README.md
|
||||||
|
- CLAUDE.md
|
||||||
|
- CHANGELOG.md
|
||||||
|
min_file_count: 4
|
||||||
|
commit_message_pattern: "^release\\(linkedin-studio\\): v4\\.0\\.0"
|
||||||
|
bash_syntax_check: []
|
||||||
|
forbidden_paths: []
|
||||||
|
must_contain:
|
||||||
|
- path: CHANGELOG.md
|
||||||
|
pattern: "4.0.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Alternatives Considered
|
||||||
|
|
||||||
|
| Approach | Pros | Cons | Why rejected |
|
||||||
|
|----------|------|------|--------------|
|
||||||
|
| Build features as the audit sketched them (enforce 9:16; "triple-notification" surface; "cannot auto-publish" boundary; saves untrackable) | Less research; matches the audit | Research refuted all four premises — would ship NEW false claims, the exact disease being treated | Rejected — built on corrected premises instead |
|
||||||
|
| New Opus de-AI agent (per convention-scanner) | Matches the 6-gate Opus cluster | `voice-guardian.md` already does AI-detection + the brief's success-criterion greps `differentiation-checker` → a new agent duplicates logic and misses the criterion | Rejected — wire the existing orphan + extend voice-guardian |
|
||||||
|
| Single mega-release vs phased | One reinstall | Huge unreviewable diff; harder to bisect | Phased per §9, one Voyage session per phase, lint guards each |
|
||||||
|
| Reconcile stats by editing each file independently | Simpler per-file | 40 files restate numbers → guaranteed to miss one | Rejected — substrate-first + enumerate-by-grep + cite-not-restate; the lint stat-grep guards against *contradiction* (it cannot prove every citer cites-vs-restates, so completeness is also spot-checked at Step 3) |
|
||||||
|
|
||||||
|
## Test Strategy
|
||||||
|
|
||||||
|
- **Framework:** `node:test` (`.test.mjs` / `.test.ts`, glob form `node --test path/*.test.mjs` — Node 25 directory mode is broken); bash for the structural lint; fixture-shape tests for agents.
|
||||||
|
- **Existing patterns:** `hooks/scripts/__tests__/state-updater.test.mjs` (pure-module), `scripts/analytics/tests/storage.test.ts` (tmpdir + env override), `agents/__tests__/*-fixture.test.mjs` (shape-only, never self-certify live output).
|
||||||
|
- **New tests in this plan:** `scripts/analytics/tests/storage-root.test.ts` (CWD-fix, red-first), `hooks/scripts/__tests__/personalization-score.test.mjs` (placeholder sentinel), `hooks/scripts/__tests__/linkedin-content-filter.test.mjs` (gate scope), state-updater extension (first-hour additive state). The rebuilt `test-runner.sh` is the cross-cutting drift gate.
|
||||||
|
|
||||||
|
### Tests to write
|
||||||
|
|
||||||
|
| Type | File | Verifies | Model test |
|
||||||
|
|------|------|----------|------------|
|
||||||
|
| Unit | `scripts/analytics/tests/storage-root.test.ts` | default root anchors on plugin marker; env override | `scripts/analytics/tests/storage.test.ts` |
|
||||||
|
| Unit | `hooks/scripts/__tests__/personalization-score.test.mjs` | placeholder→0 voice pts; real→pts | `state-updater.test.mjs` |
|
||||||
|
| Unit | `hooks/scripts/__tests__/linkedin-content-filter.test.mjs` | gate fires on short-form content paths only | `state-updater.test.mjs` |
|
||||||
|
| Lint | `scripts/test-runner.sh` | exit 0 healthy; exit 1 on agent add/remove | existing sections |
|
||||||
|
|
||||||
|
## Risks and Mitigations
|
||||||
|
|
||||||
|
| Priority | Risk | Location | Impact | Mitigation |
|
||||||
|
|----------|------|----------|--------|------------|
|
||||||
|
| Critical | Voice move breaks ~15 readers if not atomic | `assets/voice-samples/` + 15 readers | content commands break | Step 5 lands placeholder + sentinel-detector + all readers in one commit |
|
||||||
|
| Critical | Placeholder mis-detected → false "Voice ✓" | `personalization-score.mjs:23` | adopter writes in stock voice, told done | sentinel check (not `[Your Name]`); test both cases |
|
||||||
|
| Critical | Stat reconciliation misses a file | ~40 citers | contradiction persists | substrate-first + cite-not-restate + Step 1 lint stat-grep proves completeness |
|
||||||
|
| High | Lint unguarded until rebuilt | `scripts/test-runner.sh` | later steps ship unguarded | Step 1 is FIRST |
|
||||||
|
| High | CWD bug writes data to wrong dir even with deps | `storage.ts:17-22` | analytics silently wrong | anchor on `.claude-plugin/`; red-first test |
|
||||||
|
| High | Orphan wire uses bare type / no reload | `commands/*` | Task fails at runtime | namespaced `linkedin-studio:<name>`; note reload; lint asserts count |
|
||||||
|
| Medium | Hook edit without recompile → stale runtime | `hooks/hooks.json` | gate doesn't fire | always run `compile-hooks.py`; lint wires `--check` |
|
||||||
|
| Medium | State change non-additive → orphans 2 shipped editions | `state-updater.mjs`, templates | editions break | additive scalar/non-`R` section only; preserve `extractField||default` |
|
||||||
|
| Medium | Series-default change mis-routes new editions | `newsletter.md`, `edition-state.template.json` | wrong write path | keep `${LTL_SERIES_ROOT:-…}` override; neutral default; shipped editions are outside the repo |
|
||||||
|
| Low | Auto-publish boundary re-states a new false claim | `README.md` | dishonest again | research/02 wording: "possible, deliberately not built" — never "cannot" |
|
||||||
|
|
||||||
|
## Assumptions
|
||||||
|
|
||||||
|
| # | Assumption | Why unverifiable | Impact if wrong |
|
||||||
|
|---|-----------|-----------------|-----------------|
|
||||||
|
| 1 | Final command count after Steps 13/16 = 26 ± deletions + 1 new (`firsthour`) | depends on per-agent wire/delete decisions made at execution | counts in Step 21 adjust; lint catches drift |
|
||||||
|
| 2 | The in-repo fasit fixture is rich enough to measure review-pass overlap (Step 20) | the fixture is Del-4-scoped, not a full edition | Step 20 on-failure: record "inconclusive, redundancy retained" |
|
||||||
|
| 3 | ToS permits documenting auto-publish as "possible but not built" | LinkedIn Terms language not re-read first-hand | finalize-time check before Step 10 wording locks |
|
||||||
|
| 4 | Each phase = one Voyage `/trekcontinue` session | execution cadence | adjust session grouping in the Execution Strategy |
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
*(Per-step manifests verify each step during execution. These are the end-to-end integration checks.)*
|
||||||
|
|
||||||
|
- [ ] `bash scripts/test-runner.sh; echo $?` → `0` (lint green on final state)
|
||||||
|
- [ ] `grep -rnE '40-50%|25-40%|1\.92%|360Brew|January 2026|-40-60%' references/ commands/ skills/ hooks/prompts/ CLAUDE.md README.md | grep -v algorithm-signals-reference` → no stale contradiction
|
||||||
|
- [ ] `grep -rIn '/Users/ktg' config/ commands/ render/ agents/ | grep -v '.local'` → no private path in shipped files
|
||||||
|
- [ ] `grep -rIn 'Kjell Tore' assets/ .claude-plugin/plugin.json` → no PII in shipped files
|
||||||
|
- [ ] `grep -rni 'independently reviewed\|thought leadership\|cannot auto-publish\|triple.notification' README.md skills/ commands/` → no matches
|
||||||
|
- [ ] from `/tmp`: analytics CLI runs without `ERR_MODULE_NOT_FOUND` and writes to `<plugin>/assets/analytics`
|
||||||
|
- [ ] for each non-deleted orphan: `grep -rl "subagent_type: linkedin-studio:<name>" commands/` → ≥1
|
||||||
|
- [ ] `node --test hooks/scripts/__tests__/*.test.mjs` and `cd scripts/analytics && npm test` → all pass
|
||||||
|
- [ ] `grep -rn "3\.1\.0"` (excluding CHANGELOG + docs/remediation) → no stale version
|
||||||
|
- [ ] counts identical across plugin README, plugin CLAUDE.md, root README
|
||||||
|
|
||||||
|
## Estimated Scope
|
||||||
|
|
||||||
|
- **Files to modify:** ~55–60 (heavy on references/ + commands/ propagation in Step 3)
|
||||||
|
- **Files to create:** ~7 (placeholder template, 3 test files, firsthour command, overlap-measurement, voice .template)
|
||||||
|
- **Complexity:** high (breadth + coupled criticals + 4 corrected feature premises), but each step is small and lint-guarded
|
||||||
|
|
||||||
|
## Execution Strategy
|
||||||
|
|
||||||
|
*21 steps grouped into 7 sessions across 5 waves. One phase ≈ one Voyage `/trekcontinue` session.*
|
||||||
|
|
||||||
|
### Session 1: Guard + substrate (Phase 0a)
|
||||||
|
- **Steps:** 1, 2, 3
|
||||||
|
- **Wave:** 1
|
||||||
|
- **Depends on:** none
|
||||||
|
- **Scope fence:** Touch: `scripts/test-runner.sh`, `references/`, the Step-3 citers, `hooks/prompts/content-quality-gate.md`. Never touch: `scripts/analytics/`, `assets/voice-samples/`.
|
||||||
|
|
||||||
|
### Session 2: CLI + voice + correctness (Phase 0b)
|
||||||
|
- **Steps:** 4, 5, 6, 7
|
||||||
|
- **Wave:** 2
|
||||||
|
- **Depends on:** Session 1 (lint must guard)
|
||||||
|
- **Scope fence:** Touch: `scripts/analytics/`, `assets/voice-samples/`, `personalization-score.mjs`, `commands/ab-test.md`, `commands/report.md`, `commands/setup.md`, `.claude-plugin/plugin.json`. Never touch: `references/algorithm-signals-reference.md`.
|
||||||
|
|
||||||
|
### Session 3: Generalize (Phase 1a)
|
||||||
|
- **Steps:** 8, 9
|
||||||
|
- **Wave:** 3
|
||||||
|
- **Depends on:** Session 1
|
||||||
|
- **Scope fence:** Touch: `config/`, `render/`, the long-form agents, `commands/newsletter.md`. Never touch: short-form commands.
|
||||||
|
|
||||||
|
### Session 4: Honesty + discoverability (Phase 1b)
|
||||||
|
- **Steps:** 10, 11, 12
|
||||||
|
- **Wave:** 3 (parallel with Session 3 — disjoint files)
|
||||||
|
- **Depends on:** Session 1
|
||||||
|
- **Scope fence:** Touch: `README.md`, `skills/`, `commands/onboarding.md`/`setup.md`/`linkedin.md`/`calendar.md`/`report.md`/`import.md`. Never touch: `config/`, `render/`, `agents/`.
|
||||||
|
|
||||||
|
### Session 5: Orphans + gates (Phase 2a)
|
||||||
|
- **Steps:** 13, 14, 15, 18
|
||||||
|
- **Wave:** 4
|
||||||
|
- **Depends on:** Sessions 1–4
|
||||||
|
- **Scope fence:** Touch: `commands/` (short-form + video), `hooks/prompts/voice-guardian.md`, `agents/` (wire/delete + post-feedback-monitor), `references/linkedin-formats.md`. Never touch: `README.md` counts (Step 21 owns).
|
||||||
|
|
||||||
|
### Session 6: Feature surfaces (Phase 2b)
|
||||||
|
- **Steps:** 16, 17
|
||||||
|
- **Wave:** 4 (parallel with Session 5 — mostly disjoint; coordinate `commands/newsletter.md`/`profile.md`)
|
||||||
|
- **Depends on:** Sessions 1–4
|
||||||
|
- **Scope fence:** Touch: `commands/firsthour.md`, `config/state-file.template.md`, `state-updater.mjs`, `commands/newsletter.md`/`profile.md`/`outreach.md`. Never touch: short-form gate files.
|
||||||
|
|
||||||
|
### Session 7: Long-form earn + release (Phase 3 + cross-cutting)
|
||||||
|
- **Steps:** 19, 20, 21
|
||||||
|
- **Wave:** 5
|
||||||
|
- **Depends on:** all prior
|
||||||
|
- **Scope fence:** Touch: `commands/newsletter.md`, `docs/remediation/overlap-measurement.md`, all version/count/doc files. Never touch: anything not yet committed by earlier sessions.
|
||||||
|
|
||||||
|
### Execution Order
|
||||||
|
- **Wave 1:** Session 1
|
||||||
|
- **Wave 2:** Session 2 (after Wave 1)
|
||||||
|
- **Wave 3:** Sessions 3 + 4 (parallel, after Wave 1)
|
||||||
|
- **Wave 4:** Sessions 5 + 6 (parallel, after Waves 2–3)
|
||||||
|
- **Wave 5:** Session 7 (after all)
|
||||||
|
|
||||||
|
### Grouping rules applied
|
||||||
|
- Steps sharing files → same session (the algorithm-stat propagation is all in Session 1; newsletter touches coordinated across Sessions 6/7).
|
||||||
|
- Independent modules → separate sessions (CLI vs voice vs generalize).
|
||||||
|
- The lint (Step 1) gates everything → Wave 1 alone.
|
||||||
|
- Release reconciliation (Step 21) → last, captures all prior counts.
|
||||||
|
|
||||||
|
## Plan Quality Score
|
||||||
|
|
||||||
|
| Dimension | Weight | Score | Notes |
|
||||||
|
|-----------|--------|-------|-------|
|
||||||
|
| Structural integrity | 0.15 | 90 | dependency-ordered; lint-first; release-last; Step 13/21 count-determinism fixed |
|
||||||
|
| Step quality | 0.20 | 86 | real paths+lines; Step 4 re-diagnosed, Step 3 enumerate-by-grep; doc-heavy Step 3/13 inherently broad |
|
||||||
|
| Coverage completeness | 0.20 | 90 | every brief criterion mapped + in Verification (scope-guardian ALIGNED); missing 360Brew citers added |
|
||||||
|
| Specification quality | 0.15 | 88 | concrete greps; manifests on all steps; "decide at execution" removed from Step 13 |
|
||||||
|
| Risk & pre-mortem | 0.15 | 90 | coupled criticals sequenced; second voice-writer + analytics-misdiagnosis now caught |
|
||||||
|
| Headless readiness | 0.10 | 88 | On-failure + Checkpoint per step; Step 13 dispositions fixed; Step 20 honest on-failure |
|
||||||
|
| Manifest quality | 0.05 | 86 | all steps have manifests; false `hooks.json` entry removed from Step 3 |
|
||||||
|
| **Weighted total** | **1.00** | **88** | **Grade: A− (post-revision)** |
|
||||||
|
|
||||||
|
**Adversarial review:**
|
||||||
|
- **Plan critic:** REVISE → addressed. Initial pass found 3 blockers + 7 majors (Grade C, 73); all 3 blockers and the actionable majors fixed in the Revisions below. The three blockers were genuine and load-bearing (analytics misdiagnosis, incomplete stat-propagation file-list, false hook-recompile premise) — exactly the value an independent pass adds.
|
||||||
|
- **Scope guardian:** ALIGNED — all 21 brief success criteria map to a step and appear in Verification; all four refined-by-research criteria honored (video, newsletter, boundaries, saves); all non-goals respected; every cited path verified to exist. 2 low-risk creep items (Step 18 Opus-promotion, Step 8 de-branding), both justified by Constraints/Intent.
|
||||||
|
|
||||||
|
## Revisions
|
||||||
|
|
||||||
|
*Added by adversarial review (Phase 9). Plan-critic REVISE → addressed.*
|
||||||
|
|
||||||
|
| # | Finding | Severity | Resolution |
|
||||||
|
|---|---------|----------|------------|
|
||||||
|
| 1 | Step 4 misdiagnosed the analytics crash: `getAnalyticsRoot()` is `__dirname`-based (CWD-independent) and bypassed by the commands' explicit `ANALYTICS_ROOT` override; the real fresh-clone crash is missing `tsx`/`csv-parse` | blocker | Step 4 reframed: PRIMARY = `npm install` surfacing (the actual crash); the depth-anchor is now a SECONDARY latent-correctness fix; Verify installs before running |
|
||||||
|
| 2 | Step 3's Verify greps all of `commands/` for 360Brew/penalty but the file-list omitted `audit.md`/`strategy.md`/`linkedin.md`/`topic-rotation-gate.md` → step fails its own gate; propagation incomplete | blocker | Step 3 now enumerates the citer set by grep at execution (not a stale hand-list) + added the four missing files; Verify excludes the substrate file from the no-match grep |
|
||||||
|
| 3 | Step 3 & 14 claimed editing a runtime-loaded prompt requires `compile-hooks.py` + regenerates `hooks.json` — false (those prompts are read at runtime; only ADDING a template entry needs recompile) | blocker | Removed the recompile/`hooks.json` claims from Step 3 and Step 14 (+ dropped `hooks.json` from Step 3's manifest) |
|
||||||
|
| 4 | Step 5 not atomic: `onboarding.md:142` APPENDS the user's voice below the placeholder → sentinel survives, score stays 0 after population; `setup.md:28` detection table left stale | major | Step 5 now fixes BOTH writers (setup.md:99 + onboarding.md:142 → overwrite) and the setup.md:28 table |
|
||||||
|
| 5 | "No-PII" NFR ambiguous re: `LICENSE` / historical docs | major | Step 5 clarified: `LICENSE` author name is the required MIT copyright holder (intentional, not scrubbed); the leak scope is the voice profile + `plugin.json` author field |
|
||||||
|
| 6 | Step 13 deferred 3 agents' wire/delete "to execution"; 11 orphans never enumerated; non-determinism propagated to Step 21 counts | major | Step 13 now has an explicit 11-row disposition table (DEFAULT: wire all → 19 agents, no deletions); Step 21 counts are consequently determinable (19 agents · 27 commands) |
|
||||||
|
| 7 | Step 4 Verify ran `npm test` without `npm install` first → fails on the fresh-clone scenario | major | Verify now installs first |
|
||||||
|
| 8 | Step 1 over-claimed the stat-grep "proves propagation completeness" (it only detects contradiction); restore step not failure-safe | major→minor | Softened the Alternatives claim (guards contradiction; completeness spot-checked) + made the Step 1 Verify restore self-running regardless of `set -e` |
|
||||||
|
| 9 | "skrivekontrakt §C2 does not ship" neither fixed nor dropped; Step 9 verify weak | minor | Step 9 now makes the §C2 reference fall back to an in-tree checklist + adds a positive language-threading assertion |
|
||||||
|
| — | `commands/report.md` edited in Steps 7/10/13 | minor (note) | Execution note: later edits to `report.md` must preserve the Step 7 saves-honesty wording (the cross-cutting Verify greps for it) |
|
||||||
|
|
||||||
|
## Adversarial Pass 2 (gemini-bridge, v5.1.1 high-effort)
|
||||||
|
|
||||||
|
An independent Gemini Deep Research pass (~16 min, 24 sources) stress-tested the five
|
||||||
|
load-bearing decisions. It surfaced **two genuine blind spots the local reviewers and the
|
||||||
|
author shared**, both now folded in, plus three points already substantially covered.
|
||||||
|
|
||||||
|
**Folded in (real):**
|
||||||
|
1. **Git history ≠ gitignore (Decision 3).** `.gitignore` does not remove the already-committed
|
||||||
|
real voice profile from past commits. → Step 5 now records an explicit, proportionate
|
||||||
|
decision: this is the author's own *attributed* open-source plugin (name already public in
|
||||||
|
LICENSE/README/Forgejo), so the historical file is attributed authorship, not a leaked
|
||||||
|
secret; the bug is the adopter-default, which placeholder+gitignore fixes; **no force-push
|
||||||
|
history rewrite** (disproportionate + disruptive for an open-source plugin with forks).
|
||||||
|
2. **Lint stat-grep chicken-and-egg (Decision 1).** Putting the stat-consistency grep in the
|
||||||
|
Step-1 lint would make Step 1 fail its own Verify (contradictions persist until Step 3). →
|
||||||
|
Step 1 now rebuilds the STRUCTURAL lint only (already-broken, goes green immediately); the
|
||||||
|
stat-grep moves to Step 3 (after reconciliation), the version-grep to Step 21.
|
||||||
|
|
||||||
|
**Already covered (Gemini's caveats map to existing plan content):**
|
||||||
|
3. **Downgrade-to-direction still asserts a mechanism (Decision 2)** → the per-claim
|
||||||
|
**Source + Confidence column** (Step 2) IS the "Unverified/Illustrative" labeling Gemini
|
||||||
|
prescribes; honest `low`/`unverified` confidence values satisfy the integrity concern.
|
||||||
|
First-party benchmarking is out of scope (operator runs no benchmarks).
|
||||||
|
4. **Prompt-bloat / single-responsibility on the de-AI gate (Decision 4)** → the PRIMARY de-AI
|
||||||
|
mechanism is already an **isolated agent** (`differentiation-checker`, invoked via `Task` =
|
||||||
|
fresh context window — exactly Gemini's "isolated subagent" prescription); the
|
||||||
|
`voice-guardian` touch is a minimal prose extension to an existing runtime prompt, kept small.
|
||||||
|
5. **i18n scope (Decision 5)** → the goal is **removing the Norwegian lock so one other operator
|
||||||
|
can run it in their language**, NOT a multi-language-output i18n framework. Step 9 keeps the
|
||||||
|
lighter touch (language as a configurable input to the review agents) and avoids
|
||||||
|
"configuration as the new hardcoding" by defaulting cleanly; full i18n/pseudo-localization is
|
||||||
|
deliberately out of scope (no second-language *output* requirement).
|
||||||
|
|
||||||
|
**Verdict:** Pass 2 strengthened the plan on two real axes (history decision + lint sequencing)
|
||||||
|
without expanding scope. No finding overturned the approach; the phasing and decisions hold.
|
||||||
|
|
@ -0,0 +1,310 @@
|
||||||
|
---
|
||||||
|
type: trekresearch-brief
|
||||||
|
created: 2026-05-29
|
||||||
|
question: "What does the 2026 LinkedIn feed-ranking system actually reward — comment-vs-reaction weighting, document/carousel engagement rate, external-link reach effect and first-comment status, the early-engagement window incl. delayed reinjection, and the deployed ranking model's verifiable name and date — with a source and confidence per claim?"
|
||||||
|
confidence: 0.82
|
||||||
|
dimensions: 8
|
||||||
|
mcp_servers_used: [tavily, gemini-deep-research]
|
||||||
|
local_agents_used: []
|
||||||
|
external_agents_used: [docs-researcher, community-researcher, security-researcher, contrarian-researcher, gemini-bridge]
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2026 LinkedIn Feed-Ranking — Canonical Signal Statement
|
||||||
|
|
||||||
|
> Generated by trekresearch (high-effort swarm: 4 external + Gemini) on 2026-05-29.
|
||||||
|
> Topic 1 of 3 for the linkedin-studio remediation. This is the **substrate**: the
|
||||||
|
> Phase-0 fixes that reconcile the plugin's contradictory algorithm stats consume it.
|
||||||
|
|
||||||
|
## Research Question
|
||||||
|
|
||||||
|
What does the 2026 LinkedIn feed-ranking system actually reward — comment-vs-reaction
|
||||||
|
weighting, document/carousel engagement rate, external-link reach effect and the
|
||||||
|
current first-comment-workaround status, the early-engagement ("golden hour") window
|
||||||
|
incl. delayed/evergreen reinjection, and the deployed ranking model's verifiable name
|
||||||
|
and deployment date — with a primary or credible source and a confidence level per claim?
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
The plugin's algorithm "facts" are **directionally right but numerically indefensible**:
|
||||||
|
every specific magnitude it states (comment "15x", carousel "6.6%"/"1.92%", link
|
||||||
|
"40-50%"/"25-40%", a clean "−40-60% before distribution", "360Brew, January 2026") is
|
||||||
|
either third-party-only, self-contradictory, conflated across denominators, or — for the
|
||||||
|
model name/date — **not establishable from any primary source.** What IS defensible and
|
||||||
|
high-confidence: an LLM-based relevance-ranking system is live in 2026; the engagement
|
||||||
|
hierarchy is **saves > shares > quality comments > reactions** with **dwell-time a
|
||||||
|
top-tier signal** (the only two signals LinkedIn officially confirms by name are *dwell
|
||||||
|
time* and *topic/interest relevance*); documents/carousels are the #1 format; body links
|
||||||
|
reduce reach (magnitude contested, ~19–60% across studies, LinkedIn denies it is
|
||||||
|
*intentional*); the early window is **60–90 min** (90 is the 2026 consensus); and — the
|
||||||
|
single best-supported actionable finding — **LinkedIn now officially suppresses generic
|
||||||
|
AI "slop"** (named executive, May 2026), which directly justifies a short-form de-AI gate.
|
||||||
|
**Key caveat:** treat every number as directional and per-account-testable; encode
|
||||||
|
*ordering + sourced direction*, never hard coefficients. (Overall confidence 0.82 — high
|
||||||
|
on direction, medium on magnitude.)
|
||||||
|
|
||||||
|
## Dimensions
|
||||||
|
|
||||||
|
### D1. Deployed ranking model — name & date — Confidence: high (on the negative claim)
|
||||||
|
|
||||||
|
**External findings:**
|
||||||
|
- The arXiv paper *"360Brew: A Decoder-only Foundation Model…"* (2501.16450) is dated
|
||||||
|
**2025-01-27**, self-labels as a **"research pre-production model" (V1.0, 150B params)**
|
||||||
|
claiming *offline* parity only, and was **withdrawn 2025-08-23** (submitter lacked
|
||||||
|
license rights). It is neither a deployment announcement nor a clean citable artifact.
|
||||||
|
[arXiv 2501.16450]
|
||||||
|
- LinkedIn's own 2026 communications describe a live LLM-based feed system but the
|
||||||
|
**production name is not reliably establishable**: the docs + contrarian agents both
|
||||||
|
read a LinkedIn Engineering post ("Generative Recommender / GR", attributed to Hristo
|
||||||
|
Danchev, 2026-03-12); the independent Gemini pass **flagged a third-party citation of
|
||||||
|
that same post as possibly fabricated** (Danchev's verifiable authorship is on AWS
|
||||||
|
OpenSearch work). So even the "GR" name carries a provenance question.
|
||||||
|
- "January 2026" as a deployment date appears in **no** primary source; it is third-party
|
||||||
|
extrapolation from the paper's Jan-**2025** date.
|
||||||
|
|
||||||
|
**Contradictions:** docs/contrarian treat the GR engineering blog as primary; Gemini
|
||||||
|
casts doubt on its provenance. **Conservative resolution:** assert neither name nor date.
|
||||||
|
An LLM relevance-ranking system is live (high confidence); its *deployed name* and
|
||||||
|
*go-live date* are **not publishable as fact**.
|
||||||
|
|
||||||
|
### D2. Comment vs reaction weighting + saves/dwell hierarchy — Confidence: high (ordering) / medium (magnitude)
|
||||||
|
|
||||||
|
**External findings:**
|
||||||
|
- "Comment = 15x a like" is **unverified folklore** — no primary source; meet-lea labels
|
||||||
|
it "industry estimate, original source unclear." Sources span 2x–15x with no anchor.
|
||||||
|
AuthoredUp's NLP-quality-scored analysis puts the real comment-vs-like effect **~2x**.
|
||||||
|
[authoredup.com/blog/linkedin-algorithm; meet-lea]
|
||||||
|
- Convergent across AuthoredUp + Vertebrae + van der Blom (1.8M): **a save ≈ 5x a like,
|
||||||
|
≈ 2x a comment** — saves are the top signal (and a follow-graph signal: saving a post
|
||||||
|
gives the author's next post ~80% feed-appearance odds). The plugin's stray "5x" is the
|
||||||
|
**saves** number mis-assigned to comments.
|
||||||
|
- **Officially confirmed (the only two named):** *dwell time* is a ranking signal
|
||||||
|
(LinkedIn Eng "Understanding feed dwell time" 2020; "Leveraging Dwell Time" /
|
||||||
|
Auto-Normalized-Long-Dwell model 2024); LinkedIn describes active (like/comment/share)
|
||||||
|
vs passive (click/skip/long-dwell) tasks but **assigns no weights**. [linkedin.com/blog/engineering/feed/leveraging-dwell-time-to-improve-member-experiences-on-the-linkedin-feed]
|
||||||
|
|
||||||
|
**Resolution (for the canonical statement):** order is **saves > shares > quality
|
||||||
|
comments > reactions/likes**, with **dwell-time top-tier**; comment ≈ 2x like
|
||||||
|
(quality-weighted, single-vendor). Drop "15x" and the comment-"5x" entirely.
|
||||||
|
|
||||||
|
### D3. Document/carousel engagement rate — Confidence: high (format rank) / medium (number)
|
||||||
|
|
||||||
|
**External findings:**
|
||||||
|
- Three independent large-N studies agree documents/carousels are **#1**: Socialinsider
|
||||||
|
(1.3M) native document **7.00%** (multi-image 6.80%), Buffer (2M) carousel **21.77%**
|
||||||
|
median, Metricool (673K) **49.52%**. The 7 vs 21.77 vs 49.52 spread is a
|
||||||
|
**denominator/methodology artifact**, not disagreement about the winner.
|
||||||
|
[socialinsider.io/social-media-benchmarks/linkedin; buffer.com/resources/data-best-content-format-social-media/; metricool.com/linkedin-trends/]
|
||||||
|
- The "6.6%" is a **stale 2024 multi-image** figure (now ~6.45% multi-image / ~7.00%
|
||||||
|
document) — and LinkedIn removed native carousels Dec 2023, so "carousel" = PDF document
|
||||||
|
post; the multi-image↔document conflation is real.
|
||||||
|
- **The plugin's "1.92%" is NOT a carousel rate** — it matches the **personal-profile
|
||||||
|
per-post baseline** (Metricool personal 2.60% / company 1.74%; AuthoredUp 2.10–2.67%).
|
||||||
|
The plugin mixed a format benchmark with a personal-profile baseline.
|
||||||
|
|
||||||
|
**Resolution:** documents/carousels = top format (high confidence). For a number use
|
||||||
|
**~7% (Socialinsider, conservative, company-page per-impression)**; never present 1.92%
|
||||||
|
as a carousel figure; state the format-vs-account-type distinction.
|
||||||
|
|
||||||
|
### D4. External-link reach effect + first-comment status — Confidence: medium (effect) / low (intent, first-comment)
|
||||||
|
|
||||||
|
**External findings:**
|
||||||
|
- A body-link reach reduction is real and observational. The most rigorous source
|
||||||
|
(Ordinal, 900K posts, Mann-Whitney p<0.001) shows it **changed over time: 5% (2023) →
|
||||||
|
35% (2024) → 42% (2025) → ~38% (2026 YTD)**, 37-month avg 26.5%. van der Blom reports a
|
||||||
|
milder **~18.8% median**; DigitalApplied/Gemini cite **~60%**. So the plugin's "40-50%"
|
||||||
|
≈ the 2024-25 peak and "25-40%" ≈ the long-run average — **both partial views of one
|
||||||
|
moving number.** [tryordinal.com/blog/linkedin-link-penalty-study]
|
||||||
|
- **LinkedIn denies an *intentional* penalty** (Sr. Director Product, reported Aug 2025):
|
||||||
|
no penalty "if the post leads with value"; the effect is engagement-driven, not a flat
|
||||||
|
tax. The observed reach gap is real **regardless of intent**. [threads.com/@mattnavarra/post/DOWa_61Cown/]
|
||||||
|
- First-comment workaround is **genuinely contested**: Ordinal data leans "still
|
||||||
|
net-positive but reduced (~−5 to −10%)"; multiple 2026 blogs claim it's now detected as
|
||||||
|
"bridge behavior" and throttled — but that claim is **practitioner-only, no large-N
|
||||||
|
backing.** The one officially-confirmed principle: what gets limited is
|
||||||
|
**off-platform-funnel intent + thin standalone value**, *regardless of link location*.
|
||||||
|
|
||||||
|
**Resolution:** state it as a **correlational reach reduction (~38% in 2026, contested
|
||||||
|
band ~19–60%, LinkedIn disputes intent)**, not a hard penalty. Reframe first-comment as
|
||||||
|
**neither a magic fix nor a confirmed penalty** — lead with standalone value; native
|
||||||
|
formats are the durable answer. Drop the precise % from the enforcing hook.
|
||||||
|
|
||||||
|
### D5. Early-engagement window + evergreen reinjection — Confidence: high (60-90 min) / low (24-72h timing)
|
||||||
|
|
||||||
|
**External findings:**
|
||||||
|
- 2026 consensus has widened from "strict 60 min" to **60–90 min** (90 is van der Blom's
|
||||||
|
current figure), with the **first 15–30 min** the highest-leverage sub-window and ~70%
|
||||||
|
of reach decided in it. [buffer.com/resources/linkedin-algorithm/; expandi.io/blog/best-time-to-post-on-linkedin/]
|
||||||
|
- Evergreen resurfacing is **real in direction** (the 2026 relevance model resurfaces
|
||||||
|
strong-save / high-dwell posts days-to-weeks later on viewer intent; AuthoredUp: posts
|
||||||
|
now live 2–3 weeks vs days) — but **no large-N source confirms a specific "24–72h
|
||||||
|
reinjection" rule**; it is intent-driven and irregular.
|
||||||
|
|
||||||
|
**Resolution:** "**60–90 min golden window; first 15–30 min highest-leverage**"; describe
|
||||||
|
evergreen as "**can resurface days-to-weeks later on intent-match**", not a fixed 24–72h
|
||||||
|
second wave. The plugin both over-indexes the strict first hour AND omits evergreen — fix
|
||||||
|
both.
|
||||||
|
|
||||||
|
### D6. Profile/topic relevance as a ranking input — Confidence: high (signal) / none (the −40-60% figure)
|
||||||
|
|
||||||
|
**External findings:**
|
||||||
|
- **Officially confirmed (qualitatively):** topic/interest relevance drives distribution,
|
||||||
|
including beyond your network — Tim Jurka (Head of Feed AI, 2025-08-11): "Exceptional
|
||||||
|
content may even be distributed broadly … to members interested in the type of content
|
||||||
|
you post, even if they don't follow you." 2026 comms add an Interest Picker + "relevant
|
||||||
|
to your interests, not a popularity contest." [linkedin.com/pulse/how-does-linkedin-feed-work-tim-jurka-oxraf]
|
||||||
|
- **No primary source** states any **−40-60% reach reduction** for off-topic content, nor
|
||||||
|
a discrete "validation-before-distribution gate" with a number. That figure is
|
||||||
|
third-party.
|
||||||
|
|
||||||
|
**Resolution:** keep "profile/topic alignment is a real ranking input" (sourced
|
||||||
|
direction); **drop the "−40-60% before anyone sees it" figure** entirely.
|
||||||
|
|
||||||
|
### D7. Buzzword penalty — Confidence: high (that it is NOT a measured ranking mechanic)
|
||||||
|
|
||||||
|
**External findings:**
|
||||||
|
- **No primary source** ties specific words to a measured reach penalty. Evidence is
|
||||||
|
either editorial/clarity advice (Inc.) or unmeasured vendor assertion (linkboost
|
||||||
|
"LLMs throttle corporate speak"). A semantic-relevance ranker *may* indirectly favor
|
||||||
|
specific over generic phrasing — inferred, not confirmed. [inc.com/...buzzwords; linkboost.co/blog]
|
||||||
|
|
||||||
|
**Resolution:** keep buzzword-avoidance as **editorial guidance**, not a "reduces reach"
|
||||||
|
ranking claim. (The plugin already enforces a buzzword list via a hook — keep the list,
|
||||||
|
fix the *justification*.)
|
||||||
|
|
||||||
|
### D8. AI-content down-rank — Confidence: high (officially confirmed) — *the build-justifying finding*
|
||||||
|
|
||||||
|
**External findings:**
|
||||||
|
- **Officially confirmed, named executive:** LinkedIn VP & Executive Editor Laura
|
||||||
|
Lorenzetti (2026-05-19) confirmed an active program targeting (1) generic AI-written
|
||||||
|
posts/comments, (2) automation tools, (3) attention-bait video. Mechanism: ML models
|
||||||
|
trained on thousands of human-annotated posts distinguish "original thinking" from
|
||||||
|
"posts lacking substance"; **low-quality-flagged posts are reach-suppressed (reportedly
|
||||||
|
down to first-degree connections), not deleted.** [entrepreneur.com/business-news/linkedin-is-fighting-back-against-ai-slop-and-ai-comments]
|
||||||
|
- Corroborated: Jobanputra (Feed) — "we actively detect and limit the reach of spammy or
|
||||||
|
low-quality content, including bot-generated posts." Originality.ai (8,795 posts):
|
||||||
|
likely-AI posts saw **45% less engagement** (correlational). [prdaily.com/...guardians-of-the-feed; originality.ai/blog/ai-content-published-linkedin]
|
||||||
|
- Also officially confirmed and relevant: **engagement-pod crackdown** (VP Product
|
||||||
|
Gyanda Sachdeva, 2026-02-16 — auto-comments demoted out of "Most Relevant", scoped to
|
||||||
|
own network, repeat offenders restricted). [socialmediatoday.com/news/linkedin-outlines-more-measures-to-combat-engagement-pods/812290/]
|
||||||
|
|
||||||
|
**Resolution:** **build the short-form de-AI / differentiation gate** — it targets an
|
||||||
|
officially-confirmed suppression surface. Enforce the signals LinkedIn *named* (personal
|
||||||
|
substance, original thinking, concrete specifics, genuine voice), not an unverified SEO
|
||||||
|
"tell-list."
|
||||||
|
|
||||||
|
## External Knowledge
|
||||||
|
|
||||||
|
### Best Practice (official / primary)
|
||||||
|
Only two ranking signals are officially named: **dwell time** and **topic/interest
|
||||||
|
relevance**. LinkedIn officially **denies an intentional link penalty** and officially
|
||||||
|
**confirms an AI-slop down-rank** + **engagement-pod enforcement**. Everything else
|
||||||
|
(coefficients, multipliers, windows) is third-party.
|
||||||
|
|
||||||
|
### Alternatives / contrarian
|
||||||
|
The contrarian pass refuted 6 of 7 plugin claims **on magnitude/naming, not direction**:
|
||||||
|
the strategic advice (favor native formats, prompt quality comments, write with
|
||||||
|
substance, expect link posts to underperform, post when the audience is active) survives;
|
||||||
|
the specific numbers and the "360Brew, Jan 2026" branding do not. Two need **outright
|
||||||
|
correction**: the model name/date, and the "no analytics API → CSV only" premise (see D9
|
||||||
|
in Topic 2 — Member Post Analytics API launched 2025-07-08).
|
||||||
|
|
||||||
|
### Known issues
|
||||||
|
Numbers rot: every magnitude is observational and moves year-to-year (link penalty
|
||||||
|
5%→42%→38%; carousel 6.6%→6.45%). A fabricated citation ("Hristo Danchev / Mar-12-2026")
|
||||||
|
is actively circulating — do not propagate any single named-source deployment claim
|
||||||
|
without first-hand re-verification.
|
||||||
|
|
||||||
|
## Gemini Second Opinion
|
||||||
|
|
||||||
|
Independent ~22-min deep-research pass (27 grounding sources). Agreements with the swarm:
|
||||||
|
360Brew is a Jan-**2025** pre-production paper, not a confirmed 2026 production system;
|
||||||
|
saves/dwell primacy; carousel #1 with methodology-driven rate spread; 90-min window;
|
||||||
|
**per-post Saves ARE visible in the native UI for your own posts**; a Member Post
|
||||||
|
Analytics API exists but is gated behind Community Management API approval (not
|
||||||
|
self-serve). Unique contribution: independently flagged the "Hristo Danchev / March 2026
|
||||||
|
engineering post" citation as likely **fabricated**, which is *why* this brief refuses to
|
||||||
|
publish any deployed-model name even though two of the swarm agents cited "GR."
|
||||||
|
|
||||||
|
## Synthesis
|
||||||
|
|
||||||
|
Three insights emerge only from triangulation:
|
||||||
|
|
||||||
|
1. **The plugin's contradictions are mostly denominator/era artifacts, not errors of
|
||||||
|
fact.** "40-50% vs 25-40%" = the same link number at peak vs average; "6.6% vs 1.92%"
|
||||||
|
= a format benchmark vs a personal-profile baseline; "15x vs 5x" = a folklore comment
|
||||||
|
figure vs the real *saves* figure mis-assigned. The fix is therefore **one canonical
|
||||||
|
statement that names the era, the denominator, and the account type** — not a hunt for
|
||||||
|
"the right number." This is the single most important design instruction for Phase 0.2.
|
||||||
|
|
||||||
|
2. **Encode ordering + officially-named signals, not coefficients.** The only durable,
|
||||||
|
defensible spine is: *dwell + topic-relevance are the two officially-named signals;
|
||||||
|
saves > shares > quality-comments > reactions is the engagement order; documents are
|
||||||
|
the top format.* Every coefficient must carry a source + confidence + "directional,
|
||||||
|
test per account" caveat. A `references/algorithm-signals-reference.md` rebuilt around
|
||||||
|
*named signals + ordering + per-claim source column* makes the contradictions
|
||||||
|
structurally impossible to reintroduce.
|
||||||
|
|
||||||
|
3. **The two highest-confidence findings each map to a Phase-2 build decision.** The
|
||||||
|
officially-confirmed **AI-slop down-rank** justifies the **short-form de-AI gate**
|
||||||
|
(D8); the officially-confirmed **link-intent principle** (value-first, location-
|
||||||
|
secondary) rewrites the link advice (D4). Both are now grounded in *named-executive*
|
||||||
|
sources, not vendor blogs — the strongest evidence in the whole pass.
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
- **Deployed model name/date** — unresolvable from open sources and partly contaminated
|
||||||
|
by a fabricated citation. *Carry as: do not assert; state "an LLM relevance model is
|
||||||
|
live in 2026" only.* No further research will likely fix this before publication.
|
||||||
|
- **Link-penalty exact magnitude & first-comment status** — genuinely contested
|
||||||
|
(~19–60%; first-comment net-positive vs detected). *Carry as a range + "test per
|
||||||
|
account"; do not hard-code.*
|
||||||
|
- **Member Post Analytics API self-serve depth** — answered enough here to act, but is the
|
||||||
|
primary subject of **Topic 2** (verify gating + saves-UI before writing boundary prose).
|
||||||
|
|
||||||
|
## Recommendation
|
||||||
|
|
||||||
|
For the Phase-0 "reconcile to one sourced statement" step, adopt this canonical spine and
|
||||||
|
make every command/agent cite it:
|
||||||
|
|
||||||
|
1. **Model:** "An LLM-based relevance-ranking system is live on LinkedIn in 2026." **No
|
||||||
|
name, no date.** Remove "360Brew" and "January 2026" from CLAUDE.md/README/profile.
|
||||||
|
2. **Signals (officially named):** dwell time; topic/interest relevance. **Engagement
|
||||||
|
order:** saves > shares > quality comments > reactions; likes ≈ 1x baseline. No
|
||||||
|
coefficients without a source column; comment ≈ 2x like is the most defensible single
|
||||||
|
figure (medium).
|
||||||
|
3. **Format:** documents/carousels are the top organic format (~7%, Socialinsider,
|
||||||
|
company-page per-impression). Delete the 1.92% carousel claim (it's a personal-profile
|
||||||
|
baseline). Native video #2 and *declining*.
|
||||||
|
4. **Links:** correlational reach reduction (~38% in 2026; contested ~19–60%); LinkedIn
|
||||||
|
denies intentional penalty; value-first matters more than link location; first-comment
|
||||||
|
is a hedge, not a fix. Soften the enforcing hook from a hard % mechanic.
|
||||||
|
5. **Timing:** 60–90 min early window (first 15–30 min highest-leverage); add evergreen
|
||||||
|
resurfacing (days-to-weeks, intent-driven); drop the strict-60-min fixation and the
|
||||||
|
"24–72h reinjection" precision.
|
||||||
|
6. **Profile/topic:** real ranking input (keep); **drop the −40-60% figure.**
|
||||||
|
7. **Buzzwords:** editorial guidance only (keep the list, fix the "reduces reach" claim).
|
||||||
|
8. **Build the de-AI gate** (D8, officially-confirmed surface) and **reframe link advice
|
||||||
|
around intent** (D4). Both are Phase-2 builds with named-executive backing.
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
| # | Source | Type | Quality | Used in |
|
||||||
|
|---|--------|------|---------|---------|
|
||||||
|
| 1 | [arXiv 2501.16450 — 360Brew (withdrawn 2025-08-23)](https://arxiv.org/abs/2501.16450) | official | high | D1 |
|
||||||
|
| 2 | [LinkedIn Eng — Engineering the next-gen Feed (provenance contested)](https://www.linkedin.com/blog/engineering/feed/engineering-the-next-generation-of-linkedins-feed) | official(?) | low | D1 |
|
||||||
|
| 3 | [LinkedIn Eng — Leveraging Dwell Time (2024-10-01)](https://www.linkedin.com/blog/engineering/feed/leveraging-dwell-time-to-improve-member-experiences-on-the-linkedin-feed) | official | high | D2 |
|
||||||
|
| 4 | [Tim Jurka — How Does the LinkedIn Feed Work? (2025-08-11)](https://www.linkedin.com/pulse/how-does-linkedin-feed-work-tim-jurka-oxraf) | official | high | D6 |
|
||||||
|
| 5 | [AuthoredUp — LinkedIn Algorithm (621K posts)](https://authoredup.com/blog/linkedin-algorithm) | community | medium | D2, D3, D5 |
|
||||||
|
| 6 | [Socialinsider — LinkedIn benchmarks (1.3M)](https://www.socialinsider.io/social-media-benchmarks/linkedin) | community | medium | D3 |
|
||||||
|
| 7 | [Buffer — Best Content Format (2M+)](https://buffer.com/resources/data-best-content-format-social-media/) | community | medium | D3 |
|
||||||
|
| 8 | [Metricool — 2026 LinkedIn study (673K)](https://metricool.com/linkedin-trends/) | community | medium | D3 |
|
||||||
|
| 9 | [Ordinal — Link Penalty Study (900K, p<0.001)](https://www.tryordinal.com/blog/linkedin-link-penalty-study) | community | medium-high | D4 |
|
||||||
|
| 10 | [Threads/Matt Navarra — LinkedIn denies intentional link penalty](https://www.threads.com/@mattnavarra/post/DOWa_61Cown/) | official (relayed) | medium | D4 |
|
||||||
|
| 11 | [Entrepreneur — LinkedIn fights AI slop (Lorenzetti, 2026-05-19)](https://www.entrepreneur.com/business-news/linkedin-is-fighting-back-against-ai-slop-and-ai-comments) | official (reported) | high | D8 |
|
||||||
|
| 12 | [PR Daily — Guardians of the Feed (Jobanputra)](https://www.prdaily.com/what-works-and-doesnt-on-linkedin-according-to-guardians-of-the-feed/) | official (reported) | medium-high | D4, D8 |
|
||||||
|
| 13 | [Social Media Today — engagement-pod crackdown (Sachdeva, 2026-02-16)](https://www.socialmediatoday.com/news/linkedin-outlines-more-measures-to-combat-engagement-pods/812290/) | official (reported) | high | D8 |
|
||||||
|
| 14 | [Originality.ai — AI content on LinkedIn (45% gap)](https://originality.ai/blog/ai-content-published-linkedin) | community | medium | D8 |
|
||||||
|
| 15 | [van der Blom — Algorithm Insights 2025 (1.8M)](https://www.scribd.com/document/984921783/Algorithm-Insights-Report-2025-chapter-1-Richard-Van-der-Blom) | community | medium | D2, D4, D5 |
|
||||||
|
| 16 | [meet-lea — LinkedIn Algorithm Explained 2026](https://meet-lea.com/en/blog/linkedin-algorithm-explained) | community | low-medium | D2 |
|
||||||
|
| 17 | [Microsoft Learn — Member Post Statistics API](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/members/post-statistics?view=li-lms-2025-11) | official | high | D2/Topic-2 |
|
||||||
|
| 18 | [Inc. — buzzwords to scrub](https://www.inc.com/amy-george/14-buzzwords-to-scrub-from-your-linkedin-page-right-now.html) | community | low | D7 |
|
||||||
|
|
@ -0,0 +1,218 @@
|
||||||
|
---
|
||||||
|
type: trekresearch-brief
|
||||||
|
created: 2026-05-29
|
||||||
|
question: "As of 2026, can a personal LinkedIn profile self-serve post-level analytics via an API, are per-post saves visible in the native UI, and can a personal profile auto-publish via any API — with the exact constraints for a solo user without partner/company-page access?"
|
||||||
|
confidence: 0.86
|
||||||
|
dimensions: 4
|
||||||
|
mcp_servers_used: [tavily]
|
||||||
|
local_agents_used: []
|
||||||
|
external_agents_used: [docs-researcher, community-researcher, contrarian-researcher]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Personal-Profile Analytics + Auto-Publish Boundaries (2026)
|
||||||
|
|
||||||
|
> Generated by trekresearch (standard external swarm: docs + community + contrarian; no
|
||||||
|
> Gemini — scoped to Topic 1) on 2026-05-29. Topic 2 of 3 for the linkedin-studio
|
||||||
|
> remediation. Feeds Phase-1 honest boundary statements + the Phase-2 saves honesty-fix.
|
||||||
|
> **Primary-sourced from Microsoft Learn (LinkedIn's canonical dev docs).**
|
||||||
|
|
||||||
|
## Research Question
|
||||||
|
|
||||||
|
As of 2026, can a personal LinkedIn profile self-serve post-level analytics via an API,
|
||||||
|
are per-post saves visible in the native UI, and can a personal profile auto-publish via
|
||||||
|
any API — with the exact constraints for a solo user without partner/company-page access?
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
**The audit's own boundary assumptions are partly wrong, and fixing them naively would
|
||||||
|
replace one false claim with another.** Three findings, all primary-sourced and
|
||||||
|
high-confidence: (1) a personal profile **CAN auto-publish self-serve** — `w_member_social`
|
||||||
|
is an Open Permission via the free "Share on LinkedIn" product, publishing immediately at
|
||||||
|
~150 posts/member/day; the plugin's clipboard-stop is a **design + Terms-of-Service
|
||||||
|
choice, not an API impossibility**. (2) Per-post **saves ARE visible** in native post
|
||||||
|
analytics (rolled out ~Sept 2025, count-only, no saver identity) **and** exposed via the
|
||||||
|
API's `POST_SAVE` metric (since version li-lms-2026-04) — so "saves aren't trackable" is
|
||||||
|
**stale/false**; the honest line is "visible in the UI, not self-serve via API." (3)
|
||||||
|
Post-level analytics via API **exist** (`memberCreatorPostAnalytics` / `r_member_postAnalytics`)
|
||||||
|
but sit behind the **vetted Community Management API** (verified organization + associated
|
||||||
|
LinkedIn Page + use-case review) — **not self-serve for a solo creator**; CSV export is the
|
||||||
|
practical floor, not the only technical path. **Key caveat:** every boundary statement
|
||||||
|
must be *dated* ("as of 2026-05") — this surface changed fast (saves went UI→API inside
|
||||||
|
~12 months).
|
||||||
|
|
||||||
|
## Dimensions
|
||||||
|
|
||||||
|
### D1. Post-level analytics API for personal profiles — Confidence: high
|
||||||
|
|
||||||
|
**External findings:**
|
||||||
|
- The endpoint exists: `GET /rest/memberCreatorPostAnalytics` ("Member Post Statistics"),
|
||||||
|
permission `r_member_postAnalytics` (versions ≥ li-lms-202506). Finders `q=entity`
|
||||||
|
(one post) and `q=me` (aggregated). Metrics: `IMPRESSION`, `MEMBERS_REACHED`, `RESHARE`,
|
||||||
|
`REACTION`, `COMMENT` (since 2025-06) and — added **li-lms-2026-04** — `POST_SAVE`,
|
||||||
|
`POST_SEND`, `LINK_CLICKS`, `PREMIUM_CTA_CLICKS`, `FOLLOWER_GAINED_FROM_CONTENT`,
|
||||||
|
`PROFILE_VIEW_FROM_CONTENT`. (Video metrics are a separate endpoint
|
||||||
|
`memberCreatorVideoAnalytics`; follower count is `memberFollowersCount` /
|
||||||
|
`r_member_profileAnalytics`.) [learn.microsoft.com/.../members/post-statistics?view=li-lms-2026-05]
|
||||||
|
- **The access gate is the crux:** `r_member_postAnalytics` is listed **exclusively under
|
||||||
|
the Community Management API** (a "Vetted Product") — never in the consumer Open
|
||||||
|
Permissions. Community Management approval requires an approved use case, **verified
|
||||||
|
organization**, verified domain, **an app verified by a LinkedIn Page associated with
|
||||||
|
the same organization**, and (Standard tier) a privacy policy + screencast review.
|
||||||
|
Dev-tier rate limits: 500 calls/app/24h, 100/member/24h. [learn.microsoft.com/.../increasing-access; .../community-management-app-review]
|
||||||
|
- The 2025 "LinkedIn opened Member Post Analytics to individuals" headline is true **only
|
||||||
|
through approved partner platforms** (Metricool/Buffer/Hootsuite-class) that hold the
|
||||||
|
approval — not by a creator calling the API directly. LinkedIn is also *tightening*:
|
||||||
|
the read scope `r_member_social` (Member Post Management) is flatly **closed** — "not
|
||||||
|
accepting access requests at this time due to resource constraints."
|
||||||
|
|
||||||
|
**Contradictions:** "CSV is the only way" (plugin/audit) is wrong at the *capability*
|
||||||
|
level (an API exists) but right at the *practical* level for a solo dev (the API is
|
||||||
|
org-vetted). **Resolution:** state "exists but partner-gated; not self-serve; CSV is the
|
||||||
|
practical floor for a solo creator," not "no API exists."
|
||||||
|
|
||||||
|
### D2. Per-post saves visibility — Confidence: high
|
||||||
|
|
||||||
|
**External findings:**
|
||||||
|
- **Native UI:** the official LinkedIn Help page on post analytics lists, under Social
|
||||||
|
Engagement: Reactions, Comments, Reposts, **Saves** ("number of times members saved
|
||||||
|
your post"), Sends — rolled out ~**Sept 2025**. **Count only, never saver identity.**
|
||||||
|
Rollout was phased and historical backfill limited (older posts may show no saves).
|
||||||
|
[linkedin.com/help/linkedin/answer/a516971; socialmediatoday.com/news/linkedin-adds-save-and-send-data-to-content-insights/759828/]
|
||||||
|
- **API:** `POST_SAVE` exposed via `memberCreatorPostAnalytics` from li-lms-2026-04 — but
|
||||||
|
behind the same Community-Management gate as D1 (not self-serve).
|
||||||
|
|
||||||
|
**Resolution (sharpens the Q3 honesty-fix):** the honest downgrade is **NOT** "saves
|
||||||
|
can't be tracked." It is: *"saves are visible in your native LinkedIn post analytics
|
||||||
|
(since Sept 2025, count-only) but there is no self-serve API to pull them, so this tool
|
||||||
|
does not auto-ingest them — read them in LinkedIn directly."* The operator's decision (no
|
||||||
|
manual-entry feature) stands and is defensible: the number is human-readable but
|
||||||
|
programmatically out of reach + would be hand-typed and instantly stale.
|
||||||
|
|
||||||
|
### D3. Auto-publish from a personal profile — Confidence: high (capability) / the ToS line is the real boundary
|
||||||
|
|
||||||
|
**External findings:**
|
||||||
|
- **A personal profile CAN auto-publish, self-serve.** `w_member_social` is an **Open
|
||||||
|
Permission** (no approval), granted by adding the free **"Share on LinkedIn"** product
|
||||||
|
(filed under `.../integrations/self-serve/`). `POST /v2/ugcPosts` (or `/rest/posts`)
|
||||||
|
with `lifecycleState: PUBLISHED` publishes **immediately, no human-in-the-loop**. Rate
|
||||||
|
limit ~150 requests/member/day. Self-serve content types: text, image, video,
|
||||||
|
article/URL share. [learn.microsoft.com/.../share-on-linkedin; .../getting-access]
|
||||||
|
- **Genuine limitations:** organic **carousels (ad-style) are NOT available** via API for
|
||||||
|
a personal profile (sponsored only) — use **MultiImage** for swipeable images; **member
|
||||||
|
document/PDF** posts and the modern `/rest/posts` *member* path are doc-ambiguous (don't
|
||||||
|
promise without testing). You also **cannot self-serve read your own posts back**
|
||||||
|
(`r_member_social` is restricted/closed) — capture the post URN from the publish
|
||||||
|
response header instead.
|
||||||
|
- **Operational friction (real, first-hand):** 3-legged OAuth (one interactive auth);
|
||||||
|
60-day access tokens / ~365-day refresh tokens that invalidate on revoke **or scope
|
||||||
|
change** (`invalid_grant` footgun); a common `403 me.GET.NO_VERSION` trap (use
|
||||||
|
`/userinfo` `sub`, not `/v2/me`); a placeholder company-page link required at app setup
|
||||||
|
even for personal-only posting; "outdated docs everywhere." [marcusnoble.co.uk/2025-02-02-posting-to-linkedin-via-the-api/; github.com/linkedin-developers/linkedin-api-js-client/issues/35]
|
||||||
|
- **The real boundary is ToS, not capability:** `w_member_social` being "open" does not
|
||||||
|
mean automated/scheduled posting is within LinkedIn's API Terms. The legitimate basis
|
||||||
|
for the plugin's clipboard-stop is **(a) per-user OAuth + token-refresh operational
|
||||||
|
overhead and (b) LinkedIn's Terms posture on automated posting** — not "the API can't
|
||||||
|
do it."
|
||||||
|
|
||||||
|
**Resolution:** the plugin must **not** write "cannot auto-publish." Honest framing:
|
||||||
|
*"auto-publish to a personal profile is technically possible and self-serve (Share on
|
||||||
|
LinkedIn / `w_member_social`); the plugin deliberately stops at the clipboard — OAuth +
|
||||||
|
60-day-token overhead and LinkedIn's Terms on automated posting make human-in-the-loop
|
||||||
|
the safer default. A choice, not a wall."* (Verify current Platform/Marketing API Terms
|
||||||
|
language on automated personal posting before finalizing the wording.)
|
||||||
|
|
||||||
|
### D4. Dwell time exportability — Confidence: medium-high (organic boundary holds, with carve-outs)
|
||||||
|
|
||||||
|
**External findings:**
|
||||||
|
- For **organic personal posts**, there is **no creator-facing dwell metric** — not in the
|
||||||
|
UI Help metric list, not in `memberCreatorPostAnalytics`. Dwell is an internal ranking
|
||||||
|
signal only. [linkedin.com/help/linkedin/answer/a516971]
|
||||||
|
- **Carve-outs (so the claim isn't falsifiable):** (1) **video** posts expose **Watch
|
||||||
|
time / Average watch time** (UI + member video API) — a real per-post depth metric, just
|
||||||
|
not for non-video; (2) `averageDwellTime` exists in the **ads** `adAnalytics` API (paid
|
||||||
|
campaigns only, methodology improved ~+25% in a 2026 change).
|
||||||
|
|
||||||
|
**Resolution:** "for organic posts, dwell is internal-only — no creator number; video
|
||||||
|
Watch time is the closest creator-visible depth proxy; a true dwell field exists but only
|
||||||
|
for paid ads." Do not say "LinkedIn has no dwell metric anywhere."
|
||||||
|
|
||||||
|
## External Knowledge
|
||||||
|
|
||||||
|
### Best Practice (official / primary)
|
||||||
|
Microsoft Learn (li-lms-2026-05) is authoritative. The self-serve set for a solo dev is
|
||||||
|
exactly three Open Permissions: `profile`, `email`, `w_member_social`. **Everything
|
||||||
|
analytics** is Community-Management-vetted. This single fact drives every honest boundary
|
||||||
|
statement.
|
||||||
|
|
||||||
|
### Known issues
|
||||||
|
The surface moves fast and docs lag the API (saves went invisible→UI→API in ~12 months;
|
||||||
|
`r_member_social` went from program to closed). Any boundary statement must be **dated**.
|
||||||
|
Practitioner reality adds friction (token fragility, the `/me` 403 trap) that makes
|
||||||
|
"feasible" ≠ "frictionless."
|
||||||
|
|
||||||
|
## Synthesis
|
||||||
|
|
||||||
|
1. **The audit's "honest scheduling boundary" finding (§5) was itself half-wrong.** It
|
||||||
|
assumed the plugin "stops at the clipboard because it can't auto-publish." The truth:
|
||||||
|
it *can* (self-serve), and the honest disclosure is about the **design + ToS choice**,
|
||||||
|
not an impossibility. Phase 1's boundary prose must encode the *choice*, or it ships a
|
||||||
|
brand-new false claim — exactly the failure mode this whole remediation exists to kill.
|
||||||
|
|
||||||
|
2. **The saves honesty-fix is a wording fix, not a "we can't" fix.** Saves are now
|
||||||
|
visible in the UI. The accurate downgrade points the user to LinkedIn's own analytics
|
||||||
|
for the number while being honest that the tool can't pull it. This keeps the operator's
|
||||||
|
"no manual-entry" decision *and* avoids a stale "saves aren't trackable" claim.
|
||||||
|
|
||||||
|
3. **There is a latent, deliberately-deferred capability here.** Auto-publish is buildable
|
||||||
|
self-serve. It is explicitly a **Non-Goal** of this remediation (operator: disclose
|
||||||
|
boundaries, don't engineer them away). Worth a one-line "possible but intentionally not
|
||||||
|
built (ToS + token overhead)" note so a future maintainer doesn't rediscover it as a
|
||||||
|
"missing feature."
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
- **Exact ToS language on automated/scheduled personal posting** — the load-bearing reason
|
||||||
|
for the clipboard-stop. *Carry as: verify current Platform/Marketing API Terms before
|
||||||
|
finalizing the boundary wording.* (Not a blocker for the plan; a finalize-time check.)
|
||||||
|
- **Member document/PDF + `/rest/posts` member-author path** — doc-ambiguous. Irrelevant
|
||||||
|
unless a future version builds publishing; flag, don't resolve.
|
||||||
|
|
||||||
|
## Recommendation
|
||||||
|
|
||||||
|
For Phase 1 (honest boundaries) and Phase 2 (saves honesty-fix), write these **dated**
|
||||||
|
statements:
|
||||||
|
|
||||||
|
1. **Analytics API:** "A post-level analytics API for personal posts exists, but it's
|
||||||
|
behind LinkedIn's vetted Community Management API (verified organization + LinkedIn
|
||||||
|
Page + use-case review) — not self-serve for an individual. For a solo creator, CSV
|
||||||
|
export is the practical path. (As of 2026-05.)"
|
||||||
|
2. **Saves:** "Saves are visible in your native LinkedIn post analytics (since ~Sept 2025,
|
||||||
|
count-only). There's no self-serve API to pull them, so this tool doesn't track saves
|
||||||
|
automatically — read them in LinkedIn directly." Remove any "saves (10x weight) are the
|
||||||
|
highest-impact signal" claim presented *inside a report the tool populates*; reframe as
|
||||||
|
strategy guidance pointing to the native number.
|
||||||
|
3. **Auto-publish:** "Auto-publishing to a personal profile is technically possible
|
||||||
|
(self-serve `w_member_social`); this tool deliberately stops at the clipboard — OAuth +
|
||||||
|
token-refresh overhead and LinkedIn's Terms on automated posting. A choice, not a
|
||||||
|
limitation. (As of 2026-05.)" Reconcile the calendar/queue/"publish action" wording so
|
||||||
|
it never implies the tool auto-posts.
|
||||||
|
4. **Dwell:** "Dwell time is an internal ranking signal — no creator-facing number for
|
||||||
|
organic posts (video Watch time is the closest proxy)."
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
| # | Source | Type | Quality | Used in |
|
||||||
|
|---|--------|------|---------|---------|
|
||||||
|
| 1 | [Member Post Statistics API (li-lms-2026-05)](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/members/post-statistics?view=li-lms-2026-05) | official | high | D1, D2 |
|
||||||
|
| 2 | [Increasing Access — permissions table](https://learn.microsoft.com/en-us/linkedin/marketing/increasing-access?view=li-lms-2026-05) | official | high | D1 |
|
||||||
|
| 3 | [Community Management App Review](https://learn.microsoft.com/en-us/linkedin/marketing/community-management-app-review?view=li-lms-2026-05) | official | high | D1 |
|
||||||
|
| 4 | [Community Management Overview / FAQ (r_member_social closed)](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/community-management-overview?view=li-lms-2026-05) | official | high | D1 |
|
||||||
|
| 5 | [Share on LinkedIn (self-serve)](https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/share-on-linkedin) | official | high | D3 |
|
||||||
|
| 6 | [Getting Access — Open Permissions](https://learn.microsoft.com/en-us/linkedin/shared/authentication/getting-access) | official | high | D3 |
|
||||||
|
| 7 | [Post analytics for your content — LinkedIn Help (Saves listed)](https://www.linkedin.com/help/linkedin/answer/a516971) | official | high | D2, D4 |
|
||||||
|
| 8 | [Social Media Today — LinkedIn adds Save/Send data (Sept 2025)](https://www.socialmediatoday.com/news/linkedin-adds-save-and-send-data-to-content-insights/759828/) | community | medium-high | D2 |
|
||||||
|
| 9 | [Refresh Tokens with OAuth 2.0](https://learn.microsoft.com/en-us/linkedin/shared/authentication/programmatic-refresh-tokens) | official | high | D3 |
|
||||||
|
| 10 | [Marcus Noble — Posting to LinkedIn via the API (first-hand)](https://marcusnoble.co.uk/2025-02-02-posting-to-linkedin-via-the-api/) | community | medium-high | D3 |
|
||||||
|
| 11 | [GitHub — linkedin-api-js-client #35 (403 /me trap)](https://github.com/linkedin-developers/linkedin-api-js-client/issues/35) | community | medium | D3 |
|
||||||
|
| 12 | [Recent Marketing API Changes (ads averageDwellTime)](https://learn.microsoft.com/en-us/linkedin/marketing/integrations/recent-changes?view=li-lms-2026-01) | official | high | D4 |
|
||||||
|
| 13 | [ConnectSafely — LinkedIn API guide 2026 (approval reality)](https://connectsafely.ai/articles/linkedin-api-complete-guide-2026) | community | low-medium | D1 |
|
||||||
|
|
@ -0,0 +1,225 @@
|
||||||
|
---
|
||||||
|
type: trekresearch-brief
|
||||||
|
created: 2026-05-29
|
||||||
|
question: "For LinkedIn in 2026: hard short-form video requirements the algorithm rewards (aspect ratio, resolution, hook timing, captions); what triggers the templated-AI/engagement-bait down-rank; and newsletter-distribution mechanics (notification leverage, cadence, realistic cold-start numbers)?"
|
||||||
|
confidence: 0.80
|
||||||
|
dimensions: 5
|
||||||
|
mcp_servers_used: [tavily]
|
||||||
|
local_agents_used: []
|
||||||
|
external_agents_used: [docs-researcher, community-researcher, contrarian-researcher]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Coverage-Gap Feature Specs — Video · De-AI · Newsletter Distribution (2026)
|
||||||
|
|
||||||
|
> Generated by trekresearch (standard external swarm: docs + community + contrarian)
|
||||||
|
> on 2026-05-29. Topic 3 of 3 for the linkedin-studio remediation. Feeds the Phase-2
|
||||||
|
> video gate, short-form de-AI gate, and newsletter-distribution surface.
|
||||||
|
> **The de-AI signal is covered in depth in Topic 1 (D8); this brief carries the
|
||||||
|
> video + newsletter specs and a short de-AI cross-reference.**
|
||||||
|
|
||||||
|
## Research Question
|
||||||
|
|
||||||
|
For LinkedIn in 2026: hard short-form video requirements the algorithm rewards (aspect
|
||||||
|
ratio, resolution, hook timing, captions); what triggers the templated-AI / engagement-bait
|
||||||
|
down-rank; and newsletter-distribution mechanics (notification leverage, cadence, realistic
|
||||||
|
cold-start numbers)?
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
**Three of the audit's proposed coverage-gap features rest on wrong premises, and building
|
||||||
|
them naively would bake new false claims into the plugin.** (1) A **hard 9:16 video gate is
|
||||||
|
wrong** — LinkedIn's audience is desktop-heavy; 9:16 is mobile-only and **crops on desktop**;
|
||||||
|
the broad-distribution picks are **4:5 / 1:1**, with 9:16 a mobile-only opt-in. The plugin's
|
||||||
|
internal contradiction ("4:5 preferred" vs "deprioritized") resolves toward **"4:5 preferred."**
|
||||||
|
The "3-second hook" is **imported TikTok folklore**, not a LinkedIn rule. The one video spec
|
||||||
|
**safe to enforce is captions** (80–85% watch muted; +12% watch-time per LinkedIn; caption
|
||||||
|
text is indexed) — but framed as best-practice, not "LinkedIn requires SRT." (2) **Native
|
||||||
|
video reach is DOWN ~36% YoY** (per-video views, Socialinsider 1.3M); the "video is king
|
||||||
|
2026" narrative is hype from a benchmark misread — so the video gate must be a **quality
|
||||||
|
gate for users who choose video**, never copy that positions video as top-reach. (3) The
|
||||||
|
newsletter **"triple-notification" framing is oversold** — LinkedIn's own FAQ says the three
|
||||||
|
channels are **deduplicated** (one notification per event); the real benefit is
|
||||||
|
"**bypasses organic feed ranking**," not three guaranteed touchpoints; and the **one-time
|
||||||
|
launch invite** makes a sub-~1–2K-follower newsletter premature. **Key caveat:** aspect-ratio
|
||||||
|
performance has **no rigorous comparative study** — encode as heuristic, never a hard gate;
|
||||||
|
date every claim ("as of 2026-05"). (Confidence 0.80 — high on captions / video-decline /
|
||||||
|
newsletter mechanics; medium on aspect-ratio + cold-start ranges.)
|
||||||
|
|
||||||
|
## Dimensions
|
||||||
|
|
||||||
|
### D1. Video upload specs (official, enforceable) — Confidence: high
|
||||||
|
|
||||||
|
**External findings (LinkedIn Help, primary):**
|
||||||
|
- Aspect ratio accepted **1:2.4–2.4:1**; resolution **256×144–4096×2304**; duration **min
|
||||||
|
3s desktop / 2s mobile, max 15min desktop / 10min mobile**; file **75KB–5GB**; **10–60fps**;
|
||||||
|
**192Kbps–30Mbps**; **MP4 the safe default**. [linkedin.com/help/linkedin/answer/a548372; .../a1311816]
|
||||||
|
- **Official-source conflict:** the member troubleshooting page lists MOV/AVI as *supported*;
|
||||||
|
the Pages spec says LinkedIn "no longer supports AVI, QuickTime, or MOV." **Resolution for
|
||||||
|
a gate:** enforce MP4 as the safe default; **warn-only** (not block) on MOV/AVI.
|
||||||
|
|
||||||
|
### D2. Aspect ratio — 9:16 is NOT a clean win — Confidence: medium (no rigorous data)
|
||||||
|
|
||||||
|
**External findings:**
|
||||||
|
- LinkedIn runs an official **full-screen vertical video experience** (video tab + carousel)
|
||||||
|
that **vertical (≈9:16) fills uncropped**; landscape is letterboxed. BUT you **cannot
|
||||||
|
directly post into** that surface — placement is algorithmic. [linkedin.com/help/linkedin/answer/a6290168]
|
||||||
|
- The contrarian + spec-guide consensus: for the **main feed** on a **desktop-heavy
|
||||||
|
professional audience**, **4:5 and 1:1** are the broad-distribution picks; **9:16 is
|
||||||
|
delivered mobile-only and crops to 1:1 on desktop** — a real degradation. The only official
|
||||||
|
numeric 9:16 target (720×1280 rec / 1080×1920 max) is scoped to **ads**, not organic.
|
||||||
|
[aspectratiocalculator.com/linkedin-aspect-ratios/; LinkedIn recommendation post]
|
||||||
|
- **No peer-reviewed or official study compares 4:5 vs 9:16 vs 1:1 engagement.** The
|
||||||
|
"vertical takes more feed real estate → more engagement" claim is uncorroborated heuristic.
|
||||||
|
|
||||||
|
**Resolution:** the plugin's contradiction resolves toward **"4:5 / 1:1 preferred for broad
|
||||||
|
distribution; 9:16 mobile-only opt-in / for the video tab."** Fix the file that calls 4:5
|
||||||
|
"deprioritized." **Do not build a hard 9:16 gate** — make aspect ratio guidance, not enforcement.
|
||||||
|
|
||||||
|
### D3. Hook timing + captions — Confidence: high (captions) / low (3-sec hook)
|
||||||
|
|
||||||
|
**External findings:**
|
||||||
|
- The **"3-second hook" is cross-platform folklore** (TikTok/Reels), absent from
|
||||||
|
LinkedIn-specific algorithm analyses; LinkedIn's only official "3 seconds" is the *minimum
|
||||||
|
video length*. The real LinkedIn-native reason the opening matters is **muted autoplay**.
|
||||||
|
[dataslayer.ai/blog/linkedin-algorithm-february-2026-whats-working-now; sproutsocial.com/insights/linkedin-video/]
|
||||||
|
- **Captions are the one spec safe to enforce:** ~80–85% watch muted; LinkedIn's own data
|
||||||
|
~**+12% watch-time** with captions; **caption text is indexed for search/discovery** and
|
||||||
|
factored into distribution. Both **SRT upload and native auto-captions** are first-party
|
||||||
|
(auto-captions in 10 languages, opt-in, reviewable). [opus.pro/blog/linkedin-video-caption-subtitle-best-practices; linkedin.com/help/linkedin/answer/a1327025; .../a552177]
|
||||||
|
|
||||||
|
**Resolution:** enforce a **captions** quality gate (BLOCK is defensible at 80% muted),
|
||||||
|
accept SRT OR auto-captions, label it "best-practice / algorithmic signal" (not "required").
|
||||||
|
Replace any "3-second hook" rule with **"front-load value for muted autoplay."**
|
||||||
|
|
||||||
|
### D4. De-AI / engagement-bait down-rank — Confidence: high (cross-ref Topic 1 D8)
|
||||||
|
|
||||||
|
**External findings (see Topic 1 D8 for full sourcing):**
|
||||||
|
- **Officially confirmed:** LinkedIn VP Laura Lorenzetti (2026-05-19) — active program
|
||||||
|
suppressing generic AI posts/comments + automation + attention-bait video; mechanism is
|
||||||
|
**reach-suppression (down to first-degree), not deletion**, via ML trained on human-
|
||||||
|
annotated "original thinking vs lacking substance." Engagement-pod crackdown officially
|
||||||
|
confirmed too (Sachdeva, 2026-02-16). [entrepreneur.com/business-news/linkedin-is-fighting-back-against-ai-slop-and-ai-comments]
|
||||||
|
- **Engagement bait** ("Comment YES", "Like for Part 2") → post-level throttle; genuine
|
||||||
|
open questions are **not** penalized. The line is *real answer* vs *reflexive token*.
|
||||||
|
|
||||||
|
**Resolution:** **build the short-form de-AI gate** targeting the signals LinkedIn *named*
|
||||||
|
(personal substance, original thinking, concrete specifics, genuine voice) — not an
|
||||||
|
unverified SEO "tell-list." Add a soft engagement-bait check (block mechanical-response CTAs,
|
||||||
|
allow genuine questions).
|
||||||
|
|
||||||
|
### D5. Newsletter distribution mechanics — Confidence: high (mechanics) / medium (cold-start)
|
||||||
|
|
||||||
|
**External findings:**
|
||||||
|
- **Official, solid:** all members can create newsletters (**max 5, 2-week cooldown**); on
|
||||||
|
first edition LinkedIn **auto-invites all connections/followers to subscribe** (and on each
|
||||||
|
new follow thereafter); editions are **also posted to the feed** + resurface via engagement
|
||||||
|
+ appear in interest/trending sections; **lowest-friction subscribe** (no typing — LinkedIn
|
||||||
|
has the email). [linkedin.com/help/linkedin/answer/a517925; .../a522525; .../a517914]
|
||||||
|
- **"Triple-notification" is OVERSOLD / contested:** LinkedIn's FAQ states the in-app / push /
|
||||||
|
email channels are **deduplicated** — "if you receive an in-app or push notification, you
|
||||||
|
should NOT expect to also receive an email for the same." So it is **one notification per
|
||||||
|
event via the subscriber's preferred channel**, not three guaranteed touchpoints. Delivery
|
||||||
|
failures are reported; one creator measured **~2–3% click vs 8–10% on their own email list.**
|
||||||
|
The defensible benefit is **"bypasses organic feed ranking,"** not "triple notification."
|
||||||
|
- **One-time launch invite → follower floor:** the mass "invite all followers" fires **once,
|
||||||
|
at any size** — launching sub-~1–2K followers permanently spends the blast on a tiny base.
|
||||||
|
Practitioner floor: **500+ min, 3,000+ ideal.** Defensible plugin gate: ~**1–2K floor**
|
||||||
|
(aligns with the existing ~1K `/monetize`/`/outreach` unlock).
|
||||||
|
- **Realistic cold-start (don't inflate):** true zero-audience start ≈ **0–100 subs months
|
||||||
|
1–3**; viral "0→9K/7-days" and "0→10K" case studies **were NOT cold starts** (leveraged
|
||||||
|
existing audiences / 12-month grinds). Cadence: **weekly common among top performers;
|
||||||
|
biweekly a safe default for original analysis.**
|
||||||
|
- **Honest downsides to disclose:** subscribers are **non-exportable** (platform lock-in — lose
|
||||||
|
them all if LinkedIn kills the feature); LinkedIn **outranks your own site** for the same
|
||||||
|
article (no canonical) — harmful if building an owned property; **no read/open/unsubscribe
|
||||||
|
analytics**; per-subscriber reach **decays** as the list grows.
|
||||||
|
|
||||||
|
**Resolution:** the newsletter-distribution surface must teach the **honest** version:
|
||||||
|
notification-bypass-of-feed (with the dedup caveat), the one-time-launch-blast + follower
|
||||||
|
floor, realistic cold-start floors, and the lock-in/no-canonical/no-analytics downsides — NOT
|
||||||
|
the audit's "triple-notification leverage" framing.
|
||||||
|
|
||||||
|
## External Knowledge
|
||||||
|
|
||||||
|
### Best Practice (official)
|
||||||
|
Enforceable/teachable from LinkedIn's own docs: video upload limits; captions (SRT + auto);
|
||||||
|
the full-screen vertical experience exists but isn't directly postable; newsletter mechanics
|
||||||
|
(5-max/2-week cooldown, auto-invite, feed resurfacing, dedup notifications). Everything about
|
||||||
|
*reach magnitude* (video, aspect ratio, newsletter click rates) is practitioner-sourced.
|
||||||
|
|
||||||
|
### Alternatives / contrarian
|
||||||
|
The "video is king 2026" and "triple-notification leverage" narratives are **both refuted**
|
||||||
|
— the first by a benchmark misread (views −36% YoY per video; +36% is the platform aggregate),
|
||||||
|
the second by LinkedIn's own dedup FAQ. Documents/carousels lead video on engagement (~7% vs
|
||||||
|
~6%). Build features on the corrected premises.
|
||||||
|
|
||||||
|
### Known issues
|
||||||
|
Aspect-ratio guidance has no rigorous data — heuristic only. Newsletter data is non-portable.
|
||||||
|
The MOV/AVI support conflict is unresolved in LinkedIn's own docs. Date every claim.
|
||||||
|
|
||||||
|
## Synthesis
|
||||||
|
|
||||||
|
1. **"Close the coverage gaps" ≠ "build what the audit sketched."** The audit named the
|
||||||
|
gaps correctly (no video enforcement, no de-AI gate, thin newsletter distribution) but
|
||||||
|
sketched two of the fixes on hype: a 9:16 gate and "triple-notification leverage." The
|
||||||
|
research says build a **captions/aspect-guidance** video gate (not 9:16-mandatory) and an
|
||||||
|
**honest** newsletter surface (bypass-feed + caveats), not the sketched versions. This is
|
||||||
|
the same disease the whole remediation treats — features must rest on verified premises.
|
||||||
|
|
||||||
|
2. **The de-AI gate is the highest-confidence Phase-2 build** (D4 + Topic 1 D8): officially
|
||||||
|
confirmed, named-executive, with a stated mechanism. It is also the **single most robustly
|
||||||
|
triangulated 2026 down-rank signal** the audit flagged as unguarded on short-form. Prioritize it.
|
||||||
|
|
||||||
|
3. **The newsletter follower-floor reconciles two findings cleanly:** the one-time launch
|
||||||
|
blast + the existing ~1K `/monetize`/`/outreach` unlock → gate the newsletter behind a
|
||||||
|
~1–2K floor framed as "wait until you can spend the launch blast well." Below it, the
|
||||||
|
plugin should steer the user to short-form/document content to *build* the base.
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
- **Newsletter email-delivery behavior** — genuinely contested (FAQ dedup vs third-party
|
||||||
|
"always emailed"). *Carry as: state the dedup behavior from the FAQ; note delivery is not
|
||||||
|
guaranteed; a one-edition live test would resolve it.* Not a blocker.
|
||||||
|
- **Aspect-ratio engagement** — no rigorous data exists. *Carry as heuristic; never a hard gate.*
|
||||||
|
- **Dedicated vertical video feed as a primary surface** — if LinkedIn promotes it, the 9:16
|
||||||
|
calculus could flip. *Re-check before finalizing the video gate copy.*
|
||||||
|
|
||||||
|
## Recommendation
|
||||||
|
|
||||||
|
For Phase 2:
|
||||||
|
|
||||||
|
1. **Video gate = quality gate, not reach-push.** Enforce MP4 + within-limits (warn on
|
||||||
|
MOV/AVI); **enforce/strongly-recommend captions** (SRT or auto); make aspect ratio
|
||||||
|
**guidance — 4:5 / 1:1 preferred, 9:16 mobile-only opt-in**; fix the "4:5 deprioritized"
|
||||||
|
contradiction toward "4:5 preferred"; drop any "3-second hook" rule and any "video
|
||||||
|
maximizes reach" copy. Add a one-line note that per-video reach is declining and
|
||||||
|
documents out-engage video.
|
||||||
|
2. **Build the short-form de-AI gate** (highest-confidence build) on LinkedIn's named signals
|
||||||
|
(substance / original thinking / specifics / voice) + a soft engagement-bait check.
|
||||||
|
3. **Newsletter-distribution surface = the honest version:** "bypasses organic feed ranking
|
||||||
|
(one deduplicated notification per subscriber per edition)"; one-time launch-blast +
|
||||||
|
**~1–2K follower floor**; realistic cold-start floors (0–100 subs months 1–3); disclose
|
||||||
|
non-export/no-canonical/no-analytics/per-subscriber-decay. Steer sub-floor users to build
|
||||||
|
the base first.
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
| # | Source | Type | Quality | Used in |
|
||||||
|
|---|--------|------|---------|---------|
|
||||||
|
| 1 | [Video sharing troubleshooting (specs)](https://www.linkedin.com/help/linkedin/answer/a548372) | official | high | D1 |
|
||||||
|
| 2 | [Video specs for Pages (MOV/AVI conflict)](https://www.linkedin.com/help/linkedin/answer/a1311816) | official | high | D1 |
|
||||||
|
| 3 | [Full-screen vertical video](https://www.linkedin.com/help/linkedin/answer/a6290168) | official | high | D2 |
|
||||||
|
| 4 | [Aspect Ratio Calculator — LinkedIn 2026](https://www.aspectratiocalculator.com/linkedin-aspect-ratios/) | community | medium | D2 |
|
||||||
|
| 5 | [Add Closed Captions (SRT)](https://www.linkedin.com/help/linkedin/answer/a552177/add-closed-captions-to-videos-on-linkedin) | official | high | D3 |
|
||||||
|
| 6 | [Auto captions for videos](https://www.linkedin.com/help/linkedin/answer/a1327025) | official | high | D3 |
|
||||||
|
| 7 | [OpusClip — caption best practices (80% muted, +12%)](https://www.opus.pro/blog/linkedin-video-caption-subtitle-best-practices) | community | medium | D3 |
|
||||||
|
| 8 | [Socialinsider 2026 benchmarks (video −36% YoY)](https://www.socialinsider.io/social-media-benchmarks/linkedin) | community | medium-high | D2, D5 |
|
||||||
|
| 9 | [Omni Lab — video views down 36% YoY](https://www.omnilabconsulting.com/blog/linkedin-video-views-down-36-yoy) | community | medium | D2 |
|
||||||
|
| 10 | [Entrepreneur — LinkedIn fights AI slop (Lorenzetti)](https://www.entrepreneur.com/business-news/linkedin-is-fighting-back-against-ai-slop-and-ai-comments) | official (reported) | high | D4 |
|
||||||
|
| 11 | [Manage a newsletter (5-max, cooldown, auto-invite)](https://www.linkedin.com/help/linkedin/answer/a517925) | official | high | D5 |
|
||||||
|
| 12 | [Newsletters overview (triple notification wording)](https://www.linkedin.com/help/linkedin/answer/a522525) | official | high | D5 |
|
||||||
|
| 13 | [Newsletters FAQ (dedup; resurfacing)](https://www.linkedin.com/help/linkedin/answer/a517914) | official | high | D5 |
|
||||||
|
| 14 | [The Lime One — follower floor for newsletter](https://thelime.one/blog/how-many-followers-do-you-need-for-linkedin-newsletter) | community | medium | D5 |
|
||||||
|
| 15 | [The Science Marketer — newsletter pros/cons (lock-in, click rates)](https://thesciencemarketer.com/p/linkedin-newsletter-pros-cons) | community | medium | D5 |
|
||||||
|
| 16 | [InfluenceFlow — newsletter cold-start ranges 2026](https://influenceflow.io/resources/linkedin-newsletter-strategy-complete-guide-to-building-an-engaged-subscriber-base-in-2026/) | community | low-medium | D5 |
|
||||||
|
| 17 | [dataslayer — LinkedIn algorithm Feb 2026](https://www.dataslayer.ai/blog/linkedin-algorithm-february-2026-whats-working-now) | community | low-medium | D3 |
|
||||||
Loading…
Add table
Add a link
Reference in a new issue