linkedin-studio/docs/hardening/log.md
Kjell Tore Guttormsen adb8c5bde3 docs(linkedin-studio): S10 harden headless-review — verified clean, no change (4 axes PASS)
headless-review is the standalone surface of newsletter Step 6.5: a cold
adversarial review-orchestrator, NOT post-emitting — it emits a consolidated
review report + optional edition-state persist, never drafts prose, never
publishes a feed post, so the feed-post predicates (hook 110-140, 1,200-1,800
band, no-body-link) do not apply (even further from feed-post than newsletter).
The 4 archetype agents are shared with newsletter Step 6.5 -> S9 baseline reused.
Ran the 7 predicates tool-grounded against the final file (248 lines):

- 4 namespaced subagent types resolve (frontmatter name: match)
- 8/8 Reference Files exist (:241-248)
- always-present fallback longform-quality-rules.md exists
- articles.NN.headlessReview schema complete in edition-state.template.json
  (:56-69, ladder pending->run->folded)
- 5 degradation points (freeze cp :130 / draft ask-once :104 / persona :117 /
  [GATE] re-run :163-164 / output-default :93 + SendUserFile-else-file:// :208-210)
- persona resolution chain = newsletter Step 1 (:179-183)
- "five archetypes" (line-wrapped :5-6/:30-31) = 5 modes from 4 agents

Borderline at talk: freeze (:126) assumes review/ exists (no mkdir -p).
Operator chose Alt A (no change) — byte-identical to newsletter :1050 (S9-clean);
review/ is created by the render pipeline that produces the draft. Logged as a
NICE below the axis-pass bar, not pursued.

4-axis verdict: a/b/c/d all PASS, 0 gaps, 0 deferrals -> NO CHANGE.

Gate: test-runner 81/0/0 · counts 29/19/26 unchanged · headless-review.md
byte-identical to 3cd174c (S9); only docs/hardening/log.md added.

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

941 lines
70 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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

# LinkedIn Studio — Command Hardening Log
> Per-command audit trail for the hardening phase (`docs/hardening/brief.md` +
> `plan.md`). One **anchored** entry per command surface. The operator's parallel
> live-testing cross-references every change here.
>
> **Entry contract (SC-A…SC-E).** Each entry begins with the UNIQUE anchored header
> `### /linkedin:<command-name> — <one-line intent>` (coverage greps `^### /linkedin:<name>`,
> never the bare word). Every entry carries: **INTENT** · **SIMULATE** (persona + the
> CONCRETE before-output + friction log) · **EVALUATE** (4 axes, with the per-type
> **mechanical predicate** — never "N/A → judgment") · **HARDEN** (surgical diff +
> concrete after-output, or "no edit — passes") · **VERIFY** (lint `Failed: 0` + counts
> + the failing axis now passes). The cold `/trekreview` reviewer adjudicates every
> hardened command's before/after — the author does not self-certify.
>
> **Mechanical-predicate classes:** *post-emitting* (hook 110140 · length band ·
> no body link · no banned buzzword · topic→5 pillars) · *routing* (every emitted
> target resolves to a real `commands/<x>.md`) · *analytics* (graceful-degradation
> present + saves/dwell honesty intact) · *guided/stateful* (primary promised artifact
> actually produced; promised `subagent_type` targets resolve).
>
> **Stopping rule (anti-gold-plating):** harden until every axis returns pass OR a
> recorded deferral; no NICE-only polish beyond axis-pass.
>
> **Method discipline (learned the hard way in the S1 calibration — three stumbles).**
> Write the HARDEN / after-output section **only from the applied + re-grepped diff**, and
> assert a gap **only after reading the actual file line**. Every number (char counts,
> edit counts) must come from a tool, not from memory. The S1 calibration produced three
> assert-before-verify errors — (1) a non-existent inline buzzword list in `quick`, (2) a
> non-existent "unused Task" in `quick`, (3) `onboarding`/`first-post` edits asserted as
> landed when the Edits had failed on wrong strings, plus a false "25 commands → stale"
> claim (the file already said 29). All were caught by failed Edits / empty greps / the
> git status, and corrected here. This is exactly the failure mode the independent
> `/trekreview` oracle exists to catch — verify-before-assert is part of the method, not optional.
---
## Session 1 — Method calibration (`quick`) + Start journey
> S1 status: calibration corrected + method locked (operator, 2026-05-31). onboarding /
> first-post / setup follow below under the same entry shape. Field-notes inbox:
> **absent** at S1 start → graceful no-op (SC-I).
### /linkedin:quick — 5-minute 3-line post from a topic, ≤1 question, clipboard-ready
**INTENT.** `quick` is the Create journey's speed path: a topic in → a publishable
150500-char post out in ~5 minutes, max one question, auto-copied to clipboard. It must
honor the content-quality bar mechanically (hook **110140**, length **150500**, no body
links, no banned buzzword, topic maps to one of the 5 pillars, exactly one CTA) and the
algorithm bar (hook decisive before the mobile fold; links to first comment not body;
native; a CTA that invites comments without manufactured engagement-bait). Its defining
constraint is *speed* — "skip interrogation, generate immediately."
**SIMULATE.**
- **Persona (ICP):** the author — 1048 followers, "Validation", 5 pillars; voice =
direct/technical/low-formality, NO/EN mix, short sentences, contrarian openings
(grounded in the state file + the gitignored `authentic-voice-samples.local.md` the
command reads at runtime). `recent_posts: []` → no rotation conflict. *Fresh-adopter
path (no voice samples) reasoned, not separately re-drafted: `quick` reads voice from
SKILL.md and falls back to defaults; the personalization score is hidden < 3 posts and
the voice guardian is suppressed < 5 samples — degrades cleanly, no block, no dead-end.*
- **Invocation:** `/linkedin:quick AI-kodeassistenter i offentlig sektor`
(pillars "AI-rådgivning i offentlig sektor" / "Claude Code / agentisk koding").
- **Walk:** topic from `$ARGUMENTS` (no question) → load voice + pillars → infer type
(HOT TAKE — contrarian) → 3-line formula → auto-CTA → quality check →
de-AI/differentiation gate (skip unless commodity) → clipboard → present.
- **CONCRETE before-output** (produced under `quick.md` **as written**, pre-fix):
> Alle vil ha AI-kodeassistenter i staten. Men hvem tar ansvaret når koden tar feil i
> produksjon?
>
> Verktøyet skriver koden på sekunder. Ansvaret for at den er riktig flyttes ikke — det
> blir ditt, raskere enn før.
>
> Hvordan kvalitetssikrer dere AI-generert kode hos dere?
*(Pillar: Claude Code / agentisk koding · type: HOT TAKE · **hook = 95 chars** (node-verified) ·
body has 0 links / 0 buzzwords.)*
- **Friction log:** the **95-char hook passes `quick.md`'s own checks** — Step 2 said
"Hook (under 140 characters)" and the Step 5 checklist asked "Hook works in 140 chars?",
**both upper-bound only** — yet it breaks the canonical **110** floor (the PreToolUse gate:
"under 110: wasting prime real estate"). Same one-sidedness on length ("Under 500?" vs
the 150500 band). The Step 5 checklist also had **no buzzword line**, and a quick post is
auto-copied to clipboard (no file Write), so the PreToolUse *file* gate may not fire — the
checklist is the load-bearing surface. No dead-ends.
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** One-question speed path, 3-line formula, 8 templates,
auto-CTA, clipboard, why-hook/reach tips, `/linkedin:post` upgrade path — delivers the promise.
- **(b) algorithm bar — PASS.** No body link (links → first comment); native; CTA invites
comments (comments > reactions) without bait; de-AI gate cites the confirmed low-substance
down-rank. Consistent with `references/algorithm-signals-reference.md`.
- **(c) MECHANICAL predicate (post-emitting) — GAP → fixed.** no body link ✓ · topic→pillar ✓
· **hook band ✗** (spec + checklist enforced ≤140, omitted the 110 floor; the 95-char
before-hook is the live proof) · **length band ✗** (checklist enforced <500, omitted the
150 floor) · **buzzword ✗** (no checklist line; clipboard path bypasses the file gate).
- **(d) agent-wiring + graceful degradation — PASS (verified).** `quick` **conditionally**
delegates to `differentiation-checker` via `Task` (`quick.md:151`,
`subagent_type: linkedin-studio:differentiation-checker`) — only when the take is commodity,
preserving the 5-minute promise by default. `Task` in `allowed-tools` is therefore *used*,
not vestigial. Fresh-adopter degradation works (defaults; guardian suppressed < 5 samples).
**HARDEN (surgical — 2 edits, axis-c only — both grep-confirmed landed).**
- `commands/quick.md:68``**Line 1: Hook (under 140 characters)**``**Line 1: Hook (110-140 characters)**`.
- `commands/quick.md` Step 5 checklist — hook check `"Hook works in 140 chars?"`
`"Hook in the 110-140 band (not just under 140)?"`; length check `"Under 500 characters?"`
`"In the 150-500 band (not just under 500)?"`; **added** `"No corporate buzzwords?"`; count
`**All 6 = Yes?**``**All 7 = Yes?**`.
- **Deferrals:** none (the earlier "incomplete buzzword list" and "unused Task" findings were
file-misreads, struck — see Method discipline).
- **CONCRETE after-output** (under the hardened spec — hook expanded into the 110140 band):
> Alle vil ha AI-kodeassistenter i staten. Få spør hvem som tar ansvaret når assistenten
> foreslår noe ingen kan forklare etterpå.
>
> Verktøyet skriver koden på sekunder. Ansvaret for at den er riktig flyttes ikke — det
> blir ditt, raskere enn før.
>
> Hvordan kvalitetssikrer dere AI-generert kode hos dere?
*(**hook = 127 chars** (node-verified, in band) · 0 body links · 0 buzzwords · same pillar.)*
**VERIFY.**
- `bash scripts/test-runner.sh``Failed: 0` + exit 0 + counts 29/19/25/6 unchanged
(re-run AFTER the real edits; recorded at the session gate).
- Before/after delta: axis (c) failing sub-checks (hook 110 floor, length 150 floor,
buzzword) now pass — the 95-char before-hook slips `quick.md`'s old checks; the hardened
checklist catches it and the after-hook is 127 chars, buzzword-clean. The two edits are the cause.
- Disposition: **HARDENED** (2 edits, axis-c) · 0 deferrals · axes a/b/d PASS.
---
### /linkedin:onboarding — zero→published first post as one guided wizard (Start front-door)
**INTENT.** Multi-phase wizard taking a brand-new user from zero to a published first post
as one cohesive flow (profile → personalization → first-post), so they don't navigate the
full command surface alone. Primary artifacts: a saved profile + a drafted first post (the
S15-B1 inline-draft). Guided/stateful; inlines rather than spawning subagents.
**SIMULATE.**
- **Persona (fresh adopter — the right persona here):** just installed, no profile, no voice
samples, `recent_posts: []`.
- **Invocation:** `/linkedin:onboarding`.
- **Walk:** Phase 0 already-onboarded check → Phase 1 profile/topic-relevance checklist →
Phase 2 personalization (voice + user profile, or defaults when < 3 posts) → Phase 3 first
post (3.1 topic → 3.2 3-line draft → 3.3 quality check → 3.4 present+clipboard → 3.5 record)
→ Phase 4 summary. **CONCRETE artifact produced:** a refined first-post inline draft (S15-B1
path — **spot-confirmed still delivers:** Phase 3.2 "Draft the post… Line 1/2/3", 3.4
"present and copy").
- **Friction log:** Phase 3.2 said "Line 1 — Hook (**under 140** chars)" and the Phase 3.3
check asked "Hook works in **140** chars?" — **both upper-bound only**, so a 95-char hook
would pass while breaking the 110 floor. (Phase 3.2 line 204 already states "150-500
characters" + "No external links in the post body", so length + links were **NOT** gaps —
only the hook floor.) The description + Phase-4 tip hardcode "29 commands" — **accurate
today, not stale** (an earlier "25 commands" claim was a misread, struck).
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** Cohesive zero→post wizard; S15-B1 inline draft holds (Phase 3).
- **(b) algorithm bar — GAP → fixed.** Phase 3 emitted a post but enforced only the hook upper
bound; now the full 110140 band (length 150500 + no-body-links were already present).
- **(c) MECHANICAL predicate (guided/stateful) — PASS; one post-emitting sub-check fixed.**
Primary artifact (first-post draft) produced ✓; the one-sided hook bound in Phase 3.2 + 3.3 closed.
- **(d) agent-wiring + graceful degradation — PASS.** No `Task` in `allowed-tools` — onboarding
inlines every step (Phase 2 "delegate to setup" = inline its logic; commands aren't subagents,
so no broken `subagent_type`). Built for the no-profile path; degrades cleanly.
**HARDEN (surgical — 2 edits, hook floor — both grep-confirmed landed).**
- `commands/onboarding.md:200``**Line 1 — Hook (under 140 chars):**``**Line 1 — Hook (110-140 chars):**`.
- `commands/onboarding.md:209` (Phase 3.3 check) — `Hook works in 140 chars?``Hook in the 110-140 band (not just under 140)?`.
- **Deferred (NICE-only, stopping rule):** the hardcoded "29 commands" (description + Phase-4
tip) is correct now; making it count-free is drift-proofing, not an axis fix → recorded, not edited.
**VERIFY.** lint `Failed: 0` + counts unchanged (recorded at gate); S15-B1 inline-draft
spot-confirmed; hook floor now enforced in Phase 3.2 + 3.3. Disposition: **HARDENED** (2 edits) ·
1 recorded deferral.
---
### /linkedin:first-post — zero→published in <10 min, maximum hand-holding
**INTENT.** First-post accelerator: from "never posted" to "just published" in <10 min,
breaking the blank-page barrier. Produces one published first post. Guided/stateful; inlines.
**SIMULATE.**
- **Persona (fresh adopter):** no profile (Step 2 offers a voice quick-setup or 5-question
calibration; proceeds either way — momentum over completeness).
- **Invocation:** `/linkedin:first-post`.
- **Walk:** Step 1 welcome → Step 2 voice setup (samples or 5 Qs; graceful if none) → Step 3
topic (5 angles) → Step 4 write (3-line formula) → Step 5 simplified quality check → Step 6
present + clipboard → Step 7 record → Step 8 first-hour engagement guidance.
- **CONCRETE before (the gap):** Step 4 "Line 1: Hook (**under 140** characters)" and the Step 5
check "Hook works in **140** chars?" were **both upper-bound only** — a 95-char hook would pass.
(Step 4 already states "Target: 150-500 characters" and "No external links in the post body",
so length + links were **NOT** gaps — only the hook floor.)
- **Friction log:** the hook bound was one-sided in Step 4 + Step 5; length + no-links already covered.
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** Delivers zero→published with hand-holding; first-hour guidance present.
- **(b) algorithm bar — PASS.** First-hour engagement window cited; no-body-links already in Step 4 tips.
- **(c) MECHANICAL predicate (post-emitting) — GAP → fixed.** Hook bound was upper-only in Step 4 +
Step 5; now the full 110140 band (length 150500 + no-body-links already present).
- **(d) agent-wiring + graceful degradation — PASS.** No `Task`; inlines; no-profile/no-samples
path graceful. Differentiation-checker deliberately skipped (a first post optimizes for momentum).
**HARDEN (surgical — 2 edits, hook floor — both grep-confirmed landed).**
- `commands/first-post.md:101``**Line 1: Hook (under 140 characters)**``**Line 1: Hook (110-140 characters)**`.
- `commands/first-post.md:125` (Step 5 check) — `Hook works in 140 chars?``Hook in the 110-140 band (not just under 140)?`.
- **Deferrals:** none.
**VERIFY.** lint `Failed: 0` + counts unchanged; Step 4 ↔ Step 5 hook bound now consistent (both 110-140).
Disposition: **HARDENED** (2 edits) · 0 deferrals.
---
### /linkedin:setup — guided personalization (5 pillars + voice profile + prefs)
**INTENT.** Build the user's voice profile, expertise pillars, and content preferences into
the state/asset files so every post sounds like them. Primary artifact: a populated voice
profile + populated asset templates (8-category personalization score). Guided/stateful;
delegates voice to an agent.
**SIMULATE.**
- **Persona (fresh adopter):** no existing data (all templates at placeholder).
- **Invocation:** `/linkedin:setup`.
- **Walk:** Step 0 calculate score → Step 1 dashboard → Step 2 choose what to set up → Step 3a3f
sub-workflows (voice / case study / framework / post analysis / demographics / user profile) →
Step 4 recalculate → Step 5 continue or exit. **CONCRETE artifact:** e.g. Step 3a writes a real
voice profile to `assets/voice-samples/authentic-voice-samples.md` (placeholder sentinel removed).
- **Friction log:** none — no dead-ends, no ambiguous steps.
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** Delivers the personalization the description promises (score + 6 sub-flows).
- **(b) algorithm bar — PASS (n/a-direct).** Emits no post; the expertise/pillar capture is the
topic-relevance foundation the bar depends on.
- **(c) MECHANICAL predicate (guided/stateful) — PASS.** Primary artifact (populated profile/assets)
produced ✓; the promised `subagent_type: linkedin-studio:voice-trainer` (`setup.md:86`) **resolves**
to a real `agents/voice-trainer.md` (verified).
- **(d) agent-wiring + graceful degradation — PASS.** voice-trainer invoked via `Task` (correct
namespaced type); the placeholder-sentinel logic is explicit; samples optional → degrades if none.
**HARDEN.** **No edit — passes all four axes.** (Demonstrates a legitimate zero-edit pass; per the
plan, "command file modified" is not a coverage predicate — the `log.md` entry + `/trekreview` are.)
**VERIFY.** lint `Failed: 0` + counts unchanged; voice-trainer target resolves.
Disposition: **PASS, no edit** · 0 deferrals.
---
## Session 2 — Create I (`post`)
> S2 status: the v1 autonomous attempt fabricated and was reverted (→ v2 method; see `plan.md`
> header). This is the v2 re-run: parked at step 5 across a `/clear`, re-grounded from STATE.md's
> two P4 findings against the actual file lines before talking (jerndisiplin: assert-after-read).
> Field-notes inbox **absent** at S2 → graceful no-op (SC-I).
### /linkedin:post — interactive full-workflow post (angle→write→quality→refine→publish), 12001800 chars
**INTENT.** `post` is the Create journey's full path: from raw material (data / article / experience /
observation / opinion / question) to a publishable 1,2001,800-char thought-leadership post via an
8-step workflow — angle → format → structure+write → quality check → present → refine → pre-publish
→ deterministic state update. Post-emitting, so it must honor the content bar mechanically (hook
**110140**, length **1,2001,800**, no body links, no banned buzzword, topic→5 pillars) and the
algorithm bar (hook decisive before the mobile fold, links→first comment, native, comment-inviting
CTA without bait, de-AI substance signals).
**SIMULATE.**
- **Persona (ICP):** the author — 1048 followers, "Validation", 5 pillars; direct/technical NO/EN voice.
- **Invocation:** `/linkedin:post about AI-kodeassistenter i offentlig sektor`.
- **Walk:** Step 0 load state/SKILL → Step 1 type=Opinion → Step 2 angle (contrarian) → Step 3 medium
text → Step 4 structure (hook 110140 ✓ internally) → **Step 5 quality check (the gap)** → Step 6
present+clipboard → Step 7 refine → Step 8 pre-publish + state.
- **CONCRETE friction (spec-level, the live proof — anchored, not a generated post):** at **Step 5**
the quality checklist (`post.md:127135`, 9 items) had **no buzzword line**, so a draft opening
*"How to leverage AI to drive synergy in the public sector…"* would pass Step 5 unflagged — the
banned-list (content rule #4, `CLAUDE.md:124`) was never enforced at the gate. Step 5 also routes to
`quality-scorecard.md` (`post.md:125`), which **self-contradicted on the hook bound**: l.32 "Works in
**140** characters" + l.114 "Hook works in **140** chars?" (upper-only) vs l.128 "**110-140**" — the
same one-sided bound S1 fixed in the command files, surviving in the shared asset.
- **Friction log:** `post.md` itself is internally consistent on the hook (110140 at l.100/113/127);
the only command-side gap is the missing Step-5 buzzword line. The shared scorecard carried both
gaps (buzzword absent + two one-sided hook bounds). Scorecard is shared by 8 surfaces
(`pipeline · video · post · react · batch` + 3 skills) — fixing it is high-leverage, operator-approved.
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** Full 8-step workflow delivers the description's promise.
- **(b) algorithm bar — PASS.** No body link (`post.md:133/176`); comment-inviting CTA; de-AI gate
cites the confirmed reach-suppression (`post.md:137141`). Consistent with `algorithm-signals-reference.md`.
- **(c) MECHANICAL predicate (post-emitting) — GAP → fixed.** hook band ✓ · length band ✓ · no-body-link
✓ · topic→pillars ✓ · subagent resolves ✓ · **buzzword ✗** (Step-5 checklist had no line; grep NONE).
Shared scorecard added two sub-gaps: hook one-sided at l.32 + l.114.
- **(d) agent-wiring + graceful degradation — PASS.** `differentiation-checker` (`post.md:141`,
conditional) + `content-optimizer` (`post.md:166`, conditional) both resolve, both correctly
`subagent_type: linkedin-studio:`-namespaced. Voice→defaults; fresh-adopter path graceful.
**HARDEN (surgical — 4 edits across 2 files, all grep-confirmed landed).**
- `commands/post.md:134`**added** Step-5 checklist line: `- [ ] No corporate buzzwords (leverage,
synergy, paradigm shift, thought leader, disruptive, value proposition, ecosystem, holistic approach)`.
- `assets/checklists/quality-scorecard.md:32` — `Works in 140 characters` → `Works in 110-140
characters` (Notes `Mobile threshold` → `Mobile band (floor + ceiling)`).
- `assets/checklists/quality-scorecard.md:114` — `Hook works in 140 chars?` → `Hook works in 110-140 chars?`.
- `assets/checklists/quality-scorecard.md:132` — **added** Pre-Publish Basic Checklist line:
`- [ ] No corporate buzzwords (…)` (same banned list).
- **Deferrals:** none. **Scope note:** the 3 scorecard edits improve all 8 consumers; only `post` is
hardened this session — `react/batch/video/pipeline` get their own EVALUATE in S3/S5/S6/S8.
**VERIFY.**
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`; counts **29/19/26/6** unchanged.
- Anti-check grep: no one-sided bare `140 char` hook bound remains in the scorecard (`110-140` at
l.32/114/128). Buzzword line present in both files (`post.md:134`, `quality-scorecard.md:132`).
- Before/after delta: axis (c) buzzword sub-check now enforced at the load-bearing Step-5 gate; the
scorecard hook self-contradiction is resolved (all three mentions banded). The 4 edits are the cause.
- Disposition: **HARDENED** (4 edits, axis-c + shared-asset consistency) · 0 deferrals · axes a/b/d PASS.
---
## Session 3 — Create I (`react`)
> S3 status: re-grounded STATE's S3 predicate against the actual `react.md` lines BEFORE talk
> (jerndisiplin: assert-after-read). Field-notes inbox **absent** at S3 → graceful no-op (SC-I).
> All `file:line` below are tool-grounded against the FINAL (post-edit) file.
### /linkedin:react — URL-to-post pipeline (single + comparison), your-take-as-hook, 1,2001,800 chars
**INTENT.** `react` is the Create journey's **URL→post path**: fetch external content
(article / news / research / YouTube), extract the key points, pick an angle, and draft in the
author's voice. Post-emitting (medium **1,2001,800**), two paths — single-URL (Steps 18) +
comparison/multi-URL (Steps 1b8b). Must honor the content bar mechanically (hook **110140**,
length **1,2001,800**, no body link, no banned buzzword, topic→5 pillars) and the algorithm bar
(URL→first comment not body, post stands alone, *your reaction is the hook* not a summary,
comment-inviting CTA, de-AI substance). **Distinct from `post`/`quick`:** the value-add is YOUR
perspective *extending* the source, not echoing it — the commodity-reaction guard (differentiation-checker).
**SIMULATE.**
- **Persona (ICP):** the author — 1048 followers, "Validation", 5 pillars; direct/technical NO/EN voice.
- **Invocation:** `/linkedin:react <public-sector-AI news URL>`.
- **Walk:** Step 0 load → Step 1 single URL → Step 2 WebFetch extract → Step 3 classify (Product
Launch) → Step 4 angle (First-Take Analysis) → Step 5 draft (hook 110140 ✓) → **Step 6 quality
check (the gap)** → Step 7 present+clipboard → Step 8 state.
- **CONCRETE friction (spec-level, anchored — not a generated post):** at **Step 6** the quality
checklist (pre-edit `react.md:122128`, 7 items) enforced hook/links/standalone/voice/charcount/
topic/CTA but had **no buzzword line**, so a draft opening *"This launch lets agencies leverage AI
to unlock synergy across the value proposition…"* passes unflagged — the banned-list (content rule
#4, `CLAUDE.md:124`) was never enforced at the gate. **Step 6b** (`:237` "Same as Step 6, plus")
inherits the hole. `react` auto-copies to clipboard (`:144148`, no file Write) → the PreToolUse
**file** gate may not fire → Step 6 is the load-bearing surface. **Same gap S2 fixed in `post`.**
- **Second friction (axis d, URL-pipeline-specific):** `grep` for fetch-failure/paywall/transcript/
fallback → **NONE**. Step 2 (`:54`) "Use WebFetch to extract" had **no fallback** when extraction
yields nothing (paywall / JS-heavy SPA / **YouTube-no-transcript** — an explicitly accepted input,
`:37/:71`); Step 2b (`:185`) same. Risk: proceed on a thin/empty extraction or fabricate from the URL string.
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** Both paths, angle selection, your-take-not-summary, commodity
guard, clipboard, URL→first comment — delivers the description's promise.
- **(b) algorithm bar — PASS.** No body link (`react.md:115/125/235`); URL→first comment
(`:116/:144/:253`); stands alone (`:118/:126`); take-as-hook (`:101/:118`); de-AI gate bans
mechanical engagement-bait (`:135137`). Consistent with `references/algorithm-signals-reference.md`.
- **(c) MECHANICAL predicate (post-emitting) — GAP → fixed.** hook band ✓ (`:101/:124/:221`) ·
length band ✓ (`:119/:128/:239`) · no-body-link ✓ (`:115/:125/:235`) · topic→pillars ✓ (`:129`) ·
subagent resolves ✓ (`:137` → `agents/differentiation-checker.md`) · **buzzword ✗** (grep NONE; no
inline line in Step 6/6b).
- **(d) agent-wiring + graceful degradation — wiring PASS; degradation GAP → fixed.** `differentiation-checker`
resolves, namespaced, conditional (`:137`). **Graceful degradation:** no WebFetch-failure/empty-extraction
fallback on EITHER path (grep NONE) — YouTube-no-transcript the sharpest, since it is a stated accepted input.
- **Shared asset:** `quality-scorecard.md` already clean post-S2 (`110-140` at `:32/:114/:128` +
buzzword `:132`) — `react`'s referenced asset (`react.md:270`) is good; the gap was `react`'s OWN inline Step 6.
**HARDEN (surgical — 3 edits, 1 file, all grep-confirmed landed).**
- `commands/react.md:131` — **added** Step-6 checklist line: `- [ ] No corporate buzzwords (leverage,
synergy, paradigm shift, thought leader, disruptive, value proposition, ecosystem, holistic approach)`.
Covers Step 6b via "Same as Step 6".
- `commands/react.md:60` — **added** Step-2 WebFetch fallback: *"If WebFetch can't extract usable
content (paywall / JS-heavy / YouTube-no-transcript): say so, ask the user to paste the key points
or choose another URL; never fabricate from the URL string or title."*
- `commands/react.md:194` — **added** Step-2b pointer: *"If a source can't be extracted: apply the
Step 2 fallback — drop it if 2+ usable sources remain, else ask the user; never fabricate."*
- **Deferrals:** none.
- **CONCRETE after:** the "leverage…synergy…value proposition" draft now trips the Step-6 buzzword
line; a YouTube-no-transcript invocation now degrades to "paste the key points / pick another URL"
instead of a fabricated reaction.
**VERIFY.**
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`; counts **29/19/26/6** unchanged
(`ls commands/*.md | wc -l` == 29; `git diff --stat` = `react.md` only, +5 insertions, 0 deletions).
- Before/after delta: axis (c) buzzword sub-check now enforced at the load-bearing Step-6 gate (both
paths via "Same as Step 6"); axis (d) WebFetch-failure now has a sensible fallback on both paths. The 3 edits are the cause.
- Disposition: **HARDENED** (3 edits, axis-c + axis-d) · 0 deferrals · axes a/b PASS.
---
## Session 4 — Create III (`multiplatform`)
> S4 status: re-grounded the proposed predicates against the actual `multiplatform.md` lines
> BEFORE talk (jerndisiplin: assert-after-read). Field-notes inbox **absent** at S4 → graceful
> no-op (SC-I). All `file:line` below are tool-grounded against the FINAL (post-edit) file.
> **Class call (operator-agreed):** NOT post-emitting — output is Twitter/slides/YouTube, so the
> LinkedIn content predicates (hook 110140, length band, no-body-link, topic→5 pillars) do **not**
> apply per platform; imposing them here would be the exact category error the Create-III brief warns
> against. Tested as **guided/cross-format with a routing sub-predicate**.
### /linkedin:multiplatform — adapt existing LinkedIn content to short-/cross-format (Twitter thread · slides · YouTube script)
**INTENT.** `multiplatform` is the Create journey's **cross-format adapter**: take existing LinkedIn
content and re-shape it for other platforms (Twitter/X thread, presentation slides, YouTube script) —
*write once, publish everywhere*. It explicitly does **not** own long-form (newsletters / blog / essays
→ `/linkedin:newsletter`, the single long-form entry point). It is an *adapter*, not a post-generator:
the value-add is adaptation fidelity (each platform's own rules) + correct routing of out-of-scope
(long-form) requests, not LinkedIn-feed quality gating.
**SIMULATE.**
- **Persona (ICP):** the author — a finished LinkedIn post in drafts, wants to reuse it as a Twitter thread.
- **Invocation:** `/linkedin:multiplatform` → pick "Twitter/X thread".
- **Walk:** Step 0 (`:21`) load source from `drafts/` + state → Step 1 (`:31`) AskUserQuestion pick
Twitter → Twitter template (`:53`, hook ≤**280** + 🧵 + one-idea tweets + CTA) → Step 2 (`:115`) save
`drafts/multiplatform/twitter-[slug].md` (`:118`) + clipboard, "Copied" (`:123`). **Promised artifact
produced.**
- **CONCRETE friction (axis d, adapter-specific — anchored, not a generated post):** `grep` for an
empty-source / fabrication guard in Step 0 → only matched l.23 "Ask the user to provide … or read
from drafts" — **no fallback** for the case where drafts is empty/absent, state has no recent posts,
AND the user pasted nothing. Risk: the model **fabricates a LinkedIn post to adapt** rather than
stopping. (Same failure class as S3's WebFetch-no-extraction gap.)
- **Second friction (axis c, voice hygiene):** the banned-buzzword list (content rule #4) is
*platform-agnostic voice hygiene*, not a char rule — and adaptation **rewrites** the source
(condense the hook to 280, expand to a script), which can introduce buzzwords absent from the
clean source. Pre-edit the file mentioned buzzwords **nowhere** (full read). Operator-judged in
scope (light line, not a full gate).
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** Three adapters (Twitter/slides/YouTube), source-loading,
save + clipboard, long-form correctly delegated — delivers the description's promise.
- **(b) algorithm/platform bar — PASS.** Per-platform rules correct, **no LinkedIn-rule leak**:
Twitter "280 chars max" (`:53`); **no** `110`/`140` hook band anywhere in the file (grep NONE —
the desired result for a non-LinkedIn surface). Long-form routing intact (see (c)).
- **(c) MECHANICAL predicate (routing sub-predicate) — PASS; voice-hygiene sub-gap fixed.** Every
emitted `/linkedin:newsletter` (`:6/:34/:36/:132`) resolves to a real `commands/newsletter.md`
(verified, ls) ✓; both reference files resolve — `references/linkedin-formats.md` (`:128`) +
`engagement-frameworks.md` (`:129`) (verified, ls) ✓. **Buzzword voice-hygiene line absent** →
added (`:45`).
- **(d) agent-wiring + graceful degradation — wiring N/A; degradation GAP → fixed.** No subagents
invoked (`grep subagent_type` → NONE — nothing to resolve). **Graceful degradation:** no
empty-source/fabrication guard in Step 0 (grep matched only the weak "ask … or read from drafts")
→ added a stop-and-ask-never-fabricate fallback (`:28`).
**HARDEN (surgical — 2 edits, 1 file, both grep-confirmed landed).**
- `commands/multiplatform.md:28` — **added** Step-0 empty-source fallback: *"If none of these yields
source content (empty/absent drafts, no recent posts, and nothing pasted), **stop and ask the user
to paste the LinkedIn post to adapt** — never fabricate source content to adapt."*
- `commands/multiplatform.md:45` — **added** an "Adaptation Templates" voice-hygiene blockquote:
*"adaptation rewrites the source — keep the adapted copy free of the banned corporate buzzwords
(content rule #4: leverage, synergy, paradigm shift, thought leader, disruptive, value proposition,
ecosystem, holistic approach) on every platform; don't let the rewrite introduce them."*
- **Deferrals:** none. **Scope note (flagged, not touched):** Step 2's "Copied to clipboard." (`:123`)
is unconditional, but `clipboard-helper.mjs` is shared, tested infra used identically by ~9 content
commands — re-gating it here would be scope-creep into a shared pattern (surgical discipline).
**VERIFY.**
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`; counts **29/19/26/6**
unchanged (`ls commands/*.md|wc -l` == 29, `ls agents/*.md|wc -l` == 19; `git diff --stat` =
`multiplatform.md` only, +9 insertions, 0 deletions).
- Before/after delta: axis (d) empty-source now degrades to "paste the post / never fabricate"
instead of an invented source; axis (c) the rewrite is now bound by the banned-buzzword list on
every platform. The 2 edits are the cause.
- Disposition: **HARDENED** (2 edits, axis-c + axis-d) · 0 deferrals · axes a/b PASS · class
confirmed non-post-emitting (no LinkedIn predicate imposed).
---
## Session 5 — Create II (`carousel`)
> S5 status: re-grounded the proposed predicates against the actual `carousel.md` lines BEFORE
> talk (jerndisiplin: assert-after-read). Field-notes inbox **absent** at S5 → graceful no-op (SC-I).
> All `file:line` below are tool-grounded against the FINAL (post-edit) file.
> **Class call (operator-agreed):** hybrid **guided/stateful + post-emitting on the caption** — the
> deck/images are the guided artifact, but the caption IS LinkedIn feed text, so the post predicates
> apply *to the caption* (not to the slides). Slides are bound by mobile-readability rules, not the
> feed bar.
### /linkedin:carousel — template→slide-by-slide deck + caption + optional mcp-image, full deck to clipboard
**INTENT.** `carousel` is the Create journey's **document-post path**: from a chosen template
(How-To / Listicle / Story / Comparison / Framework) + a topic, produce a publish-ready carousel —
structured slide-by-slide copy (header + body + visual note per slide) + a feed caption + optional
slide images (mcp-image), all assembled into ONE clipboard payload. Carousels/documents are the
top organic format (~7%; `references/algorithm-signals-reference.md`). Hybrid class: the deck is the
guided/stateful artifact; the **caption is post-emitting** and must honor the content bar mechanically
on the caption (hook **110140**, no banned buzzword, topic→5 pillars) + graceful mcp-image degradation.
**SIMULATE.**
- **Persona (ICP):** the author — 1048 followers, "Validation", 5 pillars; direct/technical NO/EN voice.
Builds a **How-To Guide** carousel on a pillar topic; tested both mcp-image-available and -unavailable.
- **Invocation:** `/linkedin:carousel` → pick template 1 (How-To) → topic + audience.
- **Walk:** Step 0 (`:24`) load state/voice/recent → Step 1 (`:30`) AskUserQuestion template → Step 2
(`:50`) topic+audience → Step 3 (`:58`) slide copy → Step 4 (`:94`) caption (hook 110140 `:97`, band
300500 `:103`) → **Step 5 quality + De-AI gate (the gap)** → Step 5.5 (`:127`) mcp-image (graceful)
→ Step 6 (`:156`) present full deck → clipboard one-payload (`:191213`) → Step 7 (`:218`) state.
- **CONCRETE friction (spec-level, anchored — not a generated deck):** at **Step 5** the De-AI /
Differentiation gate (pre-edit `:121123`) confirmed *personal substance · original thinking ·
concrete specifics · genuine voice* and banned mechanical engagement-bait, but had **no buzzword
line** — `grep -niE "buzzword|leverage|synergy|paradigm"` over the whole file = **NONE**. So a
caption opening *"How to leverage AI to drive synergy across the public-sector ecosystem…"* would
pass Step 5 unflagged; content rule #4 (`CLAUDE.md:124`) was never enforced at the caption gate.
Same failure class S2/S3 fixed in `post`/`react`. (Caption generation in Step 4 is exactly where a
rewrite can slip buzzwords in.)
- **No dead-ends:** all 5 referenced files exist (`agents/differentiation-checker.md`,
`assets/templates/carousel-templates.md`, `references/algorithm-signals-reference.md`,
`hooks/scripts/clipboard-helper.mjs`, `hooks/scripts/state-updater.mjs` — `[ -f ]` all FINNES).
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** Template→slides→caption→full-deck-clipboard-as-one-payload
(`:191213`, explicitly "the whole carousel, not just the caption") + state — delivers the promise.
- **(b) algorithm bar — PASS.** ~7% (`:37`) matches the ref exactly (`algorithm-signals-reference.md:55`,
"Documents / carousels | Top organic format (~7%…)"); "document post" framing correct (`:170`, LinkedIn
removed native carousels Dec 2023); caption hook 110140 (`:97`).
- **(c) MECHANICAL predicate (post-emitting, on the caption) — GAP → fixed.** hook band ✓ (`:97`) ·
caption band 300500 ✓ (`:103`) · topic→pillars ✓ (`:26/:56`) · subagent resolves ✓ (`:125`) ·
**buzzword ✗** (grep NONE; De-AI gate had no rule-#4 line). *no-body-link not imposed: a carousel
caption is not a typical link site and the command never asks for links — minor, deferred (matches
S2S4 which left it untouched).*
- **(d) agent-wiring + graceful degradation — PASS.** `differentiation-checker` resolves, namespaced,
conditional (`:125`, `subagent_type: linkedin-studio:differentiation-checker`). **mcp-image graceful
degradation solid:** "If mcp-image is unavailable or fails, skip… degrades to text-only" (`:129`),
per-call failure logged + continue, all-fail → text-only Step-6 design-guide branch (`:154`, `:176`).
`state-updater` exports `updatePostTracking` (`:55`) + `writeState` (`:346`) — Step-7 wiring correct.
**HARDEN (surgical — 1 edit, 1 file, grep-confirmed landed).**
- `commands/carousel.md:123` — **extended** the Step-5 De-AI gate with content rule #4:
`**Strip corporate buzzwords** from the caption (Content Quality Rule #4: leverage, synergy,
paradigm shift, thought leader, disruptive, value proposition, ecosystem, holistic approach) —
caption generation can slip them in, and they trip the same low-substance signal.`
- **Deferrals:** *no-body-link on the caption* (low risk, command never emits links; S2S4 precedent)
+ two cosmetic notes recorded, not edited (slide line-count 5 vs 7 between `:7377` and `:87`; both
are NICE-only, below the stopping-rule's axis-pass bar).
- **CONCRETE after:** the "leverage…synergy…ecosystem" caption now trips the Step-5 buzzword line
before the deck is presented.
**VERIFY.**
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`; counts **29/19/26/6**
unchanged (`ls commands/*.md|wc -l` == 29, `ls agents/*.md|wc -l` == 19; `git diff --stat` =
`carousel.md` only, 1 line changed). Only `.md` touched → hooks/analytics `node --test` not needed.
- Before/after delta: axis (c) buzzword sub-check now enforced at the load-bearing Step-5 caption gate.
The 1 edit is the cause.
- Disposition: **HARDENED** (1 edit, axis-c) · 2 recorded deferrals (no-body-link + cosmetic) · axes
a/b/d PASS.
---
## Session 6 — Create II (`video`)
> S6 status: re-grounded the proposed predicates against the actual `video.md` lines BEFORE
> talk (jerndisiplin: assert-after-read). Field-notes inbox **absent** at S6 → graceful no-op (SC-I).
> All `file:line` below are tool-grounded against the FINAL (post-edit) file.
> **Class call (operator-agreed):** hybrid **guided/stateful + post-emitting on the post caption** —
> the script / captions / thumbnail are the guided artifact (bound by video rules: muted-autoplay,
> 2.5 wps, completion-rate), but the **POST CAPTION (200400) IS LinkedIn feed text**, so the post
> predicates apply *to the caption* (not the script). **Hook 110140 deliberately NOT imposed on the
> caption (operator-agreed):** the caption accompanies the video; the primary hook discipline is the
> muted-autoplay opening (`:100`), not a standalone-post hook — imposing it would be S4's category error.
### /linkedin:video — type→length→angle→video-scripter script + captions + 200400 post caption + first comment
**INTENT.** `video` is the Create journey's **video-script path**: from type (talking-head / screen-rec /
slideshow / convert-a-text-post) + length (30/60/90/120 s) produce a production-ready script via the
`video-scripter` agent — timing markers, visual/energy cues, captions, thumbnail, a 200400-char post
caption, and a first comment — then save to drafts + deterministic state update. Hybrid class: the script
is the guided/stateful artifact; the **post caption is post-emitting** and must honor the content bar
mechanically on the caption (no banned buzzword, topic→5 pillars, no body link) + 2.5-wps pacing +
graceful degradation when voice samples are absent.
**SIMULATE.**
- **Persona (ICP):** the author — 1048 followers, "Validation", 5 pillars; direct/technical NO/EN voice.
Builds a 60-s talking-head on a pillar topic. **+ fresh-adopter path** (no voice samples) reasoned to
exercise graceful degradation (relevant here because of the pre-edit "REQUIRED before scripting").
- **Invocation:** `/linkedin:video` → talking-head → 60 s → pillar topic.
- **Walk:** Step 0 load state/voice/refs → Step 1 type → Step 2 length (2.5-wps budgets) → Step 3 angle
(topic→pillars) → Step 4 `video-scripter` generates → **Step 5 quality + De-AI gate (the gap)** →
Step 6 present + clipboard post caption → Step 7 refine → Step 8 save + state.
- **CONCRETE friction (spec-level, anchored — not a generated script):**
- **(axis c)** at **Step 5** the only buzzword line (`:106`, pre-edit) sat under the **"Natural Speech
Test"** = the *script's spoken language*; the **caption** De-AI gate (`:129`) carried **no rule-#4
line** (`grep buzzword` → single hit at `:106`). A caption opening *"How to leverage AI to drive
synergy across the public-sector ecosystem…"* would pass the caption gate unflagged. Same failure
class S2/S3/S5 fixed in `post`/`react`/`carousel` (the caption is re-shaped from a text idea in
Step 4 — exactly where a rewrite slips buzzwords in).
- **(axis d)** `:42` (pre-edit) "voice-samples … **(REQUIRED before scripting)**" + `:90` "Match voice
against voice-samples/" had **no fallback** for a fresh adopter with an empty/absent samples dir —
"REQUIRED" reads as a hard block / dead-end. Same failure class as S3 (WebFetch-no-extraction) / S4
(empty-source).
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** 8-step workflow delivers the description's promise (script + pacing +
cues + captions + thumbnail + 200400 caption + first comment + refine + save + state).
- **(b) algorithm bar — PASS.** Muted-autoplay correctly de-mythologized (`:100`, "three-second hook =
folklore; LinkedIn's only official 3 s = minimum length"); captions = the enforceable spec (`:120`);
60 s sweet spot; 2.5-wps budgets exact (`:64/:66`, 75/30 = 150/60 = 225/90 = 300/120 = 2.5). *Honesty
note (deferred, operator-agreed):* `:69` "LinkedIn requires 30 % completion or **zero distribution**"
is more absolute than the declared SSOT `algorithm-signals-reference.md:56` (no 30 %, confidence
*medium*); it is consistent with `linkedin-formats.md` (cited at `:39`, l.58). Inter-reference tension,
NOT a video-command gap — deferred (brief Non-Goal: no re-litigation of the bar).
- **(c) MECHANICAL predicate (post-emitting, on the caption) — GAP → fixed.** no-body-link ✓ (`:125`) ·
topic→pillars ✓ (`:124` + Step 3) · caption band 200400 ✓ (`:121`) · subagent resolves ✓ (`:83/:131`) ·
**buzzword ✗** (grep: single hit `:106`, under Natural Speech = script not caption; caption De-AI gate
had no rule-#4 line). **hook 110140 deliberately not imposed** on the accompanying caption (class
boundary, operator-agreed; primary hook = muted-autoplay `:100`).
- **(d) agent-wiring + graceful degradation — wiring PASS; degradation GAP → fixed.** `video-scripter`
(`:83`) + `differentiation-checker` (`:131`, conditional) both resolve to real `agents/*.md`, both
`subagent_type: linkedin-studio:`-namespaced. **Graceful degradation:** the "REQUIRED before scripting"
voice-samples dead-end closed → fresh-adopter fallback to SKILL.md/defaults. (mcp-image n/a — video
generates no images.)
**HARDEN (surgical — 2 edits, 1 file, both grep-confirmed landed).**
- `commands/video.md:129` — **extended** the Step-5 De-AI caption gate with Content Quality Rule #4:
*"**Strip corporate buzzwords** from the post caption (… leverage, synergy, paradigm shift, thought
leader, disruptive, value proposition, ecosystem, holistic approach) — re-shaping a text idea into a
video caption can slip them in, and they trip the same low-substance signal."*
- `commands/video.md:42 + :45` — softened "(REQUIRED before scripting)" → "(read before scripting)" and
**added** a Step-0 graceful-degradation line: *"if `voice-samples/` is empty or absent, don't block —
fall back to the voice settings in SKILL.md (or neutral defaults) … the voice-guardian is suppressed
under 5 samples, so there is no hard gate to satisfy."*
- **Deferrals (2, recorded):** (1) hook 110140 on the caption — deliberate class boundary, not a gap
(operator-agreed); (2) `:69` "zero distribution" honesty — inter-reference tension, brief Non-Goal (no
re-litigation of the algorithm bar); leave to a future reference-consistency pass if desired.
- **CONCRETE after:** the "leverage…synergy…ecosystem" caption now trips the Step-5 buzzword line before
the script is presented; a fresh adopter with no voice samples now degrades to SKILL.md/defaults instead
of hitting a "REQUIRED" dead-end.
**VERIFY.**
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`, exit 0; counts **29/19/26/6**
unchanged (`ls commands/*.md|wc -l` == 29, `ls agents/*.md|wc -l` == 19; `git diff --stat` =
`video.md` only, +4/2). Only `.md` touched → hooks/analytics `node --test` not needed.
- Before/after delta: axis (c) buzzword sub-check now enforced at the load-bearing Step-5 caption gate;
axis (d) the voice-samples dead-end now degrades gracefully. The 2 edits are the cause.
- Disposition: **HARDENED** (2 edits, axis-c + axis-d) · 2 recorded deferrals (caption-hook boundary +
:69 honesty) · axes a/b PASS.
---
## Session 7 — Create II (`batch`)
> S7 status: re-grounded the proposed predicates against the actual `batch.md` lines BEFORE
> talk (jerndisiplin: assert-after-read). Field-notes inbox **absent** at S7 → graceful no-op (SC-I).
> All `file:line` below are tool-grounded against the FINAL (post-edit) file.
> **Class call (operator-agreed):** hybrid **guided/stateful + post-emitting per post** — each of
> the 35 drafts IS a feed post, so the post predicates apply *per post*; the orchestration around
> them (state-load, agent-delegation, scheduling, queue, `.ics`) is the guided/stateful layer.
### /linkedin:batch — one theme/pillar → 35 mixed-format feed posts, scheduled + queued + .ics
**INTENT.** `batch` is the Create journey's **volume path**: from one theme or pillar produce a full
week (35 posts) with varied angles + formats, each scheduled, queued (so it shows in
`/linkedin:calendar` + session-start reminders), and exported to a `.ics`. It is an **orchestrator** —
delegates angle discovery to `trend-spotter`, the balanced batch plan to `content-planner`, and owns
scheduling/queue/ical itself. Post-emitting per post, so each draft must honor the content bar
mechanically (hook **110140**, format-appropriate length band, no body link, no banned buzzword,
topic→5 pillars); guided/stateful for the orchestration (subagent targets + node helpers resolve;
graceful degradation).
**SIMULATE.**
- **Persona (ICP):** the author — 1048 followers, "Validation", 5 pillars; direct/technical NO/EN voice.
"Sunday prep": one theme → a week of **mixed-format** posts (standard → carousel → quick → video rotation).
- **Invocation:** `/linkedin:batch` → "One main theme" → 35 angles.
- **Walk:** Step 0 (`:23`) load state/SKILL/plan/templates → Step 1 (`:33`) theme/pillar (+ `trend-spotter`
for timely angles) → Step 2 (`:45`) `content-planner` plan + schedule (slot template by `weekly_goal`,
queue-conflict check `:58`) → **Step 3 per post: 3a draft (`:86`) → 3b Quick Quality Check (`:94`, the
gap) → 3c save (`:101`) → 3d queueAdd (`:124`)** → Step 4 review (`:130`) → Step 5 finalize + `.ics` (`:172`).
- **CONCRETE friction (spec-level, anchored — not a generated batch):** at **Step 3b** the per-post check
`:95` read **"Character count in range"** — **no band named** (range of *what?*), so it is unenforceable;
and the Step 3a "standard structure" (`:8892`: hook 110140 + context 200300 + insight 400800 +
implication 200300 + CTA 50100) **sums to 9601640**, whose floor sits **below** the canonical standard
band **1,2001,800** (CLAUDE.md rule #2). A "standard" post drafted to the structure can land ~1,000 chars
and pass `:95` "in range" unflagged. Complication: batch is **mixed-format**, so the band is
format-dependent — imposing 1,2001,800 on everything would wrongly fail a quick post (the S4 category
error in miniature). **Buzzword + no-body-link were NOT gaps here** (`:98` + `:97` already present — the
hole S2S6 kept finding is already closed in `batch`).
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** Theme→plan→schedule→per-post draft/check/save/queue→review→finalize+
`.ics` delivers the week-of-content promise. *Tension (deferred):* Step 3a imposes one "standard structure"
though Step 2 (`:76`) promises format variety — `batch` is an orchestrator, so carousel/video inherit their
own formats' rules; recorded, not edited.
- **(b) algorithm bar — PASS.** No body link (`:97`); buzzword-clean (`:98`); pillar-aligned scheduling
(`scheduling-strategy.md`); 5x5x5 reminder (`:169`). `batch` uses a deliberately lighter "Quick Quality
Check" (no per-post `differentiation-checker`) — breadth-over-depth by design for a week-at-once tool; *note,
not a fix.*
- **(c) MECHANICAL predicate (post-emitting, per post) — GAP → fixed.** hook 110140 ✓ (`:88`) · no-body-link
✓ (`:97`) · buzzword ✓ (`:98`) · topic→pillars ✓ (`:39/:77/:112`) · subagent resolves ✓ (`:43/:47`) ·
**length band ✗** (`:95` "in range" named no band; 3a structure sums 9601640, under the 1,200 standard floor).
- **(d) agent-wiring + graceful degradation — wiring PASS; PASS.** `trend-spotter` (`:43`) + `content-planner`
(`:47`) both resolve to real `agents/*.md`, both `subagent_type: linkedin-studio:`-namespaced. All 5 node
helpers resolve with matching signatures: `queueUpcoming(days=7)` / `queueAdd(…8 params)` (batch passes 8 args)
/ `queueFormatSummary(entries)` (queue-manager.mjs `:52/:63/:112`), `generateIcalFromQueue` / `writeIcalFile`
(ical-generator.mjs `:183/:201`). Degradation: no-plan→`:31` "create one first"; empty queue→`:181` guard.
*Minor (deferred):* no fallback if `weekly_goal` is unset (`:52` slot-template lookup) — micro, below axis-pass.
**HARDEN (surgical — 1 edit, 1 file, grep-confirmed landed).**
- `commands/batch.md:95` — `- Character count in range` → `- Character count in the band for this post's
format (standard 1,2001,800 · quick 150500 · carousel/video caption per that format) — not just "in range"`.
- **Deferrals (3, recorded):** (1) Step 3a single-structure vs mixed-format promise (orchestrator inherits
per-format rules); (2) no per-post differentiation gate (deliberate breadth-over-depth design); (3)
`weekly_goal`-unset fallback (micro-degradation). All below the stopping-rule's axis-pass bar.
- **CONCRETE after:** a ~1,000-char "standard" draft now trips the format band (under the 1,200 floor) instead
of passing the vague "in range"; a quick post is judged against 150500, not wrongly against 1,2001,800.
**VERIFY.**
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`; counts **29/19** confirmed
(`ls commands/*.md|wc -l` == 29, `ls agents/*.md|wc -l` == 19; `git diff --stat` = `batch.md` only,
1 insertion / 1 deletion). Only `.md` touched → hooks/analytics `node --test` not needed.
- Anti-check grep: bare vague `^- Character count in range$` → NONE (gone); format-band line present (`:95`).
- Before/after delta: axis (c) length-band sub-check now enforced + format-aware at the load-bearing Step-3b
gate. The 1 edit is the cause.
- Disposition: **HARDENED** (1 edit, axis-c) · 3 recorded deferrals · axes a/b/d PASS.
---
## Session 8 — Pipeline (`pipeline`)
> S8 status: re-grounded the proposed predicates against the actual `pipeline.md` lines BEFORE
> talk (jerndisiplin: assert-after-read). Field-notes inbox **absent** at S8 → graceful no-op (SC-I).
> All `file:line` below are tool-grounded against the FINAL (post-edit) file.
> **Class call (operator-delegated → quality + precedent):** hybrid **guided/stateful + post-emitting
> on the final post** — the 8-step idea→publish→analysis pipeline produces ONE feed post (Step 2 draft
> `:5762` → Step 3 gate `:6686` → Step 6 publish `:135`), so the post predicates apply to that post;
> the orchestration around it (Step 0 load · Step 1 agent-deleg · Step 4 schedule/queue · Step 5 5x5x5 ·
> Step 7 first-hour · Step 8 analysis · state-update `:186199`) is the guided/stateful layer.
### /linkedin:pipeline — full idea→publish→analysis pipeline, one 1,2001,800-char feed post, 8 steps
**INTENT.** `pipeline` is the Create journey's **end-to-end path**: from idea (or `$ARGUMENTS` topic)
through ideation → draft → optimize → schedule → 5x5x5 pre-engagement → publish → first-hour → 48h
analysis, with a deterministic state update. Post-emitting on the final post (hook **110140**, length
**1,2001,800**, no body link, no banned buzzword, topic→5 pillars); guided/stateful for the
orchestration (subagent targets + 5 node helpers resolve; graceful degradation).
**SIMULATE.**
- **Persona (ICP):** the author — 1048 followers, "Validation", 5 pillars; direct/technical NO/EN voice.
- **Invocation:** `/linkedin:pipeline about AI-kodeassistenter i offentlig sektor` (topic in `$ARGUMENTS`
→ Step 1 ideation skipped, `:41`).
- **Walk:** Step 0 load state/SKILL/voice → Step 1 skipped → Step 2 angle + draft to the structure
(`:5762`) → **Step 3 optimize gate (`:6686`, the gap)** → Step 4 schedule (+ Option-3 queue) →
Step 5 5x5x5 → Step 6 publish+clipboard → Step 7 first-hour → Step 8 analysis → state.
- **CONCRETE friction (spec-level, anchored — not a generated post):** at **Step 3** the inline quality
scorecard (`:7581`) carried the buzzword line (`:79`) but as a **bare** `- [ ] No corporate buzzwords`
— no inline enumeration; it leaned on the scorecard reference (`:75` → `quality-scorecard.md:132`). A
draft opening *"How to leverage AI to drive synergy across the public-sector ecosystem…"* relies on the
operator cross-referencing a long shared asset rather than the inline gate the AI actually executes.
**Unlike S2/S3/S5/S6 the line was PRESENT** — the gap was that it was not self-contained, making
pipeline the odd-one-out among post-emitting commands (post `:134` · react `:131` · carousel `:123` ·
video `:129` all enumerate inline).
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** 8-step idea→publish→analysis delivers the description's promise.
- **(b) algorithm bar — PASS.** Step 3 signals (`:7073`) cited to the SSOT (saves rank highest · a
15+-word comment ≈ 2× a like · dwell >30 s +25 %); no body link (`:78`); 5x5x5 (`:120133`); first-hour
(`:158169`). No 30 %-claim → untouched by the open reference-consistency follow-up.
- **(c) MECHANICAL predicate (post-emitting, on the final post) — GAP → fixed.** hook 110140 ✓
(`:58/:76`) · length band ✓ (`:77` names 1,2001,800) · no-body-link ✓ (`:78`) · topic→pillars ✓
(`:80` + `:49` content-planner pillar-situating) · subagent resolves ✓ (`:49`) · **buzzword
present-but-not-self-contained ✗→fixed** (`:79` bare → enumerated inline).
- **(d) agent-wiring + graceful degradation — PASS.** `content-planner` + `trend-spotter` (`:49`) resolve
to real `agents/*.md`, both `subagent_type: linkedin-studio:`-namespaced. All 5 node helpers export with
matching signatures: `queueUpcoming(days=7)` (qm`:52`) · `queueAdd(…8 params)` (qm`:63`; pipeline passes
8 args `:116`) · `queueFormatSummary` (qm`:112`) · `writeState` (su`:346`) · `updatePostTracking`
(su`:55`). Degradation: Step 1 (`:4347`) handles no-planned-topic/no-input via AskUserQuestion; Step 0
voice-samples is a soft read (no "REQUIRED" dead-end).
**HARDEN (surgical — 1 edit, 1 file, grep-confirmed landed).**
- `commands/pipeline.md:79` — `- [ ] No corporate buzzwords` → `- [ ] No corporate buzzwords (leverage,
synergy, paradigm shift, thought leader, disruptive, value proposition, ecosystem, holistic approach)`
(same banned list as the other post-emitting gates + the scorecard).
- **Deferral (1, recorded):** the Step-2 component structure (`:5862`: hook 110140 + context 200300 +
insight 400800 + implication 200300 + CTA 50100) sums **9601 640**, whose floor sits below the
Step-3 band floor 1,200 (`:77`) — **identical structure to batch's 3a (`:8892`)**. NOT edited:
pipeline's Step-3 gate already NAMES the band (the S7 fix was naming the band on an *unenforceable* gate;
here it is already named), so a minimum-path draft is caught by the gate, not shipped — and S7 did not
edit the analogous 3a structure either. Editing Step 2 here would exceed precedent (over-engineering).
Below the axis-pass bar.
- **CONCRETE after:** the "leverage…synergy…ecosystem" draft now trips the Step-3 buzzword line inline
before optimize completes, without the operator cross-referencing the shared scorecard.
**VERIFY.**
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`; counts **29/19** confirmed
(`ls commands/*.md|wc -l` == 29, `ls agents/*.md|wc -l` == 19; `git diff --stat` = `pipeline.md` only,
1 insertion / 1 deletion). Only `.md` touched → hooks/analytics `node --test` not needed.
- Before/after delta: axis (c) buzzword sub-check is now self-contained at the load-bearing Step-3 gate
(was a bare, reference-dependent line). The 1 edit is the cause.
- Disposition: **HARDENED** (1 edit, axis-c) · 1 recorded deferral (Step-2 component-sum, gate-caught) ·
axes a/b/d PASS.
---
## Session 9 — Longform orchestrator (`newsletter`)
> S9 status: re-grounded STATE's 7 S9 predicates against the actual `newsletter.md` (1594 lines, 16
> phases) BEFORE talk (jerndisiplin: assert-after-read). Class agreed S9 steps 12: **guided/stateful
> long-form orchestrator, NOT post-emitting** — the feed-post predicates (hook 110140, 1,2001,800
> band, no-body-link) do NOT apply; imposing them would be the S4/video category error the command
> itself already rejects (`:3842`). All `file:line` below are tool-grounded against the FINAL file
> (no edits this session → final == 52e888a).
### /linkedin:newsletter — long-form edition end-to-end, 16-phase stateful pipeline, NOT post-emitting
**INTENT.** `newsletter` is the Longform orchestrator: take an edition (newsletter / essay / series
article) from load → calibration → verified research → skeleton+spine gates BEFORE prose → full draft
→ consistency/quality+voice-scrub → fact-check → editorial craft gate → persona sweep → headless
adversarial review → visual assets → LOCK (POST.html) → hook-gate → scheduling. Multi-session with a
maintained `edition-state.json`. Long-form quality is enforced by the **pipeline phases**, not
short-form hooks.
**SIMULATE (grounded persona-sim, anchored to Step 6.5 headless fan-out).**
- **Persona (operator):** KTG producing a series edition; primær reader chosen in Step 1.
- **Walk:** Step 0 load (voice/persona graceful `:258`) → … → Step 6 persona sweep PASS → **Step 6.5
freeze → fan out 5 cold archetypes** (`:10621069`, single message, all `linkedin-studio:`-namespaced,
foreground per principle 4; background loses Task `:79`) → **degradation gate** (`:10841086` "do not
proceed with a missing reviewer `[GATE]`") → consolidate via `SendUserFile` → Step 7.5 visual assets
(mcp-image not a hard dependency, external `cover-raw.png` fallback `:12221230`) → Step 8 LOCK
(`build-linkedin.mjs` → `linkedin/NN/POST.html`, exit-code N3 `:1388`) → Step 10 `queueAdd` (`:1486`).
- **Friction sought (the S2S8 pattern: a missing buzzword gate / a missing fallback):** **NONE found.**
The category-correct equivalent of the short-form buzzword gate is the de-AI/voice-scrub + editorial +
language-reviewer gates, all present and wired. Every fan-out has a structured-return degradation gate.
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** The 16-phase pipeline delivers the description's promise; primær
artifact `POST.html` is produced end-to-end at Step 8 lock (`:13791395`) with exit-code verification.
- **(b) algorithm/format bar (long-form honesty, NOT feed-post predicates) — PASS.** Native-newsletter §
(`:15081569`) is nøktern: "sells nothing", notification dedup not oversold ("not three guaranteed
touchpoints"), cold-start not inflated ("0→9K in 7 days were not cold starts"), downsides disclosed
(non-exportable / no-canonical / no-analytics / decay), sourcing note (`:1564`) separates LinkedIn
help-docs (high conf.) from practitioner ranges (medium) + flags the contested email-dedup; citation
`docs/remediation/research/03-coverage-gap-specs.md` §D5 exists (225 lines, §D5 ×8 — not dangling).
- **(c) quality rules (category-appropriate) — PASS.** Long-form quality is enforced by the pipeline
gates (skeleton 2.5 · spine 3a · consistency+voice-scrub 4 · fact-check 5 · editorial 5.5 · persona 6
· headless 6.5 · hook 9), all present + wired. Short-form hooks correctly NOT imposed (`:3842`); no
hollow gate found.
- **(d) agent-wiring + graceful degradation — PASS.** All 8 invoked subagents resolve to real
`agents/*.md`, all `subagent_type: linkedin-studio:`-namespaced (persona-reviewer ×5, fact-checker ×2,
voice-scrubber, language-reviewer, fact-reviewer, editorial-reviewer, content-reviewer,
content-repurposer). Render/queue helpers exist; `queueAdd(id, draftPath, schedDate, schedTime,
pillar, format, hookPreview, charCount)` 8-arg signature (qm`:63`) matches the call (`:1486`). All 9
plugin-root ref/template files (`:15751594`) exist. Phase strings: `_doc.phases` (16) ↔ resumption
table (16) `diff` IDENTICAL. Degradation: voice/persona `:258` · mcp-image `:12221230` · exit-codes
×4 (`:443/580/1144/1388`) · optional config+delingstekst `:13731377` · fan-out gates ×3
(`:340/789/1086`).
**HARDEN — NO CHANGE (verified clean).**
- 0 edits. `newsletter` is among the most iterated commands (v2.0→v4.1 hardened the long-form gates
across multiple production runs: Seres, Del 4 / Endring 9), and it shows: every axis passes with no
surgical opportunity at or below the axis-pass bar.
- **Deferrals:** none. **Anti-gold-plating:** no NICE-only polish pursued beyond axis-pass (stopping
rule, brief §). The native-newsletter follow-up noted in S2 STATE (reference-consistency for the
separate `video.md`/`linkedin-formats.md` 30 % surface) is NOT this command's surface — left in the
open-follow-ups list, not touched here.
**VERIFY.**
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`; counts **29/19/26** (+6
skills) unchanged; `git status --porcelain` empty → working tree byte-identical to `52e888a` (S8).
- Phase-string diff reproduced: `_doc.phases` ids vs resumption-table `currentPhase` ids → IDENTICAL,
16↔16, in order.
- Disposition: **VERIFIED — NO CHANGE** (0 edits) · 0 deferrals · axes a/b/c/d all PASS.
---
## Session 10 — Longform companion (`headless-review`)
> S10 status: re-grounded STATE's predicates against the actual `headless-review.md` (248 lines, 13 KB)
> BEFORE talk (jerndisiplin: assert-after-read). Class agreed S10 steps 12: **cold adversarial
> review-orchestrator, NOT post-emitting** — it emits a consolidated review report + optional
> `edition-state` persistence, never a feed post and never drafts prose; the feed-post predicates
> (hook 110140, 1,2001,800 band, no-body-link) do NOT apply — imposing them would be the S4/video
> category error (even further from feed-post than `newsletter`). The four archetype agents are
> SHARED with `newsletter` Step 6.5 → S9 agent findings reused as baseline. All `file:line` below are
> tool-grounded against the FINAL file (no edits this session → final == 1c31644).
### /linkedin:headless-review — cold adversarial review package on a FROZEN long-form draft, NOT post-emitting
**INTENT.** `headless-review` is the standalone surface of `newsletter` Step 6.5: read a frozen,
publish-ready (or pivoted) long-form draft COLD — with NONE of the drafting session's framing-bias —
through five independent archetypes and return direction-only flags, consolidated into one
operator-gated report. Two isolation layers: fresh session (strongest, `:6468`) + per-archetype
subagent context (`:6974`). The adversarial-independence layer the in-session gates structurally
cannot be (`:3048`).
**SIMULATE (grounded persona-sim, anchored to the Step 3 cold fan-out).**
- **Persona (operator):** KTG in a FRESH session running `--type all` on `<serie>/NN-utkast.md`.
- **Walk:** Step 1 resolve-from-disk (draft → edition-state `currentArticle` fallback → ask-once
`:103104`; contract 3-tier fallback ending at always-present `longform-quality-rules.md` `:111114`;
personas = newsletter Step 1 chain `:116118`) → Step 2 freeze (`cp` snapshot `:126`, live-draft
fallback if `cp` unavailable `:130131`) → **Step 3 fan out 5 archetypes** (single message,
`:135136`; 4 `linkedin-studio:`-namespaced types, persona-reviewer ×2 modes `:141146`) →
**degradation gate** (`:161164` "re-run that one archetype — do not paper over a missing reviewer
`[GATE]`") → Step 4 consolidate (operator gates, never auto-resolve flags `:168169`/`:196197`;
convergence marked `:200202`) → Step 5 surface via `SendUserFile` (else `file://` `:208210`) +
optional persist `articles.NN.headlessReview` (`:211215`).
- **Friction sought (the S2S8 pattern: a missing buzzword gate / a missing fallback):** **NONE found.**
Not post-emitting → the category-correct quality bar is the reviewers grading against the writing
contract + the structured-return degradation gate, both present. Every fan-out call has a degradation
re-run path.
**EVALUATE (4 axes).**
- **(a) intention fidelity — PASS.** The 5-step flow (resolve → freeze → parallel fan-out → consolidate
→ surface/persist) delivers the description's promise; primær artifact is the consolidated report
(`:171198`), surfaced + optionally persisted (`:204215`). `allowed-tools` (`:1826`) complete and
fully used (Read/Glob/Grep · Bash freeze · AskUserQuestion ask-once · Task fan-out · Write report+state).
- **(b) algorithm/format bar (long-form honesty, NOT feed-post predicates) — PASS.** Cold contract
(`:5074`) nøktern: reviewers get a "deliberately starved context", exactly-what-they-get /
exactly-what-they-don't enumerated; two-layer isolation with honest caveats (Layer 1 "recommended …
strongest" not absolute; reload caveat for the v3.1.0 agents `:8082`). Deliberate redundancy
(`fact-reviewer`↔`fact-checker`, `language-reviewer`↔`editorial` prose axis) disclosed as
on-purpose, not collapsed (`:234237`). No feed-post predicate imposed.
- **(c) quality rules (category-appropriate) — PASS.** Review quality enforced by (1) reviewers judging
against the writing contract (`:111114`), (2) the degradation gate requiring structured/populated
returns, not a hedged paragraph (`:161164`), (3) operator-gates-the-fold-in — the command recommends,
never rewrites/gates (`:168169`/`:196197`/`:210`), (4) convergence-as-signal (`:200202`). No hollow
gate found.
- **(d) agent-wiring + graceful degradation — PASS.** 4/4 invoked subagent types resolve to real
`agents/*.md` with matching frontmatter `name:`, all `subagent_type: linkedin-studio:`-namespaced
(content-reviewer · language-reviewer · fact-reviewer · persona-reviewer ×2 modes → "five
archetypes" `:56`/`:3031`). 8/8 Reference Files (`:241248`) exist. `articles.NN.headlessReview`
schema present + complete in `edition-state.template.json` (`:5669`: frozenDraft · reviewers
{content,language,fact,personaResonance,personaConversion} · consolidatedReport · foldedIn · waived ·
status; ladder pending→run→folded `:29`). Degradation ×5: freeze `cp` fallback `:130` · draft-missing
ask-once `:104` · persona fallback `:117` · `[GATE]` re-run `:163164` · output-default `:93` +
SendUserFile-else-`file://` `:208210`.
**HARDEN — NO CHANGE (verified clean).**
- 0 edits. Borderline surfaced at talk: the freeze step (`:126`) assumes `review/` exists (no
`mkdir -p`). **Operator chose Alt A (no change).** Rationale: the freeze block is **byte-identical to
`newsletter` `:1050`** (S9-verified-clean), and `review/` is created by the render pipeline that
produces the draft (`build-html.mjs` → `./review/` newsletter `:1136`; skeleton/draft render
`:444`/`:580`); adding `mkdir -p` only here would drift from its S9-clean twin, and fixing both would
re-open S9 (out of S10 scope). The `cp`-unavailable fallback (`:130`) covers the degraded path loosely.
- **Deferrals:** none. **Anti-gold-plating:** the `mkdir -p` robustness improvement is a NICE below the
axis-pass bar — left as the operator's logged decision (Alt A), not pursued.
**VERIFY.**
- `bash scripts/test-runner.sh` → `Passed: 81 · Failed: 0 · Warnings: 0`; counts **29/19/26** (+6
skills) unchanged (count/version-consistency guards inside the lint passed); `git status --porcelain`
empty → working tree byte-identical to `1c31644` (S9).
- Predicates re-grounded against the final file: 4 namespaced subagent types (frontmatter `name:`
match) · 8/8 ref files exist · `headlessReview` schema `:5669` · 5 degradation points · persona
chain = newsletter `:179183` · "five archetypes" (wrapped) = 5 modes from 4 agents.
- Disposition: **VERIFIED — NO CHANGE** (0 edits) · 0 deferrals · axes a/b/c/d all PASS.
---