diff --git a/.claude/commands/fix-issue.md b/.claude/commands/fix-issue.md new file mode 100644 index 00000000..8fa9dd20 --- /dev/null +++ b/.claude/commands/fix-issue.md @@ -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 ` and stop. + +## 1. Select the issue + +If `$ARGUMENTS` is an issue number, use it directly: + +```bash +gh issue view --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 / # left over from an earlier attempt? +git checkout -b / # …or, if it exists: git fetch origin && git checkout +``` + +Never force-push. Branch naming follows this repo's convention: `/`, 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 # 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 +``` + +Fill `.github/PULL_REQUEST_TEMPLATE.md` — use its headings verbatim, don't invent your own: + +```bash +gh pr create --title "" --body "$(cat <<'EOF' +## Issue + +Closes # + +## Summary + + + + + +## 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 + + +EOF +)" +``` + +`Closes #` 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. diff --git a/.github/workflows/fix-issues.yml b/.github/workflows/fix-issues.yml new file mode 100644 index 00000000..84c1014c --- /dev/null +++ b/.github/workflows/fix-issues.yml @@ -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 ` (.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