taphound 0.2.0-dev.1 → 0.2.0-dev.12

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 (467) 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 +507 -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 +578 -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 +357 -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 +42 -0
  66. package/dist/adapters/appium/appium-ui-snapshot-provider.js +309 -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/diagnostics-journal.d.ts +17 -0
  73. package/dist/adapters/filesystem/diagnostics-journal.js +130 -0
  74. package/dist/adapters/filesystem/external-flow-registry.d.ts +17 -0
  75. package/dist/adapters/filesystem/external-flow-registry.js +325 -0
  76. package/dist/adapters/filesystem/generation-session-store.d.ts +17 -11
  77. package/dist/adapters/filesystem/generation-session-store.js +281 -855
  78. package/dist/adapters/filesystem/generation-store/session-lock.d.ts +29 -0
  79. package/dist/adapters/filesystem/generation-store/session-lock.js +202 -0
  80. package/dist/adapters/filesystem/generation-store/session-transitions.d.ts +24 -0
  81. package/dist/adapters/filesystem/generation-store/session-transitions.js +340 -0
  82. package/dist/adapters/filesystem/generation-store/store-files.d.ts +50 -0
  83. package/dist/adapters/filesystem/generation-store/store-files.js +363 -0
  84. package/dist/adapters/filesystem/journey-composition-store.d.ts +23 -0
  85. package/dist/adapters/filesystem/journey-composition-store.js +151 -0
  86. package/dist/adapters/filesystem/knowledge-registry.d.ts +12 -0
  87. package/dist/adapters/filesystem/knowledge-registry.js +223 -0
  88. package/dist/adapters/filesystem/project-bound-file.js +3 -5
  89. package/dist/adapters/filesystem/project-file-inspector.d.ts +2 -0
  90. package/dist/adapters/filesystem/project-file-inspector.js +57 -28
  91. package/dist/adapters/filesystem/project-identity-inspector.d.ts +7 -0
  92. package/dist/adapters/filesystem/project-identity-inspector.js +215 -0
  93. package/dist/adapters/filesystem/project-inventory-inspector.d.ts +8 -0
  94. package/dist/adapters/filesystem/project-inventory-inspector.js +93 -0
  95. package/dist/adapters/filesystem/project-module-discoverer.d.ts +6 -0
  96. package/dist/adapters/filesystem/project-module-discoverer.js +158 -0
  97. package/dist/adapters/filesystem/skill-installer.d.ts +7 -2
  98. package/dist/adapters/filesystem/skill-installer.js +40 -11
  99. package/dist/adapters/filesystem/workspace-layout.d.ts +7 -0
  100. package/dist/adapters/filesystem/workspace-layout.js +57 -0
  101. package/dist/adapters/git/node-git-diff.d.ts +17 -0
  102. package/dist/adapters/git/node-git-diff.js +114 -0
  103. package/dist/adapters/process/node-detached-process-launcher.d.ts +6 -0
  104. package/dist/adapters/process/node-detached-process-launcher.js +96 -0
  105. package/dist/adapters/process/node-process-runner.js +11 -2
  106. package/dist/adapters/prompt/inquirer-align-prompt.d.ts +27 -0
  107. package/dist/adapters/prompt/inquirer-align-prompt.js +65 -0
  108. package/dist/adapters/prompt/inquirer-generation-prompt.js +3 -0
  109. package/dist/adapters/prompt/inquirer-recorder-prompt.d.ts +9 -1
  110. package/dist/adapters/prompt/inquirer-recorder-prompt.js +63 -0
  111. package/dist/adapters/runtime/adb-runtime-backend.d.ts +67 -0
  112. package/dist/adapters/runtime/adb-runtime-backend.js +206 -0
  113. package/dist/adapters/runtime/fake-runtime-backend.d.ts +87 -0
  114. package/dist/adapters/runtime/fake-runtime-backend.js +240 -0
  115. package/dist/adapters/runtime/mobile-mcp/mcp-tool-client.d.ts +45 -0
  116. package/dist/adapters/runtime/mobile-mcp/mcp-tool-client.js +153 -0
  117. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-errors.d.ts +23 -0
  118. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-errors.js +39 -0
  119. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-responses.d.ts +20 -0
  120. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-responses.js +180 -0
  121. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-runtime-backend.d.ts +78 -0
  122. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-runtime-backend.js +379 -0
  123. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-tools.d.ts +46 -0
  124. package/dist/adapters/runtime/session-adb-view.d.ts +53 -0
  125. package/dist/adapters/runtime/session-adb-view.js +193 -0
  126. package/dist/adapters/runtime/session-backed-ports.d.ts +21 -0
  127. package/dist/adapters/runtime/session-backed-ports.js +43 -0
  128. package/dist/adapters/runtime/shared-session-runtime-backend.d.ts +12 -0
  129. package/dist/adapters/runtime/shared-session-runtime-backend.js +64 -0
  130. package/dist/adapters/ui/auto-ui-snapshot-provider.d.ts +8 -0
  131. package/dist/adapters/ui/auto-ui-snapshot-provider.js +48 -0
  132. package/dist/adapters/ui/device-layout-temp.d.ts +6 -0
  133. package/dist/adapters/ui/device-layout-temp.js +18 -0
  134. package/dist/adapters/ui/device-ui-environment.d.ts +13 -0
  135. package/dist/adapters/ui/device-ui-environment.js +81 -0
  136. package/dist/adapters/ui/layout-normalization.d.ts +9 -0
  137. package/dist/adapters/ui/layout-normalization.js +44 -0
  138. package/dist/adapters/ui/ui-snapshot-error.d.ts +11 -0
  139. package/dist/adapters/ui/ui-snapshot-error.js +12 -0
  140. package/dist/adapters/ui/ui-snapshot-support.d.ts +15 -0
  141. package/dist/adapters/ui/ui-snapshot-support.js +12 -0
  142. package/dist/application/align/align-service.d.ts +42 -0
  143. package/dist/application/align/align-service.js +106 -0
  144. package/dist/application/assertion/expectation-evaluator.d.ts +18 -5
  145. package/dist/application/assertion/expectation-evaluator.js +156 -18
  146. package/dist/application/assertion/guarded-expectation.d.ts +45 -0
  147. package/dist/application/assertion/guarded-expectation.js +74 -0
  148. package/dist/application/checkpoint/baseline-capturer.d.ts +26 -0
  149. package/dist/application/checkpoint/baseline-capturer.js +163 -0
  150. package/dist/application/checkpoint/baseline-error.d.ts +5 -0
  151. package/dist/application/checkpoint/baseline-error.js +8 -0
  152. package/dist/application/checkpoint/baseline-service.d.ts +31 -0
  153. package/dist/application/checkpoint/baseline-service.js +147 -0
  154. package/dist/application/checkpoint/checkpoint-evaluator.d.ts +35 -0
  155. package/dist/application/checkpoint/checkpoint-evaluator.js +415 -0
  156. package/dist/application/checkpoint/regression-comparator.d.ts +18 -0
  157. package/dist/application/checkpoint/regression-comparator.js +192 -0
  158. package/dist/application/collector/logcat-collector.d.ts +22 -1
  159. package/dist/application/collector/logcat-collector.js +103 -15
  160. package/dist/application/collector/logcat-event.d.ts +8 -0
  161. package/dist/application/collector/logcat-event.js +79 -0
  162. package/dist/application/collector/logcat-stop.d.ts +2 -0
  163. package/dist/application/collector/logcat-stop.js +11 -0
  164. package/dist/application/context/context-generator.d.ts +45 -0
  165. package/dist/application/context/context-generator.js +228 -0
  166. package/dist/application/context/context-loader.d.ts +40 -0
  167. package/dist/application/context/context-loader.js +223 -0
  168. package/dist/application/context/context-refresher.d.ts +59 -0
  169. package/dist/application/context/context-refresher.js +349 -0
  170. package/dist/application/context/context-rehasher.d.ts +36 -0
  171. package/dist/application/context/context-rehasher.js +110 -0
  172. package/dist/application/context/context-validator.d.ts +23 -1
  173. package/dist/application/context/context-validator.js +116 -9
  174. package/dist/application/context/evidence-hash.d.ts +1 -0
  175. package/dist/application/context/evidence-hash.js +125 -0
  176. package/dist/application/context/evidence-match.d.ts +9 -0
  177. package/dist/application/context/evidence-match.js +8 -0
  178. package/dist/application/context/shard-identity.d.ts +2 -0
  179. package/dist/application/context/shard-identity.js +7 -0
  180. package/dist/application/contract/contract-loader.d.ts +25 -0
  181. package/dist/application/contract/contract-loader.js +68 -0
  182. package/dist/application/contract/contract-review.d.ts +25 -0
  183. package/dist/application/contract/contract-review.js +51 -0
  184. package/dist/application/contract/contract-verifier.d.ts +53 -0
  185. package/dist/application/contract/contract-verifier.js +645 -0
  186. package/dist/application/devices/resolve-device-assignments.d.ts +21 -0
  187. package/dist/application/devices/resolve-device-assignments.js +60 -0
  188. package/dist/application/diagnosis/failure-classifier.d.ts +23 -0
  189. package/dist/application/diagnosis/failure-classifier.js +179 -0
  190. package/dist/application/diagnostics/diagnostics-exporter.d.ts +29 -0
  191. package/dist/application/diagnostics/diagnostics-exporter.js +283 -0
  192. package/dist/application/diagnostics/ui-capture-telemetry.d.ts +16 -0
  193. package/dist/application/diagnostics/ui-capture-telemetry.js +81 -0
  194. package/dist/application/doctor/doctor-service.d.ts +36 -3
  195. package/dist/application/doctor/doctor-service.js +255 -25
  196. package/dist/application/generation/generation-app-preparer.d.ts +20 -0
  197. package/dist/application/generation/generation-app-preparer.js +28 -0
  198. package/dist/application/generation/generation-config-service.d.ts +21 -0
  199. package/dist/application/generation/generation-config-service.js +53 -0
  200. package/dist/application/generation/generation-confirmation-service.d.ts +14 -3
  201. package/dist/application/generation/generation-confirmation-service.js +109 -24
  202. package/dist/application/generation/generation-context-snapshot.d.ts +6 -0
  203. package/dist/application/generation/generation-context-snapshot.js +29 -0
  204. package/dist/application/generation/generation-finalizer.d.ts +15 -9
  205. package/dist/application/generation/generation-finalizer.js +167 -70
  206. package/dist/application/generation/generation-publisher.js +12 -8
  207. package/dist/application/generation/generation-recovery-service.d.ts +35 -0
  208. package/dist/application/generation/generation-recovery-service.js +144 -0
  209. package/dist/application/generation/generation-reopen-service.d.ts +15 -0
  210. package/dist/application/generation/generation-reopen-service.js +37 -0
  211. package/dist/application/generation/generation-replace-service.d.ts +32 -0
  212. package/dist/application/generation/generation-replace-service.js +116 -0
  213. package/dist/application/generation/generation-starter.d.ts +44 -4
  214. package/dist/application/generation/generation-starter.js +175 -8
  215. package/dist/application/generation/generation-step-executor.d.ts +67 -5
  216. package/dist/application/generation/generation-step-executor.js +471 -108
  217. package/dist/application/generation/proposed-step-validator.js +96 -30
  218. package/dist/application/generation/replay-policy-loader.d.ts +16 -0
  219. package/dist/application/generation/replay-policy-loader.js +29 -0
  220. package/dist/application/generation/risk-evaluator.d.ts +4 -1
  221. package/dist/application/generation/risk-evaluator.js +118 -3
  222. package/dist/application/generation/runtime-observer.d.ts +39 -7
  223. package/dist/application/generation/runtime-observer.js +193 -24
  224. package/dist/application/impact/impact-resolver.d.ts +32 -0
  225. package/dist/application/impact/impact-resolver.js +160 -0
  226. package/dist/application/init/init-service.js +26 -13
  227. package/dist/application/interaction/action-executor.d.ts +3 -2
  228. package/dist/application/interaction/action-executor.js +11 -1
  229. package/dist/application/interaction/action-target.d.ts +17 -0
  230. package/dist/application/interaction/action-target.js +40 -0
  231. package/dist/application/interaction/external-step-runner.d.ts +153 -0
  232. package/dist/application/interaction/external-step-runner.js +332 -0
  233. package/dist/application/interaction/fallback-resolver.d.ts +6 -3
  234. package/dist/application/interaction/fallback-resolver.js +7 -5
  235. package/dist/application/interaction/scroll-to-executor.d.ts +17 -3
  236. package/dist/application/interaction/scroll-to-executor.js +118 -36
  237. package/dist/application/journey/external-flow-resolver.d.ts +25 -0
  238. package/dist/application/journey/external-flow-resolver.js +22 -0
  239. package/dist/application/journey/journey-check-service.d.ts +54 -0
  240. package/dist/application/journey/journey-check-service.js +207 -0
  241. package/dist/application/journey/journey-promoter.d.ts +26 -0
  242. package/dist/application/journey/journey-promoter.js +123 -0
  243. package/dist/application/journey/journey-resolver.d.ts +42 -0
  244. package/dist/application/journey/journey-resolver.js +239 -0
  245. package/dist/application/journey/journey-retirer.d.ts +25 -0
  246. package/dist/application/journey/journey-retirer.js +87 -0
  247. package/dist/application/knowledge/anchor-resolver.d.ts +36 -0
  248. package/dist/application/knowledge/anchor-resolver.js +110 -0
  249. package/dist/application/knowledge/knowledge-loader.d.ts +16 -0
  250. package/dist/application/knowledge/knowledge-loader.js +36 -0
  251. package/dist/application/locator/layout-failure-summary.d.ts +2 -0
  252. package/dist/application/locator/layout-failure-summary.js +67 -0
  253. package/dist/application/locator/layout-traversal.d.ts +11 -0
  254. package/dist/application/locator/layout-traversal.js +18 -0
  255. package/dist/application/locator/locator-resolver.d.ts +13 -1
  256. package/dist/application/locator/locator-resolver.js +182 -18
  257. package/dist/application/observe/observe-service.d.ts +20 -0
  258. package/dist/application/observe/observe-service.js +91 -0
  259. package/dist/application/project/project-describer.d.ts +14 -6
  260. package/dist/application/project/project-describer.js +38 -5
  261. package/dist/application/recognition/screen-detector.d.ts +28 -0
  262. package/dist/application/recognition/screen-detector.js +111 -0
  263. package/dist/application/recorder/locator-selector.js +151 -17
  264. package/dist/application/recorder/recorder-service.d.ts +16 -4
  265. package/dist/application/recorder/recorder-service.js +420 -128
  266. package/dist/application/report/report-writer.d.ts +5 -0
  267. package/dist/application/report/report-writer.js +17 -1
  268. package/dist/application/runtime/cold-launch.d.ts +28 -0
  269. package/dist/application/runtime/cold-launch.js +59 -0
  270. package/dist/application/runtime/step-runner.d.ts +49 -4
  271. package/dist/application/runtime/step-runner.js +721 -130
  272. package/dist/application/runtime/verify-runtime.d.ts +57 -7
  273. package/dist/application/runtime/verify-runtime.js +577 -175
  274. package/dist/application/ui/cached-ui-snapshot-provider.d.ts +45 -0
  275. package/dist/application/ui/cached-ui-snapshot-provider.js +111 -0
  276. package/dist/application/ui/observed-ui-snapshot-provider.d.ts +15 -0
  277. package/dist/application/ui/observed-ui-snapshot-provider.js +66 -0
  278. package/dist/application/ui/ui-snapshot-lifecycle.d.ts +2 -0
  279. package/dist/application/ui/ui-snapshot-lifecycle.js +8 -0
  280. package/dist/application/ui/ui-stability-probe.d.ts +9 -0
  281. package/dist/application/ui/ui-stability-probe.js +24 -0
  282. package/dist/application/wait/idle-advice.d.ts +6 -0
  283. package/dist/application/wait/idle-advice.js +27 -0
  284. package/dist/application/wait/idle-profiles.d.ts +15 -0
  285. package/dist/application/wait/idle-profiles.js +75 -0
  286. package/dist/application/wait/idle-waiter.d.ts +28 -4
  287. package/dist/application/wait/idle-waiter.js +201 -15
  288. package/dist/cli/commands/align.d.ts +3 -0
  289. package/dist/cli/commands/align.js +124 -0
  290. package/dist/cli/commands/baseline.d.ts +3 -0
  291. package/dist/cli/commands/baseline.js +130 -0
  292. package/dist/cli/commands/context.js +300 -27
  293. package/dist/cli/commands/contract.d.ts +3 -0
  294. package/dist/cli/commands/contract.js +120 -0
  295. package/dist/cli/commands/diagnose.d.ts +3 -0
  296. package/dist/cli/commands/diagnose.js +84 -0
  297. package/dist/cli/commands/doctor.js +16 -9
  298. package/dist/cli/commands/failure.d.ts +3 -0
  299. package/dist/cli/commands/failure.js +63 -0
  300. package/dist/cli/commands/generation/lifecycle-commands.d.ts +5 -0
  301. package/dist/cli/commands/generation/lifecycle-commands.js +211 -0
  302. package/dist/cli/commands/generation/session-commands.d.ts +11 -0
  303. package/dist/cli/commands/generation/session-commands.js +472 -0
  304. package/dist/cli/commands/generation/shared.d.ts +1414 -0
  305. package/dist/cli/commands/generation/shared.js +291 -0
  306. package/dist/cli/commands/generation/step-commands.d.ts +8 -0
  307. package/dist/cli/commands/generation/step-commands.js +315 -0
  308. package/dist/cli/commands/generation.js +10 -507
  309. package/dist/cli/commands/impact.d.ts +3 -0
  310. package/dist/cli/commands/impact.js +87 -0
  311. package/dist/cli/commands/init.js +13 -6
  312. package/dist/cli/commands/journey.d.ts +3 -0
  313. package/dist/cli/commands/journey.js +382 -0
  314. package/dist/cli/commands/knowledge.d.ts +3 -0
  315. package/dist/cli/commands/knowledge.js +93 -0
  316. package/dist/cli/commands/observe.d.ts +3 -0
  317. package/dist/cli/commands/observe.js +127 -0
  318. package/dist/cli/commands/project.js +2 -1
  319. package/dist/cli/commands/record.js +18 -5
  320. package/dist/cli/commands/verify.js +326 -64
  321. package/dist/cli/dependencies.d.ts +136 -1
  322. package/dist/cli/dependencies.js +504 -52
  323. package/dist/cli/diagnostics-recorder.d.ts +31 -0
  324. package/dist/cli/diagnostics-recorder.js +82 -0
  325. package/dist/cli/diff-verification.d.ts +41 -0
  326. package/dist/cli/diff-verification.js +192 -0
  327. package/dist/cli/main.d.ts +1 -1
  328. package/dist/cli/main.js +107 -2
  329. package/dist/cli/output.d.ts +5 -3
  330. package/dist/cli/output.js +12 -0
  331. package/dist/cli/program.js +21 -1
  332. package/dist/cli/project-root.d.ts +1 -0
  333. package/dist/cli/project-root.js +6 -0
  334. package/dist/cli/runtime-selection.d.ts +22 -0
  335. package/dist/cli/runtime-selection.js +92 -0
  336. package/dist/cli/version.d.ts +1 -0
  337. package/dist/cli/version.js +14 -0
  338. package/dist/cli/workspace-guard.d.ts +2 -0
  339. package/dist/cli/workspace-guard.js +3 -0
  340. package/dist/domain/binding-reference.d.ts +4 -0
  341. package/dist/domain/binding-reference.js +23 -0
  342. package/dist/domain/checkpoint.d.ts +404 -0
  343. package/dist/domain/checkpoint.js +297 -0
  344. package/dist/domain/config.d.ts +107 -1
  345. package/dist/domain/config.js +52 -6
  346. package/dist/domain/contract.d.ts +413 -0
  347. package/dist/domain/contract.js +196 -0
  348. package/dist/domain/diagnostics.d.ts +1396 -0
  349. package/dist/domain/diagnostics.js +193 -0
  350. package/dist/domain/external-flow.d.ts +493 -0
  351. package/dist/domain/external-flow.js +15 -0
  352. package/dist/domain/failure-classification.d.ts +176 -0
  353. package/dist/domain/failure-classification.js +169 -0
  354. package/dist/domain/failure.d.ts +2 -1
  355. package/dist/domain/failure.js +123 -0
  356. package/dist/domain/generation.d.ts +1671 -132
  357. package/dist/domain/generation.js +202 -13
  358. package/dist/domain/geometry.d.ts +13 -0
  359. package/dist/domain/geometry.js +16 -0
  360. package/dist/domain/impact.d.ts +71 -0
  361. package/dist/domain/impact.js +76 -0
  362. package/dist/domain/init.d.ts +12 -4
  363. package/dist/domain/init.js +24 -18
  364. package/dist/domain/journey-composition.d.ts +2285 -0
  365. package/dist/domain/journey-composition.js +85 -0
  366. package/dist/domain/journey-lifecycle.d.ts +10 -0
  367. package/dist/domain/journey-lifecycle.js +9 -0
  368. package/dist/domain/journey.d.ts +2419 -212
  369. package/dist/domain/journey.js +384 -14
  370. package/dist/domain/knowledge.d.ts +165 -0
  371. package/dist/domain/knowledge.js +188 -0
  372. package/dist/domain/layout.d.ts +34 -5
  373. package/dist/domain/layout.js +64 -6
  374. package/dist/domain/locator-evidence.d.ts +3 -0
  375. package/dist/domain/locator-evidence.js +36 -0
  376. package/dist/domain/logcat-event.d.ts +54 -0
  377. package/dist/domain/logcat-event.js +40 -0
  378. package/dist/domain/observation.d.ts +33 -0
  379. package/dist/domain/observation.js +18 -0
  380. package/dist/domain/project-context.d.ts +219 -1
  381. package/dist/domain/project-context.js +209 -6
  382. package/dist/domain/proposed-step.d.ts +340 -60
  383. package/dist/domain/proposed-step.js +23 -3
  384. package/dist/domain/report.d.ts +1455 -28
  385. package/dist/domain/report.js +211 -14
  386. package/dist/domain/runtime-snapshot.d.ts +58 -1
  387. package/dist/domain/runtime-snapshot.js +14 -3
  388. package/dist/domain/runtime.d.ts +50 -0
  389. package/dist/domain/runtime.js +36 -0
  390. package/dist/domain/system-app-profiles.d.ts +6 -0
  391. package/dist/domain/system-app-profiles.js +43 -0
  392. package/dist/domain/ui-backend.d.ts +28 -0
  393. package/dist/domain/ui-backend.js +33 -0
  394. package/dist/domain/ui-cache.d.ts +11 -0
  395. package/dist/domain/ui-cache.js +10 -0
  396. package/dist/domain/verify-receipt.d.ts +17 -0
  397. package/dist/domain/verify-receipt.js +17 -0
  398. package/dist/domain/window-hierarchy.d.ts +84 -0
  399. package/dist/domain/window-hierarchy.js +172 -0
  400. package/dist/domain/workflow-manifest.d.ts +77 -0
  401. package/dist/domain/workflow-manifest.js +136 -0
  402. package/dist/domain/workspace.d.ts +30 -0
  403. package/dist/domain/workspace.js +74 -0
  404. package/dist/ports/adb.d.ts +34 -1
  405. package/dist/ports/align-prompt.d.ts +11 -0
  406. package/dist/ports/align-prompt.js +6 -0
  407. package/dist/ports/anchor-resolver.d.ts +30 -0
  408. package/dist/ports/anchor-resolver.js +1 -0
  409. package/dist/ports/annotated-screen-resolver.d.ts +4 -0
  410. package/dist/ports/annotated-screen-resolver.js +1 -0
  411. package/dist/ports/camera-probe.d.ts +23 -0
  412. package/dist/ports/camera-probe.js +8 -0
  413. package/dist/ports/context-document-writer.d.ts +24 -0
  414. package/dist/ports/context-document-writer.js +1 -0
  415. package/dist/ports/detached-process-launcher.d.ts +12 -0
  416. package/dist/ports/detached-process-launcher.js +1 -0
  417. package/dist/ports/diagnostics.d.ts +15 -0
  418. package/dist/ports/diagnostics.js +1 -0
  419. package/dist/ports/external-flow-registry.d.ts +37 -0
  420. package/dist/ports/external-flow-registry.js +1 -0
  421. package/dist/ports/generation-prompt.d.ts +2 -1
  422. package/dist/ports/generation-session-store.d.ts +12 -2
  423. package/dist/ports/git-diff.d.ts +9 -0
  424. package/dist/ports/git-diff.js +1 -0
  425. package/dist/ports/journey-composition-store.d.ts +17 -0
  426. package/dist/ports/journey-composition-store.js +1 -0
  427. package/dist/ports/knowledge-registry.d.ts +20 -0
  428. package/dist/ports/knowledge-registry.js +1 -0
  429. package/dist/ports/process-runner.d.ts +7 -0
  430. package/dist/ports/project-file-inspector.d.ts +1 -0
  431. package/dist/ports/project-identity-inspector.d.ts +24 -0
  432. package/dist/ports/project-identity-inspector.js +1 -0
  433. package/dist/ports/project-inventory-inspector.d.ts +21 -0
  434. package/dist/ports/project-inventory-inspector.js +1 -0
  435. package/dist/ports/project-module-discoverer.d.ts +26 -0
  436. package/dist/ports/project-module-discoverer.js +1 -0
  437. package/dist/ports/recorder-prompt.d.ts +10 -1
  438. package/dist/ports/runtime-backend.d.ts +89 -0
  439. package/dist/ports/runtime-backend.js +13 -0
  440. package/dist/ports/runtime-capability.d.ts +12 -0
  441. package/dist/ports/runtime-capability.js +20 -0
  442. package/dist/ports/runtime-session-ports.d.ts +25 -0
  443. package/dist/ports/runtime-session-ports.js +1 -0
  444. package/dist/ports/screenshot.d.ts +11 -0
  445. package/dist/ports/screenshot.js +1 -0
  446. package/dist/ports/skill-installer.d.ts +2 -2
  447. package/dist/ports/ui-snapshot.d.ts +35 -0
  448. package/dist/ports/ui-snapshot.js +1 -0
  449. package/dist/ports/ui-stability.d.ts +19 -0
  450. package/dist/ports/ui-stability.js +1 -0
  451. package/dist/ports/workspace-layout.d.ts +4 -0
  452. package/dist/ports/workspace-layout.js +1 -0
  453. package/dist/shared/errors.d.ts +2 -0
  454. package/dist/shared/errors.js +22 -0
  455. package/dist/shared/paths.d.ts +2 -0
  456. package/dist/shared/paths.js +24 -0
  457. package/dist/shared/strings.d.ts +2 -0
  458. package/dist/shared/strings.js +16 -0
  459. package/package.json +7 -2
  460. package/assets/skills/taphound-ai-journey/GUIDE.md +0 -862
  461. package/assets/skills/taphound-ai-journey/SKILL.md +0 -377
  462. package/assets/skills/taphound-ai-journey/prompts/analyze-project.md +0 -526
  463. package/assets/skills/taphound-ai-journey/prompts/generate-step.md +0 -109
  464. package/assets/skills/taphound-ai-journey/schemas/project-context.json +0 -81
  465. package/assets/skills/taphound-ai-journey/templates/project-context.example.json +0 -49
  466. package/dist/ports/android-cli.d.ts +0 -21
  467. /package/dist/{ports/android-cli.js → adapters/runtime/mobile-mcp/mobile-mcp-tools.js} +0 -0
@@ -0,0 +1,585 @@
1
+ #!/usr/bin/env node
2
+ // taphound-flash: a zero-dependency smoke check for an installed Android app.
3
+ //
4
+ // node flash.mjs run <plan.json> [--device <serial>] [--out <dir>]
5
+ //
6
+ // Needs Node.js 18+ and adb (on PATH, or set ADB=/path/to/adb). Prints one
7
+ // JSON result to stdout. Exit codes: 0 passed, 1 failed, 2 invalid plan or
8
+ // usage, 3 environment (adb, device, or app unavailable).
9
+ import { execFile } from "node:child_process";
10
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
11
+ import { tmpdir } from "node:os";
12
+ import { join, resolve } from "node:path";
13
+ import process from "node:process";
14
+ import { setTimeout as sleep } from "node:timers/promises";
15
+ import { fileURLToPath } from "node:url";
16
+
17
+ const NOTICE = "Smoke check only, not verification evidence: use taphound-verify-change to prove a change.";
18
+ const DUMP_PATH = "/sdcard/taphound-flash.xml";
19
+ const POLL_MS = 250;
20
+ // Shell commands that change what the device shows.
21
+ const MUTATIONS = new Set(["input", "am", "monkey", "wm"]);
22
+ const TARGET_KEYS = ["id", "text", "desc"];
23
+ const ACTIONS = {
24
+ tap: ["target"],
25
+ type: ["text"],
26
+ back: [],
27
+ wait: ["ms"],
28
+ expect: ["target", "absent", "timeoutMs"],
29
+ expectActivity: ["activity", "timeoutMs"]
30
+ };
31
+
32
+ class FlashError extends Error {
33
+ constructor(code, message, exitCode = 1) {
34
+ super(message);
35
+ this.code = code;
36
+ this.exitCode = exitCode;
37
+ }
38
+ }
39
+
40
+
41
+ // ---------------------------------------------------------------- plan
42
+
43
+ function fail(message) {
44
+ throw new FlashError("PLAN_INVALID", message, 2);
45
+ }
46
+
47
+ function onlyKeys(value, allowed, where) {
48
+ for (const key of Object.keys(value)) {
49
+ if (!allowed.includes(key)) fail(`${where}: unknown field "${key}"`);
50
+ }
51
+ }
52
+
53
+ function positiveInt(value, where) {
54
+ if (!Number.isInteger(value) || value <= 0) fail(`${where} must be a positive integer`);
55
+ return value;
56
+ }
57
+
58
+ function parseTarget(value, where) {
59
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
60
+ fail(`${where} must be an object with one of ${TARGET_KEYS.join(", ")}`);
61
+ }
62
+ onlyKeys(value, TARGET_KEYS, where);
63
+ const keys = Object.keys(value);
64
+ if (keys.length !== 1 || typeof value[keys[0]] !== "string" || value[keys[0]] === "") {
65
+ fail(`${where} needs exactly one non-empty ${TARGET_KEYS.join("/")} string`);
66
+ }
67
+ return value;
68
+ }
69
+
70
+ export function parsePlan(value) {
71
+ if (value === null || typeof value !== "object" || Array.isArray(value)) fail("plan must be an object");
72
+ onlyKeys(value, ["version", "packageName", "activity", "device", "timeoutMs", "settleTimeoutMs", "steps"], "plan");
73
+ if (value.version !== 1) fail("plan.version must be 1");
74
+ if (typeof value.packageName !== "string" || !/^[A-Za-z][\w]*(\.[A-Za-z][\w]*)+$/.test(value.packageName)) {
75
+ fail("plan.packageName must be an Android package name");
76
+ }
77
+ if (value.activity !== undefined && (typeof value.activity !== "string" || value.activity === "")) {
78
+ fail("plan.activity must be a non-empty string");
79
+ }
80
+ if (value.device !== undefined && (typeof value.device !== "string" || value.device === "")) {
81
+ fail("plan.device must be a non-empty string");
82
+ }
83
+ const plan = {
84
+ packageName: value.packageName,
85
+ activity: value.activity,
86
+ device: value.device,
87
+ timeoutMs: value.timeoutMs === undefined ? 10_000 : positiveInt(value.timeoutMs, "plan.timeoutMs"),
88
+ settleTimeoutMs: value.settleTimeoutMs === undefined
89
+ ? 5_000
90
+ : positiveInt(value.settleTimeoutMs, "plan.settleTimeoutMs"),
91
+ steps: []
92
+ };
93
+ if (!Array.isArray(value.steps) || value.steps.length === 0 || value.steps.length > 50) {
94
+ fail("plan.steps must hold 1 to 50 steps");
95
+ }
96
+ for (const [index, step] of value.steps.entries()) {
97
+ const where = `steps[${index}]`;
98
+ if (step === null || typeof step !== "object" || typeof step.action !== "string") {
99
+ fail(`${where} needs an action`);
100
+ }
101
+ const fields = ACTIONS[step.action];
102
+ if (fields === undefined) fail(`${where}: unknown action "${step.action}"`);
103
+ onlyKeys(step, ["action", ...fields], where);
104
+ const parsed = { action: step.action };
105
+ if (fields.includes("target")) parsed.target = parseTarget(step.target, `${where}.target`);
106
+ if (step.action === "type") {
107
+ if (typeof step.text !== "string" || !/^[\x20-\x7e]+$/.test(step.text)) {
108
+ fail(`${where}.text must be printable ASCII (adb input cannot type other characters)`);
109
+ }
110
+ parsed.text = step.text;
111
+ }
112
+ if (step.action === "wait") parsed.ms = positiveInt(step.ms, `${where}.ms`);
113
+ if (step.action === "expect") {
114
+ if (step.absent !== undefined && typeof step.absent !== "boolean") fail(`${where}.absent must be a boolean`);
115
+ parsed.absent = step.absent === true;
116
+ }
117
+ if (step.action === "expectActivity") {
118
+ if (typeof step.activity !== "string" || step.activity === "") fail(`${where}.activity must be a non-empty string`);
119
+ parsed.activity = step.activity;
120
+ }
121
+ if (step.timeoutMs !== undefined) parsed.timeoutMs = positiveInt(step.timeoutMs, `${where}.timeoutMs`);
122
+ plan.steps.push(parsed);
123
+ }
124
+ return plan;
125
+ }
126
+
127
+ // ---------------------------------------------------------------- adb
128
+
129
+ function adbBinary() {
130
+ return process.env.ADB ?? "adb";
131
+ }
132
+
133
+ function adb(args, { binary = false, timeoutMs = 30_000 } = {}) {
134
+ return new Promise((done, reject) => {
135
+ execFile(adbBinary(), args, {
136
+ encoding: binary ? "buffer" : "utf8",
137
+ maxBuffer: 64 * 1024 * 1024,
138
+ timeout: timeoutMs,
139
+ shell: false
140
+ }, (error, stdout, stderr) => {
141
+ if (error !== null && error.code === "ENOENT") {
142
+ reject(new FlashError("ADB_MISSING", `adb not found (${adbBinary()}); install Android platform-tools or set ADB`, 3));
143
+ return;
144
+ }
145
+ done({ code: error === null ? 0 : (typeof error.code === "number" ? error.code : 1), stdout, stderr: String(stderr) });
146
+ });
147
+ });
148
+ }
149
+
150
+ class Device {
151
+ constructor(serial, packageName) {
152
+ this.serial = serial;
153
+ this.packageName = packageName;
154
+ // The last settled UI dump, valid until the next device mutation or wait.
155
+ this.settled = undefined;
156
+ }
157
+
158
+ /** The settled dump once, then fresh dumps (it may be stale after a poll). */
159
+ async observe() {
160
+ const cached = this.settled;
161
+ this.settled = undefined;
162
+ return cached ?? this.dump();
163
+ }
164
+
165
+ run(args, options) {
166
+ if (args[0] === "shell" && MUTATIONS.has(args[1])) this.settled = undefined;
167
+ return adb(["-s", this.serial, ...args], options);
168
+ }
169
+
170
+ async shell(args, what) {
171
+ const result = await this.run(["shell", ...args]);
172
+ if (result.code !== 0) {
173
+ throw new FlashError("ADB_FAILED", `${what} failed: ${result.stderr.trim() || result.stdout.trim()}`);
174
+ }
175
+ return result.stdout;
176
+ }
177
+
178
+ async pid() {
179
+ const result = await this.run(["shell", "pidof", this.packageName]);
180
+ return result.stdout.trim();
181
+ }
182
+
183
+ async foreground() {
184
+ const activities = await this.run(["shell", "dumpsys", "activity", "activities"]);
185
+ const match = /(?:topResumedActivity|mResumedActivity|ResumedActivity)[=:]\s*ActivityRecord\{\S+ \S+ ([\w.]+)\/([\w.$]+)/
186
+ .exec(activities.stdout);
187
+ if (match !== null) return { packageName: match[1], activity: qualify(match[1], match[2]) };
188
+ const windows = await this.run(["shell", "dumpsys", "window"]);
189
+ const focus = /mCurrentFocus=Window\{\S+ \S+ ([\w.]+)\/([\w.$]+)\}/.exec(windows.stdout);
190
+ return focus === null
191
+ ? { packageName: "", activity: "" }
192
+ : { packageName: focus[1], activity: qualify(focus[1], focus[2]) };
193
+ }
194
+
195
+ async dump() {
196
+ const dumped = await this.run(["shell", "uiautomator", "dump", DUMP_PATH]);
197
+ if (dumped.code !== 0 || !/dumped to/i.test(dumped.stdout)) {
198
+ throw new FlashError("UI_DUMP_FAILED", `uiautomator dump failed: ${(dumped.stderr || dumped.stdout).trim()}`);
199
+ }
200
+ const read = await this.run(["exec-out", "cat", DUMP_PATH]);
201
+ if (read.code !== 0 || !read.stdout.includes("<hierarchy")) {
202
+ throw new FlashError("UI_DUMP_FAILED", "Could not read the UI hierarchy dump");
203
+ }
204
+ return read.stdout;
205
+ }
206
+ }
207
+
208
+ function qualify(packageName, activity) {
209
+ return activity.startsWith(".") ? `${packageName}${activity}` : activity;
210
+ }
211
+
212
+ // ---------------------------------------------------------------- layout
213
+
214
+ const ENTITIES = { amp: "&", lt: "<", gt: ">", quot: "\"", apos: "'" };
215
+
216
+ function decode(value) {
217
+ return value.replace(/&(#x?[\da-fA-F]+|\w+);/g, (whole, name) => {
218
+ if (name.startsWith("#x")) return String.fromCodePoint(Number.parseInt(name.slice(2), 16));
219
+ if (name.startsWith("#")) return String.fromCodePoint(Number.parseInt(name.slice(1), 10));
220
+ return ENTITIES[name] ?? whole;
221
+ });
222
+ }
223
+
224
+ export function parseHierarchy(xml) {
225
+ const nodes = [];
226
+ const stack = [];
227
+ const token = /<node\b([^>]*?)(\/?)>|<\/node>/g;
228
+ let match;
229
+ while ((match = token.exec(xml)) !== null) {
230
+ if (match[0] === "</node>") {
231
+ stack.pop();
232
+ continue;
233
+ }
234
+ const attributes = {};
235
+ for (const [, key, value] of match[1].matchAll(/([\w-]+)="([^"]*)"/g)) attributes[key] = decode(value);
236
+ const bounds = /^\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]$/.exec(attributes.bounds ?? "");
237
+ const node = {
238
+ id: attributes["resource-id"] ?? "",
239
+ text: attributes.text ?? "",
240
+ desc: attributes["content-desc"] ?? "",
241
+ packageName: attributes.package ?? "",
242
+ clickable: attributes.clickable === "true",
243
+ enabled: attributes.enabled !== "false",
244
+ bounds: bounds === null ? undefined : bounds.slice(1).map(Number),
245
+ parent: stack.at(-1)
246
+ };
247
+ nodes.push(node);
248
+ if (match[2] !== "/") stack.push(node);
249
+ }
250
+ return nodes;
251
+ }
252
+
253
+ function describe(target) {
254
+ const [key] = Object.keys(target);
255
+ return `${key}=${JSON.stringify(target[key])}`;
256
+ }
257
+
258
+ export function findTarget(nodes, target, packageName) {
259
+ const inApp = nodes.filter((node) => node.packageName === packageName);
260
+ if (target.id !== undefined) {
261
+ return inApp.filter((node) => node.id === target.id || node.id.endsWith(`:id/${target.id}`));
262
+ }
263
+ if (target.text !== undefined) return inApp.filter((node) => node.text === target.text);
264
+ return inApp.filter((node) => node.desc === target.desc);
265
+ }
266
+
267
+ function center(bounds) {
268
+ return [Math.round((bounds[0] + bounds[2]) / 2), Math.round((bounds[1] + bounds[3]) / 2)];
269
+ }
270
+
271
+ /**
272
+ * The touch point for a matched element: its own center, handled by the
273
+ * element itself or its nearest clickable ancestor (the same rule as
274
+ * TapHound Core), so a label inside a row taps the label, not the row center.
275
+ */
276
+ export function tapPoint(node) {
277
+ let handler = node;
278
+ while (handler !== undefined && !handler.clickable) handler = handler.parent;
279
+ if (handler === undefined) {
280
+ throw new FlashError("NOT_CLICKABLE", "Neither the target nor any ancestor is clickable");
281
+ }
282
+ if (!handler.enabled) throw new FlashError("TARGET_DISABLED", "The element that handles the tap is disabled");
283
+ const own = node.bounds === undefined ? undefined : center(node.bounds);
284
+ const box = handler.bounds;
285
+ if (own !== undefined && box !== undefined
286
+ && own[0] >= box[0] && own[0] < box[2] && own[1] >= box[1] && own[1] < box[3]) {
287
+ return own;
288
+ }
289
+ if (box === undefined) throw new FlashError("NOT_CLICKABLE", "The tap target has no bounds");
290
+ return center(box);
291
+ }
292
+
293
+ // ---------------------------------------------------------------- runner
294
+
295
+ async function settle(device, timeoutMs) {
296
+ const deadline = Date.now() + timeoutMs;
297
+ let previous = await device.dump();
298
+ for (;;) {
299
+ await sleep(POLL_MS);
300
+ const current = await device.dump();
301
+ if (current === previous) {
302
+ device.settled = current;
303
+ return current;
304
+ }
305
+ if (Date.now() >= deadline) {
306
+ throw new FlashError("UNSETTLED", `The UI kept changing for ${timeoutMs} ms`);
307
+ }
308
+ previous = current;
309
+ }
310
+ }
311
+
312
+ async function assertInApp(device) {
313
+ if (await device.pid() === "") throw new FlashError("APP_CRASHED", `${device.packageName} is no longer running`);
314
+ const foreground = await device.foreground();
315
+ if (foreground.packageName !== device.packageName) {
316
+ throw new FlashError("LEFT_APP", `Foreground is ${foreground.packageName || "unknown"}, not ${device.packageName}`);
317
+ }
318
+ }
319
+
320
+ async function poll(timeoutMs, attempt) {
321
+ const deadline = Date.now() + timeoutMs;
322
+ for (;;) {
323
+ const outcome = await attempt();
324
+ if (outcome.done) return outcome.value;
325
+ if (Date.now() >= deadline) throw outcome.error;
326
+ await sleep(POLL_MS);
327
+ }
328
+ }
329
+
330
+ async function keyguardShowing(device) {
331
+ const windows = await device.run(["shell", "dumpsys", "window"]);
332
+ return /isKeyguardShowing=true|mDreamingLockscreen=true|mShowingLockscreen=true/.test(windows.stdout);
333
+ }
334
+
335
+ /** A sleeping or locked screen hides the app from the UI hierarchy. */
336
+ async function ensureAwake(device) {
337
+ const power = await device.run(["shell", "dumpsys", "power"]);
338
+ if (/mWakefulness=(Asleep|Dozing)/.test(power.stdout)) {
339
+ await device.shell(["input", "keyevent", "224"], "wake up");
340
+ await sleep(500);
341
+ }
342
+ if (await keyguardShowing(device)) {
343
+ await device.run(["shell", "wm", "dismiss-keyguard"]);
344
+ await sleep(1_000);
345
+ if (await keyguardShowing(device)) {
346
+ throw new FlashError(
347
+ "DEVICE_LOCKED",
348
+ "The device is locked; unlock it first (a secure lock screen cannot be dismissed over adb)",
349
+ 3
350
+ );
351
+ }
352
+ }
353
+ }
354
+
355
+ function visiblePackages(nodes) {
356
+ return [...new Set(nodes.map((node) => node.packageName).filter((name) => name !== ""))];
357
+ }
358
+
359
+ async function launch(device, plan) {
360
+ await ensureAwake(device);
361
+ await device.shell(["am", "force-stop", plan.packageName], "force-stop");
362
+ const started = plan.activity === undefined
363
+ ? await device.run(["shell", "monkey", "-p", plan.packageName, "-c", "android.intent.category.LAUNCHER", "1"])
364
+ : await device.run(["shell", "am", "start", "-W", "-n", `${plan.packageName}/${plan.activity}`]);
365
+ if (started.code !== 0 || /^Error|No activities found|monkey aborted/im.test(started.stdout)) {
366
+ throw new FlashError("LAUNCH_FAILED", `Could not launch ${plan.packageName}: ${(started.stdout + started.stderr).trim()}`);
367
+ }
368
+ await poll(plan.timeoutMs, async () => ((await device.pid()) === ""
369
+ ? { done: false, error: new FlashError("LAUNCH_FAILED", "The app process did not start") }
370
+ : { done: true }));
371
+ await assertInApp(device);
372
+ await poll(plan.timeoutMs, async () => {
373
+ const nodes = parseHierarchy(await settle(device, plan.settleTimeoutMs));
374
+ return nodes.some((node) => node.packageName === plan.packageName)
375
+ ? { done: true }
376
+ : {
377
+ done: false,
378
+ error: new FlashError(
379
+ "APP_NOT_VISIBLE",
380
+ `The app runs but its UI is not visible; the screen shows ${visiblePackages(nodes).join(", ") || "nothing"}`
381
+ )
382
+ };
383
+ });
384
+ }
385
+
386
+ async function runStep(device, plan, step) {
387
+ const timeoutMs = step.timeoutMs ?? plan.timeoutMs;
388
+ switch (step.action) {
389
+ case "tap": {
390
+ const node = await poll(timeoutMs, async () => {
391
+ const matches = findTarget(parseHierarchy(await device.observe()), step.target, plan.packageName);
392
+ if (matches.length === 1) return { done: true, value: matches[0] };
393
+ if (matches.length > 1) {
394
+ throw new FlashError("TARGET_AMBIGUOUS", `${describe(step.target)} matches ${matches.length} elements`);
395
+ }
396
+ return { done: false, error: new FlashError("TARGET_NOT_FOUND", `No element with ${describe(step.target)}`) };
397
+ });
398
+ const [x, y] = tapPoint(node);
399
+ await device.shell(["input", "tap", String(x), String(y)], "tap");
400
+ return `tapped ${describe(step.target)} at ${x},${y}`;
401
+ }
402
+ case "type": {
403
+ const escaped = step.text.replace(/[\\"'`$&|;<>()*~!#?[\]{}]/g, (character) => `\\${character}`).replaceAll(" ", "%s");
404
+ await device.shell(["input", "text", escaped], "input text");
405
+ return `typed ${step.text.length} characters`;
406
+ }
407
+ case "back":
408
+ await device.shell(["input", "keyevent", "4"], "back");
409
+ return "pressed back";
410
+ case "wait":
411
+ device.settled = undefined;
412
+ await sleep(step.ms);
413
+ return `waited ${step.ms} ms`;
414
+ case "expect":
415
+ return poll(timeoutMs, async () => {
416
+ const matches = findTarget(parseHierarchy(await device.observe()), step.target, plan.packageName);
417
+ if (step.absent) {
418
+ return matches.length === 0
419
+ ? { done: true, value: `${describe(step.target)} is absent` }
420
+ : { done: false, error: new FlashError("EXPECT_FAILED", `${describe(step.target)} is still shown`) };
421
+ }
422
+ if (matches.length === 1) return { done: true, value: `${describe(step.target)} is shown` };
423
+ return {
424
+ done: false,
425
+ error: new FlashError(
426
+ matches.length > 1 ? "TARGET_AMBIGUOUS" : "EXPECT_FAILED",
427
+ matches.length > 1
428
+ ? `${describe(step.target)} matches ${matches.length} elements`
429
+ : `${describe(step.target)} did not appear within ${timeoutMs} ms`
430
+ )
431
+ };
432
+ });
433
+ case "expectActivity": {
434
+ const expected = qualify(plan.packageName, step.activity);
435
+ return poll(timeoutMs, async () => {
436
+ const { activity } = await device.foreground();
437
+ return activity === expected
438
+ ? { done: true, value: `${expected} is in the foreground` }
439
+ : { done: false, error: new FlashError("ACTIVITY_MISMATCH", `Expected ${expected}, found ${activity || "none"}`) };
440
+ });
441
+ }
442
+ default:
443
+ throw new FlashError("PLAN_INVALID", `Unknown action ${step.action}`, 2);
444
+ }
445
+ }
446
+
447
+ async function selectDevice(requested) {
448
+ const listed = await adb(["devices"]);
449
+ const online = listed.stdout.split(/\r?\n/)
450
+ .map((line) => line.trim().split(/\s+/))
451
+ .filter((parts) => parts.length >= 2 && parts[1] === "device")
452
+ .map((parts) => parts[0]);
453
+ if (requested !== undefined) {
454
+ if (!online.includes(requested)) throw new FlashError("DEVICE_UNAVAILABLE", `Device ${requested} is not online`, 3);
455
+ return requested;
456
+ }
457
+ if (online.length !== 1) {
458
+ throw new FlashError("DEVICE_UNAVAILABLE", `Expected exactly one online device, found ${online.length}; pass --device`, 3);
459
+ }
460
+ return online[0];
461
+ }
462
+
463
+ async function collectFailureEvidence(device, dir, code) {
464
+ const evidence = {};
465
+ try {
466
+ const shot = await device.run(["exec-out", "screencap", "-p"], { binary: true });
467
+ if (shot.code === 0 && shot.stdout.length > 0) {
468
+ evidence.screenshot = join(dir, "failure.png");
469
+ await writeFile(evidence.screenshot, shot.stdout);
470
+ }
471
+ } catch { /* best effort */ }
472
+ try {
473
+ evidence.hierarchy = join(dir, "failure.xml");
474
+ await writeFile(evidence.hierarchy, await device.dump());
475
+ } catch {
476
+ delete evidence.hierarchy;
477
+ }
478
+ if (code === "APP_CRASHED" || code === "LEFT_APP") {
479
+ const crash = await device.run(["logcat", "-d", "-b", "crash", "-t", "200"]);
480
+ if (crash.code === 0 && crash.stdout.trim() !== "") {
481
+ evidence.crashLog = join(dir, "crash.txt");
482
+ await writeFile(evidence.crashLog, crash.stdout);
483
+ }
484
+ }
485
+ return evidence;
486
+ }
487
+
488
+ export async function runPlan(plan, { device: requested, out }) {
489
+ const startedAt = Date.now();
490
+ const serial = await selectDevice(requested ?? plan.device);
491
+ const device = new Device(serial, plan.packageName);
492
+ const installed = await device.run(["shell", "pm", "path", plan.packageName]);
493
+ if (!installed.stdout.includes("package:")) {
494
+ throw new FlashError("APP_NOT_INSTALLED", `${plan.packageName} is not installed on ${serial}`, 3);
495
+ }
496
+ await mkdir(out, { recursive: true });
497
+ const steps = plan.steps.map((step, index) => ({ index, action: step.action, status: "notRun" }));
498
+ const result = {
499
+ tool: "taphound-flash",
500
+ version: 1,
501
+ status: "passed",
502
+ device: serial,
503
+ packageName: plan.packageName,
504
+ steps,
505
+ evidence: { dir: out },
506
+ notice: NOTICE
507
+ };
508
+ let current = -1;
509
+ try {
510
+ await launch(device, plan);
511
+ for (const [index, step] of plan.steps.entries()) {
512
+ current = index;
513
+ const stepStartedAt = Date.now();
514
+ steps[index].detail = await runStep(device, plan, step);
515
+ if (step.action !== "expect" && step.action !== "expectActivity") {
516
+ await settle(device, plan.settleTimeoutMs);
517
+ }
518
+ await assertInApp(device);
519
+ steps[index].status = "passed";
520
+ steps[index].durationMs = Date.now() - stepStartedAt;
521
+ }
522
+ const shot = await device.run(["exec-out", "screencap", "-p"], { binary: true });
523
+ if (shot.code === 0 && shot.stdout.length > 0) {
524
+ result.evidence.screenshot = join(out, "final.png");
525
+ await writeFile(result.evidence.screenshot, shot.stdout);
526
+ }
527
+ } catch (error) {
528
+ if (!(error instanceof FlashError) || error.exitCode !== 1) throw error;
529
+ result.status = "failed";
530
+ result.failure = { stepIndex: current === -1 ? null : current, code: error.code, message: error.message };
531
+ if (current !== -1) {
532
+ steps[current].status = "failed";
533
+ steps[current].detail = error.message;
534
+ }
535
+ Object.assign(result.evidence, await collectFailureEvidence(device, out, error.code));
536
+ }
537
+ result.durationMs = Date.now() - startedAt;
538
+ return result;
539
+ }
540
+
541
+ // ---------------------------------------------------------------- cli
542
+
543
+ function parseArgs(argv) {
544
+ const [command, planPath, ...rest] = argv;
545
+ if (command !== "run" || planPath === undefined) {
546
+ throw new FlashError("USAGE", "Usage: flash.mjs run <plan.json> [--device <serial>] [--out <dir>]", 2);
547
+ }
548
+ const options = { planPath };
549
+ for (let index = 0; index < rest.length; index += 2) {
550
+ const [flag, value] = [rest[index], rest[index + 1]];
551
+ if ((flag !== "--device" && flag !== "--out") || value === undefined) {
552
+ throw new FlashError("USAGE", `Unknown or incomplete option ${flag}`, 2);
553
+ }
554
+ options[flag.slice(2)] = value;
555
+ }
556
+ return options;
557
+ }
558
+
559
+ async function main() {
560
+ let exitCode;
561
+ let output;
562
+ try {
563
+ const options = parseArgs(process.argv.slice(2));
564
+ let raw;
565
+ try {
566
+ raw = JSON.parse(await readFile(options.planPath, "utf8"));
567
+ } catch (error) {
568
+ throw new FlashError("PLAN_INVALID", `Cannot read plan ${options.planPath}: ${error.message}`, 2);
569
+ }
570
+ const plan = parsePlan(raw);
571
+ const out = resolve(options.out ?? join(tmpdir(), "taphound-flash", new Date().toISOString().replaceAll(":", "-")));
572
+ output = await runPlan(plan, { device: options.device, out });
573
+ exitCode = output.status === "passed" ? 0 : 1;
574
+ } catch (error) {
575
+ const code = error instanceof FlashError ? error.code : "INTERNAL_ERROR";
576
+ exitCode = error instanceof FlashError ? error.exitCode : 1;
577
+ output = { tool: "taphound-flash", version: 1, status: "error", failure: { code, message: error.message }, notice: NOTICE };
578
+ }
579
+ process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
580
+ process.exitCode = exitCode;
581
+ }
582
+
583
+ if (process.argv[1] !== undefined && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
584
+ await main();
585
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "version": 1,
3
+ "packageName": "dev.taphound.demo",
4
+ "activity": ".MainActivity",
5
+ "steps": [
6
+ { "action": "tap", "target": { "id": "open_search" } },
7
+ { "action": "expectActivity", "activity": ".SearchActivity" },
8
+ { "action": "tap", "target": { "id": "search_input" } },
9
+ { "action": "type", "text": "hello world" },
10
+ { "action": "tap", "target": { "id": "submit_search" } },
11
+ { "action": "expect", "target": { "desc": "submitted query=hello world" } },
12
+ { "action": "back" },
13
+ { "action": "expectActivity", "activity": ".MainActivity" }
14
+ ]
15
+ }