linkedin-studio/docs/hardening/review-R4.md
Kjell Tore Guttormsen 4109fe7fd0 docs(linkedin-studio): cold-review R4 (Measure surfaces) — independent kald-review of 6 surfaces
Independent two-lens cold review of the 6 Measure-journey surfaces (import,
report, analyze, audit, ab-test, measure) on frozen HEAD 69f37ba. Largest
batch + only analytics-class batch; analytics-honesty predicate carried
alongside the standard intent/correctness lenses.

Verdict: REWORK (0 BLOCKER · 1 MAJOR · 4 MINOR · 6 SUGGESTION).
- report.md MAJOR: heatmap report type (:72) routes to a nonexistent "Step 6c";
  real handler is Step 2c (:106) — provably-wrong cross-ref on a primary menu
  branch. Caught by the correctness lens alone (intent lens never traced
  step-jump arithmetic); confirmed by main grounding the step inventory.
- 5 of 6 surfaces ALLOW (measure notably clean — delegate-only enforced by the
  allowed-tools whitelist, not just asserted).

Analytics-class predicate PASSES on all 6: saves framed as manual/count-only/
no-API and never folded into engagementRate; dwell called unmeasurable;
parseOptionalCount (csv-parser.ts:71) + getAnalyticsRoot seam described
accurately wherever quoted; graceful degradation present everywhere.

Independence: 2 convergences (import Step 6a invalid trends flags; ab-test
ER-omits-clicks) + 3 divergences resolved by main grounding in both directions
(intent over-rated import 6a MAJOR->MINOR; correctness uniquely caught report
6c + analyze twin severity scales). Two-lens method earned its keep again.

New R4 finding clusters (operator-gated fix, not done here): sibling
interface/metric drift (import stale trends flags vs report; ab-test ER vs CLI
engagementRate), one true under-declaration (report Step 8b Write). No code
changed; cold review finds only.

Cumulative cold-review coverage: 23/29 (S1 + R2a + R2b + R3 + R4). Remaining:
R5 (Grow+Router, 6 surfaces) -> 29/29. 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:58:40 +02:00

23 KiB
Raw Blame History

type batch journey scope method surfaces reviewers class status verdict counts
cold-review R4 Measure — analytics & performance surfaces FROZEN committed files at HEAD 69f37ba (clean tree; post-hardening cold pass) 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.
import
report
analyze
audit
ab-test
measure
intent-lens (conformance: intent delivery + cross-ref resolution + analytics class predicates + graceful degradation + terminology)
correctness-lens (internal consistency + bound-vs-canonical + step/phase arithmetic + allowed-tools completeness + metric-definition cross-check)
analytics — extra predicate: graceful degradation present · saves/dwell honesty intact (parseOptionalCount → unknown/never 0; dwell unmeasurable; saves NOT folded into engagementRate; analytics I/O via getAnalyticsRoot seam) COMPLETE — all 6 surfaces reviewed (import, report, analyze, audit, ab-test, measure) REWORK
BLOCKER MAJOR MINOR SUGGESTION
0 1 4 6

Cold review — R4 (Measure · analytics & performance)

Independent post-hoc cold review of the 6 Measure-journey surfaces, on the FROZEN committed files (HEAD 69f37ba). Mirrors the S1 review.md + R2a + R2b + R3 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. This is the largest batch (6 surfaces) and the only analytics-class batch, so the round carries the extra class predicate (graceful degradation + saves/dwell honesty) alongside the standard intent/correctness lenses.

Analytics-class predicate — PASSES across all 6 surfaces (the headline R4 result). Both blind lenses independently confirmed the honesty contract holds wherever a surface touches the metric: saves are consistently framed as native-only / count-only / ~Sept 2025-onward / no self-serve API / manual-entry (report.md:143,:241; import.md:30,:138,:148; matches cli.ts:144-146 + csv-parser.ts:71 where parseOptionalCount → blank/non-numeric/negative becomes undefined = unknown, never 0) and are never folded into engagementRate (csv-parser.ts:205-208 numerator = reactions+comments+shares+clicks, no saves); dwell is consistently called unmeasurable/internal-to-LinkedIn (report.md:241, import.md:30); no surface claims to import or compute either. The getAnalyticsRoot() per-user data-dir seam (storage.ts) is described accurately wherever quoted. No analytics-honesty violation anywhere in R4.

Independence cross-check — both outcomes recurred this batch (the case for two lenses, again):

  1. Convergence (×2): both blind lenses independently surfaced (a) import.md Step 6a's invalid trends flags (--period 4w / --metric engagement_rate) and (b) ab-test.md:236's manual engagement-rate formula excluding clicks. Two real defects, found twice without cross-feed.
  2. Divergence resolved by grounding (×3, in BOTH directions):
    • intent over-rated, main corrected down — on import the intent-lens flagged the Step 6a CLI block MAJOR (→ REWORK), the correctness-lens flagged the same defect MINOR (→ ALLOW, "off-primary, descriptive"). Main re-grounded import.md:194-217: the executable instruction is the delegation (:200 "Run /linkedin:report"); the bash block (:207-210) is import's description of report's internals, not import's own step → latent, errors only if copy-run → MINOR, import = ALLOW.
    • correctness uniquely caught, main confirmed — on report the correctness-lens flagged a MAJOR (heatmap branch routes to a nonexistent "Step 6c"); the intent-lens was silent (its lens probes agent/command cross-refs, not internal step-jump arithmetic). Main grounded the step inventory → no 6c exists, real handler is 2cMAJOR confirmed. This is the batch's load-bearing defect.
    • correctness uniquely caught, main confirmed — on analyze the correctness-lens flagged a MINOR (two non-reconciling severity scales); the intent-lens was silent → main grounded :155-178 vs :227-231 → confirmed MINOR. Both lenses earned their keep: intent over-rated one finding (corrected by grounding), correctness uniquely caught the two structural defects intent's lens never traced.

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

All connections re-grounded by main against the R4 files:

  • ★ #1 (clipboard printf '%s', R2a, 10 files) — R4 adds nothing. grep -nE "printf '%s'|clipboard-helper" across all 6 R4 files → NONE. The analytics surfaces do not auto-copy to clipboard (they ingest/report data, they don't emit post text), so this systemic pattern simply does not reach the Measure journey.
  • ★ #2 (5-component scaffold band-mismatch, R2b, 3 files) — R4 adds nothing. The length-band tokens that appear (analyze.md:201 "1,200-1,500", :217 "1,500-1,800"; report.md:331 example impressions; ab-test.md:80 test-variable "Short (500) vs standard (1,200-1,800) vs long (2,500+)") are recovery-protocol guidance / illustrative numbers / a test variable — none is a component scaffold that sums outside the standard band. No defect.
  • ★ #3 (bare reference paths vs ${CLAUDE_PLUGIN_ROOT}/, R2b+R3) — R4 connects lightly (lowest impact). analyze.md:22,:23,:93,:259,:260,:261 and report.md:241 carry bare references/… — but none is inside an executable Read/cat (grep -nE "(Read|cat) .*references/" → NONE executable); all are pointer-list entries or prose mentions, the same lowest-impact class as R3's firsthour bare paths. Latent parity break worth catching in the same repo-wide ${CLAUDE_PLUGIN_ROOT}/ grep pass; not elevated to a per-surface finding (neither lens raised it; cosmetic on these surfaces).

Recurring (SUGGESTION-class, NOT elevated to ★) — allowed-tools over-declaration now spans R3+R4. Four of six R4 surfaces declare a tool the body never invokes: import/report (Glob — listing done via Bash ls/find), audit (Grep — no grep call). Combined with R3's 3-of-4, the pattern now touches ~7 surfaces. Harmless (over-declaration widens the permission surface but breaks nothing; under-declaration is the real risk and there is one true instance this batch — report Step 8b, recorded as MINOR below). Optional minimal-surface trim; fold into the consolidated fix if touched.

New R4 cluster (not ★, analytics-specific) — sibling-command interface/metric-definition drift. Two of the four MINORs are the same shape: an analytics surface quotes another surface's CLI interface or a shared metric definition and drifts from the SSOT — import.md Step 6a's stale trends flags vs report.md's correct ones, and ab-test.md:236's manual engagement-rate (clicks excluded) vs the CLI's engagementRate (clicks included, csv-parser.ts:205). Both are latent (cross-reference paths, not primary execution) but both are real consistency debt between siblings. Worth a single reconciliation note in the consolidated fix: pin the canonical engagementRate definition + CLI flag vocabulary once, and make the descriptive blocks point at it rather than restate it.


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

Class: analytics (import orchestrator). Intent delivered: primary artifact is the structured JSON batch written by cli.ts import (Step 4 invokes it; output surfaced Step 5), then analysis delegated to /linkedin:report (Step 6). Step inventory 1·1b·2·3·4·5·5b·6·6a·6b·7·8 sequential, no gaps; Step 1b/Step 3 option lists each carry a Skip/Cancel disposition. allowed-tools (:10-15 Bash/Read/Glob/Write/AskUserQuestion) — Bash/Read/Write/AskUserQuestion all invoked. Cross-refs resolve (report.md, setup.md, quick-import.mjs, assets/analytics/README.md all exist; no subagent_type). Degradation present (no-CSV, nothing-anywhere, missing-deps npm install, skipped rows on empty-title/unparseable-date matching csv-parser.ts:187,193). saves/dwell honesty intact (:30,:138,:148). No "thought leadership".

MINOR — Step 6a documents report's CLI calls with invalid period + metric, contradicting the real owner (import.md:200, :206, :207-210)

  • Verified by main (the intent/correctness severity divergence, re-grounded :194-217): :207-210 shows trends --period 4w --metric impressions and --metric engagement_rate inside a bash fence. The CLI accepts period week|month|quarter|all (cli.ts:217, validated → process.exit(1) :219-221) and metric …|engagementRate camelCase (cli.ts:202-209, validated → process.exit(1) :233-234) — both 4w and engagement_rate would error. The real owner report.md:153,:171 uses the correct --period month --metric engagementRate. :200 "(period: 4w)" and :206 "Read expertise_areas" also mis-describe report (it takes no period arg, never reads expertise_areas).
  • Why MINOR not MAJOR (intent-lens rated MAJOR; main grounds down): the executable instruction on this path is the delegation at :200 ("Run /linkedin:report") → routes to report.md, which is correct. The bash block (:207-210) is import's narrative description of report's internals ("/linkedin:report will: … 2. Call trends…"), not a step import itself runs. Latent (errors only if a reader copy-runs the illustrative block) and the primary delegation path is unaffected → MINOR, not MAJOR. Both lenses converged on the defect's existence; only the severity diverged.
  • disposition: drop the illustrative bash + the "(period: 4w)"/"expertise_areas" description, or mirror report's real invocations (--period month --metric engagementRate). Fold into the sibling-drift reconciliation note.

SUGGESTION — allowed-tools over-declares Glob (:13); directory listing uses Bash ls/find (:37,:47). Both lenses. See Recurring note.


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

Class: analytics (report orchestrator). Intent delivered: produces weekly/monthly/heatmap report JSON via cli.ts report/heatmap + a formatted presentation (Step 6) + an analytics-interpreter handoff (subagent_type: linkedin-studio:analytics-interpreter :308agents/analytics-interpreter.md ✓, Task declared :14). Trends flags --period month --metric engagementRate (:153,:171,:366) all valid vs CLI. Degradation present (no-data, npm install, week-not-found/empty-week/ERR_MODULE_NOT_FOUND :387-400). saves/dwell honesty exemplary (:143,:241). No "thought leadership".

MAJOR — the heatmap report type routes to a nonexistent "Step 6c" (report.md:72)

  • Verified by main (correctness-lens caught it; intent-lens silent — lens gap, not contradiction): the step inventory (grep -nE '^### Step|^## Step') is 1·1b·2·2b·2c·3·4·5·5b·5c·6·7·8·8bthere is no Step 6c. :72 ("If heatmap (option 3): Run the heatmap CLI command and jump to Step 6c") points a reader nowhere. The real heatmap handler is Step 2c (:106, immediately below the monthly Step 2b), which itself "jump[s] to Step 7" (:114). The two sibling branches are correct (monthly :71→2b; weekly→inline), so heatmap — one of three top-level report types in Step 2 — is the lone misroute.
  • Severity: a provably-wrong cross-reference on a primary menu branch (top-level report-type selection, not a deep-dive). Recoverable (:72 also says "Run the heatmap CLI command," and 2c sits right under 2b, so a capable agent recovers by proximity) — hence MAJOR, not BLOCKER — but it is the batch's one load-bearing navigation defect.
  • disposition: retarget :72 from "Step 6c" to "Step 2c".

MINOR — Step 8b markdown export under-declares its write tool (report.md:429-431 vs :9-15)

  • Verified by main: Step 8b (reached via Step 8 option 4, "Export report as markdown file") instructs "Format the data using this template and write to file" / "Save to: …-report.md" (:429-431). Frontmatter allowed-tools (:9-15) = Bash/Read/Glob/AskUserQuestion/Task — no Write. This is the one genuine under-declaration this batch (the real-risk class). Held at MINOR by two mitigations: the step is an optional deep-dive (not the primary path), and the declared Bash can satisfy the write via a heredoc/cat >. Sibling ab-test.md:12-19 declares Write for the same .md-save, so the omission reads as an oversight/parity break.
  • disposition: add Write to allowed-tools (parity with ab-test) or rephrase Step 8b to write via the declared Bash.

SUGGESTION — allowed-tools over-declares Glob (:12); listing uses Bash ls (:30,:197). Correctness-lens. See Recurring note.


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

Class: analytics-adjacent (read-only diagnostic; no CLI). Intent delivered: diagnosis + recovery plan from reference files + AskUserQuestion (Steps Load-Context·1-8). Cross-refs resolve (subagent_type: linkedin-studio:analytics-interpreter :41 → agent ✓, Task declared :12; /linkedin:profile :188commands/profile.md ✓). Degradation present (analytics delegation is conditional :41 with a self-report fallback; functions with zero data). No saves/dwell claims → nothing to contradict. No "thought leadership".

MINOR — two non-reconciling severity scales in one command (analyze.md:155-178 vs :227-231)

  • Verified by main (correctness-lens; intent-lens silent — lens gap): Step 5 grades reach drop on four percentage bands (<25 / 25-50 / 50-75 / 75%+, :155-178). Step 7's timeline table (:227-231) uses three rows on a different axis — "Moderate (link/off-topic)", "Moderate (partial reach loss)", "Severe (sharp reach loss)". The two scales share no common key, so a user holding a Step 5 result (e.g. "Down 50-75% → algorithmic suppression likely") cannot map it to a Step 7 timeline row. Advisory/usability inconsistency, low-confidence; nothing breaks.
  • disposition: cross-label the Step 7 rows to the Step 5 bands, or state explicitly that they are independent axes.

SUGGESTION — the :41 existence check ("If imported analytics data exists …/analytics/") implies Glob/Bash, neither declared (allowed-tools :9-13 = Read/AskUserQuestion/Task). Softer than report's Write gap: no explicit tool call is written at :41 (it's a conditional prose phrase) and the real data access is delegated to analytics-interpreter via Task. Correctness-lens.


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

Class: analytics-adjacent (read-only strategy auditor; no CLI). Intent delivered: audit report (Step 7 template) + action items (Step 8). Steps 0·1·2·3·4·5·5.5·6·7·8 sequential. Routing resolves (/linkedin:strategycommands/strategy.md ✓, /linkedin:profile ✓; no subagent_type, Task correctly absent from allowed-tools). Degradation present (Step 0 checks for analytics data + asks for screenshots/metrics; milestone block self-skips with no data :140). Correctness cross-check (both lenses): the state fields the milestone block reads — follower_count, monthly_growth, growth_rate_needed (:105,:109,:128) — all exist in config/state-file.template.md:23,26,28, so it reads real fields. No saves/dwell claims. No "thought leadership".

SUGGESTION — allowed-tools over-declares Grep (:11); Step 0 uses Read/Glob, no grep call in the body. Both lenses (convergence). See Recurring note.


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

Class: analytics (experiment manager; manual metric entry, no CLI). Intent delivered: primary artifact is the test-plan markdown written to analytics/ab-tests/[name].md (Step 2a.8) + running comparison / analysis (2b/2c). Step inventory 0·1·2a(.1-.8)·2b·2c·2d·2e·3 consistent; Step 1's 6 intents map to 2a-2e + option 6 self-handles (:57). Post-count arithmetic coherent ("3 per variant / 6 total" :127; 6-row execution table :155-162; "X of 6" :255). allowed-tools (:12-19 Read/Glob/Write/Bash/AskUserQuestion/Task) — Read/Write/Bash/AskUserQuestion/Task(→content-optimizer :119 ✓) all invoked. Degradation present (Error Handling: No-Tests-Directory, Incomplete-Data, Missing-Analytics, Corrupted-files :472-493). Statistical honesty notably correct — 2c.4/Confidence-Level (:311,:320-331) explicitly demotes small-sample results to "directional, not significant." No saves/dwell claims. No "thought leadership".

MINOR — manual engagement-rate formula excludes clicks, diverging from the canonical engagementRate (ab-test.md:236)

  • Verified by main (both lenses converged): :236 computes ER as (reactions + comments + reposts) / impressions * 100 — clicks excluded (and 2b.3 :228-234 never collects clicks). The CLI's engagementRate includes clicks: totalEngagement = reactions + comments + shares + clicks (csv-parser.ts:205-208). Step 2c.3 (:282-288) cross-references the A/B numbers against the CLI weekly reports, where the two rates will not match.
  • Severity: internally consistent within a single test (same formula on both variants, and A/B is variant-relative), so the verdict-within-a-test is unaffected → MINOR, latent on the cross-reference path.
  • disposition: add a one-line "clicks excluded by design" caveat, or align the manual formula to the canonical definition. Fold into the sibling-drift reconciliation note.

SUGGESTION — bare agent name in a user-facing suggestion (ab-test.md:468): "use the content-optimizer agent" surfaces a bare name where the canonical form (correctly used at :119) is the namespaced subagent_type. Cosmetic. Intent-lens.

SUGGESTION — allowed-tools over-declares Glob (:14); scans use Bash ls (:37,:209). 2b.4/2c.5 append/update are edit-shaped but the declared Write covers them via full rewrite (no Edit gap). Correctness-lens. See Recurring note.


measure.md — VERDICT: ALLOW (0 findings — clean)

Class: front-door router (delegate-only). Intent delivered and structurally enforced (both lenses converged): allowed-tools (:10-13) = Glob + AskUserQuestion only — no Bash/Read/Task — so the command cannot run analysis itself; it identifies intent (Step 1, 5 intents :30-34) and routes (Step 2 table :41-47, one row per intent). All five routes — /linkedin:import, /linkedin:report, /linkedin:analyze, /linkedin:audit, /linkedin:ab-test — resolve to existing command files. Degradation present (Step 0 glob optional, "Do not block on it"; order-note :49-50 routes to import first when nothing imported). No dangling branch, no analysis logic to drift, no saves/dwell claims, no "thought leadership". The thinnest, cleanest surface in the batch — the delegate-only contract is enforced by the tool whitelist, not just asserted.


Gate decision — R4 COMPLETE (6 surfaces)

Surface Verdict BLOCKER MAJOR MINOR SUGGESTION
import ALLOW 0 0 1 1
report REWORK 0 1 1 1
analyze ALLOW 0 0 1 1
audit ALLOW 0 0 0 1
ab-test ALLOW 0 0 1 2
measure ALLOW 0 0 0 0
TOTAL REWORK 0 1 4 6

Batch verdict: REWORK — 1 of 6 surfaces (report) carries 1 MAJOR: the heatmap report type routes to a nonexistent "Step 6c" (real handler Step 2c) — a provably-wrong cross-reference on a primary menu branch. 0 BLOCKER anywhere. The other five are ALLOW (measure notably clean — its delegate-only contract is enforced by the allowed-tools whitelist, not merely asserted).

Analytics-class predicate verdict: PASS. The saves/dwell honesty contract holds on every surface that touches the metric — saves framed as manual/count-only/no-API and never folded into engagementRate, dwell called unmeasurable, neither claimed as imported; parseOptionalCount semantics (csv-parser.ts:71) and the getAnalyticsRoot seam described accurately wherever quoted. Graceful degradation present on all six. This was the batch-specific axis and it is clean.

Independence verdict: two convergences (import Step 6a flags; ab-test ER-omits-clicks) + three divergences resolved by main's grounding in both directions — intent over-rated import's Step 6a (MAJOR→MINOR on grounding that the executable path delegates), while correctness uniquely caught report's Step 6c misroute (MAJOR) and analyze's twin severity scales (MINOR) that intent's lens never traced. The two-lens method earned its keep again: had only the intent-lens run, report would have shipped ALLOW with a broken primary branch; had only the correctness-lens run, import would have over-escalated to REWORK.

Systemic findings now span R2a+R2b+R3+R4: clipboard printf (R2a, 10 files — R4 adds none) · component scaffold (R2b, 3 files — R4 adds none) · bare reference paths (R2b+R3+R4 — R4 adds 7 lowest-impact pointer/prose sites in analyze/report, not executable). New this batch: (a) allowed-tools over-declaration now spans R3+R4 (~7 surfaces, SUGGESTION-class, harmless) with one true under-declaration (report Step 8b Write, MINOR); (b) a small sibling interface/metric-definition drift cluster (import's stale trends flags + ab-test's clicks-excluded ER) — reconcile once against the CLI SSOT. 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], then the per-surface items — now including report Step 6c misroute (the one R4 MAJOR), report Step 8b Write, the sibling-drift reconciliation (import 6a + ab-test ER), analyze severity scales, and the over-declaration trims. Local-only (hardening-class), pushed per the 2026-06-30 operator delegation (public catalog, no secrets).

Cumulative cold-review coverage: 23/29 (review.md S1=4 · R2a=5 · R2b=4 · R3=4 · R4=6). Remaining: R5 (Grow+Router) — strategy · competitive · monetize · outreach · profile · linkedin (6 surfaces) → 29/29.