taphound 0.2.0-dev.1 → 0.2.0-dev.11

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 (447) hide show
  1. package/README.md +49 -149
  2. package/README.zh-CN.md +47 -147
  3. package/assets/skills/taphound-case-suite/SKILL.md +363 -0
  4. package/assets/skills/taphound-case-suite/schemas/base-flow-record.schema.json +22 -0
  5. package/assets/skills/taphound-case-suite/schemas/case-catalog.schema.json +26 -0
  6. package/assets/skills/taphound-case-suite/schemas/case-ledger.schema.json +158 -0
  7. package/assets/skills/taphound-case-suite/schemas/suite-input.schema.json +41 -0
  8. package/assets/skills/taphound-case-suite/schemas/transition.schema.json +61 -0
  9. package/assets/skills/taphound-case-suite/scripts/ledger.mjs +1207 -0
  10. package/assets/skills/taphound-case-suite/templates/base-flow-record.example.json +20 -0
  11. package/assets/skills/taphound-case-suite/templates/suite-input.example.json +27 -0
  12. package/assets/skills/taphound-case-suite/templates/transition.example.json +8 -0
  13. package/assets/skills/taphound-flash/SKILL.md +115 -0
  14. package/assets/skills/taphound-flash/scripts/flash.mjs +585 -0
  15. package/assets/skills/taphound-flash/templates/flash-plan.example.json +15 -0
  16. package/assets/skills/taphound-journey-brief-author/CONTEXT-GUIDE.md +471 -0
  17. package/assets/skills/taphound-journey-brief-author/SKILL.md +494 -0
  18. package/assets/skills/taphound-journey-brief-author/prompts/brief-author-role.md +96 -0
  19. package/assets/skills/taphound-journey-brief-author/prompts/brief-author-role.zh-CN.md +80 -0
  20. package/assets/skills/taphound-journey-brief-author/prompts/context-analyze-project.md +309 -0
  21. package/assets/skills/taphound-journey-brief-author/schemas/context-refresh-result.json +123 -0
  22. package/assets/skills/taphound-journey-brief-author/schemas/project-context-module.json +192 -0
  23. package/assets/skills/taphound-journey-brief-author/schemas/project-context.json +152 -0
  24. package/assets/skills/taphound-journey-brief-author/templates/project-context-module.example.json +58 -0
  25. package/assets/skills/taphound-journey-brief-author/templates/project-context.example.json +51 -0
  26. package/assets/skills/taphound-journey-brief-author/templates/taphound-journey-brief.template.md +63 -0
  27. package/assets/skills/taphound-journey-generator/SKILL.md +500 -0
  28. package/assets/skills/{taphound-ai-journey → taphound-journey-generator}/prompts/check-completion.md +3 -0
  29. package/assets/skills/taphound-journey-generator/prompts/consume-journey-brief.md +52 -0
  30. package/assets/skills/taphound-journey-generator/prompts/generate-step.md +211 -0
  31. package/assets/skills/taphound-journey-generator/prompts/select-flow.md +91 -0
  32. package/assets/skills/taphound-journey-generator/references/bridge.md +35 -0
  33. package/assets/skills/taphound-journey-generator/references/mid-session.md +44 -0
  34. package/assets/skills/taphound-journey-generator/schemas/external-flow.json +37 -0
  35. package/assets/skills/taphound-journey-generator/schemas/flow.json +28 -0
  36. package/assets/skills/taphound-journey-generator/schemas/journey-source.json +25 -0
  37. package/assets/skills/{taphound-ai-journey → taphound-journey-generator}/schemas/observe-output.json +13 -4
  38. package/assets/skills/{taphound-ai-journey → taphound-journey-generator}/schemas/proposed-step-envelope.json +127 -5
  39. package/assets/skills/taphound-journey-generator/scripts/envelope.mjs +561 -0
  40. package/assets/skills/taphound-journey-generator/templates/external-flow.example.json +22 -0
  41. package/assets/skills/taphound-journey-generator/templates/flow.example.json +22 -0
  42. package/assets/skills/taphound-journey-generator/templates/journey-source.example.json +17 -0
  43. package/assets/skills/taphound-journey-generator/templates/taphound-journey-brief.example.md +70 -0
  44. package/assets/skills/taphound-verify-change/SKILL.md +91 -0
  45. package/assets/skills/taphound-verify-change/references/accept.md +38 -0
  46. package/assets/skills/taphound-verify-change/references/preserve.md +349 -0
  47. package/assets/skills/taphound-verify-change/schemas/workflow-manifest.schema.json +295 -0
  48. package/assets/skills/taphound-verify-change/scripts/handoff.mjs +482 -0
  49. package/assets/skills/taphound-verify-change/scripts/ui-refactor.mjs +505 -0
  50. package/dist/adapters/adb/adb-adapter.d.ts +16 -2
  51. package/dist/adapters/adb/adb-adapter.js +199 -0
  52. package/dist/adapters/adb/system-uiautomator-snapshot-provider.d.ts +13 -0
  53. package/dist/adapters/adb/system-uiautomator-snapshot-provider.js +122 -0
  54. package/dist/adapters/adb/ui-automator-parser.d.ts +3 -0
  55. package/dist/adapters/adb/ui-automator-parser.js +160 -0
  56. package/dist/adapters/adb/window-topology-parser.d.ts +2 -0
  57. package/dist/adapters/adb/window-topology-parser.js +101 -0
  58. package/dist/adapters/android-cli/android-cli-adapter.d.ts +16 -8
  59. package/dist/adapters/android-cli/android-cli-adapter.js +137 -15
  60. package/dist/adapters/android-cli/android-cli-snapshot-provider.d.ts +9 -0
  61. package/dist/adapters/android-cli/android-cli-snapshot-provider.js +99 -0
  62. package/dist/adapters/android-cli/layout-parser.js +12 -8
  63. package/dist/adapters/appium/appium-doctor.d.ts +7 -0
  64. package/dist/adapters/appium/appium-doctor.js +128 -0
  65. package/dist/adapters/appium/appium-ui-snapshot-provider.d.ts +34 -0
  66. package/dist/adapters/appium/appium-ui-snapshot-provider.js +240 -0
  67. package/dist/adapters/camera/camera-probe-adapter.d.ts +18 -0
  68. package/dist/adapters/camera/camera-probe-adapter.js +286 -0
  69. package/dist/adapters/filesystem/artifact-store.js +40 -1
  70. package/dist/adapters/filesystem/context-document-writer.d.ts +15 -0
  71. package/dist/adapters/filesystem/context-document-writer.js +112 -0
  72. package/dist/adapters/filesystem/external-flow-registry.d.ts +17 -0
  73. package/dist/adapters/filesystem/external-flow-registry.js +325 -0
  74. package/dist/adapters/filesystem/generation-session-store.d.ts +17 -11
  75. package/dist/adapters/filesystem/generation-session-store.js +281 -855
  76. package/dist/adapters/filesystem/generation-store/session-lock.d.ts +29 -0
  77. package/dist/adapters/filesystem/generation-store/session-lock.js +202 -0
  78. package/dist/adapters/filesystem/generation-store/session-transitions.d.ts +24 -0
  79. package/dist/adapters/filesystem/generation-store/session-transitions.js +340 -0
  80. package/dist/adapters/filesystem/generation-store/store-files.d.ts +50 -0
  81. package/dist/adapters/filesystem/generation-store/store-files.js +363 -0
  82. package/dist/adapters/filesystem/journey-composition-store.d.ts +23 -0
  83. package/dist/adapters/filesystem/journey-composition-store.js +151 -0
  84. package/dist/adapters/filesystem/knowledge-registry.d.ts +12 -0
  85. package/dist/adapters/filesystem/knowledge-registry.js +223 -0
  86. package/dist/adapters/filesystem/project-bound-file.js +3 -5
  87. package/dist/adapters/filesystem/project-file-inspector.d.ts +2 -0
  88. package/dist/adapters/filesystem/project-file-inspector.js +57 -28
  89. package/dist/adapters/filesystem/project-identity-inspector.d.ts +7 -0
  90. package/dist/adapters/filesystem/project-identity-inspector.js +215 -0
  91. package/dist/adapters/filesystem/project-inventory-inspector.d.ts +8 -0
  92. package/dist/adapters/filesystem/project-inventory-inspector.js +93 -0
  93. package/dist/adapters/filesystem/project-module-discoverer.d.ts +6 -0
  94. package/dist/adapters/filesystem/project-module-discoverer.js +158 -0
  95. package/dist/adapters/filesystem/skill-installer.d.ts +7 -2
  96. package/dist/adapters/filesystem/skill-installer.js +40 -11
  97. package/dist/adapters/filesystem/workspace-layout.d.ts +7 -0
  98. package/dist/adapters/filesystem/workspace-layout.js +57 -0
  99. package/dist/adapters/git/node-git-diff.d.ts +17 -0
  100. package/dist/adapters/git/node-git-diff.js +114 -0
  101. package/dist/adapters/process/node-detached-process-launcher.d.ts +6 -0
  102. package/dist/adapters/process/node-detached-process-launcher.js +96 -0
  103. package/dist/adapters/process/node-process-runner.js +11 -2
  104. package/dist/adapters/prompt/inquirer-align-prompt.d.ts +27 -0
  105. package/dist/adapters/prompt/inquirer-align-prompt.js +65 -0
  106. package/dist/adapters/prompt/inquirer-generation-prompt.js +3 -0
  107. package/dist/adapters/prompt/inquirer-recorder-prompt.d.ts +9 -1
  108. package/dist/adapters/prompt/inquirer-recorder-prompt.js +63 -0
  109. package/dist/adapters/runtime/adb-runtime-backend.d.ts +67 -0
  110. package/dist/adapters/runtime/adb-runtime-backend.js +206 -0
  111. package/dist/adapters/runtime/fake-runtime-backend.d.ts +87 -0
  112. package/dist/adapters/runtime/fake-runtime-backend.js +240 -0
  113. package/dist/adapters/runtime/mobile-mcp/mcp-tool-client.d.ts +45 -0
  114. package/dist/adapters/runtime/mobile-mcp/mcp-tool-client.js +153 -0
  115. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-errors.d.ts +23 -0
  116. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-errors.js +39 -0
  117. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-responses.d.ts +20 -0
  118. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-responses.js +180 -0
  119. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-runtime-backend.d.ts +78 -0
  120. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-runtime-backend.js +379 -0
  121. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-tools.d.ts +46 -0
  122. package/dist/adapters/runtime/session-adb-view.d.ts +53 -0
  123. package/dist/adapters/runtime/session-adb-view.js +193 -0
  124. package/dist/adapters/runtime/session-backed-ports.d.ts +21 -0
  125. package/dist/adapters/runtime/session-backed-ports.js +43 -0
  126. package/dist/adapters/runtime/shared-session-runtime-backend.d.ts +12 -0
  127. package/dist/adapters/runtime/shared-session-runtime-backend.js +64 -0
  128. package/dist/adapters/ui/auto-ui-snapshot-provider.d.ts +8 -0
  129. package/dist/adapters/ui/auto-ui-snapshot-provider.js +48 -0
  130. package/dist/adapters/ui/device-layout-temp.d.ts +6 -0
  131. package/dist/adapters/ui/device-layout-temp.js +18 -0
  132. package/dist/adapters/ui/device-ui-environment.d.ts +13 -0
  133. package/dist/adapters/ui/device-ui-environment.js +81 -0
  134. package/dist/adapters/ui/layout-normalization.d.ts +9 -0
  135. package/dist/adapters/ui/layout-normalization.js +44 -0
  136. package/dist/adapters/ui/ui-snapshot-error.d.ts +11 -0
  137. package/dist/adapters/ui/ui-snapshot-error.js +12 -0
  138. package/dist/adapters/ui/ui-snapshot-support.d.ts +15 -0
  139. package/dist/adapters/ui/ui-snapshot-support.js +12 -0
  140. package/dist/application/align/align-service.d.ts +42 -0
  141. package/dist/application/align/align-service.js +106 -0
  142. package/dist/application/assertion/expectation-evaluator.d.ts +18 -5
  143. package/dist/application/assertion/expectation-evaluator.js +156 -18
  144. package/dist/application/assertion/guarded-expectation.d.ts +45 -0
  145. package/dist/application/assertion/guarded-expectation.js +74 -0
  146. package/dist/application/checkpoint/baseline-capturer.d.ts +26 -0
  147. package/dist/application/checkpoint/baseline-capturer.js +163 -0
  148. package/dist/application/checkpoint/baseline-error.d.ts +5 -0
  149. package/dist/application/checkpoint/baseline-error.js +8 -0
  150. package/dist/application/checkpoint/baseline-service.d.ts +31 -0
  151. package/dist/application/checkpoint/baseline-service.js +147 -0
  152. package/dist/application/checkpoint/checkpoint-evaluator.d.ts +35 -0
  153. package/dist/application/checkpoint/checkpoint-evaluator.js +415 -0
  154. package/dist/application/checkpoint/regression-comparator.d.ts +18 -0
  155. package/dist/application/checkpoint/regression-comparator.js +192 -0
  156. package/dist/application/collector/logcat-collector.d.ts +22 -1
  157. package/dist/application/collector/logcat-collector.js +103 -15
  158. package/dist/application/collector/logcat-event.d.ts +8 -0
  159. package/dist/application/collector/logcat-event.js +79 -0
  160. package/dist/application/collector/logcat-stop.d.ts +2 -0
  161. package/dist/application/collector/logcat-stop.js +11 -0
  162. package/dist/application/context/context-generator.d.ts +45 -0
  163. package/dist/application/context/context-generator.js +228 -0
  164. package/dist/application/context/context-loader.d.ts +40 -0
  165. package/dist/application/context/context-loader.js +223 -0
  166. package/dist/application/context/context-refresher.d.ts +59 -0
  167. package/dist/application/context/context-refresher.js +349 -0
  168. package/dist/application/context/context-rehasher.d.ts +36 -0
  169. package/dist/application/context/context-rehasher.js +110 -0
  170. package/dist/application/context/context-validator.d.ts +23 -1
  171. package/dist/application/context/context-validator.js +116 -9
  172. package/dist/application/context/evidence-hash.d.ts +1 -0
  173. package/dist/application/context/evidence-hash.js +125 -0
  174. package/dist/application/context/evidence-match.d.ts +9 -0
  175. package/dist/application/context/evidence-match.js +8 -0
  176. package/dist/application/context/shard-identity.d.ts +2 -0
  177. package/dist/application/context/shard-identity.js +7 -0
  178. package/dist/application/contract/contract-loader.d.ts +25 -0
  179. package/dist/application/contract/contract-loader.js +68 -0
  180. package/dist/application/contract/contract-review.d.ts +25 -0
  181. package/dist/application/contract/contract-review.js +51 -0
  182. package/dist/application/contract/contract-verifier.d.ts +53 -0
  183. package/dist/application/contract/contract-verifier.js +645 -0
  184. package/dist/application/devices/resolve-device-assignments.d.ts +21 -0
  185. package/dist/application/devices/resolve-device-assignments.js +60 -0
  186. package/dist/application/diagnosis/failure-classifier.d.ts +23 -0
  187. package/dist/application/diagnosis/failure-classifier.js +179 -0
  188. package/dist/application/doctor/doctor-service.d.ts +36 -3
  189. package/dist/application/doctor/doctor-service.js +255 -25
  190. package/dist/application/generation/generation-app-preparer.d.ts +20 -0
  191. package/dist/application/generation/generation-app-preparer.js +28 -0
  192. package/dist/application/generation/generation-config-service.d.ts +21 -0
  193. package/dist/application/generation/generation-config-service.js +53 -0
  194. package/dist/application/generation/generation-confirmation-service.d.ts +14 -3
  195. package/dist/application/generation/generation-confirmation-service.js +109 -24
  196. package/dist/application/generation/generation-context-snapshot.d.ts +6 -0
  197. package/dist/application/generation/generation-context-snapshot.js +29 -0
  198. package/dist/application/generation/generation-finalizer.d.ts +15 -9
  199. package/dist/application/generation/generation-finalizer.js +167 -70
  200. package/dist/application/generation/generation-publisher.js +12 -8
  201. package/dist/application/generation/generation-recovery-service.d.ts +35 -0
  202. package/dist/application/generation/generation-recovery-service.js +144 -0
  203. package/dist/application/generation/generation-reopen-service.d.ts +15 -0
  204. package/dist/application/generation/generation-reopen-service.js +37 -0
  205. package/dist/application/generation/generation-replace-service.d.ts +32 -0
  206. package/dist/application/generation/generation-replace-service.js +116 -0
  207. package/dist/application/generation/generation-starter.d.ts +44 -4
  208. package/dist/application/generation/generation-starter.js +175 -8
  209. package/dist/application/generation/generation-step-executor.d.ts +67 -5
  210. package/dist/application/generation/generation-step-executor.js +471 -108
  211. package/dist/application/generation/proposed-step-validator.js +96 -30
  212. package/dist/application/generation/replay-policy-loader.d.ts +16 -0
  213. package/dist/application/generation/replay-policy-loader.js +29 -0
  214. package/dist/application/generation/risk-evaluator.d.ts +4 -1
  215. package/dist/application/generation/risk-evaluator.js +118 -3
  216. package/dist/application/generation/runtime-observer.d.ts +39 -7
  217. package/dist/application/generation/runtime-observer.js +193 -24
  218. package/dist/application/impact/impact-resolver.d.ts +32 -0
  219. package/dist/application/impact/impact-resolver.js +160 -0
  220. package/dist/application/init/init-service.js +26 -13
  221. package/dist/application/interaction/action-executor.d.ts +3 -2
  222. package/dist/application/interaction/action-executor.js +11 -1
  223. package/dist/application/interaction/action-target.d.ts +17 -0
  224. package/dist/application/interaction/action-target.js +40 -0
  225. package/dist/application/interaction/external-step-runner.d.ts +153 -0
  226. package/dist/application/interaction/external-step-runner.js +332 -0
  227. package/dist/application/interaction/fallback-resolver.d.ts +6 -3
  228. package/dist/application/interaction/fallback-resolver.js +7 -5
  229. package/dist/application/interaction/scroll-to-executor.d.ts +17 -3
  230. package/dist/application/interaction/scroll-to-executor.js +118 -36
  231. package/dist/application/journey/external-flow-resolver.d.ts +25 -0
  232. package/dist/application/journey/external-flow-resolver.js +22 -0
  233. package/dist/application/journey/journey-check-service.d.ts +54 -0
  234. package/dist/application/journey/journey-check-service.js +207 -0
  235. package/dist/application/journey/journey-promoter.d.ts +26 -0
  236. package/dist/application/journey/journey-promoter.js +123 -0
  237. package/dist/application/journey/journey-resolver.d.ts +42 -0
  238. package/dist/application/journey/journey-resolver.js +239 -0
  239. package/dist/application/journey/journey-retirer.d.ts +25 -0
  240. package/dist/application/journey/journey-retirer.js +87 -0
  241. package/dist/application/knowledge/anchor-resolver.d.ts +36 -0
  242. package/dist/application/knowledge/anchor-resolver.js +110 -0
  243. package/dist/application/knowledge/knowledge-loader.d.ts +16 -0
  244. package/dist/application/knowledge/knowledge-loader.js +36 -0
  245. package/dist/application/locator/layout-failure-summary.d.ts +2 -0
  246. package/dist/application/locator/layout-failure-summary.js +67 -0
  247. package/dist/application/locator/layout-traversal.d.ts +11 -0
  248. package/dist/application/locator/layout-traversal.js +18 -0
  249. package/dist/application/locator/locator-resolver.d.ts +13 -1
  250. package/dist/application/locator/locator-resolver.js +182 -18
  251. package/dist/application/observe/observe-service.d.ts +20 -0
  252. package/dist/application/observe/observe-service.js +91 -0
  253. package/dist/application/project/project-describer.d.ts +14 -6
  254. package/dist/application/project/project-describer.js +38 -5
  255. package/dist/application/recognition/screen-detector.d.ts +28 -0
  256. package/dist/application/recognition/screen-detector.js +111 -0
  257. package/dist/application/recorder/locator-selector.js +151 -17
  258. package/dist/application/recorder/recorder-service.d.ts +16 -4
  259. package/dist/application/recorder/recorder-service.js +420 -128
  260. package/dist/application/report/report-writer.js +1 -1
  261. package/dist/application/runtime/cold-launch.d.ts +28 -0
  262. package/dist/application/runtime/cold-launch.js +59 -0
  263. package/dist/application/runtime/step-runner.d.ts +49 -4
  264. package/dist/application/runtime/step-runner.js +721 -130
  265. package/dist/application/runtime/verify-runtime.d.ts +57 -7
  266. package/dist/application/runtime/verify-runtime.js +554 -174
  267. package/dist/application/ui/cached-ui-snapshot-provider.d.ts +45 -0
  268. package/dist/application/ui/cached-ui-snapshot-provider.js +111 -0
  269. package/dist/application/ui/ui-snapshot-lifecycle.d.ts +2 -0
  270. package/dist/application/ui/ui-snapshot-lifecycle.js +8 -0
  271. package/dist/application/ui/ui-stability-probe.d.ts +3 -0
  272. package/dist/application/ui/ui-stability-probe.js +8 -0
  273. package/dist/application/wait/idle-advice.d.ts +6 -0
  274. package/dist/application/wait/idle-advice.js +27 -0
  275. package/dist/application/wait/idle-profiles.d.ts +15 -0
  276. package/dist/application/wait/idle-profiles.js +75 -0
  277. package/dist/application/wait/idle-waiter.d.ts +28 -4
  278. package/dist/application/wait/idle-waiter.js +186 -15
  279. package/dist/cli/commands/align.d.ts +3 -0
  280. package/dist/cli/commands/align.js +124 -0
  281. package/dist/cli/commands/baseline.d.ts +3 -0
  282. package/dist/cli/commands/baseline.js +130 -0
  283. package/dist/cli/commands/context.js +300 -27
  284. package/dist/cli/commands/contract.d.ts +3 -0
  285. package/dist/cli/commands/contract.js +120 -0
  286. package/dist/cli/commands/doctor.js +16 -9
  287. package/dist/cli/commands/failure.d.ts +3 -0
  288. package/dist/cli/commands/failure.js +63 -0
  289. package/dist/cli/commands/generation/lifecycle-commands.d.ts +5 -0
  290. package/dist/cli/commands/generation/lifecycle-commands.js +211 -0
  291. package/dist/cli/commands/generation/session-commands.d.ts +11 -0
  292. package/dist/cli/commands/generation/session-commands.js +472 -0
  293. package/dist/cli/commands/generation/shared.d.ts +1414 -0
  294. package/dist/cli/commands/generation/shared.js +291 -0
  295. package/dist/cli/commands/generation/step-commands.d.ts +8 -0
  296. package/dist/cli/commands/generation/step-commands.js +315 -0
  297. package/dist/cli/commands/generation.js +10 -507
  298. package/dist/cli/commands/impact.d.ts +3 -0
  299. package/dist/cli/commands/impact.js +87 -0
  300. package/dist/cli/commands/init.js +13 -6
  301. package/dist/cli/commands/journey.d.ts +3 -0
  302. package/dist/cli/commands/journey.js +382 -0
  303. package/dist/cli/commands/knowledge.d.ts +3 -0
  304. package/dist/cli/commands/knowledge.js +93 -0
  305. package/dist/cli/commands/observe.d.ts +3 -0
  306. package/dist/cli/commands/observe.js +127 -0
  307. package/dist/cli/commands/project.js +2 -1
  308. package/dist/cli/commands/record.js +18 -5
  309. package/dist/cli/commands/verify.js +273 -63
  310. package/dist/cli/dependencies.d.ts +125 -1
  311. package/dist/cli/dependencies.js +450 -51
  312. package/dist/cli/diff-verification.d.ts +41 -0
  313. package/dist/cli/diff-verification.js +192 -0
  314. package/dist/cli/main.js +49 -1
  315. package/dist/cli/output.d.ts +5 -3
  316. package/dist/cli/output.js +12 -0
  317. package/dist/cli/program.js +19 -1
  318. package/dist/cli/project-root.d.ts +1 -0
  319. package/dist/cli/project-root.js +6 -0
  320. package/dist/cli/runtime-selection.d.ts +22 -0
  321. package/dist/cli/runtime-selection.js +92 -0
  322. package/dist/cli/version.d.ts +1 -0
  323. package/dist/cli/version.js +14 -0
  324. package/dist/cli/workspace-guard.d.ts +2 -0
  325. package/dist/cli/workspace-guard.js +3 -0
  326. package/dist/domain/binding-reference.d.ts +4 -0
  327. package/dist/domain/binding-reference.js +23 -0
  328. package/dist/domain/checkpoint.d.ts +404 -0
  329. package/dist/domain/checkpoint.js +297 -0
  330. package/dist/domain/config.d.ts +107 -1
  331. package/dist/domain/config.js +52 -6
  332. package/dist/domain/contract.d.ts +413 -0
  333. package/dist/domain/contract.js +196 -0
  334. package/dist/domain/external-flow.d.ts +493 -0
  335. package/dist/domain/external-flow.js +15 -0
  336. package/dist/domain/failure-classification.d.ts +176 -0
  337. package/dist/domain/failure-classification.js +169 -0
  338. package/dist/domain/failure.d.ts +2 -1
  339. package/dist/domain/failure.js +123 -0
  340. package/dist/domain/generation.d.ts +1671 -132
  341. package/dist/domain/generation.js +202 -13
  342. package/dist/domain/geometry.d.ts +13 -0
  343. package/dist/domain/geometry.js +16 -0
  344. package/dist/domain/impact.d.ts +71 -0
  345. package/dist/domain/impact.js +76 -0
  346. package/dist/domain/init.d.ts +12 -4
  347. package/dist/domain/init.js +24 -18
  348. package/dist/domain/journey-composition.d.ts +2285 -0
  349. package/dist/domain/journey-composition.js +85 -0
  350. package/dist/domain/journey-lifecycle.d.ts +10 -0
  351. package/dist/domain/journey-lifecycle.js +9 -0
  352. package/dist/domain/journey.d.ts +2419 -212
  353. package/dist/domain/journey.js +384 -14
  354. package/dist/domain/knowledge.d.ts +165 -0
  355. package/dist/domain/knowledge.js +188 -0
  356. package/dist/domain/layout.d.ts +34 -5
  357. package/dist/domain/layout.js +64 -6
  358. package/dist/domain/locator-evidence.d.ts +3 -0
  359. package/dist/domain/locator-evidence.js +36 -0
  360. package/dist/domain/logcat-event.d.ts +54 -0
  361. package/dist/domain/logcat-event.js +40 -0
  362. package/dist/domain/observation.d.ts +33 -0
  363. package/dist/domain/observation.js +18 -0
  364. package/dist/domain/project-context.d.ts +219 -1
  365. package/dist/domain/project-context.js +209 -6
  366. package/dist/domain/proposed-step.d.ts +340 -60
  367. package/dist/domain/proposed-step.js +23 -3
  368. package/dist/domain/report.d.ts +1455 -28
  369. package/dist/domain/report.js +211 -14
  370. package/dist/domain/runtime-snapshot.d.ts +58 -1
  371. package/dist/domain/runtime-snapshot.js +14 -3
  372. package/dist/domain/runtime.d.ts +50 -0
  373. package/dist/domain/runtime.js +36 -0
  374. package/dist/domain/system-app-profiles.d.ts +6 -0
  375. package/dist/domain/system-app-profiles.js +43 -0
  376. package/dist/domain/ui-backend.d.ts +28 -0
  377. package/dist/domain/ui-backend.js +33 -0
  378. package/dist/domain/ui-cache.d.ts +11 -0
  379. package/dist/domain/ui-cache.js +10 -0
  380. package/dist/domain/window-hierarchy.d.ts +84 -0
  381. package/dist/domain/window-hierarchy.js +172 -0
  382. package/dist/domain/workflow-manifest.d.ts +77 -0
  383. package/dist/domain/workflow-manifest.js +136 -0
  384. package/dist/domain/workspace.d.ts +28 -0
  385. package/dist/domain/workspace.js +72 -0
  386. package/dist/ports/adb.d.ts +34 -1
  387. package/dist/ports/align-prompt.d.ts +11 -0
  388. package/dist/ports/align-prompt.js +6 -0
  389. package/dist/ports/anchor-resolver.d.ts +30 -0
  390. package/dist/ports/anchor-resolver.js +1 -0
  391. package/dist/ports/annotated-screen-resolver.d.ts +4 -0
  392. package/dist/ports/annotated-screen-resolver.js +1 -0
  393. package/dist/ports/camera-probe.d.ts +23 -0
  394. package/dist/ports/camera-probe.js +8 -0
  395. package/dist/ports/context-document-writer.d.ts +24 -0
  396. package/dist/ports/context-document-writer.js +1 -0
  397. package/dist/ports/detached-process-launcher.d.ts +12 -0
  398. package/dist/ports/detached-process-launcher.js +1 -0
  399. package/dist/ports/external-flow-registry.d.ts +37 -0
  400. package/dist/ports/external-flow-registry.js +1 -0
  401. package/dist/ports/generation-prompt.d.ts +2 -1
  402. package/dist/ports/generation-session-store.d.ts +12 -2
  403. package/dist/ports/git-diff.d.ts +9 -0
  404. package/dist/ports/git-diff.js +1 -0
  405. package/dist/ports/journey-composition-store.d.ts +17 -0
  406. package/dist/ports/journey-composition-store.js +1 -0
  407. package/dist/ports/knowledge-registry.d.ts +20 -0
  408. package/dist/ports/knowledge-registry.js +1 -0
  409. package/dist/ports/process-runner.d.ts +7 -0
  410. package/dist/ports/project-file-inspector.d.ts +1 -0
  411. package/dist/ports/project-identity-inspector.d.ts +24 -0
  412. package/dist/ports/project-identity-inspector.js +1 -0
  413. package/dist/ports/project-inventory-inspector.d.ts +21 -0
  414. package/dist/ports/project-inventory-inspector.js +1 -0
  415. package/dist/ports/project-module-discoverer.d.ts +26 -0
  416. package/dist/ports/project-module-discoverer.js +1 -0
  417. package/dist/ports/recorder-prompt.d.ts +10 -1
  418. package/dist/ports/runtime-backend.d.ts +89 -0
  419. package/dist/ports/runtime-backend.js +13 -0
  420. package/dist/ports/runtime-capability.d.ts +12 -0
  421. package/dist/ports/runtime-capability.js +20 -0
  422. package/dist/ports/runtime-session-ports.d.ts +25 -0
  423. package/dist/ports/runtime-session-ports.js +1 -0
  424. package/dist/ports/screenshot.d.ts +11 -0
  425. package/dist/ports/screenshot.js +1 -0
  426. package/dist/ports/skill-installer.d.ts +2 -2
  427. package/dist/ports/ui-snapshot.d.ts +35 -0
  428. package/dist/ports/ui-snapshot.js +1 -0
  429. package/dist/ports/ui-stability.d.ts +19 -0
  430. package/dist/ports/ui-stability.js +1 -0
  431. package/dist/ports/workspace-layout.d.ts +4 -0
  432. package/dist/ports/workspace-layout.js +1 -0
  433. package/dist/shared/errors.d.ts +2 -0
  434. package/dist/shared/errors.js +22 -0
  435. package/dist/shared/paths.d.ts +2 -0
  436. package/dist/shared/paths.js +24 -0
  437. package/dist/shared/strings.d.ts +2 -0
  438. package/dist/shared/strings.js +16 -0
  439. package/package.json +6 -1
  440. package/assets/skills/taphound-ai-journey/GUIDE.md +0 -862
  441. package/assets/skills/taphound-ai-journey/SKILL.md +0 -377
  442. package/assets/skills/taphound-ai-journey/prompts/analyze-project.md +0 -526
  443. package/assets/skills/taphound-ai-journey/prompts/generate-step.md +0 -109
  444. package/assets/skills/taphound-ai-journey/schemas/project-context.json +0 -81
  445. package/assets/skills/taphound-ai-journey/templates/project-context.example.json +0 -49
  446. package/dist/ports/android-cli.d.ts +0 -21
  447. /package/dist/{ports/android-cli.js → adapters/runtime/mobile-mcp/mobile-mcp-tools.js} +0 -0
@@ -1,5 +1,7 @@
1
1
  import { parseObservedActivityComponent } from "../../domain/activity.js";
2
2
  import { isAppProcessName } from "../../domain/app-process.js";
3
+ import { WindowTopologySchema } from "../../domain/window-hierarchy.js";
4
+ import { parseWindowTopology } from "./window-topology-parser.js";
3
5
  const PROCESS_ROW = /^(\d+)\s+(\S+)$/;
4
6
  function deviceArgs(deviceSerial) {
5
7
  return deviceSerial === undefined ? [] : ["-s", deviceSerial];
@@ -30,6 +32,30 @@ function inputTextChunks(text) {
30
32
  chunks.push(text.slice(start));
31
33
  return chunks;
32
34
  }
35
+ const DEVICEKIT_PACKAGE_NAME = "com.mobilenext.devicekit";
36
+ const DEVICEKIT_CLIPBOARD_RECEIVER = `${DEVICEKIT_PACKAGE_NAME}/.ClipboardBroadcastReceiver`;
37
+ function isAsciiText(text) {
38
+ for (let index = 0; index < text.length; index += 1) {
39
+ if (text.charCodeAt(index) > 0x7f) {
40
+ return false;
41
+ }
42
+ }
43
+ return true;
44
+ }
45
+ function inputTextUnsupportedResult() {
46
+ return {
47
+ exitCode: 1,
48
+ signal: null,
49
+ stdout: "",
50
+ stderr: "ADB input text cannot type non-ASCII characters; install the"
51
+ + " mobilenext devicekit app"
52
+ + " (https://github.com/mobile-next/devicekit-android) to deliver"
53
+ + " non-ASCII text through the clipboard",
54
+ durationMs: 0,
55
+ timedOut: false,
56
+ cancelled: false
57
+ };
58
+ }
33
59
  export class AdbAdapter {
34
60
  runner;
35
61
  constructor(runner) {
@@ -58,6 +84,30 @@ export class AdbAdapter {
58
84
  return { serial, status };
59
85
  });
60
86
  }
87
+ async deviceIdentity(identity) {
88
+ const property = async (name) => {
89
+ const result = await this.run(["-s", identity.deviceSerial, "shell", "getprop", name], identity.signal, identity.timeoutMs);
90
+ if (result.exitCode !== 0
91
+ || result.spawnError !== undefined
92
+ || result.cancelled
93
+ || result.timedOut) {
94
+ throw new Error(`getprop ${name} failed on ${identity.deviceSerial}`);
95
+ }
96
+ return result.stdout.trim();
97
+ };
98
+ const [manufacturer, model, sdk] = await Promise.all([
99
+ property("ro.product.manufacturer"),
100
+ property("ro.product.model"),
101
+ property("ro.build.version.sdk")
102
+ ]);
103
+ const sdkLevel = Number(sdk);
104
+ if (manufacturer.length === 0
105
+ || model.length === 0
106
+ || !Number.isSafeInteger(sdkLevel)) {
107
+ throw new Error(`Device ${identity.deviceSerial} returned an incomplete identity`);
108
+ }
109
+ return { manufacturer, model, sdkLevel };
110
+ }
61
111
  async foregroundComponent(identity) {
62
112
  const result = await this.run([
63
113
  ...deviceArgs(identity.deviceSerial),
@@ -101,6 +151,43 @@ export class AdbAdapter {
101
151
  `${options.packageName}/${options.activity}`
102
152
  ], options.signal, options.timeoutMs);
103
153
  }
154
+ async startActivityByIntent(options) {
155
+ const result = await this.run([
156
+ ...deviceArgs(options.deviceSerial),
157
+ "shell",
158
+ "am",
159
+ "start",
160
+ "-W",
161
+ "-a",
162
+ options.action
163
+ ], options.signal, options.timeoutMs);
164
+ if (result.exitCode === 0 && /^Error(:| type)/m.test(result.stdout)) {
165
+ return { ...result, exitCode: 1 };
166
+ }
167
+ return result;
168
+ }
169
+ async resolveLauncherActivity(options) {
170
+ const result = await this.run([
171
+ ...deviceArgs(options.deviceSerial),
172
+ "shell",
173
+ "cmd",
174
+ "package",
175
+ "resolve-activity",
176
+ "--brief",
177
+ options.packageName
178
+ ], options.signal, options.timeoutMs);
179
+ assertCommandUsable(result, "cmd package resolve-activity");
180
+ const match = /^(\S+)\/(\S+)$/m.exec(result.stdout);
181
+ if (match === null) {
182
+ return undefined;
183
+ }
184
+ const packageName = match[1];
185
+ const activity = match[2];
186
+ if (packageName === undefined || activity === undefined) {
187
+ return undefined;
188
+ }
189
+ return { packageName, activity };
190
+ }
104
191
  forceStop(identity) {
105
192
  return this.run([
106
193
  ...deviceArgs(identity.deviceSerial),
@@ -141,6 +228,41 @@ export class AdbAdapter {
141
228
  })
142
229
  .sort((left, right) => left.pid - right.pid);
143
230
  }
231
+ async windowTopology(identity) {
232
+ const result = await this.run([
233
+ ...deviceArgs(identity.deviceSerial),
234
+ "shell",
235
+ "dumpsys",
236
+ "window",
237
+ "windows"
238
+ ], identity.signal, identity.timeoutMs);
239
+ if (result.cancelled) {
240
+ throw new Error("ADB window topology was cancelled");
241
+ }
242
+ if (result.exitCode !== 0
243
+ || result.timedOut
244
+ || result.spawnError !== undefined) {
245
+ return WindowTopologySchema.parse({
246
+ version: 1,
247
+ status: "unavailable",
248
+ windows: [],
249
+ diagnostic: result.stderr.trim()
250
+ || result.spawnError
251
+ || (result.timedOut
252
+ ? "ADB window topology timed out"
253
+ : `ADB window topology exited with code ${String(result.exitCode)}`)
254
+ });
255
+ }
256
+ const topology = parseWindowTopology(result.stdout, identity.packageName);
257
+ if (topology.windows.length > 0)
258
+ return topology;
259
+ return WindowTopologySchema.parse({
260
+ version: 1,
261
+ status: "unavailable",
262
+ windows: [],
263
+ diagnostic: "ADB window topology contained no visible touchable target-app windows"
264
+ });
265
+ }
144
266
  tap(point, deviceSerial, signal) {
145
267
  return this.run([
146
268
  ...deviceArgs(deviceSerial),
@@ -177,6 +299,9 @@ export class AdbAdapter {
177
299
  ], signal);
178
300
  }
179
301
  async inputText(text, deviceSerial, signal) {
302
+ if (!isAsciiText(text)) {
303
+ return this.inputNonAsciiText(text, deviceSerial, signal);
304
+ }
180
305
  let result;
181
306
  for (const chunk of inputTextChunks(text)) {
182
307
  result = await this.run([
@@ -198,21 +323,95 @@ export class AdbAdapter {
198
323
  }
199
324
  return result;
200
325
  }
326
+ /**
327
+ * `adb shell input text` cannot type non-ASCII characters (KeyCharacterMap
328
+ * resolves them to no events and the input command fails), so non-ASCII text
329
+ * is delivered through the mobilenext devicekit clipboard: set the clipboard
330
+ * by broadcast, paste it into the focused element with KEYCODE_PASTE, then
331
+ * clear the clipboard again.
332
+ */
333
+ async inputNonAsciiText(text, deviceSerial, signal) {
334
+ const installed = await this.isInstalled({
335
+ packageName: DEVICEKIT_PACKAGE_NAME,
336
+ deviceSerial,
337
+ signal
338
+ });
339
+ if (!installed) {
340
+ return inputTextUnsupportedResult();
341
+ }
342
+ const encoded = Buffer.from(text, "utf8").toString("base64");
343
+ const commands = [
344
+ [
345
+ "am",
346
+ "broadcast",
347
+ "-a",
348
+ "devicekit.clipboard.set",
349
+ "-e",
350
+ "encoding",
351
+ "base64",
352
+ "-e",
353
+ "text",
354
+ quoteRemoteShellArgument(encoded),
355
+ "-n",
356
+ DEVICEKIT_CLIPBOARD_RECEIVER
357
+ ],
358
+ ["input", "keyevent", "KEYCODE_PASTE"],
359
+ [
360
+ "am",
361
+ "broadcast",
362
+ "-a",
363
+ "devicekit.clipboard.clear",
364
+ "-n",
365
+ DEVICEKIT_CLIPBOARD_RECEIVER
366
+ ]
367
+ ];
368
+ let result;
369
+ for (const command of commands) {
370
+ result = await this.run([
371
+ ...deviceArgs(deviceSerial),
372
+ "shell",
373
+ ...command
374
+ ], signal);
375
+ if (result.exitCode !== 0
376
+ || result.timedOut
377
+ || result.cancelled
378
+ || result.spawnError !== undefined) {
379
+ return result;
380
+ }
381
+ }
382
+ if (result === undefined) {
383
+ throw new Error("ADB input text produced no command");
384
+ }
385
+ return result;
386
+ }
201
387
  startLogcat(options) {
202
388
  return this.runner.start({
203
389
  executable: "adb",
204
390
  args: [
205
391
  ...deviceArgs(options.deviceSerial),
206
392
  "logcat",
393
+ "-T",
394
+ "1",
207
395
  "-v",
208
396
  "threadtime"
209
397
  ],
210
398
  ...(options.signal === undefined ? {} : { signal: options.signal })
211
399
  }, {
212
400
  onStdoutLine: options.onStdoutLine,
401
+ captureStdout: false,
402
+ captureStderr: false,
213
403
  ...(options.onStderrLine === undefined
214
404
  ? {}
215
405
  : { onStderrLine: options.onStderrLine })
216
406
  });
217
407
  }
408
+ dumpLogcat(options) {
409
+ return this.run([
410
+ ...deviceArgs(options.deviceSerial),
411
+ "logcat",
412
+ "-d",
413
+ "-t",
414
+ String(options.maxLines)
415
+ ], options.signal, options.timeoutMs);
416
+ }
218
417
  }
@@ -0,0 +1,13 @@
1
+ import type { ProcessRunner } from "../../ports/process-runner.js";
2
+ import type { OpenUiSnapshotProviderOptions, UiSnapshotProvider, UiSnapshotProviderFactory } from "../../ports/ui-snapshot.js";
3
+ import { type UiSnapshotTiming } from "../ui/ui-snapshot-support.js";
4
+ interface SystemUiAutomatorFactoryOptions extends UiSnapshotTiming {
5
+ createLayoutPath?: (() => string) | undefined;
6
+ }
7
+ export declare class SystemUiAutomatorSnapshotProviderFactory implements UiSnapshotProviderFactory {
8
+ private readonly runner;
9
+ private readonly options;
10
+ constructor(runner: ProcessRunner, options?: SystemUiAutomatorFactoryOptions);
11
+ open(options: OpenUiSnapshotProviderOptions): Promise<UiSnapshotProvider>;
12
+ }
13
+ export {};
@@ -0,0 +1,122 @@
1
+ import { createHash } from "node:crypto";
2
+ import { performance } from "node:perf_hooks";
3
+ import { parseUiAutomatorLayout } from "./ui-automator-parser.js";
4
+ import { readDeviceUiEnvironment } from "../ui/device-ui-environment.js";
5
+ import { createDeviceLayoutPath, sweepDeviceLayoutTemp } from "../ui/device-layout-temp.js";
6
+ import { UiSnapshotError } from "../ui/ui-snapshot-error.js";
7
+ import { snapshotFromCapture } from "../ui/ui-snapshot-support.js";
8
+ function descriptor(sdkLevel) {
9
+ return {
10
+ id: "system-uiautomator",
11
+ adapterVersion: "system-uiautomator-v1",
12
+ engineVersion: `android-api-${String(sdkLevel)}`,
13
+ configSha256: createHash("sha256")
14
+ .update(JSON.stringify({ command: "uiautomator dump", version: 1 }))
15
+ .digest("hex")
16
+ };
17
+ }
18
+ class SystemUiAutomatorSnapshotProvider {
19
+ runner;
20
+ deviceSerial;
21
+ environment;
22
+ descriptor;
23
+ createLayoutPath;
24
+ timing;
25
+ closed = false;
26
+ constructor(runner, deviceSerial, environment, descriptor, createLayoutPath, timing) {
27
+ this.runner = runner;
28
+ this.deviceSerial = deviceSerial;
29
+ this.environment = environment;
30
+ this.descriptor = descriptor;
31
+ this.createLayoutPath = createLayoutPath;
32
+ this.timing = timing;
33
+ }
34
+ async capture(options) {
35
+ if (this.closed) {
36
+ throw new UiSnapshotError("UI_SNAPSHOT_FAILED", this.descriptor.id, "UI snapshot provider is closed");
37
+ }
38
+ const startedAt = performance.now();
39
+ const path = this.createLayoutPath();
40
+ const deadline = startedAt + options.timeoutMs;
41
+ const run = (args, timeoutMs) => (this.runner.run({
42
+ executable: "adb",
43
+ args: ["-s", this.deviceSerial, ...args],
44
+ timeoutMs,
45
+ ...(options.signal === undefined ? {} : { signal: options.signal })
46
+ }));
47
+ try {
48
+ const dumped = await run(["shell", "uiautomator", "dump", path], options.timeoutMs);
49
+ if (dumped.exitCode !== 0
50
+ || dumped.timedOut
51
+ || dumped.cancelled
52
+ || dumped.spawnError !== undefined) {
53
+ throw new UiSnapshotError("UI_SNAPSHOT_FAILED", this.descriptor.id, dumped.stderr.trim() || dumped.spawnError || "UIAutomator dump failed", { terminal: dumped.timedOut || dumped.cancelled });
54
+ }
55
+ const xml = await run(["exec-out", "cat", path], Math.max(1, deadline - performance.now()));
56
+ if (xml.exitCode !== 0
57
+ || xml.timedOut
58
+ || xml.cancelled
59
+ || xml.spawnError !== undefined) {
60
+ throw new UiSnapshotError("UI_SNAPSHOT_FAILED", this.descriptor.id, xml.stderr.trim() || xml.spawnError || "UIAutomator source read failed", { terminal: xml.timedOut || xml.cancelled });
61
+ }
62
+ let roots;
63
+ try {
64
+ roots = parseUiAutomatorLayout(xml.stdout);
65
+ }
66
+ catch (error) {
67
+ throw new UiSnapshotError("UI_SNAPSHOT_INVALID", this.descriptor.id, "UIAutomator returned malformed page source", { cause: error });
68
+ }
69
+ if (roots.length === 0) {
70
+ throw new UiSnapshotError("UI_SNAPSHOT_INVALID", this.descriptor.id, "UIAutomator returned an empty layout");
71
+ }
72
+ return snapshotFromCapture({
73
+ startedAt,
74
+ roots,
75
+ backend: this.descriptor,
76
+ viewport: this.environment.viewport,
77
+ timing: this.timing
78
+ });
79
+ }
80
+ finally {
81
+ await this.runner.run({
82
+ executable: "adb",
83
+ args: ["-s", this.deviceSerial, "shell", "rm", "-f", path],
84
+ timeoutMs: 5000
85
+ }).catch(() => undefined);
86
+ }
87
+ }
88
+ close() {
89
+ this.closed = true;
90
+ return sweepDeviceLayoutTemp(this.runner, this.deviceSerial);
91
+ }
92
+ }
93
+ export class SystemUiAutomatorSnapshotProviderFactory {
94
+ runner;
95
+ options;
96
+ constructor(runner, options = {}) {
97
+ this.runner = runner;
98
+ this.options = options;
99
+ }
100
+ async open(options) {
101
+ const environment = await readDeviceUiEnvironment(this.runner, "system-uiautomator", options);
102
+ await sweepDeviceLayoutTemp(this.runner, options.deviceSerial, options.signal);
103
+ const provider = new SystemUiAutomatorSnapshotProvider(this.runner, options.deviceSerial, environment, descriptor(environment.sdkLevel), this.options.createLayoutPath ?? createDeviceLayoutPath, this.options);
104
+ try {
105
+ await provider.capture({
106
+ reason: "evidence",
107
+ timeoutMs: options.timeoutMs,
108
+ ...(options.signal === undefined ? {} : { signal: options.signal })
109
+ });
110
+ }
111
+ catch (error) {
112
+ if (error instanceof UiSnapshotError
113
+ && error.code === "UI_SNAPSHOT_FAILED"
114
+ && !error.terminal
115
+ && options.signal?.aborted !== true) {
116
+ throw new UiSnapshotError("UI_BACKEND_UNAVAILABLE", "system-uiautomator", error.message, { cause: error });
117
+ }
118
+ throw error;
119
+ }
120
+ return provider;
121
+ }
122
+ }
@@ -0,0 +1,3 @@
1
+ import { type LayoutElement } from "../../domain/layout.js";
2
+ export declare function parseUiAutomatorLayout(xml: string): readonly LayoutElement[];
3
+ export declare function parseAppiumPageSource(xml: string): readonly LayoutElement[];
@@ -0,0 +1,160 @@
1
+ import { z } from "zod";
2
+ import { LayoutElementSchema } from "../../domain/layout.js";
3
+ import { normalizeBoundsEdges, normalizeResourceId } from "../ui/layout-normalization.js";
4
+ const NAMED_ENTITIES = {
5
+ quot: "\"",
6
+ apos: "'",
7
+ lt: "<",
8
+ gt: ">",
9
+ amp: "&"
10
+ };
11
+ const ENTITY_PATTERN = /&(?:#(\d+)|#x([\da-fA-F]+)|(quot|apos|lt|gt|amp));/g;
12
+ function decodeCodePoint(entity, codePoint) {
13
+ return Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= 0x10ffff
14
+ ? String.fromCodePoint(codePoint)
15
+ : entity;
16
+ }
17
+ /**
18
+ * Single-pass XML entity decoding. Android's XML serializer escapes control
19
+ * characters such as newlines as numeric references (`&#10;`), so multi-line
20
+ * text must decode them to match Locators authored against the visible text.
21
+ */
22
+ function decodeXml(value) {
23
+ if (!value.includes("&"))
24
+ return value;
25
+ return value.replace(ENTITY_PATTERN, (entity, decimal, hex, named) => {
26
+ if (decimal !== undefined) {
27
+ return decodeCodePoint(entity, Number.parseInt(decimal, 10));
28
+ }
29
+ if (hex !== undefined) {
30
+ return decodeCodePoint(entity, Number.parseInt(hex, 16));
31
+ }
32
+ return named === undefined ? entity : NAMED_ENTITIES[named] ?? entity;
33
+ });
34
+ }
35
+ function attributes(source) {
36
+ const result = {};
37
+ const pattern = /([A-Za-z_:][\w:.-]*)=(["'])(.*?)\2/g;
38
+ for (const match of source.matchAll(pattern)) {
39
+ const key = match[1];
40
+ const value = match[3];
41
+ if (key !== undefined && value !== undefined) {
42
+ result[key] = decodeXml(value);
43
+ }
44
+ }
45
+ return result;
46
+ }
47
+ function booleanAttribute(values, key) {
48
+ const value = values[key];
49
+ return value === undefined ? undefined : value === "true";
50
+ }
51
+ function bounds(value) {
52
+ if (value === undefined)
53
+ return undefined;
54
+ const match = /^\[(\d+),(\d+)]\[(\d+),(\d+)]$/.exec(value);
55
+ if (match === null) {
56
+ throw new Error("Invalid UIAutomator bounds");
57
+ }
58
+ return normalizeBoundsEdges(Number(match[1]), Number(match[2]), Number(match[3]), Number(match[4]));
59
+ }
60
+ /**
61
+ * Builds one element without re-validating its subtree: `LayoutElementSchema`
62
+ * is recursive, so parsing every node would validate each subtree once per
63
+ * ancestor (O(nodes x depth)). The completed roots are validated exactly once
64
+ * in `parseAccessibilityLayout`.
65
+ */
66
+ function node(values, id, children) {
67
+ const parsedBounds = bounds(values.bounds);
68
+ const text = values.text;
69
+ const contentDescription = values["content-desc"];
70
+ const center = parsedBounds === undefined
71
+ ? undefined
72
+ : {
73
+ x: Math.round((parsedBounds.left + parsedBounds.right) / 2),
74
+ y: Math.round((parsedBounds.top + parsedBounds.bottom) / 2)
75
+ };
76
+ const resourceId = normalizeResourceId(values["resource-id"]);
77
+ return {
78
+ id,
79
+ ...(resourceId === undefined ? {} : { resourceId }),
80
+ ...(text === undefined || text.length === 0 ? {} : { text }),
81
+ ...(contentDescription === undefined || contentDescription.length === 0
82
+ ? {}
83
+ : { contentDescription }),
84
+ ...(booleanAttribute(values, "clickable") === undefined
85
+ ? {}
86
+ : { clickable: booleanAttribute(values, "clickable") }),
87
+ ...(booleanAttribute(values, "long-clickable") === undefined
88
+ ? {}
89
+ : { longClickable: booleanAttribute(values, "long-clickable") }),
90
+ ...(booleanAttribute(values, "scrollable") === undefined
91
+ ? {}
92
+ : { scrollable: booleanAttribute(values, "scrollable") }),
93
+ ...(booleanAttribute(values, "focusable") === undefined
94
+ ? {}
95
+ : { focusable: booleanAttribute(values, "focusable") }),
96
+ ...(booleanAttribute(values, "focused") === undefined
97
+ ? {}
98
+ : { focused: booleanAttribute(values, "focused") }),
99
+ enabled: booleanAttribute(values, "enabled") ?? true,
100
+ ...(center === undefined ? {} : { center }),
101
+ ...(parsedBounds === undefined ? {} : { bounds: parsedBounds }),
102
+ children
103
+ };
104
+ }
105
+ function parseAccessibilityLayout(xml, accepts) {
106
+ const roots = [];
107
+ const stack = [];
108
+ // Quoted attribute values may legally contain an unescaped `>`.
109
+ const tokenPattern = /<\/?([A-Za-z_][\w:.$-]*)\b(?:[^>"']|"[^"]*"|'[^']*')*\/?>/g;
110
+ let token;
111
+ let index = 0;
112
+ while ((token = tokenPattern.exec(xml)) !== null) {
113
+ const source = token[0];
114
+ const tag = token[1];
115
+ if (tag === undefined || !accepts(tag))
116
+ continue;
117
+ if (source.startsWith("</")) {
118
+ const current = stack.pop();
119
+ if (current === undefined || current.tag !== tag) {
120
+ throw new Error("Invalid UIAutomator node nesting");
121
+ }
122
+ const parsed = node(current.values, current.id, current.children);
123
+ const parent = stack.at(-1);
124
+ if (parent === undefined)
125
+ roots.push(parsed);
126
+ else
127
+ parent.children.push(parsed);
128
+ continue;
129
+ }
130
+ const selfClosing = source.endsWith("/>");
131
+ const current = {
132
+ tag,
133
+ values: attributes(source),
134
+ children: [],
135
+ id: `ui-${String(index)}`
136
+ };
137
+ index += 1;
138
+ if (selfClosing) {
139
+ const parsed = node(current.values, current.id, current.children);
140
+ const parent = stack.at(-1);
141
+ if (parent === undefined)
142
+ roots.push(parsed);
143
+ else
144
+ parent.children.push(parsed);
145
+ }
146
+ else {
147
+ stack.push(current);
148
+ }
149
+ }
150
+ if (stack.length > 0) {
151
+ throw new Error("Invalid UIAutomator node nesting");
152
+ }
153
+ return z.array(LayoutElementSchema).parse(roots);
154
+ }
155
+ export function parseUiAutomatorLayout(xml) {
156
+ return parseAccessibilityLayout(xml, (tag) => tag === "node");
157
+ }
158
+ export function parseAppiumPageSource(xml) {
159
+ return parseAccessibilityLayout(xml, (tag) => tag !== "hierarchy");
160
+ }
@@ -0,0 +1,2 @@
1
+ import { type WindowTopology } from "../../domain/window-hierarchy.js";
2
+ export declare function parseWindowTopology(stdout: string, packageName: string): WindowTopology;
@@ -0,0 +1,101 @@
1
+ import { WindowTopologySchema } from "../../domain/window-hierarchy.js";
2
+ const WINDOW_HEADER = /^\s*Window #\d+ Window\{(\S+)\s+u\d+\s+(.+)}:$/;
3
+ const FRAME = /\bframe=\[(-?\d+),(-?\d+)]\[(-?\d+),(-?\d+)]/;
4
+ function windowBlocks(stdout) {
5
+ const blocks = [];
6
+ let current;
7
+ for (const line of stdout.split(/\r?\n/)) {
8
+ if (WINDOW_HEADER.test(line)) {
9
+ current = [line];
10
+ blocks.push(current);
11
+ }
12
+ else {
13
+ current?.push(line);
14
+ }
15
+ }
16
+ return blocks;
17
+ }
18
+ function value(lines, pattern) {
19
+ for (const line of lines) {
20
+ const match = pattern.exec(line);
21
+ if (match?.[1] !== undefined)
22
+ return match[1];
23
+ }
24
+ return undefined;
25
+ }
26
+ function parseBounds(lines) {
27
+ const frames = lines.find((line) => line.includes("Frames:"));
28
+ const match = frames === undefined ? null : FRAME.exec(frames);
29
+ if (match?.[1] === undefined
30
+ || match[2] === undefined
31
+ || match[3] === undefined
32
+ || match[4] === undefined) {
33
+ return undefined;
34
+ }
35
+ const [left, top, right, bottom] = match.slice(1).map(Number);
36
+ if (left === undefined
37
+ || top === undefined
38
+ || right === undefined
39
+ || bottom === undefined
40
+ || left < 0
41
+ || top < 0
42
+ || right <= left
43
+ || bottom <= top) {
44
+ return undefined;
45
+ }
46
+ return { left, top, right, bottom };
47
+ }
48
+ function parseWindow(lines, packageName, focusedWindowId) {
49
+ const header = lines[0];
50
+ const headerMatch = header === undefined ? null : WINDOW_HEADER.exec(header);
51
+ if (headerMatch?.[1] === undefined
52
+ || headerMatch[2] === undefined
53
+ || value(lines, /\bpackage=(\S+)/) !== packageName
54
+ || !lines.some((line) => /\bisVisible=true\b/.test(line))
55
+ || !lines.some((line) => /\bisOnScreen=true\b/.test(line))
56
+ || !lines.some((line) => /\bmHasSurface=true\b/.test(line))
57
+ || !lines.some((line) => /\bisReadyForDisplay\(\)=true\b/.test(line))) {
58
+ return undefined;
59
+ }
60
+ const attrs = lines.findIndex((line) => line.includes("mAttrs={"));
61
+ const attrsEnd = attrs < 0
62
+ ? attrs
63
+ : lines.findIndex((line, index) => (index >= attrs && line.trim().endsWith("}")));
64
+ const attributeLines = attrs < 0
65
+ ? []
66
+ : lines.slice(attrs, attrsEnd < attrs ? attrs + 1 : attrsEnd + 1);
67
+ const bounds = parseBounds(lines);
68
+ const layer = value(lines, /\bmBaseLayer=(\d+)/);
69
+ const type = value(attributeLines, /\bty=([A-Z0-9_]+)/);
70
+ return {
71
+ id: headerMatch[1],
72
+ title: headerMatch[2],
73
+ packageName,
74
+ ...(type === undefined ? {} : { type }),
75
+ ...(layer === undefined ? {} : { layer: Number(layer) }),
76
+ ...(bounds === undefined ? {} : { bounds }),
77
+ touchable: !attributeLines.some((line) => /\bNOT_TOUCHABLE\b/.test(line)),
78
+ ...(attributeLines.some((line) => /\bfl=/.test(line))
79
+ ? {
80
+ focusable: !attributeLines.some((line) => /\bNOT_FOCUSABLE\b/.test(line))
81
+ }
82
+ : {}),
83
+ ...(focusedWindowId === undefined
84
+ ? {}
85
+ : { focused: focusedWindowId === headerMatch[1] })
86
+ };
87
+ }
88
+ export function parseWindowTopology(stdout, packageName) {
89
+ const focusedWindowId = value(stdout.split(/\r?\n/), /\bmCurrentFocus=Window\{(\S+)/);
90
+ return WindowTopologySchema.parse({
91
+ version: 1,
92
+ status: "observed",
93
+ windows: windowBlocks(stdout)
94
+ .flatMap((block) => {
95
+ const window = parseWindow(block, packageName, focusedWindowId);
96
+ return window === undefined || !window.touchable ? [] : [window];
97
+ })
98
+ .sort((left, right) => ((right.layer ?? 0) - (left.layer ?? 0)
99
+ || left.id.localeCompare(right.id)))
100
+ });
101
+ }