ktg-plugin-marketplace/plugins/linkedin-studio/docs/hardening/log.md
Kjell Tore Guttormsen a41cc54e73 fix(linkedin-studio): S1 hardening — method calibration (quick) + Start journey
Hardening phase S1. Tightened the hook character bound from one-sided
(under 140) to the full canonical 110-140 band across the Start-journey
creation surfaces, matching hooks/prompts/content-quality-gate.md:
- quick.md: hook band on Step 2 + Step 5 checklist; added a buzzword check
  and a 150-500 length-band check; checklist tally 6 -> 7.
- onboarding.md: hook band in Phase 3.2 + 3.3 (length + no-links already present).
- first-post.md: hook band in Step 4 + Step 5 (length + no-links already present).
- setup.md: zero-edit pass (all four axes already satisfied).

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 06:46:11 +02:00

16 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.