Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .claude/commands/commit.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ Co-Authored-By: Claude <noreply@anthropic.com>

## Steps

1. `git status` + `git diff` (and `git diff --staged` if anything's staged). **Abort if on `master`** — commit on a feature branch instead.
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**:
- **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.
- 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.
2. Stage the files that belong in this commit — be specific, don't `git add -A`.
3. Commit with `HUSKY=0` to skip the interactive husky prompt:

Expand Down
57 changes: 57 additions & 0 deletions .claude/commands/implement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
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
---

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.

Usage: `/implement [INSTUI-1234]` — pass a ticket key if you have one; otherwise use this session's ticket and investigation.

**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.

## Step 1 — Context: WHAT is happening (prefer this session over the ticket)

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:

1. **This session's triage** (primary) — carry forward the confirmed repro and agreed expected behavior as the working spec.
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.
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.

State which sources you're using and confirm the WHAT before moving on.

## Step 2 — Diagnose the cause (the WHY) and confirm the approach

The investigation triage skipped — where most wrong turns happen, so **confirm before you code**.

- **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.
- **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).
- **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.

## Step 3 — Branch (confirm first)

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`:

- Show the current branch and the proposed name (descriptive; include the ticket key if any) and wait for confirmation.
- 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.

## Step 4 — Implement, honoring InstUI conventions

Make the confirmed change. Enforce the rules `/commit` and `/pr` don't:

- **No hardcoded user-facing strings** — all UI text from props for i18n (the most common review comment).
- **New components: functional + hooks only**; styling via co-located Emotion `theme.ts`.
- **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.
- **v1/v2:** change the right version (default v2; confirm before touching deprecated v1).
- **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.

## Step 5 — Verify against the original repro

Not done until it's shown to work:

- Run the package's unit tests: `pnpm run test:vitest <pkg>`.
- 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.

## Step 6 — Hand off (stop before commit)

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.

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.
42 changes: 39 additions & 3 deletions .claude/commands/pr.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,58 @@ Open a PR for the current branch.
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.
3. If not pushed: `git push -u origin <branch>`.
4. Create the PR (see invocation below).
5. Return the PR URL.
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.
6. Return the PR URL and the assigned reviewer (if any).

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.

## gh invocation

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`.
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`.

```bash
gh pr create --title "<title>" --body-file - <<'EOF'
gh pr create --title "<title>" --assignee @me --body-file - <<'EOF'
<body>
EOF
```

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

## Reviewer assignment

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:

```bash
set -euo pipefail
[ -f .github/CODEOWNERS ] || { echo "no CODEOWNERS — skip assignment"; exit 0; }
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
SINCE=$(date -v-60d +%F 2>/dev/null || date -d '60 days ago' +%F) # macOS || GNU
AUTHOR=$(gh api user --jq .login)

# Roster: individual @handles from CODEOWNERS — drop comments, globs, and @org/team handles.
# Capture the FULL handle (incl. any '/') so 'grep -v /' can actually drop teams; '|| true' so an
# empty match (comments-only / owner-less CODEOWNERS) skips gracefully instead of tripping set -e.
{ grep -v '^[[:space:]]*#' .github/CODEOWNERS \
| grep -oE '@[A-Za-z0-9_/-]+' | sed 's/@//' | grep -v '/' | sort -u > /tmp/pr_roster.txt; } || true

: > /tmp/pr_counts.txt
while IFS= read -r U; do # real loop — this shell won't word-split $var
[ -z "$U" ] && continue
[ "$U" = "$AUTHOR" ] && continue # can't review own PR
N=$(gh search prs --repo "$REPO" --reviewed-by "$U" --created ">=$SINCE" \
--limit 1000 --json number --jq 'length') || { echo "WARN: count failed for $U" >&2; continue; }
printf '%s %s\n' "$N" "$U" >> /tmp/pr_counts.txt
done < /tmp/pr_roster.txt

[ -s /tmp/pr_counts.txt ] || { echo "no eligible reviewers — skip assignment"; exit 0; }
echo "reviewed PRs opened in last 60d, per candidate (approx. load):"; sort -n /tmp/pr_counts.txt
MIN=$(sort -n /tmp/pr_counts.txt | head -1 | awk '{print $1}')
WINNER=$(awk -v m="$MIN" '$1==m{print $2}' /tmp/pr_counts.txt | sort -R | head -1) # random tie-break
echo "==> suggested (lightest load): $WINNER (count=$MIN)"
```

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.

## Body format

Keep it **short**. No preamble, no restating the title, no "this PR does X" filler.
Expand Down
93 changes: 93 additions & 0 deletions .claude/commands/slack-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
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
---

Set up (or repair) the Slack credentials that `/slack-triage` needs to read threads. This is a
one-time concern; once it's working, run `/slack-triage` directly.

The `slack` server reads `SLACK_BOT_TOKEN` and `SLACK_TEAM_ID` from the root `.env`, which
`.mcp.json` sources when it launches the server. **That file is the single source of truth** for
these secrets (gitignored via `.env`) and the same file the rest of the repo's tooling uses, so
there's no second env file to keep in sync. See `.env.example` for the documented keys. The bot is
**read-only** — it does not post — so it never needs `chat:write`.

## Step 1 — Check what's already there

Verify whether credentials are present **without printing their values** (source the env file first,
since that's what the server uses — they won't be in the plain shell otherwise):

```sh
[ -f ./.env ] && . ./.env
( [ -n "${SLACK_BOT_TOKEN:-}" ] && [ -n "${SLACK_TEAM_ID:-}" ] \
&& ! printf '%s' "${SLACK_BOT_TOKEN:-}" | grep -q REPLACE ) && echo creds-present || echo creds-missing
```

- If `creds-missing` → go to Step 2 (create/configure the app and store the token).
- If `creds-present` → skip to Step 5 to verify scopes are actually sufficient. (A token can be set
but lack scopes — e.g. name resolution fails — so verifying is worthwhile even when present.)

## Step 2 — Create or open the Slack app and set scopes

If the user doesn't have a bot token yet, walk them through it:

- Go to https://api.slack.com/apps → *Create New App* → *From scratch*, pick the workspace (or open
the existing app).
- *OAuth & Permissions* → *Bot Token Scopes*. Add these **read-only** scopes:
- `channels:history`, `channels:read` — read public channels
- `groups:history`, `groups:read` — read private channels
- `users:read`, `users.profile:read` — resolve reporter display names
- Do **not** add `chat:write` — the user posts the reply themselves; the bot only reads.
- `SLACK_TEAM_ID` is the workspace id (`T…`), e.g. from the URL `app.slack.com/client/T0XXXXXX/…`.

## Step 3 — Install (or reinstall) to the workspace

- *OAuth & Permissions* → *Install to Workspace* → *Allow*. Copy the **Bot User OAuth Token**
(`xoxb-…`).
- **Adding scopes later requires a *Reinstall to Workspace*** — Slack only grants newly-added scopes
on (re)install, which mints a **new** token. Changing the scope list in the config alone does
nothing until you reinstall. After reinstalling, copy the new `xoxb-…` token.

## Step 4 — Invite the bot to the channel

In the target Slack channel, run `/invite @<app-name>`. This is required to read a **private**
channel even with the scopes above (and harmless for public channels).

## Step 5 — Store the credentials

Ask the user to paste their `SLACK_BOT_TOKEN` (`xoxb-…`) and `SLACK_TEAM_ID` (`T…`). **Never echo the
token back** in your replies. Then write them into the root `.env` as `KEY=value` lines — this is the
file `.mcp.json` sources for the slack server:

```
SLACK_BOT_TOKEN=xoxb-…
SLACK_TEAM_ID=T…
```

- The `.env` almost always already exists with other secrets — **preserve every other line**, only
set/replace the `SLACK_BOT_TOKEN` and `SLACK_TEAM_ID` lines (append them if absent). Edit it without
printing the existing contents.
- It's gitignored via `.env` — never commit it or write the token anywhere else. Keep `.env.example`
in sync if you introduce a new key.

Tell the user to **restart Claude Code** afterward — `.mcp.json` sources this file only when it
launches the server at startup, so a new/changed token is picked up only after a restart.

> Note: if the token string is unchanged and you only *reinstalled* to grant new scopes, Slack grants
> the new scopes to the existing token server-side, so a restart isn't strictly required for scope
> changes. A restart is required whenever the **token value** changes.

## Step 6 — Verify

After the restart, confirm the credentials actually work and carry the right scopes:

- Re-run the Step 1 check (expect `creds-present`).
- Discover the read tools with `ToolSearch` `slack thread replies conversation history permalink`,
then make one real read call — e.g. fetch a user profile or the users list. A successful response
means scopes are sufficient.
- If a call fails with `missing_scope`, the error names the scope it `needed` and lists what the token
currently `provided`. Add the missing scope in Step 2, **reinstall** (Step 3), and re-verify. Common
cases: `users.profile:read` (resolve a single user's name) and `users:read` (list users).
- The `atlassian` server (used by `/slack-triage` for Jira) authenticates via OAuth, not a token — if
Jira calls error with auth, run `/mcp` and finish the Atlassian login. No token to store here.

When the read call succeeds, setup is done — run `/slack-triage <thread-link>`.
Loading
Loading