From 2598bd16604e2a95948ac9eb296a6f73acef41bb Mon Sep 17 00:00:00 2001 From: Toshihiko SHIMOKAWA Date: Tue, 9 Jun 2026 01:38:20 +0900 Subject: [PATCH 1/2] feat: add one-shot ai-review reusable (Gemini/Claude, CODE/ACADEMIC) Add a reusable that runs PR review via smkwlab/ai-academic-paper-reviewer@v1.5 (a single LLM call, not an agent loop), provider- and mode-pluggable: - model_code picks the provider (claude-* -> Anthropic, else Google) - review_mode CODE (inline) or ACADEMIC (paper review) - single_comment for a summary comment (recommended for ACADEMIC, which spans the whole document and would hit GitHub's diff-line constraint inline) - graceful skip when the provider key is absent; review_mode is validated This is the fast/cheap replacement for the claude-code-action based claude-code-review / claude-paper-review (agent loop: ~5 min / $0.5+, often posting nothing). claude-mention stays on claude-code-action. Also add ai-review-self.yml to exercise it (Claude, CODE) on this repo's PRs. --- .github/workflows/ai-review-self.yml | 27 +++++++ .github/workflows/ai-review.yml | 108 +++++++++++++++++++++++---- 2 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/ai-review-self.yml diff --git a/.github/workflows/ai-review-self.yml b/.github/workflows/ai-review-self.yml new file mode 100644 index 0000000..10c0973 --- /dev/null +++ b/.github/workflows/ai-review-self.yml @@ -0,0 +1,27 @@ +# AI Review — self-caller for this (.github) repository. +# +# Exercises the new one-shot ai-review reusable (Claude, CODE mode) on this +# repo's own PRs. Intended to replace claude-review.yml (claude-code-action) +# once validated. + +name: AI Review (self) + +on: + pull_request: + types: [opened, reopened, ready_for_review] + +concurrency: + group: ai-review-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + review: + uses: ./.github/workflows/ai-review.yml + permissions: + contents: read + pull-requests: write + secrets: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + with: + model_code: claude-sonnet-4-6 + review_mode: CODE diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index 714a9df..751a0f6 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -1,25 +1,105 @@ -# AI Reviewer (Gemini) — caller for this (.github) repository itself. +# Reusable AI Review Workflow # -# Other repositories call the reusable workflow via -# uses: smkwlab/.github/.github/workflows/ai-reviewer.yml@v1 -# but for self-review we reference the reusable by local path so that each PR -# is reviewed with its own version of the workflow (no need to wait for a tag). +# One-shot PR review via the smkwlab/ai-academic-paper-reviewer action (a single +# LLM call, not an agent loop). Provider- and mode-pluggable: +# - MODEL_CODE chooses the provider: claude-* -> Anthropic, otherwise Google. +# - REVIEW_MODE: CODE (inline, bugs/logic) or ACADEMIC (paper review). +# - single_comment: post one summary comment instead of inline (recommended +# for ACADEMIC, whose feedback spans the whole document and would otherwise +# hit GitHub's diff-line constraint for inline comments). # -# Requires the GEMINI_API_KEY secret. When it is unset (e.g. fork PRs), the -# reusable workflow skips the review gracefully. +# This replaces the claude-code-action based claude-code-review / claude-paper- +# review, which were slow/expensive (agent loop). claude-mention stays on +# claude-code-action (interactive work genuinely needs the agent). +# +# Secrets: pass the key for the chosen provider (the other may be omitted). +# - anthropic_api_key: for claude-* models +# - gemini_api_key: for gemini-* models -name: AI Reviewer +name: AI Review (Reusable) on: - pull_request: - types: [opened, synchronize, reopened, ready_for_review] + workflow_call: + inputs: + model_code: + description: "Model: claude-* (Anthropic) or gemini-* (Google), e.g. claude-sonnet-4-6" + type: string + required: false + default: "claude-sonnet-4-6" + review_mode: + description: "CODE (inline; bugs/logic) or ACADEMIC (paper review)" + type: string + required: false + default: "CODE" + single_comment: + description: "Post one summary comment instead of inline (recommended for ACADEMIC)" + type: boolean + required: false + default: false + language: + description: "Review language (e.g. Japanese)" + type: string + required: false + default: "Japanese" + exclude_paths: + description: "Comma-separated path globs to exclude (e.g. *.bib,*.sty,*.cls)" + type: string + required: false + default: "" + timeout_minutes: + description: "Job timeout (minutes)" + type: number + required: false + default: 10 + secrets: + gemini_api_key: + required: false + anthropic_api_key: + required: false jobs: - ai-review: - # Skip drafts; review once the PR is ready. + review: + runs-on: ubuntu-latest + timeout-minutes: ${{ inputs.timeout_minutes }} if: github.event.pull_request.draft == false - uses: ./.github/workflows/ai-reviewer.yml permissions: contents: read pull-requests: write - secrets: inherit + steps: + # Validate review_mode and gracefully skip when the provider's key is + # absent (e.g. fork PRs receive no secrets). + - name: Check inputs and API key + id: check + env: + MODEL_CODE: ${{ inputs.model_code }} + REVIEW_MODE: ${{ inputs.review_mode }} + GEMINI_API_KEY: ${{ secrets.gemini_api_key }} + ANTHROPIC_API_KEY: ${{ secrets.anthropic_api_key }} + run: | + case "$REVIEW_MODE" in + CODE|ACADEMIC) ;; + *) echo "::error::invalid review_mode '$REVIEW_MODE' (CODE|ACADEMIC)"; exit 1 ;; + esac + case "$MODEL_CODE" in + *claude*) key="$ANTHROPIC_API_KEY"; name="anthropic_api_key" ;; + *) key="$GEMINI_API_KEY"; name="gemini_api_key" ;; + esac + if [ -z "$key" ]; then + echo "::notice::$name is not configured (or unavailable on fork PRs). Skipping AI review." + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + + - name: AI Review + if: steps.check.outputs.skip != 'true' + uses: smkwlab/ai-academic-paper-reviewer@v1.5 + with: + GEMINI_API_KEY: ${{ secrets.gemini_api_key }} + ANTHROPIC_API_KEY: ${{ secrets.anthropic_api_key }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MODEL_CODE: ${{ inputs.model_code }} + REVIEW_MODE: ${{ inputs.review_mode }} + LANGUAGE: ${{ inputs.language }} + EXCLUDE_PATHS: ${{ inputs.exclude_paths }} + USE_SINGLE_COMMENT_REVIEW: ${{ inputs.single_comment }} From 406e26afd8c97f12513dc7677a0b58c1324f98c5 Mon Sep 17 00:00:00 2001 From: Toshihiko SHIMOKAWA Date: Tue, 9 Jun 2026 01:51:10 +0900 Subject: [PATCH 2/2] docs: clarify synchronize exclusion and workflow_call draft guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address the AI review's valid points on #44: - note why ai-review-self omits synchronize (cost; @claude for on-demand) - note that the reusable's draft guard works because github.event is inherited from the caller's pull_request event (the review's [HIGH] claim that it always skips is a false positive — this PR's own run ran and posted, proving the guard evaluated true) --- .github/workflows/ai-review-self.yml | 2 +- .github/workflows/ai-review.yml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ai-review-self.yml b/.github/workflows/ai-review-self.yml index 10c0973..794885f 100644 --- a/.github/workflows/ai-review-self.yml +++ b/.github/workflows/ai-review-self.yml @@ -8,7 +8,7 @@ name: AI Review (self) on: pull_request: - types: [opened, reopened, ready_for_review] + types: [opened, reopened, ready_for_review] # no synchronize: avoid re-reviewing on every push (cost). Use @claude for an on-demand re-review. concurrency: group: ai-review-${{ github.event.pull_request.number }} diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index 751a0f6..6f28279 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -61,6 +61,9 @@ jobs: review: runs-on: ubuntu-latest timeout-minutes: ${{ inputs.timeout_minutes }} + # In a workflow_call, github.event is INHERITED from the caller's triggering + # event, so for a pull_request caller this draft guard works as expected + # (and is null -> skip for non-PR events). if: github.event.pull_request.draft == false permissions: contents: read