docs(linkedin-studio): cold-review R5 (Grow+Router) — independent kald-review of 6 surfaces -> 29/29 coverage

Final cold-review round: strategy, competitive, monetize, outreach, profile, linkedin.
2 independent cold Opus reviewers (intent + correctness), no cross-feed, every claim
tool-grounded; divergences re-grounded by main before registration.

Result: verdict MINOR, 0 MAJOR (cleanest batch of the sweep). Resolution integrity
PASS across all 6 (2/2 subagent_type, 28/28 routes, 11/11 router-suggested agents,
helper-script exports all resolve; 0 under-declared tools; 0 dead executable refs).

Findings (all advisory, no REWORK): thought-leader terminology cluster (5 surfaces,
9 instances; profile :79/:101 are correct negative examples, NOT violations);
monetize :6 description<->body scope self-contradiction; bare ref-paths in
strategy+profile (folds into systemic #3); monetize Audience-Size scorecard +40 vs
/25 cap (suggestion).

Cold-review sweep COMPLETE: 29/29 coverage (S1 + R2a + R2b + R3 + R4 + R5).
v1.0.0 review-blocker lifted. test-runner 138/0 unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GqEHp4uDiivfrAUjw4BdE
This commit is contained in:
Kjell Tore Guttormsen 2026-06-30 10:28:57 +02:00
commit 4fd038ad1d

258
docs/hardening/review-R5.md Normal file
View file

@ -0,0 +1,258 @@
---
type: cold-review
batch: R5
journey: "Grow + Router — growth/authority surfaces + the command router (FINAL round)"
scope: "FROZEN committed files at HEAD 4109fe7 (clean tree; post-hardening cold pass)"
method: "2 independent cold Opus reviewers for the round (intent + correctness), each covering all 6 surfaces, no cross-feed; every mechanical claim tool-grounded (anti-fabrication mandate); reviewers carry NO drafting-session context. Divergences re-grounded by main before registration."
surfaces: [strategy, competitive, monetize, outreach, profile, linkedin]
reviewers:
- "intent-lens (conformance: intent delivery + cross-ref resolution + class predicates + graceful degradation + thought-leadership terminology ban)"
- "correctness-lens (internal consistency + bound-vs-canonical + step/phase arithmetic + allowed-tools completeness + dead-ref / executable-path checks)"
class: "guided/stateful (strategy·competitive·monetize·outreach·profile — primary artifact produced · subagent targets resolve · graceful degradation) + routing (linkedin — every emitted /linkedin:Y resolves)"
status: "COMPLETE — all 6 surfaces reviewed (strategy, competitive, monetize, outreach, profile, linkedin). R5 completes 29/29 cold-review coverage."
verdict: MINOR
counts: { BLOCKER: 0, MAJOR: 0, MINOR: 3, SUGGESTION: 2 }
---
# Cold review — R5 (Grow + Router) · FINAL round → 29/29 coverage
Independent post-hoc cold review of the 6 remaining surfaces — the five Grow-journey
guided/stateful commands (`strategy`, `competitive`, `monetize`, `outreach`, `profile`) plus the
`linkedin` router — on the FROZEN committed files (HEAD `4109fe7`). Mirrors the S1 `review.md` +
R2a + R2b + R3 + R4 model (the cold-review method that did **not** fabricate): read-and-show before
assert, every `file:line` tool-confirmed, reviewers carry no drafting-session context. The
per-command interactive gate (`log.md`) already passed these; this pass adds the **independent**
axis that gate never had. **R5 completes the sweep: 23/29 + 6 = 29/29 cold-review coverage.**
**Resolution integrity — PASS across all 6 surfaces (the headline R5 result).** Both blind lenses
independently confirmed, target-by-target against their own `ls`/`test -f`:
- **2/2** `subagent_type: linkedin-studio:X` refs resolve — `strategy-advisor` (`strategy.md:153`),
`network-builder` (`outreach.md:171`).
- **28/28** unique `/linkedin:Y` route tokens resolve to `commands/Y.md` (router + cross-command
suggestions).
- **11/11** router-suggested agents (named in `linkedin.md`) resolve to `agents/*.md`.
- **2/2** helper-script invocations resolve: `outreach.md`'s `state-updater.mjs --record-outreach`
(flags `--date/--track/--partner/--stage/--next/--due` match `state-updater.mjs:394-411`
byte-for-byte) and `linkedin.md`'s `queue-manager.mjs` import (`queueUpcoming`/`queueOverdue`/
`queueFormatSummary` exist at `queue-manager.mjs:52,94,112`).
- **0 under-declared tools** — every body-invoked tool (Task/Read/Write/Edit/Bash/WebSearch/
AskUserQuestion) is in the surface's `allowed-tools`; only harmless `Glob`/`Grep` over-declarations.
- **0 dead executable Read/Bash targets** — every `references/*`, `${CLAUDE_PLUGIN_ROOT}/skills/…`,
and script path on an executable path verified present.
**No broken invocation, no missing primary artifact, no failing gate, no runtime-breaking
contradiction anywhere in R5 → 0 MAJOR, verdict MINOR (advisory, not REWORK).** This is the
cleanest batch of the sweep.
**Independence cross-check — both outcomes recurred (the case for two lenses, again):**
1. **Convergence:** both blind lenses independently surfaced the same `monetize.md` description↔body
scope mismatch and the same `thought leader` terminology cluster — high confidence these are real.
2. **Divergence (re-grounded by main):**
- **Terminology severity** — intent-lens called the `thought leader` hits MINOR (it owns the
conformity predicate; memory `no-thought-leadership-phrase` makes this a *standing plugin rule*,
not a nicety), correctness-lens called them SUGGESTION (no runtime break). **Main ruling:
MINOR** — a direct violation of an explicit plugin terminology rule in user-facing strings is a
conformity defect; it is not MAJOR (no runtime break).
- **`monetize` scope mismatch severity** — intent-lens SUGGESTION ("description under-claims
body"), correctness-lens MINOR ("description contradicts its own 0-1K Stage 1"). **Main ruling:
MINOR** — it is a genuine self-contradiction about the command's scope, not a mere under-claim.
- **Lens-unique:** correctness-lens alone caught the `monetize` Audience-Size scorecard
arithmetic; intent-lens alone caught the bare-relative-path robustness gap. Both re-grounded by
main below and kept.
---
## strategy.md — VERDICT: MINOR
- **Intent delivered:** yes. Phase auto-detect from state (`:32-44`), phase strategy + delegation
to `strategy-advisor` (`:153`), trajectory overlay (`:250-284`), authority building Phase 2+
(`:286-419`), stall points (Step 4), 90-day plan (Step 5), metrics (Step 6).
- **Resolution:** all resolve — `subagent_type: linkedin-studio:strategy-advisor` (`:153`) →
`agents/strategy-advisor.md` ✓; routes `/linkedin:profile` (`:292,:360`) ✓; `Task` declared.
- **Class predicate (guided/stateful):** growth-plan artifact produced ✓; subagent resolves ✓;
graceful degradation present ✓ (`follower_count` 0/missing handled `:42`; "If no milestone data:
Skip this step" `:284`; authority skipped in Phase 0-1 `:290`).
- **Arithmetic (correctness-lens, grounded):** 5 phases (04) consistent between Step 0.5
auto-detect ranges and Step 2 headers; step numbering monotonic (0.5,1,2,3,3.5,3.6,4,5,6), no
gaps/dupes. PASS.
- **allowed-tools:** declared {Read, Glob, Grep, AskUserQuestion, Task}; under-declared {} ✓;
over {Glob, Grep} (harmless).
- **Findings:**
1. **[MINOR]** `strategy.md:371` "Engaging with other thought leaders" — `thought leader`
terminology-ban hit (off-primary checklist label). Part of the cross-cutting cluster.
2. **[MINOR]** bare relative paths for all file loads; **0×** `${CLAUDE_PLUGIN_ROOT}` (grounded
`grep -c` = 0, vs 110× in competitive/monetize/outreach/linkedin) → `Read` resolves against
cwd, not plugin root. Has `Glob` in allowed-tools as a fallback so it degrades, not fatal.
Folds into systemic finding #3 (bare ref-paths).
## competitive.md — VERDICT: MINOR
- **Intent delivered:** yes. Competitor analysis template (Step 2), landscape map (Step 3),
gap/opportunity matrix (Step 4), differentiation plan (Step 5), inspired takeaways (Step 6),
ethics note.
- **Resolution:** fully self-contained — **no** `subagent_type`, **no** `/linkedin:` routes;
`allowed-tools` (Read/Glob/WebSearch/AskUserQuestion) correctly omits `Task`. ✓
- **Class predicate (guided/stateful):** competitive-analysis artifact produced ✓; no subagents to
resolve ✓; graceful degradation ✓ (Step 1 user-input/WebSearch-driven, runs with no state).
- **Arithmetic:** Steps 06 linear/monotonic; no stated totals to miscount. PASS.
- **allowed-tools:** declared {Read, Glob, WebSearch, AskUserQuestion}; under {} ✓; over {Glob}.
- **Findings:**
1. **[MINOR]** `thought leader(s)` appears **4×** — including the frontmatter `description`
(`:4`, **user-facing**, shows in command listings) and the opening promise (`:17`), plus
`:31,:34`. **Worst terminology offender of the six.** Conformity defect, no runtime break.
## monetize.md — VERDICT: MINOR
- **Intent delivered:** yes. All 8 steps present: scorecard (1), stage strategy (2), lead-magnet
blueprint (3), funnel calendar (4), CTA + A/B variants (5), Featured optimization (6), revenue
model (7), tracking dashboard (8).
- **Resolution:** all resolve — no `subagent_type`; routes `/linkedin:post` + `/linkedin:pipeline`
(`:367`) both exist. ✓
- **Class predicate (guided/stateful):** monetization-plan artifact produced ✓; no subagents ✓;
graceful degradation ✓ ("Stage 1: Visibility (0-1K followers)" `:92` serves brand-new users).
- **Gating honesty:** does NOT hard-gate; serves 0-1K. The real state-read gate is the router's
soft prepend (`linkedin.md:175`, `<1000` → prepend, continue anyway) — consistent with outreach.
- **Findings:**
1. **[MINOR]** **description↔body scope self-contradiction**`monetize.md:6` "Works from 1K+
followers" contradicts its own Stage 1 "Visibility (**0-1K** followers, score 0-30)" (`:92`,
`:81`) and the router's "they work at any follower count" (`linkedin.md:126`). The body
genuinely covers sub-1K; the description misstates scope. Off-primary (a frontmatter string)
but user-facing. **Strongest non-terminology R5 finding.** Fix: align `:6` to "any follower
count (compounds at 1K+)".
2. **[MINOR]** `thought leaders` in the frontmatter `description` (`:4`, user-facing). Part of the
cross-cutting cluster.
3. **[SUGGESTION]** Audience-Size scorecard arithmetic (`:51-56`): sub-items +5/+10/+15/+5/+5 sum
to **+40** against a **`/25`** cap if read additively, whereas the other three categories each
sum to exactly 25 (e.g. Engagement Quality 5+5+10+3+2=25). The follower tiers (1K/5K/10K) are
clearly intended mutually-exclusive (one tier) but aren't marked as such → a literal additive
read overflows. AI-interpreted (not machine-summed) so intent is recoverable; cosmetic, but it
feeds the band that selects the stage. Fix: mark the three follower tiers "(pick one)".
## outreach.md — VERDICT: PASS
- **Intent delivered:** yes, thoroughly. Two-track (collab + speaking) orchestrator with a
Capability Checklist mapping every predecessor function to a step (`:31-62`) + 10 steps + state
persistence.
- **Resolution:** all resolve — `subagent_type: linkedin-studio:network-builder` (`:171`) ✓;
routes `/linkedin:strategy` (`:112`), `/linkedin:firsthour`+`/linkedin:outreach` (`:1078`),
`/linkedin:calendar` (`:1097`) all exist ✓; **Bash executable path sound** — Step 8c's
`state-updater.mjs --record-outreach` (`:1084-1092`) matches the script's handler
(`state-updater.mjs:294,:394-411`) byte-for-byte. ✓
- **Class predicate (guided/stateful):** outreach-plan + persisted pipeline produced ✓; subagent +
script resolve ✓; graceful degradation ✓ (Step 2a "Not ready: <3 met → build foundation first" +
recommend `/linkedin:strategy` `:112`).
- **Arithmetic (correctness-lens, grounded):** "12 collab formats" → 12 (`FORMAT 112`); "4 talk
templates" → AD; "5 phases" → PHASE 15; scorecards 4×/25=/100 and 5×/5=/25 check out; step
numbering monotonic. PASS.
- **allowed-tools:** declared {Read, Glob, WebSearch, AskUserQuestion, Task, Bash}; under {} ✓;
over {Glob}.
- **Gating honesty:** "1K+ followers" self-report (`:100,:143`) consistent with router's `~1K` soft
gate (`linkedin.md:120,:176`) and monetize's 1K. ✓
- **Findings:**
1. **[SUGGESTION]** `thought leader` inside a WebSearch query template (`:229`,
`"[your niche] linkedin thought leader"`). It is a *search string* targeting how others
self-label (intentional — to find such profiles), so the **lowest-priority** instance of the
terminology cluster — but the literal string is still in the plugin.
## profile.md — VERDICT: PASS
- **Intent delivered:** yes. Relevance-model context (`:20-44`), Profile SEO + per-section keyword
targets (`:46-79`), 7-section audit walkthrough (`:82-199`), profile-content alignment check
(`:200-212`), prioritized action plan (`:214-231`), alignment test (`:232-238`).
- **Resolution:** self-contained — no `subagent_type`, no `/linkedin:` routes; `allowed-tools`
Read/AskUserQuestion (tightest frontmatter of the six). ✓
- **Class predicate (guided/stateful + topic-relevance-audit):** the topic-relevance audit **is**
actually performed (the body *is* that audit) ✓; artifact (audit + action plan) produced ✓;
graceful degradation ✓ (every step AskUserQuestion-driven, runs with zero state). Notable
verification discipline: explicitly refuses to fabricate a scoring breakdown (`:28,:32-36,:179`).
- **Arithmetic:** Sections 17 monotonic; profile-field limits (headline 220, About 2,600) are
LinkedIn field limits, NOT post hook/length bounds — no canonical contradiction. PASS.
- **Terminology — IMPORTANT NON-VIOLATION:** the two `thought leader` hits (`:79,:101`) are
**legitimate negative examples** — the command explicitly tells the user to AVOID the phrase
(`:79` lists it with "guru"/"ninja" as keyword-wasters; `:101` is a "Weak example"). Both lenses
agree; main confirmed by reading both lines. **profile.md models the correct behavior.**
- **Findings:** none of defect class.
1. **[Note — systemic #3]** bare relative paths; **0×** `${CLAUDE_PLUGIN_ROOT}` and no `Glob`
fallback → most-exposed instance of the bare-path robustness item. BUT correctness-lens
verified all three referenced files exist (`test -f` OK), so this is a cwd-robustness concern,
not a dead ref. Folds into systemic #3; profile.md is otherwise the cleanest surface of R5.
## linkedin.md (router) — VERDICT: MINOR
- **Intent delivered:** yes. Status line (`:19-29`), upcoming/overdue posts via queue (`:31-57`),
five-journey menu with front-doors (`:59-127`), gating rule (`:122-127`), interactive menu
(`:129-146`), direct-routing table (`:156-198`).
- **Resolution (routing class):** **all 28 unique route tokens resolve** to `commands/*.md`
(verified token-by-token vs `ls commands/`); all 11 suggested agents resolve; `queue-manager.mjs`
node call (`:35-42`) uses exports that all exist (`:52,:94,:112`). **The router advertises nothing
that doesn't exist.** ✓ Correctly has no `Task` (delegate-only).
- **Class predicate (routing):** every route resolves ✓; graceful degradation ✓ (missing state
"No LinkedIn state tracked yet" `:28`; empty queue `:57`; follower segment only if
`follower_count > 0` `:30`).
- **Gating honesty/consistency:** `:175-176` give monetize + outreach the **same** soft state-read
gate (`<1000` → prepend, continue anyway); `:122-127` document the soft-gate design honestly
("they work at any follower count… competitive is **not** gated"). Consistent with both command
bodies. ✓
- **Findings:**
1. **[MINOR]** `linkedin.md:118` "Competitive analysis of other thought leaders" — `thought
leader` terminology-ban hit in the **user-facing** routing-menu table. Part of the cluster.
---
## Summary table
| Surface | Verdict | MAJOR | MINOR | SUGGESTION |
|---|---|---|---|---|
| strategy.md | MINOR | 0 | 2 | 0 |
| competitive.md | MINOR | 0 | 1 | 0 |
| monetize.md | MINOR | 0 | 2 | 1 |
| outreach.md | PASS | 0 | 0 | 1 |
| profile.md | PASS | 0 | 0 | 0 (1 systemic-#3 note) |
| linkedin.md | MINOR | 0 | 1 | 0 |
| **R5 total** | **MINOR** | **0** | **3 distinct** | **2 distinct** |
(Per-surface MINOR counts include shared cross-cutting findings; the 3 *distinct* R5 MINOR findings
are: the terminology cluster, the monetize scope contradiction, and the bare-path robustness item.)
## Cross-cutting findings (for the consolidated fix-pass)
1. **[MINOR · NEW R5 systemic] `thought leader` terminology cluster — 5 surfaces, 9 instances.**
competitive `:4`(description, user-facing)/`:17`/`:31`/`:34` · monetize `:4`(description,
user-facing) · linkedin `:118`(menu, user-facing) · strategy `:371`(checklist) · outreach
`:229`(WebSearch string, lowest priority). **profile `:79,:101` are NON-violations** (correct
negative examples — do not "fix" them). Memory `no-thought-leadership-phrase` confirms this is a
standing plugin terminology rule. **Recommended fix:** one terminology sweep replacing the
user-facing instances first ("thought leaders" → "creators"/"experts"/"voices in your niche"),
leaving profile's avoid-list intact and the outreach search-string as lowest priority. No runtime
risk — the buzzword gate scopes to post content, not command markdown.
2. **[MINOR · per-flate] `monetize.md:6` description↔body scope self-contradiction.** "Works from
1K+ followers" vs the body's own 0-1K Stage 1 (`:92`) and the router's "any follower count"
(`linkedin.md:126`). Fix: align the description to the body's actual sub-1K-onward range.
3. **[MINOR · folds into existing systemic #3] bare ref-paths — strategy + profile (0×
`${CLAUDE_PLUGIN_ROOT}`).** profile most exposed (no `Glob` fallback). Refs verified to exist, so
cwd-robustness not a dead ref. Reconcile in the repo-wide path-style pass already scoped by
systemic #3 (R3/R4 found the same pattern in batch/pipeline/firsthour/analyze/report).
4. **[SUGGESTION · per-flate] `monetize.md:51-56` Audience-Size scorecard sums to +40 vs `/25`
cap.** Follower tiers intended mutually-exclusive but unmarked. Fix: annotate "(pick one)".
**R5 adds NO new ★ systemic finding to the existing three** (clipboard, scaffold-band, bare-paths) —
the only genuinely new cross-cutting item is the terminology cluster, which is a conformity sweep,
not a code-behavior defect.
## Verification
- Spot-checks re-grounded by main (this file): `grep -rniE 'thought.?leader'` over the 6 (9 hits,
classifications above); `sed -n '1,8p'`/`'92p'`/`'48,64p' monetize.md` (scope + scorecard);
`grep -c CLAUDE_PLUGIN_ROOT` per surface (strategy 0, competitive 4, monetize 10, outreach 10,
profile 0, linkedin 1).
- `bash scripts/test-runner.sh` → see STATE telling (expected 138 passed / 0 failed, floor 123) —
unchanged by this review (review is read-only; no command edits).
## Sweep status after R5
**29/29 cold-review coverage reached** (S1 `review.md` + R2a + R2b + R3 + R4 + R5). The independent
cold-review phase is COMPLETE; the v1.0.0 review blocker is lifted. Remaining v1.0.0 work: the
consolidated fix-pass (operator-gated — review FINDS, fix is a separate decision) and the GUI.