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

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