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,225 @@
1
+ # Example Output — Android Crash Analyzer
2
+
3
+ This is the agent's full response to the crash log in [`example-input.md`](example-input.md).
4
+
5
+ ---
6
+
7
+ ## Crash Summary
8
+
9
+ `IllegalStateException: Fragment ProfileFragment not attached to a context` thrown at `ProfileFragment.kt:124` inside a LiveData observer callback. The crash occurs when a photo upload completes (or fails) after the user has already navigated away from the Profile screen, causing the `viewModelScope` coroutine to post to `_uploadState`, which triggers the still-active LiveData observer, which calls `requireContext()` on a detached Fragment. **HIGH** severity — intermittent, proportional to upload latency and user navigation speed.
10
+
11
+ ---
12
+
13
+ ## Root Cause
14
+
15
+ There are two compounding bugs that together produce this crash:
16
+
17
+ **Bug 1 — Observer registered with wrong LifecycleOwner (`this` vs `viewLifecycleOwner`):**
18
+ ```kotlin
19
+ viewModel.uploadState.observe(this) { state -> ... }
20
+ // ^^^^
21
+ // 'this' = the Fragment's lifecycle, which starts at onCreate and ends at onDestroy.
22
+ // The Fragment's VIEW lifecycle (onCreateView → onDestroyView) is shorter.
23
+ // When the user navigates back, the view is destroyed but the Fragment object
24
+ // may still exist in the back stack. The observer remains active.
25
+ ```
26
+
27
+ **Bug 2 — `requireContext()` called inside that active observer:**
28
+ ```kotlin
29
+ private fun showUploadSuccess(photoUrl: String) {
30
+ val ctx = requireContext() // throws if Fragment is not attached to an Activity
31
+ ```
32
+
33
+ When the upload finishes on the `viewModelScope` coroutine (which survives navigation), it posts `UploadState.Success` to `_uploadState`. LiveData dispatches this on the Main thread. The observer fires — because it was registered with `this` (Fragment), not `viewLifecycleOwner` (view). Inside the callback, `requireContext()` throws because the Fragment has been detached from its Activity during back-navigation.
34
+
35
+ **Secondary bug — `Context` passed into `ViewModel`:**
36
+ ```kotlin
37
+ fun uploadPhoto(context: Context, bitmap: Bitmap) { ... }
38
+ ```
39
+ The ViewModel stores or uses a `Context` from the Fragment. If the repository retains this reference (e.g., for file access), it creates a memory leak tying the Fragment to the ViewModel.
40
+
41
+ ---
42
+
43
+ ## Why This Happens
44
+
45
+ Think of a Fragment as having two lifecycles: the Fragment itself (like the employee) and its View (like the employee's desk). When you use `observe(this)`, you're saying "keep watching until the employee leaves the company." When you use `observe(viewLifecycleOwner)`, you're saying "keep watching until the employee clears their desk."
46
+
47
+ When the user navigates back, the desk is cleared (`onDestroyView`) but the employee is still around (the Fragment stays in the back stack). Your upload finishes, and it tries to show a Snackbar — but the desk is gone. `requireContext()` is like asking "where's my desk?" when there is no desk anymore.
48
+
49
+ The fix is always to watch with `viewLifecycleOwner`, so the observer self-cancels the moment the view is destroyed — no desk, no watch.
50
+
51
+ ---
52
+
53
+ ## Risk Level
54
+
55
+ **HIGH**
56
+ Reproducibility: **Intermittent** — triggered when:
57
+ - Upload takes longer than the user's back-navigation (more likely on slow networks)
58
+ - Device has background CPU throttling (common on budget devices)
59
+
60
+ Affected users: proportional to upload success rate on slow connections × navigation-back rate. In a social app with profile photo uploads, expect this crash in the top 10 Crashlytics issues within weeks of shipping.
61
+
62
+ ---
63
+
64
+ ## Recommended Fix
65
+
66
+ **Step 1 — Change observer to use `viewLifecycleOwner`**
67
+
68
+ This is the primary fix. The observer self-cancels when the view is destroyed, preventing the callback from ever reaching `showUploadSuccess` after navigation.
69
+
70
+ **Step 2 — Remove `requireContext()` from the observer callback**
71
+
72
+ The Fragment always has access to its context through the view binding root (`binding.root.context`) without calling `requireContext()`. This is safer because `binding` is only accessible when the view exists.
73
+
74
+ **Step 3 — Remove `Context` parameter from `ViewModel.uploadPhoto()`**
75
+
76
+ ViewModels must never hold Activity or Fragment context. Use `application.applicationContext` (inject `Application` via Hilt's `@HiltViewModel`) for any context-dependent work in the ViewModel or Repository.
77
+
78
+ **Step 4 — Cancel the upload when the user navigates away (optional but recommended)**
79
+
80
+ If you want uploads to stop when the user leaves, cancel the coroutine Job and expose cancellation via a `cancelUpload()` function.
81
+
82
+ ---
83
+
84
+ ## Updated Code
85
+
86
+ ```kotlin
87
+ // ProfileFragment.kt — AFTER
88
+
89
+ class ProfileFragment : Fragment(R.layout.fragment_profile) {
90
+
91
+ private val viewModel: ProfileViewModel by viewModels()
92
+ private var _binding: FragmentProfileBinding? = null
93
+ private val binding get() = _binding!!
94
+
95
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
96
+ super.onViewCreated(view, savedInstanceState)
97
+ _binding = FragmentProfileBinding.bind(view)
98
+
99
+ // FIX 1: Use viewLifecycleOwner, not 'this'.
100
+ // The observer now self-cancels when onDestroyView() fires.
101
+ // 'uploadState' is now a StateFlow — collected with repeatOnLifecycle
102
+ // to guarantee cancellation and avoid the race window.
103
+ viewLifecycleOwner.lifecycleScope.launch {
104
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
105
+ viewModel.uploadState.collect { state ->
106
+ when (state) {
107
+ is UploadState.Success -> showUploadSuccess(state.photoUrl)
108
+ is UploadState.Error -> showError(state.message)
109
+ UploadState.Loading -> binding.uploadProgress.isVisible = true
110
+ UploadState.Idle -> binding.uploadProgress.isVisible = false
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ binding.uploadButton.setOnClickListener {
117
+ // FIX 3: No longer passes Context to the ViewModel.
118
+ viewModel.uploadPhoto(getSelectedBitmap())
119
+ }
120
+ }
121
+
122
+ private fun showUploadSuccess(photoUrl: String) {
123
+ // FIX 2: binding.root.context is always valid here because
124
+ // repeatOnLifecycle guarantees this block only runs while STARTED.
125
+ // No requireContext() needed.
126
+ binding.uploadProgress.isVisible = false
127
+ Snackbar.make(binding.root, "Photo updated!", Snackbar.LENGTH_SHORT).show()
128
+ Glide.with(binding.root.context).load(photoUrl).into(binding.avatarImage)
129
+ }
130
+
131
+ override fun onDestroyView() {
132
+ super.onDestroyView()
133
+ _binding = null
134
+ }
135
+ }
136
+
137
+ // ProfileViewModel.kt — AFTER
138
+
139
+ @HiltViewModel
140
+ class ProfileViewModel @Inject constructor(
141
+ // FIX 3: Inject Application for context-dependent work.
142
+ // Application context does NOT leak — it lives as long as the process.
143
+ private val application: Application,
144
+ private val photoRepository: PhotoRepository
145
+ ) : ViewModel() {
146
+
147
+ // Migrated from MutableLiveData to MutableStateFlow.
148
+ // StateFlow + collectAsStateWithLifecycle/repeatOnLifecycle is the modern pattern.
149
+ private val _uploadState = MutableStateFlow<UploadState>(UploadState.Idle)
150
+ val uploadState: StateFlow<UploadState> = _uploadState.asStateFlow()
151
+
152
+ private var uploadJob: Job? = null
153
+
154
+ fun uploadPhoto(bitmap: Bitmap) {
155
+ // Cancel any in-flight upload before starting a new one.
156
+ uploadJob?.cancel()
157
+ uploadJob = viewModelScope.launch {
158
+ _uploadState.value = UploadState.Loading
159
+ try {
160
+ // Repository now receives applicationContext — no leak.
161
+ val url = photoRepository.uploadPhoto(application.applicationContext, bitmap)
162
+ _uploadState.value = UploadState.Success(url)
163
+ } catch (e: CancellationException) {
164
+ // User navigated away and the coroutine was cancelled — do not emit Error.
165
+ _uploadState.value = UploadState.Idle
166
+ throw e // Always re-throw CancellationException
167
+ } catch (e: Exception) {
168
+ _uploadState.value = UploadState.Error(e.message ?: "Upload failed")
169
+ }
170
+ }
171
+ }
172
+
173
+ fun cancelUpload() {
174
+ uploadJob?.cancel()
175
+ _uploadState.value = UploadState.Idle
176
+ }
177
+ }
178
+
179
+ // UploadState.kt — sealed class for type-safe state
180
+
181
+ sealed class UploadState {
182
+ object Idle : UploadState()
183
+ object Loading : UploadState()
184
+ data class Success(val photoUrl: String) : UploadState()
185
+ data class Error(val message: String) : UploadState()
186
+ }
187
+ ```
188
+
189
+ ---
190
+
191
+ ## Edge Cases
192
+
193
+ - **ViewPager2 tabs:** Fragments in a ViewPager2 have their views destroyed and recreated when the user swipes away. Using `observe(this)` accumulates a new observer on every tab-return. With the `viewLifecycleOwner` fix, each view creation registers exactly one observer.
194
+
195
+ - **Bottom navigation with `FragmentManager` backstack:** A Fragment that is added to the back stack retains its Fragment instance. After `onDestroyView()`, any observer registered with `this` continues firing. The fix eliminates this entirely.
196
+
197
+ - **Configuration change mid-upload:** The ViewModel survives rotation, but the Fragment is recreated. Without the fix, both the old and new Fragment's observers fire. With the fix, only the new Fragment's observer is active.
198
+
199
+ - **Deep-link navigation to ProfileFragment:** If a deep link arrives while a previous ProfileFragment is still alive in the back stack, the upload from the back-stack Fragment can still post to the ViewModel's StateFlow — but with `repeatOnLifecycle(STARTED)`, only the foreground Fragment collects.
200
+
201
+ - **Application context for Glide:** `Glide.with(context)` where `context` is an Activity or Fragment that has been destroyed will log a warning and sometimes throw. Always use `Glide.with(applicationContext)` for background or long-lived loads.
202
+
203
+ ---
204
+
205
+ ## Testing Checklist
206
+
207
+ - [ ] Open Profile screen → start a photo upload → immediately press Back → confirm **no crash** in logcat or Crashlytics
208
+ - [ ] Open Profile screen → start a photo upload → rotate the device mid-upload → upload completes → confirm result appears correctly in the new Fragment instance
209
+ - [ ] Open Profile screen → start a photo upload → switch to another tab (bottom nav) → switch back → confirm upload state is restored correctly
210
+ - [ ] Open Profile screen → start a photo upload → throttle network to 3G (Developer Options → Network speed) → press Back → confirm no crash
211
+ - [ ] Run the app with LeakCanary enabled → complete a photo upload → navigate away → confirm **no Fragment or Activity leak** is reported
212
+ - [ ] Enable "Don't keep activities" in Developer Options → start upload → press Home → return to app → confirm no crash and UI state is correct
213
+ - [ ] Verify that calling `uploadPhoto()` twice rapidly does not produce two concurrent uploads (second call cancels the first)
214
+
215
+ ---
216
+
217
+ ## Prevention Tips
218
+
219
+ - **Rule:** Always use `viewLifecycleOwner` when observing LiveData or collecting Flow inside a Fragment. Add this to your code review checklist.
220
+ - **Lint:** Enable the AndroidX Lint rule `FragmentLiveDataObserve` — it flags `observe(this)` inside Fragments automatically.
221
+ - **Migration:** Replace `MutableLiveData` + `observe()` with `MutableStateFlow` + `repeatOnLifecycle(STARTED)` + `collect {}` in all new Fragments. The structured concurrency model eliminates the observer lifecycle class entirely.
222
+ - **Architecture:** ViewModels must never accept `Activity`, `Fragment`, or `View` as parameters. Use `AndroidViewModel` for Application context, or inject `@ApplicationContext` via Hilt.
223
+ - **CancellationException:** Never catch `CancellationException` generically. Use `catch (e: Exception) { if (e is CancellationException) throw e; ... }` or separate `catch` blocks.
224
+ - **Testing:** Add a `WhenFragmentDetachedTest` to your test suite that verifies ViewModel state changes after `fragmentScenario.recreate()` do not crash the Fragment.
225
+ - **Static analysis:** Add `detekt` or `ktlint` rules banning `GlobalScope` and `requireContext()` inside observer callbacks.
@@ -0,0 +1,78 @@
1
+ # Android ANR Investigation Agent
2
+
3
+ > ANR traces, Play Console clusters, and thread dumps -> root cause, production fix, and verification plan.
4
+
5
+ ---
6
+
7
+ ## What It Does
8
+
9
+ - Reads Play Console ANR clusters, Crashlytics ANR reports, `traces.txt`, bug reports, and `ApplicationExitInfo`
10
+ - Classifies input dispatch, service, foreground service, broadcast receiver, JobService, startup, binder, lock, and deadlock ANRs
11
+ - Finds the main thread state and any thread that owns the dependency blocking it
12
+ - Detects main-thread database, disk, network, startup, rendering, SDK, and synchronization bottlenecks
13
+ - Produces production-ready Kotlin fixes with structured concurrency and lifecycle-safe ownership
14
+ - Defines verification steps using StrictMode, system traces, macrobenchmarks, low-end devices, and Play Console monitoring
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 ANR-style trace 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/anr-investigation/agent.md.
32
+
33
+ APP_VERSION: 4.8.0 (480)
34
+ ANDROID_VERSION: 14
35
+ DEVICE: Pixel 6a
36
+ ANR_SOURCE: Play Console
37
+ ANR_REASON:
38
+ Input dispatching timed out
39
+ USER_ACTION:
40
+ Cold launch, then tapped Sign in.
41
+ TRACE:
42
+ <paste full ANR trace or thread dump>
43
+ RELATED_CODE:
44
+ <paste Activity, ViewModel, repository, service, receiver, or database code>
45
+ ```
46
+
47
+ Install only this agent:
48
+
49
+ ```bash
50
+ npx mobile-ai-agents add agent anr-investigation
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Design Principles
56
+
57
+ - The main thread is often the victim, not the cause.
58
+ - A high-confidence ANR report needs the main thread plus lock owner, binder peer, or blocking dependency.
59
+ - Do not wrap unknown code in `Dispatchers.IO` blindly.
60
+ - Keep receiver, service, and job lifecycle callbacks short.
61
+ - Reduce lock scope instead of moving lock contention to another thread.
62
+ - Every fix needs a reproducible timeout test or measurable performance check.
63
+
64
+ ---
65
+
66
+ ## Related
67
+
68
+ - [`skills/android/anr-investigation.md`](../../../skills/android/anr-investigation.md)
69
+ - [`agents/android/android-crash-analyzer`](../android-crash-analyzer/)
70
+ - [`agents/android/code-reviewer`](../code-reviewer/)
71
+ - [`agents/android/axiom`](../axiom/)
72
+
73
+ ---
74
+
75
+ ## Official References
76
+
77
+ - [Android Developers: ANRs](https://developer.android.com/topic/performance/vitals/anr)
78
+ - [Android Developers: Diagnose performance issues](https://developer.android.com/topic/performance)
@@ -0,0 +1,117 @@
1
+ # FREEZE — Android ANR Investigation Agent
2
+
3
+ **Platform:** Android (Kotlin / Java / Jetpack / Compose)
4
+ **Personality:** Main-thread forensic analyst. Every freeze has a blocking chain.
5
+ **Category:** Debugging / Performance / Reliability
6
+ **Complexity:** High
7
+ **Tested With:** GPT-5.4
8
+
9
+ ---
10
+
11
+ ## Purpose
12
+
13
+ Analyzes Android ANR traces, Play Console clusters, Crashlytics ANRs, thread dumps, bug reports, `ApplicationExitInfo`, and related source code to identify the blocking chain, root cause, smallest safe fix, and verification plan.
14
+
15
+ ---
16
+
17
+ ## Input Format
18
+
19
+ ```text
20
+ APP_VERSION: <version/build>
21
+ ANDROID_VERSION: <version/API or unknown>
22
+ DEVICE: <device/model or unknown>
23
+ ANR_SOURCE: <Play Console | Crashlytics | traces.txt | bugreport | ApplicationExitInfo>
24
+ ANR_REASON:
25
+ <timeout reason, cluster title, or user-perceived ANR reason>
26
+ USER_ACTION:
27
+ <what the user was doing>
28
+ TRACE:
29
+ <full ANR trace, main thread, and related worker/binder/lock owner threads>
30
+ RELATED_CODE:
31
+ <Activity, ViewModel, repository, service, receiver, worker, database, SDK init, or Compose code>
32
+ ```
33
+
34
+ ---
35
+
36
+ ## Output Format
37
+
38
+ ````text
39
+ ANDROID ANR INVESTIGATION
40
+ =========================
41
+ Result: CONFIRMED | LIKELY | INCONCLUSIVE
42
+ Confidence: HIGH | MEDIUM | LOW
43
+ ANR Type:
44
+ Trigger:
45
+
46
+ EVIDENCE SUMMARY
47
+ ----------------
48
+ | Evidence | Observation |
49
+ |---|---|
50
+
51
+ THREAD ANALYSIS
52
+ ---------------
53
+ | Thread | State | Waiting On / Work | Significance |
54
+ |---|---|---|---|
55
+
56
+ ROOT CAUSE
57
+ ----------
58
+ <proven causal chain, or what remains unknown>
59
+
60
+ FIX
61
+ ---
62
+ <smallest safe fix>
63
+
64
+ UPDATED CODE
65
+ ------------
66
+ ```kotlin
67
+ <corrected code, or "Not available without related source">
68
+ ```
69
+
70
+ VERIFICATION
71
+ ------------
72
+ - <step>
73
+
74
+ MISSING EVIDENCE
75
+ ----------------
76
+ - <item or "None">
77
+
78
+ NEXT ACTION
79
+ -----------
80
+ <one concrete action>
81
+ ````
82
+
83
+ ---
84
+
85
+ ## System Prompt
86
+
87
+ ```text
88
+ You are FREEZE, a senior Android ANR investigator specializing in Kotlin, Java, Android vitals, Play Console ANR clusters, traces.txt, ApplicationExitInfo, thread dumps, startup performance, Room/SQLite, coroutines, binder, services, broadcast receivers, JobService, Compose, locks, and deadlocks.
89
+
90
+ Analyze evidence before giving fixes. Locate the main thread, record its state, then identify the dependency that prevents it from processing input, lifecycle callbacks, broadcasts, services, or jobs. The top app frame can be a victim; find the blocking owner when locks, binder, futures, joins, latches, or monitors appear.
91
+
92
+ Rules:
93
+ 1. Classify the ANR as input dispatch timeout, service timeout, foreground service start timeout, broadcast receiver timeout, JobService timeout, main-thread I/O/computation, binder stall, lock contention, deadlock, slow startup, rendering/layout stall, or unknown.
94
+ 2. For BLOCKED main threads, identify the monitor/lock and owner thread. For WAITING, identify Future.get, CountDownLatch.await, join, runBlocking, wait, semaphore, mutex, or synchronous callback. For NATIVE, inspect binder, file, database, graphics, or native SDK frames.
95
+ 3. Check related code for main-thread Room/SQLite, disk, network, SharedPreferences commit, JSON parsing, bitmap work, crypto, eager DI, ContentProvider/Application startup, synchronous SDK init, long receiver/service/job callbacks, and broad synchronized sections.
96
+ 4. Propose the smallest safe production fix. Use structured coroutines and appropriate dispatchers, WorkManager for deferrable persistent work, shorter service/receiver/job callbacks, reduced lock scope, async SDK initialization, or startup deferral as evidence requires.
97
+ 5. Do not blindly suggest Dispatchers.IO when the cause is lock contention, binder stall, deadlock, or lifecycle misuse.
98
+ 6. Include corrected Kotlin/Java when source is present. Preserve cancellation, lifecycle, and error handling.
99
+ 7. Include verification with reproduction, low-end device testing, StrictMode or tracing, Macrobenchmark/startup measurement when relevant, and Play Console ANR cluster monitoring.
100
+ 8. If trace evidence is incomplete, mark confidence LOW or MEDIUM and list exactly what is missing.
101
+
102
+ Output MUST follow the exact format specified. Do not add extra sections or omit any section.
103
+ ```
104
+
105
+ ---
106
+
107
+ ## Example
108
+
109
+ See [`example-input.md`](example-input.md) and [`example-output.md`](example-output.md).
110
+
111
+ ---
112
+
113
+ ## Notes
114
+
115
+ - ANR reports need full thread context; a single main-thread frame is rarely enough.
116
+ - Use the dedicated `/anr-investigation` skill for smaller inline analysis.
117
+ - Pair with PERF for startup traces and with Mobile MCP QA for device reproduction.
@@ -0,0 +1,62 @@
1
+ # Example Input — Android ANR Investigation Agent
2
+
3
+ ```text
4
+ APP_VERSION: 4.8.0 (480)
5
+ ANDROID_VERSION: Android 14 / API 34
6
+ DEVICE: Pixel 6a
7
+ ANR_SOURCE: Play Console
8
+ ANR_REASON:
9
+ Input dispatching timed out. The application was too busy to process user input.
10
+ USER_ACTION:
11
+ Cold launch, then tapped Sign in while session refresh was running.
12
+
13
+ TRACE:
14
+ "main" prio=5 tid=1 BLOCKED
15
+ at com.sampleshop.session.SessionStore.read(SessionStore.kt:42)
16
+ - waiting to lock <0x1234> (a com.sampleshop.session.SessionStore) held by thread 18
17
+ at com.sampleshop.auth.AuthRepository.currentSession(AuthRepository.kt:27)
18
+ at com.sampleshop.ui.MainActivity.onCreate(MainActivity.kt:31)
19
+ at android.app.Activity.performCreate(Activity.java:8595)
20
+
21
+ "DefaultDispatcher-worker-3" prio=5 tid=18 RUNNABLE
22
+ at java.net.SocketInputStream.socketRead0(Native Method)
23
+ at okhttp3.internal.connection.RealCall.execute(RealCall.kt:153)
24
+ at com.sampleshop.session.SessionApi.refreshTokenBlocking(SessionApi.kt:21)
25
+ at com.sampleshop.session.SessionStore.refresh(SessionStore.kt:68)
26
+ - locked <0x1234> (a com.sampleshop.session.SessionStore)
27
+ at com.sampleshop.session.SessionRepository.refresh(SessionRepository.kt:51)
28
+
29
+ RELATED_CODE:
30
+ class SessionStore(
31
+ private val api: SessionApi
32
+ ) {
33
+ private var cachedSession: Session? = null
34
+
35
+ @Synchronized
36
+ fun read(): Session? = cachedSession
37
+
38
+ @Synchronized
39
+ fun refresh(): Session {
40
+ val response = api.refreshTokenBlocking()
41
+ val session = response.toSession()
42
+ cachedSession = session
43
+ return session
44
+ }
45
+ }
46
+
47
+ class AuthRepository(
48
+ private val sessionStore: SessionStore
49
+ ) {
50
+ fun currentSession(): Session? = sessionStore.read()
51
+ }
52
+
53
+ class MainActivity : ComponentActivity() {
54
+ @Inject lateinit var authRepository: AuthRepository
55
+
56
+ override fun onCreate(savedInstanceState: Bundle?) {
57
+ super.onCreate(savedInstanceState)
58
+ val session = authRepository.currentSession()
59
+ setContent { App(session = session) }
60
+ }
61
+ }
62
+ ```
@@ -0,0 +1,103 @@
1
+ # Example Output — Android ANR Investigation Agent
2
+
3
+ ````text
4
+ ANDROID ANR INVESTIGATION
5
+ =========================
6
+ Result: CONFIRMED
7
+ Confidence: HIGH
8
+ ANR Type: Lock contention causing input dispatch timeout
9
+ Trigger: Cold launch while session refresh holds the SessionStore monitor
10
+
11
+ EVIDENCE SUMMARY
12
+ ----------------
13
+ | Evidence | Observation |
14
+ |---|---|
15
+ | ANR reason | Input dispatching timed out |
16
+ | Main thread | BLOCKED in `SessionStore.read(SessionStore.kt:42)` |
17
+ | Lock | Main waits for monitor `0x1234` |
18
+ | Owner thread | `DefaultDispatcher-worker-3` owns `0x1234` |
19
+ | Owner work | Blocking OkHttp refresh inside `SessionStore.refresh()` |
20
+
21
+ THREAD ANALYSIS
22
+ ---------------
23
+ | Thread | State | Waiting On / Work | Significance |
24
+ |---|---|---|---|
25
+ | main | BLOCKED | Waiting for `SessionStore` monitor `0x1234` | Cannot finish launch or process input |
26
+ | DefaultDispatcher-worker-3 | RUNNABLE | Blocking network call while holding `SessionStore` monitor | Prevents main from reading cached session |
27
+
28
+ ROOT CAUSE
29
+ ----------
30
+ `SessionStore.refresh()` is synchronized and holds the object monitor while performing a blocking network request. During cold launch, `MainActivity.onCreate()` calls `AuthRepository.currentSession()`, which calls the synchronized `SessionStore.read()`. Because the worker thread owns the same monitor until the network request completes, the main thread blocks and cannot process the user's sign-in tap. That produces the input dispatch timeout.
31
+
32
+ FIX
33
+ ---
34
+ 1. Do not hold the `SessionStore` monitor during network I/O.
35
+ 2. Replace method-level `@Synchronized` with a narrow lock only around cached state reads/writes.
36
+ 3. Make refresh suspendable and perform the blocking call on `Dispatchers.IO` only for the network operation.
37
+ 4. Keep launch rendering independent from refresh completion; read cached state quickly and let the UI observe refresh state asynchronously.
38
+
39
+ UPDATED CODE
40
+ ------------
41
+ ```kotlin
42
+ class SessionStore(
43
+ private val api: SessionApi,
44
+ ) {
45
+ private val lock = Any()
46
+ private var cachedSession: Session? = null
47
+
48
+ fun read(): Session? = synchronized(lock) {
49
+ cachedSession
50
+ }
51
+
52
+ suspend fun refresh(): Session {
53
+ val session = withContext(Dispatchers.IO) {
54
+ api.refreshTokenBlocking().toSession()
55
+ }
56
+
57
+ synchronized(lock) {
58
+ cachedSession = session
59
+ }
60
+
61
+ return session
62
+ }
63
+ }
64
+
65
+ class AuthRepository(
66
+ private val sessionStore: SessionStore,
67
+ ) {
68
+ fun currentSession(): Session? = sessionStore.read()
69
+
70
+ suspend fun refreshSession(): Session = sessionStore.refresh()
71
+ }
72
+
73
+ class MainActivity : ComponentActivity() {
74
+ @Inject lateinit var authRepository: AuthRepository
75
+
76
+ override fun onCreate(savedInstanceState: Bundle?) {
77
+ super.onCreate(savedInstanceState)
78
+ val cachedSession = authRepository.currentSession()
79
+
80
+ setContent {
81
+ App(session = cachedSession)
82
+ }
83
+ }
84
+ }
85
+ ```
86
+
87
+ VERIFICATION
88
+ ------------
89
+ - Add a test fake that delays `refreshTokenBlocking()` by 10 seconds while launching `MainActivity`.
90
+ - Confirm `MainActivity.onCreate()` returns without waiting for refresh.
91
+ - Enable StrictMode and verify no main-thread disk/network violations during startup.
92
+ - Capture a Perfetto trace during cold launch and confirm the main thread is not BLOCKED on `SessionStore`.
93
+ - Run startup on a low-end Android device or emulator with CPU throttling.
94
+ - Monitor the Play Console ANR cluster after staged rollout.
95
+
96
+ MISSING EVIDENCE
97
+ ----------------
98
+ - None
99
+
100
+ NEXT ACTION
101
+ -----------
102
+ Replace method-level synchronization in `SessionStore` with narrow cached-state locking, then rerun the delayed-refresh cold-launch reproduction.
103
+ ````