# 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 110–140 · 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
150–500-char post out in ~5 minutes, max one question, auto-copied to clipboard. It must
honor the content-quality bar mechanically (hook **110–140**, length **150–500**, 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 150–500 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 110–140 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 110–140 band (length 150–500 + 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 110–140 band (length 150–500 + 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 3a–3f
  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.

---
