ワンクリックで
pr-review
Review a pull request against gto-support project conventions. Use when asked to review a PR, check code quality, or audit changes.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Review a pull request against gto-support project conventions. Use when asked to review a PR, check code quality, or audit changes.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | pr-review |
| description | Review a pull request against gto-support project conventions. Use when asked to review a PR, check code quality, or audit changes. |
| argument-hint | ["pr-number"] |
| allowed-tools | Bash, Read, Grep, Glob, Write, Edit |
Review pull request $ARGUMENTS against the gto-support project conventions.
Check for dismissed issues by reading .claude/skills/pr-review/dismissed-issues.md if it exists.
Load all dismissed entries — each has a Pattern and Reason. You will use these to suppress matching findings later.
Fetch the PR diff and metadata. If $ARGUMENTS is provided, use it as the PR number:
gh pr diff $ARGUMENTS
gh pr view $ARGUMENTS
If no PR number is given (or the above fails because no upstream PR exists), fall back to reviewing the current branch against master:
git diff master...HEAD
git log master...HEAD --oneline
Use the branch name and commit log as the "title" in the review header.
Identify all changed files and categorize them (new module, existing module change, build logic, source set restructure, dependency update, etc.).
Run ktlint and lint, recording results for the Code Style checklist:
./gradlew :build-logic:ktlintCheck ktlintCheck
./gradlew lint
Failures are reported as ❌ Must Fix items in the review output — they do not stop the rest of the review.
Review each category using the checklist below.
Before outputting, cross-reference every finding against dismissed patterns. A finding matches a dismissed pattern when it describes the same class of issue (not necessarily the exact file/line — match by concept). Move matched findings to a separate suppressed list.
Output a structured review (format below).
Post inline comments to the PR for every ⚠️ and ❌ finding that references a specific file and line number. Skip this step entirely when reviewing a branch with no PR — there is nowhere to post. Before posting, deduplicate against all existing comments (resolved or not) to avoid re-posting anything already raised:
# Get the head SHA, repo, and all existing review comments (resolved and unresolved)
HEAD_SHA=$(gh pr view $ARGUMENTS --json headRefOid -q .headRefOid)
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
EXISTING=$(gh api repos/$REPO/pulls/$ARGUMENTS/comments --jq '[.[] | select(.in_reply_to_id == null) | {path:.path, line:.line, body:.body}]')
For each finding, check whether any existing comment (resolved or not) already covers the same file + line (or contains substantially the same text). Skip any finding that is already covered. Then bundle the remaining new comments into a single review submission:
gh api repos/$REPO/pulls/$ARGUMENTS/reviews \
--method POST \
--field commit_id="$HEAD_SHA" \
--field event="COMMENT" \
--field "comments[][path]=<file path>" \
--field "comments[][line]=<line number>" \
--field "comments[][side]=RIGHT" \
--field "comments[][body]=<finding text>
🤖 Posted by [Claude Code](https://claude.ai/code)" \
# repeat --field "comments[]..." for each new finding
Use the exact file path from the diff and the line number in the current version of the file (RIGHT side). Each comment body should contain the full finding description. Always append the attribution footer \n\n🤖 Posted by [Claude Code](https://claude.ai/code) to each comment. If no new actionable findings exist (only ✅ items or all already commented), skip this step.
If the review has no ❌ or ⚠️ findings (only ✅ and/or ⏭️ items), ask the user whether to post the full review. Skip this step entirely when reviewing a branch with no PR — branch review mode is local-only. Otherwise, if they say yes:
gh pr view $ARGUMENTS --json author -q .author.login vs gh api user -q .login)--comment (GitHub does not allow self-approval)--approve or --comment accordingly\n\n🤖 Posted by [Claude Code](https://claude.ai/code) to the bodyAfter the review output, print:
---
To dismiss a finding so it won't appear in future reviews, say:
dismiss: <short title> — <reason>
New modules
settings.gradle.ktsandroid.namespace set (for Android or KMP-with-Android modules)libs.* version catalog aliases — no hardcoded versionsproguard-consumer.pro)Convention plugin selection
gto-support.android-conventionsgto-support.multiplatform-android-conventionsgto-support.multiplatform-conventionsgto-support.java-conventionsgto-support.android-testing-conventionscompose-conventions (Android) or compose-multiplatform-conventions (KMP) — not a manual buildFeatures.compose = true plus hand-added compose runtime/debug/test dependenciesconfigureIosTarget() (not manually declared)configureJsTarget() if neededconfigureJvmTarget() if neededandroidMain, androidHostTest, androidDeviceTest, commonMain, commonTest, iosMain, etc. (the com.android.kotlin.multiplatform.library plugin uses androidHostTest/androidDeviceTest, not androidUnitTest/androidInstrumentedTest)Context, android.*, etc.) lives in androidMain — not commonMainandroidMain if they belong in commonMain)compileOnly dependencies scoped correctly and not leaked to commonMainlibs.* aliases from gradle/libs.versions.tomllibs.versions.toml — no inline versions in build.gradle.ktslibs.androidx.core, libs.kotlin.coroutines.test, etc.)api vs implementation vs compileOnly scoping is appropriate:
api only for types exposed in the module's public APIcompileOnly for optional integrations (caller must provide the dependency)ksp(...) + alias(libs.plugins.ksp)), not kapt. legacy.kapt (com.android.legacy-kapt) is reserved for AGP databinding / view-binding modules that KSP can't handle — not for Dagger/Hilt/Moshi codegenWhen an existing Android module is converted to KMP:
src/main/ → src/androidMain/src/test/ → src/androidHostTest/src/androidTest/ → src/androidDeviceTest/src/main/ and src/test/ directories fully removeddependencies { } block replaced with kotlin { sourceSets { androidMain { dependencies { } } } }android.namespace kept (now set inside the kotlin { android { namespace = … } } block)build-logic/)Changes to convention plugins or configuration files affect every module — review carefully:
org.ccci.gto.android / org.ccci.gto.android.testing)Ktlint and .editorconfig enforce most style rules (line length, final newline, unused imports, formatter rules) — step 4's pre-flight already covers those. Manual checks:
trailing-comma-on-* rules are disabled in .editorconfig, so the pre-flight does NOT catch this)internal used appropriately — avoid over-exposing API surfaceflow.test { … })commonTest for shared logic, androidHostTest for Android/Robolectric tests@Deprecated annotations with a since-version and migration hintContext, Activity, etc.) leaked into commonMain source setsproject(":module-name") — no external coordinate references to sibling modulesgit diff master...HEAD --stat — flag files whose churn doesn't match the stated PR scope)## PR Review: <title> (#<number>)
### Summary
<1–2 sentence summary of what the PR does>
### Checklist Findings
#### ✅ Looks Good
- <item>
#### ⚠️ Minor Issues
- <file:line> — <issue> — <suggested fix>
#### ❌ Must Fix
- <file:line> — <issue> — <suggested fix>
#### ⏭️ Suppressed
- <short title> — dismissed: <reason>
(omit this section entirely if nothing was suppressed)
### Overall Verdict
APPROVE / REQUEST CHANGES / COMMENT
<brief rationale>
Be specific. Reference file paths and line numbers. Cite the relevant convention from CLAUDE.md or this checklist when flagging an issue.
When the user says dismiss: <title> — <reason> (in any form — "dismiss the X issue because Y", etc.):
.claude/skills/pr-review/dismissed-issues.md if it exists (create it if not).git config user.name to get the current user's name.## <title>
**Pattern**: <describe the class of issue broadly enough to match future occurrences>
**Reason**: <reason the user gave>
**Dismissed**: <today's date as YYYY-MM-DD>
**Dismissed by**: <git user.name>
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
OWNER=${REPO%%/*}
REPONAME=${REPO##*/}
# Find unresolved review threads
gh api graphql -f query="
{
repository(owner: \"$OWNER\", name: \"$REPONAME\") {
pullRequest(number: $PR_NUMBER) {
reviewThreads(first: 100) {
nodes {
id
isResolved
comments(first: 1) {
nodes { id body path line }
}
}
}
}
}
}"
Match the thread by file path, line number, or substantial text overlap with the dismissed finding. Then reply to the thread and resolve it:
# Reply to the thread's first comment explaining the dismissal
gh api repos/$REPO/pulls/$PR_NUMBER/comments \
--method POST \
--field in_reply_to=<comment_id> \
--field body="Dismissed: <reason given by user>
🤖 [Claude Code](https://claude.ai/code)"
# Resolve the thread via GraphQL
gh api graphql -f query="
mutation {
resolveReviewThread(input: { threadId: \"<thread_node_id>\" }) {
thread { id isResolved }
}
}"