Skip to content

Commit 8b16acd

Browse files
balzssclaude
andcommitted
chore: add Slack triage tooling, PR reviewer assignment, and MCP config
Adds Claude Code slash commands and supporting config: - /slack-triage: triage a thread from the internal Slack channel — classify, investigate the code (incl. tracing the commit/PR/author behind a regression), reproduce and confirm with the user, draft a Jira ticket, and draft a reply for the user to post (the skill never posts to Slack itself). - /slack-setup: one-time bootstrap for the read-only Slack bot token and scopes, stored in .claude/mcp.local.env (the single source .mcp.json sources). - /implement: bridge a triaged ticket to a verified change on a branch, reusing the in-session investigation rather than just the ticket summary. - /pr: suggest a reviewer from .github/CODEOWNERS ranked by fewest reviews in the last 60 days (user picks; not auto-assigned), and self-assign the PR author. - /commit: refuse to commit on master/main and offer to create a feature branch (also flags committing onto an unrelated branch). - .github/CODEOWNERS: reviewer roster the /pr skill reads. - .mcp.json: wire up the atlassian and slack MCP servers. - .gitignore: track the new shared command files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 8379832 commit 8b16acd

8 files changed

Lines changed: 376 additions & 4 deletions

File tree

.claude/commands/commit.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ Co-Authored-By: Claude <noreply@anthropic.com>
2626

2727
## Steps
2828

29-
1. `git status` + `git diff` (and `git diff --staged` if anything's staged). **Abort if on `master`** — commit on a feature branch instead.
29+
1. `git status` + `git diff` (and `git diff --staged` if anything's staged), and **check the current branch is the right place for this commit**:
30+
- **Never commit on `master`/`main`.** If you're on it, stop and **offer to create a feature branch** from the current HEAD — `git switch -c <type>/<short-desc>` carries the uncommitted changes onto the new branch — then commit there. Don't proceed on `master` even if the user didn't mention branching; confirm first.
31+
- If you're on a feature branch, glance at its name. If it looks **unrelated** to the change you're about to commit, flag it and offer to branch off (so you don't pile an unrelated commit onto someone else's WIP); otherwise proceed.
3032
2. Stage the files that belong in this commit — be specific, don't `git add -A`.
3133
3. Commit with `HUSKY=0` to skip the interactive husky prompt:
3234

.claude/commands/implement.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
---
2+
description: Take a triaged bug or feature request through to a verified change on a branch, ready for /commit and /pr — using this session's investigation plus the Jira ticket
3+
---
4+
5+
Implement a fix or feature that's already been triaged (typically by `/slack-triage` earlier in this
6+
same session) and land it as a **verified change on a branch**, stopping before commit. This is the
7+
bridge between a Jira ticket and `/commit``/pr`. Works for both bugs and feature requests — it's
8+
not always a "fix".
9+
10+
Usage: `/implement [INSTUI-1234]` — pass a ticket key if you have one. If `$ARGUMENTS` is empty, use
11+
the ticket and investigation from this session.
12+
13+
## Step 1 — Establish context (prefer this session over the ticket)
14+
15+
A Jira ticket is a **short summary** of a much richer investigation. When `/slack-triage` ran earlier
16+
in this conversation, the full picture is already in context — root cause, exact `file:line`, the
17+
introducing commit/PR, the confirmed reproduction recipe, version/v1-v2 notes. **Use that. Do not
18+
downgrade to the ticket's summary when the fuller investigation is in the session.**
19+
20+
Source context in this priority order:
21+
22+
1. **This session's investigation** (primary). If triage already happened here, carry forward its root
23+
cause, the precise code location, and the confirmed repro. Treat these as the working spec.
24+
2. **The Jira ticket** (durable anchor). Read it (Jira MCP / `$ARGUMENTS` key) to pin the scope,
25+
acceptance criteria, and the Slack-thread link — but as a *reference point*, not a replacement for
26+
the session's detail. If the ticket and your session findings conflict, prefer the verified session
27+
findings and note the discrepancy to the user.
28+
3. **Cold start fallback.** If there's *no* prior investigation in this session (fresh conversation, or
29+
you only have a ticket key), reconstruct it before coding: read the ticket, then re-run the
30+
investigation — mirror `/slack-triage` Step 3 (read the README/`props.ts`/`theme.ts`, trace the
31+
introducing commit/PR for a regression, cross-check the published docs) and rebuild the repro. Don't
32+
start editing off a one-paragraph ticket.
33+
34+
State briefly which sources you're working from before you start changing code.
35+
36+
## Step 2 — Branch (confirm first)
37+
38+
The change goes on a **new branch off `master`** — not the current branch (CLAUDE.md: branch from
39+
master, integrate by **rebasing**, never merge). Before creating it, **sanity-check with the user**:
40+
41+
- Show the current branch and the branch you propose to create (a descriptive name; include the
42+
ticket key if there is one), and wait for confirmation before running `git switch -c`.
43+
- Call out anything off: if you're already on a feature branch (especially one with unrelated WIP, or
44+
the very session that ran `/slack-triage`), say so — the user may want to branch off `master`
45+
rather than stack on it, or may intend to keep working on the current branch.
46+
47+
Only branch once the user confirms the name and starting point.
48+
49+
## Step 3 — Implement, honoring InstUI conventions
50+
51+
Make the change at the location identified in Step 1. Enforce the rules the commit/PR skills don't:
52+
53+
- **No hardcoded user-facing strings** — all UI text comes from props for i18n (the most common review
54+
comment).
55+
- **New components: functional + hooks only.** Styling via the co-located Emotion `theme.ts`.
56+
- **No breaking changes unless the user explicitly asked** — removing/renaming a prop, component, theme
57+
variable, or exported util; changing a prop type or a behavior-altering default. Adding optional
58+
props / new components / new theme variables is fine. If a break is genuinely required, flag it and
59+
get explicit sign-off; it must carry `BREAKING CHANGE:` in the commit.
60+
- **v1/v2:** if the component has both, change the right version (default to v2 for new work; confirm
61+
before touching deprecated v1).
62+
- **Docs & tests in the same change:** if you add/change a prop, update the component **README**. Add or
63+
extend co-located **unit tests** (`*.test.tsx`), a **regression-test page** under
64+
`/regression-test/src/app/<name>/page.tsx`, and a **Cypress entry** in
65+
`/regression-test/cypress/e2e/spec.cy.ts`. Maintain WCAG 2.1 AA and RTL support.
66+
67+
## Step 4 — Verify against the original repro
68+
69+
A change isn't done until it's shown to work:
70+
71+
- Run the package's unit tests: `pnpm run test:vitest <pkg>`.
72+
- Re-run the **confirmed reproduction from triage** and show it now behaves as expected (use the
73+
`verify` skill / `pnpm run dev`). For a bug, the exact repro that demonstrated the failure should now
74+
pass; for a feature, the use case from the ticket should work. Report what you observed.
75+
76+
## Step 5 — Hand off (stop before commit)
77+
78+
Stop at a **verified diff on the branch** and summarize: what changed and why, files touched, test/repro
79+
results, and any follow-ups. **Do not commit or open a PR automatically** — the diff needs human review.
80+
Tell the user to run `/commit` then `/pr` (the PR body should reference the `INSTUI-` ticket) once they're
81+
happy.
82+
83+
If the change is non-trivial and you only got partway, **say so plainly** — leave a documented WIP branch
84+
and list what's left, rather than forcing a wrong or incomplete change to look finished.

.claude/commands/pr.md

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,56 @@ Open a PR for the current branch.
1010
2. `git log master..HEAD` and `git diff master...HEAD` — read **all** commits in the branch (not just the latest) so the summary covers everything that's changed.
1111
3. If not pushed: `git push -u origin <branch>`.
1212
4. Create the PR (see invocation below).
13-
5. Return the PR URL.
13+
5. Suggest reviewers and assign the user's pick (see **Reviewer assignment**).
14+
6. Return the PR URL and the assigned reviewer (if any).
1415

1516
If the branch name or any commit references a Jira ticket (e.g. `INSTUI-1234`), include it. If you can't find one, ask the user once before opening — don't invent one.
1617

1718
## gh invocation
1819

19-
Use `--body-file -` with a heredoc on stdin. This avoids shell-quoting issues and is the form supported by current `gh` versions. If unsure about flags, run `gh pr create --help` first — do **not** fall back to older forms like `gh pr create -t ... -b ...` with inline `-b`.
20+
Use `--body-file -` with a heredoc on stdin. This avoids shell-quoting issues and is the form supported by current `gh` versions. `--assignee @me` self-assigns the PR to its author (`gh` doesn't do this by default — it only records authorship). If unsure about flags, run `gh pr create --help` first — do **not** fall back to older forms like `gh pr create -t ... -b ...` with inline `-b`.
2021

2122
```bash
22-
gh pr create --title "<title>" --body-file - <<'EOF'
23+
gh pr create --title "<title>" --assignee @me --body-file - <<'EOF'
2324
<body>
2425
EOF
2526
```
2627

2728
Open as draft (`--draft`) if the work is in progress.
2829

30+
## Reviewer assignment
31+
32+
After the PR is created, help the user choose a reviewer from the CODEOWNERS roster, ranked by **fewest reviews in the last 60 days** (lightest load first). Don't auto-assign — the counts can't see who's on PTO or heads-down, so the human makes the call. **Run this verbatim** to gather the ranked list:
33+
34+
```bash
35+
set -euo pipefail
36+
[ -f .github/CODEOWNERS ] || { echo "no CODEOWNERS — skip assignment"; exit 0; }
37+
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
38+
SINCE=$(date -v-60d +%F 2>/dev/null || date -d '60 days ago' +%F) # macOS || GNU
39+
AUTHOR=$(gh api user --jq .login)
40+
41+
# Roster: individual @handles from CODEOWNERS — drop comments, globs, and @org/team handles.
42+
grep -v '^[[:space:]]*#' .github/CODEOWNERS \
43+
| grep -oE '@[A-Za-z0-9_-]+' | sed 's/@//' | grep -v '/' | sort -u > /tmp/pr_roster.txt
44+
45+
: > /tmp/pr_counts.txt
46+
while IFS= read -r U; do # real loop — this shell won't word-split $var
47+
[ -z "$U" ] && continue
48+
[ "$U" = "$AUTHOR" ] && continue # can't review own PR
49+
N=$(gh search prs --repo "$REPO" --reviewed-by "$U" --updated ">=$SINCE" \
50+
--limit 1000 --json number --jq 'length') || { echo "WARN: count failed for $U" >&2; continue; }
51+
printf '%s %s\n' "$N" "$U" >> /tmp/pr_counts.txt
52+
done < /tmp/pr_roster.txt
53+
54+
[ -s /tmp/pr_counts.txt ] || { echo "no eligible reviewers — skip assignment"; exit 0; }
55+
echo "reviews(60d) per candidate:"; sort -n /tmp/pr_counts.txt
56+
MIN=$(sort -n /tmp/pr_counts.txt | head -1 | awk '{print $1}')
57+
WINNER=$(awk -v m="$MIN" '$1==m{print $2}' /tmp/pr_counts.txt | sort -R | head -1) # random tie-break
58+
echo "==> suggested (lightest load): $WINNER (count=$MIN)"
59+
```
60+
61+
Then **present the ranked list to the user** — each candidate with their 60-day review count, lightest first — and recommend the top one as the default. Ask them who to assign (they may pick a heavier-loaded person who's actually available, or skip entirely). Only after they choose, assign with `gh pr edit <pr> --add-reviewer <their-pick>` and confirm. If the script printed a skip message (no CODEOWNERS / no eligible reviewers), say so and leave the PR unassigned.
62+
2963
## Body format
3064

3165
Keep it **short**. No preamble, no restating the title, no "this PR does X" filler.

.claude/commands/slack-setup.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
---
2+
description: One-time setup for the Slack bot token used by /slack-triage — create/scope the Slack app, store the credentials, and verify they work
3+
---
4+
5+
Set up (or repair) the Slack credentials that `/slack-triage` needs to read threads. This is a
6+
one-time concern; once it's working, run `/slack-triage` directly.
7+
8+
The `slack` server reads `SLACK_BOT_TOKEN` and `SLACK_TEAM_ID` from `.claude/mcp.local.env`, which
9+
`.mcp.json` sources when it launches the server. **That file is the single source of truth** for
10+
these secrets (gitignored via `.claude/*.local.*`). The bot is **read-only** — it does not post — so
11+
it never needs `chat:write`.
12+
13+
## Step 1 — Check what's already there
14+
15+
Verify whether credentials are present **without printing their values** (source the env file first,
16+
since that's what the server uses — they won't be in the plain shell otherwise):
17+
18+
```sh
19+
[ -f ./.claude/mcp.local.env ] && . ./.claude/mcp.local.env
20+
( [ -n "${SLACK_BOT_TOKEN:-}" ] && [ -n "${SLACK_TEAM_ID:-}" ] \
21+
&& ! printf '%s' "${SLACK_BOT_TOKEN:-}" | grep -q REPLACE ) && echo creds-present || echo creds-missing
22+
```
23+
24+
- If `creds-missing` → go to Step 2 (create/configure the app and store the token).
25+
- If `creds-present` → skip to Step 5 to verify scopes are actually sufficient. (A token can be set
26+
but lack scopes — e.g. name resolution fails — so verifying is worthwhile even when present.)
27+
28+
## Step 2 — Create or open the Slack app and set scopes
29+
30+
If the user doesn't have a bot token yet, walk them through it:
31+
32+
- Go to https://api.slack.com/apps*Create New App**From scratch*, pick the workspace (or open
33+
the existing app).
34+
- *OAuth & Permissions**Bot Token Scopes*. Add these **read-only** scopes:
35+
- `channels:history`, `channels:read` — read public channels
36+
- `groups:history`, `groups:read` — read private channels
37+
- `users:read`, `users.profile:read` — resolve reporter display names
38+
- Do **not** add `chat:write` — the user posts the reply themselves; the bot only reads.
39+
- `SLACK_TEAM_ID` is the workspace id (`T…`), e.g. from the URL `app.slack.com/client/T0XXXXXX/…`.
40+
41+
## Step 3 — Install (or reinstall) to the workspace
42+
43+
- *OAuth & Permissions**Install to Workspace**Allow*. Copy the **Bot User OAuth Token**
44+
(`xoxb-…`).
45+
- **Adding scopes later requires a *Reinstall to Workspace*** — Slack only grants newly-added scopes
46+
on (re)install, which mints a **new** token. Changing the scope list in the config alone does
47+
nothing until you reinstall. After reinstalling, copy the new `xoxb-…` token.
48+
49+
## Step 4 — Invite the bot to the channel
50+
51+
In the target Slack channel, run `/invite @<app-name>`. This is required to read a **private**
52+
channel even with the scopes above (and harmless for public channels).
53+
54+
## Step 5 — Store the credentials
55+
56+
Ask the user to paste their `SLACK_BOT_TOKEN` (`xoxb-…`) and `SLACK_TEAM_ID` (`T…`). **Never echo the
57+
token back** in your replies. Then write them into `.claude/mcp.local.env` as `KEY=value` lines —
58+
this is the file `.mcp.json` sources for the slack server:
59+
60+
```
61+
SLACK_BOT_TOKEN=xoxb-…
62+
SLACK_TEAM_ID=T…
63+
```
64+
65+
- If the file already exists, **preserve its other lines** — only set/replace the `SLACK_BOT_TOKEN`
66+
and `SLACK_TEAM_ID` lines.
67+
- It's gitignored via `.claude/*.local.*` — never commit it or write the token anywhere else.
68+
69+
Tell the user to **restart Claude Code** afterward — `.mcp.json` sources this file only when it
70+
launches the server at startup, so a new/changed token is picked up only after a restart.
71+
72+
> Note: if the token string is unchanged and you only *reinstalled* to grant new scopes, Slack grants
73+
> the new scopes to the existing token server-side, so a restart isn't strictly required for scope
74+
> changes. A restart is required whenever the **token value** changes.
75+
76+
## Step 6 — Verify
77+
78+
After the restart, confirm the credentials actually work and carry the right scopes:
79+
80+
- Re-run the Step 1 check (expect `creds-present`).
81+
- Discover the read tools with `ToolSearch` `slack thread replies conversation history permalink`,
82+
then make one real read call — e.g. fetch a user profile or the users list. A successful response
83+
means scopes are sufficient.
84+
- If a call fails with `missing_scope`, the error names the scope it `needed` and lists what the token
85+
currently `provided`. Add the missing scope in Step 2, **reinstall** (Step 3), and re-verify. Common
86+
cases: `users.profile:read` (resolve a single user's name) and `users:read` (list users).
87+
- The `atlassian` server (used by `/slack-triage` for Jira) authenticates via OAuth, not a token — if
88+
Jira calls error with auth, run `/mcp` and finish the Atlassian login. No token to store here.
89+
90+
When the read call succeeds, setup is done — run `/slack-triage <thread-link>`.

0 commit comments

Comments
 (0)