mobile-ai-agents 1.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/AGENTS.md +228 -0
  2. package/CLAUDE.md +236 -0
  3. package/LICENSE +21 -0
  4. package/README.md +652 -0
  5. package/agents/android/android-crash-analyzer/README.md +134 -0
  6. package/agents/android/android-crash-analyzer/agent.md +206 -0
  7. package/agents/android/android-crash-analyzer/example-input.md +138 -0
  8. package/agents/android/android-crash-analyzer/example-output.md +225 -0
  9. package/agents/android/anr-investigation/README.md +78 -0
  10. package/agents/android/anr-investigation/agent.md +117 -0
  11. package/agents/android/anr-investigation/example-input.md +62 -0
  12. package/agents/android/anr-investigation/example-output.md +103 -0
  13. package/agents/android/axiom/agent.md +197 -0
  14. package/agents/android/code-reviewer/README.md +108 -0
  15. package/agents/android/code-reviewer/agent.md +210 -0
  16. package/agents/android/code-reviewer/example-input.md +116 -0
  17. package/agents/android/code-reviewer/example-output.md +115 -0
  18. package/agents/android/compose-navigation/README.md +76 -0
  19. package/agents/android/compose-navigation/agent.md +121 -0
  20. package/agents/android/compose-navigation/example-input.md +49 -0
  21. package/agents/android/compose-navigation/example-output.md +325 -0
  22. package/agents/android/compose-screen-builder/README.md +95 -0
  23. package/agents/android/compose-screen-builder/agent.md +170 -0
  24. package/agents/android/compose-screen-builder/example-input.md +40 -0
  25. package/agents/android/compose-screen-builder/example-output.md +568 -0
  26. package/agents/android/compose-ui-reviewer/README.md +73 -0
  27. package/agents/android/compose-ui-reviewer/agent.md +253 -0
  28. package/agents/android/compose-ui-reviewer/example-input.md +84 -0
  29. package/agents/android/compose-ui-reviewer/example-output.md +102 -0
  30. package/agents/android/crash-analyzer/README.md +53 -0
  31. package/agents/android/crash-analyzer/agent.md +295 -0
  32. package/agents/android/memory-leak-analyzer/README.md +79 -0
  33. package/agents/android/memory-leak-analyzer/agent.md +116 -0
  34. package/agents/android/memory-leak-analyzer/example-input.md +78 -0
  35. package/agents/android/memory-leak-analyzer/example-output.md +128 -0
  36. package/agents/cross-platform/accessibility-auditor/README.md +47 -0
  37. package/agents/cross-platform/accessibility-auditor/agent.md +268 -0
  38. package/agents/cross-platform/accessibility-auditor/example-input.md +84 -0
  39. package/agents/cross-platform/accessibility-auditor/example-output.md +132 -0
  40. package/agents/cross-platform/appforge/agent.md +303 -0
  41. package/agents/cross-platform/ci-cd-generator/README.md +65 -0
  42. package/agents/cross-platform/ci-cd-generator/agent.md +422 -0
  43. package/agents/cross-platform/ci-cd-generator/example-input.md +69 -0
  44. package/agents/cross-platform/ci-cd-generator/example-output.md +261 -0
  45. package/agents/cross-platform/crasher/agent.md +221 -0
  46. package/agents/cross-platform/figma/agent.md +247 -0
  47. package/agents/cross-platform/launchpad/agent.md +330 -0
  48. package/agents/cross-platform/mobile-harness/agent.md +422 -0
  49. package/agents/cross-platform/mobile-memory/agent.md +429 -0
  50. package/agents/cross-platform/perf/agent.md +261 -0
  51. package/agents/cross-platform/pipeline/agent.md +288 -0
  52. package/agents/cross-platform/release-notes-generator/README.md +63 -0
  53. package/agents/cross-platform/release-notes-generator/agent.md +218 -0
  54. package/agents/cross-platform/release-notes-generator/example-input.md +71 -0
  55. package/agents/cross-platform/release-notes-generator/example-output.md +96 -0
  56. package/agents/cross-platform/scribe/agent.md +180 -0
  57. package/agents/cross-platform/security-scanner/README.md +61 -0
  58. package/agents/cross-platform/security-scanner/agent.md +287 -0
  59. package/agents/cross-platform/security-scanner/example-input.md +77 -0
  60. package/agents/cross-platform/security-scanner/example-output.md +141 -0
  61. package/agents/cross-platform/sentinel/agent.md +217 -0
  62. package/agents/cross-platform/store-listing-writer/README.md +58 -0
  63. package/agents/cross-platform/store-listing-writer/agent.md +380 -0
  64. package/agents/cross-platform/store-listing-writer/example-input.md +67 -0
  65. package/agents/cross-platform/store-listing-writer/example-output.md +131 -0
  66. package/agents/flutter/bloc-feature-builder/README.md +101 -0
  67. package/agents/flutter/bloc-feature-builder/agent.md +225 -0
  68. package/agents/flutter/bloc-feature-builder/example-input.md +47 -0
  69. package/agents/flutter/bloc-feature-builder/example-output.md +838 -0
  70. package/agents/flutter/dart/agent.md +223 -0
  71. package/agents/flutter/widget-generator/README.md +66 -0
  72. package/agents/flutter/widget-generator/agent.md +264 -0
  73. package/agents/flutter/widget-generator/example-input.md +71 -0
  74. package/agents/flutter/widget-generator/example-output.md +163 -0
  75. package/agents/gaming/forge/agent.md +197 -0
  76. package/agents/gaming/unreal/agent.md +200 -0
  77. package/agents/ios/crash-analyzer/README.md +69 -0
  78. package/agents/ios/crash-analyzer/agent.md +300 -0
  79. package/agents/ios/crash-analyzer/example-input.md +83 -0
  80. package/agents/ios/crash-analyzer/example-output.md +126 -0
  81. package/agents/ios/swift/agent.md +206 -0
  82. package/agents/ios/swift-reviewer/README.md +68 -0
  83. package/agents/ios/swift-reviewer/agent.md +231 -0
  84. package/agents/ios/swift-reviewer/example-input.md +100 -0
  85. package/agents/ios/swift-reviewer/example-output.md +127 -0
  86. package/agents/react-native/bridge/agent.md +212 -0
  87. package/agents/react-native/performance-optimizer/README.md +81 -0
  88. package/agents/react-native/performance-optimizer/agent.md +284 -0
  89. package/agents/react-native/performance-optimizer/example-input.md +104 -0
  90. package/agents/react-native/performance-optimizer/example-output.md +132 -0
  91. package/agents/unity/shader-generator/README.md +77 -0
  92. package/agents/unity/shader-generator/agent.md +270 -0
  93. package/agents/unity/shader-generator/example-input.md +70 -0
  94. package/agents/unity/shader-generator/example-output.md +160 -0
  95. package/agents/unreal/blueprint-advisor/README.md +71 -0
  96. package/agents/unreal/blueprint-advisor/agent.md +311 -0
  97. package/agents/unreal/blueprint-advisor/example-input.md +73 -0
  98. package/agents/unreal/blueprint-advisor/example-output.md +217 -0
  99. package/cli/index.js +1091 -0
  100. package/docs/agent-guide.md +138 -0
  101. package/docs/chatgpt-integration.md +219 -0
  102. package/docs/core-loops.md +143 -0
  103. package/docs/cursor-integration.md +143 -0
  104. package/docs/getting-started.md +132 -0
  105. package/docs/index.html +711 -0
  106. package/docs/installation.md +271 -0
  107. package/docs/mobile-harness.md +160 -0
  108. package/docs/mobile-mcp.md +172 -0
  109. package/docs/mobile-memory.md +349 -0
  110. package/docs/release-process.md +85 -0
  111. package/docs/releases/release-v1.0.0.md +165 -0
  112. package/docs/roadmap/github-issues.md +239 -0
  113. package/docs/skill-categories.md +125 -0
  114. package/docs/vscode-copilot-integration.md +104 -0
  115. package/docs/wiki.md +615 -0
  116. package/install.sh +507 -0
  117. package/mobile-karpathy.md +79 -0
  118. package/package.json +57 -0
  119. package/skills/android/README.md +31 -0
  120. package/skills/android/android-tdd.md +89 -0
  121. package/skills/android/anr-investigation.md +251 -0
  122. package/skills/android/code-review.md +98 -0
  123. package/skills/android/compose-migration.md +48 -0
  124. package/skills/android/compose-review.md +78 -0
  125. package/skills/android/kotlin-modernize.md +51 -0
  126. package/skills/android/memory-leak-investigation.md +256 -0
  127. package/skills/android/proguard-rules.md +48 -0
  128. package/skills/cross-platform/accessibility-audit.md +63 -0
  129. package/skills/cross-platform/api-versioning.md +59 -0
  130. package/skills/cross-platform/clean-code-audit.md +239 -0
  131. package/skills/cross-platform/crash-triage.md +53 -0
  132. package/skills/cross-platform/deeplink-debug.md +57 -0
  133. package/skills/cross-platform/feature-slice.md +48 -0
  134. package/skills/cross-platform/grill-mobile.md +64 -0
  135. package/skills/cross-platform/mobile-mcp-qa.md +193 -0
  136. package/skills/cross-platform/mobile-memory-graph.md +179 -0
  137. package/skills/cross-platform/mobile-memory-save.md +210 -0
  138. package/skills/cross-platform/mobile-memory-search.md +154 -0
  139. package/skills/cross-platform/perf-audit.md +61 -0
  140. package/skills/cross-platform/prd-verification.md +324 -0
  141. package/skills/cross-platform/release-prep.md +64 -0
  142. package/skills/cross-platform/security-audit.md +281 -0
  143. package/skills/cross-platform/store-listing.md +75 -0
  144. package/skills/flutter/README.md +32 -0
  145. package/skills/flutter/dart-modernize.md +47 -0
  146. package/skills/flutter/flutter-review.md +59 -0
  147. package/skills/flutter/flutter-tdd.md +73 -0
  148. package/skills/flutter/widget-extract.md +41 -0
  149. package/skills/flutter/widget-gen.md +131 -0
  150. package/skills/gaming/blueprint-to-cpp.md +51 -0
  151. package/skills/gaming/game-perf.md +54 -0
  152. package/skills/gaming/shader-gen.md +44 -0
  153. package/skills/gaming/unity-tdd.md +85 -0
  154. package/skills/ios/README.md +45 -0
  155. package/skills/ios/data-persistence.md +151 -0
  156. package/skills/ios/ios-tdd.md +90 -0
  157. package/skills/ios/networking.md +139 -0
  158. package/skills/ios/performance.md +122 -0
  159. package/skills/ios/swift-concurrency.md +57 -0
  160. package/skills/ios/swift-review.md +124 -0
  161. package/skills/ios/swiftui-review.md +49 -0
  162. package/skills/ios/swiftui-state.md +122 -0
  163. package/skills/ios/unit-testing.md +144 -0
  164. package/skills/ios/xcode-warnings.md +37 -0
  165. package/skills/react-native/README.md +42 -0
  166. package/skills/react-native/bridge-audit.md +104 -0
  167. package/skills/react-native/expo-optimize.md +55 -0
  168. package/skills/react-native/new-arch-migrate.md +53 -0
  169. package/skills/react-native/performance.md +124 -0
  170. package/skills/react-native/rn-review.md +53 -0
  171. package/skills/react-native/rn-tdd.md +76 -0
  172. package/skills/shared/README.md +46 -0
  173. package/skills/shared/accessibility-audit.md +132 -0
  174. package/skills/shared/crash-analysis.md +110 -0
  175. package/skills/shared/security-scan.md +149 -0
  176. package/skills/unity/README.md +31 -0
  177. package/skills/unity/shader-review.md +110 -0
  178. package/templates/agent-template.md +88 -0
  179. package/templates/prompt-template.md +50 -0
  180. package/templates/skill-template.md +64 -0
  181. package/templates/workflow-template.md +48 -0
  182. package/workflows/app-launch.md +66 -0
  183. package/workflows/appforge-workflow.md +232 -0
  184. package/workflows/ci-setup.md +77 -0
  185. package/workflows/crash-to-fix.md +71 -0
  186. package/workflows/feature-ship.md +61 -0
  187. package/workflows/game-level.md +72 -0
  188. package/workflows/mobile-harness.md +319 -0
  189. package/workflows/mobile-mcp-qa.md +228 -0
  190. package/workflows/mobile-memory-workflow.md +243 -0
  191. package/workflows/new-project-setup.md +93 -0
  192. package/workflows/new-screen.md +68 -0
  193. package/workflows/perf-sprint.md +78 -0
  194. package/workflows/release-cycle.md +71 -0
@@ -0,0 +1,253 @@
1
+ # Agent 13 — Android Compose UI Reviewer
2
+
3
+ **Platform:** Android (Jetpack Compose)
4
+ **Category:** Code Quality / Performance
5
+ **Complexity:** Medium
6
+
7
+ ---
8
+
9
+ ## Purpose
10
+
11
+ A Compose-specific code reviewer that focuses exclusively on recomposition scope, state hoisting correctness, LazyColumn performance, side effect placement, slot API design, and `remember`/`derivedStateOf` selection. Complements the Android Code Reviewer (Agent 01), which covers the full file including architecture and coroutines.
12
+
13
+ ---
14
+
15
+ ## Input Format
16
+
17
+ ```
18
+ COMPOSE_VERSION: <e.g. 1.7>
19
+ KOTLIN_VERSION: <e.g. 2.0>
20
+ FILE_PATH: <relative path, e.g. ui/profile/ProfileScreen.kt>
21
+ CODE:
22
+ <paste the Composable function(s) to review>
23
+ ```
24
+
25
+ **Fields:**
26
+
27
+ | Field | Required | Description |
28
+ |---|---|---|
29
+ | `COMPOSE_VERSION` | Yes | Determines available APIs |
30
+ | `KOTLIN_VERSION` | Yes | Affects stability inference |
31
+ | `FILE_PATH` | Yes | Used to infer component role (Screen, Component, Item) |
32
+ | `CODE` | Yes | The Composable(s) to review |
33
+
34
+ ---
35
+
36
+ ## Output Format
37
+
38
+ ```
39
+ COMPOSE REVIEW
40
+ ==============
41
+ File: <file path>
42
+ Composable(s): <list of top-level @Composable functions reviewed>
43
+ Issues Found: <count>
44
+ Critical: <count> Warning: <count> Info: <count>
45
+
46
+ FINDINGS
47
+ --------
48
+ [CRITICAL] <Composable name> — <Issue title>
49
+ Problem : <What is wrong and the recomposition or correctness impact>
50
+ Fix : <Corrected Compose code snippet>
51
+
52
+ [WARNING] <Composable name> — <Issue title>
53
+ Problem : <What is wrong>
54
+ Fix : <Corrected approach>
55
+
56
+ [INFO] <Composable name> — <Issue title>
57
+ Problem : <Suggestion>
58
+ Fix : <Improvement>
59
+
60
+ RECOMPOSITION SCOPE ASSESSMENT
61
+ -------------------------------
62
+ Recomposition scopes identified: <count>
63
+ Unnecessary broad scopes: <Yes/No>
64
+ → <Explanation>
65
+
66
+ STATE HOISTING
67
+ --------------
68
+ State correctly hoisted: <Yes/No>
69
+ → <Explanation of any violations>
70
+
71
+ SIDE EFFECTS
72
+ ------------
73
+ Side effects correctly placed: <Yes/No>
74
+ → <Explanation>
75
+
76
+ OVERALL VERDICT: <PASS / NEEDS WORK / REWRITE>
77
+ ```
78
+
79
+ ---
80
+
81
+ ## System Prompt
82
+
83
+ ```
84
+ You are a senior Android engineer specializing in Jetpack Compose internals, recomposition
85
+ optimization, and idiomatic Compose API design. Your job is to review Composable functions
86
+ for Compose-specific issues — not general Kotlin or architecture concerns.
87
+
88
+ Review dimensions:
89
+
90
+ 1. RECOMPOSITION SCOPE
91
+ - Is state read inside the smallest possible lambda scope? Reading a State<T> inside a
92
+ lambda (Modifier.graphicsLayer, Canvas drawBehind) limits recomposition to that lambda.
93
+ Reading it in the Composable body triggers full recomposition.
94
+ - Are unstable types (regular classes, List, Map) causing unnecessary recompositions?
95
+ Prefer immutable types, @Stable, @Immutable, or kotlinx.collections.immutable.
96
+
97
+ 2. REMEMBER AND DERIVEDSTATEOF
98
+ - remember { } should wrap every object instantiated in a Composable body that is expensive
99
+ to create or must maintain identity across recompositions.
100
+ - derivedStateOf { } is for computed values that depend on State<T> — it only recomposes
101
+ when the result changes, not on every upstream state change. Flag expensive computations
102
+ inside a Composable body that should use derivedStateOf.
103
+ - rememberSaveable { } when the value must survive configuration changes.
104
+ - Flag remember with no keys on values that depend on external parameters — they will
105
+ not update when the parameter changes.
106
+
107
+ 3. LAZYCOLUMN / LAZYGRID PERFORMANCE
108
+ - key parameter on each item: required for correct animation and to preserve item state.
109
+ Absence of key causes full list recomposition on any data change.
110
+ - contentType parameter: when items have different layouts, providing contentType allows
111
+ Compose to reuse composition nodes correctly.
112
+ - Heavy computations or object allocations inside items {} lambdas: these run per item
113
+ per recomposition. Move expensive work outside the list or memoize with remember.
114
+ - Never use Column { items.forEach { } } for long lists — this does not virtualize.
115
+
116
+ 4. SIDE EFFECTS
117
+ - LaunchedEffect: for coroutines tied to the Composable lifecycle. Key must change when
118
+ you want the effect to restart.
119
+ - SideEffect: for pushing Compose state to non-Compose code after every successful recomposition.
120
+ - DisposableEffect: for effects with cleanup (listeners, subscriptions). Must call onDispose.
121
+ - Flag: network calls, database writes, or ViewModel interactions directly in the Composable
122
+ body (outside an effect) — these run on every recomposition.
123
+
124
+ 5. STATE HOISTING
125
+ - Stateful Composables (owning their own State) are harder to test and less reusable.
126
+ State should be hoisted to the lowest common ancestor or to a ViewModel.
127
+ - Flag: @Composable fun that takes both a value AND handles its own internal state for
128
+ the same concern (split brain state).
129
+
130
+ 6. SLOT API DESIGN
131
+ - Composables that render complex child content should accept a content: @Composable () -> Unit
132
+ lambda (slot) rather than a specific content type. This avoids recomposing the parent
133
+ when only the child changes.
134
+
135
+ Output MUST follow the exact format specified.
136
+ ```
137
+
138
+ ---
139
+
140
+ ## Example
141
+
142
+ ### Input
143
+
144
+ ```
145
+ COMPOSE_VERSION: 1.7
146
+ KOTLIN_VERSION: 2.0
147
+ FILE_PATH: ui/feed/FeedScreen.kt
148
+ CODE:
149
+ @Composable
150
+ fun FeedScreen(viewModel: FeedViewModel = hiltViewModel()) {
151
+ val posts by viewModel.posts.collectAsStateWithLifecycle()
152
+ val isLoading by viewModel.isLoading.collectAsStateWithLifecycle()
153
+
154
+ Column {
155
+ if (isLoading) {
156
+ CircularProgressIndicator()
157
+ }
158
+ LazyColumn {
159
+ items(posts) { post ->
160
+ val formattedDate = SimpleDateFormat("dd MMM yyyy", Locale.getDefault())
161
+ .format(post.createdAt)
162
+ PostItem(post = post, formattedDate = formattedDate)
163
+ }
164
+ }
165
+ }
166
+ }
167
+
168
+ @Composable
169
+ fun PostItem(post: Post, formattedDate: String) {
170
+ var isExpanded by remember { mutableStateOf(false) }
171
+ Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
172
+ Text(post.title)
173
+ if (isExpanded) Text(post.body)
174
+ Text(formattedDate)
175
+ }
176
+ }
177
+ ```
178
+
179
+ ### Output
180
+
181
+ ```
182
+ COMPOSE REVIEW
183
+ ==============
184
+ File: ui/feed/FeedScreen.kt
185
+ Composable(s): FeedScreen, PostItem
186
+ Issues Found: 3
187
+ Critical: 1 Warning: 1 Info: 1
188
+
189
+ FINDINGS
190
+ --------
191
+ [CRITICAL] FeedScreen — SimpleDateFormat instantiated inside LazyColumn items lambda
192
+ Problem : `SimpleDateFormat(...)` is created fresh for every post on every recomposition
193
+ of the items lambda. SimpleDateFormat construction is expensive (locale lookup,
194
+ pattern parsing). With 50 posts and frequent scroll recompositions, this creates
195
+ hundreds of objects per second.
196
+ Fix : Move date formatting outside the list, or memoize per-item:
197
+ items(posts, key = { it.id }) { post ->
198
+ val formattedDate = remember(post.createdAt) {
199
+ SimpleDateFormat("dd MMM yyyy", Locale.getDefault()).format(post.createdAt)
200
+ }
201
+ PostItem(post = post, formattedDate = formattedDate)
202
+ }
203
+ Better: format the date in the ViewModel/mapper layer and expose it as a String
204
+ on the UI model — no formatting in Composables at all.
205
+
206
+ [WARNING] FeedScreen — LazyColumn items missing key parameter
207
+ Problem : Without a key, Compose cannot identify which item is which when the list
208
+ changes. Any insertion, deletion, or reorder causes the entire list to
209
+ recompose and PostItem to lose its `isExpanded` state.
210
+ Fix : items(posts, key = { it.id }) { post -> ... }
211
+ This limits recomposition to changed items and preserves PostItem state.
212
+
213
+ [INFO] FeedScreen — Column wrapping LazyColumn limits scroll performance
214
+ Problem : Wrapping LazyColumn inside a Column is fine here, but if more content is
215
+ added above (e.g., a header that is also scrollable), the Column will force
216
+ LazyColumn to measure all items eagerly.
217
+ Fix : Consider using LazyColumn with a stickyHeader { } or a header item for the
218
+ loading indicator, keeping everything in one lazy scope:
219
+ LazyColumn {
220
+ if (isLoading) item { CircularProgressIndicator() }
221
+ items(posts, key = { it.id }) { post -> ... }
222
+ }
223
+
224
+ RECOMPOSITION SCOPE ASSESSMENT
225
+ -------------------------------
226
+ Recomposition scopes identified: 2 (FeedScreen body, PostItem body)
227
+ Unnecessary broad scopes: Yes
228
+ → Both `posts` and `isLoading` are read in the FeedScreen body. If isLoading changes
229
+ while posts is stable, the entire Column (including LazyColumn) recomposes. Consider
230
+ splitting into a LoadingIndicator composable that reads isLoading separately.
231
+
232
+ STATE HOISTING
233
+ --------------
234
+ State correctly hoisted: Partially
235
+ → `isExpanded` in PostItem is local UI state — this is correct. However, if expanded
236
+ state needs to survive scrolling (item leaving/entering the viewport), it should be
237
+ hoisted to the ViewModel and keyed by post ID.
238
+
239
+ SIDE EFFECTS
240
+ ------------
241
+ Side effects correctly placed: Yes
242
+ → No misplaced side effects detected. ViewModel interaction is correctly in the ViewModel.
243
+
244
+ OVERALL VERDICT: NEEDS WORK
245
+ ```
246
+
247
+ ---
248
+
249
+ ## Notes
250
+
251
+ - Run alongside Agent 01 (Android Code Reviewer) for full coverage: this agent handles Compose internals, Agent 01 handles architecture, coroutines, and Kotlin idioms.
252
+ - Compose compiler metrics (`-P plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=...`) can confirm whether types are stable — reference the metrics when available.
253
+ - Tested with: Claude Sonnet 4.6, GPT-4o.
@@ -0,0 +1,84 @@
1
+ # Example Input — Android Compose UI Reviewer
2
+
3
+ ---
4
+
5
+ ```
6
+ COMPOSE_VERSION: 1.7
7
+ KOTLIN_VERSION: 2.0
8
+ FILE_PATH: ui/feed/FeedScreen.kt
9
+ CODE:
10
+ @Composable
11
+ fun FeedScreen(viewModel: FeedViewModel = hiltViewModel()) {
12
+ val posts by viewModel.posts.collectAsStateWithLifecycle()
13
+ val isLoading by viewModel.isLoading.collectAsStateWithLifecycle()
14
+
15
+ Column {
16
+ if (isLoading) {
17
+ CircularProgressIndicator()
18
+ }
19
+ LazyColumn {
20
+ items(posts) { post ->
21
+ val formattedDate = SimpleDateFormat("dd MMM yyyy", Locale.getDefault())
22
+ .format(post.createdAt)
23
+ PostItem(post = post, formattedDate = formattedDate)
24
+ }
25
+ }
26
+ }
27
+ }
28
+
29
+ @Composable
30
+ fun PostItem(post: Post, formattedDate: String) {
31
+ var isExpanded by remember { mutableStateOf(false) }
32
+ Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
33
+ Text(post.title)
34
+ if (isExpanded) Text(post.body)
35
+ Text(formattedDate)
36
+ }
37
+ }
38
+ ```
39
+
40
+ ---
41
+
42
+ ## What to Expect
43
+
44
+ The agent identifies 3 issues. See [`example-output.md`](example-output.md) for the full report.
45
+
46
+ **Issue map:**
47
+ 1. `SimpleDateFormat` instantiated per item per recomposition inside `items { }` — CRITICAL
48
+ 2. `LazyColumn items` missing `key` parameter — WARNING
49
+ 3. `Column` wrapping `LazyColumn` — should be merged into one `LazyColumn` — INFO
50
+
51
+ ---
52
+
53
+ ## Variations
54
+
55
+ ### Missing derivedStateOf
56
+ ```
57
+ COMPOSE_VERSION: 1.7
58
+ KOTLIN_VERSION: 2.0
59
+ FILE_PATH: ui/cart/CartScreen.kt
60
+ CODE:
61
+ @Composable
62
+ fun CartScreen(items: List<CartItem>) {
63
+ val total = items.sumOf { it.price * it.quantity } // recalculated every recomposition
64
+ val isCheckoutEnabled = total > 0 && items.isNotEmpty()
65
+ Text("Total: $$total")
66
+ Button(enabled = isCheckoutEnabled, onClick = {}) { Text("Checkout") }
67
+ }
68
+ ```
69
+ Issue: `total` and `isCheckoutEnabled` should be `remember(items) { ... }` or `derivedStateOf { ... }`.
70
+
71
+ ### Incorrect LaunchedEffect key
72
+ ```
73
+ COMPOSE_VERSION: 1.7
74
+ KOTLIN_VERSION: 2.0
75
+ FILE_PATH: ui/profile/ProfileScreen.kt
76
+ CODE:
77
+ @Composable
78
+ fun ProfileScreen(userId: String, viewModel: ProfileViewModel = hiltViewModel()) {
79
+ LaunchedEffect(Unit) { // wrong key — won't restart when userId changes
80
+ viewModel.loadProfile(userId)
81
+ }
82
+ }
83
+ ```
84
+ Issue: Key should be `LaunchedEffect(userId)` so the effect re-fires when the `userId` changes (e.g., viewing different profiles).
@@ -0,0 +1,102 @@
1
+ # Example Output — Android Compose UI Reviewer
2
+
3
+ Full agent response to the input in [`example-input.md`](example-input.md).
4
+
5
+ ---
6
+
7
+ ```
8
+ COMPOSE REVIEW
9
+ ==============
10
+ File: ui/feed/FeedScreen.kt
11
+ Composable(s): FeedScreen, PostItem
12
+ Issues Found: 3
13
+ Critical: 1 Warning: 1 Info: 1
14
+
15
+ FINDINGS
16
+ --------
17
+
18
+ [CRITICAL] FeedScreen — SimpleDateFormat instantiated inside LazyColumn items lambda
19
+ Problem : SimpleDateFormat("dd MMM yyyy", Locale.getDefault()) is constructed fresh
20
+ for EVERY post on EVERY recomposition of FeedScreen. SimpleDateFormat construction
21
+ is expensive — it involves locale lookup, pattern parsing, and calendar initialization.
22
+ With 20 visible posts and recompositions triggered by any parent state change
23
+ (e.g., isLoading toggling), this creates hundreds of object allocations per second
24
+ during active use and significantly increases GC pressure.
25
+ Fix :
26
+ Option A — memoize per post inside the items lambda:
27
+ items(posts, key = { it.id }) { post ->
28
+ val formattedDate = remember(post.createdAt) {
29
+ SimpleDateFormat("dd MMM yyyy", Locale.getDefault()).format(post.createdAt)
30
+ }
31
+ PostItem(post = post, formattedDate = formattedDate)
32
+ }
33
+
34
+ Option B (preferred) — format in the ViewModel or mapper, expose as String:
35
+ // In ViewModel or UiModel mapper:
36
+ data class PostUiModel(val id: String, val title: String, val body: String, val date: String)
37
+ // date = SimpleDateFormat(...).format(createdAt) — runs once, not on every recomposition
38
+
39
+ Option C — use Kotlin stdlib format which avoids SimpleDateFormat entirely:
40
+ val formattedDate = remember(post.createdAt) {
41
+ post.createdAt.toInstant().atZone(ZoneId.systemDefault())
42
+ .format(DateTimeFormatter.ofPattern("dd MMM yyyy"))
43
+ }
44
+
45
+ [WARNING] FeedScreen — LazyColumn items missing key parameter
46
+ Problem : Without a key, Compose cannot identify which item is which when the list
47
+ changes. On any insertion, deletion, or reorder:
48
+ - All visible items recompose unnecessarily
49
+ - PostItem loses its isExpanded local state (expand/collapse resets)
50
+ - Animations (if added later) will not animate correctly
51
+ For 50+ posts this causes visible jank on data refresh.
52
+ Fix :
53
+ items(
54
+ items = posts,
55
+ key = { it.id }, // stable String ID — item identity across recompositions
56
+ contentType = { "post_item" } // allows Compose to reuse composition nodes efficiently
57
+ ) { post -> ... }
58
+
59
+ [INFO] FeedScreen — Column wrapping LazyColumn limits scroll composition
60
+ Problem : Wrapping LazyColumn inside Column is fine here but creates a fragile structure.
61
+ If a scrollable header or footer is added above/below the Column, the Column
62
+ forces LazyColumn to measure all items eagerly (no lazy virtualization). This is
63
+ a common source of "ConstraintsException: Nesting scrollable in the same direction"
64
+ errors.
65
+ Fix : Use a single LazyColumn with header and loading items:
66
+ LazyColumn {
67
+ if (isLoading) {
68
+ item(key = "loading") { CircularProgressIndicator() }
69
+ }
70
+ items(posts, key = { it.id }, contentType = { "post" }) { post ->
71
+ val formattedDate = remember(post.createdAt) { ... }
72
+ PostItem(post = post, formattedDate = formattedDate)
73
+ }
74
+ }
75
+
76
+ RECOMPOSITION SCOPE ASSESSMENT
77
+ -------------------------------
78
+ Recomposition scopes identified: 2 (FeedScreen body, PostItem body)
79
+ Unnecessary broad scopes: Yes
80
+ → Both `posts` and `isLoading` are read in FeedScreen body. A change to `isLoading`
81
+ while `posts` is stable causes the entire Column (including LazyColumn) to recompose.
82
+ Extract the loading indicator into a separate composable that reads `isLoading`
83
+ independently, or use an AnimatedVisibility that reads isLoading in its own scope:
84
+ AnimatedVisibility(visible = isLoading) { CircularProgressIndicator() }
85
+ With AnimatedVisibility, the visibility animation runs without recomposing the LazyColumn.
86
+
87
+ STATE HOISTING
88
+ --------------
89
+ State correctly hoisted: Partially
90
+ → `isExpanded` in PostItem is local UI state — this is correct for toggle state.
91
+ However, if expanded state must survive scrolling (PostItem leaving the viewport
92
+ causes it to recompose and lose state), hoist `isExpanded` to the ViewModel keyed
93
+ by post ID, or use rememberSaveable with a custom Saver.
94
+
95
+ SIDE EFFECTS
96
+ ------------
97
+ Side effects correctly placed: Yes
98
+ → No misplaced side effects detected. ViewModel is called from button handlers,
99
+ not directly from the Composable body.
100
+
101
+ OVERALL VERDICT: NEEDS WORK
102
+ ```
@@ -0,0 +1,53 @@
1
+ # Android Crash Analyzer (Legacy)
2
+
3
+ > **This agent has been superseded by the enhanced [`android-crash-analyzer`](../android-crash-analyzer/).**
4
+
5
+ ---
6
+
7
+ ## What Changed
8
+
9
+ The original crash analyzer at `agents/android/crash-analyzer/` used a 6-section output format and covered Android and iOS in a single agent.
10
+
11
+ It has been replaced by two specialized agents:
12
+
13
+ | New Agent | Platform | What's Better |
14
+ |---|---|---|
15
+ | [`android/android-crash-analyzer`](../android-crash-analyzer/) | Android | 9-section output, ANR support, LeakCanary, RecyclerView, Firebase Crashlytics, coroutine-specific rules |
16
+ | [`ios/crash-analyzer`](../../ios/crash-analyzer/) | iOS | ARC, force-unwrap, EXC_BAD_ACCESS, watchdog terminations, SwiftUI crashes |
17
+
18
+ ---
19
+
20
+ ## Migrate in 30 Seconds
21
+
22
+ **Before:**
23
+ ```
24
+ # Using the old agent
25
+ Use agents/android/crash-analyzer/agent.md system prompt
26
+ ```
27
+
28
+ **After:**
29
+ ```
30
+ # Use the enhanced Android agent
31
+ Use agents/android/android-crash-analyzer/agent.md system prompt
32
+ ```
33
+
34
+ The new input format is identical — just add the `USER_ACTION` field for significantly better lifecycle crash analysis:
35
+
36
+ ```
37
+ PLATFORM: Android
38
+ APP_VERSION: <version>
39
+ OS_VERSION: <Android version>
40
+ DEVICE: <device model>
41
+ USER_ACTION: <what the user was doing> ← new, highly recommended
42
+ CRASH_LOG:
43
+ <paste the full crash log>
44
+ RELATED_CODE: <optional>
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Why This File Exists
50
+
51
+ The `agents/android/crash-analyzer/` folder is kept to avoid breaking existing links and bookmarks. The `agent.md` inside still works, but the enhanced version produces significantly better output.
52
+
53
+ **Recommendation:** Update any team documentation, Cursor rules, or chat session bookmarks to point to `agents/android/android-crash-analyzer/`.