diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 4f6145b..2966d03 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -10,6 +10,13 @@ on: # - "src/**/*.js" # - "src/**/*.jsx" +# One review per PR at a time: a new push cancels the in-flight review so the +# only verdict that lands is for the latest commit (implementbot keys off the +# verdict's SHA, so stale verdicts must not race the current one). +concurrency: + group: claude-review-${{ github.event.pull_request.number }} + cancel-in-progress: true + jobs: claude-review: # Optional: Filter by PR author @@ -21,8 +28,8 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - pull-requests: read - issues: read + pull-requests: write + issues: write id-token: write steps: @@ -36,9 +43,74 @@ jobs: uses: anthropics/claude-code-action@v1 with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - plugin_marketplaces: 'https://github.com/anthropics/claude-code.git' - plugins: 'code-review@claude-code-plugins' - prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}' + prompt: | + Review this pull request as an experienced engineer on this + codebase. This repo's CLAUDE.md is your rubric — follow it, and where + a more specific guideline applies, it wins. + + Context for posting (do not review these values, just use them): + - Repository: ${{ github.repository }} + - PR number: ${{ github.event.pull_request.number }} + - Head commit SHA: ${{ github.event.pull_request.head.sha }} + - Opened by implementbot: ${{ contains(github.event.pull_request.body, 'implementbot:issue=') }} + + Priorities, most important first — this is what this team cares about: + 1. Make invalid states unrepresentable. Flag nullable/optional fields + with no real "absent" case, tri-state "omitted vs explicit null" + confusion in partial-update schemas, mode/boolean flags that should + be two functions, and fields only valid together but typed apart. + 2. Tests for new or changed behaviour — especially the negative and + edge cases (invalid input, missing record, empty input, the + default/omitted argument). A missing test for new behaviour is a + finding even when the code looks correct. + 3. Encapsulation — anything exposed that could be private (_-prefixed). + 4. Domain types over bare primitives for ids/paths/urls/tokens/counts, + applied where the value is created, not as casts at call sites. + 5. Reuse over duplication — logic a model, helper, or library already owns. + 6. Simplicity and placement — speculative abstraction, code in the wrong + module, dead code, and comments that state "what" instead of "why". + + Rules of engagement: + - Only comment when you are confident the issue is real and worth the + author's attention. Prefer a few high-value, design-level comments + over many small nits. + - Classify each finding as BLOCKING (a real correctness, spec, or + guideline violation that should not merge as-is) or OPTIONAL (a + nice-to-have or minor improvement). Only blocking findings gate. + - A clean PR is a success. Never invent findings or restate the diff to + look thorough. + - Do not report anything a formatter, linter, or type checker catches; + CI runs those. Do not report pre-existing issues on lines this PR + did not change. + + How to post (the PR branch is already checked out here): + - Leave each finding as an inline comment on the specific line with + `mcp__github_inline_comment__create_inline_comment` (pass + `confirmed: true`). Say whether it is BLOCKING or OPTIONAL, then what + is wrong, why it matters (cite CLAUDE.md where relevant), and what + would resolve it. + - Then handle the top-level verdict comment by whether implementbot + opened this PR (see the "Opened by implementbot" value above): + + * If TRUE — always post a verdict, even when the PR is clean, because + implementbot waits for it. Build the body with this exact first line: + `` + using the Head commit SHA above, and `changes` if you left any + BLOCKING finding else `clean`; follow it with one or two sentences + (and "details are inline" when there are findings). Post it as a + single STICKY comment — never a second one: first look for your + previous verdict with + `gh api "repos//issues//comments" --jq 'map(select(.body|startswith("