Skip to content

Commit 4fb186e

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 3ac1e64 commit 4fb186e

10 files changed

Lines changed: 348 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: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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 triaged fix or feature (typically from `/slack-triage` earlier this session) and land it as a **verified change on a branch, stopping before commit**. The bridge between a Jira ticket and `/commit``/pr`. Works for bugs *and* feature requests.
6+
7+
Usage: `/implement [INSTUI-1234]` — pass a ticket key if you have one; otherwise use this session's ticket and investigation.
8+
9+
**Confirm direction at every decision point.** A wrong assumption here wastes a branch. At each step, state your read and proposed next move, then pause for the user before investing effort.
10+
11+
## Step 1 — Context: WHAT is happening (prefer this session over the ticket)
12+
13+
Triage captured **WHAT** happens and **WHAT SHOULD** — confirmed repro, observed-vs-expected, affected component, v1/v2 notes — and deliberately skipped the cause. That confirmed behavior is your spec; the WHY is Step 2. Source it in priority order:
14+
15+
1. **This session's triage** (primary) — carry forward the confirmed repro and agreed expected behavior as the working spec.
16+
2. **The Jira ticket** (durable anchor) — read it (Jira MCP / `$ARGUMENTS` key) for scope, acceptance criteria, and the Slack link. If it conflicts with verified session findings, prefer the session findings and note the discrepancy.
17+
3. **Cold start** (no prior triage) — reconstruct the WHAT before coding: read the ticket, then re-establish observed/expected from README/`props.ts`/`theme.ts`, cross-check the published docs, and rebuild the repro. Don't start from a one-paragraph ticket.
18+
19+
State which sources you're using and confirm the WHAT before moving on.
20+
21+
## Step 2 — Diagnose the cause (the WHY) and confirm the approach
22+
23+
The investigation triage skipped — where most wrong turns happen, so **confirm before you code**.
24+
25+
- **Find the root cause.** Trace the confirmed repro to the responsible code path; cite exact `file:line`. Use the Chrome DevTools MCP against `pnpm run dev` (http://localhost:9090) to inspect state/DOM/console where it helps.
26+
- **For a regression, trace the introducing commit/PR.** Use `git log -p` / `git blame` / `git log -S'<symbol>'`, or bisect against `CHANGELOG.md` / release tags. Capture short SHA + subject, author/date (`git show -s --format='%an <%ae>, %ad' <sha>`), and the merging PR (`gh pr list --search <sha> --state merged --json number,title,url`). **Verify the suspect code is actually on `master`/the released tag** — a commit on an unmerged branch isn't the shipped regression. Skip the archaeology only for a clear original defect (say so).
27+
- **Propose the fix and get sign-off.** State the root cause and intended change (which file(s), v1 vs v2, prop vs theme vs logic) plus any alternatives, in a few lines. **Wait for the user to confirm** before editing.
28+
29+
## Step 3 — Branch (confirm first)
30+
31+
The change goes on a **new branch off `master`** — not the current branch (CLAUDE.md: branch from master, integrate by rebasing). Before `git switch -c`:
32+
33+
- Show the current branch and the proposed name (descriptive; include the ticket key if any) and wait for confirmation.
34+
- Flag anything off — e.g. if you're on a feature branch with unrelated WIP (or the `/slack-triage` session itself), the user may want to branch off `master` instead of stacking.
35+
36+
## Step 4 — Implement, honoring InstUI conventions
37+
38+
Make the confirmed change. Enforce the rules `/commit` and `/pr` don't:
39+
40+
- **No hardcoded user-facing strings** — all UI text from props for i18n (the most common review comment).
41+
- **New components: functional + hooks only**; styling via co-located Emotion `theme.ts`.
42+
- **No breaking changes unless explicitly asked** — removing/renaming a prop, component, theme variable, or exported util; changing a prop type or behavior-altering default. Adding optional props / components / theme variables is fine. If a break is required, flag it, get sign-off, and carry `BREAKING CHANGE:` in the commit.
43+
- **v1/v2:** change the right version (default v2; confirm before touching deprecated v1).
44+
- **Docs & tests in the same change:** update the component **README** for any prop change; add/extend co-located **unit tests** (`*.test.tsx`), a **regression-test page** (`/regression-test/src/app/<name>/page.tsx`), and a **Cypress entry** (`/regression-test/cypress/e2e/spec.cy.ts`). Maintain WCAG 2.1 AA and RTL.
45+
46+
## Step 5 — Verify against the original repro
47+
48+
Not done until it's shown to work:
49+
50+
- Run the package's unit tests: `pnpm run test:vitest <pkg>`.
51+
- Re-run the **confirmed repro from triage** live via the Chrome DevTools MCP (or the `verify` skill) against `pnpm run dev` — open the exact spot, exercise the repro, capture the now-correct behavior (snapshot/console/screenshot). The exact failing repro should now pass (bug) / the ticket's use case should work (feature). Report what you observed.
52+
53+
## Step 6 — Hand off (stop before commit)
54+
55+
Stop at a **verified diff on the branch** and summarize: what changed and why (now you can state the confirmed cause), files touched, test/repro results, follow-ups. **Do not commit or open a PR** — tell the user to run `/commit` then `/pr` (PR body references the `INSTUI-` ticket) once they're happy.
56+
57+
If you only got partway, **say so plainly** — leave a documented WIP branch listing what's left, rather than forcing an incomplete change to look finished.

.claude/commands/pr.md

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,58 @@ 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 (required — do not skip).** `/pr` is **not complete** until you've run the **Reviewer assignment** flow and presented the ranked list to the user. Creating the PR is only half the job; do this every time, even on a long session.
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 recently-opened PRs reviewed (last 60 days)** — an approximate load proxy, lightest first. (GitHub search can't filter by review *date*, so this counts PRs *created* in the window that the person reviewed; it understates reviews on older, still-active PRs.) 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+
# Capture the FULL handle (incl. any '/') so 'grep -v /' can actually drop teams; '|| true' so an
43+
# empty match (comments-only / owner-less CODEOWNERS) skips gracefully instead of tripping set -e.
44+
{ grep -v '^[[:space:]]*#' .github/CODEOWNERS \
45+
| grep -oE '@[A-Za-z0-9_/-]+' | sed 's/@//' | grep -v '/' | sort -u > /tmp/pr_roster.txt; } || true
46+
47+
: > /tmp/pr_counts.txt
48+
while IFS= read -r U; do # real loop — this shell won't word-split $var
49+
[ -z "$U" ] && continue
50+
[ "$U" = "$AUTHOR" ] && continue # can't review own PR
51+
N=$(gh search prs --repo "$REPO" --reviewed-by "$U" --created ">=$SINCE" \
52+
--limit 1000 --json number --jq 'length') || { echo "WARN: count failed for $U" >&2; continue; }
53+
printf '%s %s\n' "$N" "$U" >> /tmp/pr_counts.txt
54+
done < /tmp/pr_roster.txt
55+
56+
[ -s /tmp/pr_counts.txt ] || { echo "no eligible reviewers — skip assignment"; exit 0; }
57+
echo "reviewed PRs opened in last 60d, per candidate (approx. load):"; sort -n /tmp/pr_counts.txt
58+
MIN=$(sort -n /tmp/pr_counts.txt | head -1 | awk '{print $1}')
59+
WINNER=$(awk -v m="$MIN" '$1==m{print $2}' /tmp/pr_counts.txt | sort -R | head -1) # random tie-break
60+
echo "==> suggested (lightest load): $WINNER (count=$MIN)"
61+
```
62+
63+
The loop's `gh search prs` / `gh repo view` calls may trigger a one-time permission prompt — that's expected; approve and let it finish. **Do not abandon the reviewer step because of a prompt.** 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.
64+
2965
## Body format
3066

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

.claude/commands/slack-setup.md

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

0 commit comments

Comments
 (0)