linkedin-studio/docs/hardening/log.md
Kjell Tore Guttormsen bfc8719011 docs(linkedin-studio): S29c hardening log entry — terminology-scrub references/
Append the S29c verification record to docs/hardening/log.md (anchored
"### S29c terminology-scrub" entry, matching S29a/S29b format): the 34-edit replacement map
across 15 reference files, the S29e filename lock (thought-leadership-angles.md ->
content-angles.md) with the canonical file's in-file title/headers scrubbed now, the
KEPT-BY-DESIGN items (ironic quote, TL;DW false positive), and the verify block (FORM A in
references/ = only the kept ironic quote; canonical in-file FORM A = NONE; FORM B unchanged 4;
filename pointers unchanged 3; gate 81/0/0; counts 29/19/26/6 unchanged). Documents fix commit
25b356f.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qgzo6rxthw7KuxHjn5vyE
2026-06-20 05:45:50 +02:00

2090 lines
157 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# LinkedIn Studio — Command Hardening Log
> Per-command audit trail for the hardening phase (`docs/hardening/brief.md` +
> `plan.md`). One **anchored** entry per command surface. The operator's parallel
> live-testing cross-references every change here.
>
> **Entry contract (SC-A…SC-E).** Each entry begins with the UNIQUE anchored header
> `### /linkedin:<command-name> — <one-line intent>` (coverage greps `^### /linkedin:<name>`,
> never the bare word). Every entry carries: **INTENT** · **SIMULATE** (persona + the
> CONCRETE before-output + friction log) · **EVALUATE** (4 axes, with the per-type
> **mechanical predicate** — never "N/A → judgment") · **HARDEN** (surgical diff +
> concrete after-output, or "no edit — passes") · **VERIFY** (lint `Failed: 0` + counts
> + the failing axis now passes). The cold `/trekreview` reviewer adjudicates every
> hardened command's before/after — the author does not self-certify.
>
> **Mechanical-predicate classes:** *post-emitting* (hook 110140 · length band ·
> no body link · no banned buzzword · topic→5 pillars) · *routing* (every emitted
> target resolves to a real `commands/<x>.md`) · *analytics* (graceful-degradation
> present + saves/dwell honesty intact) · *guided/stateful* (primary promised artifact
> actually produced; promised `subagent_type` targets resolve).
>
> **Stopping rule (anti-gold-plating):** harden until every axis returns pass OR a
> recorded deferral; no NICE-only polish beyond axis-pass.
>
> **Method discipline (learned the hard way in the S1 calibration — three stumbles).**
> Write the HARDEN / after-output section **only from the applied + re-grepped diff**, and
> assert a gap **only after reading the actual file line**. Every number (char counts,
> edit counts) must come from a tool, not from memory. The S1 calibration produced three
> assert-before-verify errors — (1) a non-existent inline buzzword list in `quick`, (2) a
> non-existent "unused Task" in `quick`, (3) `onboarding`/`first-post` edits asserted as
> landed when the Edits had failed on wrong strings, plus a false "25 commands → stale"
> claim (the file already said 29). All were caught by failed Edits / empty greps / the
> git status, and corrected here. This is exactly the failure mode the independent
> `/trekreview` oracle exists to catch — verify-before-assert is part of the method, not optional.
---
## Session 1 — Method calibration (`quick`) + Start journey
> S1 status: calibration corrected + method locked (operator, 2026-05-31). onboarding /
> first-post / setup follow below under the same entry shape. Field-notes inbox:
> **absent** at S1 start → graceful no-op (SC-I).
### /linkedin:quick — 5-minute 3-line post from a topic, ≤1 question, clipboard-ready
**INTENT.** `quick` is the Create journey's speed path: a topic in → a publishable
150500-char post out in ~5 minutes, max one question, auto-copied to clipboard. It must
honor the content-quality bar mechanically (hook **110140**, length **150500**, no body
links, no banned buzzword, topic maps to one of the 5 pillars, exactly one CTA) and the
algorithm bar (hook decisive before the mobile fold; links to first comment not body;
native; a CTA that invites comments without manufactured engagement-bait). Its defining
constraint is *speed* — "skip interrogation, generate immediately."
**SIMULATE.**
- **Persona (ICP):** the author — 1048 followers, "Validation", 5 pillars; voice =
direct/technical/low-formality, NO/EN mix, short sentences, contrarian openings
(grounded in the state file + the gitignored `authentic-voice-samples.local.md` the
command reads at runtime). `recent_posts: []` → no rotation conflict. *Fresh-adopter
path (no voice samples) reasoned, not separately re-drafted: `quick` reads voice from
SKILL.md and falls back to defaults; the personalization score is hidden < 3 posts and
the voice guardian is suppressed < 5 samples — degrades cleanly, no block, no dead-end.*
- **Invocation:** `/linkedin:quick AI-kodeassistenter i offentlig sektor`
(pillars "AI-rådgivning i offentlig sektor" / "Claude Code / agentisk koding").
- **Walk:** topic from `$ARGUMENTS` (no question) → load voice + pillars → infer type
(HOT TAKE — contrarian) → 3-line formula → auto-CTA → quality check →
de-AI/differentiation gate (skip unless commodity) → clipboard → present.
- **CONCRETE before-output** (produced under `quick.md` **as written**, pre-fix):
> Alle vil ha AI-kodeassistenter i staten. Men hvem tar ansvaret når koden tar feil i
> produksjon?
>
> Verktøyet skriver koden på sekunder. Ansvaret for at den er riktig flyttes ikke — det
> blir ditt, raskere enn før.
>
> Hvordan kvalitetssikrer dere AI-generert kode hos dere?
*(Pillar: Claude Code / agentisk koding · type: HOT TAKE · **hook = 95 chars** (node-verified) ·
body has 0 links / 0 buzzwords.)*
- **Friction log:** the **95-char hook passes `quick.md`'s own checks** — Step 2 said
"Hook (under 140 characters)" and the Step 5 checklist asked "Hook works in 140 chars?",
**both upper-bound only** — yet it breaks the canonical **110** floor (the PreToolUse gate:
"under 110: wasting prime real estate"). Same one-sidedness on length ("Under 500?" vs
the 150500 band). The Step 5 checklist also had **no buzzword line**, and a quick post is
auto-copied to clipboard (no file Write), so the PreToolUse *file* gate may not fire — the
checklist is the load-bearing surface. No dead-ends.
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** One-question speed path, 3-line formula, 8 templates,
auto-CTA, clipboard, why-hook/reach tips, `/linkedin:post` upgrade path — delivers the promise.
- **(b) algorithm bar — PASS.** No body link (links → first comment); native; CTA invites
comments (comments > reactions) without bait; de-AI gate cites the confirmed low-substance
down-rank. Consistent with `references/algorithm-signals-reference.md`.
- **(c) MECHANICAL predicate (post-emitting) — GAP → fixed.** no body link ✓ · topic→pillar ✓
· **hook band ✗** (spec + checklist enforced ≤140, omitted the 110 floor; the 95-char
before-hook is the live proof) · **length band ✗** (checklist enforced <500, omitted the
150 floor) · **buzzword ✗** (no checklist line; clipboard path bypasses the file gate).
- **(d) agent-wiring + graceful degradation — PASS (verified).** `quick` **conditionally**
delegates to `differentiation-checker` via `Task` (`quick.md:151`,
`subagent_type: linkedin-studio:differentiation-checker`) — only when the take is commodity,
preserving the 5-minute promise by default. `Task` in `allowed-tools` is therefore *used*,
not vestigial. Fresh-adopter degradation works (defaults; guardian suppressed < 5 samples).
**HARDEN (surgical — 2 edits, axis-c only — both grep-confirmed landed).**
- `commands/quick.md:68``**Line 1: Hook (under 140 characters)**``**Line 1: Hook (110-140 characters)**`.
- `commands/quick.md` Step 5 checklist — hook check `"Hook works in 140 chars?"`
`"Hook in the 110-140 band (not just under 140)?"`; length check `"Under 500 characters?"`
`"In the 150-500 band (not just under 500)?"`; **added** `"No corporate buzzwords?"`; count
`**All 6 = Yes?**``**All 7 = Yes?**`.
- **Deferrals:** none (the earlier "incomplete buzzword list" and "unused Task" findings were
file-misreads, struck — see Method discipline).
- **CONCRETE after-output** (under the hardened spec — hook expanded into the 110140 band):
> Alle vil ha AI-kodeassistenter i staten. Få spør hvem som tar ansvaret når assistenten
> foreslår noe ingen kan forklare etterpå.
>
> Verktøyet skriver koden på sekunder. Ansvaret for at den er riktig flyttes ikke — det
> blir ditt, raskere enn før.
>
> Hvordan kvalitetssikrer dere AI-generert kode hos dere?
*(**hook = 127 chars** (node-verified, in band) · 0 body links · 0 buzzwords · same pillar.)*
**VERIFY.**
- `bash scripts/test-runner.sh``Failed: 0` + exit 0 + counts 29/19/25/6 unchanged
(re-run AFTER the real edits; recorded at the session gate).
- Before/after delta: axis (c) failing sub-checks (hook 110 floor, length 150 floor,
buzzword) now pass — the 95-char before-hook slips `quick.md`'s old checks; the hardened
checklist catches it and the after-hook is 127 chars, buzzword-clean. The two edits are the cause.
- Disposition: **HARDENED** (2 edits, axis-c) · 0 deferrals · axes a/b/d PASS.
---
### /linkedin:onboarding — zero→published first post as one guided wizard (Start front-door)
**INTENT.** Multi-phase wizard taking a brand-new user from zero to a published first post
as one cohesive flow (profile → personalization → first-post), so they don't navigate the
full command surface alone. Primary artifacts: a saved profile + a drafted first post (the
S15-B1 inline-draft). Guided/stateful; inlines rather than spawning subagents.
**SIMULATE.**
- **Persona (fresh adopter — the right persona here):** just installed, no profile, no voice
samples, `recent_posts: []`.
- **Invocation:** `/linkedin:onboarding`.
- **Walk:** Phase 0 already-onboarded check → Phase 1 profile/topic-relevance checklist →
Phase 2 personalization (voice + user profile, or defaults when < 3 posts) → Phase 3 first
post (3.1 topic → 3.2 3-line draft → 3.3 quality check → 3.4 present+clipboard → 3.5 record)
→ Phase 4 summary. **CONCRETE artifact produced:** a refined first-post inline draft (S15-B1
path — **spot-confirmed still delivers:** Phase 3.2 "Draft the post… Line 1/2/3", 3.4
"present and copy").
- **Friction log:** Phase 3.2 said "Line 1 — Hook (**under 140** chars)" and the Phase 3.3
check asked "Hook works in **140** chars?" — **both upper-bound only**, so a 95-char hook
would pass while breaking the 110 floor. (Phase 3.2 line 204 already states "150-500
characters" + "No external links in the post body", so length + links were **NOT** gaps —
only the hook floor.) The description + Phase-4 tip hardcode "29 commands" — **accurate
today, not stale** (an earlier "25 commands" claim was a misread, struck).
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** Cohesive zero→post wizard; S15-B1 inline draft holds (Phase 3).
- **(b) algorithm bar — GAP → fixed.** Phase 3 emitted a post but enforced only the hook upper
bound; now the full 110140 band (length 150500 + no-body-links were already present).
- **(c) MECHANICAL predicate (guided/stateful) — PASS; one post-emitting sub-check fixed.**
Primary artifact (first-post draft) produced ✓; the one-sided hook bound in Phase 3.2 + 3.3 closed.
- **(d) agent-wiring + graceful degradation — PASS.** No `Task` in `allowed-tools` — onboarding
inlines every step (Phase 2 "delegate to setup" = inline its logic; commands aren't subagents,
so no broken `subagent_type`). Built for the no-profile path; degrades cleanly.
**HARDEN (surgical — 2 edits, hook floor — both grep-confirmed landed).**
- `commands/onboarding.md:200``**Line 1 — Hook (under 140 chars):**``**Line 1 — Hook (110-140 chars):**`.
- `commands/onboarding.md:209` (Phase 3.3 check) — `Hook works in 140 chars?``Hook in the 110-140 band (not just under 140)?`.
- **Deferred (NICE-only, stopping rule):** the hardcoded "29 commands" (description + Phase-4
tip) is correct now; making it count-free is drift-proofing, not an axis fix → recorded, not edited.
**VERIFY.** lint `Failed: 0` + counts unchanged (recorded at gate); S15-B1 inline-draft
spot-confirmed; hook floor now enforced in Phase 3.2 + 3.3. Disposition: **HARDENED** (2 edits) ·
1 recorded deferral.
---
### /linkedin:first-post — zero→published in <10 min, maximum hand-holding
**INTENT.** First-post accelerator: from "never posted" to "just published" in <10 min,
breaking the blank-page barrier. Produces one published first post. Guided/stateful; inlines.
**SIMULATE.**
- **Persona (fresh adopter):** no profile (Step 2 offers a voice quick-setup or 5-question
calibration; proceeds either way — momentum over completeness).
- **Invocation:** `/linkedin:first-post`.
- **Walk:** Step 1 welcome → Step 2 voice setup (samples or 5 Qs; graceful if none) → Step 3
topic (5 angles) → Step 4 write (3-line formula) → Step 5 simplified quality check → Step 6
present + clipboard → Step 7 record → Step 8 first-hour engagement guidance.
- **CONCRETE before (the gap):** Step 4 "Line 1: Hook (**under 140** characters)" and the Step 5
check "Hook works in **140** chars?" were **both upper-bound only** — a 95-char hook would pass.
(Step 4 already states "Target: 150-500 characters" and "No external links in the post body",
so length + links were **NOT** gaps — only the hook floor.)
- **Friction log:** the hook bound was one-sided in Step 4 + Step 5; length + no-links already covered.
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** Delivers zero→published with hand-holding; first-hour guidance present.
- **(b) algorithm bar — PASS.** First-hour engagement window cited; no-body-links already in Step 4 tips.
- **(c) MECHANICAL predicate (post-emitting) — GAP → fixed.** Hook bound was upper-only in Step 4 +
Step 5; now the full 110140 band (length 150500 + no-body-links already present).
- **(d) agent-wiring + graceful degradation — PASS.** No `Task`; inlines; no-profile/no-samples
path graceful. Differentiation-checker deliberately skipped (a first post optimizes for momentum).
**HARDEN (surgical — 2 edits, hook floor — both grep-confirmed landed).**
- `commands/first-post.md:101``**Line 1: Hook (under 140 characters)**``**Line 1: Hook (110-140 characters)**`.
- `commands/first-post.md:125` (Step 5 check) — `Hook works in 140 chars?``Hook in the 110-140 band (not just under 140)?`.
- **Deferrals:** none.
**VERIFY.** lint `Failed: 0` + counts unchanged; Step 4 ↔ Step 5 hook bound now consistent (both 110-140).
Disposition: **HARDENED** (2 edits) · 0 deferrals.
---
### /linkedin:setup — guided personalization (5 pillars + voice profile + prefs)
**INTENT.** Build the user's voice profile, expertise pillars, and content preferences into
the state/asset files so every post sounds like them. Primary artifact: a populated voice
profile + populated asset templates (8-category personalization score). Guided/stateful;
delegates voice to an agent.
**SIMULATE.**
- **Persona (fresh adopter):** no existing data (all templates at placeholder).
- **Invocation:** `/linkedin:setup`.
- **Walk:** Step 0 calculate score → Step 1 dashboard → Step 2 choose what to set up → Step 3a3f
sub-workflows (voice / case study / framework / post analysis / demographics / user profile) →
Step 4 recalculate → Step 5 continue or exit. **CONCRETE artifact:** e.g. Step 3a writes a real
voice profile to `assets/voice-samples/authentic-voice-samples.md` (placeholder sentinel removed).
- **Friction log:** none — no dead-ends, no ambiguous steps.
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** Delivers the personalization the description promises (score + 6 sub-flows).
- **(b) algorithm bar — PASS (n/a-direct).** Emits no post; the expertise/pillar capture is the
topic-relevance foundation the bar depends on.
- **(c) MECHANICAL predicate (guided/stateful) — PASS.** Primary artifact (populated profile/assets)
produced ✓; the promised `subagent_type: linkedin-studio:voice-trainer` (`setup.md:86`) **resolves**
to a real `agents/voice-trainer.md` (verified).
- **(d) agent-wiring + graceful degradation — PASS.** voice-trainer invoked via `Task` (correct
namespaced type); the placeholder-sentinel logic is explicit; samples optional → degrades if none.
**HARDEN.** **No edit — passes all four axes.** (Demonstrates a legitimate zero-edit pass; per the
plan, "command file modified" is not a coverage predicate — the `log.md` entry + `/trekreview` are.)
**VERIFY.** lint `Failed: 0` + counts unchanged; voice-trainer target resolves.
Disposition: **PASS, no edit** · 0 deferrals.
---
## Session 2 — Create I (`post`)
> S2 status: the v1 autonomous attempt fabricated and was reverted (→ v2 method; see `plan.md`
> header). This is the v2 re-run: parked at step 5 across a `/clear`, re-grounded from STATE.md's
> two P4 findings against the actual file lines before talking (jerndisiplin: assert-after-read).
> Field-notes inbox **absent** at S2 → graceful no-op (SC-I).
### /linkedin:post — interactive full-workflow post (angle→write→quality→refine→publish), 12001800 chars
**INTENT.** `post` is the Create journey's full path: from raw material (data / article / experience /
observation / opinion / question) to a publishable 1,2001,800-char thought-leadership post via an
8-step workflow — angle → format → structure+write → quality check → present → refine → pre-publish
→ deterministic state update. Post-emitting, so it must honor the content bar mechanically (hook
**110140**, length **1,2001,800**, no body links, no banned buzzword, topic→5 pillars) and the
algorithm bar (hook decisive before the mobile fold, links→first comment, native, comment-inviting
CTA without bait, de-AI substance signals).
**SIMULATE.**
- **Persona (ICP):** the author — 1048 followers, "Validation", 5 pillars; direct/technical NO/EN voice.
- **Invocation:** `/linkedin:post about AI-kodeassistenter i offentlig sektor`.
- **Walk:** Step 0 load state/SKILL → Step 1 type=Opinion → Step 2 angle (contrarian) → Step 3 medium
text → Step 4 structure (hook 110140 ✓ internally) → **Step 5 quality check (the gap)** → Step 6
present+clipboard → Step 7 refine → Step 8 pre-publish + state.
- **CONCRETE friction (spec-level, the live proof — anchored, not a generated post):** at **Step 5**
the quality checklist (`post.md:127135`, 9 items) had **no buzzword line**, so a draft opening
*"How to leverage AI to drive synergy in the public sector…"* would pass Step 5 unflagged — the
banned-list (content rule #4, `CLAUDE.md:124`) was never enforced at the gate. Step 5 also routes to
`quality-scorecard.md` (`post.md:125`), which **self-contradicted on the hook bound**: l.32 "Works in
**140** characters" + l.114 "Hook works in **140** chars?" (upper-only) vs l.128 "**110-140**" — the
same one-sided bound S1 fixed in the command files, surviving in the shared asset.
- **Friction log:** `post.md` itself is internally consistent on the hook (110140 at l.100/113/127);
the only command-side gap is the missing Step-5 buzzword line. The shared scorecard carried both
gaps (buzzword absent + two one-sided hook bounds). Scorecard is shared by 8 surfaces
(`pipeline · video · post · react · batch` + 3 skills) — fixing it is high-leverage, operator-approved.
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** Full 8-step workflow delivers the description's promise.
- **(b) algorithm bar — PASS.** No body link (`post.md:133/176`); comment-inviting CTA; de-AI gate
cites the confirmed reach-suppression (`post.md:137141`). Consistent with `algorithm-signals-reference.md`.
- **(c) MECHANICAL predicate (post-emitting) — GAP → fixed.** hook band ✓ · length band ✓ · no-body-link
✓ · topic→pillars ✓ · subagent resolves ✓ · **buzzword ✗** (Step-5 checklist had no line; grep NONE).
Shared scorecard added two sub-gaps: hook one-sided at l.32 + l.114.
- **(d) agent-wiring + graceful degradation — PASS.** `differentiation-checker` (`post.md:141`,
conditional) + `content-optimizer` (`post.md:166`, conditional) both resolve, both correctly
`subagent_type: linkedin-studio:`-namespaced. Voice→defaults; fresh-adopter path graceful.
**HARDEN (surgical — 4 edits across 2 files, all grep-confirmed landed).**
- `commands/post.md:134`**added** Step-5 checklist line: `- [ ] No corporate buzzwords (leverage,
synergy, paradigm shift, thought leader, disruptive, value proposition, ecosystem, holistic approach)`.
- `assets/checklists/quality-scorecard.md:32` — `Works in 140 characters` → `Works in 110-140
characters` (Notes `Mobile threshold` → `Mobile band (floor + ceiling)`).
- `assets/checklists/quality-scorecard.md:114` — `Hook works in 140 chars?` → `Hook works in 110-140 chars?`.
- `assets/checklists/quality-scorecard.md:132` — **added** Pre-Publish Basic Checklist line:
`- [ ] No corporate buzzwords (…)` (same banned list).
- **Deferrals:** none. **Scope note:** the 3 scorecard edits improve all 8 consumers; only `post` is
hardened this session — `react/batch/video/pipeline` get their own EVALUATE in S3/S5/S6/S8.
**VERIFY.**
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`; counts **29/19/26/6** unchanged.
- Anti-check grep: no one-sided bare `140 char` hook bound remains in the scorecard (`110-140` at
l.32/114/128). Buzzword line present in both files (`post.md:134`, `quality-scorecard.md:132`).
- Before/after delta: axis (c) buzzword sub-check now enforced at the load-bearing Step-5 gate; the
scorecard hook self-contradiction is resolved (all three mentions banded). The 4 edits are the cause.
- Disposition: **HARDENED** (4 edits, axis-c + shared-asset consistency) · 0 deferrals · axes a/b/d PASS.
---
## Session 3 — Create I (`react`)
> S3 status: re-grounded STATE's S3 predicate against the actual `react.md` lines BEFORE talk
> (jerndisiplin: assert-after-read). Field-notes inbox **absent** at S3 → graceful no-op (SC-I).
> All `file:line` below are tool-grounded against the FINAL (post-edit) file.
### /linkedin:react — URL-to-post pipeline (single + comparison), your-take-as-hook, 1,2001,800 chars
**INTENT.** `react` is the Create journey's **URL→post path**: fetch external content
(article / news / research / YouTube), extract the key points, pick an angle, and draft in the
author's voice. Post-emitting (medium **1,2001,800**), two paths — single-URL (Steps 18) +
comparison/multi-URL (Steps 1b8b). Must honor the content bar mechanically (hook **110140**,
length **1,2001,800**, no body link, no banned buzzword, topic→5 pillars) and the algorithm bar
(URL→first comment not body, post stands alone, *your reaction is the hook* not a summary,
comment-inviting CTA, de-AI substance). **Distinct from `post`/`quick`:** the value-add is YOUR
perspective *extending* the source, not echoing it — the commodity-reaction guard (differentiation-checker).
**SIMULATE.**
- **Persona (ICP):** the author — 1048 followers, "Validation", 5 pillars; direct/technical NO/EN voice.
- **Invocation:** `/linkedin:react <public-sector-AI news URL>`.
- **Walk:** Step 0 load → Step 1 single URL → Step 2 WebFetch extract → Step 3 classify (Product
Launch) → Step 4 angle (First-Take Analysis) → Step 5 draft (hook 110140 ✓) → **Step 6 quality
check (the gap)** → Step 7 present+clipboard → Step 8 state.
- **CONCRETE friction (spec-level, anchored — not a generated post):** at **Step 6** the quality
checklist (pre-edit `react.md:122128`, 7 items) enforced hook/links/standalone/voice/charcount/
topic/CTA but had **no buzzword line**, so a draft opening *"This launch lets agencies leverage AI
to unlock synergy across the value proposition…"* passes unflagged — the banned-list (content rule
#4, `CLAUDE.md:124`) was never enforced at the gate. **Step 6b** (`:237` "Same as Step 6, plus")
inherits the hole. `react` auto-copies to clipboard (`:144148`, no file Write) → the PreToolUse
**file** gate may not fire → Step 6 is the load-bearing surface. **Same gap S2 fixed in `post`.**
- **Second friction (axis d, URL-pipeline-specific):** `grep` for fetch-failure/paywall/transcript/
fallback → **NONE**. Step 2 (`:54`) "Use WebFetch to extract" had **no fallback** when extraction
yields nothing (paywall / JS-heavy SPA / **YouTube-no-transcript** — an explicitly accepted input,
`:37/:71`); Step 2b (`:185`) same. Risk: proceed on a thin/empty extraction or fabricate from the URL string.
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** Both paths, angle selection, your-take-not-summary, commodity
guard, clipboard, URL→first comment — delivers the description's promise.
- **(b) algorithm bar — PASS.** No body link (`react.md:115/125/235`); URL→first comment
(`:116/:144/:253`); stands alone (`:118/:126`); take-as-hook (`:101/:118`); de-AI gate bans
mechanical engagement-bait (`:135137`). Consistent with `references/algorithm-signals-reference.md`.
- **(c) MECHANICAL predicate (post-emitting) — GAP → fixed.** hook band ✓ (`:101/:124/:221`) ·
length band ✓ (`:119/:128/:239`) · no-body-link ✓ (`:115/:125/:235`) · topic→pillars ✓ (`:129`) ·
subagent resolves ✓ (`:137` → `agents/differentiation-checker.md`) · **buzzword ✗** (grep NONE; no
inline line in Step 6/6b).
- **(d) agent-wiring + graceful degradation — wiring PASS; degradation GAP → fixed.** `differentiation-checker`
resolves, namespaced, conditional (`:137`). **Graceful degradation:** no WebFetch-failure/empty-extraction
fallback on EITHER path (grep NONE) — YouTube-no-transcript the sharpest, since it is a stated accepted input.
- **Shared asset:** `quality-scorecard.md` already clean post-S2 (`110-140` at `:32/:114/:128` +
buzzword `:132`) — `react`'s referenced asset (`react.md:270`) is good; the gap was `react`'s OWN inline Step 6.
**HARDEN (surgical — 3 edits, 1 file, all grep-confirmed landed).**
- `commands/react.md:131` — **added** Step-6 checklist line: `- [ ] No corporate buzzwords (leverage,
synergy, paradigm shift, thought leader, disruptive, value proposition, ecosystem, holistic approach)`.
Covers Step 6b via "Same as Step 6".
- `commands/react.md:60` — **added** Step-2 WebFetch fallback: *"If WebFetch can't extract usable
content (paywall / JS-heavy / YouTube-no-transcript): say so, ask the user to paste the key points
or choose another URL; never fabricate from the URL string or title."*
- `commands/react.md:194` — **added** Step-2b pointer: *"If a source can't be extracted: apply the
Step 2 fallback — drop it if 2+ usable sources remain, else ask the user; never fabricate."*
- **Deferrals:** none.
- **CONCRETE after:** the "leverage…synergy…value proposition" draft now trips the Step-6 buzzword
line; a YouTube-no-transcript invocation now degrades to "paste the key points / pick another URL"
instead of a fabricated reaction.
**VERIFY.**
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`; counts **29/19/26/6** unchanged
(`ls commands/*.md | wc -l` == 29; `git diff --stat` = `react.md` only, +5 insertions, 0 deletions).
- Before/after delta: axis (c) buzzword sub-check now enforced at the load-bearing Step-6 gate (both
paths via "Same as Step 6"); axis (d) WebFetch-failure now has a sensible fallback on both paths. The 3 edits are the cause.
- Disposition: **HARDENED** (3 edits, axis-c + axis-d) · 0 deferrals · axes a/b PASS.
---
## Session 4 — Create III (`multiplatform`)
> S4 status: re-grounded the proposed predicates against the actual `multiplatform.md` lines
> BEFORE talk (jerndisiplin: assert-after-read). Field-notes inbox **absent** at S4 → graceful
> no-op (SC-I). All `file:line` below are tool-grounded against the FINAL (post-edit) file.
> **Class call (operator-agreed):** NOT post-emitting — output is Twitter/slides/YouTube, so the
> LinkedIn content predicates (hook 110140, length band, no-body-link, topic→5 pillars) do **not**
> apply per platform; imposing them here would be the exact category error the Create-III brief warns
> against. Tested as **guided/cross-format with a routing sub-predicate**.
### /linkedin:multiplatform — adapt existing LinkedIn content to short-/cross-format (Twitter thread · slides · YouTube script)
**INTENT.** `multiplatform` is the Create journey's **cross-format adapter**: take existing LinkedIn
content and re-shape it for other platforms (Twitter/X thread, presentation slides, YouTube script) —
*write once, publish everywhere*. It explicitly does **not** own long-form (newsletters / blog / essays
→ `/linkedin:newsletter`, the single long-form entry point). It is an *adapter*, not a post-generator:
the value-add is adaptation fidelity (each platform's own rules) + correct routing of out-of-scope
(long-form) requests, not LinkedIn-feed quality gating.
**SIMULATE.**
- **Persona (ICP):** the author — a finished LinkedIn post in drafts, wants to reuse it as a Twitter thread.
- **Invocation:** `/linkedin:multiplatform` → pick "Twitter/X thread".
- **Walk:** Step 0 (`:21`) load source from `drafts/` + state → Step 1 (`:31`) AskUserQuestion pick
Twitter → Twitter template (`:53`, hook ≤**280** + 🧵 + one-idea tweets + CTA) → Step 2 (`:115`) save
`drafts/multiplatform/twitter-[slug].md` (`:118`) + clipboard, "Copied" (`:123`). **Promised artifact
produced.**
- **CONCRETE friction (axis d, adapter-specific — anchored, not a generated post):** `grep` for an
empty-source / fabrication guard in Step 0 → only matched l.23 "Ask the user to provide … or read
from drafts" — **no fallback** for the case where drafts is empty/absent, state has no recent posts,
AND the user pasted nothing. Risk: the model **fabricates a LinkedIn post to adapt** rather than
stopping. (Same failure class as S3's WebFetch-no-extraction gap.)
- **Second friction (axis c, voice hygiene):** the banned-buzzword list (content rule #4) is
*platform-agnostic voice hygiene*, not a char rule — and adaptation **rewrites** the source
(condense the hook to 280, expand to a script), which can introduce buzzwords absent from the
clean source. Pre-edit the file mentioned buzzwords **nowhere** (full read). Operator-judged in
scope (light line, not a full gate).
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** Three adapters (Twitter/slides/YouTube), source-loading,
save + clipboard, long-form correctly delegated — delivers the description's promise.
- **(b) algorithm/platform bar — PASS.** Per-platform rules correct, **no LinkedIn-rule leak**:
Twitter "280 chars max" (`:53`); **no** `110`/`140` hook band anywhere in the file (grep NONE —
the desired result for a non-LinkedIn surface). Long-form routing intact (see (c)).
- **(c) MECHANICAL predicate (routing sub-predicate) — PASS; voice-hygiene sub-gap fixed.** Every
emitted `/linkedin:newsletter` (`:6/:34/:36/:132`) resolves to a real `commands/newsletter.md`
(verified, ls) ✓; both reference files resolve — `references/linkedin-formats.md` (`:128`) +
`engagement-frameworks.md` (`:129`) (verified, ls) ✓. **Buzzword voice-hygiene line absent** →
added (`:45`).
- **(d) agent-wiring + graceful degradation — wiring N/A; degradation GAP → fixed.** No subagents
invoked (`grep subagent_type` → NONE — nothing to resolve). **Graceful degradation:** no
empty-source/fabrication guard in Step 0 (grep matched only the weak "ask … or read from drafts")
→ added a stop-and-ask-never-fabricate fallback (`:28`).
**HARDEN (surgical — 2 edits, 1 file, both grep-confirmed landed).**
- `commands/multiplatform.md:28` — **added** Step-0 empty-source fallback: *"If none of these yields
source content (empty/absent drafts, no recent posts, and nothing pasted), **stop and ask the user
to paste the LinkedIn post to adapt** — never fabricate source content to adapt."*
- `commands/multiplatform.md:45` — **added** an "Adaptation Templates" voice-hygiene blockquote:
*"adaptation rewrites the source — keep the adapted copy free of the banned corporate buzzwords
(content rule #4: leverage, synergy, paradigm shift, thought leader, disruptive, value proposition,
ecosystem, holistic approach) on every platform; don't let the rewrite introduce them."*
- **Deferrals:** none. **Scope note (flagged, not touched):** Step 2's "Copied to clipboard." (`:123`)
is unconditional, but `clipboard-helper.mjs` is shared, tested infra used identically by ~9 content
commands — re-gating it here would be scope-creep into a shared pattern (surgical discipline).
**VERIFY.**
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`; counts **29/19/26/6**
unchanged (`ls commands/*.md|wc -l` == 29, `ls agents/*.md|wc -l` == 19; `git diff --stat` =
`multiplatform.md` only, +9 insertions, 0 deletions).
- Before/after delta: axis (d) empty-source now degrades to "paste the post / never fabricate"
instead of an invented source; axis (c) the rewrite is now bound by the banned-buzzword list on
every platform. The 2 edits are the cause.
- Disposition: **HARDENED** (2 edits, axis-c + axis-d) · 0 deferrals · axes a/b PASS · class
confirmed non-post-emitting (no LinkedIn predicate imposed).
---
## Session 5 — Create II (`carousel`)
> S5 status: re-grounded the proposed predicates against the actual `carousel.md` lines BEFORE
> talk (jerndisiplin: assert-after-read). Field-notes inbox **absent** at S5 → graceful no-op (SC-I).
> All `file:line` below are tool-grounded against the FINAL (post-edit) file.
> **Class call (operator-agreed):** hybrid **guided/stateful + post-emitting on the caption** — the
> deck/images are the guided artifact, but the caption IS LinkedIn feed text, so the post predicates
> apply *to the caption* (not to the slides). Slides are bound by mobile-readability rules, not the
> feed bar.
### /linkedin:carousel — template→slide-by-slide deck + caption + optional mcp-image, full deck to clipboard
**INTENT.** `carousel` is the Create journey's **document-post path**: from a chosen template
(How-To / Listicle / Story / Comparison / Framework) + a topic, produce a publish-ready carousel —
structured slide-by-slide copy (header + body + visual note per slide) + a feed caption + optional
slide images (mcp-image), all assembled into ONE clipboard payload. Carousels/documents are the
top organic format (~7%; `references/algorithm-signals-reference.md`). Hybrid class: the deck is the
guided/stateful artifact; the **caption is post-emitting** and must honor the content bar mechanically
on the caption (hook **110140**, no banned buzzword, topic→5 pillars) + graceful mcp-image degradation.
**SIMULATE.**
- **Persona (ICP):** the author — 1048 followers, "Validation", 5 pillars; direct/technical NO/EN voice.
Builds a **How-To Guide** carousel on a pillar topic; tested both mcp-image-available and -unavailable.
- **Invocation:** `/linkedin:carousel` → pick template 1 (How-To) → topic + audience.
- **Walk:** Step 0 (`:24`) load state/voice/recent → Step 1 (`:30`) AskUserQuestion template → Step 2
(`:50`) topic+audience → Step 3 (`:58`) slide copy → Step 4 (`:94`) caption (hook 110140 `:97`, band
300500 `:103`) → **Step 5 quality + De-AI gate (the gap)** → Step 5.5 (`:127`) mcp-image (graceful)
→ Step 6 (`:156`) present full deck → clipboard one-payload (`:191213`) → Step 7 (`:218`) state.
- **CONCRETE friction (spec-level, anchored — not a generated deck):** at **Step 5** the De-AI /
Differentiation gate (pre-edit `:121123`) confirmed *personal substance · original thinking ·
concrete specifics · genuine voice* and banned mechanical engagement-bait, but had **no buzzword
line** — `grep -niE "buzzword|leverage|synergy|paradigm"` over the whole file = **NONE**. So a
caption opening *"How to leverage AI to drive synergy across the public-sector ecosystem…"* would
pass Step 5 unflagged; content rule #4 (`CLAUDE.md:124`) was never enforced at the caption gate.
Same failure class S2/S3 fixed in `post`/`react`. (Caption generation in Step 4 is exactly where a
rewrite can slip buzzwords in.)
- **No dead-ends:** all 5 referenced files exist (`agents/differentiation-checker.md`,
`assets/templates/carousel-templates.md`, `references/algorithm-signals-reference.md`,
`hooks/scripts/clipboard-helper.mjs`, `hooks/scripts/state-updater.mjs` — `[ -f ]` all FINNES).
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** Template→slides→caption→full-deck-clipboard-as-one-payload
(`:191213`, explicitly "the whole carousel, not just the caption") + state — delivers the promise.
- **(b) algorithm bar — PASS.** ~7% (`:37`) matches the ref exactly (`algorithm-signals-reference.md:55`,
"Documents / carousels | Top organic format (~7%…)"); "document post" framing correct (`:170`, LinkedIn
removed native carousels Dec 2023); caption hook 110140 (`:97`).
- **(c) MECHANICAL predicate (post-emitting, on the caption) — GAP → fixed.** hook band ✓ (`:97`) ·
caption band 300500 ✓ (`:103`) · topic→pillars ✓ (`:26/:56`) · subagent resolves ✓ (`:125`) ·
**buzzword ✗** (grep NONE; De-AI gate had no rule-#4 line). *no-body-link not imposed: a carousel
caption is not a typical link site and the command never asks for links — minor, deferred (matches
S2S4 which left it untouched).*
- **(d) agent-wiring + graceful degradation — PASS.** `differentiation-checker` resolves, namespaced,
conditional (`:125`, `subagent_type: linkedin-studio:differentiation-checker`). **mcp-image graceful
degradation solid:** "If mcp-image is unavailable or fails, skip… degrades to text-only" (`:129`),
per-call failure logged + continue, all-fail → text-only Step-6 design-guide branch (`:154`, `:176`).
`state-updater` exports `updatePostTracking` (`:55`) + `writeState` (`:346`) — Step-7 wiring correct.
**HARDEN (surgical — 1 edit, 1 file, grep-confirmed landed).**
- `commands/carousel.md:123` — **extended** the Step-5 De-AI gate with content rule #4:
`**Strip corporate buzzwords** from the caption (Content Quality Rule #4: leverage, synergy,
paradigm shift, thought leader, disruptive, value proposition, ecosystem, holistic approach) —
caption generation can slip them in, and they trip the same low-substance signal.`
- **Deferrals:** *no-body-link on the caption* (low risk, command never emits links; S2S4 precedent)
+ two cosmetic notes recorded, not edited (slide line-count 5 vs 7 between `:7377` and `:87`; both
are NICE-only, below the stopping-rule's axis-pass bar).
- **CONCRETE after:** the "leverage…synergy…ecosystem" caption now trips the Step-5 buzzword line
before the deck is presented.
**VERIFY.**
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`; counts **29/19/26/6**
unchanged (`ls commands/*.md|wc -l` == 29, `ls agents/*.md|wc -l` == 19; `git diff --stat` =
`carousel.md` only, 1 line changed). Only `.md` touched → hooks/analytics `node --test` not needed.
- Before/after delta: axis (c) buzzword sub-check now enforced at the load-bearing Step-5 caption gate.
The 1 edit is the cause.
- Disposition: **HARDENED** (1 edit, axis-c) · 2 recorded deferrals (no-body-link + cosmetic) · axes
a/b/d PASS.
---
## Session 6 — Create II (`video`)
> S6 status: re-grounded the proposed predicates against the actual `video.md` lines BEFORE
> talk (jerndisiplin: assert-after-read). Field-notes inbox **absent** at S6 → graceful no-op (SC-I).
> All `file:line` below are tool-grounded against the FINAL (post-edit) file.
> **Class call (operator-agreed):** hybrid **guided/stateful + post-emitting on the post caption** —
> the script / captions / thumbnail are the guided artifact (bound by video rules: muted-autoplay,
> 2.5 wps, completion-rate), but the **POST CAPTION (200400) IS LinkedIn feed text**, so the post
> predicates apply *to the caption* (not the script). **Hook 110140 deliberately NOT imposed on the
> caption (operator-agreed):** the caption accompanies the video; the primary hook discipline is the
> muted-autoplay opening (`:100`), not a standalone-post hook — imposing it would be S4's category error.
### /linkedin:video — type→length→angle→video-scripter script + captions + 200400 post caption + first comment
**INTENT.** `video` is the Create journey's **video-script path**: from type (talking-head / screen-rec /
slideshow / convert-a-text-post) + length (30/60/90/120 s) produce a production-ready script via the
`video-scripter` agent — timing markers, visual/energy cues, captions, thumbnail, a 200400-char post
caption, and a first comment — then save to drafts + deterministic state update. Hybrid class: the script
is the guided/stateful artifact; the **post caption is post-emitting** and must honor the content bar
mechanically on the caption (no banned buzzword, topic→5 pillars, no body link) + 2.5-wps pacing +
graceful degradation when voice samples are absent.
**SIMULATE.**
- **Persona (ICP):** the author — 1048 followers, "Validation", 5 pillars; direct/technical NO/EN voice.
Builds a 60-s talking-head on a pillar topic. **+ fresh-adopter path** (no voice samples) reasoned to
exercise graceful degradation (relevant here because of the pre-edit "REQUIRED before scripting").
- **Invocation:** `/linkedin:video` → talking-head → 60 s → pillar topic.
- **Walk:** Step 0 load state/voice/refs → Step 1 type → Step 2 length (2.5-wps budgets) → Step 3 angle
(topic→pillars) → Step 4 `video-scripter` generates → **Step 5 quality + De-AI gate (the gap)** →
Step 6 present + clipboard post caption → Step 7 refine → Step 8 save + state.
- **CONCRETE friction (spec-level, anchored — not a generated script):**
- **(axis c)** at **Step 5** the only buzzword line (`:106`, pre-edit) sat under the **"Natural Speech
Test"** = the *script's spoken language*; the **caption** De-AI gate (`:129`) carried **no rule-#4
line** (`grep buzzword` → single hit at `:106`). A caption opening *"How to leverage AI to drive
synergy across the public-sector ecosystem…"* would pass the caption gate unflagged. Same failure
class S2/S3/S5 fixed in `post`/`react`/`carousel` (the caption is re-shaped from a text idea in
Step 4 — exactly where a rewrite slips buzzwords in).
- **(axis d)** `:42` (pre-edit) "voice-samples … **(REQUIRED before scripting)**" + `:90` "Match voice
against voice-samples/" had **no fallback** for a fresh adopter with an empty/absent samples dir —
"REQUIRED" reads as a hard block / dead-end. Same failure class as S3 (WebFetch-no-extraction) / S4
(empty-source).
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** 8-step workflow delivers the description's promise (script + pacing +
cues + captions + thumbnail + 200400 caption + first comment + refine + save + state).
- **(b) algorithm bar — PASS.** Muted-autoplay correctly de-mythologized (`:100`, "three-second hook =
folklore; LinkedIn's only official 3 s = minimum length"); captions = the enforceable spec (`:120`);
60 s sweet spot; 2.5-wps budgets exact (`:64/:66`, 75/30 = 150/60 = 225/90 = 300/120 = 2.5). *Honesty
note (deferred, operator-agreed):* `:69` "LinkedIn requires 30 % completion or **zero distribution**"
is more absolute than the declared SSOT `algorithm-signals-reference.md:56` (no 30 %, confidence
*medium*); it is consistent with `linkedin-formats.md` (cited at `:39`, l.58). Inter-reference tension,
NOT a video-command gap — deferred (brief Non-Goal: no re-litigation of the bar).
- **(c) MECHANICAL predicate (post-emitting, on the caption) — GAP → fixed.** no-body-link ✓ (`:125`) ·
topic→pillars ✓ (`:124` + Step 3) · caption band 200400 ✓ (`:121`) · subagent resolves ✓ (`:83/:131`) ·
**buzzword ✗** (grep: single hit `:106`, under Natural Speech = script not caption; caption De-AI gate
had no rule-#4 line). **hook 110140 deliberately not imposed** on the accompanying caption (class
boundary, operator-agreed; primary hook = muted-autoplay `:100`).
- **(d) agent-wiring + graceful degradation — wiring PASS; degradation GAP → fixed.** `video-scripter`
(`:83`) + `differentiation-checker` (`:131`, conditional) both resolve to real `agents/*.md`, both
`subagent_type: linkedin-studio:`-namespaced. **Graceful degradation:** the "REQUIRED before scripting"
voice-samples dead-end closed → fresh-adopter fallback to SKILL.md/defaults. (mcp-image n/a — video
generates no images.)
**HARDEN (surgical — 2 edits, 1 file, both grep-confirmed landed).**
- `commands/video.md:129` — **extended** the Step-5 De-AI caption gate with Content Quality Rule #4:
*"**Strip corporate buzzwords** from the post caption (… leverage, synergy, paradigm shift, thought
leader, disruptive, value proposition, ecosystem, holistic approach) — re-shaping a text idea into a
video caption can slip them in, and they trip the same low-substance signal."*
- `commands/video.md:42 + :45` — softened "(REQUIRED before scripting)" → "(read before scripting)" and
**added** a Step-0 graceful-degradation line: *"if `voice-samples/` is empty or absent, don't block —
fall back to the voice settings in SKILL.md (or neutral defaults) … the voice-guardian is suppressed
under 5 samples, so there is no hard gate to satisfy."*
- **Deferrals (2, recorded):** (1) hook 110140 on the caption — deliberate class boundary, not a gap
(operator-agreed); (2) `:69` "zero distribution" honesty — inter-reference tension, brief Non-Goal (no
re-litigation of the algorithm bar); leave to a future reference-consistency pass if desired.
- **CONCRETE after:** the "leverage…synergy…ecosystem" caption now trips the Step-5 buzzword line before
the script is presented; a fresh adopter with no voice samples now degrades to SKILL.md/defaults instead
of hitting a "REQUIRED" dead-end.
**VERIFY.**
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`, exit 0; counts **29/19/26/6**
unchanged (`ls commands/*.md|wc -l` == 29, `ls agents/*.md|wc -l` == 19; `git diff --stat` =
`video.md` only, +4/2). Only `.md` touched → hooks/analytics `node --test` not needed.
- Before/after delta: axis (c) buzzword sub-check now enforced at the load-bearing Step-5 caption gate;
axis (d) the voice-samples dead-end now degrades gracefully. The 2 edits are the cause.
- Disposition: **HARDENED** (2 edits, axis-c + axis-d) · 2 recorded deferrals (caption-hook boundary +
:69 honesty) · axes a/b PASS.
---
## Session 7 — Create II (`batch`)
> S7 status: re-grounded the proposed predicates against the actual `batch.md` lines BEFORE
> talk (jerndisiplin: assert-after-read). Field-notes inbox **absent** at S7 → graceful no-op (SC-I).
> All `file:line` below are tool-grounded against the FINAL (post-edit) file.
> **Class call (operator-agreed):** hybrid **guided/stateful + post-emitting per post** — each of
> the 35 drafts IS a feed post, so the post predicates apply *per post*; the orchestration around
> them (state-load, agent-delegation, scheduling, queue, `.ics`) is the guided/stateful layer.
### /linkedin:batch — one theme/pillar → 35 mixed-format feed posts, scheduled + queued + .ics
**INTENT.** `batch` is the Create journey's **volume path**: from one theme or pillar produce a full
week (35 posts) with varied angles + formats, each scheduled, queued (so it shows in
`/linkedin:calendar` + session-start reminders), and exported to a `.ics`. It is an **orchestrator** —
delegates angle discovery to `trend-spotter`, the balanced batch plan to `content-planner`, and owns
scheduling/queue/ical itself. Post-emitting per post, so each draft must honor the content bar
mechanically (hook **110140**, format-appropriate length band, no body link, no banned buzzword,
topic→5 pillars); guided/stateful for the orchestration (subagent targets + node helpers resolve;
graceful degradation).
**SIMULATE.**
- **Persona (ICP):** the author — 1048 followers, "Validation", 5 pillars; direct/technical NO/EN voice.
"Sunday prep": one theme → a week of **mixed-format** posts (standard → carousel → quick → video rotation).
- **Invocation:** `/linkedin:batch` → "One main theme" → 35 angles.
- **Walk:** Step 0 (`:23`) load state/SKILL/plan/templates → Step 1 (`:33`) theme/pillar (+ `trend-spotter`
for timely angles) → Step 2 (`:45`) `content-planner` plan + schedule (slot template by `weekly_goal`,
queue-conflict check `:58`) → **Step 3 per post: 3a draft (`:86`) → 3b Quick Quality Check (`:94`, the
gap) → 3c save (`:101`) → 3d queueAdd (`:124`)** → Step 4 review (`:130`) → Step 5 finalize + `.ics` (`:172`).
- **CONCRETE friction (spec-level, anchored — not a generated batch):** at **Step 3b** the per-post check
`:95` read **"Character count in range"** — **no band named** (range of *what?*), so it is unenforceable;
and the Step 3a "standard structure" (`:8892`: hook 110140 + context 200300 + insight 400800 +
implication 200300 + CTA 50100) **sums to 9601640**, whose floor sits **below** the canonical standard
band **1,2001,800** (CLAUDE.md rule #2). A "standard" post drafted to the structure can land ~1,000 chars
and pass `:95` "in range" unflagged. Complication: batch is **mixed-format**, so the band is
format-dependent — imposing 1,2001,800 on everything would wrongly fail a quick post (the S4 category
error in miniature). **Buzzword + no-body-link were NOT gaps here** (`:98` + `:97` already present — the
hole S2S6 kept finding is already closed in `batch`).
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** Theme→plan→schedule→per-post draft/check/save/queue→review→finalize+
`.ics` delivers the week-of-content promise. *Tension (deferred):* Step 3a imposes one "standard structure"
though Step 2 (`:76`) promises format variety — `batch` is an orchestrator, so carousel/video inherit their
own formats' rules; recorded, not edited.
- **(b) algorithm bar — PASS.** No body link (`:97`); buzzword-clean (`:98`); pillar-aligned scheduling
(`scheduling-strategy.md`); 5x5x5 reminder (`:169`). `batch` uses a deliberately lighter "Quick Quality
Check" (no per-post `differentiation-checker`) — breadth-over-depth by design for a week-at-once tool; *note,
not a fix.*
- **(c) MECHANICAL predicate (post-emitting, per post) — GAP → fixed.** hook 110140 ✓ (`:88`) · no-body-link
✓ (`:97`) · buzzword ✓ (`:98`) · topic→pillars ✓ (`:39/:77/:112`) · subagent resolves ✓ (`:43/:47`) ·
**length band ✗** (`:95` "in range" named no band; 3a structure sums 9601640, under the 1,200 standard floor).
- **(d) agent-wiring + graceful degradation — wiring PASS; PASS.** `trend-spotter` (`:43`) + `content-planner`
(`:47`) both resolve to real `agents/*.md`, both `subagent_type: linkedin-studio:`-namespaced. All 5 node
helpers resolve with matching signatures: `queueUpcoming(days=7)` / `queueAdd(…8 params)` (batch passes 8 args)
/ `queueFormatSummary(entries)` (queue-manager.mjs `:52/:63/:112`), `generateIcalFromQueue` / `writeIcalFile`
(ical-generator.mjs `:183/:201`). Degradation: no-plan→`:31` "create one first"; empty queue→`:181` guard.
*Minor (deferred):* no fallback if `weekly_goal` is unset (`:52` slot-template lookup) — micro, below axis-pass.
**HARDEN (surgical — 1 edit, 1 file, grep-confirmed landed).**
- `commands/batch.md:95` — `- Character count in range` → `- Character count in the band for this post's
format (standard 1,2001,800 · quick 150500 · carousel/video caption per that format) — not just "in range"`.
- **Deferrals (3, recorded):** (1) Step 3a single-structure vs mixed-format promise (orchestrator inherits
per-format rules); (2) no per-post differentiation gate (deliberate breadth-over-depth design); (3)
`weekly_goal`-unset fallback (micro-degradation). All below the stopping-rule's axis-pass bar.
- **CONCRETE after:** a ~1,000-char "standard" draft now trips the format band (under the 1,200 floor) instead
of passing the vague "in range"; a quick post is judged against 150500, not wrongly against 1,2001,800.
**VERIFY.**
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`; counts **29/19** confirmed
(`ls commands/*.md|wc -l` == 29, `ls agents/*.md|wc -l` == 19; `git diff --stat` = `batch.md` only,
1 insertion / 1 deletion). Only `.md` touched → hooks/analytics `node --test` not needed.
- Anti-check grep: bare vague `^- Character count in range$` → NONE (gone); format-band line present (`:95`).
- Before/after delta: axis (c) length-band sub-check now enforced + format-aware at the load-bearing Step-3b
gate. The 1 edit is the cause.
- Disposition: **HARDENED** (1 edit, axis-c) · 3 recorded deferrals · axes a/b/d PASS.
---
## Session 8 — Pipeline (`pipeline`)
> S8 status: re-grounded the proposed predicates against the actual `pipeline.md` lines BEFORE
> talk (jerndisiplin: assert-after-read). Field-notes inbox **absent** at S8 → graceful no-op (SC-I).
> All `file:line` below are tool-grounded against the FINAL (post-edit) file.
> **Class call (operator-delegated → quality + precedent):** hybrid **guided/stateful + post-emitting
> on the final post** — the 8-step idea→publish→analysis pipeline produces ONE feed post (Step 2 draft
> `:5762` → Step 3 gate `:6686` → Step 6 publish `:135`), so the post predicates apply to that post;
> the orchestration around it (Step 0 load · Step 1 agent-deleg · Step 4 schedule/queue · Step 5 5x5x5 ·
> Step 7 first-hour · Step 8 analysis · state-update `:186199`) is the guided/stateful layer.
### /linkedin:pipeline — full idea→publish→analysis pipeline, one 1,2001,800-char feed post, 8 steps
**INTENT.** `pipeline` is the Create journey's **end-to-end path**: from idea (or `$ARGUMENTS` topic)
through ideation → draft → optimize → schedule → 5x5x5 pre-engagement → publish → first-hour → 48h
analysis, with a deterministic state update. Post-emitting on the final post (hook **110140**, length
**1,2001,800**, no body link, no banned buzzword, topic→5 pillars); guided/stateful for the
orchestration (subagent targets + 5 node helpers resolve; graceful degradation).
**SIMULATE.**
- **Persona (ICP):** the author — 1048 followers, "Validation", 5 pillars; direct/technical NO/EN voice.
- **Invocation:** `/linkedin:pipeline about AI-kodeassistenter i offentlig sektor` (topic in `$ARGUMENTS`
→ Step 1 ideation skipped, `:41`).
- **Walk:** Step 0 load state/SKILL/voice → Step 1 skipped → Step 2 angle + draft to the structure
(`:5762`) → **Step 3 optimize gate (`:6686`, the gap)** → Step 4 schedule (+ Option-3 queue) →
Step 5 5x5x5 → Step 6 publish+clipboard → Step 7 first-hour → Step 8 analysis → state.
- **CONCRETE friction (spec-level, anchored — not a generated post):** at **Step 3** the inline quality
scorecard (`:7581`) carried the buzzword line (`:79`) but as a **bare** `- [ ] No corporate buzzwords`
— no inline enumeration; it leaned on the scorecard reference (`:75` → `quality-scorecard.md:132`). A
draft opening *"How to leverage AI to drive synergy across the public-sector ecosystem…"* relies on the
operator cross-referencing a long shared asset rather than the inline gate the AI actually executes.
**Unlike S2/S3/S5/S6 the line was PRESENT** — the gap was that it was not self-contained, making
pipeline the odd-one-out among post-emitting commands (post `:134` · react `:131` · carousel `:123` ·
video `:129` all enumerate inline).
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** 8-step idea→publish→analysis delivers the description's promise.
- **(b) algorithm bar — PASS.** Step 3 signals (`:7073`) cited to the SSOT (saves rank highest · a
15+-word comment ≈ 2× a like · dwell >30 s +25 %); no body link (`:78`); 5x5x5 (`:120133`); first-hour
(`:158169`). No 30 %-claim → untouched by the open reference-consistency follow-up.
- **(c) MECHANICAL predicate (post-emitting, on the final post) — GAP → fixed.** hook 110140 ✓
(`:58/:76`) · length band ✓ (`:77` names 1,2001,800) · no-body-link ✓ (`:78`) · topic→pillars ✓
(`:80` + `:49` content-planner pillar-situating) · subagent resolves ✓ (`:49`) · **buzzword
present-but-not-self-contained ✗→fixed** (`:79` bare → enumerated inline).
- **(d) agent-wiring + graceful degradation — PASS.** `content-planner` + `trend-spotter` (`:49`) resolve
to real `agents/*.md`, both `subagent_type: linkedin-studio:`-namespaced. All 5 node helpers export with
matching signatures: `queueUpcoming(days=7)` (qm`:52`) · `queueAdd(…8 params)` (qm`:63`; pipeline passes
8 args `:116`) · `queueFormatSummary` (qm`:112`) · `writeState` (su`:346`) · `updatePostTracking`
(su`:55`). Degradation: Step 1 (`:4347`) handles no-planned-topic/no-input via AskUserQuestion; Step 0
voice-samples is a soft read (no "REQUIRED" dead-end).
**HARDEN (surgical — 1 edit, 1 file, grep-confirmed landed).**
- `commands/pipeline.md:79` — `- [ ] No corporate buzzwords` → `- [ ] No corporate buzzwords (leverage,
synergy, paradigm shift, thought leader, disruptive, value proposition, ecosystem, holistic approach)`
(same banned list as the other post-emitting gates + the scorecard).
- **Deferral (1, recorded):** the Step-2 component structure (`:5862`: hook 110140 + context 200300 +
insight 400800 + implication 200300 + CTA 50100) sums **9601 640**, whose floor sits below the
Step-3 band floor 1,200 (`:77`) — **identical structure to batch's 3a (`:8892`)**. NOT edited:
pipeline's Step-3 gate already NAMES the band (the S7 fix was naming the band on an *unenforceable* gate;
here it is already named), so a minimum-path draft is caught by the gate, not shipped — and S7 did not
edit the analogous 3a structure either. Editing Step 2 here would exceed precedent (over-engineering).
Below the axis-pass bar.
- **CONCRETE after:** the "leverage…synergy…ecosystem" draft now trips the Step-3 buzzword line inline
before optimize completes, without the operator cross-referencing the shared scorecard.
**VERIFY.**
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`; counts **29/19** confirmed
(`ls commands/*.md|wc -l` == 29, `ls agents/*.md|wc -l` == 19; `git diff --stat` = `pipeline.md` only,
1 insertion / 1 deletion). Only `.md` touched → hooks/analytics `node --test` not needed.
- Before/after delta: axis (c) buzzword sub-check is now self-contained at the load-bearing Step-3 gate
(was a bare, reference-dependent line). The 1 edit is the cause.
- Disposition: **HARDENED** (1 edit, axis-c) · 1 recorded deferral (Step-2 component-sum, gate-caught) ·
axes a/b/d PASS.
---
## Session 9 — Longform orchestrator (`newsletter`)
> S9 status: re-grounded STATE's 7 S9 predicates against the actual `newsletter.md` (1594 lines, 16
> phases) BEFORE talk (jerndisiplin: assert-after-read). Class agreed S9 steps 12: **guided/stateful
> long-form orchestrator, NOT post-emitting** — the feed-post predicates (hook 110140, 1,2001,800
> band, no-body-link) do NOT apply; imposing them would be the S4/video category error the command
> itself already rejects (`:3842`). All `file:line` below are tool-grounded against the FINAL file
> (no edits this session → final == 52e888a).
### /linkedin:newsletter — long-form edition end-to-end, 16-phase stateful pipeline, NOT post-emitting
**INTENT.** `newsletter` is the Longform orchestrator: take an edition (newsletter / essay / series
article) from load → calibration → verified research → skeleton+spine gates BEFORE prose → full draft
→ consistency/quality+voice-scrub → fact-check → editorial craft gate → persona sweep → headless
adversarial review → visual assets → LOCK (POST.html) → hook-gate → scheduling. Multi-session with a
maintained `edition-state.json`. Long-form quality is enforced by the **pipeline phases**, not
short-form hooks.
**SIMULATE (grounded persona-sim, anchored to Step 6.5 headless fan-out).**
- **Persona (operator):** KTG producing a series edition; primær reader chosen in Step 1.
- **Walk:** Step 0 load (voice/persona graceful `:258`) → … → Step 6 persona sweep PASS → **Step 6.5
freeze → fan out 5 cold archetypes** (`:10621069`, single message, all `linkedin-studio:`-namespaced,
foreground per principle 4; background loses Task `:79`) → **degradation gate** (`:10841086` "do not
proceed with a missing reviewer `[GATE]`") → consolidate via `SendUserFile` → Step 7.5 visual assets
(mcp-image not a hard dependency, external `cover-raw.png` fallback `:12221230`) → Step 8 LOCK
(`build-linkedin.mjs` → `linkedin/NN/POST.html`, exit-code N3 `:1388`) → Step 10 `queueAdd` (`:1486`).
- **Friction sought (the S2S8 pattern: a missing buzzword gate / a missing fallback):** **NONE found.**
The category-correct equivalent of the short-form buzzword gate is the de-AI/voice-scrub + editorial +
language-reviewer gates, all present and wired. Every fan-out has a structured-return degradation gate.
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** The 16-phase pipeline delivers the description's promise; primær
artifact `POST.html` is produced end-to-end at Step 8 lock (`:13791395`) with exit-code verification.
- **(b) algorithm/format bar (long-form honesty, NOT feed-post predicates) — PASS.** Native-newsletter §
(`:15081569`) is nøktern: "sells nothing", notification dedup not oversold ("not three guaranteed
touchpoints"), cold-start not inflated ("0→9K in 7 days were not cold starts"), downsides disclosed
(non-exportable / no-canonical / no-analytics / decay), sourcing note (`:1564`) separates LinkedIn
help-docs (high conf.) from practitioner ranges (medium) + flags the contested email-dedup; citation
`docs/remediation/research/03-coverage-gap-specs.md` §D5 exists (225 lines, §D5 ×8 — not dangling).
- **(c) quality rules (category-appropriate) — PASS.** Long-form quality is enforced by the pipeline
gates (skeleton 2.5 · spine 3a · consistency+voice-scrub 4 · fact-check 5 · editorial 5.5 · persona 6
· headless 6.5 · hook 9), all present + wired. Short-form hooks correctly NOT imposed (`:3842`); no
hollow gate found.
- **(d) agent-wiring + graceful degradation — PASS.** All 8 invoked subagents resolve to real
`agents/*.md`, all `subagent_type: linkedin-studio:`-namespaced (persona-reviewer ×5, fact-checker ×2,
voice-scrubber, language-reviewer, fact-reviewer, editorial-reviewer, content-reviewer,
content-repurposer). Render/queue helpers exist; `queueAdd(id, draftPath, schedDate, schedTime,
pillar, format, hookPreview, charCount)` 8-arg signature (qm`:63`) matches the call (`:1486`). All 9
plugin-root ref/template files (`:15751594`) exist. Phase strings: `_doc.phases` (16) ↔ resumption
table (16) `diff` IDENTICAL. Degradation: voice/persona `:258` · mcp-image `:12221230` · exit-codes
×4 (`:443/580/1144/1388`) · optional config+delingstekst `:13731377` · fan-out gates ×3
(`:340/789/1086`).
**HARDEN — NO CHANGE (verified clean).**
- 0 edits. `newsletter` is among the most iterated commands (v2.0→v4.1 hardened the long-form gates
across multiple production runs: Seres, Del 4 / Endring 9), and it shows: every axis passes with no
surgical opportunity at or below the axis-pass bar.
- **Deferrals:** none. **Anti-gold-plating:** no NICE-only polish pursued beyond axis-pass (stopping
rule, brief §). The native-newsletter follow-up noted in S2 STATE (reference-consistency for the
separate `video.md`/`linkedin-formats.md` 30 % surface) is NOT this command's surface — left in the
open-follow-ups list, not touched here.
**VERIFY.**
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`; counts **29/19/26** (+6
skills) unchanged; `git status --porcelain` empty → working tree byte-identical to `52e888a` (S8).
- Phase-string diff reproduced: `_doc.phases` ids vs resumption-table `currentPhase` ids → IDENTICAL,
16↔16, in order.
- Disposition: **VERIFIED — NO CHANGE** (0 edits) · 0 deferrals · axes a/b/c/d all PASS.
---
## Session 10 — Longform companion (`headless-review`)
> S10 status: re-grounded STATE's predicates against the actual `headless-review.md` (248 lines, 13 KB)
> BEFORE talk (jerndisiplin: assert-after-read). Class agreed S10 steps 12: **cold adversarial
> review-orchestrator, NOT post-emitting** — it emits a consolidated review report + optional
> `edition-state` persistence, never a feed post and never drafts prose; the feed-post predicates
> (hook 110140, 1,2001,800 band, no-body-link) do NOT apply — imposing them would be the S4/video
> category error (even further from feed-post than `newsletter`). The four archetype agents are
> SHARED with `newsletter` Step 6.5 → S9 agent findings reused as baseline. All `file:line` below are
> tool-grounded against the FINAL file (no edits this session → final == 1c31644).
### /linkedin:headless-review — cold adversarial review package on a FROZEN long-form draft, NOT post-emitting
**INTENT.** `headless-review` is the standalone surface of `newsletter` Step 6.5: read a frozen,
publish-ready (or pivoted) long-form draft COLD — with NONE of the drafting session's framing-bias —
through five independent archetypes and return direction-only flags, consolidated into one
operator-gated report. Two isolation layers: fresh session (strongest, `:6468`) + per-archetype
subagent context (`:6974`). The adversarial-independence layer the in-session gates structurally
cannot be (`:3048`).
**SIMULATE (grounded persona-sim, anchored to the Step 3 cold fan-out).**
- **Persona (operator):** KTG in a FRESH session running `--type all` on `<serie>/NN-utkast.md`.
- **Walk:** Step 1 resolve-from-disk (draft → edition-state `currentArticle` fallback → ask-once
`:103104`; contract 3-tier fallback ending at always-present `longform-quality-rules.md` `:111114`;
personas = newsletter Step 1 chain `:116118`) → Step 2 freeze (`cp` snapshot `:126`, live-draft
fallback if `cp` unavailable `:130131`) → **Step 3 fan out 5 archetypes** (single message,
`:135136`; 4 `linkedin-studio:`-namespaced types, persona-reviewer ×2 modes `:141146`) →
**degradation gate** (`:161164` "re-run that one archetype — do not paper over a missing reviewer
`[GATE]`") → Step 4 consolidate (operator gates, never auto-resolve flags `:168169`/`:196197`;
convergence marked `:200202`) → Step 5 surface via `SendUserFile` (else `file://` `:208210`) +
optional persist `articles.NN.headlessReview` (`:211215`).
- **Friction sought (the S2S8 pattern: a missing buzzword gate / a missing fallback):** **NONE found.**
Not post-emitting → the category-correct quality bar is the reviewers grading against the writing
contract + the structured-return degradation gate, both present. Every fan-out call has a degradation
re-run path.
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** The 5-step flow (resolve → freeze → parallel fan-out → consolidate
→ surface/persist) delivers the description's promise; primær artifact is the consolidated report
(`:171198`), surfaced + optionally persisted (`:204215`). `allowed-tools` (`:1826`) complete and
fully used (Read/Glob/Grep · Bash freeze · AskUserQuestion ask-once · Task fan-out · Write report+state).
- **(b) algorithm/format bar (long-form honesty, NOT feed-post predicates) — PASS.** Cold contract
(`:5074`) nøktern: reviewers get a "deliberately starved context", exactly-what-they-get /
exactly-what-they-don't enumerated; two-layer isolation with honest caveats (Layer 1 "recommended …
strongest" not absolute; reload caveat for the v3.1.0 agents `:8082`). Deliberate redundancy
(`fact-reviewer`↔`fact-checker`, `language-reviewer`↔`editorial` prose axis) disclosed as
on-purpose, not collapsed (`:234237`). No feed-post predicate imposed.
- **(c) quality rules (category-appropriate) — PASS.** Review quality enforced by (1) reviewers judging
against the writing contract (`:111114`), (2) the degradation gate requiring structured/populated
returns, not a hedged paragraph (`:161164`), (3) operator-gates-the-fold-in — the command recommends,
never rewrites/gates (`:168169`/`:196197`/`:210`), (4) convergence-as-signal (`:200202`). No hollow
gate found.
- **(d) agent-wiring + graceful degradation — PASS.** 4/4 invoked subagent types resolve to real
`agents/*.md` with matching frontmatter `name:`, all `subagent_type: linkedin-studio:`-namespaced
(content-reviewer · language-reviewer · fact-reviewer · persona-reviewer ×2 modes → "five
archetypes" `:56`/`:3031`). 8/8 Reference Files (`:241248`) exist. `articles.NN.headlessReview`
schema present + complete in `edition-state.template.json` (`:5669`: frozenDraft · reviewers
{content,language,fact,personaResonance,personaConversion} · consolidatedReport · foldedIn · waived ·
status; ladder pending→run→folded `:29`). Degradation ×5: freeze `cp` fallback `:130` · draft-missing
ask-once `:104` · persona fallback `:117` · `[GATE]` re-run `:163164` · output-default `:93` +
SendUserFile-else-`file://` `:208210`.
**HARDEN — NO CHANGE (verified clean).**
- 0 edits. Borderline surfaced at talk: the freeze step (`:126`) assumes `review/` exists (no
`mkdir -p`). **Operator chose Alt A (no change).** Rationale: the freeze block is **byte-identical to
`newsletter` `:1050`** (S9-verified-clean), and `review/` is created by the render pipeline that
produces the draft (`build-html.mjs` → `./review/` newsletter `:1136`; skeleton/draft render
`:444`/`:580`); adding `mkdir -p` only here would drift from its S9-clean twin, and fixing both would
re-open S9 (out of S10 scope). The `cp`-unavailable fallback (`:130`) covers the degraded path loosely.
- **Deferrals:** none. **Anti-gold-plating:** the `mkdir -p` robustness improvement is a NICE below the
axis-pass bar — left as the operator's logged decision (Alt A), not pursued.
**VERIFY.**
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`; counts **29/19/26** (+6
skills) unchanged (count/version-consistency guards inside the lint passed); `git status --porcelain`
empty → working tree byte-identical to `1c31644` (S9).
- Predicates re-grounded against the final file: 4 namespaced subagent types (frontmatter `name:`
match) · 8/8 ref files exist · `headlessReview` schema `:5669` · 5 degradation points · persona
chain = newsletter `:179183` · "five archetypes" (wrapped) = 5 modes from 4 agents.
- Disposition: **VERIFIED — NO CHANGE** (0 edits) · 0 deferrals · axes a/b/c/d all PASS.
---
## Session 11 — Longform companion (`pivot`)
> S11 status: re-grounded STATE's predicates against the actual `pivot.md` (162 lines) BEFORE talk.
> Class agreed steps 12: **pure state-mutation / orchestration command, NOT post-emitting, invokes NO
> agents, never drafts prose.** It re-opens a long-form edition after a late substantive change so the
> cleared gates re-run before lock — mutating `edition-state.json` (append `pivots[]`, reset
> `currentPhase`, un-lock, invalidate downstream verdicts), writing a next-step line to `<serie>/STATE.md`,
> and deferring the actual gate-runs to `/linkedin:newsletter` (`:144`). Feed-post predicates
> (hook 110140, 1,2001,800 band, no-body-link) do NOT apply — even further from feed-post than
> `headless-review` (this one does not even produce a review of prose). The pivot heuristic + `pivots[]`
> schema + gates-to-rerun chain are SHARED with `newsletter` (Step 8) and `headless-review` →
> S9/S10 findings reused as baseline. **Unlike S9/S10 this was NOT a no-change session — P4 surfaced a
> real cross-file off-by-one bug.**
### /linkedin:pivot — re-open a cleared edition for re-review, NOT post-emitting
**INTENT.** Make the post-lock re-open a NAMED RITUAL, not a manual scramble (the Del 4 Security
Champions case, `:3338`): log the pivot, reset `currentPhase` so newsletter's deterministic resumption
re-runs the invalidated gates (fact-check 5 → editorial 5.5 → persona 6 → headless 6.5), un-lock if
needed, and invalidate the stale downstream verdicts so a pre-pivot PASS cannot be mistaken for current.
**SIMULATE (grounded persona-sim).**
- **Persona (operator):** KTG having made a late change to a LOCKED edition, running
`/linkedin:pivot --article NN --reason "<...>"`.
- **Walk:** Step 1 load state + locate article (missing → stop+report `:75`) → Step 2 measure scope
(`wc -w` current vs baseline `personaSweep.resonance.wordCount`; baseline-absent → ask/default
structural `:8486`; ambiguous sections → confirm `:90`) → **Step 2 classify → default `--to-phase`**
→ Step 3 append `pivots[]` + reset `currentPhase` + un-lock + invalidate verdicts (`:100131`) →
Step 4 write next-step line to `<serie>/STATE.md`, defer gates to newsletter (`:133155`).
- **Friction sought (the S2S8 pattern):** **FOUND — a phase-reset off-by-one (P4).**
**EVALUATE (4 axes — re-tolket for a non-post-emitting orchestrator).**
- **(a) intention fidelity — PASS after fix.** The ritual (log → reset → un-lock → invalidate → point)
delivers the promise; `allowed-tools` complete and used. The off-by-one (below) made the *moderate*
branch silently betray the stated intent ("so the new claims get verified") → fixed.
- **(b) format bar (heuristic-consistency, NOT feed predicates) — PASS.** Three-way SSOT verified
identical: pivot heuristic (>20 % words OR >2 sections, baseline = `personaSweep.resonance.wordCount`)
== newsletter Step 8 lock-precondition (`:13481362`) == template `_doc.pivots` (`:30`). Newsletter
Step 6 actually writes the baseline field (`:990994`, `:1005`) → not a phantom. Cardinal rule
("never reopen a locked text", newsletter `:10341036`) honoured: pivot is the *sanctioned* exception
— it loudly un-locks + flags POST.html stale (`:124`) + invalidates verdicts (`:126130`).
- **(c) quality rules (state-integrity) — PASS.** `gatesToRerun` `:113` = the 4 real gate phase-names;
invalidation targets (`personaSweep.resonance` · `editorialReview` · `headlessReview.status`) all
exist in the schema; `factcheckLog` + `pivots[]` left intact (durable history `:130`).
- **(d) agent-wiring + graceful degradation — PASS.** No agents (orchestrator). 3 degradation points
enumerated (article-missing `:75` · baseline-absent `:8486` · ambiguous-sections `:90`). 3/3 ref
files exist.
**THE BUG (P4 — confirmed cross-file off-by-one).** Newsletter's resumption table is explicit
(`:196198`, `:203221`): `currentPhase` = the *last completed* phase, and the pipeline resumes at the
step **after** it (`draft`→Step 4, `consistency-quality`→Step 5, `factcheck-sweep`→Step 5.5). But
pivot.md set the **moderate** default to `to-phase: factcheck-sweep` "so the new claims get verified" —
which resumes at **Step 5.5 (editorial), SKIPPING the Step 5 fact-check** the moderate pivot exists to
run. Self-contradictory, and a violation of the command's own invariant that "a pivot always invalidates
the fact-check" (`:116119`). The **structural** branch (`to-phase: draft`→Step 4→fact-check at 5) was
functionally safe but mis-labelled "(Step 3b)". Root cause: conflating "the phase to resume AT" with the
`currentPhase` (last-completed) value to write.
**THE FIX (4 surgical edits, confined to command-anatomy + Step 2).**
1. Moderate default `:9697`: `factcheck-sweep` → **`consistency-quality`** (resumes Step 5, fact-check
runs) — the bug.
2. Structural label `:9295`: "(Step 3b) … full prose expansion" → "resumes **Step 4
(consistency-quality)** … then fact-check (5) → editorial (5.5) → persona (6) → headless (6.5)".
3. Off-by-one guard callout `:102108`: explains `to-phase` is a `currentPhase` (last-completed) value
and newsletter resumes at the NEXT step → why a pivot never resets to `factcheck-sweep`.
4. Anatomy enum `:46`: dropped `factcheck-sweep` from the offered values (it skips fact-check, violating
the always-invalidate-fact-check invariant) → `draft | consistency-quality`.
`gatesToRerun` `:113` and the worked example `:6470` (structural, `draft`) left intact — both correct.
**VERIFY.**
- Re-grepped final file: `factcheck-sweep` now only at `:106` (guard, as the forbidden value) + `:123`
(gatesToRerun gate-name); to-phase defaults = `draft` (`:93`) / `consistency-quality` (`:97`); enum
`:46` = two values; guard landed `:102`.
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`; counts **29/19/26** unchanged
(count/version/model-consistency guards passed; .md-only edit, node/analytics tests not triggered).
- **One NICE left (anti-gold-plating):** template `:30` says "+~530 words" (sum of the 2 named sections)
vs worked-example 1400→1992 = +592; both "~", both +42 % — cosmetic, below the axis-pass bar, left.
- Disposition: **FIXED** (4 edits, all in `commands/pivot.md`) · 0 deferrals · axes a/b/c/d all PASS
post-fix.
---
## Session 12 — Hardening (`firsthour`)
> S12 status: re-grounded STATE's predicates against the actual `firsthour.md` (119 lines) BEFORE talk.
> Class agreed steps 12: **post-publish engagement orchestrator — first-hour / reply-loop sprint.
> NOT post-emitting / feed-draft.** It builds a first-hour plan (target list + draft comments + timeline)
> by DELEGATING construction to `engagement-coach` (Step 2, `Task`), presents + auto-copies the draft
> comments (Step 3), persists via `recordFirstHourPlan` (Step 4), and hands off to `post-feedback-monitor`
> (Step 5). It emits COMMENT copy, not a feed post → the feed-format predicates (hook 110140, 1,2001,800
> band, no-body-link) do NOT apply; the inline comment band (2550 words, voice, no bait, no generic
> praise) is its own surface. Axis-d heavy: 2 agents + a state-writer + a hand-off chain.
**WHAT VERIFIED CLEAN (no change).**
- Agent wiring (axis-d): both `subagent_type: linkedin-studio:`-namespaced. `engagement-coach` frontmatter
= name / model:sonnet / color:magenta; `post-feedback-monitor` = name / model:opus / color:lime — both
match the CLAUDE.md table exactly.
- State-writer (axis-d): `recordFirstHourPlan` exported (`state-updater.mjs:235`), additive, `$`-safe
(function-replacements), stamps `last_firsthour_date` + `firsthour_active`, writes `## First-Hour Plans`
— exactly as Step 4 claims. `writeState` / `clipboard-helper.mjs` / both ref files present.
- Delegation sound (axis-d): CEA / 5x5x5 / first-hour / target-selection genuinely owned by
`engagement-coach.md` (whales / inner-circle, velocity table). Hand-off coach→monitor present (Step 5).
- Axis-c: the draft-comment band is consistent with the SSOT AI-slop suppression + the 15+-word floor.
- Principle 2 "comments rank above reactions" (`:111`) IS supported — SSOT `:38`
`saves > shares > quality comments > reactions/likes` + `:160`. (My initial narrow grep was a false
negative — withdrawn.)
**THE FIX (2 findings, 3 surgical edits, confined to prose + footer).**
1. [REWORK · axis-b] `~70%`-stat MISATTRIBUTED to "the first 60 min". The command (desc `:4`, body `:22`)
said the first hour / "first 60 minutes" decide ~70% of reach. SSOT `algorithm-signals-reference.md:81`
pins ~70% to the **first 1530 min** sub-window (golden window = 6090 min); the command even cites
that SSOT (`:118`) → it contradicted its own source. Fix: desc + body re-attributed to "first 1530
minutes decide ~70%", framing the hour as the golden window you work. (Operational guidance was already
front-loaded — the 5/15/30/60 velocity checkpoints were left unchanged.)
2. [REWORK-low · axis-a] Reference-label: `engagement-frameworks.md` does NOT contain CEA. The footer
labelled it "hook types, CEA, engagement hierarchy" — but CEA lives in `algorithm-signals-reference.md:162`
+ `engagement-coach.md`; the ref carries hook types + "Engagement Quality Hierarchy" only. Fix: dropped
"CEA" from the footer line (CEA already covered by the Step 2 coach-delegation + the SSOT pointer).
**VERIFY.**
- Re-grepped the final file: `~70%` is now attributed to "1530 minutes" at desc `:4` + body `:22`; no
"first 60" / "60 minutes set" attribution remains (`:66` "5/15/30/60-minute" is the velocity-checkpoints
table, unchanged). CEA now only at `:6 / :44 / :54 / :65 / :112` (desc + Step 2 + Step 3 + principle),
GONE from the footer; footer `:118` = "hook types, engagement hierarchy".
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`; counts **29/19/26** unchanged
(count / version / model / stat-consistency guards passed; .md-only edit, node/analytics tests not
triggered).
- **One NICE left (anti-gold-plating):** no degradation note for an `engagement-coach`-Task failure /
absent voice-samples (thin orchestrator, fully coach-dependent) — consistent with the suite's posture,
below the axis-pass bar, left.
- Disposition: **FIXED** (3 edits, all in `commands/firsthour.md`) · 0 deferrals · axes a/b/c/d all PASS
post-fix.
---
## Session 13 — Hardening (`calendar`)
> S13 status: read-and-show of every mechanical dependency against the actual `queue-manager.mjs` +
> `state-updater.mjs` BEFORE talk. Class agreed steps 12: **stateful queue / publish command** —
> view + manage the scheduling queue + run the publish action. **NOT post-emitting / feed-draft.**
> It loads the queue (`queueToday/Upcoming/Overdue/Count/FormatSummary`), renders a 14-day view,
> offers actions (mark-published / reschedule / cancel / view-draft), and on publish does
> `queueUpdateStatus('published')` → `updatePostTracking` (state) → a quick first-hour plan that
> defers the full sprint to `/linkedin:firsthour` + `post-feedback-monitor`. It DISPLAYS hook previews
> of already-queued drafts → it emits no feed content → the feed-format predicates (hook 110140,
> 1,2001,800 band, no-body-link, buzzwords, rotation) do NOT apply. Axis-a/d heavy.
**WHAT VERIFIED CLEAN (no change).**
- Queue wiring (axis-a/d): Step 1 (`queueToday/Upcoming/Overdue/Count/FormatSummary`), 3c + Cancel
(`queueUpdateStatus`) all match exports in `queue-manager.mjs:41112` exactly.
- State-writer (axis-d): `updatePostTracking(stateContent, { postDate, postTopic, hookText, charCount,
format })` (`state-updater.mjs:55`) + `writeState` (`:346`) — signature matches Step 3d's call verbatim.
- Agent wiring (axis-d): `subagent_type: linkedin-studio:post-feedback-monitor` (`:156`) namespaced,
agent file present, `Task` in `allowed-tools`. Hand-off to `/linkedin:firsthour` (`:138`) = S12 chain.
- Graceful degradation (axis-d): missing/corrupt queue → `ensureQueue()` + `readQueue()` try/catch → `[]`
(`queue-manager.mjs:1227`); the "No posts scheduled" branch (`:106111`) covers the empty case.
- Algorithm bar (axis-b): the quick first-hour plan (`:151` "15+ engagements in first 60 min", check at
30/60) is deliberately light and delegates the full sprint to `/linkedin:firsthour`. No ~70%-reach
misattribution (the S12 bug is absent here). SSOT `:81` golden window 6090 min / first 1530 min — not
contradicted. PASS.
- Content-quality (axis-c): emits no feed content; the hook "preview" is a truncated display of an
existing draft, not a generated hook → feed predicates N/A → PASS.
**THE FIX (1 finding, 1 surgical edit, confined to the Reschedule action).**
1. [REWORK · axis-a/d] The **Reschedule** action (`:164170`) was under-determined vs its sibling actions:
prose-only with NO bash snippet (unlike Cancel `:177180` and publish 3c `:116118`), and it referenced
`queue_update_status + queue_add` — **snake_case that does not match the exports** `queueUpdateStatus` /
`queueAdd` (`queue-manager.mjs:63,82`). An agent executing it had no concrete command and the names
would not resolve if copied literally. Fix: replaced step 3 with a concrete `queueAdd` one-liner using
the **same id** (since `queueAdd` filters out the prior id first, `:64`, re-adding moves the post in
place — no duplicate), carrying the unchanged fields from the Step-2 entry. Mirrors the Cancel snippet
pattern.
**VERIFY.**
- Re-grepped the final file: no `queue_update_status` / `queue_add` remains; the `queueAdd` reschedule
snippet is at `:174`; every `queue*` reference in the file is now camelCase and matches an export.
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`, **exit 0**; counts **29/19**
unchanged (.md-only edit, node/analytics tests not triggered — unchanged from S12).
- **One NICE left (anti-gold-plating):** the quick first-hour plan could name the SSOT "first 1530 min"
sub-window, but it deliberately defers to `/linkedin:firsthour`; duplicating it would be scope creep —
left.
- Disposition: **FIXED** (1 edit in `commands/calendar.md`) · 0 deferrals · axes a/b/c/d all PASS post-fix.
---
## Session 14 — Hardening (`import`)
> S14 status: read-and-show of the analytics CLI (`csv-parser.ts`, `cli.ts handleImport`,
> `storage.ts`, `alerts.ts`) BEFORE simulate, then a GROUNDED run against a throwaway fixture
> (`ANALYTICS_ROOT=/private/tmp/…`, 5-row CSV, discarded after). Class agreed steps 12:
> **analytics ingest** — CSV export → structured weekly JSON via `cli.ts import`, delegating all
> analysis to `/linkedin:report` (Step 6). **NOT post-emitting** → the feed-format predicates
> (hook 110140, length band, no-body-link, buzzwords, rotation) do NOT apply. Axis-a/d heavy
> (path-seam consistency + graceful degradation + the over-promise).
### /linkedin:import — CSV export → structured weekly JSON, intra-batch anomaly surface, analysis delegated to report
**WHAT VERIFIED CLEAN (no change).**
- Path-seam (axis-a): `getAnalyticsRoot()` (`storage.ts:6772`) → `getDataRoot("analytics")`
(`:5459`) = `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/analytics` = the exact path
the prose tells the user to use (`import.md:37,62,91`); identical `LINKEDIN_STUDIO_DATA` override.
`ANALYTICS_ROOT` back-compat override (`storage.ts:68`) confirmed (validates the throwaway isolation).
- saves/dwell honesty (axis-c): grounded run → EU `"5.234"`→5234, US `"8,710"`→8710;
`parseOptionalCount` (`csv-parser.ts:7185`) kept saves 42/128 on data rows, **blank → no `saves`
field** (unknown, not 0); `engagementRate` excludes saves (`:203208`); **0 dwell field** anywhere.
README boundary blockquote (`import.md:2630`) intact.
- Wiring (axis-d): `/linkedin:report` → `commands/report.md` present; **0** `subagent_type`
(CLI-delegating, correct); state-file Step 6b = `~/.claude/linkedin-studio.local.md`.
- Degradation (axis-d): `npm install` pre-step (`:114121`) covers fresh-clone `ERR_MODULE_NOT_FOUND`;
file-not-found exit 1 (`cli.ts:6771`) ↔ Error Handling; empty title (file line 5) + bad date
(line 6) **skipped** in the run (`csv-parser.ts:186195`).
**THE FIX (1 finding, 5 surgical edits, all in `commands/import.md`).**
1. [REWORK · axis-a/d] **Over-promise: baseline / metadata infrastructure the code never had.**
import.md repeatedly promised baseline comparison + rolling 4-week averages + a `baselines.json` /
`metadata.json` the importer "creates" + ">2 sigma from baseline" alerts. Grep over the whole repo
(`scripts/analytics/src` + all `*.mjs/ts/py/js`) = **NONE writes either file**; `saveBatch` writes
only `posts/*.json` (`storage.ts:157`); `detectAlerts` (`alerts.ts:1869`) flags **intra-batch**
std-dev from the batch's own mean, not a stored baseline. Concrete bug: Step 5b's
`cat baselines.json 2>/dev/null` is always empty → the command falls forever to "first import —
baselines will be established", on every import. Fix: rewrote 5 sections to the truth — Step 5
(real CLI fields + `YYYY-MM-DD-<shortid>.json` filename; dropped "Duplicates skipped" / "340% above
baseline" / "Viral threshold"), Step 5b (honest intra-batch surfacing; cross-week analysis deferred
to `/linkedin:report`), Step 7 (Next-Steps conditioned on the report's trend, not a local baseline),
State Tracking + Reference Files (no `baselines.json` / `metadata.json`; correct batch filename).
**VERIFY.**
- Re-grepped the final file: `metadata.json` / `YYYY-WXX.json` (filename) / "Duplicates skipped" /
"340%" / "Viral threshold" all GONE; the only `YYYY-WXX` left is `:226` (`last_import_week` ISO-week
state field — legitimate); the 3 remaining "baseline" hits are the new honest negations ("keeps no
baseline file", "no persisted baseline").
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`, **exit 0**; counts **29/19**
unchanged (.md-only edit, node/analytics tests not triggered).
- Grounded simulation passes the failing axis: the rewritten prose matches the observed CLI output
verbatim (filename `2026-06-02-batch-17.json`, "No anomalies detected in imported data.",
"Saves entered: 170 across 2 post(s) (manual)").
- Disposition: **FIXED** (5 edits, all in `commands/import.md`) · 0 deferrals · axes a/b/c/d all PASS
post-fix.
---
### /linkedin:report — imported analytics → weekly/monthly/heatmap report, alerts surfaced, interpretation delegated to analytics-interpreter
**WHAT VERIFIED CLEAN (no change).**
- Path-seam (axis-a): `getAnalyticsRoot()` (`storage.ts:6772`) → `getDataRoot("analytics")` (`:5459`)
= the exact `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/analytics` paths the prose reads;
`ANALYTICS_ROOT` override confirmed (the grounded sim ran isolated in `/private/tmp`).
- CLI surface (axis-a): `report --week`/`--month`, `trends --period --metric`, `heatmap` all dispatch
(`cli.ts:447459`); grounded sim produced verbatim weekly report + trends + heatmap on a 3-row fixture.
- Wiring (axis-d): `subagent_type: linkedin-studio:analytics-interpreter` (`report.md:305`) resolves —
`agents/analytics-interpreter.md` present with report-mode (`:10,40,295`); one namespaced `Task`.
- Degradation (axis-d): `trends`/`heatmap` "No posts found. Import some data first." exit 1
(`cli.ts:245248,397400`); weekly report try/catch exit 1 (`:191194`); Step 1 import-nudge;
npm-install Step 1b for fresh-clone `ERR_MODULE_NOT_FOUND`.
- saves/dwell honesty (axis-c): `totalSaves` surfaced only when present, labelled "(manual entry — top
engagement signal)" (`cli.ts:144146,342344`; `weekly.ts:172173`); grounded run — FIXTURE B's blank
Saves cell produced **no** saves suffix (unknown≠0); never folded into engagement. **dwell** absent,
explicit "do not fabricate a dwell field" (`types.ts:2628`).
**THE FIX (3 findings, 6 surgical edits, all in `commands/report.md`).**
1. [REWORK · axis-a/d] **Over-promise: `baselines.json` + `metadata.json` infrastructure the code never
had** (the S14 import twin). Step 5c told the model to `cat baselines.json` and flag ">2 standard
deviations above/below mean"; Reference Files listed both files. Grep over `scripts/analytics/src` =
**NONE writes either**; `storage.ts` writes only `posts/<date>-<shortid>.json`,
`weekly-reports/<week>.json`, `monthly-reports/<month>.json`; `ensureDirectories` (`:7790`) makes no
baselines dir; a full import+report cycle in the fixture created **NEITHER**. The real alerts are
`detectAlerts` (intra-week) + `detectWeeklyAlerts` (WoW) surfaced in the report's own `alerts[]`
(`weekly.ts:222238`). Fix: Step 5c rewritten to read `alerts[]` straight from the generated
weekly-report JSON; Reference Files dropped both phantoms, fixed the posts filename to
`YYYY-MM-DD-<shortid>.json`, added the real `monthly-reports/` file.
2. [REWORK · axis-a] **Broken metric flag `engagement_rate` (snake_case, `:172`).** Grounded:
`--metric engagement_rate` → exit 1 "Invalid metric"; the CLI accepts only camelCase `engagementRate`
(`cli.ts:47,202209`). Fix: `:172` snake→camel (the other call, old `:363`, was already correct).
3. [REWORK · axis-a] **Step 4 / Step 6 schema field names that don't exist.** Step 4 claimed JSON fields
`dateRange`, `postCount`, `aggregateMetrics`; the real `WeeklyReport` (`weekly.ts:124149`) top keys
are `week, generatedAt, summary, topPerformers, underperformers, trends, alerts` (no dateRange, no
aggregateMetrics, no top-level postCount — it is `summary.totalPosts`). Fix: Step 4 rewritten to the
real schema; Step 6 template `({dateRange})` dropped, `{postCount}`→`{summary.totalPosts}`,
eng-rate→`{summary.avgEngagementRate}`; Deep-Dive post-search fixed from the non-existent
`posts/<YYYY-WXX>.json` to iterating the real per-batch `posts/*.json`.
**VERIFY.**
- Re-grepped the final file: `engagement_rate` / `baselines.json` / `metadata.json` / `aggregateMetrics`
/ `({dateRange})` / `{postCount}` / `posts/<YYYY-WXX>.json` all GONE; corrected strings present
(`metric engagementRate` `:171,366`; `get('alerts', [])` `:227`; `posts/YYYY-MM-DD-<shortid>.json`
`:414`; `monthly-reports/YYYY-MM.json` `:416`; Step 4 `summary` schema `:143`; Step 6
`{summary.totalPosts}`/`{summary.avgEngagementRate}` `:271272`); no residual `dateRange`/`postCount`.
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`, **exit 0**; counts **29/19**
unchanged (.md-only edit).
- Grounded simulation (throwaway `ANALYTICS_ROOT=/private/tmp/claude-report-fixture`, discarded): import
seeded 3 fake W25 posts → `report --week 2026-W25` printed "Total saves: 10 (manual entry…)", FIXTURE B
(blank saves) showed no suffix, empty alerts (no baseline consulted); `trends --metric engagementRate`
ok, `--metric engagement_rate` exit 1; heatmap rendered.
- Disposition: **FIXED** (6 edits, all in `commands/report.md`) · 0 deferrals · axes a/c/d all PASS
post-fix (b N/A — not feed-emitting).
---
### /linkedin:analyze — diagnose why content underperforms (reach drop / low traction / low engagement) → recovery plan, analytics grounded via analytics-interpreter
**INTENT.** Prose-diagnosis + conditional agent-delegation — **NOT CLI-backed** (`allowed-tools: Read,
AskUserQuestion, Task`, no Bash, `analyze.md:912`). Measure-journey diagnostic. Steps 18 are a
faithful restatement of `references/troubleshooting-guide.md` (drop-% tiers, 14-day recovery protocol,
timeline table, prevention checklist).
**SIMULATE.** ICP author, scenario 1 (reach 50 %, 1 wk ago, 2 off-pillar posts, 1 body-link). Step 1
picks "Reach suddenly dropped" (`:32`); Step 2 analytics present → `Task
subagent_type: linkedin-studio:analytics-interpreter` interpret-mode (`:41`); Step 3 → pattern "Good
Content, Low Reach" (`:95102`); Step 4 checklist flags external link + topic drift — **and, before fix,
asked "5+ hashtags?" (`:149`)**; Step 5 → "Algorithmic suppression likely" → 14-day recovery
(`:167173`); Step 6 → `/linkedin:profile` (`:189`); Step 7 → "Moderate 5070 %: 23 wks initial"
(`:231`). Friction: exactly one unsourced checklist item.
**EVALUATE (4 axes; analytics-class predicate = graceful degradation + saves/dwell honesty).**
- **axis-a (intent): PASS** — faithful render of the cited guide; AskUserQuestion → framework → recovery
flow intact.
- **axis-b (algorithm-claim vs SSOT — feed-bar reframed since not feed-emitting): 1 FUNN.** `:149`
"Did you use 5+ hashtags in posts?" is a penalty check absent from **both** the SSOT
(`grep hashtag references/algorithm-signals-reference.md` = NONE — hashtags deliberately omitted) **and**
the cited `references/troubleshooting-guide.md` (NONE; its own penalty checklist is 6 items `:101106`).
Command-only unsourced folklore carrying a concrete number ("5+").
- **axis-c (saves/dwell honesty): PASS** — no Bash; never claims to read analytics itself; delegates
honestly; no saves/dwell mention anywhere → no over-promise.
- **axis-d (wiring + degradation): PASS** — `linkedin-studio:analytics-interpreter` resolves;
interpret-mode is real/default (`agent:6,39,141`); shared seam `${LINKEDIN_STUDIO_DATA:-…}/analytics/`
(`analyze:41` ↔ `agent:49`); `/linkedin:profile` resolves (`commands/profile.md`); "if analytics exists
→ delegate, else self-report" IF-branch (`:41`); all 4 referenced files exist.
**HARDEN (1 surgical edit, `commands/analyze.md`).**
1. [REWORK · axis-b] Dropped `:149` "Did you use 5+ hashtags in posts?" — unsourced in both the SSOT and
the cited guide. Step 4 checklist 8 → 7 items; **kept** `:150` "tag unrelated people for reach"
(defensible — aligns with the SSOT bait/pod-suppression direction, no numeric claim).
**Out-of-scope (logged follow-up, NOT touched in S16).** `troubleshooting-guide.md:127` "External link
overuse — LinkedIn penalizes directing traffic away" contradicts the SSOT (`:6975`: LinkedIn *denies* an
intentional penalty; soften to "body links correlate with lower reach"). `analyze.md:102` only echoes the
milder guide wording verbatim (`guide:15`) — softening the command alone would drift it from its own cited
source, so the real fix belongs to the reference-consistency pass.
**VERIFY.**
- Re-grepped final file: `grep hashtag commands/analyze.md` → **NONE**; Step 4 checklist now 7 items
(`:143149`), `:149` = "tag unrelated people for reach" (survived).
- `git diff --stat` = 1 file, **1 deletion**.
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`, **exit 0**; counts **29/19**
unchanged (.md-only edit).
- Disposition: **FIXED** (1 edit, `commands/analyze.md`) · 0 deferrals · axes a/c/d PASS, b PASS post-fix.
---
### /linkedin:audit — quarterly content-strategy audit (top/bottom posts · topic & format mix · trends · milestone) → report + routes to strategy/profile
**INTENT.** Prose-heavy, self-contained quarterly audit — **NOT CLI-backed, NOT agent-delegated**
(`allowed-tools: Read, Glob, Grep, AskUserQuestion` post-fix, no Bash, no Task, `audit.md:812`).
Measure-journey (reachable via `/linkedin:measure`). The 8 steps (08 + 5.5) faithfully cover the
description scope: top/bottom performers, topic distribution, format mix, engagement trends, milestone
progress, profile alignment — quarterly cadence. Names the gap; routes the fix to `/linkedin:strategy`
(trajectory) + `/linkedin:profile` (profile audit).
**SIMULATE.** ICP author, scenario A (partial data). Step 0 reads state + `plans/` + checks `analytics/`
+ audience-insights/examples, asks user for top-5/bottom-5 + screenshot; Steps 12 tabulate vs 7 factors
(`:4149`); Step 3 topic-mix vs pillars (`:6981`); Step 4 format mix → emits "Increase carousel content
(**highest save rate**)" (`:93`, before fix); Step 5 trends; Step 5.5 milestone if `follower_count>0`;
Step 6 routes to `/linkedin:profile` (`:151`); Step 7 report; Step 8 AskUserQuestion. Scenario B (fresh
adopter, no data): Step 0 `:26` "Check for any" = soft, audience files absent → degrades to user-provided
data; Step 5.5 `:141` `follower_count` not >0 → "Skip this step. Add note: …not configured" — no crash.
Friction: one orphan tool-grant + one unsourced format-claim.
**EVALUATE (4 axes; analytics-class predicate = graceful degradation + saves/dwell honesty).**
- **axis-a (intent): PASS** — 8 steps faithfully cover the description scope; routing-by-delegation (not
agent delegation) is honest ("names the gap, strategy/profile prescribe the fix", `:145156`).
- **axis-b (algorithm/format claim vs SSOT): 1 FUNN.** `:93` "(highest save rate)" — the specific
metric-attribution is absent from the SSOT (which sources "carousels = **top organic format**",
`algorithm-signals-reference.md:55`, and "**saves** = top engagement signal", `:42`, but never
"carousels = highest *save rate*") AND from the cited refs (playbook `:248` "Carousels: 1.6x", `:192`
"6070 % reduced reach"; no save-rate superlative). Postulated mechanic embedded in an example block.
- **axis-c (quality rules): PASS** — emits no content; no format/length advice contradicting CLAUDE.md.
- **axis-d (wiring + degradation): 1 FUNN.** No Task → claims no agent delegation (`grep
subagent_type|Task` = NONE) ✅; `/linkedin:strategy` + `/linkedin:profile` resolve ✅; graceful
degradation present (Step 5.5 explicit skip + user-provided-data primary path) ✅; data-seam consistent
(`${LINKEDIN_STUDIO_DATA:-…}/` data, `~/.claude/linkedin-studio.local.md` state, `${CLAUDE_PLUGIN_ROOT}/`
refs) ✅; saves/dwell not over-promised (no Bash; never claims auto-track) ✅. **FUNN: WebFetch orphan
tool-grant** — `:12` granted it; body `:20234` never fetches (grep = declaration line only).
**HARDEN (2 surgical edits, `commands/audit.md`).**
1. [REWORK · axis-d] Dropped `WebFetch` from allowed-tools — granted but never invoked; audit reads local
files + asks user + routes, fetches no URL. Tool-surface now matches behavior (5 → 4 tools).
2. [REWORK · axis-b] `:93` → `:92` "(highest save rate)" → "(top organic format)" — aligned to the SSOT's
sourced framing; dropped the unsourced save-rate superlative.
**Out-of-scope (logged, NOT touched in S17).** `:61` "off-topic (topic-relevance penalty)" — SSOT frames
topic-relevance as a positive distribution driver (`:30,89`, high), not a named penalty; but off-topic →
weaker relevance match → less reach is defensible and "topic-relevance signal" is plugin convention
(CLAUDE.md rule #5). Left as-is (NICE, directionally correct diagnostic parenthesis).
**VERIFY.**
- Re-grepped final file: `grep WebFetch commands/audit.md` → **NONE** (allowed-tools now
Read/Glob/Grep/AskUserQuestion, `:912`); `grep "highest save rate"` → **NONE**; `:92` = "Increase
carousel content (top organic format)".
- `git diff --stat` = 1 file, **+1 / 2**.
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`, **exit 0**; counts **29/19**
unchanged (.md-only edit).
- Disposition: **FIXED** (2 edits, `commands/audit.md`) · 0 deferrals · axes a/c PASS, b + d PASS post-fix.
---
### /linkedin:ab-test — design/log/analyze/history/suggest A/B experiments → managed test files, challenger rewrite delegated to content-optimizer
**INTENT.** Stateful analytics command — the most composite class in the queue so far. CLI-backed (Bash
`ls`/`cat`/`mkdir -p` + `Write` of `[test-name].md` test files under the data-dir), agent-delegated
(`content-optimizer` via `Task subagent_type: linkedin-studio:content-optimizer`, `:119`), AND routing
(`/linkedin:post` `:448`, `/linkedin:report` `:459`, `/linkedin:analyze` `:467`). Measure-journey
(reachable via `/linkedin:measure`). Five lifecycle modes (2a2e) map 1:1 to the description: design ·
log · analyze · history · suggest. (STATE predicted `analytics-interpreter` delegation; actual is
`content-optimizer`.)
**SIMULATE.** ICP author → analyze an active hook experiment. Throwaway 2-experiment fixture in
`/private/tmp/claude-abtest-fixture/analytics/ab-tests/` (ACTIVE 6-post + COMPLETED), run via
`LINKEDIN_STUDIO_DATA=<fixture>` to exercise the real seam. Seam-discovery (`:37`,`:270`) found both
files; analyze arithmetic computed correct (eng-rate `:236` per-post 3.0%/5.0%; variant avg + %-diff
`:278279` → eng +66.7%, comments +200%, impressions/reposts +0%); directional test (`:311`) cleared
20% AND consistent across all 3 paired posts → "Adopt B (directional)"; significance honesty (`:320331`)
correctly flagged 3/variant << ~50 → directional, NOT significant. History-parse targets all present in
COMPLETED fixture. Empty `weekly-reports/` + `posts/` → graceful degradation (`:485488`). Fixture
discarded (`find -delete`).
**EVALUATE (8 predicates across 4 axes; class = stateful + analytics + agent-wiring + routing).**
- **P1 (axis-d wiring): PASS** — `content-optimizer` resolves; `agents/content-optimizer.md` `name:`
matches the namespaced invocation form.
- **P2 (routing): PASS** — `post`/`report`/`analyze`.md all resolve.
- **P3 (intent refs): PASS** — `ab-testing-framework.md` + `algorithm-signals-reference.md` exist.
- **P4 (axis-b SSOT): 1 FUNN.** ② 20%-threshold **verbatim** from framework SSOT (`fw:173` = `ab:311`)
✅; ③ significance honesty under-claims (a real significance test needs more than ~50/variant → the
framing can only make the "directional not significant" verdict safer) ✅; **① `:128` "(Tue-Thu, 8-9 AM
optimal)"** — sourced (cites the ref) but "optimal" over-claims precision against the SSOT's own `:23`
"trust the *ordering*, test the *number*", and Thu is 9 AM1 PM in SSOT (`alg:144`), not 8 AM; it is the
default slot held constant while testing another variable, so "optimal" is the wrong word.
- **P5 (data-seam): PASS** — all data via `${LINKEDIN_STUDIO_DATA:-…}/`; `:44` = state file (correct), not
a leak; refs via `${CLAUDE_PLUGIN_ROOT}/`.
- **P6 (graceful degradation): PASS** — empty weekly-reports/posts → proceed test-only (`:485488`); 4
error modes (`:474493`).
- **P7 (saves/dwell honesty): PASS** — no saves/dwell mention; eng-rate `:236` excludes saves (S16-consistent).
- **P8 (stateful artifact trace): PASS** — see SIMULATE; full analyze + history path sound against a
realistic file.
**HARDEN (1 surgical edit, `commands/ab-test.md`).**
1. [REWORK · axis-b] `:128` "(Tue-Thu, 8-9 AM optimal)" → "(Tue-Thu mornings)" — dropped the precision
over-claim ("optimal" + the inaccurate 8 AM window); the line stem already cites
`algorithm-signals-reference.md`, so source attribution is retained.
**Out-of-scope (logged, NOT touched in S18).** `:284` "baseline comparison" cross-ref vs `weekly-reports/`
— echoes the S15 "baselines/metadata phantom" vocabulary, but here it reads an existing artifact (the
report command's weekly reports), optional + graceful-degraded; legitimate soft context, not a phantom.
Left as-is (low, no-change).
**VERIFY.**
- Re-grepped final file: `grep "8-9 AM optimal"` → **NONE**; `:128` = "...based on
algorithm-signals-reference.md (Tue-Thu mornings)".
- `git diff --stat` = 1 file, **+1 / 1**.
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`, **exit 0**; counts **29/19**
unchanged (.md-only edit).
- Disposition: **FIXED** (1 edit, `commands/ab-test.md`) · 0 deferrals · P1P3,P5P8 PASS, P4 PASS post-fix.
---
### /linkedin:strategy — phase-aware growth + authority-building plan (S19)
**INTENT.** Grow-journey front-door (CLAUDE.md elevates `onboarding`/`strategy` as the Start/Grow
front-doors) **and** atomic command. Prose-heavy, **no Bash** (`allowed-tools`: Read·Glob·Grep·
WebFetch·AskUserQuestion·Task). Reads state (`follower_count` → phase auto-detect) + 8 local refs,
delegates the growth recommendation to `strategy-advisor`, routes the profile audit to
`/linkedin:profile`. Most composite non-CLI class: prose + state-read + agent-delegation + routing +
axis-b.
**SIMULATE (ICP author, grounded prose-trace — no fixture; touches no real state/data).** "What should
I focus on at my level?" → Step 0.5 auto-detects phase from `follower_count` (graceful: `:43` falls
through to Step-1 questions if 0/missing) → AskUserQuestion → Step 3 delegates to
`linkedin-studio:strategy-advisor` (`:154`) → phase strategy + Step 3.5 trajectory overlay (graceful:
`:285` "Skip this step. Note: …" when no milestone data) → Step 3.6 authority-building (Phase 2+).
Friction: none surfaced; phase bands consistent between Step 0.5 (`:3842`) and the Step 2 headers
(`:79/:93/:107/:122/:137`).
**EVALUATE (4 axes; mechanical predicate per axis).**
- **(a) intention: PASS** — body delivers exactly the description: phase-specific guidance 01K→10K+,
trajectory overlay, Phase 2+ signature-content compounding.
- **(b) algorithm/format-bar (axis-b): PASS** — topic-relevance is the SSOT signal (`:88/:175/:520`);
growth rates / timelines are roadmap-sourced (command loads `growth-roadmaps.md` +
`trajectory-strategy-adjustments.md`), not postulated in-command; saves-honesty intact (`:300/:408`
"read from native LinkedIn analytics — not auto-tracked by this tool").
- **(c) quality rules: PASS** — no buzzwords, no body-links; prose-only command (no post emission).
- **(d) agent-wiring + graceful degradation: 1 FINDING** — P-checks: agent `linkedin-studio:strategy-advisor`
(`:154`, namespaced) → `agents/strategy-advisor.md` **resolves**; routing `/linkedin:profile`
(`:293/:361`) → `commands/profile.md` **resolves**; 8/8 referenced files exist; graceful degradation
present (`:43/:285/:408`). **allowed-tools over-grant:** `:16` `WebFetch` is the ONLY occurrence in
the file (`grep -n WebFetch` = `:16` only) — never used in the body (no URL fetch anywhere; body reads
local refs + state, delegates, AskUserQuestion); delegate `strategy-advisor` is Read+Glob only, so it
doesn't need it either. Exact S17 pattern (`62b7081`). Borderline Grep/Glob left as-is (local,
low-risk, plausibly used for state/analytics reads) — only the external-network WebFetch trimmed.
**HARDEN (1 surgical edit, `commands/strategy.md`).**
1. [REWORK · axis-d] `:16` removed `- WebFetch` from `allowed-tools` — orphan grant (external-network
capability, zero usage). allowed-tools now Read·Glob·Grep·AskUserQuestion·Task.
**VERIFY.**
- Re-grepped final file: `grep -n WebFetch commands/strategy.md` → **NONE**; allowed-tools block =
Read/Glob/Grep/AskUserQuestion/Task.
- `git diff --stat` = 1 file, **1**.
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`, **exit 0**; counts **29/19**
unchanged (.md-only edit).
- Disposition: **FIXED** (1 edit, `commands/strategy.md`) · 0 deferrals · axes a/b/c PASS, axis-d PASS post-fix.
---
### /linkedin:competitive — niche thought-leader analysis → landscape map · gap matrix · differentiation plan (S20)
**INTENT.** Grow-journey atomic command (with `monetize`/`outreach`/`profile`). Prose-heavy guided
analysis — **not** post-emitting, **not** routing (no `/linkedin:Y` emitted), **not** own-data
analytics. Six-step flow (Step 06) producing prose output (no persisted artifact: no `Write`/`Task`).
User names 35 competitor profiles (or asks for help finding them) → competitive map + gap analysis +
differentiation plan. Ethics Note (`:155-157`) steers toward inspiration, not copying.
**SIMULATE (ICP author, grounded prose-trace — no fixture; touches no real state/data).** Two branches
proved against Step 06 (`:19-153`): **A** (names 3 profiles, choice 1 `:30`) → reads SKILL.md+state
(`:21-23`) → per-competitor template Step 2 (`:46-76`, all fields are `[placeholders]` the user/observation
fills — no fabricated data) → landscape map Step 3 → gap matrix Step 4 → differentiation plan Step 5 →
takeaways Step 6 → Ethics Note. **B** (choice 2 "help me find", `:31`) → triggers WebSearch (`:34`) →
same analysis loop. **No branch invokes WebFetch.**
**EVALUATE (relevant axes; mechanical predicate each).**
- **(a) intention: PASS** — body delivers exactly the description: posting frequency, content types,
hooks, engagement strategy, gaps → differentiation.
- **(b) algorithm/format-bar (axis-b): PASS** — `grep -nE "[0-9]+ ?(%|x|times)"` = **NONE**; format fields
(`:54` `text X%/carousel Y%/video Z%`) are letter-placeholders, not postulated stats; folklore grep hit
only `:8`/`:116` (false positives). No unsourced algorithm claim.
- **(c) quality rules: PASS** — prose-only command (no post emission), Ethics Note present.
- **(d) agent-wiring + graceful degradation: 1 FINDING** — no agent delegation (selbständig prose,
correct for class); 5/5 referenced files resolve (`:21,:161-163` + state PRESENT); graceful degradation
present (Step 1 AskUserQuestion 3 branches + WebSearch fallback `:34`). **allowed-tools over-grant:**
`:11` `WebFetch` was the ONLY occurrence (`grep -n WebFetch` = `:11` only) — never used in the body;
WebSearch (`:12` grant + `:34` real use) covers the entire legitimate external need (finding thought
leaders), and WebFetch against LinkedIn profile URLs cannot deliver the public feed content the grant
implies (login/anti-bot). Exact S17/S19 pattern. Borderline Glob (`:10` grant, no body use) left as-is
(local, low-risk read tool) — only external-network WebFetch trimmed.
**HARDEN (1 surgical edit, `commands/competitive.md`).**
1. [REWORK · axis-d] `:11` removed `- WebFetch` from `allowed-tools` — orphan grant (external-network
capability, zero usage, not realizable for LinkedIn profiles). allowed-tools now
Read·Glob·WebSearch·AskUserQuestion.
**VERIFY.**
- Re-grepped final file: `grep -n WebFetch commands/competitive.md` → **NONE**; allowed-tools block =
Read/Glob/WebSearch/AskUserQuestion.
- `git diff --stat` = 1 file, **1**.
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`, **exit 0**; counts **29/19**
unchanged (.md-only edit).
- Disposition: **FIXED** (1 edit, `commands/competitive.md`) · 0 deferrals · axes a/b/c PASS, axis-d PASS post-fix.
---
### /linkedin:monetize — turn thought leadership into revenue (readiness → stage strategy → lead magnet → funnel → tracking)
**INTENT.** Prose-heavy guided monetization strategist (Grow-tier atomic, unlocks ~1K followers). 9-step flow
(Step 08 `:22-487`): load refs → readiness scorecard → stage-specific strategy → lead-magnet blueprint →
funnel content → CTA optimization → Featured section → revenue model worksheet → tracking dashboard. Output is
prose templates the user fills in; **persists nothing**.
**SIMULATE (aspiring monetizer, grounded prose-trace — no fixture; touches no real state/data).** Traced
Step 08 against the file. Step 1 collects follower count / engagement / offers / goals via **AskUserQuestion**
(`:37`) — does NOT read state, so it degrades gracefully (asks rather than depends). Stage-1 branch (01K,
score 030, `:94-112`) handles the sub-unlock case honestly ("Revenue is secondary… DO NOT sell aggressively").
All scorecards/blueprints/worksheets are `[placeholder]` fill-ins — no fabricated user data. **No branch
invokes WebFetch, Write, Glob, or Grep.**
**EVALUATE (relevant axes; mechanical predicate each).**
- **(a) intention: PASS** — body delivers the description (readiness scoring, stage strategy, lead magnets,
CTA A/B, funnel, consulting-inquiry tracking). 7/7 referenced files resolve (Step 0 + Reference Files).
- **(b) algorithm/conversion-bar (axis-b): 1 FINDING** — `:344-346` postulated a generic time-decay curve
("same-day ~half / next-day 20-30% / **3+ days <10%**"). SSOT (`opportunity-generation.md:302-310`) gives
**per-channel** rates (speaking 30-50% same-day, consulting 20-30% within-24h, podcast 70-80%) + qualitative
"speed matters", NOT a generic decay curve — and **`<10%` is invented**, no SSOT basis. Same over-precise
-unsourced pattern as S18. (`:495` external-links claim was already SSOT-sourced + softened — clean.)
- **(c) quality rules: PASS** — prose-only (no post emission); no buzzwords; `:495` correctly sourced.
- **(d) agent-wiring + graceful degradation: 2 FINDINGS (allowed-tools over-grant)** — no agent delegation
(selbständig prose, correct for class). **`:13` `WebFetch`** = orphan grant, ONLY occurrence (`grep -n
WebFetch` = `:13` only), zero body use, WebSearch not even granted → pure dead external-capability grant,
exact S17/S19/S20 pattern. **`:15` `Write`** = orphan grant, ONLY occurrence, zero body use — command
never persists a file; unused **mutation** capability (stronger least-privilege case than read grants).
Borderline `Glob`/`Grep` (`:11-12`, no body use) left as-is (local, low-risk read tools — S20 precedent).
**HARDEN (2 surgical edits, `commands/monetize.md`).**
1. [REWORK · axis-d] removed `- WebFetch` and `- Write` from `allowed-tools` — orphan grants (zero usage;
external-fetch never realized, file never written). allowed-tools now Read·Glob·Grep·AskUserQuestion.
2. [REWORK · axis-b] `:343-346` replaced the fabricated decay curve (incl. invented `<10%`) with the
SSOT-supported qualitative claim ("speed compounds conversion — same-day converts far better than
days-later") + cite to `references/opportunity-generation.md` for the real per-channel benchmarks.
`:313/:323` "within 2 hours" left as-is (defensible tactical advice, not a fabricated statistic — operator OK).
**VERIFY.**
- Re-grepped final file: `grep -n WebFetch` → **NONE**; `grep -n Write` → **NONE**; `grep -n "roughly
half|<10%|3+ days|20-30% conversion"` → **NONE**; allowed-tools block = Read/Glob/Grep/AskUserQuestion.
- `git diff --stat` = 1 file, **+3 / 6**.
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`, **exit 0**; counts **29/19**
unchanged (.md-only edit).
- Disposition: **FIXED** (2 edits, `commands/monetize.md`) · 0 deferrals · axes a/c PASS, axes b/d PASS post-fix.
---
### /linkedin:outreach — collab + speaking outreach orchestrator (track select → readiness → targets → format/pitch → production → pipeline persist) (S22)
**INTENT.** Grow-tier **orchestrator** (unlocks ~1K), prose-heavy **+ agent-delegating + state-persisting**.
Two tracks (collab + speaking) under one paradigm, 11 steps (Step 010 `:65-1252`). Absorbed
`collab`/`speaking`/`authority` (capability checklist `:36-63` maps each predecessor function). **First in
the queue to BOTH delegate to an agent AND mutate real state** — shifts the weight onto axis-d.
**SIMULATE (adversarial cold quality-reviewer; grounded prose-trace + throwaway state fixture — operator
chose the thorough path; touches no real state/data-dir).** Traced Step 010 against the file; verified every
mechanical claim (refs resolve, agent resolves, mutation exists, grants match body use, axis-b claims trace
to SSOT) with a tool, never by reasoning.
**EVALUATE (relevant axes; mechanical predicate each).**
- **(a) intention: PASS** — checklist maps both predecessors; all 5 refs + SKILL.md resolve (Step 0 +
Reference Files); state path `~/.claude/linkedin-studio.local.md` correct post-M0 (per CLAUDE.md).
- **(b) algorithm/conversion-bar (axis-b): 2 FINDINGS.** **B1** `:652` "DM amplification can boost
first-hour engagement **2-3x**" — over-quantified: 2-3x is SSOT for *collab reach*
(`collaborations-guide.md:32/165/187`), while the DM-loop's first-hour effect is held *qualitative* there
(`:335`). **B2** `:921-946` Step 7 pod-warning fabricated magnitudes — "**30-55%** reach reduction",
"shadow suppression", "account credibility score reduction", "penalizes", "**Post-March 2025** Update" +
"low dwell" detection (dwell unmeasurable per CLAUDE.md). SSOT (`algorithm-signals-reference.md:98`) states
ONLY: "auto-comments demoted out of 'Most Relevant', scoped to own network; repeat offenders restricted"
(Sachdeva 2026-02-16, high) + design rule "do not quote a percentage / soften penalty". Same S18/S21 +
follow-up-#1 pattern.
- **(c) quality rules: PASS** — prose templates only (no post emission); "thought leader"/"ecosystem"
appear only in search-queries / internal *goals* (`:230/:813/:820`), not in published post-copy (Rule 4).
- **(d) agent-wiring + grants + state + graceful: 3 FINDINGS.** **D1 PASS** — `Task subagent_type:
linkedin-studio:network-builder` (`:172`) resolves (`agents/network-builder.md`). **D2 PASS** —
`recordOutreachContact` is **NOT fabricated** (`state-updater.mjs:294`, CLI `--record-outreach :394`,
additive, `$`-safe). **D3 Bash MISSING** — Step 8c (`:1064`) runs `node …state-updater.mjs --record-outreach`
(needs Bash) but frontmatter didn't grant it; `firsthour.md` grants Bash for the same `node`-mutation
pattern → functional defect (command promised a mutation it couldn't execute). **D4 WebFetch orphan**
(`:19`, zero body use). **D5 Write orphan** (`:21`; `:410/:1040` are prose "Write a post", not tool use).
**HARDEN (5 edits across 2 files — operator-approved scope extension to also fix the SSOT-diverging guide
source so the B2 rewrite can't be re-rendered from a stale reference).**
1. [REWORK · axis-d] `commands/outreach.md` frontmatter: dropped `- WebFetch` + `- Write` (orphans),
added `- Bash` (Step 8c needs it). allowed-tools now Read·Glob·WebSearch·AskUserQuestion·Task·Bash
(borderline Glob kept — S20 precedent).
2. [REWORK · axis-b] `:652` (B1): re-attributed 2-3x to collab reach (where SSOT places it), kept the
first-hour effect qualitative + cite to `collaborations-guide.md`.
3. [REWORK · axis-b] Step 7 (B2): replaced "Post-March 2025 Update" header + the fabricated
detection/penalties block with the SSOT crackdown statement (demote out of "Most Relevant" / scope to own
network / restrict repeat offenders) + cite; softened "penalizes", dropped "low dwell".
4. [REWORK · axis-b, scope-extension] `references/collaborations-guide.md:349-370` (B2 propagation): same
SSOT rewrite of the "March 2025 Crackdown" section — dropped "shadow banning ALL future content / 90+ days
/ 6+ months / permanent reach penalty", the date, and the speculative detection methods (browser-extension
tracking, linguistic fingerprinting).
**VERIFY.**
- Re-grepped final files. `commands/outreach.md`: `grep WebFetch` → **NONE**, `^ - Write$` → **NONE**,
`^ - Bash$` → `:21`; B1 old form → **NONE** (new `:651`); B2 `30-55%|Post-March 2025|penalizes engagement
pods|credibility score|Low dwell|Shadow suppression` → **NONE** (SSOT text `:928-929`).
`references/collaborations-guide.md`: `90+ days|6+ months|never fully recover|permanent reach
penalty|Shadow banning affects ALL|March 2025 Crackdown` → **NONE** (SSOT text `:349/:364`).
- **D2/D3 throwaway fixture** (`/private/tmp/claude-*`, fictional, deleted after): `recordOutreachContact`
direct-call (no `writeState`), **10/10 assertions PASS** — additive insert after `last_post_date`,
`outreach_active` untouched when undeclared, pre-existing fields + custom section intact, newest-first
append, **`$`-safe** (`@kari$boss` preserved literally — S13 behavioral), section auto-created.
- `git diff --stat` = 2 files, **+20 / 28**.
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`, **exit 0**; counts **29/19**
unchanged (.md-only edits — node hook/analytics tests not triggered).
- Disposition: **FIXED** (5 edits, 2 files) · 0 deferrals · axes a/c PASS, axes b/d PASS post-fix.
Note: state-updater HTML-comment lands between newest+older rows on repeat append = cosmetic, logged as
non-scope follow-up (the command calls the mutation correctly).
---
### /linkedin:profile — profile/topic-relevance + profile-SEO audit walkthrough (7 sections → prioritized action plan); self-contained, no agent/routing/state (S23)
**INTENT.** Grow-tier **atomic execution command**, prose-heavy + guided (`AskUserQuestion`).
**Self-contained** — no agent delegation, no `/linkedin:Y` routing, no state mutation. Audits the
profile for the topic-relevance ranking signal + profile search-SEO.
**SIMULATE (adversarial cold quality-reviewer; grounded prose-trace — pure prose, NO fixture: command
reads/mutates no state).** Headline section (`:79-96`) traced against operator input
`"AI-rådgiver | KI-transformasjon | Innholdsprodusent"` → checklist `:85-88` yields NO audience / NO
outcome / vague titles → concrete non-generic rewrite direction. Every mechanical claim (grant grep, SSOT
read, pattern greps) from a tool, never by reasoning.
**EVALUATE (relevant axes; mechanical predicate each).**
- **(a) intention: PASS** — desc + Grow-journey role coherent; 3 refs (`algorithm-signals-reference.md`,
`troubleshooting-guide.md`, `SKILL.md`) resolve.
- **(b) algorithm-bar (axis-b): 1 systemic FINDING (B1) + 1 minor (B2).** **B1 — "profile-as-validated-object"
overclaim, 9 sites:** the file repeatedly asserts the relevance model **reads & validates your profile**
and gates distribution on it — `:5` "validates your profile BEFORE distributing content"; `:31` "the model
**evaluates five criteria** (see SSOT)" (**false SSOT attribution** — SSOT has no such breakdown); `:35-39`
"Impact if Missing: **HIGH/HIGH/MEDIUM…**" (postulated coefficients SSOT forbids, `:14/:23`); `:99` "first
signal telling … what you're **qualified** to discuss"; `:161` "relevance **validation**"; `:173` "**checks
if** you're connected"; `:230` "if LinkedIn's AI **read my profile**, would it **believe**"; `:51/:67` SEO
conflation ("**tell the relevance model** what you're expert in" / "the **relevance model's expertise
signal**"). SSOT (`algorithm-signals-reference.md:25-30/:85-90`) confirms ONLY: topic/interest relevance is a
ranking input — content matched to a **viewer's interests** is distributed incl. beyond your network (Jurka,
high); **no off-topic magnitude figure; no weighted profile criteria**. File was **internally inconsistent** —
its own soft, SSOT-true framing already at `:17/:25/:27`. Same class as follow-up #1 (`audit.md:61`) +
v4.0.0 "reconcile to one sourced statement". **B2** `:24` "Goes to **10% of audience**" — postulated
old-model number (`:131` "40%" is an illustrative impact-bullet example → PASS).
- **(c) quality rules: PASS** — no post emission (audit prose only); buzzword list cited correctly (`:73`
steers away from "thought leader/guru/ninja").
- **(d) grants + graceful + wiring: PASS.** **Grant-hygiene REN (two-way):** `allowed-tools` = `Read` +
`AskUserQuestion` (`:11-13`), both used (`:21/:27/:31/:236` Read; `:77` AskUserQuestion); WebFetch / Write /
Bash / Task / subagent_type / .mjs → **NONE** (no orphans, no missing node/Bash grant). No agent/routing.
Graceful degradation OK (Q&A runs regardless of ref availability).
- **P3 — profile-SEO honesty (`:41-73`): largely defensible** (about LinkedIn *search*, not the ranking
model; correctly does NOT cite SSOT). One practitioner-claim-as-fact: "**highest-weight** search field"
(`:52` prose + `:61` table) — self-justified mechanically, softened not removed.
**HARDEN (12 edits, 1 file — `commands/profile.md`; all surgical re-source to SSOT framing).**
1. [REWORK · b] `:5` desc — "validates your profile BEFORE distributing content" → "a coherent, on-topic
profile reinforces the topic-relevance signal LinkedIn uses to decide how widely your content is distributed".
2. [REWORK · b] `:24` (B2) — "Goes to 10% of audience" → "a slice of your network sees it".
3. [REWORK · b] `:25/:27` — "content matched to **your demonstrated expertise**" → "**a viewer's interests**"
(SSOT wording) + added "LinkedIn confirms no off-topic reach-reduction figure — not a quantified penalty".
4. [REWORK · b] `:31` + table `:33-39` — dropped "the model evaluates five criteria (see SSOT)" + HIGH/MEDIUM
"Impact if Missing" weights → "LinkedIn does **not** publish a profile-scoring breakdown … practitioner
heuristics", column → "Priority (heuristic)", High/Medium relabeled directional.
5. [REWORK · b] `:99` — "first signal telling … qualified to discuss" → "the clearest place to state … the
strongest single contribution to a coherent topic signal".
6. [REWORK · b] `:161` — "Critical for profile/topic-relevance validation" → "Your top skills are a strong,
searchable topic signal".
7. [REWORK · b] `:173` — "relevance checks if you're connected" → "a network concentrated in your expertise
area reinforces your topic signal and social proof (heuristic — LinkedIn doesn't publish network as a
profile-ranking factor)".
8. [REWORK · b] `:230` — "If LinkedIn's AI read my profile, would it believe I'm an expert" → "Does my
profile make it obvious — to a human and to LinkedIn's topic-matching".
9. [REWORK · b] `:51/:67` SEO conflation — "tell the relevance model what you're expert in" → "make your
topic legible … to topic-relevance distribution"; "the relevance model's expertise signal" → "carry your
strongest on-topic signal".
10. [NICE · P3] `:52/:61` — "highest-weight search field" → "highest-leverage" (×2; rest self-justified, kept).
**VERIFY.**
- Re-grepped final file. All overclaim/conflation patterns (`validates your profile before | evaluates five
criteria | first signal telling | qualified to discuss | checks if you're connected | relevance validation |
if linkedin's ai read | 10% of audience | highest-weight | demonstrated expertise | Impact if Missing | tell
the relevance model | relevance model's expertise signal`) → **NONE**. New SSOT text present (`:26/:28/:33/
:38/:54/:178/:235`).
- Grant-hygiene unchanged: `allowed-tools` = Read + AskUserQuestion (`:11-13`); WebFetch/Write/Bash/Task/.mjs → **NONE**.
- `git diff --stat` = 1 file, **+28 / 22**.
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`, **exit 0**; counts **29/19**
unchanged (.md-only edits — node hook/analytics tests not triggered).
- Disposition: **FIXED** (12 edits, 1 file) · 0 deferrals · axis-b **FIXED**, axes a/c/d **PASS** · no
fixture needed (pure prose, no state).
---
### /linkedin:create — content-creation front-door (guided "what to make?" → routes to the owning creation command); delegates only, no drafting (S24)
**CLASS: routing front-door** (Create-journey, v4.1). Predicate: every emitted `/linkedin:Y` resolves to
`commands/Y.md`; bidirectional grant-hygiene; "delegates only" (never drafts).
**TEST (tool-grounded).**
- Routing (main axis): **8/8 targets resolve** — `commands/{post,quick,react,carousel,video,multiplatform,
batch,newsletter}.md` all present (menu `:38-45` / table `:54-63`).
- Grant-hygiene (bidirectional): `Read` (`:13`) used at `:26` (fixed-path state read); `AskUserQuestion`
(`:15`) used at `:36`; **`Glob` (`:14`) ORPHAN** — zero body usage, only file access is the fixed-path
Read (`:26`; Read handles the "if exists" itself — no pattern-matching). No missing grants (routing =
narrative handoff, no tool).
- Delegates-only: all 4 "draft" hits are the no-draft promise (`:7/:22`, long-form lock `:66`) or
target-description (`:38` = what `/linkedin:post` does); no drafting leak.
- Persona (ICP author, no format named): Step 0 `:24-28` state glance → Step 1 `:30-45` AskUserQuestion
(8 opts) → Step 2 `:47-63` route into target workflow, steps not inlined; URL fast-path `:32-34` →
`/linkedin:react`. ✓
**HARDEN (1 edit, 1 file — `commands/create.md`).**
1. [REWORK · grant-hygiene] dropped orphan `Glob` from `allowed-tools` (`:14`); net = {Read, AskUserQuestion}.
**VERIFY.**
- Re-grep final: `Glob` → **NONE**; `allowed-tools` = Read + AskUserQuestion (`:13-14`).
- `git diff --stat` = 1 file, **1**.
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`, **exit 0**; counts **29/19**
unchanged (.md-only edit).
- Disposition: **FIXED** (1 edit, 1 file) · 0 deferrals · routing axis **PASS**, delegates-only **PASS**,
grant-hygiene **FIXED** · no fixture (pure routing prose).
- Scope note: `Glob`-orphan may be systemic in sibling front-doors (`measure` S25, `linkedin` S26) — not
expanded (one command per session); each catches its own.
---
### /linkedin:measure — performance front-door (guided "how am I doing?" → routes to the owning analytics command); delegates only, no number-crunching (S25)
**CLASS: routing front-door** (Measure-journey, v4.1). Predicate: every emitted `/linkedin:Y` resolves to
`commands/Y.md`; bidirectional grant-hygiene; "delegates only" (never crunches numbers).
**TEST (tool-grounded).**
- Routing (main axis): **5/5 targets resolve** — `commands/{import,report,analyze,audit,ab-test}.md` all
present (menu `:31-35` / table `:44-48` / order-note `:51`).
- Grant-hygiene (bidirectional): `AskUserQuestion` (`:13`) used at `:29`; `Glob` (`:12`) + `Read` (`:11`) —
neither named a tool in body, both hung only on the **optional Step 0** filesystem glance (`:22-25`,
pre-edit). UNLIKE `create` (Glob a pure orphan, no fs-check at all), measure HAS a Step 0 ("if
`…/analytics/` holds imported data, note last import"), giving `Glob` a real home — but the grant↔use
link was implicit (body named no tool).
- Delegates-only: 3 no-do promises (`:7`, `:20`, `:40`); all crunch/analysis hits are negations — no leak.
- Persona (ICP author, "how am I doing?"): Step 0 optional analytics glance → Step 1 AskUserQuestion (5
opts) `:29` → Step 2 route into target workflow `:37-48`; order-note `:50-51` (no data → import first). ✓
- No fixture (pure routing prose).
**HARDEN (operator-chosen — explicit `Glob`, drop `Read`; 2 edits, 1 file — `commands/measure.md`).**
1. [REWORK · grant-hygiene] dropped `Read` from `allowed-tools` (`:11`, pre-edit) — redundant; the Step 0
import-date need is met by a `Glob` listing of the analytics dir.
2. [REWORK · grant↔use traceability] made Step 0 explicit: "You may `Glob` `…/analytics/`; if it holds
imported data, note last import" (`:23`), giving the retained `Glob` grant a named body home.
Net grants = {Glob, AskUserQuestion}.
**VERIFY.**
- Re-grep final: `Read` as a tool name → **NONE**; `allowed-tools` = Glob + AskUserQuestion (`:11-12`);
`Glob` now in body `:23`.
- Routing unchanged: 5 distinct targets, all present.
- `git diff --stat` = 1 file, **+1/2**.
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`, **exit 0**; counts **29/19**
unchanged (.md-only edit).
- Disposition: **FIXED** (2 edits, 1 file) · 0 deferrals · routing axis **PASS**, delegates-only **PASS**,
grant-hygiene **FIXED** (Glob retained + made explicit, Read dropped).
- Scope note: the `Glob`-orphan question is now resolved for `measure` (it had a home via Step 0; `create`
did not). `linkedin` (S26) still to check independently.
---
### /linkedin:linkedin — top router (five-journey menu + status line + queue glance); routes to every command/agent, executes nothing (S26)
**CLASS: routing** (the master router over the whole surface — the largest routing check: 28 command
pointers + 10 agent pointers). Predicate: every emitted `/linkedin:Y` resolves to `commands/Y.md`; every
"Suggest `X` agent" resolves to `agents/X.md`; bidirectional grant-hygiene; "delegates only" (shows
menu/status, never executes).
**TEST (tool-grounded).**
- Routing (main axis): **28/28 distinct `/linkedin:Y` targets resolve** to `commands/Y.md` (menu `:66-127`,
ask-menu `:136-140`, direct-routing `:157-198`) — the full sub-command surface (+`linkedin` itself = 29).
- Agent pointers (axis d): **10/10** "Suggest `X` agent" resolve to `agents/X.md` (analytics-interpreter,
content-optimizer, content-planner, content-repurposer, differentiation-checker, engagement-coach,
network-builder, post-feedback-monitor, trend-spotter, voice-trainer).
- Grant-hygiene (bidirectional): `allowed-tools` = Read/Bash/AskUserQuestion (`:8-11`); each has a body home
— Read→state status line (`:20`), Bash→node queue-manager block (`:34-42`), AskUserQuestion→sub-choice
(`:145`); **no ungranted tool referenced, no Glob at all** — third front-door is clean (unlike create's
Glob-orphan / measure's Glob-via-Step-0).
- Delegates-only: body routes/suggests; `:146` "invoke it directly" = hand-off to the command, not
self-execution. No drafting / number-crunch. PASS.
- Graceful degrade (axis d): no-state fallback (`:28`) + empty-queue fallback (`:57`). PASS.
- Persona (ICP author types `/linkedin`): graceful-degrade path — no fixture (pure routing prose).
**FINDING (terminology — operator rule surfaced this session): "thought leadership" must not appear in the
plugin (it is named LinkedIn Studio).** linkedin.md used it at `:4` (frontmatter description) + `:16` (body).
**HARDEN (2 edits, 1 file — `commands/linkedin.md`).**
1. [REWORK · terminology] `:4` "Main router for LinkedIn **thought leadership** commands" → "Main router for
**LinkedIn Studio** commands".
2. [REWORK · terminology] `:16` "You are a LinkedIn **thought leadership** assistant" → "You are the
**LinkedIn Studio** assistant".
**VERIFY.**
- Re-grep final: "thought leadership" in linkedin.md → **NONE**; `:4`/`:16` show "LinkedIn Studio".
- Routing/agent axes unchanged: 28 + 10 targets all resolve.
- `git diff --stat` = 1 file, **+2/2**.
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`, **exit 0**; counts **29/19**
unchanged (.md-only prose edit).
- Disposition: **FIXED** (2 edits, 1 file) · 0 deferrals · routing **PASS**, agent-wiring **PASS**,
grant-hygiene **PASS** (clean — no orphan; third front-door has no Glob), delegates-only **PASS**.
**SURFACED (out of scope → queued as S29).** "thought leadership" appears plugin-wide on **85 lines across
~45 files** (commands, agents, references incl. the filename `references/thought-leadership-angles.md`,
skills, plus historical CHANGELOG/docs). Promoted to a dedicated terminology-scrub pass **S29**
(`docs/hardening/plan.md`); the user-facing-vs-historical boundary + the file rename are open scoping
questions for that pass.
---
### S27 ref-consistency A — "penalty/penalizes" framing → SSOT (external links + topic-relevance)
**CLASS: reference-consistency** (NOT re-hardening of ✅ commands — surgical SSOT-reconciliation of a
documented divergence-class across multiple files; same tool-grounded discipline: read-and-show →
grep-confirm → re-grep final). Predicate: no surface frames external-link reach-loss or off-topic
reach-loss as a hard *named penalty* — SSOT (`references/algorithm-signals-reference.md:69-75`, `:89-90`)
says links **correlate with lower reach** (LinkedIn denies an *intentional* penalty) and topic relevance is
a **positive ranking input** with **no quantified off-topic figure**.
**TEST (tool-grounded).** Plugin-wide `grep -rniE "penaliz|penalty"` over commands/agents/references/skills.
The 3 plan-cited lines confirmed (line-nr drift ±1 since plan written: `analyze.md:101` not `:102`,
`audit.md:60` not `:61`). Catalog split into 4 buckets, operator-approved:
- **Bucket A (fix now):** 10 hard-penalty framings of link/topic-relevance in non-format files.
- **Bucket B (defer → S28):** 6 link-penalty lines in `references/linkedin-formats.md` (`:52,:91,:194,:198,:200,:432`) — S28 already opens that file for format magnitudes; reconcile link + format in one pass.
- **Bucket C (leave — already SSOT):** `first-comment-strategy.md:7`, `glossary.md:171` (def cites SSOT); glossary term `### Link Penalty :170` kept (def is correct).
- **Bucket D (surface → new S30):** other unsourced magnitudes (68% hashtags, 25% short-post, 15-25% topic-gap, 30-50% bait, 55% AI-comment, 25%/post, poll-overuse) — a *different* class. Engagement-pod + AI-slop "penalized" left intact (officially confirmed, high confidence).
**HARDEN (10 edits, 6 files — Bucket A).**
1. `commands/audit.md:60` `(topic-relevance penalty)` → `(topic relevance is a ranking input — off-topic gets lower reach)`.
2. `agents/trend-spotter.md:338` `ranking will penalize off-topic content` → `Topic relevance is a ranking input — off-topic content gets lower reach`.
3. `agents/trend-spotter.md:353` `topic-relevance penalty, damages authority` → `off-topic content gets lower reach, damages authority`.
4. `references/troubleshooting-guide.md:15` `External link penalizing reach` → `External links correlate with lower reach`.
5. `references/troubleshooting-guide.md:127` `LinkedIn penalizes directing traffic away` → `body links correlate with lower reach (LinkedIn denies an intentional penalty; lead with value)`.
6. `commands/analyze.md:101` `External link penalizing reach` → `External links correlate with lower reach`.
7. `references/first-comment-strategy.md:3` `without triggering algorithm penalties` → `without the lower-reach correlation of body links`.
8. `references/first-comment-strategy.md:10` `Avoids link penalty` → `Avoids the body-link reach correlation`.
9. `references/first-comment-strategy.md:147` `Avoid link penalty` → `Avoid body-link reach hit`.
10. `references/url-processing-templates.md:396` `Reach penalty` → `Correlates with lower reach`.
**VERIFY.**
- Re-grep final: `topic-relevance penalty|will penalize off-topic` in audit/trend-spotter → **NONE**;
`link penalty|penalizing reach|penalizes directing|triggering algorithm penalties|Reach penalty` in the
4 link-files → **NONE**. All 10 new SSOT-framed lines present.
- `git diff --stat` = **6 files, +10/10**.
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`, **exit 0**; counts **29/19**
unchanged (.md-only prose edits).
- Disposition: **FIXED** (10 edits, 6 files) · Bucket A complete · Bucket B → S28 · Bucket D → S30.
**SURFACED (out of scope → queued).** Bucket D unsourced-magnitude class promoted to **S30 magnitude-scrub**
(`docs/hardening/plan.md`). Bucket B link-penalty lines folded into **S28**'s pass over `linkedin-formats.md`.
---
### S28 ref-consistency B — format magnitudes + folded-in link lines → SSOT (`video.md` + `linkedin-formats.md`)
**CLASS: reference-consistency** (NOT re-hardening — surgical SSOT-reconciliation of format-magnitude
divergences across `commands/video.md` + `references/linkedin-formats.md`, incl. the 6 link-penalty lines
folded in from S27 Bucket B; one pass over the file). Predicate: no surface states an unsourced hard
format coefficient as fact — SSOT (`references/algorithm-signals-reference.md:51-63`, the "trust ordering,
test the number — never hard coefficients" rule `:12-23`) carries **no completion-rate gate**, no video ER,
no poll/Live multiplier; native video = "captions are the enforceable spec, no hard gate".
**TEST (tool-grounded).** Full read of `linkedin-formats.md` (674 l.) + `video.md:69` context. Plugin grep
`[0-9]+%|zero.distribution|penaliz|penalty|punish|multiplier|aspect ratio`. The "30% zero-distribution"
gate recurs **7×** (`video.md:69`; `formats:58,:291,:478,:543,:644,:662`). Catalog bucketed,
operator-approved (run buckets A+B+C+E now; D → S30):
- **A — "30% zero-distribution" hard-gate (7):** the named S28 divergence.
- **B — S27-folded link-penalty lines (6):** `formats:52,:91,:194,:198,:200,:432`.
- **C — other unsourced FORMAT magnitudes (6):** video 5.60% ER (`:56`), Live 12-24x (`:59,:594`), polls 1.64x (`:81`), 3x shares (`:273`), Creator-Mode 35% (`:674`). `:49` 24.42% left (already hedged + reconciled `:50`).
- **E — internal contradiction (1):** `:356` "3 seconds determine 70% of retention" contradicted the already-reconciled `:62/:100` folklore note → reframed.
- **D — S30-class in this file (deferred, operator's call):** `:6` 47-50% reach-decline, `:7` 15%→31%, `:176` AI-comment 30%/55%, `:231/:279` hashtags 68%. **Left intact**; S30 re-scoped to cover them + the other files.
**HARDEN (20 edits, 2 files — Buckets A+B+C+E).**
- **A:** `video.md:69` + `formats:58,:291,:478,:543,:644,:662` → "completion/dwell is a ranking input; shorter completes better; no published hard completion gate".
- **B:** `formats:52,:91,:198,:200,:432` → "correlate with lower reach" framing; `formats:194` heading `### The External Link Penalty` → `### External Links and Lower Reach`.
- **C:** `formats:56` `5.60% ER` → "engagement varies (no reliable single rate)"; `:59,:594` `12-24x` → "strong … (multiplier unverified)"; `:81` `1.64x` → "declining; audience research only (no reliable multiplier)"; `:273` `3x more shares` → "tend to earn more shares"; `:674` `35% more reach` → "tend to see more reach (figure unverified)".
- **E:** `formats:356` `3 seconds determine 70% of retention` → "opening seconds carry the most weight … three-second hook is folklore".
**VERIFY.**
- Re-grep final: assertive 30%-gate forms (`requires 30%|30% minimum completion rate or|video gets zero distribution|…`) → **NONE** (remaining "completion gate" hits are the *negated* SSOT form "no hard completion gate"); link penalty/penalized/punished in `formats` → **NONE**; stray magnitudes (`5.60%|12-24x|1.64x|3x more shares|35% more reach`) → **NONE**; `:356` old → **NONE**.
- Bucket D preserved: `47-50%`, `15% to 31%`, `68% reach`, AI-comment `30%/55%` still present (S30).
- `git diff --stat` = **2 files, +20/20**.
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`, **exit 0**; counts **29/19** unchanged.
- Disposition: **FIXED** (20 edits, 2 files) · Buckets A+B+C+E complete · Bucket D → S30.
**SURFACED (out of scope → queued).** Bucket D (this file's non-format coefficients) deferred to **S30
magnitude-scrub**, which is re-scoped to: `linkedin-formats.md` D-lines (`:6,:7,:176,:231,:279`) + the other
files (`linkedin-growth-playbook`, `glossary`, `engagement-coach`, `firsthour`, `post-feedback-monitor`,
`ab-testing-framework`, `poll-strategy-guide`). Pod/AI-slop "penalized" stays intact (officially confirmed).
---
### S29a terminology-scrub — "thought leadership" → neutral (`commands/`)
**CLASS: terminology-scrub** (NOT re-hardening — surgical removal of the operator-banned brand phrase
"thought leadership", FORM A, from the user-facing command surface; memory `no-thought-leadership-phrase`).
First sub-pass of the plugin-wide S29 scrub. Predicate: no command surface uses the phrase "thought
leadership"; the plugin is named **LinkedIn Studio**.
**SCOPE (operator-locked for ALL S29 sub-passes ae).**
- **FORM A only** ("thought leadership"). **FORM B** ("thought leader(s)" as role — 38 plugin-wide,
incl. the buzzword denylist `post.md:134`) = **untouched**.
- **Layers = user-facing surface + forward-looking docs.** Pure history (CHANGELOG, `hardening/log.md`+
`plan.md`, `voyage-build/*`, `remediation/*`) = untouched ("history → git"). Forward docs =
`docs/integration-test-guide.md` + `docs/expert-review/plan.md`.
- **Per-directory sub-passes, one per session.** Replacement map approved BEFORE edit.
- FORM A baseline = **140 in `.md`** (99 surface + 41 history) + 1 test-`.mjs`.
**TEST (tool-grounded).** Plugin grep `thought.leadership` (case-insensitive). `commands/` = 26 hits;
split: **11 filename pointers** to `references/thought-leadership-angles.md` (`post×3, video×2, batch×2,
react×1, pipeline×2, competitive×1`) → deferred to **S29e** (atomic rename + pointer update);
`competitive.md`/`pipeline.md` carry only pointers → untouched here. **15 in-file phrase occurrences** in
9 files = S29a scope. Read-and-show of all 15 in context before proposing the map.
**HARDEN (15 edits, 9 files — 1:1).**
- `post.md` (4): `:21` "LinkedIn thought leadership content creator" → "LinkedIn content creator";
`:64` "Select Thought Leadership Angle" → "Select Content Angle"; `:98` "Standard Thought Leadership
Structure" → "Standard Post Structure"; `:136` "thought leadership test" → "the insight test".
- `video.md` (1): `:146` "8 thought leadership angles" → "8 content angles".
- `strategy.md` (3): `:67` "thought leadership & authority" → "authority & influence"; `:234` table row
"Thought leadership pieces" → "Signature pieces"; `:395` "[Thought leadership milestone]" → "[Authority
milestone]".
- `batch.md` (1): `:113` "[thought leadership angle]" → "[content angle]".
- `setup.md` (2): `:256` "Which thought leadership angle" → "Which content angle"; `:323` "thought
leadership & authority" → "authority & influence".
- `react.md` (1): `:22` "into thought leadership posts" → "into insightful posts".
- `outreach.md` (1): `:813` "Demonstrate thought leadership depth" → "Demonstrate subject-matter depth".
- `onboarding.md` (1): `:18` "a LinkedIn thought leadership onboarding guide" → "the LinkedIn Studio
onboarding guide".
- `monetize.md` (1): `:18` "turn their thought leadership into revenue" → "turn their LinkedIn authority
into revenue".
**VERIFY.**
- Re-grep final, assertive non-filename FORM-A in `commands/` (`grep -v thought-leadership-angles`) →
**NONE**. Remaining `commands/` hits = 11, all filename pointers (S29e).
- FORM B in `commands/` count = **15**, unchanged (incl. `post.md:134` denylist).
- `git diff --stat` = **9 files, +15/15** (1:1, no collateral).
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`, **exit 0**; counts
**29/19/26/6** + v0.5.0 unchanged.
- Disposition: **FIXED** (15 edits, 9 files). Commit `e6db0dd`; pushed 2026-06-19 22:38 (operator OK, in
window).
**SURFACED (queued, not swept).** S29 remaining sub-passes: **S29b** `agents/` ← next · **S29c**
`references/` (in-file phrase; `thought-leadership-angles.md` body scrubbed but FILE renamed in S29e) ·
**S29d** `skills/`+`config/`+`assets/`+`README.md`+`CLAUDE.md`+`hooks/prompts/`+forward-docs+test-`.mjs` ·
**S29e** rename `references/thought-leadership-angles.md` + all 20 pointers atomically (new filename TBD).
---
### S29b terminology-scrub — "thought leadership" → neutral (`agents/`)
**CLASS: terminology-scrub** (NOT re-hardening — surgical removal of the operator-banned brand phrase
"thought leadership", FORM A, from the agent surface; memory `no-thought-leadership-phrase`). Second
sub-pass of the plugin-wide S29 scrub. SCOPE inherits the S29a operator-lock verbatim (FORM A only;
FORM B untouched; per-directory sub-pass, map approved before edit).
**TEST (tool-grounded).** `grep -in "thought leadership"` (space) in `agents/` = **15 in-file phrase
occurrences / 7 files**; the 11 `thought-leadership-angles.md` filename pointers (hyphenated) are invisible
to the space-grep and deferred to S29e. Read-and-show of all 15 in context before proposing the map.
**Discovery during the header anchor-check (`grep "Value Test"`):** 2 same-class instances the phrase-grep
missed because abbreviated — `trend-spotter.md:192/:257` "TL Value Test" (TL = Thought Leadership).
Surfaced as an addendum (#16#17), operator-approved BEFORE edit; scope not expanded silently.
**HARDEN (17 edits, 6 files — 1:1).** Vocabulary consistent with S29a: "thought leadership angle(s)" →
"content angle(s)"; "thought leadership" (positioning/practice) → "authority …"; the whole "Value Test"
family → "Authority Value Test".
- `strategy-advisor.md` (1): `:219` "Thought leadership and cross-platform visibility" → "Authority
building and …".
- `content-planner.md` (1): `:24` "sustainable thought leadership growth" → "sustainable authority
building".
- `voice-trainer.md` (1): `:24` "voice for LinkedIn thought leadership" → "… for LinkedIn authority
building".
- `network-builder.md` (1): `:26` "compound their thought leadership reach and influence" → "compound
their authority, reach, and influence".
- `differentiation-checker.md` (4): `:47` search query `"[topic]" thought leadership 2025 2026` →
`"[topic]" expert insights 2025 2026` (functional-keyword judgment-call, operator chose option b);
`:147` "marketing as thought leadership" → "marketing posing as expertise"; `:203` header "## Thought
Leadership Value Test" → "## Authority Value Test"; `:327` in-file "thought leadership test" → "authority
value test" (filename pointer `thought-leadership-angles.md` left for S29e).
- `trend-spotter.md` (9): `:5` frontmatter desc "suggest thought leadership angles" → "suggest content
angles"; `:24` "establish thought leadership positioning" → "establish authority positioning"; `:33`
"strongest thought leadership angle per trend" → "strongest content angle …"; `:156` "map to the
strongest thought leadership angle" → "… content angle"; `:179` "Future Implication for thought
leadership" → "… for authority positioning"; `:334` "create thought leadership value" → "create
authority value"; `:344` "is the thought leadership" → "is the expertise"; `:192` header "### TL Value
Test" → "### Authority Value Test"; `:257` "Run TL Value Test" → "Run Authority Value Test".
**VERIFY.**
- Re-grep final FORM A `thought leadership` (space) in `agents/` → **NONE**; `\bTL\b` → **NONE**.
- FORM B in `agents/` = **2**, unchanged (`analytics-interpreter.md:419` "(thought leader)",
`network-builder.md:562` "Thought Leader" search term).
- Filename pointers `thought-leadership-angles.md` in `agents/` = **11**, unchanged (S29e).
- 17 new replacement strings present (1:1 with edits).
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`, **exit 0**; counts
**29/19/26/6** + v0.5.0 unchanged.
- Disposition: **FIXED** (17 edits, 6 files). Commit `ebe56c5` (local; push parked per policy until operator OK).
**SURFACED (queued, not swept).** `references/glossary.md:229` "### Thought Leadership Value Test" → its
own directory pass **S29c** (temporary cross-directory naming gap until then — inherent to the
per-directory design). `differentiation-checker.md:278` "### Value Test: [X]/3 passed" is already neutral
(no FORM A / TL) and stays — consistent with the renamed header. Remaining S29 queue unchanged: **S29c**
`references/` · **S29d** misc · **S29e** rename + 20 pointers.
---
### S29c terminology-scrub — "thought leadership" → neutral (`references/`)
**CLASS: terminology-scrub** (NOT re-hardening — surgical removal of the operator-banned brand phrase
"thought leadership", FORM A, from the reference-doc surface; memory `no-thought-leadership-phrase`).
Third and largest sub-pass of the plugin-wide S29 scrub (~2× the S29a/S29b volume). SCOPE inherits the
S29a/S29b operator-lock verbatim (FORM A only; FORM B untouched; per-directory sub-pass, map approved
before edit).
**TEST (tool-grounded).** `grep -in "thought leadership"` (space) in `references/` = **35 phrase lines /
15 files** — 29 single-line across 14 files + the canonical `thought-leadership-angles.md` = 6 lines / 7
instances (line 7 ×2) ≈ **36 FORM A instances**. The 3 hyphenated `thought-leadership-angles.md` filename
pointers are invisible to the space-grep and deferred to S29e. Read-and-show of every occurrence in
context before proposing the 34-row map. Anchor-check (`grep "Value Test"` / `#thought-leadership` /
`#the-standard-thought`): **no anchor links** to the changed headers anywhere in the plugin, and the only
"Value Test" / "Standard Thought Leadership Structure" term-hits were the two SSOT spots being edited — no
hidden same-class instances.
**S29e filename locked (operator, this session): `thought-leadership-angles.md` → `content-angles.md`.**
The canonical file's in-file title/headers were therefore scrubbed now for consistency (H1 →
"# Content Angles"; "## The Authority Value Test"; "### Step 3: Test For Authority Value"); the file
rename + all 20 pointers remain S29e (atomic).
**HARDEN (34 edits, 15 files — 1:1).** Vocabulary consistent with S29a/S29b: "thought leadership
angle(s)" → "content angle(s)"; "thought leadership" (positioning/practice) → "authority …"; content-type
labels (lists/tables) → "authority content / authority posts"; the "Value Test" family → "Authority Value
Test".
- Established cross-pass equivalents closed: `glossary.md:229` "### Thought Leadership Value Test" →
"### Authority Value Test" (closes the S29b cross-directory naming gap); `glossary.md:29` "8 universal
thought leadership angles" → "content angles"; `engagement-frameworks.md:137` "Standard Thought
Leadership Structure" → "Standard Post Structure" (matches S29a `post:98`).
- `thought-leadership-angles.md` (6 edits / 7 instances): H1 → "# Content Angles"; :3 "identifying
thought leadership angles" → "content angles"; :7 (×2) "Thought leadership isn't about…/can become
thought leadership" → "Authority isn't about…/can build authority"; :92 "Test For Thought Leadership
Value" → "Test For Authority Value"; :212 "Marketing disguised as thought leadership" → "expertise";
:215 "## The Thought Leadership Test" → "## The Authority Value Test".
- `linkedin-formats.md` (5): :24/:79/:272/:309 content-type labels → "authority content/posts"; :295
"Text-based thought leadership often outperforms…" → "Text-based content …" (avoids authority…authority
echo).
- `linkedin-visual-style.md` (4): :3 "For thought leadership," → "For building authority,"; :7 "thought
leadership niche" → "authority niche"; :82 label → "authority content"; :123 "myth for thought
leadership content" → "authority content".
- `trajectory-strategy-adjustments.md` (3): :167 "Shift to thought leadership" → "authority content";
:202 "100% thought leadership and signature content" → "100% authority and signature content"; :217
"thought leadership deepening" → "authority deepening".
- `linkedin-monetization-strategies.md` (3): :285 "Inbound from thought leadership" → "authority
building"; :624 "80% thought leadership" → "authority content"; :658 "Breaks the thought leadership
positioning" → "authority positioning".
- `ai-content-framework.md` (2): :3 "build thought leadership in the AI space" → "authority"; :380 "main
LinkedIn thought leadership skill" → "content skill" (judgment-call; avoids awkward "authority skill").
- Single-edit files (8 edits / 8 files): `ab-testing-framework.md:9` → "authority building";
`growth-roadmaps.md:222` "Thought leadership pieces" → "Authority pieces";
`linkedin-growth-playbook-2025-2026.md:822` → "Reflective authority content";
`algorithm-signals-reference.md:158` (SSOT) "Text for thought leadership" → "Text for authority"
(phrase only; magnitudes = S30); `scheduling-strategy.md:35` → "Mid-week authority content";
`poll-strategy-guide.md:179` "thought leadership angle" → "content angle"; `video-strategy-guide.md:3`
→ "authority content"; `video-strategy-guide.md:580` "8 thought leadership angles" → "8 content
angles".
**KEPT BY DESIGN (operator-approved).** `video-strategy-guide.md:429` ironic quote "Your 'thought
leadership' content isn't leading anyone's thoughts." — scrubbing destroys the point.
`video-strategy-guide.md:532` "TL;DW" = false positive (too long, didn't watch).
**VERIFY.**
- Re-grep final FORM A `thought leadership` (space) in `references/` → only the kept ironic quote
(`video-strategy-guide.md:429`); canonical-file in-file FORM A → **NONE**.
- FORM B in `references/` = **4**, unchanged (`collaborations-guide.md:211`, `analytics-tools-guide.md:227`,
`opportunity-generation.md:81`, `linkedin-visual-style.md:134`).
- Filename pointers `thought-leadership-angles.md` in `references/` = **3**, unchanged (S29e).
- New terms present: "Authority Value Test" (`glossary.md:229` + `thought-leadership-angles.md:215`,
consistent), "Standard Post Structure" (`engagement-frameworks.md:137`).
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`, **exit 0**; counts
**29/19/26/6** + v0.5.0 unchanged.
- Disposition: **FIXED** (34 edits, 15 files). Commit `b9b769c` (local; push parked per policy until
operator OK).
**SURFACED (queued, not swept).** Remaining S29 queue: **S29d** `skills/` + `config/` + `assets/` +
`README.md` + `CLAUDE.md` + `hooks/prompts/` + forward-looking docs + test `.mjs` · **S29e** rename
`thought-leadership-angles.md` → `content-angles.md` + all 20 pointers (atomic; filename locked). The
canonical file's in-file phrase scrub is done, so S29e is now a pure rename + pointer-update with no
remaining FORM A inside the file.
---