linkedin-studio/docs/hardening/log.md
Kjell Tore Guttormsen f5bc579e55 fix(linkedin-studio): S3 harden react — Step-6 buzzword gate + WebFetch-failure fallback (both paths)
S3 close-out (3 edits / 1 file · lint 81/0 · counts 29/19/26/6):
- react.md:131 Step 6: add no-buzzword checklist line (was missing; banned list = content rule #4; covers Step 6b via "Same as Step 6")
- react.md:60 Step 2: WebFetch-failure fallback (paywall/JS/YouTube-no-transcript -> ask user to paste or pick another; never fabricate)
- react.md:194 Step 2b: same fallback pointer for the multi-URL comparison path

Axis-c (buzzword) + axis-d (graceful degradation) closed on both paths; axes a/b PASS. Shared scorecard already clean post-S2. Local-only per push policy.
2026-06-18 13:44:00 +02:00

388 lines
27 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.
---