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
144 changes: 144 additions & 0 deletions .claude/commands/fix-issue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
---
description: Pull the next `ready-for-agent` issue, branch off main, implement it, and open a PR.
argument-hint: "[issue number | empty to auto-pick]"
---

You are picking up a labelled GitHub issue in this Next.js 16 (App Router) + React 19 + TypeScript (strict) + Tailwind CSS 4 frontend and taking it all the way to an open pull request.

Argument from the user: `$ARGUMENTS`

Repo: `ProjectTech4DevAI/kaapi-frontend`.

## Guardrails

- You push a **branch** and open a **PR**. Never push to `main`, never merge, never `--force`, never touch `.releaserc` or the workflows in `.github/`.
- `cd-dev.yml` / `deploy-staging.yml` deploy from this repo — a bad merge to `main` ships. Branch + PR only, always.
- One issue per run. Don't batch several issues into one branch.
- If the issue is ambiguous, under-specified, or would touch auth / middleware gating / the BFF contract, **stop and ask** rather than guessing. Say what's unclear. Non-interactive run (the system prompt says so, or `$CI` is set): post the question with `gh issue comment <n>` and stop.

## 1. Select the issue

If `$ARGUMENTS` is an issue number, use it directly:

```bash
gh issue view <n> --comments # comments may hold answers to an earlier run's question
```

Otherwise list the queue:

```bash
gh issue list --label ready-for-agent --state open --json number,title,labels,body,comments
```

No open issues carry the label → say so plainly and stop. Don't invent work.

Several are queued → pick one by this priority order:

1. **Critical bugfixes** — broken behaviour users hit today
2. **Development infrastructure** — types, lint, CI, test scaffolding, dev scripts (these unblock everything after them)
3. **Tracer bullets for new features** — the thinnest end-to-end slice through every layer (route → BFF handler → client fetch → component), so the shape is validated before the feature is built out
4. **Polish and quick wins**
5. **Refactors**

Say which issue you picked and why it outranked the others.

## 2. Branch

```bash
git status # never clobber uncommitted work — stash -u or stop
git checkout main && git pull
git ls-remote --heads origin <type>/<kebab-slug> # left over from an earlier attempt?
git checkout -b <type>/<kebab-slug> # …or, if it exists: git fetch origin <branch> && git checkout <branch>
```

Never force-push. Branch naming follows this repo's convention: `<type>/<kebab-slug>`, where type maps from the issue's label:

| Issue label | Branch prefix | Commit type |
| --------------- | -------------- | ----------- |
| `bug` | `fix/` | `fix:` |
| `enhancement` | `enhancement/` | `feat:` |
| `documentation` | `docs/` | `docs:` |
| _none / other_ | `feat/` | `feat:` |

Slug from the issue title, not the issue number: `fix/eval-card-mobile-overflow`, not `fix/issue-42`.

## 3. Understand before you edit

- Read `CLAUDE.md` for architecture, then `Grep`/`Glob` for the actual code paths involved. Read whole files at change sites, not just the lines you think you need.
- **Grep for existing solutions first.** `app/components/`, `app/hooks/`, `app/lib/utils/`, `app/lib/constants.ts`, `app/components/icons/`. Reusing `Button`/`Modal`/`useToast`/`clientFetch` beats authoring a new one — re-implementing what already lives two files over is the most common failure here.
- Trace the full flow before choosing a fix. A bug report names a symptom; fix the root cause where every caller routes through, not just the path the issue mentions.
- Smallest diff that actually solves it. No speculative abstractions, no scaffolding "for later", no new dependency for what a few lines cover.

Conventions that apply to whatever you write (`/pr-review` has the full list):

- Import alias `@/...`, never relative `../../` chains
- `"use client"` only where state / effects / handlers / browser APIs demand it
- Design-system colour tokens (`text-text-primary`, `bg-accent-primary`), not raw `text-gray-500` or hex
- No `any`; no file over 500 LOC
- BFF handlers use `apiClient(...)`; browser calls use `clientFetch(...)`
- Loading, error, and empty states all handled

## 4. Verify

```bash
npm run lint
npm run build
```

Both must pass before you commit. If the change is user-visible, run `npm run dev` and exercise the golden path **and** a failure path — report what you actually saw. Never tick a checklist box for a step you didn't run.

## 5. Commit

This repo squash-merges with the PR title as the commit subject, and `semantic-release` reads that (`.releaserc`: `feat` → minor, `fix`/`chore`/`docs`/`refactor` → patch), so the **PR title must be a conventional commit** — a non-conforming one silently breaks versioning. `pr-formatter.yml` regenerates the title from the PR body on open, so the body must make the change type unmistakable.

```bash
git diff # review before staging
git add <specific files> # never `git add .`
git commit -m "fix: correct eval card overflow on mobile"
```

`.husky/pre-commit` runs `lint-staged`, which reformats staged files — if it rewrites anything, re-check `git diff HEAD` before pushing.

Scan the diff for anything that shouldn't ship: `.env*`, tokens, keys, `console.log`, commented-out code, stray lockfiles (`package-lock.json` is the only valid one here).

## 6. Push and open the PR

```bash
git push -u origin <branch>
```

Fill `.github/PULL_REQUEST_TEMPLATE.md` — use its headings verbatim, don't invent your own:

```bash
gh pr create --title "<conventional-commit-style summary of the change>" --body "$(cat <<'EOF'
## Issue

Closes #<number>

## Summary

<What problem this solves and why it matters — the motivation, not a restatement of the diff. 2-4 sentences.>

<Then 1-3 bullets: what changed and where, as `path/to/file.tsx` references.>

## Checklist

Before submitting a pull request, please ensure that you mark these task.

- [ ] Ran `npm run dev` and `npm run build` in the repository root and test.
- [ ] If you've fixed a bug or added code that is tested

## Notes

<Anything the reviewer needs: trade-offs taken, follow-ups deliberately deferred, areas worth a closer look. "None." if genuinely nothing.>
EOF
)"
```

`Closes #<number>` is what makes GitHub auto-close the issue on merge — don't drop it or reword it.

Tick a checklist box only if you ran that command and it passed. If you couldn't run `npm run dev` (no browser, headless), leave it unchecked and say so in `## Notes`.

## 7. Report back

State: the issue picked, the branch, files touched, `lint`/`build` results, the PR URL, and anything you deliberately left out of scope.
114 changes: 114 additions & 0 deletions .github/workflows/fix-issues.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: Fix ready-for-agent issues

# Daily: pick up open issues labelled `ready-for-agent` that have no linked PR
# yet (max 5 per run) and run `/fix-issue <n>` (.claude/commands/fix-issue.md)
# on each in its own job. Claude branches off main and opens a PR; it never
# merges. Every attempt drops the label, so an issue gets one try per labelling:
# re-add `ready-for-agent` to retry.
#
# Setup (repo admin, one time): the Claude GitHub App must be installed on the
# repo and an ANTHROPIC_API_KEY Actions secret must exist. `/install-github-app`
# inside `claude` does both. Note: GitHub disables cron on public repos after 60
# days without activity, and the App token exchange only works once this file is
# on main, so test with workflow_dispatch after merging, not from a branch.

on:
schedule:
- cron: "30 2 * * *" # 08:00 IST (GitHub cron is UTC)
workflow_dispatch:
inputs:
issue:
description: "Issue number (blank = whole ready-for-agent queue)"
required: false
type: string

# Whole runs serialise so an overlapping dispatch re-reads the queue after the
# previous run has dropped its labels, instead of redoing the same issues.
concurrency:
group: fix-issues
cancel-in-progress: false

jobs:
queue:
runs-on: ubuntu-latest
permissions:
issues: read
outputs:
issues: ${{ steps.list.outputs.issues }}
steps:
- id: list
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
ISSUE: ${{ inputs.issue }}
run: |
if [ -n "$ISSUE" ]; then
[[ "$ISSUE" =~ ^[0-9]+$ ]] || { echo "::error::issue must be a number, got '$ISSUE'"; exit 1; }
issues="[$ISSUE]"
else
# -linked:pr skips issues that already have a PR (Closes #n), e.g. one
# a human opened by hand.
issues=$(gh issue list --label ready-for-agent --state open \
--search "-linked:pr" --limit 5 --json number --jq '[.[].number]')
fi
echo "queue: $issues"
echo "issues=$issues" >> "$GITHUB_OUTPUT"

fix:
needs: queue
if: needs.queue.outputs.issues != '[]'
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: read # checkout only; Claude pushes with the App token
issues: write # label + comment in the cleanup step
id-token: write # Claude GitHub App token exchange
strategy:
fail-fast: false
max-parallel: 2
matrix:
issue: ${{ fromJson(needs.queue.outputs.issues) }}
steps:
- uses: actions/checkout@v4
with:
ref: main # /fix-issue branches off main regardless of the dispatching ref

- uses: actions/setup-node@v4
with:
cache: npm

- run: npm ci # /fix-issue runs lint + build; install once here, not via Claude

- uses: anthropics/claude-code-action@v1
id: claude
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: "/fix-issue ${{ matrix.issue }}"
# Bash is off by default in the action; allow exactly what /fix-issue runs.
# Deny rules win over allow rules. The force-push denies are best-effort
# (mid-rule * catches flag-after-refspec and +refspec spellings); main is
# branch-protected either way. The Edit deny keeps Claude out of CI config.
claude_args: |
--max-turns 150
--append-system-prompt "Non-interactive GitHub Actions run: no human will reply to questions. Follow the command's non-interactive instructions."
--allowedTools "Bash(git:*),Bash(gh issue:*),Bash(gh pr:*),Bash(npm run lint),Bash(npm run lint:fix),Bash(npm run build),Bash(npm run format:*),Bash(npx eslint:*),Bash(npx prettier:*),Bash(npx tsc:*),Bash(ls:*),Bash(cat:*),Read,Edit,Write,Glob,Grep"
--disallowedTools "Bash(git push *--force*),Bash(git push * -f *),Bash(git push -f*),Bash(git push * +*),Edit(/.github/**),Edit(/.releaserc)"

# One attempt per labelling. Drop the label whatever happened; if the Claude
# step didn't succeed (error, max turns, timeout) or never got an App token
# (the action exits green when this file isn't on main yet) say so on the issue.
# ponytail: a green step that opened no PR drops the label silently;
# detecting "PR exists" needs a GraphQL lookup. Check the run log instead.
- if: always()
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
ISSUE: ${{ matrix.issue }}
OUTCOME: ${{ steps.claude.outcome }}
HAD_TOKEN: ${{ steps.claude.outputs.github_token != '' }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
gh issue edit "$ISSUE" --remove-label ready-for-agent
if [ "$OUTCOME" != "success" ] || [ "$HAD_TOKEN" != "true" ]; then
gh issue comment "$ISSUE" --body "Automated fix attempt ended with status \`$OUTCOME\` and opened no PR. Run: $RUN_URL. Re-add the \`ready-for-agent\` label to retry."
fi
Loading