linkedin-studio/docs/hardening/log.md
Kjell Tore Guttormsen 0155d9eb85 docs(linkedin-studio): S29b hardening log entry — terminology-scrub agents/
Append the S29b verification record to docs/hardening/log.md (anchored
"### S29b terminology-scrub" entry, matching S29a format): the 17-edit replacement
map, the abbreviated-"TL" discovery during the header anchor-check + operator-approved
addendum (#16–#17), and the verify block (FORM A in agents/ = NONE; abbreviated TL =
NONE; FORM B unchanged 2; filename pointers unchanged 11; gate 81/0/0; counts
29/19/26/6 unchanged). Documents fix commit b6ba826.

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

151 KiB
Raw Blame History

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.shFailed: 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:134added Step-5 checklist line: - [ ] No corporate buzzwords (leverage, synergy, paradigm shift, thought leader, disruptive, value proposition, ecosystem, holistic approach).
  • assets/checklists/quality-scorecard.md:32Works in 140 charactersWorks in 110-140 characters (Notes Mobile thresholdMobile band (floor + ceiling)).
  • assets/checklists/quality-scorecard.md:114Hook works in 140 chars?Hook works in 110-140 chars?.
  • assets/checklists/quality-scorecard.md:132added 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.shPassed: 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 ✓ (:137agents/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:131added 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:60added 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:194added 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.shPassed: 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:28added 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:45added 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.shPassed: 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).

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.

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 linegrep -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:123extended 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.shPassed: 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:129extended 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.shPassed: 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.shPassed: 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 (:75quality-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.shPassed: 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.mjslinkedin/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.shPassed: 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-reviewerfact-checker, language-reviewereditorial 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.shPassed: 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-sweepconsistency-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.shPassed: 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.shPassed: 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_addsnake_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.shPassed: 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:reportcommands/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.shPassed: 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.shPassed: 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): PASSlinkedin-studio:analytics-interpreter resolves; interpret-mode is real/default (agent:6,39,141); shared seam ${LINKEDIN_STUDIO_DATA:-…}/analytics/ (analyze:41agent: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.mdNONE; 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.shPassed: 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.

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.mdNONE (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.shPassed: 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): PASScontent-optimizer resolves; agents/content-optimizer.md name: matches the namespaced invocation form.
  • P2 (routing): PASSpost/report/analyze.md all resolve.
  • P3 (intent refs): PASSab-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.shPassed: 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.mdNONE; allowed-tools block = Read/Glob/Grep/AskUserQuestion/Task.
  • git diff --stat = 1 file, 1.
  • bash scripts/test-runner.shPassed: 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): PASSgrep -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.mdNONE; allowed-tools block = Read/Glob/WebSearch/AskUserQuestion.
  • git diff --stat = 1 file, 1.
  • bash scripts/test-runner.shPassed: 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 WebFetchNONE; grep -n WriteNONE; 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.shPassed: 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 PASSTask subagent_type: linkedin-studio:network-builder (:172) resolves (agents/network-builder.md). D2 PASSrecordOutreachContact 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 WebFetchNONE, ^ - Write$NONE, ^ - Bash$:21; B1 old form → NONE (new :651); B2 30-55%|Post-March 2025|penalizes engagement pods|credibility score|Low dwell|Shadow suppressionNONE (SSOT text :928-929). references/collaborations-guide.md: 90+ days|6+ months|never fully recover|permanent reach penalty|Shadow banning affects ALL|March 2025 CrackdownNONE (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.shPassed: 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.shPassed: 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 resolvecommands/{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: GlobNONE; allowed-tools = Read + AskUserQuestion (:13-14).
  • git diff --stat = 1 file, 1.
  • bash scripts/test-runner.shPassed: 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 resolvecommands/{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.shPassed: 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.shPassed: 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.


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 contentTopic relevance is a ranking input — off-topic content gets lower reach.
  3. agents/trend-spotter.md:353 topic-relevance penalty, damages authorityoff-topic content gets lower reach, damages authority.
  4. references/troubleshooting-guide.md:15 External link penalizing reachExternal links correlate with lower reach.
  5. references/troubleshooting-guide.md:127 LinkedIn penalizes directing traffic awaybody links correlate with lower reach (LinkedIn denies an intentional penalty; lead with value).
  6. commands/analyze.md:101 External link penalizing reachExternal links correlate with lower reach.
  7. references/first-comment-strategy.md:3 without triggering algorithm penaltieswithout the lower-reach correlation of body links.
  8. references/first-comment-strategy.md:10 Avoids link penaltyAvoids the body-link reach correlation.
  9. references/first-comment-strategy.md:147 Avoid link penaltyAvoid body-link reach hit.
  10. references/url-processing-templates.md:396 Reach penaltyCorrelates 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.shPassed: 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.


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 formatsNONE; 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.shPassed: 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.shPassed: 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\bNONE.
  • 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.shPassed: 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.