Skip to content

Commit ed9532f

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 ed9532f

8 files changed

Lines changed: 411 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: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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+
**Confirm direction at every decision point.** Don't assume the diagnosis or the fix and charge
14+
ahead — a wrong assumption here means a wasted branch. At each step below, state your read and your
15+
proposed next move, then pause for the user to confirm or redirect before investing effort.
16+
17+
## Step 1 — Establish context: WHAT is happening (prefer this session over the ticket)
18+
19+
Triage (and the ticket) deliberately captured **WHAT** is happening and **WHAT SHOULD** happen — the
20+
confirmed repro, observed-vs-expected behavior, affected component, version/v1-v2 notes — and
21+
deliberately **did not** investigate the cause. So your starting spec is that confirmed behavior, and
22+
your first real job (Step 2) is the WHY.
23+
24+
Source the WHAT in this priority order:
25+
26+
1. **This session's triage** (primary). If `/slack-triage` ran earlier in this conversation, carry
27+
forward the confirmed reproduction and the agreed expected behavior. Treat these as the working spec.
28+
2. **The Jira ticket** (durable anchor). Read it (Jira MCP / `$ARGUMENTS` key) to pin the scope,
29+
acceptance criteria, and the Slack-thread link — but as a *reference point*, not a replacement for
30+
the session's detail. If the ticket and your session findings conflict, prefer the verified session
31+
findings and note the discrepancy to the user.
32+
3. **Cold start fallback.** If there's *no* prior triage in this session (fresh conversation, or you
33+
only have a ticket key), reconstruct the WHAT before coding: read the ticket, then re-establish the
34+
observed/expected behavior — read the README/`props.ts`/`theme.ts`, cross-check the published docs,
35+
and rebuild the repro. Don't start off a one-paragraph ticket.
36+
37+
State briefly which sources you're working from, and confirm the WHAT with the user, before moving on.
38+
39+
## Step 2 — Diagnose the cause (the WHY) and confirm the approach
40+
41+
This is the investigation triage skipped — and it's where most wrong turns happen, so **confirm before
42+
you code**.
43+
44+
- **Find the root cause.** Trace the confirmed repro to the responsible code path; cite the exact
45+
`file:line`. Use the Chrome DevTools MCP against the live app (`pnpm run dev`http://localhost:9090)
46+
to inspect state/DOM/console where that helps pin it down.
47+
- **For a regression, trace the introducing commit/PR.** Did this used to work and break? Use
48+
`git log -p`/`git blame`/`git log -S'<symbol>'` on the relevant path, or bisect against dated
49+
`CHANGELOG.md` entries / release tags. Capture the short SHA + subject, the author/date
50+
(`git show -s --format='%an <%ae>, %ad' <sha>`), and the merging PR
51+
(`gh pr list --search <sha> --state merged --json number,title,url`). **Verify the suspect code is
52+
actually on `master`/the released tag** — a commit on an unmerged branch is not the shipped
53+
regression. Skip the archaeology only when it's clearly an original defect (never worked); say so.
54+
- **Propose the fix approach and get sign-off.** State the root cause and the change you intend to
55+
make (which file(s), v1 vs v2, prop vs theme vs logic), and any alternatives, in a few lines. **Wait
56+
for the user to confirm the direction** before editing. If there are real options with trade-offs,
57+
lay them out rather than silently picking one.
58+
59+
## Step 3 — Branch (confirm first)
60+
61+
The change goes on a **new branch off `master`** — not the current branch (CLAUDE.md: branch from
62+
master, integrate by **rebasing**, never merge). Before creating it, **sanity-check with the user**:
63+
64+
- Show the current branch and the branch you propose to create (a descriptive name; include the
65+
ticket key if there is one), and wait for confirmation before running `git switch -c`.
66+
- Call out anything off: if you're already on a feature branch (especially one with unrelated WIP, or
67+
the very session that ran `/slack-triage`), say so — the user may want to branch off `master`
68+
rather than stack on it, or may intend to keep working on the current branch.
69+
70+
Only branch once the user confirms the name and starting point.
71+
72+
## Step 4 — Implement, honoring InstUI conventions
73+
74+
Make the change at the location and via the approach confirmed in Step 2. Enforce the rules the
75+
commit/PR skills don't:
76+
77+
- **No hardcoded user-facing strings** — all UI text comes from props for i18n (the most common review
78+
comment).
79+
- **New components: functional + hooks only.** Styling via the co-located Emotion `theme.ts`.
80+
- **No breaking changes unless the user explicitly asked** — removing/renaming a prop, component, theme
81+
variable, or exported util; changing a prop type or a behavior-altering default. Adding optional
82+
props / new components / new theme variables is fine. If a break is genuinely required, flag it and
83+
get explicit sign-off; it must carry `BREAKING CHANGE:` in the commit.
84+
- **v1/v2:** if the component has both, change the right version (default to v2 for new work; confirm
85+
before touching deprecated v1).
86+
- **Docs & tests in the same change:** if you add/change a prop, update the component **README**. Add or
87+
extend co-located **unit tests** (`*.test.tsx`), a **regression-test page** under
88+
`/regression-test/src/app/<name>/page.tsx`, and a **Cypress entry** in
89+
`/regression-test/cypress/e2e/spec.cy.ts`. Maintain WCAG 2.1 AA and RTL support.
90+
91+
## Step 5 — Verify against the original repro
92+
93+
A change isn't done until it's shown to work:
94+
95+
- Run the package's unit tests: `pnpm run test:vitest <pkg>`.
96+
- Re-run the **confirmed reproduction from triage** and show it now behaves as expected. Drive it live
97+
with the **Chrome DevTools MCP** against `pnpm run dev` (or the `verify` skill) — open the exact spot,
98+
exercise the repro, and capture the now-correct behavior (snapshot/console/screenshot). For a bug,
99+
the exact repro that demonstrated the failure should now pass; for a feature, the use case from the
100+
ticket should work. Report what you observed.
101+
102+
## Step 6 — Hand off (stop before commit)
103+
104+
Stop at a **verified diff on the branch** and summarize: what changed and why (now you *can* state the
105+
cause — it's confirmed), files touched, test/repro results, and any follow-ups. **Do not commit or open
106+
a PR automatically** — the diff needs human review. Tell the user to run `/commit` then `/pr` (the PR
107+
body should reference the `INSTUI-` ticket) once they're happy.
108+
109+
If the change is non-trivial and you only got partway, **say so plainly** — leave a documented WIP branch
110+
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)