fix(linkedin-studio): S6 harden video — caption buzzword gate + voice-samples graceful degradation

S6 close-out (2 edits / 1 file · lint 81/0 · counts 29/19/26/6):
- video.md:129 Step-5 De-AI gate: add content rule #4 buzzword strip to the post-caption gate (only buzzword line was :106 under "Natural Speech Test" = script not caption; caption is post-emitting feed text)
- video.md:42/:45 Step-0: soften "(REQUIRED before scripting)" → "(read before scripting)" + add fresh-adopter graceful-degradation fallback (empty/absent voice-samples → SKILL.md/defaults, no dead-end)

Axis-c (buzzword on caption) + axis-d (voice-samples dead-end) closed; axes a/b PASS. Class: hybrid guided/stateful + post-emitting on the caption (hook 110-140 deliberately not imposed on the accompanying caption — operator-agreed). video-scripter + differentiation-checker wiring verified; 2.5-wps budgets exact. 2 deferrals (caption-hook class boundary + :69 "zero distribution" inter-reference honesty). Local-only per push policy.
This commit is contained in:
Kjell Tore Guttormsen 2026-06-18 15:20:05 +02:00
commit 0a202059a7
2 changed files with 94 additions and 2 deletions

View file

@ -39,9 +39,11 @@ Load video-specific references:
- Read `references/linkedin-formats.md` (Video Content Deep Dive section) for algorithm data and technical specs
Check for existing assets:
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/` — Match the user's natural voice (REQUIRED before scripting)
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/voice-samples/` — Match the user's natural voice (read before scripting)
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/examples/high-engagement-posts.md` — Study successful patterns
**Graceful degradation (fresh adopter):** if `voice-samples/` is empty or absent, don't block — fall back to the voice settings in `skills/linkedin-studio/SKILL.md` (or neutral defaults), note that the script uses a default voice, and proceed. The voice-guardian is suppressed under 5 samples, so there is no hard gate to satisfy.
## Step 1: Choose Video Type
Use AskUserQuestion:
@ -124,7 +126,7 @@ Before presenting, verify the script passes the video quality gate:
### De-AI / Differentiation Gate
The post caption rides the same low-substance down-rank LinkedIn confirmed for text. Confirm the script's core idea and caption carry the signals LinkedIn named — **personal substance, original thinking, concrete specifics, genuine voice** — and use no mechanical-response engagement bait ("Comment YES", "Like for Part 2"); a genuine question is fine. (The voice-guardian hook scores the caption on save.)
The post caption rides the same low-substance down-rank LinkedIn confirmed for text. Confirm the script's core idea and caption carry the signals LinkedIn named — **personal substance, original thinking, concrete specifics, genuine voice** — and use no mechanical-response engagement bait ("Comment YES", "Like for Part 2"); a genuine question is fine. (The voice-guardian hook scores the caption on save.) **Strip corporate buzzwords** from the post caption (Content Quality Rule #4: 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.
If the idea is a take the audience has seen many times — commodity content — delegate an originality pass to the `differentiation-checker` agent: invoke it via `Task` with `subagent_type: linkedin-studio:differentiation-checker` (foreground, from this command layer), then sharpen the angle before presenting.

View file

@ -542,3 +542,93 @@ on the caption (hook **110140**, no banned buzzword, topic→5 pillars) + gra
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.
---