PR Review — Go CLI and REST contract (pass 2)
Diff reviewed: git diff origin/main..HEAD in the worktree. Multi-persona union below.
Pass-1 High/Medium findings (upload content-type, annotation-list aggregation/limit/offset/total, N+1 in annotation detail, dead code) are all fixed in the current tree and were re-verified against internal/cli/annotation.go, internal/cli/screenshot.go, and app/services/api/v1/project_scope.rb. No plan-required (Requirements Trace A1–A8) command or endpoint is missing.
High
- (none) — all must-have commands, REST endpoints, tests, CI, GoReleaser config, and docs required by the plan are present; prior High findings were resolved.
Medium
- [x] AUTO-FIX: [testing] The project-wide
annotation listaggregate path — the most complex client logic (allScreenshots,allAnnotations,pageAnnotations,cloneValuesininternal/cli/annotation.go:104-218) — has zero test coverage; only the--screenshotpassthrough (TestAnnotationListPath) is exercised: multi-screenshot pagination, in-memory limit/offset windowing, true-total reporting, and per-screenshot error-skip are unverified, yet these are exactly the behaviors pass 1 required and the plan (Unit 4) asks command tests to cover. <!-- triage: duplicate of claude-ce Medium; plan Unit 4 requires command tests -->>
Nit
- [ ] ESCALATE: [security]
screenote configprints the resolvedapi_keyin plaintext to stdout (internal/cli/config.go→writeJSON(a.stdout, resolved);Values.APIKeyserializes asapi_key), a CI-log leak footgun; consider masking unless an explicit opt-in flag is passed (carryover from pass 1, still unresolved — user product decision). <!-- triage: unresolved after checking plan/brainstorm/pass-1 escalation — same product/security decision as Q1; consolidated into single escalation -->> - [x] RESOLVED/NO-FIX: [consistency]
annotation listoutput fidelity differs by path: the aggregate path re-encodes through the GoAnnotationstruct (writeJSON), so any server field not modeled in the struct is silently dropped, while the--screenshotpath passes raw server JSON through (writeRawJSON). Current fields line up, but the two code paths can drift. <!-- triage: duplicate of claude-ce Nit (output shape); fields align today, no clean normalization without regressing passthrough — latent-only -->> - [x] RESOLVED/NO-FIX: [maintainability]
ContractSerializer.annotation_detail(app/serializers/api/v1/contract_serializer.rb:345-356) hardcodesmime_type: "image/png"even whencropped_image_base64is nil (crop failed/missing), so the field misrepresents an absent crop. <!-- triage: worktree — mirrors existing MCP GetAnnotationTool behavior (app/tools/get_annotation_tool.rb:43-44); plan requires matching MCP intent, so changing REST alone introduces drift; cosmetic nit -->> - [x] AUTO-FIX: [type-design] Unused response types in
internal/screenote/types.go—PagesResponse,ScreenshotCreateResponse, andCommentCreateResponseare declared but never referenced (all their commands usewriteRawJSON); dead declarations. <!-- triage: verified unused via grep; dead code removal -->> - [x] AUTO-FIX: [type-design]
Client.doJSON(internal/screenote/client.go:1350) models a single optional request body as a variadicbody ...io.Reader; a plainio.Reader(nil when absent) would be more idiomatic and prevents callers passing multiple readers. <!-- triage: low-risk idiomatic refactor; all call sites pass 0 or 1 reader -->> - [x] AUTO-FIX: [release]
.goreleaser.ymlusesarchives[].builds, the key deprecated in GoReleaser v2 (renamed toids); it still works but may warn now and break on a future upgrade. <!-- triage: verified in .goreleaser.yml:26-28; mechanical rename builds→ids -->> - [x] RESOLVED/NO-FIX: [release] No CI or release workflow invokes GoReleaser on tag;
go installworks, but GitHub Release archives/checksums require a manualgoreleaser releaserun (plan Unit 6 only requires the config + a next-stage dry-run, so low priority). <!-- triage: plan Unit 6 only requires config + next-stage dry-run; tag-triggered release is out of v1 scope -->> - [x] RESOLVED/NO-FIX: [maintainability] The
--interactivepersistent flag (internal/cli/root.go:677) is wired but never read; no interactive prompting exists, so the flag is inert (carryover; plan A6 defines the interactivity contract and v1 has no prompts, so a placeholder is plan-consistent — noted for completeness only). <!-- triage: plan A6 — v1 has no prompts by design; inert placeholder is plan-consistent -->>