linkedin-studio/docs/hardening/review-R3.md
Kjell Tore Guttormsen 69f37ba2b3 docs(linkedin-studio): cold-review R3 (Engage surfaces) — independent kald-review of 4 surfaces
Reproduces the S1/R2a/R2b non-fabricating method: 2 blind cold Opus lenses for
the round (intent + correctness), each covering all 4 surfaces, no cross-feed,
every mechanical claim tool-grounded. Surfaces: firsthour, calendar,
headless-review, pivot.

Verdict REWORK — 1 of 4 surfaces, 1 MAJOR; 0 BLOCKER:
- calendar: the queue load (queueFormatSummary) surfaces none of the
  id/draft_path/character_count that publish/reschedule/cancel require, and the
  reschedule step's "carry from the entry shown in Step 2" is a direct
  contradiction (those fields are never shown) -> MAJOR
- firsthour: ALLOW (1 MINOR bare ref paths; clipboard printf pointer)
- headless-review: ALLOW (2 MINOR: SendUserFile absent from allowed-tools on the
  primary surfacing path; dead v3.1.0 reload anchor post version-reset)
- pivot: ALLOW (clean — heuristic, worked example, off-by-one phase map all
  reconcile)

Independence axis earned its keep again: convergence on headless SendUserFile
(both lenses) + one divergence resolved by main grounding (calendar -
intent-lens flagged the data-gap MAJOR, correctness-lens passed it on structural
arithmetic; main grounded queueFormatSummary's output and confirmed the MAJOR,
same shape as R2b's newsletter resumption table).

Connects to existing systemic findings (no new cross-cutting): clipboard printf
(firsthour confirmed, folds into R2a's 10-file finding) and bare reference paths
(firsthour adds 3 sites to R2b's pattern). New SUGGESTION-class pattern:
allowed-tools over-declaration on 3 of 4 surfaces.

Counts: 0 BLOCKER, 1 MAJOR, 3 MINOR, 5 SUGGESTION. Cumulative cold-review
coverage 17/29. Review finds; changes no code. 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
2026-06-30 09:29:47 +02:00

16 KiB
Raw Blame History

type batch journey scope method surfaces reviewers status verdict counts
cold-review R3 Engage — post-publish & longform-support surfaces FROZEN committed files at HEAD 2b70660 (clean tree; post-hardening cold pass) 2 independent cold Opus reviewers for the round (intent + correctness), each covering all 4 surfaces, no cross-feed; every mechanical claim tool-grounded (anti-fabrication mandate); reviewers carry NO drafting-session context
firsthour
calendar
headless-review
pivot
intent-lens (conformance: intent delivery + cross-ref resolution + class predicates + graceful degradation + terminology)
correctness-lens (internal consistency + bound-vs-canonical + checklist/phase arithmetic + allowed-tools completeness)
COMPLETE — all 4 surfaces reviewed (firsthour, calendar, headless-review, pivot) REWORK
BLOCKER MAJOR MINOR SUGGESTION
0 1 3 5

Cold review — R3 (Engage · post-publish & longform-support)

Independent post-hoc cold review of the 4 Engage-journey surfaces, on the FROZEN committed files (HEAD 2b70660). Mirrors the S1 review.md + R2a + R2b 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.

Independence cross-check — both outcomes recurred this batch:

  1. Convergence: both blind lenses independently surfaced the same real defect on headless-review (SendUserFile invoked on the primary surfacing path but absent from allowed-tools).
  2. Divergence resolved by grounding (the strongest argument FOR the two-lens method, recurring from R2b's newsletter): on calendar the intent-lens flagged a MAJOR (the publish/reschedule/cancel actions key off id/draft_path/character_count that the queue load never surfaces), while the correctness-lens passed the surface as ALLOW — its structural pass found the step/option arithmetic reconciled but did not trace the data-flow from load → display → action placeholders. Main re-grounded queueFormatSummary's actual output independently (below) → the intent-lens is right. The independence axis caught a real MAJOR one lens never probed.

Connections to existing systemic findings (no NEW ★ cross-cutting this batch)

R3 surfaces connect to the two systemic patterns already recorded in R2a/R2b rather than adding new ones. Both connections were re-grounded by main on the R3 files:

  • ★ cross-cutting #1 (clipboard printf '%s', R2a, 10 files) — firsthour confirmed present. firsthour.md:72 printf '%s' '<DRAFT_COMMENTS_BLOCK>' | node …/clipboard-helper.mjs + :75 the unconditional "Copied your draft comments to clipboard." This is the exact systemic pattern: a single-quoted shell string corrupts any draft text containing an apostrophe (it's, don't — common in natural comment copy), and the "Copied" confirmation is unconditional. firsthour is one of the 10 files STATE already lists; no new derivation — folds into the R2a ★ #1 consolidated fix.
  • ★ cross-cutting #2 (bare relative reference paths vs ${CLAUDE_PLUGIN_ROOT}/, R2b) — firsthour adds 3 sites. firsthour.md:110 (prose parenthetical), :118, :119 (Reference-Files pointer list) are bare references/… while the same file's executable blocks correctly prefix ${CLAUDE_PLUGIN_ROOT}/ (:72, :84) and sibling commands prefix their Reference-Files lists too (calendar.md:206-207). Lower impact than the R2b instances (firsthour's bare paths are in a pointer list + one prose mention, not inside an executable Read), so latent rather than active — but a real parity break worth catching in the same repo-wide grep pass. Counted as a per-surface MINOR below.

Recurring (SUGGESTION-class, NOT elevated to ★) — allowed-tools over-declaration. Three of the four surfaces declare a tool the body never invokes: firsthour (Glob/Grep), calendar (Write/Edit), pivot (Grep). Harmless (over-declaration widens permission surface but breaks nothing; under-declaration would be the real risk and there is none). Noted per-surface; optional minimal-surface trim, fold into the consolidated fix if touched.


firsthour.md — VERDICT: ALLOW (0 BLOCKER/MAJOR · 1 MINOR · 2 SUGGESTION + clipboard pointer)

Class: guided/stateful + subagent orchestration ("post-publish first-hour / reply-loop sprint"). Intent delivered: Step 0 load → Step 1 identify post → Step 2 delegate to engagement-coach → Step 3 present (timeline / targets / drafts / velocity) → Step 4 recordFirstHourPlan persist → Step 5 post-feedback-monitor handoff. Both subagent targets carry the linkedin-studio: namespace and resolve (agents/engagement-coach.md, agents/post-feedback-monitor.md); recordFirstHourPlan signature (planDate, postTopic, targets, draftComments, plan) matches the call (:85-91 vs state-updater.mjs:235); 6 steps (grep -cE '^## Step' = 6), sequential, no orphan markers; Step 2→3 value-flow reconciles (coach asked for target-list / self-comments / timeline / velocity, Step 3 presents exactly those four); no "thought leadership". Empty-state degradation present.

MINOR — bare reference paths → see Connections (★ #2) (firsthour.md:110, :118, :119 vs prefixed :72, :84). Intent-lens.

SUGGESTION — Step 0 voice-samples read has no stated fallback

firsthour.md:29 reads voice-samples "so every draft comment is in the user's voice," but no path is specified when the file is absent (progressive onboarding suppresses voice until 5+ samples, CLAUDE.md rule 7). Non-breaking (the coach can still draft), but the degradation is unstated. Add "if absent, draft in a neutral first-person register and skip voice-matching."

SUGGESTION — allowed-tools over-declares Glob/Grep (:12-13); body invokes neither. Correctness-lens. See Recurring note.

(pointer) clipboard printf '%s' systemic bug — firsthour.md:72, :75 confirmed present → folds into ★ #1 (R2a). No new derivation.


calendar.md — VERDICT: REWORK (0 BLOCKER · 1 MAJOR · 0 MINOR · 1 SUGGESTION)

Class: guided/stateful + routing ("view/manage scheduling queue + publish action"). The view side (14-day calendar, format mix, pillar balance) delivers; the action side has a load-bearing data gap. Step/sub-step/option arithmetic all reconcile (correctness-lens: Steps 14 sequential; sub-markers 3a3f present and ordered; 5 options offered with 4 handlers + explicit no-op, no dangling branch; Quick-Routing anchor :89 exists); empty/missing-queue degradation present and correct (queue-manager.mjs:12-27 returns []; body 3a routes "no posts" → /linkedin:batch/quick); no "thought leadership".

MAJOR — the queue load surfaces none of the id/draft_path/character_count the publish/reschedule/cancel actions require (calendar.md:31-43, :117, :169-174, :185 vs queue-manager.mjs:112-122)

  • Verified by main (independent re-grounding of the lens divergence): Step 1 loads the queue exclusively through queueFormatSummary (:31-43). queueFormatSummary (queue-manager.mjs:112-122, read in full) emits only {date} {time} | {hook…} | {pillar} ({fmt}) [{status}] — it exposes no id, no draft_path, no character_count. The Step 2 display (:52-71) mirrors that field set.
  • Three concrete breakages on the action paths:
    1. Mark-as-published (the PRIMARY route — Quick-Routing :25 jumps straight here) calls queueUpdateStatus('[post-id]', 'published') (:117) — [post-id] was never surfaced. Step 3d also needs charCount: NNNN (:129), likewise un-surfaced.
    2. Reschedule (:174) calls queueAdd('[post-id]','[draft_path]', …, [charCount]) (8-arg signature confirmed queue-manager.mjs:63) and is explicitly told to "carry the unchanged fields (draft_path, pillar, format, hook preview, char count) from the entry shown in Step 2" (:169-172) — but Step 2 provably shows none of draft_path/char count/id. A direct contradiction: the instruction points at a view that lacks the fields it says to carry.
    3. Cancel (:185) likewise needs the un-surfaced [post-id].
  • Self-recovery caveat (honest): queue.json is in Reference Files (:208) and Read is allowed, so a capable agent could read raw entries to recover id/draft_path/char_count. But the body never instructs that, and the reschedule text actively mis-directs to Step 2. Latent-but-real on the primary route → MAJOR, not MINOR.
  • Independence note: intent-lens flagged MAJOR; correctness-lens passed the surface ALLOW (its arithmetic/structure pass reconciled but did not trace load→display→action data-flow). Main grounded queueFormatSummary's output → intent-lens confirmed. Two-lens method earned its keep (same shape as R2b's newsletter resumption table).
  • disposition: in Step 1 also dump raw entries (e.g. console.log(JSON.stringify(queueUpcoming(14))), or a queueRead() dump exposing id/draft_path/character_count), and re-point the reschedule text from "the entry shown in Step 2" to "the raw queue entry loaded in Step 1." Surface the display ordinal → queue-id mapping so 3b/reschedule/cancel can fill [post-id].

SUGGESTION — allowed-tools over-declares Write/Edit (:13-14); every mutation routes through Bash node one-liners, "View draft" uses Read. Correctness-lens. See Recurring note.


headless-review.md — VERDICT: ALLOW (0 BLOCKER/MAJOR · 2 MINOR · 1 SUGGESTION)

Class: guided/stateful + heavy subagent orchestration + routing (cold 5-archetype package on a frozen draft → one operator-gated report). Intent delivered: Step 1 resolve-from-disk → Step 2 freeze (cp snapshot) → Step 3 parallel fan-out (the --typesubagent_type table :141-145 maps to the 5 real cold review modes: content / language / fact / persona-resonance / persona-conversion) → Step 4 consolidate → Step 5 surface + optional edition-state.json persist. All reviewer agents resolve; the writing-contract fallback chain terminates in references/longform-quality-rules.md (present); degradation well-handled (missing --draft → edition-state or ask; cp unavailable → live draft + note; degraded reviewer re-runs). "five archetypes" reconciles with the 5-row --type table; 5 flags all consumed, no orphan; no "thought leadership".

MINOR — SendUserFile invoked on the primary surfacing path but absent from allowed-tools (:208, :221 vs :19-25). BOTH lenses.

  • allowed-tools (:19-25) = Read, Glob, Grep, Bash, AskUserQuestion, Task, Write — no SendUserFile; body uses it 2× (grep -c = 2), as the documented primary operator-gated delivery ("operator-gated via SendUserFile"). Held at MINOR (not MAJOR) by two guards: :208 carries an in-text fallback ("else a markdown file:// link") and the report is independently persisted via the declared Write (:206), so surfacing degrades rather than breaks.
  • disposition: add SendUserFile to allowed-tools (if a real tool in the target harness), or soften the body wording from "primary/operator-gated via SendUserFile" to "surface via a file:// link (or SendUserFile if available)."

MINOR — v3.1.0 reload anchor misleads on the post-reset version line (:81-82)

  • Verified by main: :81-82 says the three cold archetypes "were added in v3.1.0 — if the session predates them, reload." Current plugin.json version = 0.5.3 (:3); CHANGELOG [0.4.0] (2026-05-31) records the honest version reset 4.1.0 → 0.4.0, so v3.1.0 is a pre-reset tag no longer on the current line. A reader on 0.5.3 comparing numerically (0.5.3 < 3.1.0) would wrongly conclude they "predate" the agents and must reload — when 0.5.3 is post-reset and already ships all three (they are in CLAUDE.md's 19-agent list). Harmless if followed (an unnecessary reload), but the version anchor misleads.
  • disposition: anchor by event/date, not the dead tag — e.g. "added with the cold-review package (CHANGELOG 3.1.0, pre-reset); reload if your session predates those agents."

SUGGESTION — fan-out N-count unit left implicit: persona-resonance issues "one call per active persona" (:144) while the header counts " archetypes" / " run in parallel" (:172, :219). Pin whether N counts review-modes (5) or Task-calls (≥5). Non-breaking. Correctness-lens.


pivot.md — VERDICT: ALLOW (0 BLOCKER/MAJOR · 0 MINOR · 1 SUGGESTION)

Class: guided/stateful + routing (no subagent orchestration by design) ("re-open a long-form edition so cleared gates re-run before lock"). Intent delivered: Step 1 load+locate (stops if articles.NN absent) → Step 2 measure scope + classify → Step 3 append pivots[], reset currentPhase, un-lock, invalidate downstream verdicts → Step 4 write STATE.md + point at /linkedin:newsletter. Unusually well-reconciled (correctness-lens, all main-checkable): 4 steps sequential; the >20%/>2-sections heuristic stated identically in 3 places (:8-9, :54-55, :92); the worked example's arithmetic checks out (+42% = (19921400)/1400 ✓; "added 2 sections … at the boundary of '>2'" correctly attributes the trigger to the 20% arm since 2 is not >2); the off-by-one phase map is explicitly reconciled (:102-108, to-phase = last completed phase, newsletter resumes at the step after); gatesToRerun (4 entries :123) matches the summary + STATE line (:163, :149-151). allowed-tools correctly omits Task (delegates gate-running to /linkedin:newsletter, never spawns). All 3 Reference-File targets resolve; degradation present (Step 1 stop-on-missing-article; Step 2.2 absent-baseline → ask operator). No "thought leadership".

SUGGESTION — allowed-tools over-declares Grep (:18); the only grep in the body is a shell grep -c '^## ' inside a Bash block (:89), not the Grep tool. Glob plausibly resolves the series root — keep it. Correctness-lens. See Recurring note.


Gate decision — R3 COMPLETE (4 surfaces)

Surface Verdict BLOCKER MAJOR MINOR SUGGESTION
firsthour ALLOW 0 0 1 2
calendar REWORK 0 1 0 1
headless-review ALLOW 0 0 2 1
pivot ALLOW 0 0 0 1
★ #1 clipboard (pointer, firsthour) (folds into R2a)
★ #2 bare paths (firsthour, 3 sites) 0 0 (counted in firsthour)

Batch verdict: REWORK — 1 of 4 surfaces (calendar) carries 1 MAJOR: the queue load surfaces none of the id/draft_path/character_count that publish/reschedule/cancel require, and the reschedule step's "carry from the entry shown in Step 2" is a direct contradiction. 0 BLOCKER anywhere. firsthour / headless-review / pivot are ALLOW (pivot notably clean — every count, the heuristic boundary case, and the off-by-one phase map reconcile).

Independence verdict: convergence on headless-review SendUserFile (both lenses) + one divergence resolved by main's grounding (calendar: intent-lens flagged the data-gap MAJOR, correctness-lens passed it on structural arithmetic, main grounded queueFormatSummary's output and confirmed the MAJOR). Lens-B also uniquely caught the v3.1.0 dead anchor + the over-declaration pattern; Lens-A uniquely caught the bare paths + the clipboard pointer. Both lenses earned their keep.

Systemic findings now span R2a+R2b+R3: clipboard printf (R2a, 10 files incl. firsthour) · component scaffold (R2b, 3 files) · bare reference paths (R2b+R3, now 3 files incl. firsthour's 3 sites). New this batch (SUGGESTION-class, not ★): allowed-tools over-declaration on 3 of 4 R3 surfaces. Cold review finds; it changes no code. Each fix is its own operator-gated decision. Recommended consolidated-fix order unchanged: (1) clipboard printf [R2a, 10 files, highest blast radius], (2) component scaffold [3 files], (3) bare reference paths [grep-driven, now incl. firsthour], then the per-surface items (calendar queue-data MAJOR, headless SendUserFile/v3.1.0, over-declaration trims). Local-only (hardening-class), pushed per the 2026-06-30 operator delegation (public catalog, no secrets).

Cumulative cold-review coverage: 17/29 (review.md S1=4 · R2a=5 · R2b=4 · R3=4).