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
@@ -1,12 +1,29 @@
1
1
  import { resolve } from "node:path";
2
2
  import { normalizeActivity } from "../../domain/activity.js";
3
- import { exitCodeForFailure } from "../../domain/failure.js";
3
+ import { exitCodeForFailure, failureCodeFromUnknown } from "../../domain/failure.js";
4
+ import { DEFAULT_DEVICE_ROLE } from "../../domain/journey.js";
4
5
  import { hashJourney } from "../../domain/report.js";
6
+ import { uiStabilityProbe } from "../ui/ui-stability-probe.js";
5
7
  import { LogcatCollector } from "../collector/logcat-collector.js";
8
+ import { CheckpointEvaluator } from "../checkpoint/checkpoint-evaluator.js";
9
+ import { logcatStopFailed } from "../collector/logcat-stop.js";
6
10
  import { ActivityWaiter } from "./activity-waiter.js";
7
- import { launchFailure } from "./launch-failure.js";
8
- import { ProcessWaiter } from "./process-waiter.js";
11
+ import { coldLaunchApp } from "./cold-launch.js";
9
12
  import { StepRunner } from "./step-runner.js";
13
+ /**
14
+ * The first step after a cold launch is the first to see the app's persisted
15
+ * state. TapHound never clears app data, so a target that is missing there
16
+ * most often means that state drifted since the Journey was generated.
17
+ */
18
+ const INITIAL_STATE_FAILURE_CODES = new Set([
19
+ "LOCATOR_NOT_FOUND",
20
+ "ANCHOR_NOT_FOUND",
21
+ "SCROLL_TARGET_NOT_FOUND"
22
+ ]);
23
+ const INITIAL_STATE_HINT = "this is the first step after cold launch: "
24
+ + "TapHound does not reset app data, so check persisted app state such as "
25
+ + "settings, login, or layout preferences against the state the Journey "
26
+ + "was generated in before changing the Journey";
10
27
  function commandFailed(result) {
11
28
  return result.exitCode !== 0
12
29
  || result.timedOut
@@ -16,23 +33,14 @@ function commandFailed(result) {
16
33
  function commandMessage(result, fallback) {
17
34
  return result.stderr.trim() || result.spawnError || fallback;
18
35
  }
19
- function logcatStopFailed(result) {
20
- if (result.timedOut
21
- || result.cancelled
22
- || result.spawnError !== undefined) {
23
- return true;
24
- }
25
- return result.exitCode !== 0
26
- && result.signal !== "SIGTERM"
27
- && result.signal !== "SIGKILL";
28
- }
29
36
  function errorMessage(error) {
30
37
  return error instanceof Error ? error.message : String(error);
31
38
  }
32
39
  function layerForFailure(code) {
33
40
  if (code === "APP_NOT_INSTALLED"
34
41
  || code === "APP_LAUNCH_FAILED"
35
- || code === "APP_CRASHED") {
42
+ || code === "APP_CRASHED"
43
+ || code === "RUNTIME_CAPABILITY_MISSING") {
36
44
  return "run";
37
45
  }
38
46
  if (code === "ACTIVITY_BEFORE_MISMATCH"
@@ -42,27 +50,133 @@ function layerForFailure(code) {
42
50
  if (code.startsWith("EXPECT_")) {
43
51
  return "explicitExpect";
44
52
  }
53
+ if (code === "CHECKPOINT_FAILED" || code === "CHECKPOINT_UNRESOLVED") {
54
+ return "explicitExpect";
55
+ }
45
56
  if (code === "COLLECTION_FAILED") {
46
57
  return "collection";
47
58
  }
48
59
  return "structural";
49
60
  }
61
+ function soleRoleForJourney(journey) {
62
+ return journey.devices[0]?.role ?? DEFAULT_DEVICE_ROLE;
63
+ }
64
+ function taskHookOutcome(phase, runtime, result) {
65
+ return { ...result, phase, deviceRole: runtime.role };
66
+ }
67
+ async function runVerifyHook(phase, runtime, snapshot, hooks, clock) {
68
+ const hook = phase === "beforeSteps"
69
+ ? hooks.beforeSteps
70
+ : hooks.afterSteps;
71
+ if (hook === undefined) {
72
+ return taskHookOutcome(phase, runtime, {
73
+ status: "unresolved",
74
+ message: "Verify hook is not configured"
75
+ });
76
+ }
77
+ try {
78
+ const result = await hook({
79
+ deviceRole: runtime.role,
80
+ deviceSerial: runtime.deviceSerial,
81
+ adb: runtime.views.adb,
82
+ uiSnapshotProvider: runtime.provider,
83
+ logcat: runtime.logcat,
84
+ clock,
85
+ snapshot
86
+ });
87
+ return taskHookOutcome(phase, runtime, result);
88
+ }
89
+ catch (error) {
90
+ return taskHookOutcome(phase, runtime, {
91
+ status: "unresolved",
92
+ message: error instanceof Error ? error.message : String(error)
93
+ });
94
+ }
95
+ }
96
+ function stepDeviceRole(step, soleRole) {
97
+ return step.device ?? soleRole;
98
+ }
99
+ function deviceCoverageFailure(journey, devices) {
100
+ const duplicateRole = devices.find((assignment, index) => devices.some((other, otherIndex) => otherIndex > index && other.role === assignment.role));
101
+ if (duplicateRole !== undefined) {
102
+ return {
103
+ code: "CONFIG_INVALID",
104
+ message: `Duplicate device assignment for role: ${duplicateRole.role}`
105
+ };
106
+ }
107
+ const reusedSerial = devices.find((assignment, index) => devices.some((other, otherIndex) => otherIndex > index
108
+ && other.deviceSerial === assignment.deviceSerial));
109
+ if (reusedSerial !== undefined) {
110
+ return {
111
+ code: "CONFIG_INVALID",
112
+ message: `Device serial is mapped to multiple roles: ${reusedSerial.deviceSerial}`
113
+ };
114
+ }
115
+ const declaredRoles = journey.devices.map((device) => device.role);
116
+ const unknownRole = devices.find((assignment) => !declaredRoles.includes(assignment.role));
117
+ if (unknownRole !== undefined) {
118
+ return {
119
+ code: "CONFIG_INVALID",
120
+ message: `Device assignment references a role the Journey does not declare: ${unknownRole.role}`
121
+ };
122
+ }
123
+ const assignedRoles = new Set(devices.map((assignment) => assignment.role));
124
+ const missingRoles = declaredRoles.filter((role) => !assignedRoles.has(role));
125
+ if (missingRoles.length > 0) {
126
+ return {
127
+ code: "DEVICE_ROLE_UNMAPPED",
128
+ message: `No device mapping for journey role(s): ${missingRoles.join(", ")}`
129
+ };
130
+ }
131
+ return undefined;
132
+ }
133
+ function uniqueAssignmentsByRole(devices) {
134
+ const seen = new Set();
135
+ const unique = [];
136
+ for (const assignment of devices) {
137
+ if (seen.has(assignment.role)) {
138
+ continue;
139
+ }
140
+ seen.add(assignment.role);
141
+ unique.push(assignment);
142
+ }
143
+ return unique;
144
+ }
50
145
  export class VerifyRuntime {
51
146
  dependencies;
52
147
  constructor(dependencies) {
53
148
  this.dependencies = dependencies;
54
149
  }
55
150
  async verify(input) {
151
+ if (input.devices.length === 0) {
152
+ throw new Error("VerifyInput.devices requires at least one device assignment");
153
+ }
154
+ input.progress?.({ stage: "preparing" });
56
155
  const startedAt = this.dependencies.now();
57
156
  const runId = this.dependencies.createRunId();
58
157
  const launchActivity = normalizeActivity(input.config.run.packageName, input.config.run.activity);
59
158
  const session = await this.dependencies.artifactStore.begin(resolve(input.projectRoot, input.config.artifactsDir), runId);
60
- const logcat = new LogcatCollector(this.dependencies.adb, this.dependencies.clock);
61
- let logcatStarted = false;
62
- let primaryFailure;
159
+ const logcatRunStartedAt = this.dependencies.clock.now();
160
+ const markers = new Map();
161
+ const coverage = deviceCoverageFailure(input.journey, input.devices);
162
+ const assignmentByRole = new Map(input.devices.map((assignment) => [assignment.role, assignment]));
163
+ const runtimes = [];
164
+ let primaryFailure = coverage === undefined
165
+ ? undefined
166
+ : {
167
+ code: coverage.code,
168
+ message: coverage.message,
169
+ phase: "runtime"
170
+ };
63
171
  const secondaryErrors = [];
64
172
  const collectionErrors = [];
173
+ const hookOutcomes = [];
65
174
  const steps = [];
175
+ const checkpoints = [];
176
+ const checkpointScreens = [];
177
+ const checkpointEvaluator = new CheckpointEvaluator();
178
+ let knowledge;
179
+ let knowledgeError;
66
180
  const layers = {
67
181
  run: "notRun",
68
182
  structural: "notRun",
@@ -70,6 +184,9 @@ export class VerifyRuntime {
70
184
  explicitExpect: "notRun",
71
185
  collection: "passed"
72
186
  };
187
+ if (coverage !== undefined) {
188
+ layers[layerForFailure(coverage.code)] = "failed";
189
+ }
73
190
  const setPrimary = (code, message, phase, stepIndex) => {
74
191
  if (primaryFailure !== undefined) {
75
192
  return;
@@ -92,197 +209,429 @@ export class VerifyRuntime {
92
209
  collectionErrors.push(failure);
93
210
  };
94
211
  try {
95
- try {
96
- const installed = await this.dependencies.adb.isInstalled({
212
+ if (input.journey.checkpoints?.some((checkpoint) => checkpoint.expect.allOf.some((condition) => condition.kind === "screen"))) {
213
+ if (this.dependencies.loadKnowledge === undefined) {
214
+ knowledgeError = "Project Knowledge loader is not configured";
215
+ }
216
+ else {
217
+ try {
218
+ knowledge = await this.dependencies.loadKnowledge({
219
+ projectRoot: input.projectRoot,
220
+ packageName: input.config.run.packageName
221
+ });
222
+ }
223
+ catch (error) {
224
+ knowledgeError = `Project Knowledge unavailable: ${errorMessage(error)}`;
225
+ }
226
+ }
227
+ }
228
+ const evaluateCheckpoint = async (checkpoint, runtime, stepStartedAt) => {
229
+ if (runtime.provider === undefined) {
230
+ setPrimary("CHECKPOINT_UNRESOLVED", `Checkpoint ${checkpoint.id} has no UI snapshot provider`, "checkpoint", checkpoint.stepIndex);
231
+ return false;
232
+ }
233
+ const evaluation = await checkpointEvaluator.evaluate({
234
+ checkpoint,
235
+ provider: runtime.provider,
236
+ adb: runtime.views.adb,
97
237
  packageName: input.config.run.packageName,
98
- deviceSerial: input.deviceSerial,
99
- ...(input.signal === undefined ? {} : { signal: input.signal }),
100
- timeoutMs: input.config.idle.timeoutMs
238
+ deviceSerial: runtime.deviceSerial,
239
+ timeoutMs: input.config.idle.timeoutMs,
240
+ clock: this.dependencies.clock,
241
+ logcat: runtime.logcat,
242
+ runStartedAt: logcatRunStartedAt,
243
+ markers,
244
+ logcatEvidenceRef: `logcat-${runtime.role}.txt`,
245
+ ...(stepStartedAt === undefined ? {} : { stepStartedAt }),
246
+ writeSnapshot: (path, snapshot) => session.writeJson(path, snapshot),
247
+ ...(knowledge === undefined ? {} : { knowledge }),
248
+ ...(knowledgeError === undefined ? {} : { knowledgeError }),
249
+ ...(input.signal === undefined ? {} : { signal: input.signal })
101
250
  });
102
- if (!installed) {
103
- setPrimary("APP_NOT_INSTALLED", `Package ${input.config.run.packageName} is not installed on ${input.deviceSerial}`, "install");
251
+ checkpoints.push(evaluation.report);
252
+ if (evaluation.matchedScreen !== undefined) {
253
+ checkpointScreens.push({
254
+ screen: evaluation.matchedScreen,
255
+ status: "matched"
256
+ });
257
+ }
258
+ if (evaluation.report.status !== "passed") {
259
+ setPrimary(evaluation.report.status === "failed"
260
+ ? "CHECKPOINT_FAILED"
261
+ : "CHECKPOINT_UNRESOLVED", `Checkpoint ${checkpoint.id} ${evaluation.report.status}: ${evaluation.report.conditions
262
+ .filter((condition) => condition.status !== "passed")
263
+ .map((condition) => condition.message ?? condition.kind)
264
+ .join("; ")}`, "checkpoint", checkpoint.stepIndex);
265
+ return false;
266
+ }
267
+ return true;
268
+ };
269
+ const createStepRunner = this.dependencies.createStepRunner
270
+ ?? ((options) => new StepRunner(options));
271
+ // Fresh for every Replay, shared only across device runners in this run.
272
+ const bindings = new Map();
273
+ if (coverage === undefined) {
274
+ for (const declaration of input.journey.devices) {
275
+ const assignment = assignmentByRole.get(declaration.role);
276
+ if (assignment === undefined) {
277
+ continue;
278
+ }
279
+ const deviceSession = await this.dependencies.sessions.openSession({
280
+ deviceSerial: assignment.deviceSerial,
281
+ ...(input.signal === undefined ? {} : { signal: input.signal })
282
+ });
283
+ const views = this.dependencies.sessionPorts(deviceSession);
284
+ runtimes.push({
285
+ role: declaration.role,
286
+ deviceSerial: assignment.deviceSerial,
287
+ session: deviceSession,
288
+ views,
289
+ provider: undefined,
290
+ readySnapshot: undefined,
291
+ logcat: new LogcatCollector(views.adb, this.dependencies.clock),
292
+ logcatStarted: false,
293
+ runner: undefined
294
+ });
104
295
  }
105
296
  }
106
- catch (error) {
107
- setPrimary("APP_NOT_INSTALLED", errorMessage(error), "install");
108
- }
109
- if (primaryFailure === undefined) {
297
+ for (const runtime of runtimes) {
298
+ if (primaryFailure !== undefined) {
299
+ break;
300
+ }
301
+ const deviceSession = runtime.session;
302
+ const provider = await deviceSession.openUiSnapshots({
303
+ timeoutMs: input.config.ui?.snapshotTimeoutMs
304
+ ?? input.config.idle.timeoutMs,
305
+ backend: input.config.ui?.backend ?? "auto",
306
+ cacheEnabled: input.config.ui?.cacheEnabled ?? true,
307
+ ...(input.signal === undefined ? {} : { signal: input.signal })
308
+ });
309
+ runtime.provider = provider;
310
+ let installFailed = false;
311
+ try {
312
+ const installed = await deviceSession.isInstalled({
313
+ packageName: input.config.run.packageName,
314
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
315
+ timeoutMs: input.config.idle.timeoutMs
316
+ });
317
+ if (!installed) {
318
+ setPrimary("APP_NOT_INSTALLED", `Package ${input.config.run.packageName} is not installed on ${runtime.deviceSerial}`, "install");
319
+ installFailed = true;
320
+ }
321
+ }
322
+ catch (error) {
323
+ setPrimary("APP_NOT_INSTALLED", errorMessage(error), "install");
324
+ installFailed = true;
325
+ }
326
+ if (installFailed) {
327
+ break;
328
+ }
110
329
  try {
111
- await logcat.start({
112
- deviceSerial: input.deviceSerial,
330
+ await runtime.logcat.start({
331
+ deviceSerial: runtime.deviceSerial,
113
332
  ...(input.signal === undefined ? {} : { signal: input.signal })
114
333
  });
115
- logcatStarted = true;
334
+ runtime.logcatStarted = true;
116
335
  }
117
336
  catch (error) {
118
337
  layers.collection = "failed";
119
- setPrimary("COLLECTION_FAILED", errorMessage(error), "collection");
338
+ setPrimary(failureCodeFromUnknown(error) ?? "COLLECTION_FAILED", errorMessage(error), "collection");
120
339
  }
121
- if (logcatStarted) {
122
- const identity = {
340
+ if (!runtime.logcatStarted) {
341
+ break;
342
+ }
343
+ try {
344
+ const launched = await coldLaunchApp(runtime.views.adb, this.dependencies.clock, {
123
345
  packageName: input.config.run.packageName,
124
- deviceSerial: input.deviceSerial,
125
- ...(input.signal === undefined ? {} : { signal: input.signal }),
126
- timeoutMs: input.config.idle.timeoutMs
127
- };
128
- const stopped = await this.dependencies.adb.forceStop(identity);
129
- const launched = commandFailed(stopped)
130
- ? undefined
131
- : await this.dependencies.adb.launchActivity({
132
- packageName: input.config.run.packageName,
133
- activity: launchActivity,
134
- deviceSerial: input.deviceSerial,
135
- ...(input.signal === undefined ? {} : { signal: input.signal }),
136
- timeoutMs: input.config.idle.timeoutMs
137
- });
138
- const launchError = launched === undefined
139
- ? commandMessage(stopped, "App reset failed")
140
- : launchFailure(launched);
141
- if (launchError !== undefined) {
142
- layers.run = "failed";
143
- setPrimary("APP_LAUNCH_FAILED", launchError, "run");
346
+ activity: launchActivity,
347
+ deviceSerial: runtime.deviceSerial,
348
+ pollIntervalMs: input.config.idle.pollIntervalMs,
349
+ timeoutMs: input.config.idle.timeoutMs,
350
+ signal: input.signal
351
+ });
352
+ if (launched.status === "cancelled") {
353
+ setPrimary("INTERNAL_ERROR", "Verification was cancelled", "readiness");
354
+ break;
144
355
  }
145
- else {
146
- try {
147
- const launchReadinessStartedAt = this.dependencies.clock.now();
148
- const processReadiness = await new ProcessWaiter(this.dependencies.adb, this.dependencies.clock).wait({
149
- packageName: input.config.run.packageName,
150
- deviceSerial: input.deviceSerial,
151
- pollIntervalMs: input.config.idle.pollIntervalMs,
152
- timeoutMs: input.config.idle.timeoutMs,
153
- ...(input.signal === undefined ? {} : { signal: input.signal })
154
- });
155
- if (processReadiness.status === "timeout") {
156
- setPrimary("APP_LAUNCH_FAILED", "App process was not found after launch", "readiness");
157
- }
158
- else if (processReadiness.status === "cancelled") {
159
- setPrimary("INTERNAL_ERROR", "Verification was cancelled", "readiness");
160
- }
161
- else {
162
- logcat.scopeToPids(processReadiness.pids);
163
- const firstStep = input.journey.steps[0];
164
- if (firstStep === undefined) {
165
- throw new Error("Journey requires at least one step");
166
- }
167
- const remainingReadinessMs = input.config.idle.timeoutMs
168
- - (this.dependencies.clock.now()
169
- - launchReadinessStartedAt);
170
- if (remainingReadinessMs <= 0) {
171
- setPrimary("APP_LAUNCH_FAILED", `Expected startup Activity ${firstStep.activity.before}, found none before timeout`, "readiness");
172
- }
173
- else {
174
- const readiness = await new ActivityWaiter(this.dependencies.adb, this.dependencies.clock).wait({
175
- packageName: input.config.run.packageName,
176
- deviceSerial: input.deviceSerial,
177
- expectedActivity: firstStep.activity.before,
178
- pollIntervalMs: input.config.idle.pollIntervalMs,
179
- timeoutMs: remainingReadinessMs,
180
- ...(input.signal === undefined ? {} : { signal: input.signal })
181
- });
182
- if (readiness.status === "processMissing") {
183
- setPrimary("APP_LAUNCH_FAILED", "App process exited before reaching the first Journey Activity", "readiness");
184
- }
185
- else if (readiness.status === "timeout") {
186
- setPrimary("APP_LAUNCH_FAILED", `Expected startup Activity ${firstStep.activity.before}, found ${readiness.actual ?? "none"} before timeout`, "readiness");
187
- }
188
- else if (readiness.status === "cancelled") {
189
- setPrimary("INTERNAL_ERROR", "Verification was cancelled", "readiness");
190
- }
191
- else {
192
- await this.dependencies.androidCli.layout({
193
- deviceSerial: input.deviceSerial,
194
- ...(input.signal === undefined ? {} : { signal: input.signal }),
195
- timeoutMs: input.config.idle.timeoutMs
196
- });
197
- layers.run = "passed";
198
- layers.structural = "passed";
199
- layers.activityCheckpoint = "passed";
200
- layers.explicitExpect = "passed";
201
- }
202
- }
203
- }
356
+ if (launched.status === "failed") {
357
+ if (launched.stage === "process") {
358
+ setPrimary("APP_LAUNCH_FAILED", launched.message, "readiness");
204
359
  }
205
- catch (error) {
206
- setPrimary("APP_LAUNCH_FAILED", errorMessage(error), "readiness");
360
+ else {
361
+ layers.run = "failed";
362
+ setPrimary("APP_LAUNCH_FAILED", launched.message, "run");
207
363
  }
364
+ break;
365
+ }
366
+ runtime.logcat.scopeToPids(launched.pids);
367
+ const soleRole = soleRoleForJourney(input.journey);
368
+ const firstOwnStep = input.journey.steps.find((step) => stepDeviceRole(step, soleRole) === runtime.role);
369
+ if (firstOwnStep === undefined) {
370
+ throw new Error("Journey requires at least one step");
371
+ }
372
+ // The readiness budget starts once the launch command returned.
373
+ const remainingReadinessMs = input.config.idle.timeoutMs
374
+ - launched.processWaitMs;
375
+ if (remainingReadinessMs <= 0) {
376
+ setPrimary("APP_LAUNCH_FAILED", `Expected startup Activity ${firstOwnStep.activity.before}, found none before timeout`, "readiness");
377
+ break;
208
378
  }
379
+ const readiness = await new ActivityWaiter(runtime.views.adb, this.dependencies.clock).wait({
380
+ packageName: input.config.run.packageName,
381
+ deviceSerial: runtime.deviceSerial,
382
+ expectedActivity: firstOwnStep.activity.before,
383
+ pollIntervalMs: input.config.idle.pollIntervalMs,
384
+ timeoutMs: remainingReadinessMs,
385
+ ...(input.signal === undefined ? {} : { signal: input.signal })
386
+ });
387
+ if (readiness.status === "processMissing") {
388
+ setPrimary("APP_LAUNCH_FAILED", "App process exited before reaching the first Journey Activity", "readiness");
389
+ break;
390
+ }
391
+ if (readiness.status === "timeout") {
392
+ setPrimary("APP_LAUNCH_FAILED", `Expected startup Activity ${firstOwnStep.activity.before}, found ${readiness.actual ?? "none"} before timeout`, "readiness");
393
+ break;
394
+ }
395
+ if (readiness.status === "cancelled") {
396
+ setPrimary("INTERNAL_ERROR", "Verification was cancelled", "readiness");
397
+ break;
398
+ }
399
+ const readySnapshot = await provider.capture({
400
+ reason: "locate",
401
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
402
+ timeoutMs: input.config.idle.timeoutMs
403
+ });
404
+ runtime.readySnapshot = readySnapshot;
405
+ runtime.runner = createStepRunner({
406
+ adb: runtime.views.adb,
407
+ screenshots: runtime.views.screenshots,
408
+ annotatedScreens: runtime.views.annotatedScreens,
409
+ uiStability: uiStabilityProbe(provider, runtime.views.uiStability),
410
+ uiSnapshotProvider: provider,
411
+ clock: this.dependencies.clock,
412
+ logcat: runtime.logcat,
413
+ runStartedAt: logcatRunStartedAt,
414
+ markers,
415
+ bindings,
416
+ artifacts: session,
417
+ packageName: input.config.run.packageName,
418
+ deviceSerial: runtime.deviceSerial,
419
+ deviceRole: runtime.role,
420
+ idle: input.config.idle,
421
+ ...(this.dependencies.anchorResolverFor === undefined
422
+ ? {}
423
+ : { anchorResolver: this.dependencies.anchorResolverFor(input.projectRoot) }),
424
+ ...(input.requireFocusedInput === undefined
425
+ ? {}
426
+ : { requireFocusedInput: input.requireFocusedInput }),
427
+ ...(input.generatedReplayPolicy === undefined
428
+ ? {}
429
+ : { generatedReplayPolicy: input.generatedReplayPolicy }),
430
+ ...(input.manualReplay === undefined
431
+ ? {}
432
+ : { manualReplay: input.manualReplay })
433
+ });
434
+ }
435
+ catch (error) {
436
+ setPrimary(failureCodeFromUnknown(error) ?? "APP_LAUNCH_FAILED", errorMessage(error), "readiness");
437
+ break;
209
438
  }
210
439
  }
211
440
  if (primaryFailure === undefined) {
212
- const createStepRunner = this.dependencies.createStepRunner
213
- ?? ((options) => new StepRunner(options));
214
- const runner = createStepRunner({
215
- adb: this.dependencies.adb,
216
- androidCli: this.dependencies.androidCli,
217
- clock: this.dependencies.clock,
218
- logcat,
219
- artifacts: session,
220
- packageName: input.config.run.packageName,
221
- deviceSerial: input.deviceSerial,
222
- idle: input.config.idle,
223
- ...(input.requireFocusedInput === undefined
224
- ? {}
225
- : { requireFocusedInput: input.requireFocusedInput }),
226
- ...(input.generatedReplayPolicy === undefined
227
- ? {}
228
- : { generatedReplayPolicy: input.generatedReplayPolicy })
229
- });
441
+ layers.run = "passed";
442
+ layers.structural = "passed";
443
+ layers.activityCheckpoint = "passed";
444
+ layers.explicitExpect = "passed";
445
+ }
446
+ if (primaryFailure === undefined
447
+ && input.hooks?.beforeSteps !== undefined) {
448
+ for (const runtime of runtimes) {
449
+ if (runtime.provider === undefined
450
+ || runtime.readySnapshot === undefined) {
451
+ continue;
452
+ }
453
+ hookOutcomes.push(await runVerifyHook("beforeSteps", runtime, runtime.readySnapshot, input.hooks, this.dependencies.clock));
454
+ }
455
+ }
456
+ if (primaryFailure === undefined) {
457
+ const soleRole = soleRoleForJourney(input.journey);
458
+ const runtimeByRole = new Map(runtimes.map((runtime) => [runtime.role, runtime]));
459
+ const rolesStarted = new Set();
230
460
  for (const [index, step] of input.journey.steps.entries()) {
461
+ input.progress?.({
462
+ stage: "replaying",
463
+ stepIndex: index,
464
+ stepCount: input.journey.steps.length
465
+ });
466
+ const role = stepDeviceRole(step, soleRole);
467
+ const runner = runtimeByRole.get(role)?.runner;
468
+ if (runner === undefined) {
469
+ setPrimary("CONFIG_INVALID", `Step references an unmapped device role: ${role}`, "replay", index);
470
+ break;
471
+ }
231
472
  const result = await runner.run(step, index, input.signal);
473
+ const firstStepOnDevice = !rolesStarted.has(role);
474
+ rolesStarted.add(role);
232
475
  steps.push(result.report);
476
+ if (result.status === "manualRequired") {
477
+ setPrimary("MANUAL_STEP_REQUIRED", `Step ${String(index)} requires manual replay and the run is non-interactive`, "replay", index);
478
+ break;
479
+ }
233
480
  if (result.status === "cancelled") {
234
481
  setPrimary("INTERNAL_ERROR", "Verification was cancelled", "replay", index);
235
482
  break;
236
483
  }
237
484
  if (result.status === "failed") {
238
- setPrimary(result.failure.code, result.failure.message, result.failure.phase, result.failure.stepIndex);
485
+ setPrimary(result.failure.code, firstStepOnDevice
486
+ && INITIAL_STATE_FAILURE_CODES.has(result.failure.code)
487
+ ? `${result.failure.message} (${INITIAL_STATE_HINT})`
488
+ : result.failure.message, result.failure.phase, result.failure.stepIndex);
489
+ break;
490
+ }
491
+ const runtime = runtimeByRole.get(role);
492
+ if (runtime === undefined) {
493
+ throw new Error("Checkpoint device runtime is unavailable");
494
+ }
495
+ let checkpointFailed = false;
496
+ for (const checkpoint of input.journey.checkpoints ?? []) {
497
+ if (checkpoint.stepIndex !== index) {
498
+ continue;
499
+ }
500
+ if (!await evaluateCheckpoint(checkpoint, runtime, result.report.startedAtMs)) {
501
+ checkpointFailed = true;
502
+ break;
503
+ }
504
+ }
505
+ if (checkpointFailed) {
239
506
  break;
240
507
  }
241
508
  }
509
+ if (steps.length === input.journey.steps.length
510
+ && steps.every((step) => step.status === "passed")
511
+ && checkpoints.every((checkpoint) => checkpoint.status === "passed")) {
512
+ const lastStep = input.journey.steps.at(-1);
513
+ const lastRole = lastStep === undefined
514
+ ? soleRole
515
+ : stepDeviceRole(lastStep, soleRole);
516
+ const runtime = runtimeByRole.get(lastRole);
517
+ for (const checkpoint of input.journey.checkpoints ?? []) {
518
+ if (checkpoint.stepIndex !== undefined) {
519
+ continue;
520
+ }
521
+ if (runtime === undefined || !await evaluateCheckpoint(checkpoint, runtime, steps.at(-1)?.startedAtMs)) {
522
+ break;
523
+ }
524
+ }
525
+ }
526
+ if (input.hooks?.afterSteps !== undefined) {
527
+ for (const runtime of runtimes) {
528
+ if (runtime.provider === undefined) {
529
+ continue;
530
+ }
531
+ let snapshot;
532
+ try {
533
+ snapshot = await runtime.provider.capture({
534
+ reason: "evidence",
535
+ ...(input.signal === undefined
536
+ ? {}
537
+ : { signal: input.signal }),
538
+ timeoutMs: input.config.idle.timeoutMs
539
+ });
540
+ }
541
+ catch (error) {
542
+ hookOutcomes.push({
543
+ phase: "afterSteps",
544
+ deviceRole: runtime.role,
545
+ status: "unresolved",
546
+ message: `Post-journey snapshot failed: ${error instanceof Error ? error.message : String(error)}`
547
+ });
548
+ continue;
549
+ }
550
+ hookOutcomes.push(await runVerifyHook("afterSteps", runtime, snapshot, input.hooks, this.dependencies.clock));
551
+ }
552
+ }
242
553
  }
243
554
  }
244
555
  catch (error) {
556
+ const errorCode = failureCodeFromUnknown(error) ?? "INTERNAL_ERROR";
245
557
  if (primaryFailure === undefined) {
246
- setPrimary("INTERNAL_ERROR", errorMessage(error), "runtime");
558
+ setPrimary(errorCode, errorMessage(error), "runtime");
247
559
  }
248
560
  else {
249
561
  secondaryErrors.push({
250
- code: "INTERNAL_ERROR",
562
+ code: errorCode,
251
563
  message: errorMessage(error),
252
564
  phase: "runtime"
253
565
  });
254
566
  }
255
567
  }
256
- const screenshotPath = "screenshot.png";
257
- let screenshotCollected = false;
258
- try {
259
- const screenshot = await this.dependencies.androidCli.captureScreen({
260
- outputPath: session.path(screenshotPath),
261
- deviceSerial: input.deviceSerial,
262
- ...(input.signal === undefined ? {} : { signal: input.signal })
263
- });
264
- if (commandFailed(screenshot)) {
265
- collectionFailure(commandMessage(screenshot, "Screen capture failed"));
266
- }
267
- else {
268
- screenshotCollected = true;
568
+ const screenshotEntries = [];
569
+ const uiHierarchyEntries = [];
570
+ const logcatEntries = [];
571
+ input.progress?.({ stage: "collecting" });
572
+ if (coverage === undefined) {
573
+ for (const runtime of runtimes) {
574
+ if (runtime.provider !== undefined
575
+ && runtime.readySnapshot !== undefined) {
576
+ const uiHierarchyPath = `ui-hierarchy-${runtime.role}.json`;
577
+ try {
578
+ const snapshot = await runtime.provider.capture({
579
+ reason: "evidence",
580
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
581
+ timeoutMs: input.config.idle.timeoutMs
582
+ });
583
+ await session.writeJson(uiHierarchyPath, snapshot);
584
+ uiHierarchyEntries.push({
585
+ role: runtime.role,
586
+ path: uiHierarchyPath
587
+ });
588
+ }
589
+ catch (error) {
590
+ collectionFailure(errorMessage(error));
591
+ }
592
+ }
593
+ const screenshotPath = `screenshot-${runtime.role}.png`;
594
+ try {
595
+ const screenshot = await runtime.session.captureScreenshot({
596
+ outputPath: session.path(screenshotPath),
597
+ ...(input.signal === undefined ? {} : { signal: input.signal })
598
+ });
599
+ if (commandFailed(screenshot)) {
600
+ collectionFailure(commandMessage(screenshot, "Screen capture failed"));
601
+ }
602
+ else {
603
+ screenshotEntries.push({ role: runtime.role, path: screenshotPath });
604
+ }
605
+ }
606
+ catch (error) {
607
+ collectionFailure(errorMessage(error));
608
+ }
609
+ if (runtime.logcatStarted) {
610
+ const logcatPath = `logcat-${runtime.role}.txt`;
611
+ try {
612
+ const stopped = await runtime.logcat.stop();
613
+ if (logcatStopFailed(stopped)) {
614
+ collectionFailure(commandMessage(stopped, "Logcat stop failed"));
615
+ }
616
+ await session.writeText(logcatPath, runtime.logcat.rawLines().map((line) => line.raw).join("\n"));
617
+ logcatEntries.push({ role: runtime.role, path: logcatPath });
618
+ }
619
+ catch (error) {
620
+ collectionFailure(errorMessage(error));
621
+ }
622
+ }
269
623
  }
270
624
  }
271
- catch (error) {
272
- collectionFailure(errorMessage(error));
273
- }
274
- let logcatCollected = false;
275
- if (logcatStarted) {
625
+ for (const runtime of runtimes) {
276
626
  try {
277
- const stopped = await logcat.stop();
278
- if (logcatStopFailed(stopped)) {
279
- collectionFailure(commandMessage(stopped, "Logcat stop failed"));
280
- }
281
- await session.writeText("logcat.txt", logcat.lines().map((line) => line.raw).join("\n"));
282
- logcatCollected = true;
627
+ await runtime.session.close();
283
628
  }
284
629
  catch (error) {
285
- collectionFailure(errorMessage(error));
630
+ secondaryErrors.push({
631
+ code: "INTERNAL_ERROR",
632
+ message: errorMessage(error),
633
+ phase: "runtimeSessionClose"
634
+ });
286
635
  }
287
636
  }
288
637
  if (collectionErrors.length > 0) {
@@ -295,17 +644,42 @@ export class VerifyRuntime {
295
644
  const failure = primaryFailure;
296
645
  const status = failure === undefined
297
646
  ? "passed"
298
- : [
299
- "CONFIG_INVALID",
300
- "ENVIRONMENT_MISSING_TOOL",
301
- "DEVICE_UNAVAILABLE",
302
- "APP_NOT_INSTALLED",
303
- "INTERNAL_ERROR"
304
- ].includes(failure.code)
305
- ? "error"
306
- : "failed";
647
+ : failure.code === "MANUAL_STEP_REQUIRED"
648
+ ? "manualRequired"
649
+ : [
650
+ "CONFIG_INVALID",
651
+ "ENVIRONMENT_MISSING_TOOL",
652
+ "DEVICE_UNAVAILABLE",
653
+ "DEVICE_ROLE_UNMAPPED",
654
+ "APP_NOT_INSTALLED",
655
+ "UI_BACKEND_UNAVAILABLE",
656
+ "UI_SNAPSHOT_FAILED",
657
+ "INTERNAL_ERROR"
658
+ ].includes(failure.code)
659
+ ? "error"
660
+ : "failed";
307
661
  const report = {
308
- schemaVersion: 2,
662
+ schemaVersion: 4,
663
+ ...(runtimes.some((runtime) => runtime.logcatStarted
664
+ && (runtime.logcat.metadata().droppedLines ?? 0) > 0)
665
+ ? {
666
+ logcatEvidence: runtimes.flatMap((runtime) => {
667
+ if (!runtime.logcatStarted) {
668
+ return [];
669
+ }
670
+ const metadata = runtime.logcat.metadata();
671
+ return metadata.droppedLines === undefined ? [] : [{
672
+ role: runtime.role,
673
+ droppedLines: metadata.droppedLines,
674
+ droppedBytes: metadata.droppedBytes ?? 0,
675
+ ...(metadata.lastDroppedAtMs === undefined ? {} : {
676
+ lastDroppedAtMs: metadata.lastDroppedAtMs
677
+ }),
678
+ status: "incomplete"
679
+ }];
680
+ })
681
+ }
682
+ : {}),
309
683
  runId,
310
684
  status,
311
685
  startedAt: startedAt.toISOString(),
@@ -321,17 +695,44 @@ export class VerifyRuntime {
321
695
  sha256: hashJourney(input.journey)
322
696
  },
323
697
  environment: {
324
- deviceSerial: input.deviceSerial,
698
+ devices: coverage === undefined
699
+ ? runtimes.map((runtime) => ({
700
+ role: runtime.role,
701
+ deviceSerial: runtime.deviceSerial,
702
+ ...(runtime.provider === undefined
703
+ ? {}
704
+ : {
705
+ uiBackend: runtime.provider.descriptor,
706
+ ...(runtime.provider.cacheTelemetry === undefined
707
+ ? {}
708
+ : { uiCache: runtime.provider.cacheTelemetry() })
709
+ })
710
+ }))
711
+ : uniqueAssignmentsByRole(input.devices).map((assignment) => ({
712
+ role: assignment.role,
713
+ deviceSerial: assignment.deviceSerial
714
+ })),
325
715
  tools: input.toolVersions
326
716
  },
327
717
  layers,
328
718
  steps,
719
+ ...(input.journey.checkpoints === undefined ? {} : { checkpoints }),
720
+ screens: Array.from(new Map([
721
+ ...hookOutcomes.flatMap((outcome) => (outcome.status === "passed" && outcome.screen !== undefined
722
+ ? [[outcome.screen, {
723
+ screen: outcome.screen,
724
+ status: "matched"
725
+ }]]
726
+ : [])),
727
+ ...checkpointScreens.map((screen) => [screen.screen, screen])
728
+ ]).values()),
329
729
  artifacts: {
330
730
  directory: session.finalDirectory,
331
731
  report: "report.json",
332
732
  summary: "summary.txt",
333
- ...(screenshotCollected ? { screenshot: screenshotPath } : {}),
334
- ...(logcatCollected ? { logcat: "logcat.txt" } : {}),
733
+ screenshots: screenshotEntries,
734
+ uiHierarchies: uiHierarchyEntries,
735
+ logcats: logcatEntries,
335
736
  stepLogs: steps.flatMap((step) => (step.logcatPath === undefined ? [] : [step.logcatPath]))
336
737
  },
337
738
  ...(failure === undefined ? {} : { primaryFailure: failure }),
@@ -344,7 +745,8 @@ export class VerifyRuntime {
344
745
  exitCode: failure === undefined ? 0 : exitCodeForFailure(failure.code),
345
746
  report,
346
747
  reportPath: published.reportPath,
347
- summaryPath: published.summaryPath
748
+ summaryPath: published.summaryPath,
749
+ ...(hookOutcomes.length === 0 ? {} : { hookOutcomes })
348
750
  };
349
751
  }
350
752
  }