|
1 | 1 | --- |
2 | 2 | name: pr-review |
3 | | -description: This skill should be used when the user asks to review a pull request, check code quality, run a PR review, or asks "is this PR ready to merge?" for the godtools-android project. It performs a structured review checking project-specific patterns learned from historical PR feedback. |
4 | | -version: 1.0.0 |
| 3 | +description: Review a pull request against GodTools Android project conventions. Use when asked to review a PR, check code quality, or audit changes. |
| 4 | +argument-hint: [pr-number] |
| 5 | +allowed-tools: Bash, Read, Grep, Glob, Write, Edit |
5 | 6 | --- |
6 | 7 |
|
7 | | -# GodTools Android PR Review |
| 8 | +Review pull request $ARGUMENTS against the GodTools Android project conventions. |
8 | 9 |
|
9 | | -Perform a structured pull request review for the godtools-android project. This skill applies patterns learned from historical code reviews and the project's established conventions. |
| 10 | +## Steps |
10 | 11 |
|
11 | | -## Review Workflow |
| 12 | +1. Check for dismissed issues by reading `.claude/skills/pr-review/dismissed-issues.md` if it exists. |
| 13 | + Load all dismissed entries — each has a **Pattern** and **Reason**. You will use these to suppress matching findings later. |
12 | 14 |
|
13 | | -### Step 1: Get the Diff |
| 15 | +2. Fetch the PR diff and metadata: |
| 16 | +``` |
| 17 | +gh pr diff $ARGUMENTS |
| 18 | +gh pr view $ARGUMENTS |
| 19 | +``` |
14 | 20 |
|
15 | | -```bash |
16 | | -# If a PR exists |
17 | | -gh pr diff |
| 21 | +3. Identify all changed files and categorize them (Kotlin, build scripts, resources, manifests, tests). |
18 | 22 |
|
19 | | -# Otherwise compare to base branch |
20 | | -git diff develop...HEAD --name-only |
21 | | -git diff develop...HEAD |
| 23 | +4. Pre-flight check — run ktlint. This is a hard blocker: |
| 24 | +``` |
| 25 | +./gradlew :build-logic:ktlintCheck ktlintCheck |
22 | 26 | ``` |
| 27 | +If it fails, report as **Must Fix** before reviewing anything else. |
23 | 28 |
|
24 | | -Identify all changed files and categorize them by type (Kotlin, build scripts, resources, manifests, tests). |
| 29 | +5. Review each category using the checklist below. |
25 | 30 |
|
26 | | -### Step 2: Pre-Flight Check — ktlint |
| 31 | +6. 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. |
27 | 32 |
|
28 | | -Always verify ktlint passes. This is a hard blocker: |
| 33 | +7. Output a structured review (format below). |
| 34 | + |
| 35 | +8. After the review output, print: |
29 | 36 |
|
30 | | -```bash |
31 | | -./gradlew :build-logic:ktlintCheck ktlintCheck |
32 | 37 | ``` |
| 38 | +--- |
| 39 | +To dismiss a finding so it won't appear in future reviews, say: |
| 40 | + dismiss: <short title> — <reason> |
| 41 | +``` |
| 42 | + |
| 43 | +--- |
| 44 | + |
| 45 | +## Review Checklist |
| 46 | + |
| 47 | +### Build Scripts (`build.gradle.kts`) |
| 48 | + |
| 49 | +- [ ] `godtools.library-conventions` / `godtools.application-conventions` applied — do not re-declare `minSdk`, `compileSdk`, `targetSdk`, Kotlin toolchain, or proguard rules |
| 50 | +- [ ] Compose enabled via `configureCompose(project)` in `android {}` — do not manually add Compose BOM or Circuit deps |
| 51 | +- [ ] No redundant `buildFeatures` flags already covered by the convention plugin |
| 52 | +- [ ] New modules use `godtools.library-conventions`, `godtools.application-conventions`, or `godtools.dynamic-feature-conventions` as appropriate |
| 53 | +- [ ] KSP used for new modules; `kapt` only if DataBinding is still present (see TODO comment pattern) |
| 54 | + |
| 55 | +### `gradle/libs.versions.toml` |
| 56 | + |
| 57 | +- [ ] No duplicate version keys |
| 58 | +- [ ] No unused `[plugins]` entries (common AS wizard artifact) |
| 59 | +- [ ] No unused `[libraries]` entries |
| 60 | +- [ ] All new dependencies declared here, not hardcoded in `build.gradle.kts` |
| 61 | + |
| 62 | +### Android Resources & Manifests |
| 63 | + |
| 64 | +- [ ] No AS wizard scaffolding in library modules: `colors.xml`, `strings.xml` with `app_name`, default launcher icons, per-module theme definitions |
| 65 | +- [ ] Library `AndroidManifest.xml` does not declare `application` attributes (theme, label, icon) — those belong in `:app` |
| 66 | +- [ ] Hardcoded strings extracted to `strings.xml` (inline string literals in composables are flagged) |
| 67 | +- [ ] No `app_name` or generic color/theme resources in library modules |
| 68 | + |
| 69 | +### `settings.gradle.kts` |
| 70 | + |
| 71 | +- [ ] No local `includeBuild` substitutions for `kotlin-mpp-godtools-tool-parser` committed — publish artifact and update version in `libs.versions.toml` instead |
| 72 | + |
| 73 | +### Jetpack Compose |
| 74 | + |
| 75 | +- [ ] Every public or internal composable has `modifier: Modifier = Modifier` parameter |
| 76 | +- [ ] Always uses `GodToolsTheme` from `:ui:base` — no per-module `MaterialTheme(colorScheme = …)` wrappers |
| 77 | +- [ ] Colors accessed via `MaterialTheme.colorScheme.*` or `GodToolsTheme.extendedColorScheme` |
| 78 | +- [ ] CPU-heavy work (bitmap generation, etc.) done off the main thread via `produceState { withContext(Dispatchers.IO) { … } }` |
33 | 79 |
|
34 | | -If it fails, report as **Critical** — PR cannot merge until resolved. |
| 80 | +### Circuit Presenter/UI Patterns |
35 | 81 |
|
36 | | -### Step 3: Run the Checklist |
| 82 | +**Presenter** |
| 83 | +- [ ] Uses `@AssistedInject` constructor; `Navigator`/`Screen` injected via `@Assisted` |
| 84 | +- [ ] Contains nested `Factory` interface annotated with `@AssistedFactory` and `@CircuitInject(<Screen>::class, SingletonComponent::class)` |
| 85 | +- [ ] `UiState` is a `data class` implementing `CircuitUiState` |
| 86 | +- [ ] `UiEvent` is a `sealed interface` implementing `CircuitUiEvent`, marked `internal` |
| 87 | +- [ ] `UiState` and `UiEvent` defined as nested types inside the Presenter |
| 88 | +- [ ] `UiState` exposes `val eventSink: (UiEvent) -> Unit` |
| 89 | +- [ ] Presenter contains no UI logic — pure state/event handling |
37 | 90 |
|
38 | | -For each changed file, apply the relevant checks from `references/patterns.md`. Use the priority levels below to triage findings: |
| 91 | +**UI Composable** |
| 92 | +- [ ] Annotated with `@CircuitInject(<Screen>::class, SingletonComponent::class)` |
| 93 | +- [ ] Signature: `(state: <Presenter>.UiState, modifier: Modifier = Modifier)` |
| 94 | +- [ ] All user interactions delegated via `state.eventSink(UiEvent.*)` — no direct function calls from UI |
39 | 95 |
|
40 | | -- **Critical** — Hard blocker, must fix before merge |
41 | | -- **Important** — Should fix in this PR |
42 | | -- **Suggestion** — Nice to have, can be deferred |
| 96 | +**Screen** |
| 97 | +- [ ] Annotated with `@Parcelize` |
| 98 | +- [ ] Is an `object` or `data class` implementing `Screen` |
43 | 99 |
|
44 | | -### Step 4: PR Hygiene Check |
| 100 | +### Cross-Module Activity / Intent Creation |
| 101 | + |
| 102 | +- [ ] `Intent`/`PendingIntent` creation for Activities uses extension functions in `ui/base/src/main/kotlin/org/cru/godtools/base/ui/Activities.kt` (string class names, no hard compile-time deps between sibling UI modules) |
| 103 | + |
| 104 | +### Repository & DAO Patterns |
| 105 | + |
| 106 | +- [ ] DAOs use `suspend fun` for single-shot queries, `Flow<T>` for reactive queries |
| 107 | +- [ ] `@Upsert` for sync operations |
| 108 | +- [ ] `@RewriteQueriesToDropUnusedColumns` when selecting partial projections |
| 109 | + |
| 110 | +### WorkManager |
| 111 | + |
| 112 | +- [ ] Workers annotated with `@HiltWorker` + `@AssistedInject` |
| 113 | +- [ ] Extend `CoroutineWorker` for suspend support |
| 114 | +- [ ] Catch `IOException` (specific), not `Exception` (broad) |
| 115 | + |
| 116 | +### Kotlin Code Quality |
| 117 | + |
| 118 | +- [ ] Logging uses `Timber` — no `println` or `Log.*` |
| 119 | +- [ ] Exception handling catches specific types, not bare `Exception` |
| 120 | +- [ ] Visibility is intentional: `internal` for module-scoped symbols, `private` where possible |
| 121 | +- [ ] Multi-branch conditionals use `when`, not chained `if/else` |
| 122 | +- [ ] `Bundle`/`Intent` extra keys are `const val` shared between producer and consumer |
| 123 | + |
| 124 | +### Testing |
| 125 | + |
| 126 | +- [ ] Presenter tests use `presenterTestOf { }` (Circuit test API) |
| 127 | +- [ ] Paparazzi tests extend `BasePaparazziTest` from `:ui:base` testFixtures with `@TestParameter` night/accessibility matrix |
| 128 | +- [ ] Snapshots not recorded locally — triggered via GitHub Actions workflow on the feature branch |
| 129 | + |
| 130 | +### PR Hygiene |
| 131 | + |
| 132 | +- [ ] No unrelated auto-formatter whitespace changes (check with `git diff develop...HEAD --stat`) |
| 133 | +- [ ] KMP tool UI changes (app bar, lesson/tract rendering) belong in `kotlin-mpp-godtools-tool-parser`, not Android renderer modules |
| 134 | + |
| 135 | +For detailed examples of each pattern, see `references/patterns.md`. |
| 136 | + |
| 137 | +--- |
| 138 | + |
| 139 | +## Output Format |
45 | 140 |
|
46 | | -```bash |
47 | | -git diff develop...HEAD --stat |
48 | 141 | ``` |
| 142 | +## PR Review: <title> (#<number>) |
49 | 143 |
|
50 | | -Look for signs of unrelated auto-formatter changes: |
51 | | -- Large line-count diffs on files tangentially touched by the PR |
52 | | -- Whitespace-only changes in unrelated sections |
53 | | -- Reformatted import blocks or trailing commas not part of the feature |
| 144 | +### Summary |
| 145 | +<1–2 sentence summary of what the PR does> |
54 | 146 |
|
55 | | -If found, flag as **Important**: instruct to use git patch-mode staging or scope-selective reformatting in Android Studio. |
| 147 | +### Checklist Findings |
56 | 148 |
|
57 | | -### Step 5: Report Findings |
| 149 | +#### ✅ Looks Good |
| 150 | +- <item> |
58 | 151 |
|
59 | | -Structure the output as: |
| 152 | +#### ⚠️ Minor Issues |
| 153 | +- <file:line> — <issue> — <suggested fix> |
60 | 154 |
|
| 155 | +#### ❌ Must Fix |
| 156 | +- <file:line> — <issue> — <suggested fix> |
| 157 | +
|
| 158 | +#### ⏭️ Suppressed |
| 159 | +- <short title> — dismissed: <reason> |
| 160 | +(omit this section entirely if nothing was suppressed) |
| 161 | +
|
| 162 | +### Overall Verdict |
| 163 | +APPROVE / REQUEST CHANGES / COMMENT |
| 164 | +<brief rationale> |
61 | 165 | ``` |
62 | | -## PR Review: <branch or PR title> |
63 | 166 |
|
64 | | -### Critical Issues (must fix before merge) |
65 | | -- [FILE:LINE] Issue description |
| 167 | +Be specific. Reference file paths and line numbers. Cite the relevant convention when flagging an issue. |
| 168 | + |
| 169 | +--- |
| 170 | + |
| 171 | +## Handling Dismissals |
66 | 172 |
|
67 | | -### Important Issues (should fix) |
68 | | -- [FILE:LINE] Issue description |
| 173 | +When the user says `dismiss: <title> — <reason>` (in any form — "dismiss the X issue because Y", etc.): |
69 | 174 |
|
70 | | -### Suggestions |
71 | | -- [FILE:LINE] Suggestion |
| 175 | +1. Read `.claude/skills/pr-review/dismissed-issues.md` if it exists (create it if not). |
| 176 | +2. Run `git config user.name` to get the current user's name. |
| 177 | +3. Append a new entry in this format: |
72 | 178 |
|
73 | | -### Looks Good |
74 | | -- What's well done in this PR |
| 179 | +```markdown |
| 180 | +## <title> |
| 181 | +**Pattern**: <describe the class of issue broadly enough to match future occurrences> |
| 182 | +**Reason**: <reason the user gave> |
| 183 | +**Dismissed**: <today's date as YYYY-MM-DD> |
| 184 | +**Dismissed by**: <git user.name> |
75 | 185 | ``` |
76 | 186 |
|
77 | | -## Quick Reference: Top Issues from Historical Reviews |
78 | | - |
79 | | -These are the patterns most frequently flagged in past reviews. Check these first: |
80 | | - |
81 | | -1. **ktlint failures** — Run check before reporting anything else. |
82 | | -2. **AS wizard scaffolding left in** — Delete generated `colors.xml`, `strings.xml` with `app_name`, default launcher icons, and per-module theme definitions from library modules. |
83 | | -3. **Unrelated auto-formatter changes** — Flag if diff contains formatting-only changes in untouched sections. |
84 | | -4. **Convention plugin boilerplate in `build.gradle.kts`** — `godtools.library-conventions` already provides minSdk, namespace (via `android {}` block), Kotlin toolchain, proguard, and Compose setup. Don't re-declare them. |
85 | | -5. **`libs.versions.toml` clutter** — No duplicate version entries, no unused plugin declarations. |
86 | | -6. **Composables missing `modifier: Modifier = Modifier`** — Required on all public/internal composables. |
87 | | -7. **Wrong theme** — Use `GodToolsTheme` from `:ui:base`, never define a per-module theme. |
88 | | -8. **Cross-module Activity intents** — Intent/PendingIntent creation for Activities must use extension functions in `ui/base/src/main/kotlin/org/cru/godtools/base/ui/Activities.kt`. |
89 | | -9. **Circuit actions as direct calls** — User-triggered actions must flow through named `UiEvent` entries in `UiState.eventSink`, not direct function calls from the UI composable. |
90 | | -10. **Local `includeBuild` in settings.gradle.kts** — Never commit local KMP project substitutions. Publish the KMP artifact and update the version in `libs.versions.toml` instead. |
91 | | -11. **`println` for logging** — Use `Timber` instead. |
92 | | -12. **Overly broad exception catching** — Catch specific exception types, not `Exception`. |
93 | | -13. **Visibility too wide** — Prefer `internal` for module-scoped composables/functions, `private` where possible. |
94 | | - |
95 | | -For full pattern details, consult `references/patterns.md`. |
| 187 | +4. Confirm to the user what was added and that it will be suppressed in future reviews. |
0 commit comments