Lifts the original v4.0.0 Non-Goal: an optional, manually-entered `saves`
metric through the analytics layer, built location-agnostic (option c) so
UI-brief §9b/M0 relocates the data dir in one place later.
- types: PostMetrics.saves? + Weekly/Monthly summary.totalSaves? (optional);
new RankableMetric type for the always-numeric index-access whitelist
- parser: dedicated parseOptionalCount() — blank/non-numeric/negative -> undefined
("unknown != 0"), genuine 0 kept; saves NOT folded into engagementRate
- reports: totalSaves set only when >=1 post carries saves (backward-compat)
- cli: saves surfaced in import summary + weekly/monthly totals + per-post
- S16-pre: onboarding.md allowed-tools gains Write (closes S15-deferred MAJOR)
- docs (three-doc rule): plugin README boundary + analytics README + root README
+ plugin CLAUDE.md + CHANGELOG; dwell stays explicitly unmeasurable
Independent /trekreview: brief-conformance 0 findings; code-correctness 2 MAJOR
(own lockstep misses) FIXED in-session (parseOptionalCount + edge tests). Gate:
tsc clean, analytics 116/116, lint 74/0/0, hooks 98/98. Within-v4.1.0 refinement
(no surface/count/version change).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
8.3 KiB
| type | review_version | task | slug | project_dir | brief_path | scope_sha_start | scope_sha_end | reviewed_files_count | verdict | mode | effort | profile | findings |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| trekreview | 1.0 | S16 — saves manual-entry surface (lifts the original Non-Goal): optional saves in the analytics data model (types + parser + weekly/monthly + CLI), built location-agnostic for M0 (option c); + S16-pre: close the deferred onboarding Write MAJOR | remediation | docs/remediation/ | docs/remediation/brief.md | 8c52bdb |
8c52bdb |
15 | ALLOW | default | standard | premium |
Review — linkedin-studio S16 (saves manual-entry + S16-pre onboarding Write)
Executive Summary
Verdict: ALLOW for S16's delivered scope — 0 BLOCKER, 0 MAJOR, 0 MINOR, 0 SUGGESTION
open against the diff. Two independent reviewers (brief-conformance, code-correctness)
ran COLD, without cross-feeding, on the as-delivered uncommitted working tree (HEAD
8c52bdb + the S16 delta).
- brief-conformance-reviewer: 0 findings. Every Success Criterion traces to delivered
code (SC1 saves in types + parser + import/report; SC2 saves in the actionable-signal output;
SC3 dwell still explicitly unmeasurable, no fabricated field; SC4 backward-compat + no-crash;
SC5 onboarding
Write). Every Non-Goal honored: M0 not built (I/O still routes through the unmodifiedgetAnalyticsRoot()seam — option (c)), dwell not fabricated, surface counts (29/19/6/9) and version (v4.1.0) unchanged, no not-mine file touched. - code-correctness-reviewer: 2 MAJOR — both in S16's own new code, both FIXED in-session (see Findings). Neither is pre-existing; both are lockstep misses in the saves parser delivered this session, so per the operator rule ("in-session fix of the session's own misses = completion") they were corrected here, not deferred.
The M0 conflict (UI-brief §9b) was reconciled before building, by operator decision =
option (c): build now, location-agnostic. The conformance reviewer independently confirmed
the build honors it (no new hardcoded assets/analytics path; storage.ts untouched).
Coverage
Scope: HEAD 8c52bdb (S15's commit) + the uncommitted S16 working-tree delta (annotated
[uncommitted] — a brief-level contract; the brief's Assumptions allow uncommitted review).
The 3 untracked not-mine files (docs/linkedin-studio-persona-brief.md, …-ui-brief.md,
docs/voyage-build/progress.json) are explicitly excluded from scope and from the commit.
No silent skips.
| Treatment | Count | Notes |
|---|---|---|
deep-review |
0 | nothing under hooks/** / auth/** / crypto/** / **/security/** (analytics CLI + one command frontmatter) |
summary-only |
15 | analytics src (5) + analytics tests (3) + 2 fixtures + assets/analytics/README.md + README.md + CHANGELOG.md + commands/onboarding.md |
skip |
0 | no lockfiles / svg / generated / dist |
Execution criteria (orchestrator-run):
npx tsc --noEmit(analytics) → clean (the newRankableMetrictype closes themetrics[keyof]widening the optionalsaveswould otherwise introduce inalerts.ts+cli.ts).node --import tsx --test tests/*.test.ts(analytics) → 116/116.bash scripts/test-runner.sh→ 74 passed / 0 failed / 0 warnings, exit 0.node --test hooks/scripts/__tests__/*.test.mjs→ 98/98 (no hook logic changed).ls commands/*.md→ 29 (no surface-count change; no version bump — within-v4.1.0 refinement, S11–S13 precedent).- E2E: import + weekly + monthly with a saves fixture surfaces saves without crashing;
saves-free fixtures round-trip byte-identical; explicit
"0"→0, blank/"n/a"→ undefined.
Findings
0 open findings. Code-correctness raised 2 MAJOR; both are S16's own new code (not pre-existing — the saves parser is new this session), so both were fixed in-session as completion of the delivered work, then re-verified green. Recorded below — not dropped, not silenced.
[MAJOR — FIXED in-session] Non-numeric Saves cell silently coerced to 0 (unknown != 0 contract violated)
Raised by code-correctness (PLAN_EXECUTE_DRIFT), scripts/analytics/src/parsers/csv-parser.ts.
The original guard if (savesRaw && savesRaw.trim() !== "") then metrics.saves = parseMetric(savesRaw) gated only on emptiness; parseMetric ends in parseFloat(...) || 0
Math.max(0, …), so a non-empty non-numeric cell ("n/a","~40") or a negative value flattened to 0 — surfaced as a confident "top engagement signal". This contradicts theunknown != 0contract stated in the same diff (types.tssaves NOTE + the parser comment).
Fix (this session): added a dedicated parseOptionalCount() helper returning
number | undefined — blank / non-numeric / negative → undefined; a genuine number
(including explicit "0") → that number; reuses the EU/US separator normalization. The saves
parse now routes through it. The blank-cell path is unchanged; the garbage/negative path now
honors the contract.
[MAJOR — FIXED in-session] No test for the explicit-0 vs non-numeric Saves boundary
Raised by code-correctness (MISSING_TEST), scripts/analytics/tests/csv-parser.test.ts.
The original saves tests covered 42, blank→undefined, no-column→undefined, and
engagement-exclusion — but not the exact boundary where the parser's behavior diverged from
its intent: a literal "0" (genuine zero) vs a non-numeric cell (unknown).
Fix (this session): added tests/fixtures/saves-edge-export.csv (a "0" row + an "n/a"
row) and two cases pinning saves === 0 for "0" and saves === undefined for non-numeric.
The non-numeric case was red before the parser fix above and green after (TDD), so the
contract boundary is now regression-guarded.
[MAJOR — CLOSED] (carried from S15) onboarding Phase 2 file-saves need Write
S15's deferred finding. commands/onboarding.md Phase 2 saves voice/user-profile files but
the frontmatter omitted Write. Closed in S16-pre: Write added to allowed-tools
(matching first-post.md:9-14).
Remediation Summary
Gate: ALLOW for S16's delivered scope. brief-conformance is clean; code-correctness's two
MAJORs were S16's own lockstep misses (not pre-existing design findings), so both were fixed
in-session and re-verified (tsc clean, analytics 116/116, lint 74/0/0, hooks 98/98) — this is a
genuine ALLOW with no open finding, not a WARN-override. The M0 conflict was reconciled
before building (option c, location-agnostic); the conformance reviewer confirmed M0 was not
built and the data-dir seam (getAnalyticsRoot()) is untouched, so the planned migration
relocates the root in one place. The S15-deferred onboarding Write MAJOR is closed.
Per Handover 6, this review.md is consumable by /trekplan --brief …. ALLOW → S16 commits +
pushes (own files only); the finish-plan continues at S17 (C13–C46 triage).
{
"verdict": "ALLOW",
"verdict_scope": "S16 delivered changes (saves manual-entry + S16-pre onboarding Write); 15 files",
"scope": { "sha_start": "8c52bdb", "sha_end": "8c52bdb", "reviewed_files_count": 15, "uncommitted_delta": true },
"counts": { "BLOCKER": 0, "MAJOR": 0, "MINOR": 0, "SUGGESTION": 0 },
"findings": [],
"fixed_in_session": [
{
"severity": "MAJOR",
"title": "Non-numeric Saves cell silently coerced to 0 (unknown != 0 contract)",
"file": "scripts/analytics/src/parsers/csv-parser.ts",
"rule_key": "PLAN_EXECUTE_DRIFT",
"resolution": "added parseOptionalCount() returning number|undefined (blank/non-numeric/negative -> undefined; genuine 0 kept); saves parse routes through it"
},
{
"severity": "MAJOR",
"title": "No test for explicit-0 vs non-numeric Saves boundary",
"file": "scripts/analytics/tests/csv-parser.test.ts",
"rule_key": "MISSING_TEST",
"resolution": "added saves-edge-export.csv fixture + 2 cases (0 -> 0, n/a -> undefined); TDD red-before/green-after the parser fix"
},
{
"severity": "MAJOR",
"title": "onboarding Phase 2 file-saves need Write (carried from S15)",
"file": "commands/onboarding.md",
"rule_key": "PLAN_EXECUTE_DRIFT",
"resolution": "S16-pre: added Write to allowed-tools, matching first-post.md"
}
],
"deferred_findings": [],
"dropped_findings": []
}