一键导入
pr-review
Performs comprehensive Pull Request reviews for this project, focusing on Clean Architecture, Jetpack Compose, Security, and Fintech standards.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Performs comprehensive Pull Request reviews for this project, focusing on Clean Architecture, Jetpack Compose, Security, and Fintech standards.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
Runs ./gradlew check to identify code quality issues (ktlint, detekt, Spotless, tests) and automatically fixes all problems found.
Generates a standardized Network Module, API Interface, Remote Data Source, Repository, UseCase, Models, and ViewModel updates for a specific feature using the Hybrid Network Architecture and MVI Pattern.
Converts raw JSON structures into clean, production-ready Moshi DTOs for the Android_Digital_Wallet project.
基于 SOC 职业分类
| name | PR Review |
| description | Performs comprehensive Pull Request reviews for this project, focusing on Clean Architecture, Jetpack Compose, Security, and Fintech standards. |
[!IMPORTANT] Prerequisite: Before reviewing any PR, use the
@quality_checkskill to run local quality tools. This ensures code passes ktlint, detekt, and Spotless before manual review begins.
[!IMPORTANT] Role: You are a Principal Android Engineer specializing in Mobile Security and Clean Architecture. Your task is to review Pull Requests for this project.
[!TIP] Pro Tip: Update/Load all principles immediately during triggering the local
@quality_checkskill to ensure you have the latest context. While wait for@quality_checkskill processing.
[!NOTE] Above tip ensures that AI Agents can run
@quality_checkskill and update/load all principles concurrently.
- Trigger
@quality_checkto run./gradlew checkin the background.- Load Principles while waiting(
@quality_checkskill processing).
You are a Principal Android Engineer specializing in Mobile Security and Clean Architecture. Your task is to review the Pull Request for this project.
Evaluate the provided Diff based on the following criteria:
| Rule | Description |
|---|---|
| Intention-Revealing Names | Variable/function names must answer: Why it exists, what it does, and how it is used |
| MVI Action Semantics | Actions must use intent-based names (e.g., SubmitTopUp, ChangePin, LoadBalance) |
| MVI State Semantics | States must describe the current UI status (e.g., BalanceLoading, PaymentSuccess) |
| Searchable Names | Avoid "Magic Numbers" or "Magic Strings". Use named constants (e.g., MAX_PIN_ATTEMPTS instead of 3) |
| Kotlin Style | 4 spaces indentation, max 120 chars line length, no wildcard imports (import a.*), use trailing commas |
Examples:
// ❌ BAD
var d: Int // days elapsed
// ✅ GOOD
var daysSinceLastTransaction: Int
| Rule | Requirement |
|---|---|
| Small & Single Responsibility (SRP) | Max 20 lines per function. If it has "And", split it |
| Monadic/Dyadic Arguments | Max 2 arguments. If 3+, wrap into a Data Class |
| Command Query Separation (CQS) | A function should either perform an action (Command) OR return data (Query), never both |
Examples:
// ❌ BAD - Does too many things
fun processPaymentAndUpdateBalance(amount: BigDecimal, userId: String, notify: Boolean) { ... }
// ✅ GOOD - Single responsibility
fun processPayment(request: PaymentRequest): PaymentResult { ... }
fun updateBalance(userId: String, amount: BigDecimal) { ... }
[!CAUTION] The Domain layer is the core of the application and must remain pure.
| Rule | Description |
|---|---|
| Law of Demeter | Modules should not know the inner details of objects they manipulate |
| Layer Purity (Domain) | Strictly NO imports from android.*, androidx.*, or external SDKs (except @Inject) |
| Layer Purity (Data) | Must map DTOs (JSON) to Domain Entities before returning to Repository |
| Tell, Don't Ask | Don't pull data out to process it. Tell the object to perform its own logic |
Layer Constraints:
| Layer | Constraints |
|---|---|
| Domain | Zero dependencies on Android Framework, Compose, or external libraries |
| Data | Must contain DTOs and Mappers to convert API responses into Domain Entities |
| Presentation | ViewModel must only expose UI State via StateFlow. No direct View references |
Examples:
// ❌ BAD - Violates Law of Demeter
user.wallet.balance.currency.symbol
// ✅ GOOD
user.getCurrencySymbol()
features/transfer) must be self-containedPresentation → Domain ← Data| Rule | Description |
|---|---|
| Strict Immutability | All states must use data class with val properties. Never mutate; always use copy() or reduce {} |
| Functional Error Handling | Use DataState<T> or Result<T> for UseCases. Force the caller to handle failures |
| Side Effect Isolation | Use Event for navigation/dialogs/toasts. Keep UI rendering pure based on State only |
View, Context, or ActivityMviViewModel<State, Action, Event>// ✅ CORRECT - Immutable state with thread-safe updates
data class TransferState(
val isLoading: Boolean = false,
val balance: BigDecimal = BigDecimal.ZERO,
val error: UiText? = null
)
// Update using reduce {} or update {}
reduce { state -> state.copy(isLoading = true) }
| Dispatcher | Use Case |
|---|---|
Dispatchers.IO | Networking, Database, File I/O |
Dispatchers.Default | Heavy computation, sorting, parsing |
Dispatchers.Main | UI updates only |
[!WARNING] Never block the Main thread with
runBlockingor synchronous network calls.
| Rule | Description |
|---|---|
| Don't Return/Pass Null | Return empty collections emptyList() or sealed classes instead of null |
| Contextual Exceptions | Throw domain-specific exceptions (e.g., InsufficientFundsException) over generic errors |
| No Force Unwrap | Avoid !! operator. Use requireNotNull(), checkNotNull(), or safe calls with meaningful fallbacks |
Examples:
// ❌ BAD - Force unwrap
val user = getUser()!!
// ✅ GOOD - Safe handling
val user = getUser() ?: return DataState.Error(UserNotFoundError)
// ✅ GOOD - Explicit requirement
val user = requireNotNull(getUser()) { "User must be logged in" }
[!CAUTION] CRITICAL: These are non-negotiable security requirements for a Digital Wallet application. Security violations are blocking issues. Reference: OWASP Mobile Top 10 2024
| Check | Verification |
|---|---|
| No Hardcoded Credentials | Scan for API keys, secrets, passwords in source code or config files |
| Secure Credential Storage | Use SecureCacheStore, EncryptedSharedPreferences, or Android Keystore |
| Revocable Tokens | Use short-lived, device-specific access tokens that can be revoked |
| No Password Storage | Never store passwords locally; use secure tokens instead |
Patterns to Flag:
// ❌ CRITICAL - Hardcoded credentials
private const val API_KEY = "sk_live_abc123..."
// ❌ CRITICAL - Insecure storage
sharedPreferences.putString("access_token", token)
// ✅ CORRECT - Secure storage
secureCacheStore.write(KEY_ACCESS_TOKEN, token)
| Check | Verification |
|---|---|
| Dependency Audit | All third-party libraries are vetted and from trusted sources |
| Version Pinning | Use exact versions, not + or latest in dependencies |
| Vulnerability Scanning | Regular dependency vulnerability scans (Dependabot, Snyk) |
| Check | Verification |
|---|---|
| Server-Side Auth | All authentication requests validated on backend |
| No Client-Side Auth Bypass | Avoid local-only authentication that can be bypassed |
| IDOR Prevention | No user roles/permissions sent from mobile device |
| Strong Session Management | Secure, randomly generated session tokens with proper timeouts |
Patterns to Flag:
// ❌ BAD - Client-side only authorization
if (currentUser.role == "admin") { showAdminPanel() }
// ✅ CORRECT - Backend validates permissions
apiService.getAdminData(accessToken) // Backend checks token's roles
| Check | Verification |
|---|---|
| Input Validation | All user inputs validated before processing |
| Output Encoding | Data properly encoded before display (prevent injection) |
| Deeplink Validation | All deeplink parameters validated and sanitized |
| Check | Verification |
|---|---|
| HTTPS Only | No HTTP connections; all traffic over TLS 1.2+ |
| Certificate Pinning | Implement certificate or public key pinning |
| No SSL Bypass | No TrustAllCerts, AllowAllHostnameVerifier, or disabled SSL checks |
Patterns to Flag:
// ❌ CRITICAL - SSL bypass
SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER
.hostnameVerifier { _, _ -> true }
// ✅ CORRECT - Certificate pinning
CertificatePinner.Builder()
.add("api.example.com", "sha256/AAAAAAA...")
.build()
| Check | Verification |
|---|---|
| No PII Logging | ABSOLUTELY NO logging of PII, Card Numbers, PINs, OTPs |
| Data Minimization | Collect only necessary data |
| Screenshot Prevention | Use FLAG_SECURE on sensitive screens |
| Check | Verification |
|---|---|
| ProGuard/R8 | Code obfuscation enabled for release builds |
| Root Detection | Detect jailbroken/rooted devices for sensitive operations |
| Debug Detection | Disable debug features in production |
| Check | Verification |
|---|---|
| Debug Disabled | android:debuggable="false" in release |
| Backup Disabled | android:allowBackup="false" or encrypted backups |
| Export Controls | Activities/Services not exported unless necessary |
| Check | Verification |
|---|---|
| Encrypted Storage | Use EncryptedSharedPreferences, SecureCacheStore, or Room with SQLCipher |
| No External Storage | Never store sensitive data on external/shared storage |
| Keystore Usage | Cryptographic keys stored in Android Keystore |
Patterns to Flag:
// ❌ CRITICAL - Plain SharedPreferences for secrets
sharedPrefs.edit().putString("refresh_token", token).apply()
// ✅ CORRECT - Encrypted storage
secureCacheStore.write(KEY_REFRESH_TOKEN, token)
| Check | Verification |
|---|---|
| Strong Algorithms | Use AES-256-GCM, SHA-256+; No MD5, SHA1, DES |
| Secure Key Management | Keys in Android Keystore, not hardcoded |
| No Custom Crypto | Use established libraries (Tink, AndroidX Security) |
Patterns to Flag:
// ❌ BAD - Weak cryptography
MessageDigest.getInstance("MD5")
val key = "hardcoded_secret_key".toByteArray()
// ✅ CORRECT
MessageDigest.getInstance("SHA-256")
val key = keyStore.getKey("alias", null)
[!WARNING] Never use
DoubleorFloatfor money calculations.
| Correct | Incorrect |
|---|---|
BigDecimal | Double |
Long (in cents) | Float |
| Principle | Description |
|---|---|
| Fast | Tests must run quickly (< 100ms per test) |
| Independent | Tests should not depend on each other |
| Repeatable | Must pass in any environment (Local/CI) |
| Self-Validating | Clear Boolean output (Pass/Fail) |
| Timely | Write tests alongside or before code (TDD mindset) |
Testing Requirements:
Screen(state: State, onAction: (Action) -> Unit)@Stable or @Immutable annotations where necessaryremember, derivedStateOf, rememberUpdatedStateLaunchedEffect with correct keys0xFFRRGGBB) or dimensionsMaterialTheme.colorScheme and MaterialTheme.typography| Aspect | Verification |
|---|---|
| Immutable Models | All entities use data class with val properties, or use @Immutable annotation |
| Import Convention | Use full package paths for cross-module imports |
| Boilerplate | Identify code that could be generated using Mason Bricks (mvi_feature, mvi_subfeature) |
| Testing Coverage | New logic is accompanied by Unit Tests for UseCases/ViewModels |
| Naming Conventions | Contract: [Feature]State, [Feature]Action, [Feature]Event. ViewModel: [Feature]ViewModel. UseCase: [Action][Feature]UseCase. DTOs: *Dto suffix |
(Reference: Android Kotlin-First Guide)
(Reference: Docs) Kotlin reduces boilerplate. Ensure code utilizes modern language features effectively.
| Check | Requirement |
|---|---|
| Data Classes | Use data class for model objects to get equals, hashCode, toString for free. |
| Extension Functions | Use extension functions to extend existing classes without inheriting (e.g., View.visible()). |
| String Templates | Use $var or ${expression} instead of String.format or concatenation. |
| SAM Conversions | Use lambda syntax for single-abstract-method interfaces (e.g., OnClickListener { ... }). |
| Default Arguments | Use default parameter values to avoid method overloading boilerplate. |
// ❌ BAD (Java style)
val str = String.format("Hello %s", name)
// ✅ CORRECT (Expressive)
val str = "Hello $name"
(Reference: Docs)
Kotlin's type system is designed to eliminate NullPointerException and common errors.
| Check | Requirement |
|---|---|
| Null Safety | Define variables as non-nullable (String) whenever possible. Use String? only when necessary. |
| Safe Calls | Use ?. for safe access and ?: (Elvis operator) for fallback values. |
| No Force Unwrap | Strictly Ban the use of !!. Use requireNotNull or standard scoping functions (let, run) if needed. |
| Immutability | Prefer val (read-only) over var (mutable). |
// ❌ BAD
val len = text!!.length
// ✅ CORRECT
val len = text?.length ?: 0
(Reference: Docs) Code should be interoperable with Java where necessary, but idiomatic Kotlin is prioritized.
| Check | Requirement |
|---|---|
| Jvm Overloads | Use @JvmOverloads for functions with default arguments called from Java. |
| Object/Static | Use companion object for static members. Use @JvmStatic if exposing to Java. |
(Reference: Docs) Use Kotlin Coroutines for asynchronous programming, simpler and cleaner than RxJava or AsyncTasks.
| Check | Best Practice |
|---|---|
| Dispatcher Injection | ALWAYS inject Dispatchers (e.g., CoroutineDispatcher) into ViewModels/Repositories. NEVER hardcode Dispatchers.IO. |
| Scopes | Use viewModelScope or lifecycleScope. NEVER use GlobalScope. |
| Suspend Functions | Use suspend for one-shot operations. Do NOT return Flow from a suspend function. |
| Flow vs LiveData | Prefer StateFlow and SharedFlow over LiveData for architectural layers. |
// ❌ BAD - Unstructured & Hardcoded
GlobalScope.launch { ... }
// ✅ CORRECT - Structured & Injected
class MyViewModel(private val ioDispatcher: CoroutineDispatcher) : ViewModel() {
fun load() {
viewModelScope.launch(ioDispatcher) { ... }
}
}
The skill accepts two input methods:
| Input Type | Description | Command to Fetch |
|---|---|---|
| COMMIT_ID | A specific commit hash to review changes from | git show <COMMIT_ID> or git diff <COMMIT_ID>~1 <COMMIT_ID> |
| PR_DIFF | The Pull Request diff (from GitHub or pasted directly) | gh pr diff <PR_NUMBER> |
[!TIP] You can provide either one. If a commit ID is provided, the diff will be fetched automatically using
git show.
Your response MUST be structured as follows:
### 📝 Summary of Changes
(A brief description of what this PR introduces)
### 🚨 Critical Issues (Architecture/Security/Logic)
- **[File Name]:[Line Number]**: [Issue Description] → [Suggested Fix]
### 💡 Refactoring & Style (Compose/Kotlin)
- [Suggestions for cleaner code, better performance, or UI optimization]
### ⚠️ OWASP Mobile Top 10 Security Review
| Risk | Status | Notes |
|------|--------|-------|
| M1: Improper Credential Usage | ✅/❌ | |
| M2: Inadequate Supply Chain | ✅/❌ | |
| M3: Insecure Auth/Authorization | ✅/❌ | |
| M4: Input/Output Validation | ✅/❌ | |
| M5: Insecure Communication | ✅/❌ | |
| M6: Privacy Controls | ✅/❌ | |
| M7: Binary Protections | ✅/❌ | |
| M8: Security Misconfiguration | ✅/❌ | |
| M9: Insecure Data Storage | ✅/❌ | |
| M10: Insufficient Cryptography | ✅/❌ | |
### ✅ Final Verdict
(Choose one: 🟢 LGTM / 🟡 Needs Work / 🔴 Request Changes)
Use the @pr_review skill to review commit: abc123def
Use the @pr_review skill to review this PR:
<PASTE PR DIFF HERE>
# Fetch PR diff and copy to clipboard
gh pr diff <PR_NUMBER> | pbcopy
# Then paste into your request
# Review a specific commit
git show <COMMIT_ID>
# Review changes between commits
git diff <BASE_COMMIT> <HEAD_COMMIT>