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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (447) hide show
  1. package/README.md +49 -149
  2. package/README.zh-CN.md +47 -147
  3. package/assets/skills/taphound-case-suite/SKILL.md +363 -0
  4. package/assets/skills/taphound-case-suite/schemas/base-flow-record.schema.json +22 -0
  5. package/assets/skills/taphound-case-suite/schemas/case-catalog.schema.json +26 -0
  6. package/assets/skills/taphound-case-suite/schemas/case-ledger.schema.json +158 -0
  7. package/assets/skills/taphound-case-suite/schemas/suite-input.schema.json +41 -0
  8. package/assets/skills/taphound-case-suite/schemas/transition.schema.json +61 -0
  9. package/assets/skills/taphound-case-suite/scripts/ledger.mjs +1207 -0
  10. package/assets/skills/taphound-case-suite/templates/base-flow-record.example.json +20 -0
  11. package/assets/skills/taphound-case-suite/templates/suite-input.example.json +27 -0
  12. package/assets/skills/taphound-case-suite/templates/transition.example.json +8 -0
  13. package/assets/skills/taphound-flash/SKILL.md +115 -0
  14. package/assets/skills/taphound-flash/scripts/flash.mjs +585 -0
  15. package/assets/skills/taphound-flash/templates/flash-plan.example.json +15 -0
  16. package/assets/skills/taphound-journey-brief-author/CONTEXT-GUIDE.md +471 -0
  17. package/assets/skills/taphound-journey-brief-author/SKILL.md +494 -0
  18. package/assets/skills/taphound-journey-brief-author/prompts/brief-author-role.md +96 -0
  19. package/assets/skills/taphound-journey-brief-author/prompts/brief-author-role.zh-CN.md +80 -0
  20. package/assets/skills/taphound-journey-brief-author/prompts/context-analyze-project.md +309 -0
  21. package/assets/skills/taphound-journey-brief-author/schemas/context-refresh-result.json +123 -0
  22. package/assets/skills/taphound-journey-brief-author/schemas/project-context-module.json +192 -0
  23. package/assets/skills/taphound-journey-brief-author/schemas/project-context.json +152 -0
  24. package/assets/skills/taphound-journey-brief-author/templates/project-context-module.example.json +58 -0
  25. package/assets/skills/taphound-journey-brief-author/templates/project-context.example.json +51 -0
  26. package/assets/skills/taphound-journey-brief-author/templates/taphound-journey-brief.template.md +63 -0
  27. package/assets/skills/taphound-journey-generator/SKILL.md +500 -0
  28. package/assets/skills/{taphound-ai-journey → taphound-journey-generator}/prompts/check-completion.md +3 -0
  29. package/assets/skills/taphound-journey-generator/prompts/consume-journey-brief.md +52 -0
  30. package/assets/skills/taphound-journey-generator/prompts/generate-step.md +211 -0
  31. package/assets/skills/taphound-journey-generator/prompts/select-flow.md +91 -0
  32. package/assets/skills/taphound-journey-generator/references/bridge.md +35 -0
  33. package/assets/skills/taphound-journey-generator/references/mid-session.md +44 -0
  34. package/assets/skills/taphound-journey-generator/schemas/external-flow.json +37 -0
  35. package/assets/skills/taphound-journey-generator/schemas/flow.json +28 -0
  36. package/assets/skills/taphound-journey-generator/schemas/journey-source.json +25 -0
  37. package/assets/skills/{taphound-ai-journey → taphound-journey-generator}/schemas/observe-output.json +13 -4
  38. package/assets/skills/{taphound-ai-journey → taphound-journey-generator}/schemas/proposed-step-envelope.json +127 -5
  39. package/assets/skills/taphound-journey-generator/scripts/envelope.mjs +561 -0
  40. package/assets/skills/taphound-journey-generator/templates/external-flow.example.json +22 -0
  41. package/assets/skills/taphound-journey-generator/templates/flow.example.json +22 -0
  42. package/assets/skills/taphound-journey-generator/templates/journey-source.example.json +17 -0
  43. package/assets/skills/taphound-journey-generator/templates/taphound-journey-brief.example.md +70 -0
  44. package/assets/skills/taphound-verify-change/SKILL.md +91 -0
  45. package/assets/skills/taphound-verify-change/references/accept.md +38 -0
  46. package/assets/skills/taphound-verify-change/references/preserve.md +349 -0
  47. package/assets/skills/taphound-verify-change/schemas/workflow-manifest.schema.json +295 -0
  48. package/assets/skills/taphound-verify-change/scripts/handoff.mjs +482 -0
  49. package/assets/skills/taphound-verify-change/scripts/ui-refactor.mjs +505 -0
  50. package/dist/adapters/adb/adb-adapter.d.ts +16 -2
  51. package/dist/adapters/adb/adb-adapter.js +199 -0
  52. package/dist/adapters/adb/system-uiautomator-snapshot-provider.d.ts +13 -0
  53. package/dist/adapters/adb/system-uiautomator-snapshot-provider.js +122 -0
  54. package/dist/adapters/adb/ui-automator-parser.d.ts +3 -0
  55. package/dist/adapters/adb/ui-automator-parser.js +160 -0
  56. package/dist/adapters/adb/window-topology-parser.d.ts +2 -0
  57. package/dist/adapters/adb/window-topology-parser.js +101 -0
  58. package/dist/adapters/android-cli/android-cli-adapter.d.ts +16 -8
  59. package/dist/adapters/android-cli/android-cli-adapter.js +137 -15
  60. package/dist/adapters/android-cli/android-cli-snapshot-provider.d.ts +9 -0
  61. package/dist/adapters/android-cli/android-cli-snapshot-provider.js +99 -0
  62. package/dist/adapters/android-cli/layout-parser.js +12 -8
  63. package/dist/adapters/appium/appium-doctor.d.ts +7 -0
  64. package/dist/adapters/appium/appium-doctor.js +128 -0
  65. package/dist/adapters/appium/appium-ui-snapshot-provider.d.ts +34 -0
  66. package/dist/adapters/appium/appium-ui-snapshot-provider.js +240 -0
  67. package/dist/adapters/camera/camera-probe-adapter.d.ts +18 -0
  68. package/dist/adapters/camera/camera-probe-adapter.js +286 -0
  69. package/dist/adapters/filesystem/artifact-store.js +40 -1
  70. package/dist/adapters/filesystem/context-document-writer.d.ts +15 -0
  71. package/dist/adapters/filesystem/context-document-writer.js +112 -0
  72. package/dist/adapters/filesystem/external-flow-registry.d.ts +17 -0
  73. package/dist/adapters/filesystem/external-flow-registry.js +325 -0
  74. package/dist/adapters/filesystem/generation-session-store.d.ts +17 -11
  75. package/dist/adapters/filesystem/generation-session-store.js +281 -855
  76. package/dist/adapters/filesystem/generation-store/session-lock.d.ts +29 -0
  77. package/dist/adapters/filesystem/generation-store/session-lock.js +202 -0
  78. package/dist/adapters/filesystem/generation-store/session-transitions.d.ts +24 -0
  79. package/dist/adapters/filesystem/generation-store/session-transitions.js +340 -0
  80. package/dist/adapters/filesystem/generation-store/store-files.d.ts +50 -0
  81. package/dist/adapters/filesystem/generation-store/store-files.js +363 -0
  82. package/dist/adapters/filesystem/journey-composition-store.d.ts +23 -0
  83. package/dist/adapters/filesystem/journey-composition-store.js +151 -0
  84. package/dist/adapters/filesystem/knowledge-registry.d.ts +12 -0
  85. package/dist/adapters/filesystem/knowledge-registry.js +223 -0
  86. package/dist/adapters/filesystem/project-bound-file.js +3 -5
  87. package/dist/adapters/filesystem/project-file-inspector.d.ts +2 -0
  88. package/dist/adapters/filesystem/project-file-inspector.js +57 -28
  89. package/dist/adapters/filesystem/project-identity-inspector.d.ts +7 -0
  90. package/dist/adapters/filesystem/project-identity-inspector.js +215 -0
  91. package/dist/adapters/filesystem/project-inventory-inspector.d.ts +8 -0
  92. package/dist/adapters/filesystem/project-inventory-inspector.js +93 -0
  93. package/dist/adapters/filesystem/project-module-discoverer.d.ts +6 -0
  94. package/dist/adapters/filesystem/project-module-discoverer.js +158 -0
  95. package/dist/adapters/filesystem/skill-installer.d.ts +7 -2
  96. package/dist/adapters/filesystem/skill-installer.js +40 -11
  97. package/dist/adapters/filesystem/workspace-layout.d.ts +7 -0
  98. package/dist/adapters/filesystem/workspace-layout.js +57 -0
  99. package/dist/adapters/git/node-git-diff.d.ts +17 -0
  100. package/dist/adapters/git/node-git-diff.js +114 -0
  101. package/dist/adapters/process/node-detached-process-launcher.d.ts +6 -0
  102. package/dist/adapters/process/node-detached-process-launcher.js +96 -0
  103. package/dist/adapters/process/node-process-runner.js +11 -2
  104. package/dist/adapters/prompt/inquirer-align-prompt.d.ts +27 -0
  105. package/dist/adapters/prompt/inquirer-align-prompt.js +65 -0
  106. package/dist/adapters/prompt/inquirer-generation-prompt.js +3 -0
  107. package/dist/adapters/prompt/inquirer-recorder-prompt.d.ts +9 -1
  108. package/dist/adapters/prompt/inquirer-recorder-prompt.js +63 -0
  109. package/dist/adapters/runtime/adb-runtime-backend.d.ts +67 -0
  110. package/dist/adapters/runtime/adb-runtime-backend.js +206 -0
  111. package/dist/adapters/runtime/fake-runtime-backend.d.ts +87 -0
  112. package/dist/adapters/runtime/fake-runtime-backend.js +240 -0
  113. package/dist/adapters/runtime/mobile-mcp/mcp-tool-client.d.ts +45 -0
  114. package/dist/adapters/runtime/mobile-mcp/mcp-tool-client.js +153 -0
  115. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-errors.d.ts +23 -0
  116. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-errors.js +39 -0
  117. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-responses.d.ts +20 -0
  118. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-responses.js +180 -0
  119. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-runtime-backend.d.ts +78 -0
  120. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-runtime-backend.js +379 -0
  121. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-tools.d.ts +46 -0
  122. package/dist/adapters/runtime/session-adb-view.d.ts +53 -0
  123. package/dist/adapters/runtime/session-adb-view.js +193 -0
  124. package/dist/adapters/runtime/session-backed-ports.d.ts +21 -0
  125. package/dist/adapters/runtime/session-backed-ports.js +43 -0
  126. package/dist/adapters/runtime/shared-session-runtime-backend.d.ts +12 -0
  127. package/dist/adapters/runtime/shared-session-runtime-backend.js +64 -0
  128. package/dist/adapters/ui/auto-ui-snapshot-provider.d.ts +8 -0
  129. package/dist/adapters/ui/auto-ui-snapshot-provider.js +48 -0
  130. package/dist/adapters/ui/device-layout-temp.d.ts +6 -0
  131. package/dist/adapters/ui/device-layout-temp.js +18 -0
  132. package/dist/adapters/ui/device-ui-environment.d.ts +13 -0
  133. package/dist/adapters/ui/device-ui-environment.js +81 -0
  134. package/dist/adapters/ui/layout-normalization.d.ts +9 -0
  135. package/dist/adapters/ui/layout-normalization.js +44 -0
  136. package/dist/adapters/ui/ui-snapshot-error.d.ts +11 -0
  137. package/dist/adapters/ui/ui-snapshot-error.js +12 -0
  138. package/dist/adapters/ui/ui-snapshot-support.d.ts +15 -0
  139. package/dist/adapters/ui/ui-snapshot-support.js +12 -0
  140. package/dist/application/align/align-service.d.ts +42 -0
  141. package/dist/application/align/align-service.js +106 -0
  142. package/dist/application/assertion/expectation-evaluator.d.ts +18 -5
  143. package/dist/application/assertion/expectation-evaluator.js +156 -18
  144. package/dist/application/assertion/guarded-expectation.d.ts +45 -0
  145. package/dist/application/assertion/guarded-expectation.js +74 -0
  146. package/dist/application/checkpoint/baseline-capturer.d.ts +26 -0
  147. package/dist/application/checkpoint/baseline-capturer.js +163 -0
  148. package/dist/application/checkpoint/baseline-error.d.ts +5 -0
  149. package/dist/application/checkpoint/baseline-error.js +8 -0
  150. package/dist/application/checkpoint/baseline-service.d.ts +31 -0
  151. package/dist/application/checkpoint/baseline-service.js +147 -0
  152. package/dist/application/checkpoint/checkpoint-evaluator.d.ts +35 -0
  153. package/dist/application/checkpoint/checkpoint-evaluator.js +415 -0
  154. package/dist/application/checkpoint/regression-comparator.d.ts +18 -0
  155. package/dist/application/checkpoint/regression-comparator.js +192 -0
  156. package/dist/application/collector/logcat-collector.d.ts +22 -1
  157. package/dist/application/collector/logcat-collector.js +103 -15
  158. package/dist/application/collector/logcat-event.d.ts +8 -0
  159. package/dist/application/collector/logcat-event.js +79 -0
  160. package/dist/application/collector/logcat-stop.d.ts +2 -0
  161. package/dist/application/collector/logcat-stop.js +11 -0
  162. package/dist/application/context/context-generator.d.ts +45 -0
  163. package/dist/application/context/context-generator.js +228 -0
  164. package/dist/application/context/context-loader.d.ts +40 -0
  165. package/dist/application/context/context-loader.js +223 -0
  166. package/dist/application/context/context-refresher.d.ts +59 -0
  167. package/dist/application/context/context-refresher.js +349 -0
  168. package/dist/application/context/context-rehasher.d.ts +36 -0
  169. package/dist/application/context/context-rehasher.js +110 -0
  170. package/dist/application/context/context-validator.d.ts +23 -1
  171. package/dist/application/context/context-validator.js +116 -9
  172. package/dist/application/context/evidence-hash.d.ts +1 -0
  173. package/dist/application/context/evidence-hash.js +125 -0
  174. package/dist/application/context/evidence-match.d.ts +9 -0
  175. package/dist/application/context/evidence-match.js +8 -0
  176. package/dist/application/context/shard-identity.d.ts +2 -0
  177. package/dist/application/context/shard-identity.js +7 -0
  178. package/dist/application/contract/contract-loader.d.ts +25 -0
  179. package/dist/application/contract/contract-loader.js +68 -0
  180. package/dist/application/contract/contract-review.d.ts +25 -0
  181. package/dist/application/contract/contract-review.js +51 -0
  182. package/dist/application/contract/contract-verifier.d.ts +53 -0
  183. package/dist/application/contract/contract-verifier.js +645 -0
  184. package/dist/application/devices/resolve-device-assignments.d.ts +21 -0
  185. package/dist/application/devices/resolve-device-assignments.js +60 -0
  186. package/dist/application/diagnosis/failure-classifier.d.ts +23 -0
  187. package/dist/application/diagnosis/failure-classifier.js +179 -0
  188. package/dist/application/doctor/doctor-service.d.ts +36 -3
  189. package/dist/application/doctor/doctor-service.js +255 -25
  190. package/dist/application/generation/generation-app-preparer.d.ts +20 -0
  191. package/dist/application/generation/generation-app-preparer.js +28 -0
  192. package/dist/application/generation/generation-config-service.d.ts +21 -0
  193. package/dist/application/generation/generation-config-service.js +53 -0
  194. package/dist/application/generation/generation-confirmation-service.d.ts +14 -3
  195. package/dist/application/generation/generation-confirmation-service.js +109 -24
  196. package/dist/application/generation/generation-context-snapshot.d.ts +6 -0
  197. package/dist/application/generation/generation-context-snapshot.js +29 -0
  198. package/dist/application/generation/generation-finalizer.d.ts +15 -9
  199. package/dist/application/generation/generation-finalizer.js +167 -70
  200. package/dist/application/generation/generation-publisher.js +12 -8
  201. package/dist/application/generation/generation-recovery-service.d.ts +35 -0
  202. package/dist/application/generation/generation-recovery-service.js +144 -0
  203. package/dist/application/generation/generation-reopen-service.d.ts +15 -0
  204. package/dist/application/generation/generation-reopen-service.js +37 -0
  205. package/dist/application/generation/generation-replace-service.d.ts +32 -0
  206. package/dist/application/generation/generation-replace-service.js +116 -0
  207. package/dist/application/generation/generation-starter.d.ts +44 -4
  208. package/dist/application/generation/generation-starter.js +175 -8
  209. package/dist/application/generation/generation-step-executor.d.ts +67 -5
  210. package/dist/application/generation/generation-step-executor.js +471 -108
  211. package/dist/application/generation/proposed-step-validator.js +96 -30
  212. package/dist/application/generation/replay-policy-loader.d.ts +16 -0
  213. package/dist/application/generation/replay-policy-loader.js +29 -0
  214. package/dist/application/generation/risk-evaluator.d.ts +4 -1
  215. package/dist/application/generation/risk-evaluator.js +118 -3
  216. package/dist/application/generation/runtime-observer.d.ts +39 -7
  217. package/dist/application/generation/runtime-observer.js +193 -24
  218. package/dist/application/impact/impact-resolver.d.ts +32 -0
  219. package/dist/application/impact/impact-resolver.js +160 -0
  220. package/dist/application/init/init-service.js +26 -13
  221. package/dist/application/interaction/action-executor.d.ts +3 -2
  222. package/dist/application/interaction/action-executor.js +11 -1
  223. package/dist/application/interaction/action-target.d.ts +17 -0
  224. package/dist/application/interaction/action-target.js +40 -0
  225. package/dist/application/interaction/external-step-runner.d.ts +153 -0
  226. package/dist/application/interaction/external-step-runner.js +332 -0
  227. package/dist/application/interaction/fallback-resolver.d.ts +6 -3
  228. package/dist/application/interaction/fallback-resolver.js +7 -5
  229. package/dist/application/interaction/scroll-to-executor.d.ts +17 -3
  230. package/dist/application/interaction/scroll-to-executor.js +118 -36
  231. package/dist/application/journey/external-flow-resolver.d.ts +25 -0
  232. package/dist/application/journey/external-flow-resolver.js +22 -0
  233. package/dist/application/journey/journey-check-service.d.ts +54 -0
  234. package/dist/application/journey/journey-check-service.js +207 -0
  235. package/dist/application/journey/journey-promoter.d.ts +26 -0
  236. package/dist/application/journey/journey-promoter.js +123 -0
  237. package/dist/application/journey/journey-resolver.d.ts +42 -0
  238. package/dist/application/journey/journey-resolver.js +239 -0
  239. package/dist/application/journey/journey-retirer.d.ts +25 -0
  240. package/dist/application/journey/journey-retirer.js +87 -0
  241. package/dist/application/knowledge/anchor-resolver.d.ts +36 -0
  242. package/dist/application/knowledge/anchor-resolver.js +110 -0
  243. package/dist/application/knowledge/knowledge-loader.d.ts +16 -0
  244. package/dist/application/knowledge/knowledge-loader.js +36 -0
  245. package/dist/application/locator/layout-failure-summary.d.ts +2 -0
  246. package/dist/application/locator/layout-failure-summary.js +67 -0
  247. package/dist/application/locator/layout-traversal.d.ts +11 -0
  248. package/dist/application/locator/layout-traversal.js +18 -0
  249. package/dist/application/locator/locator-resolver.d.ts +13 -1
  250. package/dist/application/locator/locator-resolver.js +182 -18
  251. package/dist/application/observe/observe-service.d.ts +20 -0
  252. package/dist/application/observe/observe-service.js +91 -0
  253. package/dist/application/project/project-describer.d.ts +14 -6
  254. package/dist/application/project/project-describer.js +38 -5
  255. package/dist/application/recognition/screen-detector.d.ts +28 -0
  256. package/dist/application/recognition/screen-detector.js +111 -0
  257. package/dist/application/recorder/locator-selector.js +151 -17
  258. package/dist/application/recorder/recorder-service.d.ts +16 -4
  259. package/dist/application/recorder/recorder-service.js +420 -128
  260. package/dist/application/report/report-writer.js +1 -1
  261. package/dist/application/runtime/cold-launch.d.ts +28 -0
  262. package/dist/application/runtime/cold-launch.js +59 -0
  263. package/dist/application/runtime/step-runner.d.ts +49 -4
  264. package/dist/application/runtime/step-runner.js +721 -130
  265. package/dist/application/runtime/verify-runtime.d.ts +57 -7
  266. package/dist/application/runtime/verify-runtime.js +554 -174
  267. package/dist/application/ui/cached-ui-snapshot-provider.d.ts +45 -0
  268. package/dist/application/ui/cached-ui-snapshot-provider.js +111 -0
  269. package/dist/application/ui/ui-snapshot-lifecycle.d.ts +2 -0
  270. package/dist/application/ui/ui-snapshot-lifecycle.js +8 -0
  271. package/dist/application/ui/ui-stability-probe.d.ts +3 -0
  272. package/dist/application/ui/ui-stability-probe.js +8 -0
  273. package/dist/application/wait/idle-advice.d.ts +6 -0
  274. package/dist/application/wait/idle-advice.js +27 -0
  275. package/dist/application/wait/idle-profiles.d.ts +15 -0
  276. package/dist/application/wait/idle-profiles.js +75 -0
  277. package/dist/application/wait/idle-waiter.d.ts +28 -4
  278. package/dist/application/wait/idle-waiter.js +186 -15
  279. package/dist/cli/commands/align.d.ts +3 -0
  280. package/dist/cli/commands/align.js +124 -0
  281. package/dist/cli/commands/baseline.d.ts +3 -0
  282. package/dist/cli/commands/baseline.js +130 -0
  283. package/dist/cli/commands/context.js +300 -27
  284. package/dist/cli/commands/contract.d.ts +3 -0
  285. package/dist/cli/commands/contract.js +120 -0
  286. package/dist/cli/commands/doctor.js +16 -9
  287. package/dist/cli/commands/failure.d.ts +3 -0
  288. package/dist/cli/commands/failure.js +63 -0
  289. package/dist/cli/commands/generation/lifecycle-commands.d.ts +5 -0
  290. package/dist/cli/commands/generation/lifecycle-commands.js +211 -0
  291. package/dist/cli/commands/generation/session-commands.d.ts +11 -0
  292. package/dist/cli/commands/generation/session-commands.js +472 -0
  293. package/dist/cli/commands/generation/shared.d.ts +1414 -0
  294. package/dist/cli/commands/generation/shared.js +291 -0
  295. package/dist/cli/commands/generation/step-commands.d.ts +8 -0
  296. package/dist/cli/commands/generation/step-commands.js +315 -0
  297. package/dist/cli/commands/generation.js +10 -507
  298. package/dist/cli/commands/impact.d.ts +3 -0
  299. package/dist/cli/commands/impact.js +87 -0
  300. package/dist/cli/commands/init.js +13 -6
  301. package/dist/cli/commands/journey.d.ts +3 -0
  302. package/dist/cli/commands/journey.js +382 -0
  303. package/dist/cli/commands/knowledge.d.ts +3 -0
  304. package/dist/cli/commands/knowledge.js +93 -0
  305. package/dist/cli/commands/observe.d.ts +3 -0
  306. package/dist/cli/commands/observe.js +127 -0
  307. package/dist/cli/commands/project.js +2 -1
  308. package/dist/cli/commands/record.js +18 -5
  309. package/dist/cli/commands/verify.js +273 -63
  310. package/dist/cli/dependencies.d.ts +125 -1
  311. package/dist/cli/dependencies.js +450 -51
  312. package/dist/cli/diff-verification.d.ts +41 -0
  313. package/dist/cli/diff-verification.js +192 -0
  314. package/dist/cli/main.js +49 -1
  315. package/dist/cli/output.d.ts +5 -3
  316. package/dist/cli/output.js +12 -0
  317. package/dist/cli/program.js +19 -1
  318. package/dist/cli/project-root.d.ts +1 -0
  319. package/dist/cli/project-root.js +6 -0
  320. package/dist/cli/runtime-selection.d.ts +22 -0
  321. package/dist/cli/runtime-selection.js +92 -0
  322. package/dist/cli/version.d.ts +1 -0
  323. package/dist/cli/version.js +14 -0
  324. package/dist/cli/workspace-guard.d.ts +2 -0
  325. package/dist/cli/workspace-guard.js +3 -0
  326. package/dist/domain/binding-reference.d.ts +4 -0
  327. package/dist/domain/binding-reference.js +23 -0
  328. package/dist/domain/checkpoint.d.ts +404 -0
  329. package/dist/domain/checkpoint.js +297 -0
  330. package/dist/domain/config.d.ts +107 -1
  331. package/dist/domain/config.js +52 -6
  332. package/dist/domain/contract.d.ts +413 -0
  333. package/dist/domain/contract.js +196 -0
  334. package/dist/domain/external-flow.d.ts +493 -0
  335. package/dist/domain/external-flow.js +15 -0
  336. package/dist/domain/failure-classification.d.ts +176 -0
  337. package/dist/domain/failure-classification.js +169 -0
  338. package/dist/domain/failure.d.ts +2 -1
  339. package/dist/domain/failure.js +123 -0
  340. package/dist/domain/generation.d.ts +1671 -132
  341. package/dist/domain/generation.js +202 -13
  342. package/dist/domain/geometry.d.ts +13 -0
  343. package/dist/domain/geometry.js +16 -0
  344. package/dist/domain/impact.d.ts +71 -0
  345. package/dist/domain/impact.js +76 -0
  346. package/dist/domain/init.d.ts +12 -4
  347. package/dist/domain/init.js +24 -18
  348. package/dist/domain/journey-composition.d.ts +2285 -0
  349. package/dist/domain/journey-composition.js +85 -0
  350. package/dist/domain/journey-lifecycle.d.ts +10 -0
  351. package/dist/domain/journey-lifecycle.js +9 -0
  352. package/dist/domain/journey.d.ts +2419 -212
  353. package/dist/domain/journey.js +384 -14
  354. package/dist/domain/knowledge.d.ts +165 -0
  355. package/dist/domain/knowledge.js +188 -0
  356. package/dist/domain/layout.d.ts +34 -5
  357. package/dist/domain/layout.js +64 -6
  358. package/dist/domain/locator-evidence.d.ts +3 -0
  359. package/dist/domain/locator-evidence.js +36 -0
  360. package/dist/domain/logcat-event.d.ts +54 -0
  361. package/dist/domain/logcat-event.js +40 -0
  362. package/dist/domain/observation.d.ts +33 -0
  363. package/dist/domain/observation.js +18 -0
  364. package/dist/domain/project-context.d.ts +219 -1
  365. package/dist/domain/project-context.js +209 -6
  366. package/dist/domain/proposed-step.d.ts +340 -60
  367. package/dist/domain/proposed-step.js +23 -3
  368. package/dist/domain/report.d.ts +1455 -28
  369. package/dist/domain/report.js +211 -14
  370. package/dist/domain/runtime-snapshot.d.ts +58 -1
  371. package/dist/domain/runtime-snapshot.js +14 -3
  372. package/dist/domain/runtime.d.ts +50 -0
  373. package/dist/domain/runtime.js +36 -0
  374. package/dist/domain/system-app-profiles.d.ts +6 -0
  375. package/dist/domain/system-app-profiles.js +43 -0
  376. package/dist/domain/ui-backend.d.ts +28 -0
  377. package/dist/domain/ui-backend.js +33 -0
  378. package/dist/domain/ui-cache.d.ts +11 -0
  379. package/dist/domain/ui-cache.js +10 -0
  380. package/dist/domain/window-hierarchy.d.ts +84 -0
  381. package/dist/domain/window-hierarchy.js +172 -0
  382. package/dist/domain/workflow-manifest.d.ts +77 -0
  383. package/dist/domain/workflow-manifest.js +136 -0
  384. package/dist/domain/workspace.d.ts +28 -0
  385. package/dist/domain/workspace.js +72 -0
  386. package/dist/ports/adb.d.ts +34 -1
  387. package/dist/ports/align-prompt.d.ts +11 -0
  388. package/dist/ports/align-prompt.js +6 -0
  389. package/dist/ports/anchor-resolver.d.ts +30 -0
  390. package/dist/ports/anchor-resolver.js +1 -0
  391. package/dist/ports/annotated-screen-resolver.d.ts +4 -0
  392. package/dist/ports/annotated-screen-resolver.js +1 -0
  393. package/dist/ports/camera-probe.d.ts +23 -0
  394. package/dist/ports/camera-probe.js +8 -0
  395. package/dist/ports/context-document-writer.d.ts +24 -0
  396. package/dist/ports/context-document-writer.js +1 -0
  397. package/dist/ports/detached-process-launcher.d.ts +12 -0
  398. package/dist/ports/detached-process-launcher.js +1 -0
  399. package/dist/ports/external-flow-registry.d.ts +37 -0
  400. package/dist/ports/external-flow-registry.js +1 -0
  401. package/dist/ports/generation-prompt.d.ts +2 -1
  402. package/dist/ports/generation-session-store.d.ts +12 -2
  403. package/dist/ports/git-diff.d.ts +9 -0
  404. package/dist/ports/git-diff.js +1 -0
  405. package/dist/ports/journey-composition-store.d.ts +17 -0
  406. package/dist/ports/journey-composition-store.js +1 -0
  407. package/dist/ports/knowledge-registry.d.ts +20 -0
  408. package/dist/ports/knowledge-registry.js +1 -0
  409. package/dist/ports/process-runner.d.ts +7 -0
  410. package/dist/ports/project-file-inspector.d.ts +1 -0
  411. package/dist/ports/project-identity-inspector.d.ts +24 -0
  412. package/dist/ports/project-identity-inspector.js +1 -0
  413. package/dist/ports/project-inventory-inspector.d.ts +21 -0
  414. package/dist/ports/project-inventory-inspector.js +1 -0
  415. package/dist/ports/project-module-discoverer.d.ts +26 -0
  416. package/dist/ports/project-module-discoverer.js +1 -0
  417. package/dist/ports/recorder-prompt.d.ts +10 -1
  418. package/dist/ports/runtime-backend.d.ts +89 -0
  419. package/dist/ports/runtime-backend.js +13 -0
  420. package/dist/ports/runtime-capability.d.ts +12 -0
  421. package/dist/ports/runtime-capability.js +20 -0
  422. package/dist/ports/runtime-session-ports.d.ts +25 -0
  423. package/dist/ports/runtime-session-ports.js +1 -0
  424. package/dist/ports/screenshot.d.ts +11 -0
  425. package/dist/ports/screenshot.js +1 -0
  426. package/dist/ports/skill-installer.d.ts +2 -2
  427. package/dist/ports/ui-snapshot.d.ts +35 -0
  428. package/dist/ports/ui-snapshot.js +1 -0
  429. package/dist/ports/ui-stability.d.ts +19 -0
  430. package/dist/ports/ui-stability.js +1 -0
  431. package/dist/ports/workspace-layout.d.ts +4 -0
  432. package/dist/ports/workspace-layout.js +1 -0
  433. package/dist/shared/errors.d.ts +2 -0
  434. package/dist/shared/errors.js +22 -0
  435. package/dist/shared/paths.d.ts +2 -0
  436. package/dist/shared/paths.js +24 -0
  437. package/dist/shared/strings.d.ts +2 -0
  438. package/dist/shared/strings.js +16 -0
  439. package/package.json +6 -1
  440. package/assets/skills/taphound-ai-journey/GUIDE.md +0 -862
  441. package/assets/skills/taphound-ai-journey/SKILL.md +0 -377
  442. package/assets/skills/taphound-ai-journey/prompts/analyze-project.md +0 -526
  443. package/assets/skills/taphound-ai-journey/prompts/generate-step.md +0 -109
  444. package/assets/skills/taphound-ai-journey/schemas/project-context.json +0 -81
  445. package/assets/skills/taphound-ai-journey/templates/project-context.example.json +0 -49
  446. package/dist/ports/android-cli.d.ts +0 -21
  447. /package/dist/{ports/android-cli.js → adapters/runtime/mobile-mcp/mobile-mcp-tools.js} +0 -0
@@ -1,11 +1,14 @@
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";
10
13
  function commandFailed(result) {
11
14
  return result.exitCode !== 0
@@ -16,23 +19,14 @@ function commandFailed(result) {
16
19
  function commandMessage(result, fallback) {
17
20
  return result.stderr.trim() || result.spawnError || fallback;
18
21
  }
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
22
  function errorMessage(error) {
30
23
  return error instanceof Error ? error.message : String(error);
31
24
  }
32
25
  function layerForFailure(code) {
33
26
  if (code === "APP_NOT_INSTALLED"
34
27
  || code === "APP_LAUNCH_FAILED"
35
- || code === "APP_CRASHED") {
28
+ || code === "APP_CRASHED"
29
+ || code === "RUNTIME_CAPABILITY_MISSING") {
36
30
  return "run";
37
31
  }
38
32
  if (code === "ACTIVITY_BEFORE_MISMATCH"
@@ -42,27 +36,133 @@ function layerForFailure(code) {
42
36
  if (code.startsWith("EXPECT_")) {
43
37
  return "explicitExpect";
44
38
  }
39
+ if (code === "CHECKPOINT_FAILED" || code === "CHECKPOINT_UNRESOLVED") {
40
+ return "explicitExpect";
41
+ }
45
42
  if (code === "COLLECTION_FAILED") {
46
43
  return "collection";
47
44
  }
48
45
  return "structural";
49
46
  }
47
+ function soleRoleForJourney(journey) {
48
+ return journey.devices[0]?.role ?? DEFAULT_DEVICE_ROLE;
49
+ }
50
+ function taskHookOutcome(phase, runtime, result) {
51
+ return { ...result, phase, deviceRole: runtime.role };
52
+ }
53
+ async function runVerifyHook(phase, runtime, snapshot, hooks, clock) {
54
+ const hook = phase === "beforeSteps"
55
+ ? hooks.beforeSteps
56
+ : hooks.afterSteps;
57
+ if (hook === undefined) {
58
+ return taskHookOutcome(phase, runtime, {
59
+ status: "unresolved",
60
+ message: "Verify hook is not configured"
61
+ });
62
+ }
63
+ try {
64
+ const result = await hook({
65
+ deviceRole: runtime.role,
66
+ deviceSerial: runtime.deviceSerial,
67
+ adb: runtime.views.adb,
68
+ uiSnapshotProvider: runtime.provider,
69
+ logcat: runtime.logcat,
70
+ clock,
71
+ snapshot
72
+ });
73
+ return taskHookOutcome(phase, runtime, result);
74
+ }
75
+ catch (error) {
76
+ return taskHookOutcome(phase, runtime, {
77
+ status: "unresolved",
78
+ message: error instanceof Error ? error.message : String(error)
79
+ });
80
+ }
81
+ }
82
+ function stepDeviceRole(step, soleRole) {
83
+ return step.device ?? soleRole;
84
+ }
85
+ function deviceCoverageFailure(journey, devices) {
86
+ const duplicateRole = devices.find((assignment, index) => devices.some((other, otherIndex) => otherIndex > index && other.role === assignment.role));
87
+ if (duplicateRole !== undefined) {
88
+ return {
89
+ code: "CONFIG_INVALID",
90
+ message: `Duplicate device assignment for role: ${duplicateRole.role}`
91
+ };
92
+ }
93
+ const reusedSerial = devices.find((assignment, index) => devices.some((other, otherIndex) => otherIndex > index
94
+ && other.deviceSerial === assignment.deviceSerial));
95
+ if (reusedSerial !== undefined) {
96
+ return {
97
+ code: "CONFIG_INVALID",
98
+ message: `Device serial is mapped to multiple roles: ${reusedSerial.deviceSerial}`
99
+ };
100
+ }
101
+ const declaredRoles = journey.devices.map((device) => device.role);
102
+ const unknownRole = devices.find((assignment) => !declaredRoles.includes(assignment.role));
103
+ if (unknownRole !== undefined) {
104
+ return {
105
+ code: "CONFIG_INVALID",
106
+ message: `Device assignment references a role the Journey does not declare: ${unknownRole.role}`
107
+ };
108
+ }
109
+ const assignedRoles = new Set(devices.map((assignment) => assignment.role));
110
+ const missingRoles = declaredRoles.filter((role) => !assignedRoles.has(role));
111
+ if (missingRoles.length > 0) {
112
+ return {
113
+ code: "DEVICE_ROLE_UNMAPPED",
114
+ message: `No device mapping for journey role(s): ${missingRoles.join(", ")}`
115
+ };
116
+ }
117
+ return undefined;
118
+ }
119
+ function uniqueAssignmentsByRole(devices) {
120
+ const seen = new Set();
121
+ const unique = [];
122
+ for (const assignment of devices) {
123
+ if (seen.has(assignment.role)) {
124
+ continue;
125
+ }
126
+ seen.add(assignment.role);
127
+ unique.push(assignment);
128
+ }
129
+ return unique;
130
+ }
50
131
  export class VerifyRuntime {
51
132
  dependencies;
52
133
  constructor(dependencies) {
53
134
  this.dependencies = dependencies;
54
135
  }
55
136
  async verify(input) {
137
+ if (input.devices.length === 0) {
138
+ throw new Error("VerifyInput.devices requires at least one device assignment");
139
+ }
140
+ input.progress?.({ stage: "preparing" });
56
141
  const startedAt = this.dependencies.now();
57
142
  const runId = this.dependencies.createRunId();
58
143
  const launchActivity = normalizeActivity(input.config.run.packageName, input.config.run.activity);
59
144
  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;
145
+ const logcatRunStartedAt = this.dependencies.clock.now();
146
+ const markers = new Map();
147
+ const coverage = deviceCoverageFailure(input.journey, input.devices);
148
+ const assignmentByRole = new Map(input.devices.map((assignment) => [assignment.role, assignment]));
149
+ const runtimes = [];
150
+ let primaryFailure = coverage === undefined
151
+ ? undefined
152
+ : {
153
+ code: coverage.code,
154
+ message: coverage.message,
155
+ phase: "runtime"
156
+ };
63
157
  const secondaryErrors = [];
64
158
  const collectionErrors = [];
159
+ const hookOutcomes = [];
65
160
  const steps = [];
161
+ const checkpoints = [];
162
+ const checkpointScreens = [];
163
+ const checkpointEvaluator = new CheckpointEvaluator();
164
+ let knowledge;
165
+ let knowledgeError;
66
166
  const layers = {
67
167
  run: "notRun",
68
168
  structural: "notRun",
@@ -70,6 +170,9 @@ export class VerifyRuntime {
70
170
  explicitExpect: "notRun",
71
171
  collection: "passed"
72
172
  };
173
+ if (coverage !== undefined) {
174
+ layers[layerForFailure(coverage.code)] = "failed";
175
+ }
73
176
  const setPrimary = (code, message, phase, stepIndex) => {
74
177
  if (primaryFailure !== undefined) {
75
178
  return;
@@ -92,144 +195,271 @@ export class VerifyRuntime {
92
195
  collectionErrors.push(failure);
93
196
  };
94
197
  try {
95
- try {
96
- const installed = await this.dependencies.adb.isInstalled({
198
+ if (input.journey.checkpoints?.some((checkpoint) => checkpoint.expect.allOf.some((condition) => condition.kind === "screen"))) {
199
+ if (this.dependencies.loadKnowledge === undefined) {
200
+ knowledgeError = "Project Knowledge loader is not configured";
201
+ }
202
+ else {
203
+ try {
204
+ knowledge = await this.dependencies.loadKnowledge({
205
+ projectRoot: input.projectRoot,
206
+ packageName: input.config.run.packageName
207
+ });
208
+ }
209
+ catch (error) {
210
+ knowledgeError = `Project Knowledge unavailable: ${errorMessage(error)}`;
211
+ }
212
+ }
213
+ }
214
+ const evaluateCheckpoint = async (checkpoint, runtime, stepStartedAt) => {
215
+ if (runtime.provider === undefined) {
216
+ setPrimary("CHECKPOINT_UNRESOLVED", `Checkpoint ${checkpoint.id} has no UI snapshot provider`, "checkpoint", checkpoint.stepIndex);
217
+ return false;
218
+ }
219
+ const evaluation = await checkpointEvaluator.evaluate({
220
+ checkpoint,
221
+ provider: runtime.provider,
222
+ adb: runtime.views.adb,
97
223
  packageName: input.config.run.packageName,
98
- deviceSerial: input.deviceSerial,
99
- ...(input.signal === undefined ? {} : { signal: input.signal }),
100
- timeoutMs: input.config.idle.timeoutMs
224
+ deviceSerial: runtime.deviceSerial,
225
+ timeoutMs: input.config.idle.timeoutMs,
226
+ clock: this.dependencies.clock,
227
+ logcat: runtime.logcat,
228
+ runStartedAt: logcatRunStartedAt,
229
+ markers,
230
+ logcatEvidenceRef: `logcat-${runtime.role}.txt`,
231
+ ...(stepStartedAt === undefined ? {} : { stepStartedAt }),
232
+ writeSnapshot: (path, snapshot) => session.writeJson(path, snapshot),
233
+ ...(knowledge === undefined ? {} : { knowledge }),
234
+ ...(knowledgeError === undefined ? {} : { knowledgeError }),
235
+ ...(input.signal === undefined ? {} : { signal: input.signal })
101
236
  });
102
- if (!installed) {
103
- setPrimary("APP_NOT_INSTALLED", `Package ${input.config.run.packageName} is not installed on ${input.deviceSerial}`, "install");
237
+ checkpoints.push(evaluation.report);
238
+ if (evaluation.matchedScreen !== undefined) {
239
+ checkpointScreens.push({
240
+ screen: evaluation.matchedScreen,
241
+ status: "matched"
242
+ });
243
+ }
244
+ if (evaluation.report.status !== "passed") {
245
+ setPrimary(evaluation.report.status === "failed"
246
+ ? "CHECKPOINT_FAILED"
247
+ : "CHECKPOINT_UNRESOLVED", `Checkpoint ${checkpoint.id} ${evaluation.report.status}: ${evaluation.report.conditions
248
+ .filter((condition) => condition.status !== "passed")
249
+ .map((condition) => condition.message ?? condition.kind)
250
+ .join("; ")}`, "checkpoint", checkpoint.stepIndex);
251
+ return false;
252
+ }
253
+ return true;
254
+ };
255
+ const createStepRunner = this.dependencies.createStepRunner
256
+ ?? ((options) => new StepRunner(options));
257
+ // Fresh for every Replay, shared only across device runners in this run.
258
+ const bindings = new Map();
259
+ if (coverage === undefined) {
260
+ for (const declaration of input.journey.devices) {
261
+ const assignment = assignmentByRole.get(declaration.role);
262
+ if (assignment === undefined) {
263
+ continue;
264
+ }
265
+ const deviceSession = await this.dependencies.sessions.openSession({
266
+ deviceSerial: assignment.deviceSerial,
267
+ ...(input.signal === undefined ? {} : { signal: input.signal })
268
+ });
269
+ const views = this.dependencies.sessionPorts(deviceSession);
270
+ runtimes.push({
271
+ role: declaration.role,
272
+ deviceSerial: assignment.deviceSerial,
273
+ session: deviceSession,
274
+ views,
275
+ provider: undefined,
276
+ readySnapshot: undefined,
277
+ logcat: new LogcatCollector(views.adb, this.dependencies.clock),
278
+ logcatStarted: false,
279
+ runner: undefined
280
+ });
104
281
  }
105
282
  }
106
- catch (error) {
107
- setPrimary("APP_NOT_INSTALLED", errorMessage(error), "install");
108
- }
109
- if (primaryFailure === undefined) {
283
+ for (const runtime of runtimes) {
284
+ if (primaryFailure !== undefined) {
285
+ break;
286
+ }
287
+ const deviceSession = runtime.session;
288
+ const provider = await deviceSession.openUiSnapshots({
289
+ timeoutMs: input.config.ui?.snapshotTimeoutMs
290
+ ?? input.config.idle.timeoutMs,
291
+ backend: input.config.ui?.backend ?? "auto",
292
+ cacheEnabled: input.config.ui?.cacheEnabled ?? true,
293
+ ...(input.signal === undefined ? {} : { signal: input.signal })
294
+ });
295
+ runtime.provider = provider;
296
+ let installFailed = false;
297
+ try {
298
+ const installed = await deviceSession.isInstalled({
299
+ packageName: input.config.run.packageName,
300
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
301
+ timeoutMs: input.config.idle.timeoutMs
302
+ });
303
+ if (!installed) {
304
+ setPrimary("APP_NOT_INSTALLED", `Package ${input.config.run.packageName} is not installed on ${runtime.deviceSerial}`, "install");
305
+ installFailed = true;
306
+ }
307
+ }
308
+ catch (error) {
309
+ setPrimary("APP_NOT_INSTALLED", errorMessage(error), "install");
310
+ installFailed = true;
311
+ }
312
+ if (installFailed) {
313
+ break;
314
+ }
110
315
  try {
111
- await logcat.start({
112
- deviceSerial: input.deviceSerial,
316
+ await runtime.logcat.start({
317
+ deviceSerial: runtime.deviceSerial,
113
318
  ...(input.signal === undefined ? {} : { signal: input.signal })
114
319
  });
115
- logcatStarted = true;
320
+ runtime.logcatStarted = true;
116
321
  }
117
322
  catch (error) {
118
323
  layers.collection = "failed";
119
- setPrimary("COLLECTION_FAILED", errorMessage(error), "collection");
324
+ setPrimary(failureCodeFromUnknown(error) ?? "COLLECTION_FAILED", errorMessage(error), "collection");
120
325
  }
121
- if (logcatStarted) {
122
- const identity = {
326
+ if (!runtime.logcatStarted) {
327
+ break;
328
+ }
329
+ try {
330
+ const launched = await coldLaunchApp(runtime.views.adb, this.dependencies.clock, {
123
331
  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");
332
+ activity: launchActivity,
333
+ deviceSerial: runtime.deviceSerial,
334
+ pollIntervalMs: input.config.idle.pollIntervalMs,
335
+ timeoutMs: input.config.idle.timeoutMs,
336
+ signal: input.signal
337
+ });
338
+ if (launched.status === "cancelled") {
339
+ setPrimary("INTERNAL_ERROR", "Verification was cancelled", "readiness");
340
+ break;
144
341
  }
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
- }
342
+ if (launched.status === "failed") {
343
+ if (launched.stage === "process") {
344
+ setPrimary("APP_LAUNCH_FAILED", launched.message, "readiness");
204
345
  }
205
- catch (error) {
206
- setPrimary("APP_LAUNCH_FAILED", errorMessage(error), "readiness");
346
+ else {
347
+ layers.run = "failed";
348
+ setPrimary("APP_LAUNCH_FAILED", launched.message, "run");
207
349
  }
350
+ break;
351
+ }
352
+ runtime.logcat.scopeToPids(launched.pids);
353
+ const soleRole = soleRoleForJourney(input.journey);
354
+ const firstOwnStep = input.journey.steps.find((step) => stepDeviceRole(step, soleRole) === runtime.role);
355
+ if (firstOwnStep === undefined) {
356
+ throw new Error("Journey requires at least one step");
357
+ }
358
+ // The readiness budget starts once the launch command returned.
359
+ const remainingReadinessMs = input.config.idle.timeoutMs
360
+ - launched.processWaitMs;
361
+ if (remainingReadinessMs <= 0) {
362
+ setPrimary("APP_LAUNCH_FAILED", `Expected startup Activity ${firstOwnStep.activity.before}, found none before timeout`, "readiness");
363
+ break;
208
364
  }
365
+ const readiness = await new ActivityWaiter(runtime.views.adb, this.dependencies.clock).wait({
366
+ packageName: input.config.run.packageName,
367
+ deviceSerial: runtime.deviceSerial,
368
+ expectedActivity: firstOwnStep.activity.before,
369
+ pollIntervalMs: input.config.idle.pollIntervalMs,
370
+ timeoutMs: remainingReadinessMs,
371
+ ...(input.signal === undefined ? {} : { signal: input.signal })
372
+ });
373
+ if (readiness.status === "processMissing") {
374
+ setPrimary("APP_LAUNCH_FAILED", "App process exited before reaching the first Journey Activity", "readiness");
375
+ break;
376
+ }
377
+ if (readiness.status === "timeout") {
378
+ setPrimary("APP_LAUNCH_FAILED", `Expected startup Activity ${firstOwnStep.activity.before}, found ${readiness.actual ?? "none"} before timeout`, "readiness");
379
+ break;
380
+ }
381
+ if (readiness.status === "cancelled") {
382
+ setPrimary("INTERNAL_ERROR", "Verification was cancelled", "readiness");
383
+ break;
384
+ }
385
+ const readySnapshot = await provider.capture({
386
+ reason: "locate",
387
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
388
+ timeoutMs: input.config.idle.timeoutMs
389
+ });
390
+ runtime.readySnapshot = readySnapshot;
391
+ runtime.runner = createStepRunner({
392
+ adb: runtime.views.adb,
393
+ screenshots: runtime.views.screenshots,
394
+ annotatedScreens: runtime.views.annotatedScreens,
395
+ uiStability: uiStabilityProbe(provider, runtime.views.uiStability),
396
+ uiSnapshotProvider: provider,
397
+ clock: this.dependencies.clock,
398
+ logcat: runtime.logcat,
399
+ runStartedAt: logcatRunStartedAt,
400
+ markers,
401
+ bindings,
402
+ artifacts: session,
403
+ packageName: input.config.run.packageName,
404
+ deviceSerial: runtime.deviceSerial,
405
+ deviceRole: runtime.role,
406
+ idle: input.config.idle,
407
+ ...(this.dependencies.anchorResolverFor === undefined
408
+ ? {}
409
+ : { anchorResolver: this.dependencies.anchorResolverFor(input.projectRoot) }),
410
+ ...(input.requireFocusedInput === undefined
411
+ ? {}
412
+ : { requireFocusedInput: input.requireFocusedInput }),
413
+ ...(input.generatedReplayPolicy === undefined
414
+ ? {}
415
+ : { generatedReplayPolicy: input.generatedReplayPolicy }),
416
+ ...(input.manualReplay === undefined
417
+ ? {}
418
+ : { manualReplay: input.manualReplay })
419
+ });
420
+ }
421
+ catch (error) {
422
+ setPrimary(failureCodeFromUnknown(error) ?? "APP_LAUNCH_FAILED", errorMessage(error), "readiness");
423
+ break;
209
424
  }
210
425
  }
211
426
  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
- });
427
+ layers.run = "passed";
428
+ layers.structural = "passed";
429
+ layers.activityCheckpoint = "passed";
430
+ layers.explicitExpect = "passed";
431
+ }
432
+ if (primaryFailure === undefined
433
+ && input.hooks?.beforeSteps !== undefined) {
434
+ for (const runtime of runtimes) {
435
+ if (runtime.provider === undefined
436
+ || runtime.readySnapshot === undefined) {
437
+ continue;
438
+ }
439
+ hookOutcomes.push(await runVerifyHook("beforeSteps", runtime, runtime.readySnapshot, input.hooks, this.dependencies.clock));
440
+ }
441
+ }
442
+ if (primaryFailure === undefined) {
443
+ const soleRole = soleRoleForJourney(input.journey);
444
+ const runtimeByRole = new Map(runtimes.map((runtime) => [runtime.role, runtime]));
230
445
  for (const [index, step] of input.journey.steps.entries()) {
446
+ input.progress?.({
447
+ stage: "replaying",
448
+ stepIndex: index,
449
+ stepCount: input.journey.steps.length
450
+ });
451
+ const role = stepDeviceRole(step, soleRole);
452
+ const runner = runtimeByRole.get(role)?.runner;
453
+ if (runner === undefined) {
454
+ setPrimary("CONFIG_INVALID", `Step references an unmapped device role: ${role}`, "replay", index);
455
+ break;
456
+ }
231
457
  const result = await runner.run(step, index, input.signal);
232
458
  steps.push(result.report);
459
+ if (result.status === "manualRequired") {
460
+ setPrimary("MANUAL_STEP_REQUIRED", `Step ${String(index)} requires manual replay and the run is non-interactive`, "replay", index);
461
+ break;
462
+ }
233
463
  if (result.status === "cancelled") {
234
464
  setPrimary("INTERNAL_ERROR", "Verification was cancelled", "replay", index);
235
465
  break;
@@ -238,51 +468,150 @@ export class VerifyRuntime {
238
468
  setPrimary(result.failure.code, result.failure.message, result.failure.phase, result.failure.stepIndex);
239
469
  break;
240
470
  }
471
+ const runtime = runtimeByRole.get(role);
472
+ if (runtime === undefined) {
473
+ throw new Error("Checkpoint device runtime is unavailable");
474
+ }
475
+ let checkpointFailed = false;
476
+ for (const checkpoint of input.journey.checkpoints ?? []) {
477
+ if (checkpoint.stepIndex !== index) {
478
+ continue;
479
+ }
480
+ if (!await evaluateCheckpoint(checkpoint, runtime, result.report.startedAtMs)) {
481
+ checkpointFailed = true;
482
+ break;
483
+ }
484
+ }
485
+ if (checkpointFailed) {
486
+ break;
487
+ }
488
+ }
489
+ if (steps.length === input.journey.steps.length
490
+ && steps.every((step) => step.status === "passed")
491
+ && checkpoints.every((checkpoint) => checkpoint.status === "passed")) {
492
+ const lastStep = input.journey.steps.at(-1);
493
+ const lastRole = lastStep === undefined
494
+ ? soleRole
495
+ : stepDeviceRole(lastStep, soleRole);
496
+ const runtime = runtimeByRole.get(lastRole);
497
+ for (const checkpoint of input.journey.checkpoints ?? []) {
498
+ if (checkpoint.stepIndex !== undefined) {
499
+ continue;
500
+ }
501
+ if (runtime === undefined || !await evaluateCheckpoint(checkpoint, runtime, steps.at(-1)?.startedAtMs)) {
502
+ break;
503
+ }
504
+ }
505
+ }
506
+ if (input.hooks?.afterSteps !== undefined) {
507
+ for (const runtime of runtimes) {
508
+ if (runtime.provider === undefined) {
509
+ continue;
510
+ }
511
+ let snapshot;
512
+ try {
513
+ snapshot = await runtime.provider.capture({
514
+ reason: "evidence",
515
+ ...(input.signal === undefined
516
+ ? {}
517
+ : { signal: input.signal }),
518
+ timeoutMs: input.config.idle.timeoutMs
519
+ });
520
+ }
521
+ catch (error) {
522
+ hookOutcomes.push({
523
+ phase: "afterSteps",
524
+ deviceRole: runtime.role,
525
+ status: "unresolved",
526
+ message: `Post-journey snapshot failed: ${error instanceof Error ? error.message : String(error)}`
527
+ });
528
+ continue;
529
+ }
530
+ hookOutcomes.push(await runVerifyHook("afterSteps", runtime, snapshot, input.hooks, this.dependencies.clock));
531
+ }
241
532
  }
242
533
  }
243
534
  }
244
535
  catch (error) {
536
+ const errorCode = failureCodeFromUnknown(error) ?? "INTERNAL_ERROR";
245
537
  if (primaryFailure === undefined) {
246
- setPrimary("INTERNAL_ERROR", errorMessage(error), "runtime");
538
+ setPrimary(errorCode, errorMessage(error), "runtime");
247
539
  }
248
540
  else {
249
541
  secondaryErrors.push({
250
- code: "INTERNAL_ERROR",
542
+ code: errorCode,
251
543
  message: errorMessage(error),
252
544
  phase: "runtime"
253
545
  });
254
546
  }
255
547
  }
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;
548
+ const screenshotEntries = [];
549
+ const uiHierarchyEntries = [];
550
+ const logcatEntries = [];
551
+ input.progress?.({ stage: "collecting" });
552
+ if (coverage === undefined) {
553
+ for (const runtime of runtimes) {
554
+ if (runtime.provider !== undefined
555
+ && runtime.readySnapshot !== undefined) {
556
+ const uiHierarchyPath = `ui-hierarchy-${runtime.role}.json`;
557
+ try {
558
+ const snapshot = await runtime.provider.capture({
559
+ reason: "evidence",
560
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
561
+ timeoutMs: input.config.idle.timeoutMs
562
+ });
563
+ await session.writeJson(uiHierarchyPath, snapshot);
564
+ uiHierarchyEntries.push({
565
+ role: runtime.role,
566
+ path: uiHierarchyPath
567
+ });
568
+ }
569
+ catch (error) {
570
+ collectionFailure(errorMessage(error));
571
+ }
572
+ }
573
+ const screenshotPath = `screenshot-${runtime.role}.png`;
574
+ try {
575
+ const screenshot = await runtime.session.captureScreenshot({
576
+ outputPath: session.path(screenshotPath),
577
+ ...(input.signal === undefined ? {} : { signal: input.signal })
578
+ });
579
+ if (commandFailed(screenshot)) {
580
+ collectionFailure(commandMessage(screenshot, "Screen capture failed"));
581
+ }
582
+ else {
583
+ screenshotEntries.push({ role: runtime.role, path: screenshotPath });
584
+ }
585
+ }
586
+ catch (error) {
587
+ collectionFailure(errorMessage(error));
588
+ }
589
+ if (runtime.logcatStarted) {
590
+ const logcatPath = `logcat-${runtime.role}.txt`;
591
+ try {
592
+ const stopped = await runtime.logcat.stop();
593
+ if (logcatStopFailed(stopped)) {
594
+ collectionFailure(commandMessage(stopped, "Logcat stop failed"));
595
+ }
596
+ await session.writeText(logcatPath, runtime.logcat.rawLines().map((line) => line.raw).join("\n"));
597
+ logcatEntries.push({ role: runtime.role, path: logcatPath });
598
+ }
599
+ catch (error) {
600
+ collectionFailure(errorMessage(error));
601
+ }
602
+ }
269
603
  }
270
604
  }
271
- catch (error) {
272
- collectionFailure(errorMessage(error));
273
- }
274
- let logcatCollected = false;
275
- if (logcatStarted) {
605
+ for (const runtime of runtimes) {
276
606
  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;
607
+ await runtime.session.close();
283
608
  }
284
609
  catch (error) {
285
- collectionFailure(errorMessage(error));
610
+ secondaryErrors.push({
611
+ code: "INTERNAL_ERROR",
612
+ message: errorMessage(error),
613
+ phase: "runtimeSessionClose"
614
+ });
286
615
  }
287
616
  }
288
617
  if (collectionErrors.length > 0) {
@@ -295,17 +624,40 @@ export class VerifyRuntime {
295
624
  const failure = primaryFailure;
296
625
  const status = failure === undefined
297
626
  ? "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";
627
+ : failure.code === "MANUAL_STEP_REQUIRED"
628
+ ? "manualRequired"
629
+ : [
630
+ "CONFIG_INVALID",
631
+ "ENVIRONMENT_MISSING_TOOL",
632
+ "DEVICE_UNAVAILABLE",
633
+ "DEVICE_ROLE_UNMAPPED",
634
+ "APP_NOT_INSTALLED",
635
+ "INTERNAL_ERROR"
636
+ ].includes(failure.code)
637
+ ? "error"
638
+ : "failed";
307
639
  const report = {
308
- schemaVersion: 2,
640
+ schemaVersion: 4,
641
+ ...(runtimes.some((runtime) => runtime.logcatStarted
642
+ && (runtime.logcat.metadata().droppedLines ?? 0) > 0)
643
+ ? {
644
+ logcatEvidence: runtimes.flatMap((runtime) => {
645
+ if (!runtime.logcatStarted) {
646
+ return [];
647
+ }
648
+ const metadata = runtime.logcat.metadata();
649
+ return metadata.droppedLines === undefined ? [] : [{
650
+ role: runtime.role,
651
+ droppedLines: metadata.droppedLines,
652
+ droppedBytes: metadata.droppedBytes ?? 0,
653
+ ...(metadata.lastDroppedAtMs === undefined ? {} : {
654
+ lastDroppedAtMs: metadata.lastDroppedAtMs
655
+ }),
656
+ status: "incomplete"
657
+ }];
658
+ })
659
+ }
660
+ : {}),
309
661
  runId,
310
662
  status,
311
663
  startedAt: startedAt.toISOString(),
@@ -321,17 +673,44 @@ export class VerifyRuntime {
321
673
  sha256: hashJourney(input.journey)
322
674
  },
323
675
  environment: {
324
- deviceSerial: input.deviceSerial,
676
+ devices: coverage === undefined
677
+ ? runtimes.map((runtime) => ({
678
+ role: runtime.role,
679
+ deviceSerial: runtime.deviceSerial,
680
+ ...(runtime.provider === undefined
681
+ ? {}
682
+ : {
683
+ uiBackend: runtime.provider.descriptor,
684
+ ...(runtime.provider.cacheTelemetry === undefined
685
+ ? {}
686
+ : { uiCache: runtime.provider.cacheTelemetry() })
687
+ })
688
+ }))
689
+ : uniqueAssignmentsByRole(input.devices).map((assignment) => ({
690
+ role: assignment.role,
691
+ deviceSerial: assignment.deviceSerial
692
+ })),
325
693
  tools: input.toolVersions
326
694
  },
327
695
  layers,
328
696
  steps,
697
+ ...(input.journey.checkpoints === undefined ? {} : { checkpoints }),
698
+ screens: Array.from(new Map([
699
+ ...hookOutcomes.flatMap((outcome) => (outcome.status === "passed" && outcome.screen !== undefined
700
+ ? [[outcome.screen, {
701
+ screen: outcome.screen,
702
+ status: "matched"
703
+ }]]
704
+ : [])),
705
+ ...checkpointScreens.map((screen) => [screen.screen, screen])
706
+ ]).values()),
329
707
  artifacts: {
330
708
  directory: session.finalDirectory,
331
709
  report: "report.json",
332
710
  summary: "summary.txt",
333
- ...(screenshotCollected ? { screenshot: screenshotPath } : {}),
334
- ...(logcatCollected ? { logcat: "logcat.txt" } : {}),
711
+ screenshots: screenshotEntries,
712
+ uiHierarchies: uiHierarchyEntries,
713
+ logcats: logcatEntries,
335
714
  stepLogs: steps.flatMap((step) => (step.logcatPath === undefined ? [] : [step.logcatPath]))
336
715
  },
337
716
  ...(failure === undefined ? {} : { primaryFailure: failure }),
@@ -344,7 +723,8 @@ export class VerifyRuntime {
344
723
  exitCode: failure === undefined ? 0 : exitCodeForFailure(failure.code),
345
724
  report,
346
725
  reportPath: published.reportPath,
347
- summaryPath: published.summaryPath
726
+ summaryPath: published.summaryPath,
727
+ ...(hookOutcomes.length === 0 ? {} : { hookOutcomes })
348
728
  };
349
729
  }
350
730
  }