Commit graph

86 commits

Author SHA1 Message Date
e75cd42bed feat(linkedin-studio): de-niche rest-sweep — vary KTG-beat examples across surfaces (B-S2b) [skip-docs]
The last de-niche slice: recast the 10 sites where the vendor/sector beat
(Microsoft|Azure|Copilot|public sector) sat as the PRIVILEGED/default example,
varying each to a concrete cross-domain example instead of sterilizing
(plugin-is-domain-general — domain comes from user config, never hardcoded).

Recast (10): url-processing-templates (news worked-example Copilot->Figma),
opportunity-generation (3 headline examples + About block -> varied/ops persona),
profile (3 "good example" headlines/impact -> healthcare/e-commerce/support),
first-comment-strategy (drop "Microsoft" from research-paper example),
poll-strategy-guide (Copilot option -> generic AI assistants),
engagement-frameworks (1 of 3 direct-address audiences -> RevOps/SaaS),
setup (audience e.g. -> two varied examples), post (invocation e.g. -> SaaS pricing),
network-builder (tagline example -> ops/manufacturing),
video-scripter (2 filename slugs -> neutral topics).

Kept as false positives (would sterilize): content-angles.md (Public Sector is
1 of 6 balanced industry tables + Industry-Agnostic section), outreach.md
(Microsoft Build/Ignite/Azure UG = 3 of ~20 varied real conferences),
linkedin-growth-playbook (biographical fact in a real case study), the
Gemini/Tavily/Perplexity MCP tool-name examples, and the algorithm-signals
"Gemini provenance" SSOT citation. AI-as-topic kept (not a niche token; the
de-AI/AI-slop mechanic is the plugin's legit subject).

Gate scripts/test-runner.sh 87/0/0 (no lint touches these files yet; §17-guard
extension to content-planner is the deferred next step). 10 files, 26/26.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RBMKqPSVbvSZHtQ4heM1UY
2026-06-23 10:50:28 +02:00
b89868e3b1 feat(linkedin-studio): research-engine config layer — sources + scoring modes + MCP profile (§5 slice 2a) [skip-docs]
Declaration/config groundwork that slice 2b's trend-spotter upgrade reads.
Standalone (no agent wiring yet — that's 2b), mirroring slice 1's pattern;
[skip-docs] for the same reason slice 1 was — user-facing docs land when 2b
wires the engine live.

- references/trend-scoring-modes.md: methodology SSOT for two rubrics —
  kortform (feed post, timing 20%) + long-form (chronicle, depth 25% / timing 10%,
  per tema-research-motor-spec §4.2). Both sum to 100%. trend-spotter renders from
  this in 2b instead of inlining a matrix (S12-consistent).
- config/trends-sources.template.md: shipped generic source-list defaults →
  user override at ${LINKEDIN_STUDIO_DATA}/trends/sources.md (data-dir, survives
  reinstall; same template->data-dir pattern as user-profile).
- user-profile.template.md: new "Research Tooling" section — declared research MCPs
  (Tavily/Gemini/Perplexity/Other) + WebSearch/WebFetch floor. 2b routes MCP-first.
- setup.md Step 3f + onboarding.md Phase 2: ask "which research MCPs?" -> profile.
  Store only what the user declares; no hard-coded MCP names.
- test-runner.sh: EXPECT_REFS 26->27; generalized the M0 +1 delta-guard into a
  named-post-M0-additions guard (POSTM0_REFS) so a legit later ref doc passes while
  the anti-masking intent holds. Gate green 84/0/0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RBMKqPSVbvSZHtQ4heM1UY
2026-06-22 13:36:28 +02:00
527391ab47 feat(linkedin-studio): wire lived-specifics extraction into newsletter pipeline (fix #2 slice 3)
Wire the kilde-så-draft binding (slices 1–2) into the live /linkedin:newsletter
pipeline so a draft is grounded in the operator's real material BEFORE any prose.

- New Step 1.5 "Lived-specifics extraction" BETWEEN brief (1) and research (2):
  query the bank → 3–5 sharp elicitation questions (vagueness refused, never
  fabricated — invariant 3) → `add` to the specifics-bank → populate the
  articles.NN.livedSpecifics slot-map → render the NN-kilder.md ledger. First
  phase to write machine state (persists brief + personas + binding).
- Deterministic binding-gate (`validate-binding`, exit 1 = BLOCK) wired as a new
  step 5 of the Step 2.5 skeleton-gate (renumbered 5→9): no prose is written on
  an unresolved load-bearing claim; livedSpecifics → "bound" on pass.
- Step 2 re-scoped BY the binding: research fills the ekstern/unresolved slots +
  verifies the operator's own numbers (regel 6/7) → flips bank unverified→verified
  → re-renders NN-kilder.md, instead of re-sourcing what is already grounded.
- Pipeline 17 → 18 phases: new `lived-specifics` phase id in edition-state
  _doc.phases; resumption table + pipeline overview + CLAUDE.md + README updated.
- Lint: new Section 15 "Specifics-Bank Binding" wires the specifics-bank suite
  into CI (suite green + case-count floor 28, KTG-only/adopter-skip); the
  assertion-count anti-erosion floor moves to Section 16.

Verification: structure gate 84/0/0 (was 83), specifics-bank 28/28, contract-gate
ratify 1:1, edition-state template valid JSON (18 phases), gitleaks clean on all
5 files. Design record: docs/fix2/slice2-binding.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 22:29:01 +02:00
e26d9fb659 feat(linkedin-studio): wire contract-gate + ratify into newsletter Step 4.5 (fix #1 slice 3)
Slice 3 of fix #1 (§C2 bindende + akkumulerende): wire the deterministic
§B/§C1 contract-gate into the long-form pipeline as a pre-handoff step, so
mechanical violations die deterministically BEFORE the costly AI gates and
never reach the operator at a judgment gate ("rettelser fester seg").

Newsletter pipeline:
- New Step 4.5 (between Step 4 consistency/quality and Step 5 fact-check):
  (1) ratify the gate (rules.ts <-> §E-manifest 1:1; DRIFT escalates to
  operator, not a draft fix) and (2) gate the draft (exit 0 PASS / exit 1
  BLOCK -> fix by tightening + re-run; WARN = direction, never blocks).
  Adopter-safe: no-op when contract-gate deps / the maskinrommet contract
  are absent.
- Pipeline phase count 16 -> 17 across every LIVE surface (overview table +
  build-status + narrative + banner + schema pointer in newsletter.md;
  command-table rows in CLAUDE.md and README). Historical version-narrative
  entries left untouched.
- edition-state.template.json: new "contract-gate" phase string (Step 4.5),
  between consistency-quality and factcheck-sweep. JSON valid; phases 16->17.

CI integration (test-runner.sh, Section 14 — new):
- 14a: contract-gate suite stays green (npm test exit 0) with a 33-test
  case-count floor (anti-erosion).
- 14b: ratify invariant — rules.ts bound 1:1 to the §E-manifest.
- Both KTG-internal and adopter-safe (warn/skip, never fail, when deps or the
  contract are absent). set +e inside the test command-substitution keeps a
  red suite from aborting the runner; ratify exit 1 captured via if-subshell.
- SC6 anti-erosion section renumbered 14 -> 15; header comment updated.

Verification:
- Structure lint 81 -> 83/0/0 (contract-gate suite + ratify both green).
- Documented Step 4.5 command smoke-tested on a real draft
  (serier/silvija-seres-motsvar/utkast/04-utkast.md): 0 BLOCK / 2 WARN ->
  "Gate passert", exit 0.
- edition-state.template.json valid JSON; phases array = 17.

foldIns capture into the pipeline phases deferred to a later slice (scope A).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 20:34:49 +02:00
e17134ee9b refactor(linkedin-studio): S31c descriptive-%-scrub — platform-norm percentages asserted as fact -> SSOT
24 edits / 12 files (+26/-26). Unsourced platform/algorithm/audience percentages reconciled to
SSOT vocabulary (figure/proportion/multiplier unverified). Catalog + new sibling clusters
(64% follow-up x5, wrong-window 70% x4, Stage-2 6-10% x2) + borderlines (70% retention, 70%
mobile) + the ~3% save-worthy straggler (surfaced, not silent). The SSOT-sourced ~70% reach
figure is KEPT; only the wrong window corrected (60min/1h -> first 15-30 min). Sourced/computed
benchmarks kept (Buffer 178%/247%, Socialinsider 11%). KEPT C1: ~45% AI-comment figure (already
hedged correlational/medium-confidence). Gate 81/0/0 exit 0, counts 29/19/26 + v0.5.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qgzo6rxthw7KuxHjn5vyE
2026-06-20 09:54:44 +02:00
c850352a92 refactor(linkedin-studio): S31b multiplier-scrub (commands + agents) — generic "Nx" claims -> SSOT
Hardening, multiplier class (S31, batch b). SSOT-reconciliation of unsourced
generic "Nx" reach/growth multipliers asserted as platform fact across commands
and agents. Same discipline as S31a: read-and-show (Read tool, not Bash-tail) ->
edit -> re-grep final; 8-step gate.

5 edits / 5 files (+5/-5):
- report:249 (PDF/Carousels 3.4x reach multiplier) -> top organic format, no
  reliable multiplier.
- outreach:652 (collab content 2-3x reach, ref collaborations-guide) -> above-
  baseline reach, aligned with the S31a collab scrub.
- engagement-coach:120 (2-3 touchpoints = 3.6x more likely) -> far more likely
  (figure unverified).
- strategy-advisor:241 (saves drive 3x faster audience growth) -> faster growth
  (figure unverified).
- content-repurposer:591 (Expected reach multiplier: [2-5x original]) -> Expected
  reach: above the original (no reliable multiplier).

KEPT (operator-ruled measured-data class — report/hypothesize from the USER's own
analytics, not a platform norm; the number is an illustrative-computed example or
a pure placeholder, and removing it adds no honesty): report:303 ("[format] ...
2x more shares" example), report:335 ("your one carousel got 3x" rendered
example), ab-test:418 ("Your data shows ... 2x ... test whether this holds"),
outreach:1234 ("Average reach multiplier (collab vs solo): [X]x" output field).

KEPT INTACT: engagement-coach:148 SSOT comment ~= 2x a like. OUT (untouched):
strategy-advisor:239 4-5x/week frequency.

VERIFY: re-grep generic-claim targets in the 5 files -> 0; measured-data class +
KEEP/OUT intact; gate scripts/test-runner.sh = 81/0/0 exit 0, counts 29/19/26
+ v0.5.0 unchanged.

Push held: origin = open/ (public namespace), STATE.md must not go there.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qgzo6rxthw7KuxHjn5vyE
2026-06-20 09:34:20 +02:00
9e5695286d refactor(linkedin-studio): S29e terminology-scrub — rename thought-leadership-angles.md -> content-angles.md + all pointers
Final sub-pass of the S29 plugin-wide terminology scrub. The canonical reference file is
renamed and every functional pointer updated atomically in one commit. The file's in-file
title/headers were already FORM A-scrubbed in S29c (H1 reads "Content Angles"), so S29e is a
pure rename + pointer update — no FORM A remained in the file.

Rename: references/thought-leadership-angles.md -> references/content-angles.md (git mv).

Pointers updated (17 files, 29 occurrences) — token "thought-leadership-angles" -> "content-angles":
- references/ (2): ai-content-framework, glossary
- agents/ (7): content-repurposer, strategy-advisor, network-builder, content-planner,
  trend-spotter, video-scripter, differentiation-checker
- commands/ (6): pipeline, video, post, competitive, react, batch
- skills/ (1): linkedin-content-creation/SKILL
- docs/ (1, forward-looking): integration-test-guide

Left URØRT per the standing S29 decision (history = honest record of a past state, not a
runtime load): CHANGELOG.md, docs/hardening/log.md, docs/hardening/plan.md. STATE.md untouched
here (rewritten at session end).

Verify: no thought-leadership-angles* file remains; references/content-angles.md present; zero
residual "thought-leadership-angles" in commands/agents/references/skills/integration-test-guide;
structure gate scripts/test-runner.sh 81/0/0 exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qgzo6rxthw7KuxHjn5vyE
2026-06-20 06:39:49 +02:00
9d83dbc81c fix(linkedin-studio): S29a terminology-scrub — "thought leadership" → neutral (commands/)
First sub-pass of the S29 plugin-wide terminology scrub: the banned brand phrase
"thought leadership" (FORM A) removed from the user-facing command surface. 15 in-file
phrase occurrences across 9 commands re-sourced to neutral wording (post, video, strategy,
batch, setup, react, outreach, onboarding, monetize) — e.g. "thought leadership content
creator" → "content creator", "Select Thought Leadership Angle" → "Select Content Angle",
"Build thought leadership & authority" → "Build authority & influence", onboarding guide
→ "LinkedIn Studio onboarding guide".

Scope (operator-set): FORM A only. FORM B ("thought leader" as role, incl. the buzzword
denylist at post.md:134) left untouched. The 11 filename pointers to
references/thought-leadership-angles.md deferred to S29e (atomic rename + pointer update);
competitive.md/pipeline.md carry only pointers and are untouched here. Pure history
(CHANGELOG, docs/**) left intact per "history → git".

Verify: assertive non-filename FORM-A in commands/ = NONE; FORM B count unchanged (15);
diff +15/-15 across 9 files (1:1); gate 81/0/0; counts 29/19/26/6 + v0.5.0 unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qgzo6rxthw7KuxHjn5vyE
2026-06-19 22:37:05 +02:00
ed835e9024 fix(linkedin-studio): S28 ref-consistency B — format magnitudes → SSOT
Reconcile commands/video.md + references/linkedin-formats.md to the algorithm-signals SSOT
in one pass (20 edits/2 files). The unsourced "30% completion rate or zero distribution"
gate (7x) → "completion/dwell is a ranking input; shorter completes better; no published
hard completion gate". The 6 S27-folded link-penalty lines → "correlate with lower reach"
(incl. heading "The External Link Penalty" → "External Links and Lower Reach"). Six other
unsourced format magnitudes softened to direction-without-number (video 5.60% ER, Live
12-24x, polls 1.64x, 3x shares, Creator-Mode 35%). One internal contradiction fixed (3-sec
= 70% retention contradicted the already-reconciled three-second-folklore note).

Bucket D (this file's non-format coefficients: 47-50% reach-decline, AI-comment -30%/-55%,
hashtag -68%) left intact and deferred to S30, which is re-scoped to cover them + the other
files. Gate 81/0/0; counts 29/19 unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qgzo6rxthw7KuxHjn5vyE
2026-06-19 22:17:54 +02:00
2b9ed1cf0b fix(linkedin-studio): S27 ref-consistency A — "penalty/penalizes" framing → SSOT
Reconcile external-link and topic-relevance reach-loss framing to the algorithm-signals
SSOT across 6 files (10 edits): body links "correlate with lower reach" (LinkedIn denies
an intentional penalty), topic relevance is a positive ranking input with no quantified
off-topic figure. 3 plan-cited lines + 7 same-class siblings surfaced during verify.

Bucket B (link-penalty lines in linkedin-formats.md) folded into S28; Bucket D (other
unsourced reach-coefficients: -68%/-25%/-15-25%/-30-50%/55%) queued as new S30. Engagement-
pod + AI-slop "penalized" framing left intact (officially confirmed). Gate 81/0/0; counts
29/19 unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qgzo6rxthw7KuxHjn5vyE
2026-06-19 22:06:39 +02:00
47782eb91d fix(linkedin-studio): S26 harden linkedin — re-source router copy to "LinkedIn Studio", queue S29 terminology-scrub
Router hardening (routing/agent/grant axes all PASS). Operator terminology rule: the "thought leadership" phrase must not appear in the plugin. Re-sourced the 2 occurrences in commands/linkedin.md (:4 frontmatter description, :16 body) to "LinkedIn Studio". Routing 28/28 + agent-pointers 10/10 resolve; grant-hygiene clean (no Glob orphan — third front-door). Surfaced plugin-wide (~85 lines / ~45 files) and promoted to S29 terminology-scrub in docs/hardening/plan.md.
2026-06-19 21:52:58 +02:00
8d1ef43801 fix(linkedin-studio): S25 harden measure — explicit Glob, drop Read from routing front-door
measure is the Measure-journey routing front-door (v4.1): guided "how am I
doing?" -> routes to the analytics command that owns the work; delegates only,
never crunches numbers. Routing axis is clean — all 5 emitted targets
(import/report/analyze/audit/ab-test) resolve to commands/Y.md. Delegates-only
holds: the 3 crunch/analysis mentions (:7/:20/:40) are all no-do promises, no
leak.

Grant-hygiene was the one finding, and it differs from S24 create. There Glob
was a pure orphan (no filesystem check at all). Here measure HAS an optional
Step 0 ("if .../analytics/ holds imported data, note last import") that gives
Glob a real home — but the grant<->use link was implicit (body named no tool),
and Read hung on the same vague Step 0. Operator chose: make Glob explicit,
drop Read as redundant (the import-date need is met by a Glob listing).

2 edits, commands/measure.md only:
- allowed-tools :11: drop Read; net = {Glob, AskUserQuestion}.
- Step 0 :23: "You may Glob .../analytics/; if it holds imported data, note last
  import" — gives the retained Glob grant a named body home.

Verify: re-grep final — Read as tool name NONE, allowed-tools = Glob +
AskUserQuestion (:11-12), Glob now in body :23; routing unchanged (5/5);
git diff --stat = 1 file +1/-2; test-runner 81/0/0 exit 0; counts 29/19
unchanged (.md-only). FIXED, 0 deferrals. Scope note: Glob-orphan question now
resolved for measure (it had a home via Step 0; create did not); linkedin (S26)
still to check independently.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qgzo6rxthw7KuxHjn5vyE
2026-06-19 21:38:02 +02:00
d783f261dd fix(linkedin-studio): S24 harden create — drop orphan Glob grant from routing front-door
create is the Create-journey routing front-door (v4.1): guided "what to make?"
-> routes to the command that owns the format; delegates only, never drafts.
Routing axis is clean — all 8 emitted targets (post/quick/react/carousel/video/
multiplatform/batch/newsletter) resolve to commands/Y.md. Delegates-only holds:
every "draft" mention is the no-draft promise (:7/:22, long-form lock :66) or a
target-description (:38), no leak. The one finding was grant-hygiene: Glob was
declared (:14) but never used — the only file access is a fixed-path Read of the
state file (:26), which needs no pattern-matching. Same orphan-grant pattern as
S17-S22.

1 edit, commands/create.md only:
- allowed-tools :14: drop orphan Glob; net = {Read, AskUserQuestion}.

Verify: re-grep final — Glob NONE, allowed-tools = Read + AskUserQuestion;
git diff --stat = 1 file -1; test-runner 81/0/0 exit 0; counts 29/19 unchanged
(.md-only). FIXED, 0 deferrals. Scope note: Glob-orphan may recur in sibling
front-doors (measure S25, linkedin S26) — not expanded; each catches its own.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qgzo6rxthw7KuxHjn5vyE
2026-06-19 21:25:00 +02:00
10e10897ca fix(linkedin-studio): S23 harden profile — re-source profile-as-validated-object overclaim to SSOT, soften SEO claims
Profile is a self-contained, prose-heavy Grow-tier audit command (no agent, no
routing, no state). Grant-hygiene was already clean (Read + AskUserQuestion, both
used, no orphans/missing). The one real axis was b': the file repeatedly framed
the relevance model as reading and VALIDATING your profile and gating distribution
on it — contradicting the SSOT (algorithm-signals-reference.md), which confirms
only topic/interest relevance as a ranking input (content matched to a viewer's
interests, incl. beyond your network) with no off-topic magnitude figure and no
weighted profile criteria. The file was internally inconsistent — its own soft,
SSOT-true framing already sat at :17/:25/:27.

12 edits, commands/profile.md only:
- desc :5: drop "validates your profile BEFORE distributing content".
- :31 + table: drop "the model evaluates five criteria (see SSOT)" false
  attribution + HIGH/MEDIUM "Impact if Missing" postulated weights -> "LinkedIn
  does not publish a profile-scoring breakdown ... practitioner heuristics",
  column -> "Priority (heuristic)".
- :99/:161/:173/:230: reframe "first signal telling ... qualified" / "relevance
  validation" / "checks if you're connected" / "if LinkedIn's AI read my profile,
  would it believe" away from the profile-read-by-model mechanic.
- :25/:27/:51/:67: conflation fix — "your demonstrated expertise" / "tell the
  relevance model what you're expert in" -> "a viewer's interests" / "topic-
  relevance distribution" (SSOT wording) + no-percentage honesty.
- :24 (B2): "Goes to 10% of audience" -> "a slice of your network".
- :52/:61 (P3): "highest-weight search field" -> "highest-leverage" (self-
  justified rest kept).

Verify: re-grep final file — all overclaim/conflation patterns NONE, new SSOT
text in place; grant-hygiene unchanged; test-runner 81/0/0 exit 0; counts 29/19
unchanged (.md-only). FIXED, 0 deferrals.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qgzo6rxthw7KuxHjn5vyE
2026-06-19 21:12:54 +02:00
ff52ec8577 fix(linkedin-studio): S22 harden outreach — add missing Bash grant, drop orphan WebFetch+Write, re-source fabricated pod/DM stats to SSOT
Outreach is the first command in the hardening queue that both delegates to an
agent (network-builder) and mutates real state (recordOutreachContact), so the
weight fell on axis-d. Five fixes across two files (operator-approved scope
extension to the SSOT-diverging guide source):

- allowed-tools: ADD Bash (Step 8c runs `node state-updater.mjs --record-outreach`
  and needs it — firsthour precedent grants Bash for the same pattern; the command
  promised a mutation it couldn't execute), DROP orphan WebFetch + Write grants
  (zero body use; :410/:1040 "Write a post" is prose). Now
  Read·Glob·WebSearch·AskUserQuestion·Task·Bash.
- Step 7 pod-warning (axis-b'): replaced fabricated magnitudes ("30-55% reach
  reduction", "shadow suppression", "credibility score", "penalizes",
  "Post-March 2025", "low dwell") with the SSOT statement
  (algorithm-signals-reference.md:98 — demote out of "Most Relevant" / scope to
  own network / restrict repeat offenders) + cite.
- :652 DM-amplification "2-3x first-hour" (axis-b'): re-attributed 2-3x to collab
  reach (where SSOT places it), kept the first-hour effect qualitative.
- collaborations-guide.md:349-370 (axis-b', scope extension): same SSOT rewrite of
  the "March 2025 Crackdown" section so the Step 7 fix can't be re-rendered from a
  stale reference (dropped "shadow banning ALL future content / 90+ days / 6+
  months / permanent penalty" + speculative detection methods).

Verify: re-grep both files clean (orphans/fabricated stats NONE, Bash :21, SSOT
text in place); recordOutreachContact throwaway-fixture 10/10 PASS (additive,
$-safe, newest-first, untouched undeclared fields); test-runner 81/0/0 exit 0;
counts 29/19 unchanged (.md-only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qgzo6rxthw7KuxHjn5vyE
2026-06-19 20:33:30 +02:00
aaacb20a3f fix(linkedin-studio): S21 harden monetize — drop orphan WebFetch+Write grants, re-source fabricated DM-conversion decay curve to SSOT
- allowed-tools: drop WebFetch (:13) + Write (:15) — orphan grants, zero body
  use; command fetches no URL and persists no file (now Read/Glob/Grep/AskUserQuestion)
- :343-346: replace postulated same-day/next-day/3+day decay curve (incl. invented
  <10% floor) with SSOT-supported qualitative claim + cite references/opportunity-generation.md
- gate 81/0/0 exit 0; counts 29/19 unchanged (.md-only)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qgzo6rxthw7KuxHjn5vyE
2026-06-19 15:23:28 +02:00
36e58cab9a fix(linkedin-studio): S20 harden competitive — drop orphan WebFetch grant from allowed-tools 2026-06-19 15:05:48 +02:00
4dbebdb7b8 fix(linkedin-studio): S19 harden strategy — drop orphan WebFetch grant from allowed-tools 2026-06-19 14:02:44 +02:00
2eb463f6cd fix(linkedin-studio): S18 harden ab-test — soften "(Tue-Thu, 8-9 AM optimal)" precision over-claim to "(Tue-Thu mornings)" 2026-06-19 13:55:41 +02:00
52e39d18a9 fix(linkedin-studio): S17 harden audit — drop orphan WebFetch grant + re-source carousel save-rate claim to SSOT 2026-06-19 13:24:43 +02:00
7362d8e4b1 fix(linkedin-studio): S16 harden analyze — drop unsourced 5+ hashtags penalty
S16 analyze through the v2 interactive quality gate (analytics/diagnostic class —
prose-diagnosis + conditional agent-delegation, NOT CLI-backed: allowed-tools
Read/AskUserQuestion/Task, no Bash). 1 finding, 1 surgical edit in
commands/analyze.md, grep-confirmed + grounded persona sim (scenario 1, reach -50%):

- FUNN (axis-b'): Step 4 penalty checklist asked 'Did you use 5+ hashtags in posts?'
  — a concrete-number penalty absent from BOTH the SSOT
  (algorithm-signals-reference.md: no hashtag claim, deliberately omitted) AND the
  cited troubleshooting-guide.md (its own checklist is 6 items, no hashtag).
  Command-only unsourced folklore. Dropped analyze.md:149; kept :150 'tag unrelated
  people for reach' (defensible — aligns with SSOT bait/pod-suppression direction,
  no numeric claim).

Verified clean: interpret-mode wiring (analytics-interpreter resolves, real default
mode agent:6,39,141), shared data-dir seam (analyze:41 <-> agent:49),
/linkedin:profile cross-ref, graceful-degradation IF, saves/dwell honesty (no Bash,
no over-promise). External-link hard-penalty framing in troubleshooting-guide.md:127
vs SSOT logged as reference-pass follow-up (out of S16 scope — not touched).

Gate: test-runner 81/0/0 exit 0; counts 29/19 unchanged (.md-only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qgzo6rxthw7KuxHjn5vyE
2026-06-19 06:34:20 +02:00
060ce8a371 fix(linkedin-studio): S15 harden report — drop baselines/metadata phantom, fix engagement_rate flag + JSON schema fields
S15 report through the v2 interactive quality gate (analytics class). 3 findings,
6 surgical edits in commands/report.md, all grep-confirmed + grounded against a
throwaway-root CLI run:

- FUNN 1 (S14 twin): Step 5c baseline/std-dev mechanism + Reference Files referenced
  baselines.json/metadata.json that no code writes (storage.ts writes only
  posts/weekly-reports/monthly-reports). Rewrote Step 5c to read the report's own
  alerts[] (detectAlerts + detectWeeklyAlerts); fixed posts filename to
  YYYY-MM-DD-<shortid>.json; added real monthly-reports file.
- FUNN 2: trends --metric engagement_rate (snake_case) → exit 1 'Invalid metric';
  CLI accepts only engagementRate (cli.ts:47,202-209). Fixed report.md:172.
- FUNN 3: Step 4/Step 6 claimed JSON fields dateRange/postCount/aggregateMetrics that
  don't exist; real schema is summary/topPerformers/underperformers/trends/alerts.
  Rewrote to real fields; fixed Deep-Dive post-search path.

Gate: test-runner 81/0/0 exit 0; counts 29/19 unchanged (.md-only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qgzo6rxthw7KuxHjn5vyE
2026-06-19 06:18:44 +02:00
a413279c58 fix(linkedin-studio): S14 harden import — drop baseline/metadata over-promise, prose now matches CLI
import.md repeatedly promised baseline comparison, rolling 4-week averages, and
baselines.json/metadata.json the importer "creates" — but no code anywhere writes
either file; detectAlerts flags intra-batch std-dev from the batch's own mean, not
a stored baseline. Step 5b's `cat baselines.json` was always empty, so the command
fell forever to "first import — baselines will be established" on every import.
Rewrote 5 sections (Step 5, 5b, 7, State Tracking, Reference Files) to the truth:
real CLI fields + YYYY-MM-DD-<shortid> filename, honest intra-batch surfacing,
cross-week analysis deferred to /linkedin:report. Verified with a grounded run
against a throwaway fixture.
Axes a/b/c/d all PASS post-fix; lint 81/0/0, counts 29/19 unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qgzo6rxthw7KuxHjn5vyE
2026-06-18 20:56:47 +02:00
270e99d002 fix(linkedin-studio): S13 harden calendar — concrete Reschedule snippet + correct camelCase exports
Reschedule action was under-determined vs its sibling actions: prose-only with
no bash snippet, referencing snake_case queue_update_status/queue_add that don't
match the exports (queueUpdateStatus/queueAdd). Replaced with a concrete queueAdd
one-liner using the same id (re-add moves the post in place, no duplicate).
Axes a/b/c/d all PASS post-fix; lint 81/0/0, counts 29/19 unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qgzo6rxthw7KuxHjn5vyE
2026-06-18 20:19:31 +02:00
bc325f0243 fix(linkedin-studio): S12 harden firsthour — re-source ~70% stat to first 15-30 min + drop CEA mislabel
Axis-b: command attributed ~70% of reach to the first 60 min (desc + body);
SSOT algorithm-signals-reference.md:81 pins ~70% to the first 15-30 min
sub-window (golden window 60-90 min). The command even cited that SSOT, so it
contradicted its own source. Re-attributed to '15-30 minutes decide ~70%',
framing the hour as the golden window worked.

Axis-a: footer labelled engagement-frameworks.md as carrying CEA, but CEA lives
in the SSOT + engagement-coach. Dropped 'CEA' from the footer label.

Verified clean: both agents namespaced + frontmatter name/model/color match
CLAUDE.md; recordFirstHourPlan exported/additive/$-safe; delegation + hand-off
sound. test-runner.sh 81/0/0; counts 29/19/26 unchanged. FIXED, 0 deferrals.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qgzo6rxthw7KuxHjn5vyE
2026-06-18 18:48:19 +02:00
4fad655a66 fix(linkedin-studio): S11 harden pivot — moderate to-phase reset skipped fact-check (off-by-one)
S11 hardening of /linkedin:pivot. Class: pure state-mutation/orchestration
command, NOT post-emitting, invokes no agents — re-opens a long-form edition so
cleared gates re-run before lock. Feed-post predicates N/A.

BUG (P4, cross-file off-by-one): newsletter's resumption table treats
currentPhase as the LAST COMPLETED phase and resumes at the NEXT step
(draft->Step 4, consistency-quality->Step 5, factcheck-sweep->Step 5.5). The
moderate pivot default set to-phase: factcheck-sweep "so the new claims get
verified" — which resumes at Step 5.5 and SKIPS the Step 5 fact-check the
moderate pivot exists to run, violating the command's own invariant that a pivot
always invalidates the fact-check.

FIX (4 surgical edits, commands/pivot.md only):
1. moderate default factcheck-sweep -> consistency-quality (resumes Step 5)
2. structural label "(Step 3b)" -> "Step 4 (consistency)"
3. off-by-one guard callout explaining to-phase = currentPhase (last-completed)
4. anatomy enum drops factcheck-sweep -> draft | consistency-quality
gatesToRerun list + worked example unchanged (both correct).

4-axis verdict: a/b/c/d all PASS post-fix, 0 deferrals -> FIXED.
Gate: test-runner 81/0/0 · counts 29/19/26 unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qgzo6rxthw7KuxHjn5vyE
2026-06-18 18:34:37 +02:00
8babcf77ab fix(linkedin-studio): S8 harden pipeline — Step-3 buzzword gate enumerated inline
Step-3 optimize gate (:79) carried the buzzword line but as a bare
'- [ ] No corporate buzzwords' — not self-contained, leaning on the
quality-scorecard.md reference rather than the inline gate the AI executes.
Enumerated the banned list inline to match every other post-emitting gate
(post :134 / react :131 / carousel :123 / video :129 / scorecard :132),
removing pipeline as the odd-one-out.

Recorded deferral: Step-2 component structure (:58-62) sums 960-1640, floor
below the Step-3 band floor 1,200 — identical to batch's untouched 3a; the
gate (:77) already names the band and catches a minimum-path undershoot, so
no edit (editing Step 2 would exceed S7 precedent).

Gate: test-runner 81/0/0 · counts 29/19 unchanged · only .md touched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qgzo6rxthw7KuxHjn5vyE
2026-06-18 17:15:46 +02:00
8a3e109b35 fix(linkedin-studio): S7 harden batch — format-aware length band on per-post quality check
S7 close-out (1 edit / 1 command-file · lint 81/0 · counts 29/19/26/6):
- batch.md:95 Step-3b Quick Quality Check: replace vague "Character count in range" with a format-aware band (standard 1,200-1,800 · quick 150-500 · carousel/video caption per that format) — the old line named no band, and the 3a structure (:88-92) sums to 960-1640, under the 1,200 standard floor, so a ~1,000-char standard post passed "in range" unflagged

Axis-c (length band) closed; axes a/b/d PASS. Class: hybrid guided/stateful + post-emitting per post (each of 3-5 drafts is a feed post). Buzzword (:98) + no-body-link (:97) already present — the gap S2-S6 found is already closed in batch. Wiring verified: trend-spotter + content-planner resolve; all 5 node helpers (queueUpcoming/queueAdd 8-arg/queueFormatSummary/generateIcalFromQueue/writeIcalFile) export with matching signatures. 3 deferrals (3a single-structure vs mixed-format promise · no per-post differentiation gate by design · weekly_goal-unset micro-fallback). Local-only per push policy.
2026-06-18 15:29:31 +02:00
1524b8438f fix(linkedin-studio): S6 harden video — caption buzzword gate + voice-samples graceful degradation
S6 close-out (2 edits / 1 file · lint 81/0 · counts 29/19/26/6):
- video.md:129 Step-5 De-AI gate: add content rule #4 buzzword strip to the post-caption gate (only buzzword line was :106 under "Natural Speech Test" = script not caption; caption is post-emitting feed text)
- video.md:42/:45 Step-0: soften "(REQUIRED before scripting)" → "(read before scripting)" + add fresh-adopter graceful-degradation fallback (empty/absent voice-samples → SKILL.md/defaults, no dead-end)

Axis-c (buzzword on caption) + axis-d (voice-samples dead-end) closed; axes a/b PASS. Class: hybrid guided/stateful + post-emitting on the caption (hook 110-140 deliberately not imposed on the accompanying caption — operator-agreed). video-scripter + differentiation-checker wiring verified; 2.5-wps budgets exact. 2 deferrals (caption-hook class boundary + :69 "zero distribution" inter-reference honesty). Local-only per push policy.
2026-06-18 15:20:05 +02:00
d3efe326b4 fix(linkedin-studio): S5 harden carousel — Step-5 caption buzzword gate
S5 close-out (1 edit / 1 file · lint 81/0 · counts 29/19/26/6):
- carousel.md:123 Step 5 De-AI gate: add content rule #4 buzzword line to the caption gate (was missing; grep NONE pre-edit; caption is post-emitting feed text)

Axis-c (buzzword on the caption) closed; axes a/b/d PASS. Class: hybrid guided/stateful + post-emitting on the caption. mcp-image graceful degradation + differentiation-checker wiring verified. 2 deferrals (no-body-link on caption + cosmetic slide line-count). Local-only per push policy.
2026-06-18 14:28:07 +02:00
a3d26b223c fix(linkedin-studio): S4 harden multiplatform — Step-0 empty-source fallback + cross-platform buzzword line
S4 close-out (2 edits / 1 command file · lint 81/0 · counts 29/19/26/6):
- multiplatform.md:28 Step 0: empty-source fallback (drafts empty + no recent posts + nothing pasted -> stop, ask user to paste; never fabricate source)
- multiplatform.md:45 Adaptation Templates: cross-platform voice-hygiene line (banned buzzwords = content rule #4; adaptation rewrites can introduce them)

Class call: NOT post-emitting (output is Twitter/slides/YouTube) -> no LinkedIn predicate imposed; tested as guided/cross-format + routing sub-predicate. Routing (/linkedin:newsletter) + both reference files resolve; no subagents. Axis-c (buzzword) + axis-d (degradation) closed; axes a/b PASS. Local-only per push policy.
2026-06-18 14:10:17 +02:00
b8c2155003 fix(linkedin-studio): S3 harden react — Step-6 buzzword gate + WebFetch-failure fallback (both paths)
S3 close-out (3 edits / 1 file · lint 81/0 · counts 29/19/26/6):
- react.md:131 Step 6: add no-buzzword checklist line (was missing; banned list = content rule #4; covers Step 6b via "Same as Step 6")
- react.md:60 Step 2: WebFetch-failure fallback (paywall/JS/YouTube-no-transcript -> ask user to paste or pick another; never fabricate)
- react.md:194 Step 2b: same fallback pointer for the multi-URL comparison path

Axis-c (buzzword) + axis-d (graceful degradation) closed on both paths; axes a/b PASS. Shared scorecard already clean post-S2. Local-only per push policy.
2026-06-18 13:44:00 +02:00
69840e7a94 fix(linkedin-studio): S2 harden post — Step-5 buzzword gate + scorecard hook-band consistency
P4 close-out (4 edits / 2 files · lint 81/0 · counts 29/19/26/6):
- post.md Step 5: add no-buzzword checklist line (was missing; banned list = content rule #4)
- quality-scorecard.md l.32/114: one-sided "140" hook bound -> "110-140" (matches l.128, resolves self-contradiction)
- quality-scorecard.md: add no-buzzword line to Pre-Publish Basic Checklist

Shared scorecard fix benefits 8 consumers; only post hardened this session (react/batch/video/pipeline get own EVALUATE in S3/S5/S6/S8). Local-only per push policy.
2026-06-18 13:32:56 +02:00
6bd263144f docs(linkedin-studio): M0-15 — repoint remaining prose families + route ab-tests/plans 2026-06-18 13:04:18 +02:00
c0abb82d9c docs(linkedin-studio): M0-14 — D3 path-convention + voice-readers prototype 2026-06-18 12:38:17 +02:00
33aa3c4ca9 docs(linkedin-studio): M0-11 — drop in-plugin ANALYTICS_ROOT pins + repoint content-history (B1/B2) 2026-06-18 12:05:15 +02:00
94760cde90 fix(linkedin-studio): S1 hardening — method calibration (quick) + Start journey
Hardening phase S1. Tightened the hook character bound from one-sided
(under 140) to the full canonical 110-140 band across the Start-journey
creation surfaces, matching hooks/prompts/content-quality-gate.md:
- quick.md: hook band on Step 2 + Step 5 checklist; added a buzzword check
  and a 150-500 length-band check; checklist tally 6 -> 7.
- onboarding.md: hook band in Phase 3.2 + 3.3 (length + no-links already present).
- first-post.md: hook band in Step 4 + Step 5 (length + no-links already present).
- setup.md: zero-edit pass (all four axes already satisfied).

Adds docs/hardening/log.md (per-command audit trail, 5-step method) and
docs/hardening/review.md (cold /trekreview: ALLOW, 0 BLOCKER/0 MAJOR/1 MINOR).
Lint Failed:0, counts 29/19/25/6 unchanged. No structural/version churn.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 06:46:11 +02:00
69fec21f43 feat(linkedin-studio): S16 — optional manual saves in analytics + close deferred onboarding Write MAJOR
Lifts the original v4.0.0 Non-Goal: an optional, manually-entered `saves`
metric through the analytics layer, built location-agnostic (option c) so
UI-brief §9b/M0 relocates the data dir in one place later.

- types: PostMetrics.saves? + Weekly/Monthly summary.totalSaves? (optional);
  new RankableMetric type for the always-numeric index-access whitelist
- parser: dedicated parseOptionalCount() — blank/non-numeric/negative -> undefined
  ("unknown != 0"), genuine 0 kept; saves NOT folded into engagementRate
- reports: totalSaves set only when >=1 post carries saves (backward-compat)
- cli: saves surfaced in import summary + weekly/monthly totals + per-post
- S16-pre: onboarding.md allowed-tools gains Write (closes S15-deferred MAJOR)
- docs (three-doc rule): plugin README boundary + analytics README + root README
  + plugin CLAUDE.md + CHANGELOG; dwell stays explicitly unmeasurable

Independent /trekreview: brief-conformance 0 findings; code-correctness 2 MAJOR
(own lockstep misses) FIXED in-session (parseOptionalCount + edge tests). Gate:
tsc clean, analytics 116/116, lint 74/0/0, hooks 98/98. Within-v4.1.0 refinement
(no surface/count/version change).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 22:23:12 +02:00
e36efe9944 fix(linkedin-studio): S15 — UX finish §6c (B1 onboarding inline-draft + B3 carousel full-deck clipboard)
B2 (router tiering) was already delivered in S14, so S15 = B1 + B3 only.
No surface/count/version change -> within-v4.1.0 refinement (S11-S13 precedent).

- B1 (commands/onboarding.md): replace the "Run /linkedin:first-post" dead-end
  hand-off in Phase 3 with the first-post drafting steps embedded inline (3.1 topic
  -> 3.2 3-line draft -> 3.3 QC -> 3.4 present+clipboard -> 3.5 state-update that sets
  first_post_date). Wizard now yields a draft in-flow; 0 dead-end strings. Stays within
  the existing allowed-tools (Read/Bash/AskUserQuestion); UI-brief §12b scope-guard
  honored (no provider seams / progressive-disclosure added).
- B3 (commands/carousel.md): Step 6 now assembles the ENTIRE deck (every slide's copy
  + the caption) into the clipboard payload, not just the caption; full-deck assembly
  precedes the clipboard-helper.mjs call.

Independent /trekreview (2 Opus reviewers): brief-conformance 0 findings; code-correctness
1 MAJOR that is PRE-EXISTING and out of S15 scope (onboarding Phase 2 saves need Write in
allowed-tools; lines 142/157, untouched by the S15 diff) -> DEFERRED to next session per
"ekte design-funn -> neste sesjon". Verdict ALLOW for the delivered scope (not a WARN-override).

Gate: test-runner.sh 74/0/0; node --test 98/98; commands=29; v4.1.0 unchanged.
See docs/remediation/review.md for the full record (ALLOW + 1 deferred MAJOR).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 21:44:34 +02:00
52df229dfc feat(linkedin-studio): S14 — journey layer (create/measure front-doors + 5-journey router), v4.1.0
14a's cold command-rationalization found ZERO redundancy across the 27 commands
(no defensible merge/cut), so the operator reframed S14 from "merge/cut" to
"add a journey layer over the kept atomics".

- Add /linkedin:create + /linkedin:measure — delegate-only guided front-doors
  (Read/Glob/AskUserQuestion only; route to the command that owns the work)
- Re-tier commands/linkedin.md into 5 journeys (Start/Create/Engage/Measure/Grow);
  onboarding/strategy elevated as Start/Grow front-doors; Engage = calendar+firsthour tier
- 14a honesty nits: router now lists firsthour; calendar cross-links to firsthour;
  competitive confirmed UNGATED (the claimed 1K-gating inconsistency was unfounded)
- Lockstep: EXPECT_COMMANDS 27->29, v4.0.0->4.1.0 across plugin.json / README badges /
  plugin+root CLAUDE.md / README / CHANGELOG; new README commands-badge lint guard
- 14a deliverable corrected: multiplatform entry added (26/27 -> 27/27), header counts,
  competitive nit withdrawn
- Independent /trekreview (2 Opus reviewers) raised 3 MAJORs (stale commands badge,
  router competitive self-contradiction, STATE.md count) — ALL remediated in-session;
  verdict ALLOW

Gate: test-runner.sh 74/0/0; node --test 98/98; commands=29; v4.1.0 consistent.
Additive/minor — no command removed/renamed/behavior-changed; reload registers create+measure.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 21:27:06 +02:00
0e4466b893 fix(linkedin-studio): S9 — full algorithm-magnitude sweep + lint rebuilt to the criterion
Closes the S8 re-review (BLOCK 3/4/1). The S8 fix patched only the 2 strings S7 named; the re-review found 6 more same-class survivors. Per the systemic read, this is a comprehensive sweep, not a per-line patch.

Reconciled every retired engagement-coefficient + model-fact survivor against the canonical references/algorithm-signals-reference.md (order, not coefficients; comment ≈ 2x a like; no model name/params):
- glossary.md: coefficient table + Save-Signal '10x weight' → canonical ordering (citation now true)
- engagement-frameworks.md, analytics-interpreter.md, content-optimizer.md, pipeline.md, engagement-coach.md: the 10x/8x/7-9x/2.5x/0.2x system (incl. 4 survivors the re-review did not cite) → ordering
- playbook: '15x more algorithmic boost' + video '5x more conversations' → directional, sourced
- profile.md + linkedin-voice/SKILL.md: '150B parameter foundation model' → '2026 relevance-ranking model'
- quality-scorecard.md: '360Brew Validation' → topic-relevance framing
- setup.md: 'thought leadership plugin' → 'LinkedIn Studio plugin'

Lint (MAJOR 4): rebuilt scripts/test-runner.sh STALE_STATS to forbid EVERY retired-class phrasing (not the 2 S7 strings) + widened scope to assets/checklists/. Targets retired phrasings (7-9x, (10x), '10x weight', '5x more conversations'), NOT bare 10x/15x/5x (legit 5x5x5 / cadence / pixel-dims / '10x your reach' hyperbole). Proven non-vacuous: catches all 10 retired strings, ignores all 10 legit uses.

Tests (MAJOR 7): added no-anchor fall-through tests for recordFirstHourPlan + recordOutreachContact (date scalar not written/reported, section still appended). MINOR 8: reflowed newsletter.md content-repurposer wiring onto one line.

test-runner.sh 66/0/0; node --test 94/94 (was 92, +2). NO push until /trekreview re-clears the gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 09:56:49 +02:00
b48e1305e6 fix(linkedin-studio): close v4.0.0 audit review findings (S8)
Close the 5 findings from the S7 /trekreview release gate (review.md, verdict BLOCK):

- BLOCKER: comment-multiplier "5x" reconciled to the canonical order-only framing
  (no fixed multiplier) in agents/engagement-coach.md, linkedin-growth-playbook,
  linkedin-formats.md — per algorithm-signals-reference.md ("do not quote a comment
  multiplier").
- BLOCKER: carousel rate "6.60%/6.6% (highest)" reconciled to "~7% top organic
  format" in linkedin-formats.md:42 (was self-contradicting :50) and
  assets/templates/carousel-templates.md.
- Lint hardening: test-runner.sh STALE_STATS now matches 6.60% + the 5x comment
  folklore and scans agents/ + assets/templates/ — the grep that defines the
  Phase-0 criterion now catches both BLOCKERs.
- MAJOR: onboarding.md command count 26 -> 27.
- MAJOR: add section-append-branch (production-path) tests for recordFirstHourPlan
  + recordOutreachContact against a template-layout fixture.
- MINOR: move date-scalar changes.push inside the write branch in state-updater.mjs.

Verify: node --test hooks/scripts/__tests__/*.test.mjs -> 92/92; bash
scripts/test-runner.sh -> 66/0/0. NO push until /trekreview re-confirms ALLOW/WARN.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 09:27:15 +02:00
3893985ebd docs(linkedin-studio): add multi-session/effort banner to newsletter
Step 19 (Wave 4 S4). Adds an expectation banner at the very top of
/linkedin:newsletter: a multi-session, multi-gate process (16 phases),
~4-8+ hours across several sessions, state maintained between sessions, with
a pointer to the short-form commands for feed posts. Sets realistic
expectations before the user starts. Pure docs/content.

Verify: grep -niE 'multi-session|multi-gate|16 phases|~[0-9].*hour' near top
→ matches; test-runner.sh exit 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 07:08:26 +02:00
b0e7866d95 feat(linkedin-studio): honest newsletter distribution + profile-SEO + outreach pipeline
Step 17 (Wave 4 S4) of the audit remediation. Applies research/03 §D5 + the
two S2 residual fixes folded in. No new commands/agents (counts stay 27/19).

Newsletter (commands/newsletter.md): new "Distribution channel" section after
Step 10 teaching the HONEST native-newsletter mechanics — bypasses organic feed
ranking via ONE deduplicated notification per subscriber per edition (NOT a
three-touchpoint blast); the mass invite fires once → ~1-2K follower floor
(wait until you can spend it); realistic cold-start 0-100 subs months 1-3;
discloses non-export / no-canonical / no-read-analytics / per-subscriber decay;
explicit below-vs-above-floor decision rule. Sourced to research/03 D5.

Profile (commands/profile.md): new "Profile SEO" section — headline as the
highest-weight search field + a per-section keyword-target table
(headline/about/experience/skills/featured), consistency-over-stuffing rule.

Outreach (commands/outreach.md): Step 8c persists the pipeline board to tracked
state via the new recordOutreachContact mutation (mirrors Step 16's
recordFirstHourPlan): additive last_outreach_date/outreach_active scalars + a
non-R-initial ## Outreach Pipeline section in state-updater.mjs +
config/state-file.template.md + --record-outreach CLI branch. +7 tests
(state-updater 26→33, full hook suite 83→90).

Residual 1 (growth-playbook:216): 9:16 "distribution boost" → 4:5/1:1 guidance
(9:16 mobile-only opt-in; "immersive distribution" = uncorroborated heuristic).
Residual 2 (video-strategy-guide:300): "3-second test determines 70% retention"
→ "front-load value for muted autoplay" (three-second hook is folklore, not a
LinkedIn signal).

Verify: grep checks 1-5 pass; test-runner.sh exit 0 (stat-consistency green);
state-updater 33/33. [skip-docs] — tre-doc + version bump deferred to Step 21.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 07:07:41 +02:00
b0e8c7b19e feat(linkedin-studio): first-hour/reply-loop command with tracked state
Wire orphan agent #11 (engagement-coach) by giving it a command surface, and
add the tracked first-hour state the plan calls for (remediation Step 16).

- commands/firsthour.md (new, 27th command): post-publish first-hour /
  reply-loop sprint. Delegates plan construction to engagement-coach via
  Task (subagent_type: linkedin-studio:engagement-coach) — returns a grouped
  target list (whales/inner-circle/ICPs/new connections), 2-3 seed
  self-comments + 3-5 CEA replies in the user's voice, and a minute-by-minute
  timeline anchored to publish time. Presents timeline/targets/drafts +
  velocity checkpoints, auto-copies the drafts to clipboard, persists the
  plan, then hands off to post-feedback-monitor for the 48h window.
- hooks/scripts/state-updater.mjs: new pure mutation recordFirstHourPlan()
  mirroring updatePostTracking — additive by contract (inserts
  last_firsthour_date after last_post_date when absent, creates the
  ## First-Hour Plans section when absent, never touches existing fields).
  Section name is deliberately non-R-initial so it stays outside
  pruneContentHistory's "## Recent Posts ... (?=\n## [^R])" capture window.
  + a --record-firsthour CLI branch for parity with the other mutations.
- config/state-file.template.md: additive scalars (last_firsthour_date,
  firsthour_active) + the ## First-Hour Plans section.
- hooks/scripts/__tests__/state-updater.test.mjs: extend (existing file) with
  7 recordFirstHourPlan tests — section creation, field insertion vs in-place
  update (no duplication), round-trip non-interference, graceful empty
  defaults, changes array.
- CLAUDE.md: register the command (## Commands 26 -> 27, table row).
- scripts/test-runner.sh: EXPECT_COMMANDS 26 -> 27 (registration guard).

Verify: grep 'subagent_type: linkedin-studio:engagement-coach' commands/ ->
firsthour.md; node --test state-updater -> 26/26; full hook suite -> 83/83;
bash scripts/test-runner.sh -> exit 0 (62 passed, commands 27/27).

Plan Step 16 (Wave 4 S3).
[skip-docs]: tre-doc + version bump deferred to Step 21 per remediation plan.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 05:35:44 +02:00
900a959c0b feat(linkedin-studio): video quality gate (captions + aspect guidance, drop 9:16 mandate)
Resolve the video-advice contradiction on the two surfaces this step owns
(research/03 D1-D3):

- commands/video.md: rename the "3-Second Test" to a "Muted-Autoplay Test"
  (front-load value for ~85% muted viewing; the "three-second hook" is
  cross-platform folklore, not a LinkedIn signal); make captions the
  enforceable spec (SRT or native auto-captions, indexed for search);
  aspect ratio as guidance (4:5/1:1 preferred for broad distribution,
  9:16 opt-in for the vertical video tab, crops to 1:1 on desktop); add
  MP4-default + warn-only MOV/AVI + official upload limits to the
  pre-recording reminder.
- references/linkedin-formats.md: reframe the 9:16 "distribution boost"
  as uncorroborated heuristic; 4:5 "deprioritized" -> 4:5/1:1 preferred;
  drop the "3-second hook"; MP4 safe-default + warn-only MOV/AVI; replace
  "good video rewarded more than ever" with the honest "per-video reach
  declining; documents/carousels out-engage video."

algorithm-signals-reference.md (the canonical magnitude source) was
already reconciled in Phase 0 (line 56 reads "declining ... 4:5/1:1
preferred, captions are the enforceable spec") and carries no boost copy,
so it is intentionally untouched here — and it is not in this step's
manifest.

Verify: grep 'must be 9:16|9:16 (1080|3-second hook' video.md
linkedin-formats.md -> none; 'captions' video.md -> 5; 'deprioritized'
linkedin-formats.md -> none; structure lint 61/61.

KNOWN RESIDUAL (flagged for follow-up, NOT in this step's scope):
- references/linkedin-growth-playbook-2025-2026.md:216 still carries a
  "9:16 ... distribution boost" line. That file is owned by Step 17, but
  Step 17's changes are newsletter-distribution — the video line would
  fall through. Fold this into Step 17 or the review gate.
- references/video-strategy-guide.md:300 still says "the 3-second test
  determines 70% of retention." That file is owned by NO plan step
  (orphaned) — needs a home. Surface at session end for an operator
  decision.

Plan Step 15 (Wave 4 S2). Counts unchanged.
[skip-docs]: tre-doc + version bump deferred to Step 21 per remediation plan.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 02:37:51 +02:00
5584ae1cb8 feat(linkedin-studio): short-form de-AI gate via differentiation-checker + voice-guardian
Wire the orphan differentiation-checker (#10) into the five short-form
creation commands (post/quick/react/carousel/video) as a De-AI /
Differentiation Gate at each command's quality-check step: confirm the
LinkedIn-named substance signals (personal substance, original thinking,
concrete specifics, genuine voice) + a soft engagement-bait check, and
delegate an originality pass to linkedin-studio:differentiation-checker
when the angle risks commodity content. Add Task to allowed-tools in
quick/react/carousel (post/video already had it from Step 13).

Extend (not duplicate) hooks/prompts/voice-guardian.md's AI-pattern
section with the same named signals from research/01 D8 + research/03 D4.
Runtime-loaded prompt — no compile-hooks.py, no hooks.json change
(verified: compile-hooks --check reports no drift).

Test: new hooks/scripts/__tests__/linkedin-content-filter.test.mjs pins
the content/non-content boundary the gate is scoped by (14 tests).
Full hook suite 76/76, structure lint 61/61.

Plan Step 14 (Wave 4 S2). Counts unchanged (26 commands / 19 agents).
[skip-docs]: tre-doc + version bump deferred to Step 21 per remediation plan.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 02:31:41 +02:00
570b163bbe refactor(linkedin-studio): wire or delete 11 orphan agents (case-by-case) — 9 here, 2 in Steps 14/16
Resolves the orphan-agent audit finding by the locked default: wire all, no deletions, so the agent count stays 19. Per agent, added Task to the target command's allowed-tools and a coherent 'subagent_type: linkedin-studio:<name>' delegation at a real point in the command's flow (not a token grep-match).

Wired (agents 1-9 of 11): video-scripter -> video.md (Step 4); content-optimizer -> post.md (Step 7 refinement) + ab-test.md (2a.4 optimized challenger); analytics-interpreter -> report.md (Step 7, report mode) + analyze.md (Step 2, interpret mode); content-planner -> batch.md (Step 2) + pipeline.md (Step 1); trend-spotter -> batch.md (Step 1) + pipeline.md (Step 1); network-builder -> outreach.md (Step 3a); strategy-advisor -> strategy.md (Step 3); voice-trainer -> setup.md (Step 3a); post-feedback-monitor -> calendar.md (publish action, 48h monitor).

Deferred to their dedicated steps: #10 differentiation-checker -> Step 14 (short-form de-AI gate), #11 engagement-coach -> Step 16 (first-hour command). Namespaced subagent_type form requires a session reload before the wired agents are invokable.

Verify: each of the 9 has >=1 invocation in commands/; structural lint 61/61 (counts 19/26/25/6 intact); agent-fixtures 35/35; hook tests 62/62. Three-doc + version reconciliation deferred to Step 21 per the locked plan [skip-docs].

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 01:26:36 +02:00
0e04bbc662 docs(linkedin-studio): reconcile discoverability surfaces + skill naming
Wave 3 / Step 12 of the remediation plan (Phase 1 — usable by a non-author).

Fix the discoverability defects the audit flagged:
- skills/linkedin-studio/SKILL.md (the auto-activating router): self-naming 'the
  LinkedIn thought leadership plugin' -> 'LinkedIn Studio' (v3.0 rename leftover);
  the 'All Agents' table corrected from 14 to the real 19 (added editorial-reviewer,
  voice-scrubber, content-reviewer, language-reviewer, fact-reviewer); the 'All
  Commands' table completed to 26 (added headless-review, pivot, carousel) so it
  routes to newsletter/headless-review/pivot/react.
- commands/onboarding.md: '25 commands' -> '26 commands' (x2); pillar count '3-5
  expertise areas' -> '5 expertise areas' (reconciles onboarding's 3-5 with
  setup.md's '5 core topics' and the CLAUDE.md '5 core expertise areas' rule).
- commands/linkedin.md: router table + numbered option list gain headless-review
  and pivot.

Scope note (operator decision, this session): the plan's Verify grep
'grep -rni "thought leadership" skills/ -> no matches' is broader than the brief's
actual criterion (which targets only the router skill's self-naming). 'thought
leadership' is a plugin-wide DOMAIN term — the '8 Thought Leadership Angles'
framework lives in references/thought-leadership-angles.md and is referenced by ~40
files (glossary, agents, post/video/batch commands). Renaming it only inside skills/
would create cross-file inconsistency; renaming it plugin-wide is a separate
vocabulary migration outside Step 12's discoverability scope. Per operator choice,
the router SELF-NAMING is fixed (brief criterion met) and the 4 remaining skills/
hits (linkedin-content-creation headings + one linkedin-strategy phase cell) are
legitimate domain usage kept consistent with the rest of the tree.

setup.md needed no edit: its pillar number was already '5'; reconciliation was a
one-sided fix in onboarding.

Verify: router no longer says 'thought leadership plugin'; grep -nc 'headless-review'
commands/linkedin.md -> 2; onboarding pillar count '5' matches setup.md; SKILL.md
agent table 19 rows, command table 26 rows; structural lint exit 0 (61 passed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 01:05:19 +02:00
b27ceb437b docs(linkedin-studio): honest dated API/auto-publish/analytics boundaries
Wave 3 / Step 10 of the remediation plan (Phase 1 — usable by a non-author), per
research/02.

State what the plugin can and cannot do for a PERSONAL profile, dated 'as of
2026-05', without replacing one false claim with another:
- README.md: new 'Boundaries (as of 2026-05)' section. (a) post-level analytics API
  EXISTS but is partner-gated (vetted Community Management app + verified org +
  Page) — not self-serve; CSV is the practical floor; saves visible natively
  (count-only, ~Sept 2025) with no self-serve API pull. (b) auto-publish is
  technically POSSIBLE self-serve (w_member_social) but deliberately NOT built — a
  design + ToS choice, explicitly 'not an impossibility' (avoids the new false
  'cannot auto-publish' claim). (c) dwell internal-only for organic, not exportable.
- commands/calendar.md: the publish action now states plainly it marks a post YOU
  posted manually as published — the tool does not post on your behalf.
- commands/report.md: dated the saves/no-self-serve-API note ('as of 2026-05').
- commands/import.md: dated 'Why CSV' note — API is partner-gated, CSV is the floor.

Verify: grep -niE 'cannot auto-publish|only way to (get|access)' README.md
commands/*.md -> no matches; grep -ni 'as of 2026' README.md -> 2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 00:58:05 +02:00