mobile-debug-mcp 0.34.0 → 0.36.0

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 (500) hide show
  1. package/.agents/skills/mcp-builder/SKILL.md +44 -64
  2. package/.agents/skills/mcp-builder/references/build-flags.md +44 -43
  3. package/.agents/skills/mcp-builder/references/diagnostics-schema.md +4 -1
  4. package/.agents/skills/mcp-builder/references/toolchain-details.md +49 -62
  5. package/.agents/skills/spec-review/references/spec-review-checklist.md +7 -0
  6. package/.agents/skills/test-authoring/SKILL.md +7 -6
  7. package/.agents/skills/test-authoring/references/repo-test-layout.md +3 -1
  8. package/.agents/skills/test-authoring/references/test-authoring-workflow.md +16 -7
  9. package/.agents/skills/test-authoring/references/test-quality-checklist.md +2 -1
  10. package/.cursor-plugin/marketplace.json +1 -1
  11. package/.github/copilot-instructions.md +14 -36
  12. package/.github/workflows/ci.yml +4 -4
  13. package/AGENTS.md +12 -3
  14. package/CONTRIBUTING.md +79 -0
  15. package/README.md +218 -129
  16. package/agent-plugin/.antigravity-plugin/README.md +48 -0
  17. package/agent-plugin/.antigravity-plugin/plugin.json +18 -0
  18. package/agent-plugin/.claude-plugin/plugin.json +2 -2
  19. package/agent-plugin/.codex-plugin/plugin.json +1 -1
  20. package/agent-plugin/.cursor-plugin/plugin.json +2 -2
  21. package/agent-plugin/.mcp.json +1 -1
  22. package/agent-plugin/README.md +50 -11
  23. package/agent-plugin/cursor/.cursor-plugin/plugin.json +23 -0
  24. package/agent-plugin/cursor/LICENSE +201 -0
  25. package/agent-plugin/cursor/README.md +9 -0
  26. package/agent-plugin/cursor/skills/mobile-debug-mcp/SKILL.md +253 -0
  27. package/agent-plugin/mcp.json +1 -1
  28. package/agent-plugin/mcp_config.json +13 -0
  29. package/agent-plugin/plugin.json +1 -1
  30. package/agent-plugin/skills/mobile-debug-mcp/SKILL.md +62 -17
  31. package/dist/contracts/execution-controls.js +24 -0
  32. package/dist/contracts/selector.js +8 -0
  33. package/dist/contracts/target.js +15 -0
  34. package/dist/contracts/ui.js +1 -0
  35. package/dist/debugger/android.js +2 -1
  36. package/dist/debugger/diagnostics.js +54 -0
  37. package/dist/debugger/guard.js +4 -4
  38. package/dist/debugger/ios-worker.js +62 -0
  39. package/dist/debugger/ios.js +57 -0
  40. package/dist/debugger/protocol.js +33 -0
  41. package/dist/debugger/sessions.js +133 -74
  42. package/dist/debugger/worker.js +59 -46
  43. package/dist/interact/adjustment/adjustment-context.js +41 -0
  44. package/dist/interact/adjustment/adjustment-gestures.js +55 -0
  45. package/dist/interact/adjustment/adjustment-plan.js +18 -0
  46. package/dist/interact/adjustment/adjustment-resolution.js +127 -0
  47. package/dist/interact/adjustment/adjustment-result.js +19 -0
  48. package/dist/interact/adjustment/adjustment-semantic.js +64 -0
  49. package/dist/interact/adjustment/adjustment-verification.js +35 -0
  50. package/dist/interact/adjustment/adjustment.js +52 -0
  51. package/dist/interact/adjustment/control-geometry.js +87 -0
  52. package/dist/interact/adjustment/control-values.js +136 -0
  53. package/dist/interact/adjustment/slider-labels.js +84 -0
  54. package/dist/interact/assertions/screen-assertion.js +37 -0
  55. package/dist/interact/assertions/state-assertion-context.js +35 -0
  56. package/dist/interact/assertions/state-assertion.js +76 -0
  57. package/dist/interact/assertions/state-comparison.js +141 -0
  58. package/dist/interact/assertions/visibility-assertion.js +34 -0
  59. package/dist/interact/conditional-action.js +12 -7
  60. package/dist/interact/index.js +77 -3092
  61. package/dist/interact/input-service.js +1 -0
  62. package/dist/interact/interaction-trace.js +21 -0
  63. package/dist/interact/model.js +1 -0
  64. package/dist/interact/observation.js +1 -0
  65. package/dist/interact/targeting/candidate-ancestor.js +58 -0
  66. package/dist/interact/targeting/candidate-result.js +23 -0
  67. package/dist/interact/targeting/candidate-scoring.js +50 -0
  68. package/dist/interact/targeting/element-helpers.js +216 -0
  69. package/dist/interact/targeting/find-element.js +65 -0
  70. package/dist/interact/targeting/nearby-control.js +56 -0
  71. package/dist/interact/targeting/resolved-elements.js +21 -0
  72. package/dist/interact/targeting/tap-target.js +17 -0
  73. package/dist/interact/targeting/target-scope.js +104 -0
  74. package/dist/interact/waits/legacy-log-wait.js +38 -0
  75. package/dist/interact/waits/legacy-wait-observations.js +103 -0
  76. package/dist/interact/waits/legacy-wait-state.js +34 -0
  77. package/dist/interact/waits/legacy-wait.js +48 -0
  78. package/dist/interact/waits/screen-wait.js +49 -0
  79. package/dist/interact/waits/ui-change-helpers.js +157 -0
  80. package/dist/interact/waits/ui-change-state.js +18 -0
  81. package/dist/interact/waits/ui-change-wait.js +77 -0
  82. package/dist/interact/waits/ui-wait-condition.js +56 -0
  83. package/dist/interact/waits/ui-wait-state.js +38 -0
  84. package/dist/interact/waits/ui-wait.js +106 -0
  85. package/dist/manage/android.js +55 -51
  86. package/dist/manage/index.js +38 -75
  87. package/dist/manage/ios/build-types.js +1 -0
  88. package/dist/manage/ios/build.js +210 -0
  89. package/dist/manage/ios/project-discovery.js +29 -0
  90. package/dist/manage/ios/scheme.js +18 -0
  91. package/dist/manage/ios.js +44 -256
  92. package/dist/observe/android/context.js +1 -0
  93. package/dist/observe/android/current-screen.js +55 -0
  94. package/dist/observe/android/fingerprint.js +13 -0
  95. package/dist/observe/android/log-stream.js +132 -0
  96. package/dist/observe/android/logs.js +107 -0
  97. package/dist/observe/android/screenshot.js +70 -0
  98. package/dist/observe/android/ui-tree.js +107 -0
  99. package/dist/observe/android.js +16 -520
  100. package/dist/observe/evidence.js +114 -75
  101. package/dist/observe/index.js +16 -263
  102. package/dist/observe/ios/context.js +16 -0
  103. package/dist/observe/ios/fingerprint.js +12 -0
  104. package/dist/observe/ios/log-stream.js +105 -0
  105. package/dist/observe/ios/logs.js +168 -0
  106. package/dist/observe/ios/screenshot.js +22 -0
  107. package/dist/observe/ios/ui-elements.js +241 -0
  108. package/dist/observe/ios/ui-tree.js +137 -0
  109. package/dist/observe/ios-trace/artifact.js +63 -0
  110. package/dist/observe/ios-trace/capabilities.js +86 -0
  111. package/dist/observe/ios-trace/native.js +93 -0
  112. package/dist/observe/ios-trace/notification.js +35 -0
  113. package/dist/observe/ios-trace/process.js +98 -0
  114. package/dist/observe/ios-trace/sessions.js +244 -0
  115. package/dist/observe/ios.js +17 -715
  116. package/dist/observe/logs/model.js +1 -0
  117. package/dist/{network → observe/network}/index.js +2 -2
  118. package/dist/observe/readiness-service.js +5 -3
  119. package/dist/observe/readiness.js +33 -14
  120. package/dist/observe/screenshot-encoding.js +41 -0
  121. package/dist/observe/snapshot-collection.js +79 -0
  122. package/dist/observe/snapshot-logs.js +30 -0
  123. package/dist/observe/snapshot-metadata.js +43 -31
  124. package/dist/observe/snapshot-semantic.js +115 -0
  125. package/dist/observe/system-trace-error.js +7 -0
  126. package/dist/observe/system-trace-router.js +81 -0
  127. package/dist/observe/system-trace.js +26 -21
  128. package/dist/runtime/action-results.js +270 -0
  129. package/dist/runtime/clock.js +4 -0
  130. package/dist/server/common.js +1 -256
  131. package/dist/server/definitions/assert.js +162 -0
  132. package/dist/server/definitions/diagnostic.js +118 -0
  133. package/dist/server/definitions/input.js +274 -0
  134. package/dist/server/definitions/manage.js +194 -0
  135. package/dist/server/definitions/observe.js +149 -0
  136. package/dist/server/definitions/target.js +138 -0
  137. package/dist/server/definitions/wait.js +105 -0
  138. package/dist/server/handlers/action-execution.js +163 -0
  139. package/dist/server/handlers/correlation.js +23 -0
  140. package/dist/server/handlers/debugger-handlers.js +67 -0
  141. package/dist/server/handlers/debugger-response.js +9 -0
  142. package/dist/server/handlers/diagnostic.js +77 -0
  143. package/dist/server/handlers/handler-types.js +1 -0
  144. package/dist/server/handlers/input.js +154 -0
  145. package/dist/server/handlers/interact.js +113 -0
  146. package/dist/server/handlers/journey-assertions.js +35 -0
  147. package/dist/server/handlers/journey.js +147 -0
  148. package/dist/server/handlers/manage.js +119 -0
  149. package/dist/server/handlers/observe.js +95 -0
  150. package/dist/server/handlers/payload.js +4 -0
  151. package/dist/server/handlers/tool-handlers.js +170 -0
  152. package/dist/server/schemas/condition-schema.js +2 -0
  153. package/dist/server/{debugger-schema.js → schemas/debugger-schema.js} +20 -12
  154. package/dist/server/{journey-schema.js → schemas/journey-schema.js} +7 -22
  155. package/dist/server/schemas/system-trace-schema.js +22 -0
  156. package/dist/server/tool-definitions.js +58 -1135
  157. package/dist/server-core.js +2 -2
  158. package/dist/server.js +4 -4
  159. package/dist/system/android-status.js +101 -0
  160. package/dist/system/android.js +37 -27
  161. package/dist/system/index.js +13 -439
  162. package/dist/system/ios-status.js +132 -0
  163. package/dist/system/ios.js +11 -8
  164. package/dist/system/model.js +1 -0
  165. package/dist/system/probes.js +105 -0
  166. package/dist/system/status-summary.js +43 -0
  167. package/dist/system/unsupported-status.js +52 -0
  168. package/dist/utils/android/commands.js +89 -0
  169. package/dist/utils/android/gradle.js +139 -0
  170. package/dist/utils/android/logs.js +103 -0
  171. package/dist/utils/android/ui-tree.js +233 -0
  172. package/dist/utils/android/utils.js +6 -564
  173. package/dist/utils/cli/idb/idb-helper.js +4 -6
  174. package/dist/utils/cli/idb/install-idb.js +13 -9
  175. package/dist/utils/cli/ios/preflight-ios.js +42 -38
  176. package/dist/utils/ios/utils.js +48 -46
  177. package/dist/utils/java.js +65 -49
  178. package/dist/utils/resolve-device.js +4 -2
  179. package/dist/utils/ui/index.js +26 -26
  180. package/docs/CHANGELOG.md +67 -1
  181. package/docs/README.md +38 -0
  182. package/docs/agent-plugin-installation-verification.md +39 -0
  183. package/docs/benchmarks/README.md +18 -0
  184. package/docs/benchmarks/interaction-latency/README.md +14 -0
  185. package/docs/benchmarks/interaction-latency/final.md +4 -1
  186. package/docs/benchmarks/interaction-latency/ios-validation.md +2 -0
  187. package/docs/benchmarks/ui-tree-performance/README.md +24 -16
  188. package/docs/benchmarks/ui-tree-performance/comparison.md +11 -10
  189. package/docs/code-structure.md +120 -0
  190. package/docs/specs/001-state-verification/spec.md +16 -14
  191. package/docs/specs/002-richer-element-identity/spec.md +12 -10
  192. package/docs/specs/003-wait-and-synchronization-reliability/spec.md +12 -10
  193. package/docs/specs/004-action-verification-routing/spec.md +3 -3
  194. package/docs/specs/005-unified-action-execution-and-verification-model/spec.md +2 -0
  195. package/docs/specs/006-runtime-action-instrumentation-and-binding-layer/spec.md +2 -0
  196. package/docs/specs/007-actionability-resolution-and-executable-target-selection/spec.md +3 -1
  197. package/docs/specs/008-adjustable-control-support-and-semantic-value-manipulation/spec.md +3 -4
  198. package/docs/specs/009-semantic-control-modeling-for-custom-and-composite-controls/spec.md +2 -0
  199. package/docs/specs/010-verification-stabilization-and-temporal-convergence/spec.md +2 -2
  200. package/docs/specs/011-recovery-and-replanning-for-failed-or-ambiguous-interaction-flows/spec.md +2 -2
  201. package/docs/specs/011.1-recovery-contract-types-and-runtime-wiring-spec/spec.md +2 -0
  202. package/docs/specs/012-action-trace-and-xecution-observability/spec.md +2 -3
  203. package/docs/specs/013-wait-and-synchronization-reliability/spec.md +19 -19
  204. package/docs/specs/014-actionability-resolution/spec.md +20 -18
  205. package/docs/specs/015-adjustable-control-support/spec.md +17 -15
  206. package/docs/specs/016-environment-tools/spec.md +20 -20
  207. package/docs/specs/018-interaction-latency-and-batched-journeys/spec.md +2 -0
  208. package/docs/specs/019-ui-tree-observation-performance/spec.md +3 -1
  209. package/docs/specs/020-conditional-actions/intent.md +5 -3
  210. package/docs/specs/020-conditional-actions/plan.md +8 -9
  211. package/docs/specs/020-conditional-actions/spec.md +4 -2
  212. package/docs/specs/021-app-readiness-signals/intent.md +4 -2
  213. package/docs/specs/021-app-readiness-signals/plan.md +7 -6
  214. package/docs/specs/021-app-readiness-signals/spec.md +3 -1
  215. package/docs/specs/022-correlated-evidence/intent.md +4 -4
  216. package/docs/specs/022-correlated-evidence/plan.md +10 -11
  217. package/docs/specs/022-correlated-evidence/spec.md +3 -1
  218. package/docs/specs/023-android-system-traces/intent.md +3 -2
  219. package/docs/specs/023-android-system-traces/plan.md +5 -7
  220. package/docs/specs/023-android-system-traces/spec.md +3 -1
  221. package/docs/specs/024-android-heap-dump-capture/intent.md +3 -1
  222. package/docs/specs/025-android-runtime-debugger/intent.md +4 -3
  223. package/docs/specs/025-android-runtime-debugger/plan.md +3 -1
  224. package/docs/specs/025-android-runtime-debugger/spec.md +4 -3
  225. package/docs/specs/026-code-structure-and-quality-gates/health-plan.md +88 -0
  226. package/docs/specs/026-code-structure-and-quality-gates/intent.md +47 -0
  227. package/docs/specs/026-code-structure-and-quality-gates/plan.md +191 -0
  228. package/docs/specs/026-code-structure-and-quality-gates/spec.md +138 -0
  229. package/docs/specs/026-code-structure-and-quality-gates/verification-final.json +200 -0
  230. package/docs/specs/026-code-structure-and-quality-gates/verification-thin-facade.json +154 -0
  231. package/docs/specs/026-code-structure-and-quality-gates/verification.json +136 -0
  232. package/docs/specs/027-debugger-failure-diagnostics/intent.md +43 -0
  233. package/docs/specs/027-debugger-failure-diagnostics/plan.md +86 -0
  234. package/docs/specs/027-debugger-failure-diagnostics/spec.md +133 -0
  235. package/docs/specs/028-compact-debugger-responses/intent.md +41 -0
  236. package/docs/specs/028-compact-debugger-responses/plan.md +112 -0
  237. package/docs/specs/028-compact-debugger-responses/spec.md +108 -0
  238. package/docs/specs/029-kotlin-locals-validation/report.md +119 -0
  239. package/docs/specs/030-android-coroutine-validation/evidence.json +475 -0
  240. package/docs/specs/030-android-coroutine-validation/intent.md +47 -0
  241. package/docs/specs/030-android-coroutine-validation/plan.md +147 -0
  242. package/docs/specs/030-android-coroutine-validation/report.md +240 -0
  243. package/docs/specs/030-android-coroutine-validation/spec.md +107 -0
  244. package/docs/specs/031-ios-system-traces/intent.md +38 -0
  245. package/docs/specs/031-ios-system-traces/investigation.md +92 -0
  246. package/docs/specs/031-ios-system-traces/late-run-failure-evidence.json +32 -0
  247. package/docs/specs/031-ios-system-traces/plan.md +138 -0
  248. package/docs/specs/031-ios-system-traces/pr102-monitor-evidence.json +22 -0
  249. package/docs/specs/031-ios-system-traces/simulator-evidence.json +47 -0
  250. package/docs/specs/031-ios-system-traces/spec.md +135 -0
  251. package/docs/specs/032-ios-runtime-debugger/evidence.json +176 -0
  252. package/docs/specs/032-ios-runtime-debugger/intent.md +38 -0
  253. package/docs/specs/032-ios-runtime-debugger/plan.md +95 -0
  254. package/docs/specs/032-ios-runtime-debugger/report.md +205 -0
  255. package/docs/specs/032-ios-runtime-debugger/spec.md +153 -0
  256. package/docs/tools/TOOLS.md +31 -18
  257. package/docs/tools/{evidence.md → correlated-evidence.md} +9 -1
  258. package/docs/tools/debugger.md +120 -4
  259. package/docs/tools/interact.md +42 -20
  260. package/docs/tools/manage.md +25 -44
  261. package/docs/tools/observe.md +55 -23
  262. package/docs/tools/system-traces.md +89 -14
  263. package/docs/tools/system.md +12 -5
  264. package/package.json +12 -8
  265. package/scripts/agent-plugin.mjs +55 -5
  266. package/scripts/architecture-graph.mjs +69 -0
  267. package/scripts/check-architecture.mjs +47 -0
  268. package/scripts/code-quality-baseline.json +1 -0
  269. package/scripts/code-quality.mjs +161 -0
  270. package/src/contracts/execution-controls.ts +26 -0
  271. package/src/contracts/selector.ts +17 -0
  272. package/src/contracts/target.ts +19 -0
  273. package/src/contracts/ui.ts +94 -0
  274. package/src/debugger/android.ts +3 -2
  275. package/src/debugger/diagnostics.ts +43 -0
  276. package/src/debugger/guard.ts +10 -5
  277. package/src/debugger/ios-worker.ts +49 -0
  278. package/src/debugger/ios.ts +45 -0
  279. package/{worker → src/debugger/java}/DebuggerWorker.java +9 -3
  280. package/src/debugger/lldb/breakpoints.py +51 -0
  281. package/src/debugger/lldb/inspection.py +109 -0
  282. package/src/debugger/lldb/worker.py +203 -0
  283. package/src/debugger/protocol.ts +37 -0
  284. package/src/debugger/sessions.ts +326 -155
  285. package/src/debugger/worker.ts +57 -41
  286. package/src/interact/adjustment/adjustment-context.ts +83 -0
  287. package/src/interact/adjustment/adjustment-gestures.ts +48 -0
  288. package/src/interact/adjustment/adjustment-plan.ts +23 -0
  289. package/src/interact/adjustment/adjustment-resolution.ts +104 -0
  290. package/src/interact/adjustment/adjustment-result.ts +34 -0
  291. package/src/interact/adjustment/adjustment-semantic.ts +53 -0
  292. package/src/interact/adjustment/adjustment-verification.ts +60 -0
  293. package/src/interact/adjustment/adjustment.ts +49 -0
  294. package/src/interact/adjustment/control-geometry.ts +120 -0
  295. package/src/interact/adjustment/control-values.ts +141 -0
  296. package/src/interact/adjustment/slider-labels.ts +84 -0
  297. package/src/interact/assertions/screen-assertion.ts +39 -0
  298. package/src/interact/assertions/state-assertion-context.ts +48 -0
  299. package/src/interact/assertions/state-assertion.ts +64 -0
  300. package/src/interact/assertions/state-comparison.ts +168 -0
  301. package/src/interact/assertions/visibility-assertion.ts +41 -0
  302. package/src/interact/conditional-action.ts +18 -12
  303. package/src/interact/index.ts +89 -3494
  304. package/src/interact/input-service.ts +11 -0
  305. package/src/interact/interaction-trace.ts +36 -0
  306. package/src/interact/model.ts +109 -0
  307. package/src/interact/observation.ts +7 -0
  308. package/src/interact/targeting/candidate-ancestor.ts +49 -0
  309. package/src/interact/targeting/candidate-result.ts +24 -0
  310. package/src/interact/targeting/candidate-scoring.ts +42 -0
  311. package/src/interact/targeting/element-helpers.ts +243 -0
  312. package/src/interact/targeting/find-element.ts +47 -0
  313. package/src/interact/targeting/nearby-control.ts +46 -0
  314. package/src/interact/targeting/resolved-elements.ts +20 -0
  315. package/src/interact/targeting/tap-target.ts +22 -0
  316. package/src/interact/targeting/target-scope.ts +121 -0
  317. package/src/interact/waits/legacy-log-wait.ts +30 -0
  318. package/src/interact/waits/legacy-wait-observations.ts +55 -0
  319. package/src/interact/waits/legacy-wait-state.ts +48 -0
  320. package/src/interact/waits/legacy-wait.ts +34 -0
  321. package/src/interact/waits/screen-wait.ts +37 -0
  322. package/src/interact/waits/ui-change-helpers.ts +184 -0
  323. package/src/interact/waits/ui-change-state.ts +26 -0
  324. package/src/interact/waits/ui-change-wait.ts +59 -0
  325. package/src/interact/waits/ui-wait-condition.ts +52 -0
  326. package/src/interact/waits/ui-wait-state.ts +55 -0
  327. package/src/interact/waits/ui-wait.ts +84 -0
  328. package/src/manage/android.ts +56 -50
  329. package/src/manage/index.ts +63 -93
  330. package/src/manage/ios/build-types.ts +14 -0
  331. package/src/manage/ios/build.ts +230 -0
  332. package/src/manage/ios/project-discovery.ts +26 -0
  333. package/src/manage/ios/scheme.ts +16 -0
  334. package/src/manage/ios.ts +67 -298
  335. package/src/observe/android/context.ts +1 -0
  336. package/src/observe/android/current-screen.ts +63 -0
  337. package/src/observe/android/fingerprint.ts +13 -0
  338. package/src/observe/android/log-stream.ts +127 -0
  339. package/src/observe/android/logs.ts +110 -0
  340. package/src/observe/android/screenshot.ts +79 -0
  341. package/src/observe/android/ui-tree.ts +113 -0
  342. package/src/observe/android.ts +17 -531
  343. package/src/observe/evidence.ts +122 -70
  344. package/src/observe/index.ts +24 -286
  345. package/src/observe/ios/context.ts +22 -0
  346. package/src/observe/ios/fingerprint.ts +12 -0
  347. package/src/observe/ios/log-stream.ts +103 -0
  348. package/src/observe/ios/logs.ts +179 -0
  349. package/src/observe/ios/screenshot.ts +26 -0
  350. package/src/observe/ios/ui-elements.ts +266 -0
  351. package/src/observe/ios/ui-tree.ts +143 -0
  352. package/src/observe/ios-trace/artifact.ts +55 -0
  353. package/src/observe/ios-trace/capabilities.ts +74 -0
  354. package/src/observe/ios-trace/native.ts +72 -0
  355. package/src/observe/ios-trace/notification.ts +30 -0
  356. package/src/observe/ios-trace/process.ts +82 -0
  357. package/src/observe/ios-trace/sessions.ts +204 -0
  358. package/src/observe/ios.ts +18 -749
  359. package/src/observe/logs/model.ts +11 -0
  360. package/src/{network → observe/network}/index.ts +2 -2
  361. package/src/observe/readiness-service.ts +6 -4
  362. package/src/observe/readiness.ts +42 -21
  363. package/src/observe/screenshot-encoding.ts +32 -0
  364. package/src/observe/snapshot-collection.ts +69 -0
  365. package/src/observe/snapshot-logs.ts +26 -0
  366. package/src/observe/snapshot-metadata.ts +51 -31
  367. package/src/observe/snapshot-semantic.ts +153 -0
  368. package/src/observe/system-trace-error.ts +3 -0
  369. package/src/observe/system-trace-router.ts +61 -0
  370. package/src/observe/system-trace.ts +28 -19
  371. package/src/runtime/action-results.ts +376 -0
  372. package/src/runtime/clock.ts +9 -0
  373. package/src/server/common.ts +9 -359
  374. package/src/server/definitions/assert.ts +164 -0
  375. package/src/server/definitions/diagnostic.ts +120 -0
  376. package/src/server/definitions/input.ts +276 -0
  377. package/src/server/definitions/manage.ts +196 -0
  378. package/src/server/definitions/observe.ts +151 -0
  379. package/src/server/definitions/target.ts +140 -0
  380. package/src/server/definitions/wait.ts +107 -0
  381. package/src/server/handlers/action-execution.ts +164 -0
  382. package/src/server/handlers/correlation.ts +20 -0
  383. package/src/server/handlers/debugger-handlers.ts +58 -0
  384. package/src/server/handlers/debugger-response.ts +7 -0
  385. package/src/server/handlers/diagnostic.ts +86 -0
  386. package/src/server/handlers/handler-types.ts +7 -0
  387. package/src/server/handlers/input.ts +173 -0
  388. package/src/server/handlers/interact.ts +135 -0
  389. package/src/server/handlers/journey-assertions.ts +39 -0
  390. package/src/server/handlers/journey.ts +132 -0
  391. package/src/server/handlers/manage.ts +141 -0
  392. package/src/server/handlers/observe.ts +116 -0
  393. package/src/server/handlers/payload.ts +36 -0
  394. package/src/server/handlers/tool-handlers.ts +170 -0
  395. package/src/server/schemas/condition-schema.ts +2 -0
  396. package/src/server/schemas/debugger-schema.ts +46 -0
  397. package/src/server/{journey-schema.ts → schemas/journey-schema.ts} +7 -22
  398. package/src/server/schemas/system-trace-schema.ts +23 -0
  399. package/src/server/tool-definitions.ts +67 -1143
  400. package/src/server-core.ts +2 -2
  401. package/src/server.ts +4 -4
  402. package/src/system/android-status.ts +124 -0
  403. package/src/system/android.ts +33 -21
  404. package/src/system/index.ts +21 -525
  405. package/src/system/ios-status.ts +147 -0
  406. package/src/system/ios.ts +14 -10
  407. package/src/system/model.ts +41 -0
  408. package/src/system/probes.ts +114 -0
  409. package/src/system/status-summary.ts +47 -0
  410. package/src/system/unsupported-status.ts +55 -0
  411. package/src/types.ts +16 -106
  412. package/src/utils/android/commands.ts +81 -0
  413. package/src/utils/android/gradle.ts +132 -0
  414. package/src/utils/android/logs.ts +103 -0
  415. package/src/utils/android/ui-tree.ts +239 -0
  416. package/src/utils/android/utils.ts +16 -556
  417. package/src/utils/cli/idb/check-idb.ts +4 -4
  418. package/src/utils/cli/idb/idb-helper.ts +6 -8
  419. package/src/utils/cli/idb/install-idb.ts +15 -9
  420. package/src/utils/cli/ios/preflight-ios.ts +39 -31
  421. package/src/utils/cli/ios/run-ios-ui-tree-tap.ts +4 -4
  422. package/src/utils/ios/utils.ts +60 -61
  423. package/src/utils/java.ts +62 -43
  424. package/src/utils/resolve-device.ts +9 -6
  425. package/src/utils/ui/index.ts +28 -28
  426. package/test/device/automated/debugger/coroutine-lifecycle.ts +27 -0
  427. package/test/device/automated/debugger/coroutine-support.ts +140 -0
  428. package/test/device/automated/debugger/coroutine.android.test.ts +169 -0
  429. package/test/device/automated/debugger/kotlin.android.test.ts +127 -0
  430. package/test/device/automated/debugger/runtime.android.test.ts +19 -3
  431. package/test/device/automated/debugger/runtime.ios.test.ts +221 -0
  432. package/test/device/automated/interact/interaction.android.test.ts +56 -0
  433. package/test/device/automated/observe/ios-system-trace.smoke.ts +119 -0
  434. package/test/device/automated/observe/system-trace.smoke.ts +1 -1
  435. package/test/device/manual/interact/modul8_selector_tap.manual.ts +1 -1
  436. package/test/fixtures/contracts/tool-definitions.json +3696 -0
  437. package/test/fixtures/coroutine-app/MainActivity.kt +100 -0
  438. package/test/fixtures/coroutine-app/README.md +48 -0
  439. package/test/fixtures/coroutine-app/build.ts +35 -0
  440. package/test/fixtures/debugger-app/MainActivity.java +21 -0
  441. package/test/fixtures/debugger-app/MainActivity.kt +32 -0
  442. package/test/fixtures/debugger-app/README.md +52 -0
  443. package/test/fixtures/debugger-app/build.ts +25 -15
  444. package/test/fixtures/ios-debugger-app/Main.swift +95 -0
  445. package/test/fixtures/ios-debugger-app/README.md +27 -0
  446. package/test/fixtures/ios-debugger-app/build.ts +21 -0
  447. package/test/fixtures/latency-journey-app/README.md +2 -0
  448. package/test/fixtures/readiness-app/README.md +2 -0
  449. package/test/helpers/ios-trace-shutdown.ts +29 -0
  450. package/test/unit/debugger/coroutine-lifecycle.test.ts +43 -0
  451. package/test/unit/debugger/diagnostic-sessions.test.ts +67 -0
  452. package/test/unit/debugger/diagnostics.test.ts +80 -0
  453. package/test/unit/debugger/guards.test.ts +4 -0
  454. package/test/unit/debugger/handle-guards.test.ts +88 -0
  455. package/test/unit/debugger/ios-preparation.test.ts +57 -0
  456. package/test/unit/debugger/ios.test.ts +104 -0
  457. package/test/unit/debugger/lldb-worker.py +166 -0
  458. package/test/unit/debugger/lldb-worker.test.ts +10 -0
  459. package/test/unit/debugger/preparation.test.ts +32 -0
  460. package/test/unit/debugger/protocol.test.ts +15 -0
  461. package/test/unit/debugger/sessions.test.ts +1 -1
  462. package/test/unit/interact/adjustment_service.test.ts +42 -0
  463. package/test/unit/interact/adjustment_verification.test.ts +35 -0
  464. package/test/unit/interact/clock_dependencies.test.ts +63 -0
  465. package/test/unit/interact/element_helpers.test.ts +19 -0
  466. package/test/unit/interact/legacy_wait_service.test.ts +17 -0
  467. package/test/unit/interact/state_comparison.test.ts +20 -0
  468. package/test/unit/interact/target_scope.test.ts +35 -0
  469. package/test/unit/network/get_network_activity.test.ts +1 -1
  470. package/test/unit/observe/ios-trace-native.test.ts +90 -0
  471. package/test/unit/observe/ios-trace-sessions.test.ts +131 -0
  472. package/test/unit/observe/readiness.test.ts +1 -1
  473. package/test/unit/observe/screenshot-encoding.test.ts +30 -0
  474. package/test/unit/observe/snapshot-logs.test.ts +14 -0
  475. package/test/unit/observe/system-trace-router.test.ts +49 -0
  476. package/test/unit/quality/agent-plugin-export.test.ts +38 -0
  477. package/test/unit/quality/architecture.test.ts +60 -0
  478. package/test/unit/quality/code-quality.test.ts +58 -0
  479. package/test/unit/quality/interaction_boundaries.test.ts +24 -0
  480. package/test/unit/server/conditional_actions.test.ts +1 -1
  481. package/test/unit/server/conditional_schema.test.ts +2 -2
  482. package/test/unit/server/debugger-response.test.ts +63 -0
  483. package/test/unit/server/system-trace.test.ts +12 -3
  484. package/test/unit/server/tool_definitions_parity.test.ts +8 -0
  485. package/dist/server/debugger-handlers.js +0 -38
  486. package/dist/server/system-trace-schema.js +0 -17
  487. package/dist/server/tool-handlers.js +0 -1000
  488. package/docs/ROADMAP.md +0 -646
  489. package/docs/benchmarks/ui-tree-performance/bridge-probe.json +0 -29
  490. package/docs/benchmarks/ui-tree-performance/initial-baseline.json +0 -59560
  491. package/docs/specs/baseline-spec-v0.md +0 -312
  492. package/docs/specs/mcp-tooling-spec-v1.md +0 -446
  493. package/src/server/debugger-handlers.ts +0 -29
  494. package/src/server/debugger-schema.ts +0 -37
  495. package/src/server/system-trace-schema.ts +0 -17
  496. package/src/server/tool-handlers.ts +0 -1008
  497. /package/dist/{server/condition-schema.js → contracts/condition.js} +0 -0
  498. /package/dist/server/{readiness-schema.js → schemas/readiness-schema.js} +0 -0
  499. /package/src/{server/condition-schema.ts → contracts/condition.ts} +0 -0
  500. /package/src/server/{readiness-schema.ts → schemas/readiness-schema.ts} +0 -0
@@ -0,0 +1,100 @@
1
+ package dev.mobiledebugmcp.coroutinefixture
2
+
3
+ import android.app.Activity
4
+ import android.os.Bundle
5
+ import android.widget.Button
6
+ import android.widget.LinearLayout
7
+ import android.widget.TextView
8
+ import java.util.concurrent.Executors
9
+ import kotlinx.coroutines.*
10
+
11
+ class MainActivity : Activity() {
12
+ private val first = Executors.newSingleThreadExecutor { Thread(it, "fixture-first") }.asCoroutineDispatcher()
13
+ private val second = Executors.newSingleThreadExecutor { Thread(it, "fixture-second") }.asCoroutineDispatcher()
14
+ private val scope = CoroutineScope(SupervisorJob() + first)
15
+ private var gate = CompletableDeferred<Unit>()
16
+ private var job: Job? = null
17
+ private lateinit var status: TextView
18
+
19
+ override fun onCreate(saved: Bundle?) {
20
+ super.onCreate(saved)
21
+ val layout = LinearLayout(this)
22
+ layout.orientation = LinearLayout.VERTICAL
23
+ layout.setPadding(0, 160, 0, 0)
24
+ status = TextView(this)
25
+ status.text = "Coroutine ready"
26
+ layout.addView(status)
27
+ fun button(label: String, action: () -> Unit) {
28
+ val button = Button(this)
29
+ button.text = label
30
+ button.setOnClickListener { action() }
31
+ layout.addView(button)
32
+ }
33
+ button("Complete run") { start(false) }
34
+ button("Cancellation run") { start(true) }
35
+ button("Release gate") { gate.complete(Unit) }
36
+ button("Cancel job") { job?.cancel() }
37
+ setContentView(layout)
38
+ }
39
+
40
+ private fun show(text: String) { runOnUiThread { status.text = text } }
41
+
42
+ private fun start(cancel: Boolean) {
43
+ if (job?.isActive == true) { show("Coroutine busy"); return }
44
+ gate = CompletableDeferred()
45
+ job = scope.launch {
46
+ if (cancel) cancelWork() else completeWork()
47
+ }
48
+ }
49
+
50
+ private suspend fun completeWork() {
51
+ val firstThread = Thread.currentThread().name
52
+ val known = 42
53
+ check(firstThread == "fixture-first") // BREAK_BEFORE
54
+ // Queued on the same single-thread executor: runs only after gate.await yields.
55
+ first.executor.execute { show("Coroutine waiting") }
56
+ gate.await() // BREAK_SUSPEND
57
+ val after = known + 1 // BREAK_AFTER
58
+ withContext(second) {
59
+ val dispatcherThread = Thread.currentThread().name
60
+ val nested = async { helper(after) } // BREAK_DISPATCH
61
+ check(dispatcherThread == "fixture-second")
62
+ val answer = nested.await()
63
+ show("Coroutine result $answer") // BREAK_RESULT
64
+ }
65
+ }
66
+
67
+ private fun helper(value: Int): Int {
68
+ val result = increment(value) // BREAK_HELPER
69
+ return result // BREAK_HELPER_RETURN
70
+ }
71
+
72
+ private fun increment(value: Int): Int {
73
+ val result = value + 1 // BREAK_INCREMENT
74
+ return result // BREAK_INCREMENT_RETURN
75
+ }
76
+
77
+ private suspend fun cancelWork() = coroutineScope {
78
+ val child = launch(second) {
79
+ try {
80
+ // The child must yield at gate.await before this queued work can run.
81
+ second.executor.execute { show("Cancellation waiting") }
82
+ gate.await()
83
+ error("Cancelled child must not cross gate")
84
+ } finally {
85
+ show("Cancellation finally") // BREAK_CANCEL
86
+ }
87
+ }
88
+ try { child.join() } finally {
89
+ withContext(NonCancellable) { child.cancelAndJoin() }
90
+ show("Coroutine cancelled")
91
+ }
92
+ }
93
+
94
+ override fun onDestroy() {
95
+ scope.cancel()
96
+ first.close()
97
+ second.close()
98
+ super.onDestroy()
99
+ }
100
+ }
@@ -0,0 +1,48 @@
1
+ # Isolated Android coroutine debugger fixture
2
+
3
+ Separate disposable package `dev.mobiledebugmcp.coroutinefixture`; the runner
4
+ refuses to replace an existing installation. No server dependency, debug agent,
5
+ automatic download or production app instrumentation is introduced.
6
+
7
+ Requires Node 22+, JDK 17, Android SDK platform/build-tools 36, Kotlin JVM compiler
8
+ and matching stdlib 2.0.21, and kotlinx-coroutines-core-jvm 1.9.0. Provide local
9
+ paths explicitly:
10
+
11
+ ```sh
12
+ export JAVA_HOME=/path/to/jdk-17
13
+ export MCP_KOTLINC=/path/to/kotlin/bin/kotlinc
14
+ export MCP_KOTLIN_STDLIB=/path/to/kotlin/lib/kotlin-stdlib.jar
15
+ export MCP_COROUTINES_CORE=/path/to/kotlinx-coroutines-core-jvm-1.9.0.jar
16
+ npm run build
17
+ MCP_COROUTINE_DEVICE=emulator-5554 npx tsx test/device/automated/debugger/coroutine.android.test.ts
18
+ ```
19
+
20
+ `npm run test:device` discovers the runner and reports **UNRUN** unless
21
+ `MCP_COROUTINE_DEVICE` is set. A configured run with missing prerequisites fails;
22
+ it cannot silently pass. Do not run other fixture journeys concurrently on the
23
+ same serial. A physical device must be selected explicitly and reported separately.
24
+
25
+ The source owns two named single-thread dispatcher executors, an explicit gate,
26
+ nested async work and a cancellation/finally path. Readiness is posted by work
27
+ queued on the same single-thread executor behind the coroutine, so the runner
28
+ releases or cancels only after the gate wait has yielded. UI controls release the
29
+ gate only after debugger resume. Compiler line/variable tables are captured for generated
30
+ continuation classes as well as ordinary methods. The assessment compares existing
31
+ MCP stops, thread stacks and shallow locals; none represents a reconstructed
32
+ logical coroutine stack. A bounded step attempt is recorded as an observation,
33
+ not a promise to follow work across suspension. Source breakpoints are tested
34
+ independently as a workaround.
35
+
36
+ The runner checks pause guards, old frame rejection, event-wait cancellation,
37
+ completion/cancellation UI recovery and owned ADB forward cleanup. Finally it
38
+ removes its package and temporary build/signing output. The overall JSON PASS is
39
+ emitted only after cleanup; cleanup failures emit JSON FAIL and reject the run.
40
+ Forward comparisons filter the selected serial and sort entries, so another
41
+ device changing its forwards does not fail this journey. SIGKILL/host loss cannot
42
+ execute finally; inspect outstanding sessions/packages manually after such loss.
43
+ Local output includes synthetic fixture values, source/APK/server/dependency hashes
44
+ and compiler metadata. Redirect stdout/stderr to an intentional local evidence
45
+ file; no data is uploaded automatically.
46
+
47
+ See the [accepted specification](../../../docs/specs/030-android-coroutine-validation/spec.md)
48
+ and [implementation/verification record](../../../docs/specs/030-android-coroutine-validation/plan.md).
@@ -0,0 +1,35 @@
1
+ import { execFileSync } from 'node:child_process'
2
+ import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
3
+ import { tmpdir } from 'node:os'
4
+ import path from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+ export const coroutineAppId = 'dev.mobiledebugmcp.coroutinefixture'
7
+ export function buildCoroutineFixture() {
8
+ if (!process.env.MCP_COROUTINES_CORE) throw new Error('MCP_COROUTINES_CORE must identify kotlinx-coroutines-core-jvm.jar')
9
+ if (!process.env.MCP_KOTLIN_STDLIB) throw new Error('MCP_KOTLIN_STDLIB must identify the matching Kotlin standard-library jar')
10
+ const source = path.dirname(fileURLToPath(import.meta.url))
11
+ const directory = mkdtempSync(path.join(tmpdir(), 'mcp-coroutine-fixture-'))
12
+ const java = process.env.JAVA_HOME ?? (existsSync('/Applications/Android Studio.app/Contents/jbr/Contents/Home') ? '/Applications/Android Studio.app/Contents/jbr/Contents/Home' : undefined)
13
+ const run = (cmd: string, args: string[]) => execFileSync(cmd, args, { cwd: directory, encoding: 'utf8', stdio: 'pipe', timeout: 120000 })
14
+ const sdk = process.env.ANDROID_HOME ?? path.join(process.env.HOME!, 'Library/Android/sdk')
15
+ const tools = path.join(sdk, 'build-tools/36.0.0')
16
+ const androidJar = path.join(sdk, 'platforms/android-36/android.jar')
17
+ const jdk = (name: string) => java ? path.join(java, 'bin', name) : name
18
+ try {
19
+ mkdirSync(path.join(directory, 'classes'))
20
+ writeFileSync(path.join(directory, 'AndroidManifest.xml'), `<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="${coroutineAppId}" android:versionCode="1" android:versionName="1.0"><uses-sdk android:minSdkVersion="26" android:targetSdkVersion="36"/><application android:debuggable="true" android:label="Debugger fixture" android:theme="@android:style/Theme.Material.Light.NoActionBar"><activity android:name=".MainActivity" android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER"/></intent-filter></activity></application></manifest>`)
21
+ run(process.env.MCP_KOTLINC ?? 'kotlinc', ['-no-stdlib', '-no-reflect', '-jvm-target', '1.8', '-classpath', [androidJar, process.env.MCP_KOTLIN_STDLIB!, process.env.MCP_COROUTINES_CORE!].join(path.delimiter), '-d', 'classes', path.join(source, 'MainActivity.kt')])
22
+ run(jdk('jar'), ['cf', 'classes.jar', '-C', 'classes', '.'])
23
+ run(path.join(tools, 'd8'), ['--min-api', '26', '--lib', androidJar, '--output', directory, 'classes.jar', process.env.MCP_KOTLIN_STDLIB!, process.env.MCP_COROUTINES_CORE!])
24
+ run(path.join(tools, 'aapt2'), ['link', '-I', androidJar, '--manifest', 'AndroidManifest.xml', '-o', 'unsigned.apk'])
25
+ run('zip', ['-q', 'unsigned.apk', 'classes.dex'])
26
+ run(path.join(tools, 'zipalign'), ['-f', '4', 'unsigned.apk', 'aligned.apk'])
27
+ run(jdk('keytool'), ['-genkeypair', '-keystore', 'fixture.jks', '-storepass', 'android', '-keypass', 'android', '-alias', 'fixture', '-dname', 'CN=Readiness Fixture', '-keyalg', 'RSA', '-validity', '365'])
28
+ const artifact = path.join(directory, 'Debugger.apk')
29
+ run(path.join(tools, 'apksigner'), ['sign', '--ks', 'fixture.jks', '--ks-pass', 'pass:android', '--out', artifact, 'aligned.apk'])
30
+ return { artifact, directory }
31
+ } catch (error) {
32
+ rmSync(directory, { recursive: true, force: true })
33
+ throw error
34
+ }
35
+ }
@@ -23,8 +23,29 @@ public class MainActivity extends Activity {
23
23
  try { Class.forName("dev.mobiledebugmcp.debugfixture.NoLocals").getDeclaredMethod("work").invoke(null); }
24
24
  catch (Exception e) { status.setText("Metadata failed"); }
25
25
  }); layout.addView(noLocals);
26
+ addLevelControl(layout);
26
27
  setContentView(layout);
27
28
  }
29
+ void addLevelControl(LinearLayout layout) {
30
+ TextView level = new TextView(this);
31
+ level.setGravity(android.view.Gravity.CENTER);
32
+ level.setText("25 %"); layout.addView(level);
33
+ android.widget.SeekBar slider = new android.widget.SeekBar(this);
34
+ slider.setMax(100); slider.setProgress(25);
35
+ slider.setContentDescription("Fixture level");
36
+ slider.setOnSeekBarChangeListener(new android.widget.SeekBar.OnSeekBarChangeListener() {
37
+ public void onProgressChanged(android.widget.SeekBar view, int progress, boolean fromUser) { level.setText(progress + " %"); }
38
+ public void onStartTrackingTouch(android.widget.SeekBar view) {}
39
+ public void onStopTrackingTouch(android.widget.SeekBar view) {}
40
+ });
41
+ layout.addView(slider);
42
+ LinearLayout range = new LinearLayout(this);
43
+ TextView minimum = new TextView(this); minimum.setText("0 %");
44
+ TextView maximum = new TextView(this); maximum.setText("100 %"); maximum.setGravity(android.view.Gravity.RIGHT);
45
+ range.addView(minimum, new LinearLayout.LayoutParams(0, 80, 1));
46
+ range.addView(maximum, new LinearLayout.LayoutParams(0, 80, 1));
47
+ layout.addView(range);
48
+ }
28
49
  void calculate(int seed) {
29
50
  int known = seed + 35;
30
51
  String longText = new String(new char[300]).replace('\0', 'x');
@@ -0,0 +1,32 @@
1
+ package dev.mobiledebugmcp.debugfixture
2
+
3
+ import android.app.Activity
4
+ import android.os.Bundle
5
+ import android.widget.Button
6
+ import android.widget.LinearLayout
7
+ import android.widget.TextView
8
+
9
+ class MainActivity : Activity() {
10
+ private lateinit var status: TextView
11
+
12
+ override fun onCreate(saved: Bundle?) {
13
+ super.onCreate(saved)
14
+ val layout = LinearLayout(this)
15
+ layout.orientation = LinearLayout.VERTICAL
16
+ layout.setPadding(0, 160, 0, 0)
17
+ status = TextView(this)
18
+ status.text = "Kotlin ready"
19
+ layout.addView(status)
20
+ val button = Button(this)
21
+ button.text = "Kotlin calculate"
22
+ button.setOnClickListener { calculate(7) }
23
+ layout.addView(button)
24
+ setContentView(layout)
25
+ }
26
+
27
+ private fun calculate(seed: Int) {
28
+ val known = seed + 35 // BREAK_KOTLIN_BEFORE
29
+ val answer = known + 1 // BREAK_KOTLIN_KNOWN
30
+ status.text = "Kotlin result $answer" // BREAK_KOTLIN_RESULT
31
+ }
32
+ }
@@ -1,5 +1,7 @@
1
1
  # Isolated Android debugger fixture
2
2
 
3
+ [Contributing](../../../CONTRIBUTING.md) · [Documentation](../../../docs/README.md)
4
+
3
5
  Package `dev.mobiledebugmcp.debugfixture`, built with `android:debuggable=true`
4
6
  and Java source/line/local metadata. Calculate exposes `known=42`, an increment
5
7
  call for into/over/out stepping, and a visible `Result 43` after resume. Late class
@@ -11,3 +13,53 @@ Build helper uses SDK platform/build-tools 36, the existing direct javac/D8/aapt
11
13
  pattern, ephemeral signing material and temporary output. It does not modify other
12
14
  apps or display settings. The automated journey installs/removes only this fixture
13
15
  and checks session-owned ADB forwards. See [debugger tools](../../../docs/tools/debugger.md).
16
+
17
+ ## Kotlin locals validation
18
+
19
+ The opt-in Kotlin variant compiles `MainActivity.kt` instead of the Java sources
20
+ into the same disposable package. Do not run the variants concurrently on one
21
+ device. It requires a Kotlin JVM compiler (`MCP_KOTLINC`, or `kotlinc` on PATH),
22
+ the matching standard-library jar (`MCP_KOTLIN_STDLIB`), and a compatible JDK.
23
+ Kotlin 2.0.21 with JDK 17 was used for the recorded validation. The fixture does
24
+ not add Gradle, download a compiler, or change the server's dependencies.
25
+
26
+ ```sh
27
+ npm run build
28
+ export JAVA_HOME=/path/to/jdk-17
29
+ export MCP_KOTLINC=/path/to/kotlin/bin/kotlinc
30
+ export MCP_KOTLIN_STDLIB=/path/to/kotlin/lib/kotlin-stdlib.jar
31
+ MCP_KOTLIN_DEVICE=emulator-5554 npx tsx test/device/automated/debugger/kotlin.android.test.ts
32
+ ```
33
+
34
+ The compiler retains its default line/local metadata; D8 runs without shrinking
35
+ or obfuscation. The runner checks the class-file local-variable scope tables,
36
+ then reads `seed=7` before `known` is initialized, `known=42` before `answer` is
37
+ initialized, and `answer=43` afterward through MCP. Old references must fail as
38
+ stale. Resume/detach is followed by a fresh hierarchy assertion for
39
+ `Kotlin result 43`; exact owned forwards, package and temporary files are cleaned
40
+ up. Build failures also remove their temporary directory.
41
+
42
+ `npm run test:device` includes this journey only when `MCP_KOTLIN_DEVICE` is set;
43
+ otherwise it reports **UNRUN** explicitly. The existing Java journey separately
44
+ covers absent variable tables. A local outside its compiler scope and missing
45
+ metadata are different conditions; an empty ART table alone proves neither.
46
+ See the [dated validation record](../../../docs/specs/029-kotlin-locals-validation/report.md).
47
+
48
+ ## Interaction regression journey
49
+
50
+ The same isolated fixture is also used by the [interaction device check](../../device/automated/interact/interaction.android.test.ts)
51
+ for clickable waits, stored handles, selector taps, visibility and enabled-state
52
+ assertions. Run it after building the server:
53
+
54
+ ```sh
55
+ MCP_INTERACTION_DEVICE=emulator-5554 npx tsx test/device/automated/interact/interaction.android.test.ts
56
+ ```
57
+
58
+ It also runs in `npm run test:device` when `MCP_INTERACTION_DEVICE` or
59
+ `MCP_DEBUGGER_DEVICE` selects a device. Fixture journeys run sequentially and
60
+ remove their own package and temporary build outputs. This check does not attach
61
+ a debugger or modify another app's data.
62
+
63
+ The interaction journey also exercises a labelled native SeekBar. It adjusts
64
+ from 25 toward 75, checks convergence within tolerance, and confirms the displayed
65
+ value with a fresh hierarchy assertion. The same fixture cleanup applies.
@@ -1,10 +1,11 @@
1
1
  import { execFileSync } from 'node:child_process'
2
- import { existsSync, mkdirSync, mkdtempSync, writeFileSync } from 'node:fs'
2
+ import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
3
3
  import { tmpdir } from 'node:os'
4
4
  import path from 'node:path'
5
5
  import { fileURLToPath } from 'node:url'
6
6
  export const debuggerAppId = 'dev.mobiledebugmcp.debugfixture'
7
- export function buildDebuggerFixture() {
7
+ export function buildDebuggerFixture(kotlin = false) {
8
+ if (kotlin && !process.env.MCP_KOTLIN_STDLIB) throw new Error('MCP_KOTLIN_STDLIB must identify the matching Kotlin standard-library jar')
8
9
  const source = path.dirname(fileURLToPath(import.meta.url))
9
10
  const directory = mkdtempSync(path.join(tmpdir(), 'mcp-debugger-fixture-'))
10
11
  const java = process.env.JAVA_HOME ?? (existsSync('/Applications/Android Studio.app/Contents/jbr/Contents/Home') ? '/Applications/Android Studio.app/Contents/jbr/Contents/Home' : undefined)
@@ -13,17 +14,26 @@ export function buildDebuggerFixture() {
13
14
  const tools = path.join(sdk, 'build-tools/36.0.0')
14
15
  const androidJar = path.join(sdk, 'platforms/android-36/android.jar')
15
16
  const jdk = (name: string) => java ? path.join(java, 'bin', name) : name
16
- mkdirSync(path.join(directory, 'classes'))
17
- writeFileSync(path.join(directory, 'AndroidManifest.xml'), `<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="${debuggerAppId}" android:versionCode="1" android:versionName="1.0"><uses-sdk android:minSdkVersion="26" android:targetSdkVersion="36"/><application android:debuggable="true" android:label="Debugger fixture" android:theme="@android:style/Theme.Material.Light.NoActionBar"><activity android:name=".MainActivity" android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER"/></intent-filter></activity></application></manifest>`)
18
- run(jdk('javac'), ['-g', '--release', '8', '-classpath', androidJar, '-d', 'classes', path.join(source, 'MainActivity.java')])
19
- run(jdk('javac'), ['-g:source,lines', '--release', '8', '-classpath', androidJar, '-d', 'classes', path.join(source, 'NoLocals.java')])
20
- run(jdk('jar'), ['cf', 'classes.jar', '-C', 'classes', '.'])
21
- run(path.join(tools, 'd8'), ['--lib', androidJar, '--output', directory, 'classes.jar'])
22
- run(path.join(tools, 'aapt2'), ['link', '-I', androidJar, '--manifest', 'AndroidManifest.xml', '-o', 'unsigned.apk'])
23
- run('zip', ['-q', 'unsigned.apk', 'classes.dex'])
24
- run(path.join(tools, 'zipalign'), ['-f', '4', 'unsigned.apk', 'aligned.apk'])
25
- run(jdk('keytool'), ['-genkeypair', '-keystore', 'fixture.jks', '-storepass', 'android', '-keypass', 'android', '-alias', 'fixture', '-dname', 'CN=Readiness Fixture', '-keyalg', 'RSA', '-validity', '365'])
26
- const artifact = path.join(directory, 'Debugger.apk')
27
- run(path.join(tools, 'apksigner'), ['sign', '--ks', 'fixture.jks', '--ks-pass', 'pass:android', '--out', artifact, 'aligned.apk'])
28
- return { artifact, directory }
17
+ try {
18
+ mkdirSync(path.join(directory, 'classes'))
19
+ writeFileSync(path.join(directory, 'AndroidManifest.xml'), `<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="${debuggerAppId}" android:versionCode="1" android:versionName="1.0"><uses-sdk android:minSdkVersion="26" android:targetSdkVersion="36"/><application android:debuggable="true" android:label="Debugger fixture" android:theme="@android:style/Theme.Material.Light.NoActionBar"><activity android:name=".MainActivity" android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER"/></intent-filter></activity></application></manifest>`)
20
+ if (kotlin) {
21
+ run(process.env.MCP_KOTLINC ?? 'kotlinc', ['-no-stdlib', '-no-reflect', '-jvm-target', '1.8', '-classpath', [androidJar, process.env.MCP_KOTLIN_STDLIB!].join(path.delimiter), '-d', 'classes', path.join(source, 'MainActivity.kt')])
22
+ } else {
23
+ run(jdk('javac'), ['-g', '--release', '8', '-classpath', androidJar, '-d', 'classes', path.join(source, 'MainActivity.java')])
24
+ run(jdk('javac'), ['-g:source,lines', '--release', '8', '-classpath', androidJar, '-d', 'classes', path.join(source, 'NoLocals.java')])
25
+ }
26
+ run(jdk('jar'), ['cf', 'classes.jar', '-C', 'classes', '.'])
27
+ run(path.join(tools, 'd8'), ['--lib', androidJar, '--output', directory, 'classes.jar', ...(kotlin ? [process.env.MCP_KOTLIN_STDLIB!] : [])])
28
+ run(path.join(tools, 'aapt2'), ['link', '-I', androidJar, '--manifest', 'AndroidManifest.xml', '-o', 'unsigned.apk'])
29
+ run('zip', ['-q', 'unsigned.apk', 'classes.dex'])
30
+ run(path.join(tools, 'zipalign'), ['-f', '4', 'unsigned.apk', 'aligned.apk'])
31
+ run(jdk('keytool'), ['-genkeypair', '-keystore', 'fixture.jks', '-storepass', 'android', '-keypass', 'android', '-alias', 'fixture', '-dname', 'CN=Readiness Fixture', '-keyalg', 'RSA', '-validity', '365'])
32
+ const artifact = path.join(directory, 'Debugger.apk')
33
+ run(path.join(tools, 'apksigner'), ['sign', '--ks', 'fixture.jks', '--ks-pass', 'pass:android', '--out', artifact, 'aligned.apk'])
34
+ return { artifact, directory }
35
+ } catch (error) {
36
+ rmSync(directory, { recursive: true, force: true })
37
+ throw error
38
+ }
29
39
  }
@@ -0,0 +1,95 @@
1
+ import UIKit
2
+
3
+ @inline(never) func increment(_ value: Int) -> Int {
4
+ let adjusted = value + 1 // STEP_INCREMENT
5
+ return adjusted // STEP_RETURN
6
+ }
7
+ @inline(never) func helper(_ value: Int) -> Int {
8
+ let result = increment(value) // STEP_HELPER
9
+ return result // HELPER_RETURN
10
+ }
11
+ actor AsyncWork {
12
+ func child(_ input: Int) async -> Int {
13
+ try? await Task.sleep(nanoseconds: 300_000_000)
14
+ let childValue = input + 1
15
+ return childValue // CHILD_RETURN
16
+ }
17
+ @inline(never) func calculate(_ seed: Int) async -> Int {
18
+ let known = seed
19
+ async let childResult = child(known)
20
+ let after = await childResult // BEFORE_AWAIT
21
+ let answer = helper(after + known - seed) // AFTER_AWAIT
22
+ return answer // ASYNC_RETURN
23
+ }
24
+ }
25
+ class Controller: UIViewController {
26
+ let label = UILabel()
27
+ var task: Task<Void, Never>?
28
+ override func viewDidLoad() {
29
+ super.viewDidLoad()
30
+ view.backgroundColor = .white
31
+ label.text = "Debugger ready"
32
+ label.accessibilityIdentifier = "debugger-result"
33
+ let stack = UIStackView()
34
+ stack.axis = .vertical
35
+ stack.spacing = 24
36
+ stack.addArrangedSubview(label)
37
+ for (title, action) in [("Calculate", #selector(calculate)), ("Run async", #selector(runAsync)), ("Start cancellation", #selector(startCancellation)), ("Cancel task", #selector(cancelTask))] {
38
+ let button = UIButton(type: .system)
39
+ button.setTitle(title, for: .normal)
40
+ button.addTarget(self, action: action, for: .touchUpInside)
41
+ stack.addArrangedSubview(button)
42
+ }
43
+ stack.translatesAutoresizingMaskIntoConstraints = false
44
+ view.addSubview(stack)
45
+ NSLayoutConstraint.activate([stack.centerXAnchor.constraint(equalTo: view.centerXAnchor), stack.centerYAnchor.constraint(equalTo: view.centerYAnchor)])
46
+ }
47
+ @objc func calculate() {
48
+ let known = 41
49
+ let result = helper(known) // BREAK_CALCULATE
50
+ label.text = "Debugger result \(result)" // CALCULATE_DONE
51
+ }
52
+ @objc func runAsync() {
53
+ task = Task { @MainActor in
54
+ let result = await AsyncWork().calculate(42)
55
+ label.text = "Async result \(result)"
56
+ }
57
+ }
58
+ @objc func startCancellation() {
59
+ label.text = "Task running"
60
+ task = Task { @MainActor in
61
+ do {
62
+ try await Task.sleep(nanoseconds: 60_000_000_000)
63
+ label.text = "Unexpected completion"
64
+ } catch {
65
+ let cancelled = Task.isCancelled
66
+ label.text = cancelled ? "Task cancelled" : "Unexpected error" // CANCELLED
67
+ }
68
+ }
69
+ }
70
+ @objc func cancelTask() { task?.cancel() }
71
+ }
72
+ @main class App: UIResponder, UIApplicationDelegate {
73
+ var window: UIWindow?
74
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
75
+ window = UIWindow(frame: UIScreen.main.bounds)
76
+ window?.rootViewController = Controller()
77
+ window?.makeKeyAndVisible()
78
+ // Test-only control channel; no HID dependency or debugger evaluation.
79
+ Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { [weak self] _ in
80
+ let commandFile = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("debugger-command")
81
+ guard let command = try? String(contentsOf: commandFile, encoding: .utf8),
82
+ let controller = self?.window?.rootViewController as? Controller else { return }
83
+ try? FileManager.default.removeItem(at: commandFile)
84
+ switch command {
85
+ case "exit": exit(0)
86
+ case "calculate": controller.calculate()
87
+ case "async": controller.runAsync()
88
+ case "start-cancellation": controller.startCancellation()
89
+ case "cancel": controller.cancelTask()
90
+ default: break
91
+ }
92
+ }
93
+ return true
94
+ }
95
+ }
@@ -0,0 +1,27 @@
1
+ # iOS debugger fixture
2
+
3
+ Disposable UIKit app for simulator-only LLDB MCP checks. Builds with Xcode's
4
+ `swiftc -g -Onone`; no external package or user app is used. Select an explicit
5
+ booted simulator:
6
+
7
+ ```sh
8
+ npm run build
9
+ MCP_IOS_DEBUGGER_DEVICE=<UDID> node --import tsx test/device/automated/debugger/runtime.ios.test.ts
10
+ # Include the real 60-second pause expiry:
11
+ MCP_IOS_DEBUGGER_DEVICE=<UDID> MCP_IOS_DEBUGGER_EXTENDED=1 node --import tsx test/device/automated/debugger/runtime.ios.test.ts
12
+ ```
13
+
14
+ Requires macOS, full Xcode with Swift-enabled LLDB/Python, and working idb UI-tree
15
+ reads. The test installs/removes only `dev.mobiledebug.iosfixture`, and cleans its
16
+ private temporary build/module-cache directory. It does not boot/shut down devices.
17
+ The app's fixture-only `Documents/debugger-command` file invokes the same actions
18
+ as its buttons through a main-loop timer. The smoke runner writes only that file
19
+ because the installed idb HID backend expects SimulatorKit at a path absent in
20
+ this Xcode. This is a fixture trigger, not evidence that MCP tap delivery
21
+ succeeded. Paused MCP taps must be rejected before delivery. Completion and
22
+ cancellation use fresh MCP UI-tree assertions.
23
+
24
+ Async work uses a suspended child operation in an actor, known values on either
25
+ side of await, a main-actor UI update and a separate cancellation task. Boundary
26
+ stepping is recorded independently from source-breakpoint coverage. Stop-scoped
27
+ thread/frame IDs do not establish logical task identity.
@@ -0,0 +1,21 @@
1
+ import { execFileSync } from 'node:child_process'
2
+ import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'
3
+ import os from 'node:os'
4
+ import path from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+ export const iosDebuggerAppId = 'dev.mobiledebug.iosfixture'
7
+ export const iosDebuggerSource = fileURLToPath(new URL('./Main.swift', import.meta.url))
8
+ export function buildIOSDebuggerFixture() {
9
+ const directory = mkdtempSync(path.join(os.tmpdir(), 'mobile-debug-ios-fixture-'))
10
+ const app = path.join(directory, 'DebuggerFixture.app')
11
+ try {
12
+ execFileSync('/bin/mkdir', ['-p', app])
13
+ const sdk = execFileSync('xcrun', ['--sdk', 'iphonesimulator', '--show-sdk-path'], { encoding: 'utf8' }).trim()
14
+ execFileSync('xcrun', ['swiftc', '-g', '-module-cache-path', path.join(directory, 'modules'), '-Onone', '-parse-as-library', '-sdk', sdk, '-target', `${os.arch() === 'arm64' ? 'arm64' : 'x86_64'}-apple-ios18.0-simulator`, '-module-name', 'DebuggerFixture', iosDebuggerSource, '-o', path.join(app, 'DebuggerFixture')], { timeout: 120000, stdio: 'pipe' })
15
+ writeFileSync(path.join(app, 'Info.plist'), `<?xml version="1.0"?><plist version="1.0"><dict><key>CFBundleIdentifier</key><string>${iosDebuggerAppId}</string><key>CFBundleExecutable</key><string>DebuggerFixture</string><key>CFBundleName</key><string>DebuggerFixture</string><key>CFBundlePackageType</key><string>APPL</string><key>CFBundleVersion</key><string>1</string><key>CFBundleShortVersionString</key><string>1.0</string><key>LSRequiresIPhoneOS</key><true/><key>UILaunchScreen</key><dict/></dict></plist>`)
16
+ return { app, directory }
17
+ } catch (error) {
18
+ rmSync(directory, { recursive: true, force: true })
19
+ throw error
20
+ }
21
+ }
@@ -1,5 +1,7 @@
1
1
  # Interaction latency fixture
2
2
 
3
+ [Contributing](../../../CONTRIBUTING.md) · [Documentation](../../../docs/README.md)
4
+
3
5
  An offline native Android fixture, version 1.0. It requires Android SDK platform 36,
4
6
  build-tools 36.0.0 and JDK 21. The harness builds and signs it using those installed
5
7
  tools; no Gradle dependency download is needed.
@@ -1,5 +1,7 @@
1
1
  # Readiness fixture
2
2
 
3
+ [Contributing](../../../CONTRIBUTING.md) · [Documentation](../../../docs/README.md)
4
+
3
5
  Offline native Android/iOS simulator apps used only for explicit readiness
4
6
  verification. Ready stays visible while Show loader starts a 12-second loader;
5
7
  completion hides the loader and changes its status to Done. No permissions,
@@ -0,0 +1,29 @@
1
+ import assert from 'node:assert/strict'
2
+ import { promises as fs } from 'node:fs'
3
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js'
4
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
5
+
6
+ export async function verifyIosTraceShutdown(target: Record<string,unknown>, env: Record<string,string> = {}) {
7
+ const client=new Client({name:'ios-trace-shutdown',version:'1.0.0'})
8
+ const transport=new StdioClientTransport({command:process.execPath,args:['dist/server.js'],stderr:'pipe',env})
9
+ let diagnostics=''
10
+ transport.stderr?.on('data',chunk=>{diagnostics=(diagnostics+String(chunk)).slice(-4096)})
11
+ try {
12
+ await client.connect(transport)
13
+ const capability=await client.callTool({name:'get_system_trace_capabilities',arguments:target},undefined,{timeout:120000})
14
+ const capabilities=JSON.parse((capability.content as any[]).find(c=>c.type==='text').text)
15
+ assert.equal(capabilities.supported,true,JSON.stringify(capabilities))
16
+ const response=await client.callTool({name:'start_system_trace',arguments:{...target,durationMs:30000}},undefined,{timeout:120000})
17
+ const result=JSON.parse((response.content as any[]).find(c=>c.type==='text').text)
18
+ assert.equal(result.state,'recording',JSON.stringify(result))
19
+ const directory=result.cleanup.remaining_resources[0]
20
+ assert.ok(transport.pid)
21
+ process.kill(transport.pid,'SIGTERM')
22
+ let removed=false
23
+ for(let i=0;i<24;i++){
24
+ try {await fs.stat(directory)} catch(error) {if((error as NodeJS.ErrnoException).code==='ENOENT'){removed=true;break}throw error}
25
+ await new Promise(resolve=>setTimeout(resolve,500))
26
+ }
27
+ assert.equal(removed,true,`Server shutdown must remove its owned partial trace directory; ${diagnostics}`)
28
+ } finally {await client.close()}
29
+ }
@@ -0,0 +1,43 @@
1
+ import assert from 'node:assert/strict'
2
+ import { deviceForwards, reportAfterCleanup } from '../../device/automated/debugger/coroutine-lifecycle.js'
3
+
4
+ const baseline = deviceForwards('emulator-5554 tcp:2 jdwp:20\nemulator-5554 tcp:1 jdwp:10\nother-device tcp:3 jdwp:30', 'emulator-5554')
5
+ assert.deepEqual(deviceForwards('other-device tcp:4 jdwp:40\nemulator-5554 tcp:1 jdwp:10\nemulator-5554 tcp:2 jdwp:20\n', 'emulator-5554'), baseline)
6
+ assert.notDeepEqual(deviceForwards('emulator-5554 tcp:1 jdwp:10', 'emulator-5554'), baseline)
7
+ assert.deepEqual(deviceForwards('emulator-55540 tcp:1 jdwp:10', 'emulator-5554'), [])
8
+
9
+ const order: string[] = []
10
+ await reportAfterCleanup(async () => { order.push('work'); return 42 }, async () => { order.push('cleanup') }, (status, result) => {
11
+ order.push(status)
12
+ assert.equal(result, 42)
13
+ })
14
+ assert.deepEqual(order, ['work', 'cleanup', 'PASS'])
15
+
16
+ // Reproduce the review bug: cleanup rejects after work succeeds or fails.
17
+ // The runner must reject and emit only FAIL in both cases.
18
+ for (const workFails of [false, true]) {
19
+ const records: string[] = []
20
+ const cleanupError = new Error('cleanup')
21
+ const journeyError = new Error('journey')
22
+ await assert.rejects(reportAfterCleanup(async () => {
23
+ if (workFails) throw journeyError
24
+ return 42
25
+ }, async () => { throw cleanupError }, (status, evidence, error) => {
26
+ records.push(status)
27
+ assert.equal(evidence, undefined)
28
+ if (workFails) {
29
+ assert.ok(error instanceof AggregateError)
30
+ assert.deepEqual(error.errors, [journeyError, cleanupError])
31
+ } else assert.equal(error, cleanupError)
32
+ }), error => workFails ? error instanceof AggregateError : error === cleanupError)
33
+ assert.deepEqual(records, ['FAIL'])
34
+ }
35
+ let cleaned = false
36
+ const workError = new Error('journey')
37
+ await assert.rejects(reportAfterCleanup(async () => { throw workError }, async () => { cleaned = true }, (status, evidence, error) => {
38
+ assert.equal(cleaned, true)
39
+ assert.equal(status, 'FAIL')
40
+ assert.equal(evidence, undefined)
41
+ assert.equal(error, workError)
42
+ }), error => error === workError)
43
+ console.log('PASS coroutine assessment: cleanup precedes PASS; work/cleanup failures report FAIL; forwards scoped and sorted')