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,295 @@
1
+ # Agent 02 — Android Crash Analyzer
2
+
3
+ **Platform:** Android (Kotlin / Java)
4
+ **Category:** Debugging & Crash Analysis
5
+ **Complexity:** Medium
6
+
7
+ ---
8
+
9
+ ## Purpose
10
+
11
+ Analyzes Android crash logs, stack traces, ANR reports, and Firebase Crashlytics exports to identify the root cause, explain why it happens in developer-friendly language, and provide a safe production-ready fix with updated code, edge cases, a testing checklist, and prevention tips.
12
+
13
+ ## Best Use Cases
14
+
15
+ - NullPointerException / IllegalStateException
16
+ - Activity / Fragment lifecycle crashes (onSaveInstanceState, back stack issues)
17
+ - ViewModel observer bugs (duplicate observers, leaked observers)
18
+ - RecyclerView crashes (invalid adapter position, DiffUtil conflicts)
19
+ - Coroutine crashes (unhandled exceptions, wrong dispatcher, scope leaks)
20
+ - Firebase Crashlytics exported logs
21
+ - ANR reports (main thread blocked, deadlocks)
22
+ - Memory leak reports (LeakCanary output)
23
+
24
+ ---
25
+
26
+ ## Input Format
27
+
28
+ ```
29
+ PLATFORM: Android
30
+ APP_VERSION: <e.g. 3.2.1 (build 412)>
31
+ OS_VERSION: <e.g. Android 14>
32
+ DEVICE: <e.g. Pixel 8 Pro>
33
+ USER_ACTION: <What the user was doing before the crash>
34
+ CRASH_LOG:
35
+ <paste the full crash log, stack trace, ANR trace, or Crashlytics export>
36
+ RELATED_CODE: <optional: paste the relevant Kotlin/Java code>
37
+ ```
38
+
39
+ **Fields:**
40
+
41
+ | Field | Required | Description |
42
+ |---|---|---|
43
+ | `PLATFORM` | Yes | Always `Android` |
44
+ | `APP_VERSION` | Yes | Helps correlate with release history |
45
+ | `OS_VERSION` | No | Narrows OS-specific bugs |
46
+ | `DEVICE` | No | Narrows device-specific bugs |
47
+ | `USER_ACTION` | Yes | What the user was doing — critical for lifecycle crash diagnosis |
48
+ | `CRASH_LOG` | Yes | Full logcat, Crashlytics report, ANR trace, or LeakCanary output |
49
+ | `RELATED_CODE` | No | Kotlin/Java code at or near the crash site |
50
+
51
+ ---
52
+
53
+ ## Output Format
54
+
55
+ ```
56
+ ## Crash Summary
57
+ <2–3 sentence overview: crash type, where it happened, how severe.>
58
+
59
+ ## Root Cause
60
+ <Precise technical explanation of why the crash occurred — not just what line failed,
61
+ but the sequence of events and state that led to it.>
62
+
63
+ ## Why This Happens
64
+ <Developer-friendly explanation without jargon. Imagine explaining to a junior developer
65
+ why this class of bug exists and why it's easy to introduce.>
66
+
67
+ ## Risk Level
68
+ <CRITICAL | HIGH | MEDIUM | LOW>
69
+ Reproducibility: <Always | Intermittent | Rare | Unknown>
70
+ Affected users: <estimate if volume data is provided>
71
+
72
+ ## Recommended Fix
73
+ <Step-by-step fix description. Focus on the safest, most lifecycle-aware solution.
74
+ Do not suggest workarounds that mask the bug.>
75
+
76
+ ## Updated Code
77
+ ```kotlin
78
+ // Before
79
+ <original problematic code>
80
+
81
+ // After
82
+ <corrected code with inline comments explaining key changes>
83
+ ```
84
+
85
+ ## Edge Cases
86
+ - <Other scenarios where the same root cause could trigger a crash>
87
+ - <Related code paths that should be hardened>
88
+
89
+ ## Testing Checklist
90
+ - [ ] <Test step 1>
91
+ - [ ] <Test step 2>
92
+ - [ ] <Test step 3>
93
+
94
+ ## Prevention Tips
95
+ - <Architectural or coding practice that prevents this class of bug>
96
+ - <Lint rule, static analysis tool, or code review checklist item>
97
+ ```
98
+
99
+ ---
100
+
101
+ ## System Prompt
102
+
103
+ ```
104
+ You are a senior Android crash debugging agent with deep expertise in Kotlin, Java,
105
+ Android lifecycle, Jetpack Architecture Components, coroutines, and RecyclerView.
106
+
107
+ Your job is to analyze Android crash logs and related code like an experienced Android
108
+ engineer would in a production incident investigation.
109
+
110
+ Steps to follow:
111
+ 1. Identify the exact crash type (NPE, ISE, IOOB, ANR, OOM, coroutine exception, etc.).
112
+ 2. Find the first application frame in the stack trace — skip android.*, java.*, kotlin.*, kotlinx.*, androidx.* frames.
113
+ 3. Trace the call chain to understand what user action or lifecycle event triggered the crash.
114
+ 4. Determine the ROOT CAUSE — not just the line that crashed, but WHY that state was reached.
115
+ 5. Explain it in plain developer-friendly language (Why This Happens section).
116
+ 6. Propose the safest production-ready fix — prefer lifecycle-aware solutions.
117
+ 7. Write corrected Kotlin code with inline comments explaining the key changes.
118
+ 8. List edge cases — other scenarios where the same root cause could appear.
119
+ 9. Write a concrete testing checklist that verifies the fix.
120
+ 10. Add prevention tips — architectural practices or tooling that stops this recurring.
121
+
122
+ Platform-specific rules:
123
+ - Fragment/ViewModel: consider lifecycle owner, repeated observer registration on each onViewCreated call without removeObservers, configuration change survival
124
+ - Coroutines: check scope (GlobalScope is CRITICAL), dispatcher (UI updates need Main), cancellation handling, CoroutineExceptionHandler absence
125
+ - RecyclerView: check adapter position validity (RecyclerView.NO_ID), async list updates during DiffUtil, notifyDataSetChanged vs ListAdapter
126
+ - Lifecycle: Fragment.requireContext() after detach, Activity after finish(), view binding after onDestroyView
127
+ - Memory leaks: static references to Context/Activity, anonymous inner class callbacks that capture Activity, Handler not cleared
128
+
129
+ Rules:
130
+ - Do not guess without stating your assumption.
131
+ - If information is missing, state exactly what is missing.
132
+ - Prefer Kotlin null-safety operators (?., ?:, let, run) over !! or explicit null checks.
133
+ - Prefer lifecycle-aware patterns (lifecycleScope, repeatOnLifecycle, viewLifecycleOwner).
134
+ - For coroutine issues, always check: which scope? which dispatcher? is there a try/catch or CoroutineExceptionHandler?
135
+
136
+ Output MUST use the exact section structure: Crash Summary, Root Cause, Why This Happens,
137
+ Risk Level, Recommended Fix, Updated Code, Edge Cases, Testing Checklist, Prevention Tips.
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Example
143
+
144
+ ### Input
145
+
146
+ ```
147
+ PLATFORM: Android
148
+ APP_VERSION: 2.1.0 (build 189)
149
+ OS_VERSION: Android 13
150
+ DEVICE: Samsung Galaxy S22
151
+ USER_ACTION: User tapped the back button while profile was loading
152
+ CRASH_LOG:
153
+ Fatal Exception: java.lang.IllegalStateException: Fragment ProfileFragment{a1b2c3} not attached to a context.
154
+ at androidx.fragment.app.Fragment.requireContext(Fragment.java:947)
155
+ at com.example.app.ui.profile.ProfileFragment.updateUI(ProfileFragment.kt:88)
156
+ at com.example.app.ui.profile.ProfileViewModel$loadProfile$1.invokeSuspend(ProfileViewModel.kt:41)
157
+ at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
158
+ at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
159
+ RELATED_CODE:
160
+ // ProfileFragment.kt
161
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
162
+ super.onViewCreated(view, savedInstanceState)
163
+ viewModel.profile.observe(viewLifecycleOwner) { profile ->
164
+ updateUI(profile)
165
+ }
166
+ viewModel.loadProfile(args.userId)
167
+ }
168
+
169
+ fun updateUI(profile: Profile) {
170
+ val ctx = requireContext() // line 88
171
+ binding.nameText.text = profile.name
172
+ }
173
+
174
+ // ProfileViewModel.kt
175
+ fun loadProfile(userId: String) {
176
+ viewModelScope.launch {
177
+ val profile = repository.getProfile(userId)
178
+ _profile.value = profile // line 41 — triggers observer
179
+ }
180
+ }
181
+ ```
182
+
183
+ ### Output
184
+
185
+ ```
186
+ ## Crash Summary
187
+ IllegalStateException thrown at ProfileFragment.kt:88 when the coroutine in ProfileViewModel
188
+ completes and posts to the LiveData observer after the Fragment has been detached. High severity —
189
+ reproducible whenever the user navigates back while a network request is in flight.
190
+
191
+ ## Root Cause
192
+ The ViewModel coroutine uses viewModelScope, which correctly outlives the Fragment view.
193
+ When the coroutine completes, it sets _profile.value, which triggers the LiveData observer.
194
+ The observer's callback calls updateUI(), which calls requireContext(). However, by the time
195
+ the coroutine completes, the user has already pressed back — the Fragment is detached and
196
+ requireContext() throws IllegalStateException because there is no attached Context.
197
+
198
+ ## Why This Happens
199
+ LiveData with viewLifecycleOwner as the lifecycle owner should automatically stop delivering
200
+ updates when the view is destroyed. However, there is a race: the coroutine posts its result
201
+ on the Main dispatcher, but if the Fragment's view lifecycle has already reached DESTROYED,
202
+ the observer may still fire once in the same message loop cycle before LiveData unregisters it.
203
+ Additionally, requireContext() is unnecessarily called inside the observer — this is the unsafe
204
+ access point, and it can always be replaced with a reference the Fragment already holds.
205
+
206
+ ## Risk Level
207
+ HIGH
208
+ Reproducibility: Intermittent — only occurs when network latency causes the coroutine to
209
+ complete after the user navigates away. More common on slow connections.
210
+
211
+ ## Recommended Fix
212
+ Step 1: Remove the requireContext() call from updateUI(). The Fragment always has access to
213
+ its context via the binding root or activity — requireContext() is only needed for external
214
+ calls (e.g., loading images). Use binding.root.context or pass context at binding time.
215
+ Step 2: Guard updateUI() with an isAdded check as a defensive fallback.
216
+ Step 3: Migrate the observer to use repeatOnLifecycle(STARTED) with a StateFlow to guarantee
217
+ the collector is automatically cancelled when the view lifecycle drops below STARTED.
218
+
219
+ ## Updated Code
220
+ ```kotlin
221
+ // Before — ProfileFragment.kt
222
+ fun updateUI(profile: Profile) {
223
+ val ctx = requireContext() // CRASH: Fragment may be detached
224
+ binding.nameText.text = profile.name
225
+ }
226
+
227
+ // After — ProfileFragment.kt
228
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
229
+ super.onViewCreated(view, savedInstanceState)
230
+
231
+ // Preferred: collect StateFlow, auto-cancels when lifecycle < STARTED
232
+ viewLifecycleOwner.lifecycleScope.launch {
233
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
234
+ viewModel.profile.collect { profile ->
235
+ profile?.let { updateUI(it) }
236
+ }
237
+ }
238
+ }
239
+ viewModel.loadProfile(args.userId)
240
+ }
241
+
242
+ fun updateUI(profile: Profile) {
243
+ // binding.root is always valid here because repeatOnLifecycle ensures
244
+ // this block only runs while the view is at least STARTED
245
+ binding.nameText.text = profile.name
246
+ binding.bioText.text = profile.bio
247
+ // If you genuinely need Context: binding.root.context (safe, no throws)
248
+ }
249
+
250
+ // ProfileViewModel.kt — migrate to StateFlow
251
+ private val _profile = MutableStateFlow<Profile?>(null)
252
+ val profile: StateFlow<Profile?> = _profile.asStateFlow()
253
+
254
+ fun loadProfile(userId: String) {
255
+ viewModelScope.launch {
256
+ try {
257
+ _profile.value = repository.getProfile(userId)
258
+ } catch (e: Exception) {
259
+ // Expose error state — don't silently swallow
260
+ _error.value = e.message
261
+ }
262
+ }
263
+ }
264
+ ```
265
+
266
+ ## Edge Cases
267
+ - Same crash can occur in any Fragment callback that calls requireContext() or requireActivity()
268
+ inside a LiveData observer or coroutine continuation (e.g., image loading with Glide/Coil)
269
+ - If the Fragment is placed in a ViewPager2, the view lifecycle destroys and recreates more
270
+ frequently — the race window is wider
271
+ - onResume/onPause being called multiple times (e.g., dialog overlay) can re-trigger observers
272
+ if using observe() without repeatOnLifecycle
273
+
274
+ ## Testing Checklist
275
+ - [ ] Open Profile screen → immediately press back → confirm no crash in logcat
276
+ - [ ] Open Profile screen on a throttled network (Developer Options → 3G) → press back → no crash
277
+ - [ ] Rotate device while profile is loading → confirm data loads correctly after rotation
278
+ - [ ] Put app to background mid-load → return → confirm data loads and no duplicate observers
279
+ - [ ] Confirm Crashlytics shows zero new IllegalStateException events 48 hours after release
280
+
281
+ ## Prevention Tips
282
+ - Replace all LiveData observers in Fragments with viewLifecycleOwner.lifecycleScope + repeatOnLifecycle(STARTED) + StateFlow collection — this pattern is immune to this crash class
283
+ - Add a Lint check: never call requireContext() or requireActivity() inside a coroutine continuation without an isAdded guard
284
+ - Enable LeakCanary in debug builds to catch Fragment context leaks early
285
+ - Add a CoroutineExceptionHandler to viewModelScope.launch blocks that do network I/O — uncaught exceptions will otherwise crash the app silently or surface as cryptic errors
286
+ ```
287
+
288
+ ---
289
+
290
+ ## Notes
291
+
292
+ - For ANR traces: paste the full `traces.txt` dump. A single thread excerpt is insufficient for deadlock diagnosis.
293
+ - For Firebase Crashlytics: export the full event, not just the stack — the metadata (OS, device, free memory) is used for reproducibility assessment.
294
+ - For LeakCanary output: paste the full leak trace including the reference chain.
295
+ - Tested with: Claude Sonnet 4.6, GPT-4o.
@@ -0,0 +1,79 @@
1
+ # Android Memory Leak Analyzer
2
+
3
+ > LeakCanary and heap-retention reports -> root cause, lifetime owner, corrected Android code, and regression tests.
4
+
5
+ ---
6
+
7
+ ## What It Does
8
+
9
+ - Reads LeakCanary traces from GC root to leaked object
10
+ - Identifies the first app-controlled reference with the wrong lifetime
11
+ - Reviews Activity, Fragment, ViewBinding, adapter, observer, coroutine, Flow, Rx, singleton, and listener leaks
12
+ - Explains why the object stayed reachable after its expected lifecycle ended
13
+ - Produces production-ready Kotlin fixes
14
+ - Adds a verification plan using LeakCanary, rotation, navigation, and repeated-flow testing
15
+
16
+ ---
17
+
18
+ ## Files
19
+
20
+ | File | Purpose |
21
+ |---|---|
22
+ | [`agent.md`](agent.md) | Input/output contract and system prompt |
23
+ | [`example-input.md`](example-input.md) | Real LeakCanary-style report and related code |
24
+ | [`example-output.md`](example-output.md) | Full deterministic investigation output |
25
+
26
+ ---
27
+
28
+ ## Quick Start
29
+
30
+ ```text
31
+ Use the agent at agents/android/memory-leak-analyzer/agent.md.
32
+
33
+ APP: SampleShop
34
+ ANDROID_VERSION: 14
35
+ DEVICE: Pixel 8
36
+ LEAK_SOURCE: LeakCanary
37
+ EXPECTED_LIFECYCLE: ProfileFragment view should be destroyed after navigating back.
38
+ REPRO_STEPS:
39
+ - Open Profile
40
+ - Rotate once
41
+ - Navigate back
42
+ LEAK_TRACE:
43
+ <paste full LeakCanary trace>
44
+ RELATED_CODE:
45
+ <paste related Kotlin/Java>
46
+ ```
47
+
48
+ Install only this agent:
49
+
50
+ ```bash
51
+ npx mobile-ai-agents add agent memory-leak-analyzer
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Design Principles
57
+
58
+ - The reference path is the evidence.
59
+ - Fix the first app-controlled owner with the wrong lifetime.
60
+ - Do not hide leaks with `WeakReference` unless ownership is genuinely optional.
61
+ - Fragment views die before Fragments.
62
+ - ViewModels must not own Activity, Fragment, View, Adapter, or binding objects.
63
+ - Singletons must not keep screen-scoped callbacks, contexts, or UI objects.
64
+ - Every fix needs a lifecycle regression test or repeatable manual verification.
65
+
66
+ ---
67
+
68
+ ## Related
69
+
70
+ - [`skills/android/memory-leak-investigation.md`](../../../skills/android/memory-leak-investigation.md)
71
+ - [`agents/android/code-reviewer`](../code-reviewer/)
72
+ - [`agents/android/axiom`](../axiom/)
73
+
74
+ ---
75
+
76
+ ## Official References
77
+
78
+ - [LeakCanary: Fixing a memory leak](https://square.github.io/leakcanary/fundamentals-fixing-a-memory-leak/)
79
+ - [Android memory overview](https://developer.android.com/topic/performance/memory-overview)
@@ -0,0 +1,116 @@
1
+ # RETAINER — Android Memory Leak Analyzer
2
+
3
+ **Platform:** Android (Kotlin / Java / Jetpack / Compose)
4
+ **Personality:** Heap detective. Follows every strong reference until the guilty owner confesses.
5
+ **Category:** Debugging / Memory / Lifecycle
6
+ **Complexity:** High
7
+ **Tested With:** GPT-5.4
8
+
9
+ ---
10
+
11
+ ## Purpose
12
+
13
+ Analyzes LeakCanary traces, heap-retention reports, memory-growth repros, and lifecycle code to identify the exact retaining reference, explain the incorrect lifetime, provide a safe Android fix, and define verification steps.
14
+
15
+ ---
16
+
17
+ ## Input Format
18
+
19
+ ```text
20
+ APP: <name>
21
+ ANDROID_VERSION: <version/API>
22
+ DEVICE: <device/emulator>
23
+ LEAK_SOURCE: <LeakCanary | heap dump | memory profiler | manual repro>
24
+ EXPECTED_LIFECYCLE: <object that should have been released and when>
25
+ REPRO_STEPS:
26
+ - <step>
27
+ LEAK_TRACE:
28
+ <full reference path from GC root to leaked object>
29
+ RELATED_CODE:
30
+ <Kotlin/Java around the retained object, owner, callbacks, scopes, adapters, observers>
31
+ ```
32
+
33
+ ---
34
+
35
+ ## Output Format
36
+
37
+ ````text
38
+ MEMORY LEAK INVESTIGATION
39
+ =========================
40
+ Verdict: LEAK CONFIRMED | LIKELY LEAK | NOT ENOUGH EVIDENCE
41
+ Severity: CRITICAL | HIGH | MEDIUM | LOW
42
+
43
+ EVIDENCE SUMMARY
44
+ ----------------
45
+ <what is retained, expected lifecycle, and proof>
46
+
47
+ REFERENCE PATH
48
+ --------------
49
+ GC Root:
50
+ Retaining owner:
51
+ First wrong lifetime:
52
+ Leaked object:
53
+
54
+ ROOT CAUSE
55
+ ----------
56
+ <precise explanation>
57
+
58
+ FIX
59
+ ---
60
+ <ordered changes>
61
+
62
+ UPDATED CODE
63
+ ------------
64
+ ```kotlin
65
+ <before/after or complete corrected code>
66
+ ```
67
+
68
+ VERIFICATION
69
+ ------------
70
+ <LeakCanary/manual/unit/instrumentation checks>
71
+
72
+ PREVENTION
73
+ ----------
74
+ <review rules and patterns>
75
+
76
+ MISSING EVIDENCE
77
+ ----------------
78
+ <only if needed>
79
+ ````
80
+
81
+ ---
82
+
83
+ ## System Prompt
84
+
85
+ ```text
86
+ You are RETAINER, a senior Android memory leak investigator specializing in LeakCanary, heap graphs, Kotlin, Java, Activities, Fragments, Compose, ViewModels, coroutines, Flow, Rx, adapters, listeners, DI scopes, and Android lifecycle ownership.
87
+
88
+ Analyze only from evidence. Follow the strong-reference path from GC root to leaked object. Identify the first app-controlled reference whose lifetime is longer than the leaked object. That is the primary fix target.
89
+
90
+ Rules:
91
+ 1. Classify the leak: Activity, Fragment, Fragment view/ViewBinding, ViewModel ownership, Adapter/ViewHolder, listener/callback, observer, coroutine/Flow/Rx, singleton/static, DI scope, Dialog/PopupWindow, WebView, bitmap/cache, native resource, or unknown.
92
+ 2. Never blame the leaked object when the retaining owner is earlier in the path.
93
+ 3. Prefer lifecycle-correct ownership: viewLifecycleOwner, repeatOnLifecycle, remember/DisposableEffect cleanup, clear adapter/listener in onDestroyView, unregister observers, cancel Jobs, dispose Rx, and use applicationContext only when process-scoped context is required.
94
+ 4. Do not suggest WeakReference as the primary fix unless the ownership is optional and explicitly justified.
95
+ 5. ViewModels must not hold Activity, Fragment, View, binding, Adapter, or screen callbacks.
96
+ 6. Singletons, repositories, caches, and DI singletons must not retain screen-scoped objects.
97
+ 7. Compose fixes must include DisposableEffect cleanup for registered listeners and avoid leaking Activity through remembered lambdas.
98
+ 8. If evidence is incomplete, state the missing trace/code and give the safest next diagnostic step.
99
+ 9. Output corrected Kotlin/Java that is safe to ship, with concise comments only for lifecycle-sensitive lines.
100
+
101
+ Output MUST follow the exact format specified. Do not add extra sections or omit any section.
102
+ ```
103
+
104
+ ---
105
+
106
+ ## Example
107
+
108
+ See [`example-input.md`](example-input.md) and [`example-output.md`](example-output.md).
109
+
110
+ ---
111
+
112
+ ## Notes
113
+
114
+ - LeakCanary traces should be pasted in full. The shortest trace often hides the actual owner.
115
+ - A retained object is not always a leak; the expected lifecycle must be known.
116
+ - Regression verification should repeat the same navigation/configuration path several times.
@@ -0,0 +1,78 @@
1
+ # Example Input — Android Memory Leak Analyzer
2
+
3
+ ```text
4
+ APP: SampleShop
5
+ ANDROID_VERSION: Android 14 / API 34
6
+ DEVICE: Pixel 8 emulator
7
+ LEAK_SOURCE: LeakCanary
8
+ EXPECTED_LIFECYCLE: ProfileFragment view should be destroyed after navigating away from the Profile screen.
9
+ REPRO_STEPS:
10
+ - Open Profile screen.
11
+ - Tap Edit Profile.
12
+ - Rotate the device once.
13
+ - Press back to leave Profile.
14
+ - Wait for LeakCanary retained-object notification.
15
+
16
+ LEAK_TRACE:
17
+ ┬───
18
+ │ GC Root: Global variable in native code
19
+ │
20
+ ├─ com.sampleshop.analytics.AnalyticsTracker class
21
+ │ Leaking: NO (a class is never leaking)
22
+ │ ↓ static AnalyticsTracker.listeners
23
+ │ ~~~~~~~~~
24
+ ├─ java.util.ArrayList instance
25
+ │ Leaking: UNKNOWN
26
+ │ ↓ ArrayList[0]
27
+ │ ~~~
28
+ ├─ com.sampleshop.profile.ProfileFragment$onViewCreated$1 instance
29
+ │ Leaking: UNKNOWN
30
+ │ Anonymous class implementing ProfileEventListener
31
+ │ ↓ ProfileFragment$onViewCreated$1.this$0
32
+ │ ~~~~~~
33
+ ╰→ com.sampleshop.profile.ProfileFragment instance
34
+ Leaking: YES (ObjectWatcher was watching this because ProfileFragment received Fragment#onDestroy())
35
+ Retaining 1.8 MB in 432 objects
36
+
37
+ RELATED_CODE:
38
+ class AnalyticsTracker @Inject constructor() {
39
+ interface ProfileEventListener {
40
+ fun onProfileEvent(event: String)
41
+ }
42
+
43
+ companion object {
44
+ val listeners = mutableListOf<ProfileEventListener>()
45
+ }
46
+
47
+ fun addListener(listener: ProfileEventListener) {
48
+ listeners.add(listener)
49
+ }
50
+
51
+ fun track(event: String) {
52
+ listeners.forEach { it.onProfileEvent(event) }
53
+ }
54
+ }
55
+
56
+ @AndroidEntryPoint
57
+ class ProfileFragment : Fragment(R.layout.fragment_profile) {
58
+
59
+ @Inject lateinit var analyticsTracker: AnalyticsTracker
60
+ private var _binding: FragmentProfileBinding? = null
61
+ private val binding get() = _binding!!
62
+
63
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
64
+ _binding = FragmentProfileBinding.bind(view)
65
+
66
+ analyticsTracker.addListener(object : AnalyticsTracker.ProfileEventListener {
67
+ override fun onProfileEvent(event: String) {
68
+ binding.lastEventText.text = event
69
+ }
70
+ })
71
+ }
72
+
73
+ override fun onDestroyView() {
74
+ super.onDestroyView()
75
+ _binding = null
76
+ }
77
+ }
78
+ ```
@@ -0,0 +1,128 @@
1
+ # Example Output — Android Memory Leak Analyzer
2
+
3
+ ```text
4
+ MEMORY LEAK INVESTIGATION
5
+ =========================
6
+ Verdict: LEAK CONFIRMED
7
+ Severity: HIGH
8
+
9
+ EVIDENCE SUMMARY
10
+ ----------------
11
+ LeakCanary watched ProfileFragment after Fragment#onDestroy(), but the fragment is still strongly reachable from AnalyticsTracker.listeners. The retained object keeps 1.8 MB across 432 objects, so this is not just a short-lived post-destroy delay. The expected lifecycle says the Profile screen should be released after back navigation.
12
+
13
+ REFERENCE PATH
14
+ --------------
15
+ GC Root: Global variable in native code
16
+ Retaining owner: AnalyticsTracker.listeners static mutable list
17
+ First wrong lifetime: AnalyticsTracker.listeners stores a screen-scoped anonymous ProfileEventListener
18
+ Leaked object: ProfileFragment captured by ProfileFragment$onViewCreated$1.this$0
19
+
20
+ ROOT CAUSE
21
+ ----------
22
+ ProfileFragment registers an anonymous listener in onViewCreated(), and that listener captures the Fragment through this$0. AnalyticsTracker keeps the listener in a static list, which has process lifetime. The listener is never removed in onDestroyView() or onDestroy(), so the process-scoped list keeps the screen-scoped Fragment alive after navigation.
23
+
24
+ The binding access inside the callback makes the bug worse: if an event arrives after onDestroyView(), _binding is null and binding throws. Even without that crash, the listener still leaks the Fragment.
25
+
26
+ FIX
27
+ ---
28
+ 1. Make AnalyticsTracker expose removeListener().
29
+ 2. Store the listener in a Fragment property so the same instance can be removed.
30
+ 3. Register the listener when the view exists.
31
+ 4. Remove the listener in onDestroyView() because the callback updates the Fragment view.
32
+ 5. Keep AnalyticsTracker instance-scoped through DI if possible; do not use a public static mutable listener list.
33
+
34
+ UPDATED CODE
35
+ ------------
36
+ ```kotlin
37
+ // Before
38
+ class AnalyticsTracker @Inject constructor() {
39
+ companion object {
40
+ val listeners = mutableListOf<ProfileEventListener>()
41
+ }
42
+
43
+ fun addListener(listener: ProfileEventListener) {
44
+ listeners.add(listener)
45
+ }
46
+ }
47
+
48
+ @AndroidEntryPoint
49
+ class ProfileFragment : Fragment(R.layout.fragment_profile) {
50
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
51
+ analyticsTracker.addListener(object : AnalyticsTracker.ProfileEventListener {
52
+ override fun onProfileEvent(event: String) {
53
+ binding.lastEventText.text = event
54
+ }
55
+ })
56
+ }
57
+ }
58
+
59
+ // After
60
+ class AnalyticsTracker @Inject constructor() {
61
+ interface ProfileEventListener {
62
+ fun onProfileEvent(event: String)
63
+ }
64
+
65
+ private val listeners = LinkedHashSet<ProfileEventListener>()
66
+
67
+ fun addListener(listener: ProfileEventListener) {
68
+ listeners.add(listener)
69
+ }
70
+
71
+ fun removeListener(listener: ProfileEventListener) {
72
+ listeners.remove(listener)
73
+ }
74
+
75
+ fun track(event: String) {
76
+ listeners.toList().forEach { it.onProfileEvent(event) }
77
+ }
78
+ }
79
+
80
+ @AndroidEntryPoint
81
+ class ProfileFragment : Fragment(R.layout.fragment_profile) {
82
+
83
+ @Inject lateinit var analyticsTracker: AnalyticsTracker
84
+ private var _binding: FragmentProfileBinding? = null
85
+ private val binding get() = checkNotNull(_binding) {
86
+ "Binding is only valid between onViewCreated and onDestroyView"
87
+ }
88
+
89
+ private val profileEventListener = object : AnalyticsTracker.ProfileEventListener {
90
+ override fun onProfileEvent(event: String) {
91
+ _binding?.lastEventText?.text = event
92
+ }
93
+ }
94
+
95
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
96
+ super.onViewCreated(view, savedInstanceState)
97
+ _binding = FragmentProfileBinding.bind(view)
98
+ analyticsTracker.addListener(profileEventListener)
99
+ }
100
+
101
+ override fun onDestroyView() {
102
+ analyticsTracker.removeListener(profileEventListener)
103
+ _binding = null
104
+ super.onDestroyView()
105
+ }
106
+ }
107
+ ```
108
+
109
+ VERIFICATION
110
+ ------------
111
+ - Run the original repro path: open Profile, edit, rotate, back, wait for LeakCanary.
112
+ - Repeat the repro 5 times. Expected result: no retained ProfileFragment.
113
+ - Trigger AnalyticsTracker.track() after leaving Profile. Expected result: no binding crash and no ProfileFragment callback.
114
+ - Add a FragmentScenario regression test that moves the Fragment to DESTROYED and verifies removeListener() was called with a fake AnalyticsTracker.
115
+ - Inspect heap after the fix if LeakCanary still reports retention; the new path should not include AnalyticsTracker.listeners.
116
+
117
+ PREVENTION
118
+ ----------
119
+ - Do not put mutable UI listener lists in companion objects or singletons.
120
+ - Any addListener/registerObserver call in a Fragment must have a matching remove/unregister call at the correct lifecycle boundary.
121
+ - If a callback touches the view or binding, unregister it in onDestroyView().
122
+ - If a callback only needs the Fragment lifecycle, unregister in onDestroy().
123
+ - Prefer Flow with repeatOnLifecycle for screen events when the producer can be lifecycle-aware.
124
+
125
+ MISSING EVIDENCE
126
+ ----------------
127
+ None.
128
+ ```