Repository navigation
feat(pins): add pins add, remove and list - #218
Conversation
slck can now pin and unpin a message and list what is pinned in a channel, through pins.add, pins.remove and pins.list. Pinning an already pinned message, or unpinning one that is not pinned, succeeds and says so. A missing_scope error names the scope the command needs (pins:write or pins:read). The extended manifest gains pins:write.
monit-reviewer
left a comment
There was a problem hiding this comment.
Automated PR Review
Reviewed commit: fa40e6210ae6
Profile: claude-monit-reviewer - Posting as: monit-reviewer
Summary
| Reviewer | Findings |
|---|---|
| go:implementation-tests | 0 |
| harness-engineering:repo-health | 1 |
| policies:conventions | 0 |
| documentation:docs | 0 |
| architecture:solid-reviewer-agnostic | 1 |
| security:code-auditor | 0 |
harness-engineering:repo-health (1 finding)
Nits - internal/cmd/pins/pins.go:35
quiet()is a verbatim copy of the helper ininternal/cmd/apps/apps.go(same body, slightly different doc comment). The PR describes "print the runtime error and hint once, without usage" as a convention shared withslck apps, so it is becoming a cross-package pattern, but each new command package now copies it locally and the two comments already disagree on the reason (apps mentions the silent exitError). The next command to adopt the behavior will copy it a third time. Suggested fix: move it into one shared place (for example a small exported helper beside the root command, or setSilenceUsage/SilenceErrorsin aPersistentPreRunon the subcommands that opt in) and call it from both apps and pins. Also add a line to the Command Patterns section of docs/development.md so future agents use it instead of copying it.
architecture:solid-reviewer-agnostic (1 finding)
Minor - internal/client/errors.go:64
U-L2 (Errors are contracts): the new exported
WrapScopeErrordecides behaviour by substring-matching the error message (IsSlackError→strings.Contains(err.Error(), code)). The newAddPin/RemovePinin internal/client/pins.go use the same check foralready_pinned/no_pin/not_pinned. The generic Slack path in client.go already returnsfmt.Errorf("slack API error: %s"), so this follows existing practice, and the codes cannot collide today. Still, it adds three more call sites and one more public helper built on message matching, while the apps path in the same package already has a typedAppsAPIError{Code}thatapps.wrapmatches witherrors.As. This is non-blocking because it copies an existing pattern. A follow-up (or this PR, if cheap) could givepost/geta typed*SlackAPIError{Code string}and haveIsSlackErrorandWrapScopeErroruseerrors.Asand compareCodeexactly. Everything that callsIsSlackErrorwould then use the typed check without changes.
Reviewer Coverage
Complete coverage details are retained in the local coverage.json artifact.
go:implementation-tests— complete (broad); scope: 19 files; examples:internal/client/errors.go,internal/client/errors_test.go,internal/client/pins.go,internal/cmd/pins/add.go,internal/cmd/pins/list.go; 14 omitted; body-inspected: 19 files; examples:internal/client/errors.go,internal/client/errors_test.go,internal/client/pins.go,internal/cmd/pins/add.go,internal/cmd/pins/list.go; 14 omitted; skipped: none; context (not coverage): 5 files; examples:internal/client/channel_resolver.go,internal/client/client.go,internal/cmd/apps/apps.go,internal/cmd/messages/react.go,internal/output/output.go; constraints: Lack of context.Context propagation and substring-based IsSlackError matching are pre-existing client patterns that the new code follows; not flagged as diff-introduced drift. Static review only; tests were not executed.harness-engineering:repo-health— complete (constrained); scope: 19 files; examples:internal/client/errors.go,internal/client/errors_test.go,internal/client/pins.go,internal/cmd/pins/add.go,internal/cmd/pins/list.go; 14 omitted; body-inspected: 19 files; examples:internal/client/errors.go,internal/client/errors_test.go,internal/client/pins.go,internal/cmd/pins/add.go,internal/cmd/pins/list.go; 14 omitted; skipped: none; context (not coverage): 7 files; examples:ARCHITECTURE.md,CLAUDE.md,README.md,docs/development.md,integration-tests.md; 2 omitted; constraints: Test fixture JSON bodies were not read one by one. Their behavior was checked through the assertions in pins_test.go. Tests and lint were not run. The review was a static read only. Tests were not run. The fixtures were checked only by reading them, comparing them with the assertions in pins_test.go and with the existing fixture layout in internal/cmd/apps/testdata.policies:conventions— complete (broad); scope: 8 files; examples:README.md,docs/development.md,internal/client/errors.go,internal/cmd/pins/add.go,internal/cmd/pins/list.go; 3 omitted; body-inspected: 8 files; examples:README.md,docs/development.md,internal/client/errors.go,internal/cmd/pins/add.go,internal/cmd/pins/list.go; 3 omitted; skipped: none; context (not coverage): 5 files; examples:AGENTS.md,CLAUDE.md,internal/cmd/apps/apps.go,internal/cmd/search/render.go,internal/output/output.go; constraints: Shared cli-common docs (../cli-common/docs) were not available locally; review used repo-local docs/development.md, README conventions, and existing command patterns (apps quiet(), search SearchTable, text/table-only output contract).documentation:docs— complete (broad); scope: 3 files; examples:README.md,docs/development.md,integration-tests.md; body-inspected: 3 files; examples:README.md,docs/development.md,integration-tests.md; skipped: none; context (not coverage): 5 files; examples:internal/cmd/pins/add.go,internal/cmd/pins/list.go,internal/cmd/pins/pins.go,internal/cmd/pins/remove.go,internal/render/render.go; constraints: Docs checked against the pins command source (aliases, output strings, list columns, file-pin dash handling, scope requirements); the commands were not run against a live workspace.architecture:solid-reviewer-agnostic— complete (broad); scope: 7 files; examples:internal/client/errors.go,internal/client/pins.go,internal/cmd/pins/add.go,internal/cmd/pins/list.go,internal/cmd/pins/pins.go; 2 omitted; body-inspected: 7 files; examples:internal/client/errors.go,internal/client/pins.go,internal/cmd/pins/add.go,internal/cmd/pins/list.go,internal/cmd/pins/pins.go; 2 omitted; skipped: none; context (not coverage): 5 files; examples:ARCHITECTURE.md,docs/development.md,internal/client/client.go,internal/cmd/apps/apps.go,internal/cmd/messages/react.go; constraints: Rango test ./internal/cmd/pins/ ./internal/client/(both pass); did not runmake lint. Shared cli-common standards referenced by docs/development.md were not available locally.security:code-auditor— complete (constrained); scope: 6 files; examples:internal/client/errors.go,internal/client/pins.go,internal/cmd/pins/add.go,internal/cmd/pins/list.go,internal/cmd/pins/pins.go; 1 omitted; body-inspected: 6 files; examples:internal/client/errors.go,internal/client/pins.go,internal/cmd/pins/add.go,internal/cmd/pins/list.go,internal/cmd/pins/pins.go; 1 omitted; skipped: none; context (not coverage): 3 files; examples:internal/cmd/root/root.go,internal/output/output.go,internal/validate/validate.go; constraints: Static review only; tests were not executed. Checked that token selection uses the shared root --as-user/--as-bot PersistentPreRunE, that timestamps are regex-validated before any API call, and that Slack-sourced table cells pass through sanitizeSearchCell.
0 PR discussion threads considered. 0 summarized; 0 resolved.
Completed in 2m 59s | ~$2.30 (est.) | claude-opus-5-5 | cr 0.10.354
| Field | Value |
|---|---|
| Model | claude-opus-5-5 |
| Reviewers | go:implementation-tests, harness-engineering:repo-health, policies:conventions, documentation:docs, architecture:solid-reviewer-agnostic, security:code-auditor |
| Engine | claude_cli · claude-opus-5-5 |
| Reviewed by | cr · monit-reviewer |
| Duration | 2m 59s wall · 6m 36s compute |
| Cost | ~$2.30 (est.) |
| Pricing basis | baseline-2026-10-02/pricing |
| Tokens | 110 in / 22.3k out |
Per-workstream usage
orchestrator-selection— claude-opus-5-5- In: 6
- Out: 2.7k
- Cache read: 25.2k
- Cache create: 14.5k
- Cost: ~$0.18 (est.)
- Duration: 27s
go:implementation-tests— claude-opus-5-5- In: 12
- Out: 3.0k
- Cache read: 114.5k
- Cache create: 29.0k
- Cost: ~$0.31 (est.)
- Duration: 54s
harness-engineering:repo-health— claude-opus-5-5- In: 26
- Out: 4.6k
- Cache read: 371.2k
- Cache create: 41.3k
- Cost: ~$0.50 (est.)
- Duration: 1m 17s
policies:conventions— claude-opus-5-5- In: 16
- Out: 2.9k
- Cache read: 154.0k
- Cache create: 27.1k
- Cost: ~$0.31 (est.)
- Duration: 58s
documentation:docs— claude-opus-5-5- In: 18
- Out: 2.8k
- Cache read: 170.2k
- Cache create: 23.7k
- Cost: ~$0.28 (est.)
- Duration: 58s
architecture:solid-reviewer-agnostic— claude-opus-5-5- In: 16
- Out: 3.8k
- Cache read: 206.6k
- Cache create: 36.7k
- Cost: ~$0.41 (est.)
- Duration: 1m 03s
security:code-auditor— claude-opus-5-5- In: 10
- Out: 1.6k
- Cache read: 81.9k
- Cache create: 25.1k
- Cost: ~$0.25 (est.)
- Duration: 38s
orchestrator-rollup— claude-opus-5-5- In: 6
- Out: 944
- Cache read: 71.8k
- Cache create: 4.7k
- Cost: ~$0.07 (est.)
- Duration: 17s
apps and pins each had their own copy of the helper that stops cobra printing a runtime error twice with the usage text. It now lives in internal/cmd/cmdutil, and docs/development.md tells new commands to use it.
monit-reviewer
left a comment
There was a problem hiding this comment.
Automated PR Review
Reviewed commit: 114e9ead1543
Profile: claude-monit-reviewer - Posting as: monit-reviewer
Summary
| Reviewer | Findings |
|---|---|
| go:implementation-tests | 0 |
| harness-engineering:repo-health | 0 |
| policies:conventions | 0 |
| documentation:docs | 0 |
| architecture:solid-reviewer-agnostic | 0 |
| security:code-auditor | 0 |
Reviewer Coverage
Complete coverage details are retained in the local coverage.json artifact.
go:implementation-tests— complete (constrained); scope: 21 files; examples:internal/client/errors.go,internal/client/errors_test.go,internal/client/pins.go,internal/cmd/apps/apps.go,internal/cmd/cmdutil/cmdutil.go; 16 omitted; body-inspected: 21 files; examples:internal/client/errors.go,internal/client/errors_test.go,internal/client/pins.go,internal/cmd/apps/apps.go,internal/cmd/cmdutil/cmdutil.go; 16 omitted; skipped: none; context (not coverage): 5 files; examples:docs/development.md,internal/client/channel_resolver.go,internal/client/client.go,internal/cmd/messages/react.go,internal/output/output.go; constraints: Static review only; tests were not executed. Substring IsSlackError matching and missing context.Context are pre-existing client patterns; the typed-error migration was settled in discussion as a follow-up. The 114e9ea delta is a mechanical move of quiet() into cmdutil.Quiet; pins behavior is still covered by TestRuntimeErrorsPrintNoUsage.harness-engineering:repo-health— complete (constrained); scope: 19 files; examples:internal/client/errors.go,internal/client/errors_test.go,internal/client/pins.go,internal/cmd/pins/add.go,internal/cmd/pins/list.go; 14 omitted; body-inspected: 19 files; examples:internal/client/errors.go,internal/client/errors_test.go,internal/client/pins.go,internal/cmd/pins/add.go,internal/cmd/pins/list.go; 14 omitted; skipped: none; context (not coverage): 5 files; examples:ARCHITECTURE.md,CLAUDE.md,docs/development.md,internal/cmd/apps/apps.go,internal/cmd/cmdutil/cmdutil.go; constraints: Tests and lint were not run. The review was a static read only. The substring-matching IsSlackError thread is settled and deferred to a follow-up, so it is not raised again. This pass also checked commit 114e9ea against the earlier finding about the duplicated quiet() helper. The helper is now consolidated in internal/cmd/cmdutil and documented in docs/development.md, so the finding is not raised again.policies:conventions— complete (constrained); scope: 8 files; examples:README.md,docs/development.md,internal/client/errors.go,internal/cmd/pins/add.go,internal/cmd/pins/list.go; 3 omitted; body-inspected: 8 files; examples:README.md,docs/development.md,internal/client/errors.go,internal/cmd/pins/add.go,internal/cmd/pins/list.go; 3 omitted; skipped: none; context (not coverage): 4 files; examples:AGENTS.md,CLAUDE.md,internal/cmd/apps/apps.go,internal/cmd/cmdutil/cmdutil.go; constraints: Incremental commit 114e9ea (shared cmdutil.Quiet, documented in Command Patterns) checked; root exitCode prints silenced errors once, consistent with the new doc rule. Shared cli-common docs (../cli-common/docs) were not available locally; review relied on repo-local docs/development.md, README conventions, and existing command patterns.documentation:docs— complete (constrained); scope: 3 files; examples:README.md,docs/development.md,integration-tests.md; body-inspected: 3 files; examples:README.md,docs/development.md,integration-tests.md; skipped: none; context (not coverage): 7 files; examples:internal/cmd/cmdutil/cmdutil.go,internal/cmd/pins/add.go,internal/cmd/pins/list.go,internal/cmd/pins/pins.go,internal/cmd/pins/remove.go; 2 omitted; constraints: Docs checked against the pins command source, cmdutil.Quiet and root error printing; the commands were not run against a live workspace.architecture:solid-reviewer-agnostic— complete (constrained); scope: 7 files; examples:internal/client/errors.go,internal/client/pins.go,internal/cmd/pins/add.go,internal/cmd/pins/list.go,internal/cmd/pins/pins.go; 2 omitted; body-inspected: 7 files; examples:internal/client/errors.go,internal/client/pins.go,internal/cmd/pins/add.go,internal/cmd/pins/list.go,internal/cmd/pins/pins.go; 2 omitted; skipped: none; context (not coverage): 5 files; examples:docs/development.md,internal/client/client.go,internal/cmd/apps/apps.go,internal/cmd/cmdutil/cmdutil.go,internal/cmd/messages/react.go; constraints: Prior threads settled: substring-based IsSlackError matching is deferred to a typed-error follow-up; duplicated quiet() was consolidated into internal/cmd/cmdutil.Quiet (outside assigned scope). Neither is re-raised. Rango test ./internal/cmd/... ./internal/client/at 114e9ea (all pass); did not runmake lint.security:code-auditor— complete (constrained); scope: 6 files; examples:internal/client/errors.go,internal/client/pins.go,internal/cmd/pins/add.go,internal/cmd/pins/list.go,internal/cmd/pins/pins.go; 1 omitted; body-inspected: 6 files; examples:internal/client/errors.go,internal/client/pins.go,internal/cmd/pins/add.go,internal/cmd/pins/list.go,internal/cmd/pins/pins.go; 1 omitted; skipped: none; context (not coverage): 4 files; examples:internal/cmd/cmdutil/cmdutil.go,internal/cmd/root/root.go,internal/output/output.go,internal/validate/validate.go; constraints: Static review only; tests not executed. Delta since fa40e62 is the quiet()->cmdutil.Quiet refactor with no security impact. Token selection uses the shared root --as-user/--as-bot handling, timestamps are regex-validated before API calls, and pins list cells pass through sanitizeSearchCell.
0 PR discussion threads considered. 0 summarized; 0 resolved.
Completed in 2m 17s | ~$0.99 (est.) | claude-opus-5-5 | cr 0.10.354
| Field | Value |
|---|---|
| Model | claude-opus-5-5 |
| Reviewers | go:implementation-tests, harness-engineering:repo-health, policies:conventions, documentation:docs, architecture:solid-reviewer-agnostic, security:code-auditor |
| Engine | claude_cli · claude-opus-5-5 |
| Reviewed by | cr · monit-reviewer |
| Duration | 2m 17s wall · 5m 55s compute |
| Cost | ~$0.99 (est.) |
| Pricing basis | baseline-2026-10-02/pricing |
| Tokens | 58 in / 10.8k out |
Per-workstream usage
go:implementation-tests— claude-opus-5-5- In: 6
- Out: 1.4k
- Cache read: 107.0k
- Cache create: 8.6k
- Cost: ~$0.12 (est.)
- Duration: 48s
harness-engineering:repo-health— claude-opus-5-5- In: 6
- Out: 1.7k
- Cache read: 143.1k
- Cache create: 9.0k
- Cost: ~$0.13 (est.)
- Duration: 45s
policies:conventions— claude-opus-5-5- In: 10
- Out: 2.0k
- Cache read: 182.8k
- Cache create: 10.5k
- Cost: ~$0.16 (est.)
- Duration: 52s
documentation:docs— claude-opus-5-5- In: 12
- Out: 1.8k
- Cache read: 201.2k
- Cache create: 11.6k
- Cost: ~$0.17 (est.)
- Duration: 1m 11s
architecture:solid-reviewer-agnostic— claude-opus-5-5- In: 10
- Out: 2.1k
- Cache read: 230.9k
- Cache create: 11.1k
- Cost: ~$0.18 (est.)
- Duration: 1m 13s
security:code-auditor— claude-opus-5-5- In: 10
- Out: 1.5k
- Cache read: 183.8k
- Cache create: 15.3k
- Cost: ~$0.19 (est.)
- Duration: 51s
orchestrator-rollup— claude-opus-5-5- In: 4
- Out: 422
- Cache read: 54.7k
- Cache create: 2.8k
- Cost: ~$0.04 (est.)
- Duration: 12s
Summary
Adds
slck pins add,slck pins removeandslck pins list, over Slack'spins.add,pins.removeandpins.list. slck had no way to pin a message.@handlefor a DM). The timestamp takes the API form, the p-prefixed form or the message's Slack URL.addon a pinned message (already_pinned) andremoveon an unpinned one (no_pinornot_pinned) exit 0 and say so, asmessages reactandunreactdo.missing_scopeerror names the scope the command needs,pins:writefor add and remove andpins:readfor list, and where to add it.listprintsTS | PINNED | PINNED_BY | AUTHOR | TEXT, most recently pinned first;TSis whatremovetakes.slck appscommands do. The helper that does this moves fromappstointernal/cmd/cmdutil, so both packages share it, anddocs/development.mdtells new commands to use it.--as-userand--as-botpick the token as on every other command.integration-tests.mdand the extended manifest (which gainspins:write) are updated.Verification
make testandmake lintpass. The tests cover each command's request, the already-pinned and not-pinned outcomes, the missing-scope hints, and the list rendering, against fixture payloads.pins list --as-userwith a user token that lackspins:readprinted the missing-scope hint and exited 1;pins list --as-botwith a bot token that has it printedNo pins in <channel>.Model: Claude Opus 5.5 (1M context, effort unknown) in Claude Code.