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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (467) hide show
  1. package/README.md +49 -149
  2. package/README.zh-CN.md +47 -147
  3. package/assets/skills/taphound-case-suite/SKILL.md +363 -0
  4. package/assets/skills/taphound-case-suite/schemas/base-flow-record.schema.json +22 -0
  5. package/assets/skills/taphound-case-suite/schemas/case-catalog.schema.json +26 -0
  6. package/assets/skills/taphound-case-suite/schemas/case-ledger.schema.json +158 -0
  7. package/assets/skills/taphound-case-suite/schemas/suite-input.schema.json +41 -0
  8. package/assets/skills/taphound-case-suite/schemas/transition.schema.json +61 -0
  9. package/assets/skills/taphound-case-suite/scripts/ledger.mjs +1207 -0
  10. package/assets/skills/taphound-case-suite/templates/base-flow-record.example.json +20 -0
  11. package/assets/skills/taphound-case-suite/templates/suite-input.example.json +27 -0
  12. package/assets/skills/taphound-case-suite/templates/transition.example.json +8 -0
  13. package/assets/skills/taphound-flash/SKILL.md +115 -0
  14. package/assets/skills/taphound-flash/scripts/flash.mjs +585 -0
  15. package/assets/skills/taphound-flash/templates/flash-plan.example.json +15 -0
  16. package/assets/skills/taphound-journey-brief-author/CONTEXT-GUIDE.md +471 -0
  17. package/assets/skills/taphound-journey-brief-author/SKILL.md +494 -0
  18. package/assets/skills/taphound-journey-brief-author/prompts/brief-author-role.md +96 -0
  19. package/assets/skills/taphound-journey-brief-author/prompts/brief-author-role.zh-CN.md +80 -0
  20. package/assets/skills/taphound-journey-brief-author/prompts/context-analyze-project.md +309 -0
  21. package/assets/skills/taphound-journey-brief-author/schemas/context-refresh-result.json +123 -0
  22. package/assets/skills/taphound-journey-brief-author/schemas/project-context-module.json +192 -0
  23. package/assets/skills/taphound-journey-brief-author/schemas/project-context.json +152 -0
  24. package/assets/skills/taphound-journey-brief-author/templates/project-context-module.example.json +58 -0
  25. package/assets/skills/taphound-journey-brief-author/templates/project-context.example.json +51 -0
  26. package/assets/skills/taphound-journey-brief-author/templates/taphound-journey-brief.template.md +63 -0
  27. package/assets/skills/taphound-journey-generator/SKILL.md +507 -0
  28. package/assets/skills/{taphound-ai-journey → taphound-journey-generator}/prompts/check-completion.md +3 -0
  29. package/assets/skills/taphound-journey-generator/prompts/consume-journey-brief.md +52 -0
  30. package/assets/skills/taphound-journey-generator/prompts/generate-step.md +211 -0
  31. package/assets/skills/taphound-journey-generator/prompts/select-flow.md +91 -0
  32. package/assets/skills/taphound-journey-generator/references/bridge.md +35 -0
  33. package/assets/skills/taphound-journey-generator/references/mid-session.md +44 -0
  34. package/assets/skills/taphound-journey-generator/schemas/external-flow.json +37 -0
  35. package/assets/skills/taphound-journey-generator/schemas/flow.json +28 -0
  36. package/assets/skills/taphound-journey-generator/schemas/journey-source.json +25 -0
  37. package/assets/skills/{taphound-ai-journey → taphound-journey-generator}/schemas/observe-output.json +13 -4
  38. package/assets/skills/{taphound-ai-journey → taphound-journey-generator}/schemas/proposed-step-envelope.json +127 -5
  39. package/assets/skills/taphound-journey-generator/scripts/envelope.mjs +578 -0
  40. package/assets/skills/taphound-journey-generator/templates/external-flow.example.json +22 -0
  41. package/assets/skills/taphound-journey-generator/templates/flow.example.json +22 -0
  42. package/assets/skills/taphound-journey-generator/templates/journey-source.example.json +17 -0
  43. package/assets/skills/taphound-journey-generator/templates/taphound-journey-brief.example.md +70 -0
  44. package/assets/skills/taphound-verify-change/SKILL.md +91 -0
  45. package/assets/skills/taphound-verify-change/references/accept.md +38 -0
  46. package/assets/skills/taphound-verify-change/references/preserve.md +357 -0
  47. package/assets/skills/taphound-verify-change/schemas/workflow-manifest.schema.json +295 -0
  48. package/assets/skills/taphound-verify-change/scripts/handoff.mjs +482 -0
  49. package/assets/skills/taphound-verify-change/scripts/ui-refactor.mjs +505 -0
  50. package/dist/adapters/adb/adb-adapter.d.ts +16 -2
  51. package/dist/adapters/adb/adb-adapter.js +199 -0
  52. package/dist/adapters/adb/system-uiautomator-snapshot-provider.d.ts +13 -0
  53. package/dist/adapters/adb/system-uiautomator-snapshot-provider.js +122 -0
  54. package/dist/adapters/adb/ui-automator-parser.d.ts +3 -0
  55. package/dist/adapters/adb/ui-automator-parser.js +160 -0
  56. package/dist/adapters/adb/window-topology-parser.d.ts +2 -0
  57. package/dist/adapters/adb/window-topology-parser.js +101 -0
  58. package/dist/adapters/android-cli/android-cli-adapter.d.ts +16 -8
  59. package/dist/adapters/android-cli/android-cli-adapter.js +137 -15
  60. package/dist/adapters/android-cli/android-cli-snapshot-provider.d.ts +9 -0
  61. package/dist/adapters/android-cli/android-cli-snapshot-provider.js +99 -0
  62. package/dist/adapters/android-cli/layout-parser.js +12 -8
  63. package/dist/adapters/appium/appium-doctor.d.ts +7 -0
  64. package/dist/adapters/appium/appium-doctor.js +128 -0
  65. package/dist/adapters/appium/appium-ui-snapshot-provider.d.ts +42 -0
  66. package/dist/adapters/appium/appium-ui-snapshot-provider.js +309 -0
  67. package/dist/adapters/camera/camera-probe-adapter.d.ts +18 -0
  68. package/dist/adapters/camera/camera-probe-adapter.js +286 -0
  69. package/dist/adapters/filesystem/artifact-store.js +40 -1
  70. package/dist/adapters/filesystem/context-document-writer.d.ts +15 -0
  71. package/dist/adapters/filesystem/context-document-writer.js +112 -0
  72. package/dist/adapters/filesystem/diagnostics-journal.d.ts +17 -0
  73. package/dist/adapters/filesystem/diagnostics-journal.js +130 -0
  74. package/dist/adapters/filesystem/external-flow-registry.d.ts +17 -0
  75. package/dist/adapters/filesystem/external-flow-registry.js +325 -0
  76. package/dist/adapters/filesystem/generation-session-store.d.ts +17 -11
  77. package/dist/adapters/filesystem/generation-session-store.js +281 -855
  78. package/dist/adapters/filesystem/generation-store/session-lock.d.ts +29 -0
  79. package/dist/adapters/filesystem/generation-store/session-lock.js +202 -0
  80. package/dist/adapters/filesystem/generation-store/session-transitions.d.ts +24 -0
  81. package/dist/adapters/filesystem/generation-store/session-transitions.js +340 -0
  82. package/dist/adapters/filesystem/generation-store/store-files.d.ts +50 -0
  83. package/dist/adapters/filesystem/generation-store/store-files.js +363 -0
  84. package/dist/adapters/filesystem/journey-composition-store.d.ts +23 -0
  85. package/dist/adapters/filesystem/journey-composition-store.js +151 -0
  86. package/dist/adapters/filesystem/knowledge-registry.d.ts +12 -0
  87. package/dist/adapters/filesystem/knowledge-registry.js +223 -0
  88. package/dist/adapters/filesystem/project-bound-file.js +3 -5
  89. package/dist/adapters/filesystem/project-file-inspector.d.ts +2 -0
  90. package/dist/adapters/filesystem/project-file-inspector.js +57 -28
  91. package/dist/adapters/filesystem/project-identity-inspector.d.ts +7 -0
  92. package/dist/adapters/filesystem/project-identity-inspector.js +215 -0
  93. package/dist/adapters/filesystem/project-inventory-inspector.d.ts +8 -0
  94. package/dist/adapters/filesystem/project-inventory-inspector.js +93 -0
  95. package/dist/adapters/filesystem/project-module-discoverer.d.ts +6 -0
  96. package/dist/adapters/filesystem/project-module-discoverer.js +158 -0
  97. package/dist/adapters/filesystem/skill-installer.d.ts +7 -2
  98. package/dist/adapters/filesystem/skill-installer.js +40 -11
  99. package/dist/adapters/filesystem/workspace-layout.d.ts +7 -0
  100. package/dist/adapters/filesystem/workspace-layout.js +57 -0
  101. package/dist/adapters/git/node-git-diff.d.ts +17 -0
  102. package/dist/adapters/git/node-git-diff.js +114 -0
  103. package/dist/adapters/process/node-detached-process-launcher.d.ts +6 -0
  104. package/dist/adapters/process/node-detached-process-launcher.js +96 -0
  105. package/dist/adapters/process/node-process-runner.js +11 -2
  106. package/dist/adapters/prompt/inquirer-align-prompt.d.ts +27 -0
  107. package/dist/adapters/prompt/inquirer-align-prompt.js +65 -0
  108. package/dist/adapters/prompt/inquirer-generation-prompt.js +3 -0
  109. package/dist/adapters/prompt/inquirer-recorder-prompt.d.ts +9 -1
  110. package/dist/adapters/prompt/inquirer-recorder-prompt.js +63 -0
  111. package/dist/adapters/runtime/adb-runtime-backend.d.ts +67 -0
  112. package/dist/adapters/runtime/adb-runtime-backend.js +206 -0
  113. package/dist/adapters/runtime/fake-runtime-backend.d.ts +87 -0
  114. package/dist/adapters/runtime/fake-runtime-backend.js +240 -0
  115. package/dist/adapters/runtime/mobile-mcp/mcp-tool-client.d.ts +45 -0
  116. package/dist/adapters/runtime/mobile-mcp/mcp-tool-client.js +153 -0
  117. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-errors.d.ts +23 -0
  118. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-errors.js +39 -0
  119. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-responses.d.ts +20 -0
  120. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-responses.js +180 -0
  121. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-runtime-backend.d.ts +78 -0
  122. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-runtime-backend.js +379 -0
  123. package/dist/adapters/runtime/mobile-mcp/mobile-mcp-tools.d.ts +46 -0
  124. package/dist/adapters/runtime/session-adb-view.d.ts +53 -0
  125. package/dist/adapters/runtime/session-adb-view.js +193 -0
  126. package/dist/adapters/runtime/session-backed-ports.d.ts +21 -0
  127. package/dist/adapters/runtime/session-backed-ports.js +43 -0
  128. package/dist/adapters/runtime/shared-session-runtime-backend.d.ts +12 -0
  129. package/dist/adapters/runtime/shared-session-runtime-backend.js +64 -0
  130. package/dist/adapters/ui/auto-ui-snapshot-provider.d.ts +8 -0
  131. package/dist/adapters/ui/auto-ui-snapshot-provider.js +48 -0
  132. package/dist/adapters/ui/device-layout-temp.d.ts +6 -0
  133. package/dist/adapters/ui/device-layout-temp.js +18 -0
  134. package/dist/adapters/ui/device-ui-environment.d.ts +13 -0
  135. package/dist/adapters/ui/device-ui-environment.js +81 -0
  136. package/dist/adapters/ui/layout-normalization.d.ts +9 -0
  137. package/dist/adapters/ui/layout-normalization.js +44 -0
  138. package/dist/adapters/ui/ui-snapshot-error.d.ts +11 -0
  139. package/dist/adapters/ui/ui-snapshot-error.js +12 -0
  140. package/dist/adapters/ui/ui-snapshot-support.d.ts +15 -0
  141. package/dist/adapters/ui/ui-snapshot-support.js +12 -0
  142. package/dist/application/align/align-service.d.ts +42 -0
  143. package/dist/application/align/align-service.js +106 -0
  144. package/dist/application/assertion/expectation-evaluator.d.ts +18 -5
  145. package/dist/application/assertion/expectation-evaluator.js +156 -18
  146. package/dist/application/assertion/guarded-expectation.d.ts +45 -0
  147. package/dist/application/assertion/guarded-expectation.js +74 -0
  148. package/dist/application/checkpoint/baseline-capturer.d.ts +26 -0
  149. package/dist/application/checkpoint/baseline-capturer.js +163 -0
  150. package/dist/application/checkpoint/baseline-error.d.ts +5 -0
  151. package/dist/application/checkpoint/baseline-error.js +8 -0
  152. package/dist/application/checkpoint/baseline-service.d.ts +31 -0
  153. package/dist/application/checkpoint/baseline-service.js +147 -0
  154. package/dist/application/checkpoint/checkpoint-evaluator.d.ts +35 -0
  155. package/dist/application/checkpoint/checkpoint-evaluator.js +415 -0
  156. package/dist/application/checkpoint/regression-comparator.d.ts +18 -0
  157. package/dist/application/checkpoint/regression-comparator.js +192 -0
  158. package/dist/application/collector/logcat-collector.d.ts +22 -1
  159. package/dist/application/collector/logcat-collector.js +103 -15
  160. package/dist/application/collector/logcat-event.d.ts +8 -0
  161. package/dist/application/collector/logcat-event.js +79 -0
  162. package/dist/application/collector/logcat-stop.d.ts +2 -0
  163. package/dist/application/collector/logcat-stop.js +11 -0
  164. package/dist/application/context/context-generator.d.ts +45 -0
  165. package/dist/application/context/context-generator.js +228 -0
  166. package/dist/application/context/context-loader.d.ts +40 -0
  167. package/dist/application/context/context-loader.js +223 -0
  168. package/dist/application/context/context-refresher.d.ts +59 -0
  169. package/dist/application/context/context-refresher.js +349 -0
  170. package/dist/application/context/context-rehasher.d.ts +36 -0
  171. package/dist/application/context/context-rehasher.js +110 -0
  172. package/dist/application/context/context-validator.d.ts +23 -1
  173. package/dist/application/context/context-validator.js +116 -9
  174. package/dist/application/context/evidence-hash.d.ts +1 -0
  175. package/dist/application/context/evidence-hash.js +125 -0
  176. package/dist/application/context/evidence-match.d.ts +9 -0
  177. package/dist/application/context/evidence-match.js +8 -0
  178. package/dist/application/context/shard-identity.d.ts +2 -0
  179. package/dist/application/context/shard-identity.js +7 -0
  180. package/dist/application/contract/contract-loader.d.ts +25 -0
  181. package/dist/application/contract/contract-loader.js +68 -0
  182. package/dist/application/contract/contract-review.d.ts +25 -0
  183. package/dist/application/contract/contract-review.js +51 -0
  184. package/dist/application/contract/contract-verifier.d.ts +53 -0
  185. package/dist/application/contract/contract-verifier.js +645 -0
  186. package/dist/application/devices/resolve-device-assignments.d.ts +21 -0
  187. package/dist/application/devices/resolve-device-assignments.js +60 -0
  188. package/dist/application/diagnosis/failure-classifier.d.ts +23 -0
  189. package/dist/application/diagnosis/failure-classifier.js +179 -0
  190. package/dist/application/diagnostics/diagnostics-exporter.d.ts +29 -0
  191. package/dist/application/diagnostics/diagnostics-exporter.js +283 -0
  192. package/dist/application/diagnostics/ui-capture-telemetry.d.ts +16 -0
  193. package/dist/application/diagnostics/ui-capture-telemetry.js +81 -0
  194. package/dist/application/doctor/doctor-service.d.ts +36 -3
  195. package/dist/application/doctor/doctor-service.js +255 -25
  196. package/dist/application/generation/generation-app-preparer.d.ts +20 -0
  197. package/dist/application/generation/generation-app-preparer.js +28 -0
  198. package/dist/application/generation/generation-config-service.d.ts +21 -0
  199. package/dist/application/generation/generation-config-service.js +53 -0
  200. package/dist/application/generation/generation-confirmation-service.d.ts +14 -3
  201. package/dist/application/generation/generation-confirmation-service.js +109 -24
  202. package/dist/application/generation/generation-context-snapshot.d.ts +6 -0
  203. package/dist/application/generation/generation-context-snapshot.js +29 -0
  204. package/dist/application/generation/generation-finalizer.d.ts +15 -9
  205. package/dist/application/generation/generation-finalizer.js +167 -70
  206. package/dist/application/generation/generation-publisher.js +12 -8
  207. package/dist/application/generation/generation-recovery-service.d.ts +35 -0
  208. package/dist/application/generation/generation-recovery-service.js +144 -0
  209. package/dist/application/generation/generation-reopen-service.d.ts +15 -0
  210. package/dist/application/generation/generation-reopen-service.js +37 -0
  211. package/dist/application/generation/generation-replace-service.d.ts +32 -0
  212. package/dist/application/generation/generation-replace-service.js +116 -0
  213. package/dist/application/generation/generation-starter.d.ts +44 -4
  214. package/dist/application/generation/generation-starter.js +175 -8
  215. package/dist/application/generation/generation-step-executor.d.ts +67 -5
  216. package/dist/application/generation/generation-step-executor.js +471 -108
  217. package/dist/application/generation/proposed-step-validator.js +96 -30
  218. package/dist/application/generation/replay-policy-loader.d.ts +16 -0
  219. package/dist/application/generation/replay-policy-loader.js +29 -0
  220. package/dist/application/generation/risk-evaluator.d.ts +4 -1
  221. package/dist/application/generation/risk-evaluator.js +118 -3
  222. package/dist/application/generation/runtime-observer.d.ts +39 -7
  223. package/dist/application/generation/runtime-observer.js +193 -24
  224. package/dist/application/impact/impact-resolver.d.ts +32 -0
  225. package/dist/application/impact/impact-resolver.js +160 -0
  226. package/dist/application/init/init-service.js +26 -13
  227. package/dist/application/interaction/action-executor.d.ts +3 -2
  228. package/dist/application/interaction/action-executor.js +11 -1
  229. package/dist/application/interaction/action-target.d.ts +17 -0
  230. package/dist/application/interaction/action-target.js +40 -0
  231. package/dist/application/interaction/external-step-runner.d.ts +153 -0
  232. package/dist/application/interaction/external-step-runner.js +332 -0
  233. package/dist/application/interaction/fallback-resolver.d.ts +6 -3
  234. package/dist/application/interaction/fallback-resolver.js +7 -5
  235. package/dist/application/interaction/scroll-to-executor.d.ts +17 -3
  236. package/dist/application/interaction/scroll-to-executor.js +118 -36
  237. package/dist/application/journey/external-flow-resolver.d.ts +25 -0
  238. package/dist/application/journey/external-flow-resolver.js +22 -0
  239. package/dist/application/journey/journey-check-service.d.ts +54 -0
  240. package/dist/application/journey/journey-check-service.js +207 -0
  241. package/dist/application/journey/journey-promoter.d.ts +26 -0
  242. package/dist/application/journey/journey-promoter.js +123 -0
  243. package/dist/application/journey/journey-resolver.d.ts +42 -0
  244. package/dist/application/journey/journey-resolver.js +239 -0
  245. package/dist/application/journey/journey-retirer.d.ts +25 -0
  246. package/dist/application/journey/journey-retirer.js +87 -0
  247. package/dist/application/knowledge/anchor-resolver.d.ts +36 -0
  248. package/dist/application/knowledge/anchor-resolver.js +110 -0
  249. package/dist/application/knowledge/knowledge-loader.d.ts +16 -0
  250. package/dist/application/knowledge/knowledge-loader.js +36 -0
  251. package/dist/application/locator/layout-failure-summary.d.ts +2 -0
  252. package/dist/application/locator/layout-failure-summary.js +67 -0
  253. package/dist/application/locator/layout-traversal.d.ts +11 -0
  254. package/dist/application/locator/layout-traversal.js +18 -0
  255. package/dist/application/locator/locator-resolver.d.ts +13 -1
  256. package/dist/application/locator/locator-resolver.js +182 -18
  257. package/dist/application/observe/observe-service.d.ts +20 -0
  258. package/dist/application/observe/observe-service.js +91 -0
  259. package/dist/application/project/project-describer.d.ts +14 -6
  260. package/dist/application/project/project-describer.js +38 -5
  261. package/dist/application/recognition/screen-detector.d.ts +28 -0
  262. package/dist/application/recognition/screen-detector.js +111 -0
  263. package/dist/application/recorder/locator-selector.js +151 -17
  264. package/dist/application/recorder/recorder-service.d.ts +16 -4
  265. package/dist/application/recorder/recorder-service.js +420 -128
  266. package/dist/application/report/report-writer.d.ts +5 -0
  267. package/dist/application/report/report-writer.js +17 -1
  268. package/dist/application/runtime/cold-launch.d.ts +28 -0
  269. package/dist/application/runtime/cold-launch.js +59 -0
  270. package/dist/application/runtime/step-runner.d.ts +49 -4
  271. package/dist/application/runtime/step-runner.js +721 -130
  272. package/dist/application/runtime/verify-runtime.d.ts +57 -7
  273. package/dist/application/runtime/verify-runtime.js +577 -175
  274. package/dist/application/ui/cached-ui-snapshot-provider.d.ts +45 -0
  275. package/dist/application/ui/cached-ui-snapshot-provider.js +111 -0
  276. package/dist/application/ui/observed-ui-snapshot-provider.d.ts +15 -0
  277. package/dist/application/ui/observed-ui-snapshot-provider.js +66 -0
  278. package/dist/application/ui/ui-snapshot-lifecycle.d.ts +2 -0
  279. package/dist/application/ui/ui-snapshot-lifecycle.js +8 -0
  280. package/dist/application/ui/ui-stability-probe.d.ts +9 -0
  281. package/dist/application/ui/ui-stability-probe.js +24 -0
  282. package/dist/application/wait/idle-advice.d.ts +6 -0
  283. package/dist/application/wait/idle-advice.js +27 -0
  284. package/dist/application/wait/idle-profiles.d.ts +15 -0
  285. package/dist/application/wait/idle-profiles.js +75 -0
  286. package/dist/application/wait/idle-waiter.d.ts +28 -4
  287. package/dist/application/wait/idle-waiter.js +201 -15
  288. package/dist/cli/commands/align.d.ts +3 -0
  289. package/dist/cli/commands/align.js +124 -0
  290. package/dist/cli/commands/baseline.d.ts +3 -0
  291. package/dist/cli/commands/baseline.js +130 -0
  292. package/dist/cli/commands/context.js +300 -27
  293. package/dist/cli/commands/contract.d.ts +3 -0
  294. package/dist/cli/commands/contract.js +120 -0
  295. package/dist/cli/commands/diagnose.d.ts +3 -0
  296. package/dist/cli/commands/diagnose.js +84 -0
  297. package/dist/cli/commands/doctor.js +16 -9
  298. package/dist/cli/commands/failure.d.ts +3 -0
  299. package/dist/cli/commands/failure.js +63 -0
  300. package/dist/cli/commands/generation/lifecycle-commands.d.ts +5 -0
  301. package/dist/cli/commands/generation/lifecycle-commands.js +211 -0
  302. package/dist/cli/commands/generation/session-commands.d.ts +11 -0
  303. package/dist/cli/commands/generation/session-commands.js +472 -0
  304. package/dist/cli/commands/generation/shared.d.ts +1414 -0
  305. package/dist/cli/commands/generation/shared.js +291 -0
  306. package/dist/cli/commands/generation/step-commands.d.ts +8 -0
  307. package/dist/cli/commands/generation/step-commands.js +315 -0
  308. package/dist/cli/commands/generation.js +10 -507
  309. package/dist/cli/commands/impact.d.ts +3 -0
  310. package/dist/cli/commands/impact.js +87 -0
  311. package/dist/cli/commands/init.js +13 -6
  312. package/dist/cli/commands/journey.d.ts +3 -0
  313. package/dist/cli/commands/journey.js +382 -0
  314. package/dist/cli/commands/knowledge.d.ts +3 -0
  315. package/dist/cli/commands/knowledge.js +93 -0
  316. package/dist/cli/commands/observe.d.ts +3 -0
  317. package/dist/cli/commands/observe.js +127 -0
  318. package/dist/cli/commands/project.js +2 -1
  319. package/dist/cli/commands/record.js +18 -5
  320. package/dist/cli/commands/verify.js +326 -64
  321. package/dist/cli/dependencies.d.ts +136 -1
  322. package/dist/cli/dependencies.js +504 -52
  323. package/dist/cli/diagnostics-recorder.d.ts +31 -0
  324. package/dist/cli/diagnostics-recorder.js +82 -0
  325. package/dist/cli/diff-verification.d.ts +41 -0
  326. package/dist/cli/diff-verification.js +192 -0
  327. package/dist/cli/main.d.ts +1 -1
  328. package/dist/cli/main.js +107 -2
  329. package/dist/cli/output.d.ts +5 -3
  330. package/dist/cli/output.js +12 -0
  331. package/dist/cli/program.js +21 -1
  332. package/dist/cli/project-root.d.ts +1 -0
  333. package/dist/cli/project-root.js +6 -0
  334. package/dist/cli/runtime-selection.d.ts +22 -0
  335. package/dist/cli/runtime-selection.js +92 -0
  336. package/dist/cli/version.d.ts +1 -0
  337. package/dist/cli/version.js +14 -0
  338. package/dist/cli/workspace-guard.d.ts +2 -0
  339. package/dist/cli/workspace-guard.js +3 -0
  340. package/dist/domain/binding-reference.d.ts +4 -0
  341. package/dist/domain/binding-reference.js +23 -0
  342. package/dist/domain/checkpoint.d.ts +404 -0
  343. package/dist/domain/checkpoint.js +297 -0
  344. package/dist/domain/config.d.ts +107 -1
  345. package/dist/domain/config.js +52 -6
  346. package/dist/domain/contract.d.ts +413 -0
  347. package/dist/domain/contract.js +196 -0
  348. package/dist/domain/diagnostics.d.ts +1396 -0
  349. package/dist/domain/diagnostics.js +193 -0
  350. package/dist/domain/external-flow.d.ts +493 -0
  351. package/dist/domain/external-flow.js +15 -0
  352. package/dist/domain/failure-classification.d.ts +176 -0
  353. package/dist/domain/failure-classification.js +169 -0
  354. package/dist/domain/failure.d.ts +2 -1
  355. package/dist/domain/failure.js +123 -0
  356. package/dist/domain/generation.d.ts +1671 -132
  357. package/dist/domain/generation.js +202 -13
  358. package/dist/domain/geometry.d.ts +13 -0
  359. package/dist/domain/geometry.js +16 -0
  360. package/dist/domain/impact.d.ts +71 -0
  361. package/dist/domain/impact.js +76 -0
  362. package/dist/domain/init.d.ts +12 -4
  363. package/dist/domain/init.js +24 -18
  364. package/dist/domain/journey-composition.d.ts +2285 -0
  365. package/dist/domain/journey-composition.js +85 -0
  366. package/dist/domain/journey-lifecycle.d.ts +10 -0
  367. package/dist/domain/journey-lifecycle.js +9 -0
  368. package/dist/domain/journey.d.ts +2419 -212
  369. package/dist/domain/journey.js +384 -14
  370. package/dist/domain/knowledge.d.ts +165 -0
  371. package/dist/domain/knowledge.js +188 -0
  372. package/dist/domain/layout.d.ts +34 -5
  373. package/dist/domain/layout.js +64 -6
  374. package/dist/domain/locator-evidence.d.ts +3 -0
  375. package/dist/domain/locator-evidence.js +36 -0
  376. package/dist/domain/logcat-event.d.ts +54 -0
  377. package/dist/domain/logcat-event.js +40 -0
  378. package/dist/domain/observation.d.ts +33 -0
  379. package/dist/domain/observation.js +18 -0
  380. package/dist/domain/project-context.d.ts +219 -1
  381. package/dist/domain/project-context.js +209 -6
  382. package/dist/domain/proposed-step.d.ts +340 -60
  383. package/dist/domain/proposed-step.js +23 -3
  384. package/dist/domain/report.d.ts +1455 -28
  385. package/dist/domain/report.js +211 -14
  386. package/dist/domain/runtime-snapshot.d.ts +58 -1
  387. package/dist/domain/runtime-snapshot.js +14 -3
  388. package/dist/domain/runtime.d.ts +50 -0
  389. package/dist/domain/runtime.js +36 -0
  390. package/dist/domain/system-app-profiles.d.ts +6 -0
  391. package/dist/domain/system-app-profiles.js +43 -0
  392. package/dist/domain/ui-backend.d.ts +28 -0
  393. package/dist/domain/ui-backend.js +33 -0
  394. package/dist/domain/ui-cache.d.ts +11 -0
  395. package/dist/domain/ui-cache.js +10 -0
  396. package/dist/domain/verify-receipt.d.ts +17 -0
  397. package/dist/domain/verify-receipt.js +17 -0
  398. package/dist/domain/window-hierarchy.d.ts +84 -0
  399. package/dist/domain/window-hierarchy.js +172 -0
  400. package/dist/domain/workflow-manifest.d.ts +77 -0
  401. package/dist/domain/workflow-manifest.js +136 -0
  402. package/dist/domain/workspace.d.ts +30 -0
  403. package/dist/domain/workspace.js +74 -0
  404. package/dist/ports/adb.d.ts +34 -1
  405. package/dist/ports/align-prompt.d.ts +11 -0
  406. package/dist/ports/align-prompt.js +6 -0
  407. package/dist/ports/anchor-resolver.d.ts +30 -0
  408. package/dist/ports/anchor-resolver.js +1 -0
  409. package/dist/ports/annotated-screen-resolver.d.ts +4 -0
  410. package/dist/ports/annotated-screen-resolver.js +1 -0
  411. package/dist/ports/camera-probe.d.ts +23 -0
  412. package/dist/ports/camera-probe.js +8 -0
  413. package/dist/ports/context-document-writer.d.ts +24 -0
  414. package/dist/ports/context-document-writer.js +1 -0
  415. package/dist/ports/detached-process-launcher.d.ts +12 -0
  416. package/dist/ports/detached-process-launcher.js +1 -0
  417. package/dist/ports/diagnostics.d.ts +15 -0
  418. package/dist/ports/diagnostics.js +1 -0
  419. package/dist/ports/external-flow-registry.d.ts +37 -0
  420. package/dist/ports/external-flow-registry.js +1 -0
  421. package/dist/ports/generation-prompt.d.ts +2 -1
  422. package/dist/ports/generation-session-store.d.ts +12 -2
  423. package/dist/ports/git-diff.d.ts +9 -0
  424. package/dist/ports/git-diff.js +1 -0
  425. package/dist/ports/journey-composition-store.d.ts +17 -0
  426. package/dist/ports/journey-composition-store.js +1 -0
  427. package/dist/ports/knowledge-registry.d.ts +20 -0
  428. package/dist/ports/knowledge-registry.js +1 -0
  429. package/dist/ports/process-runner.d.ts +7 -0
  430. package/dist/ports/project-file-inspector.d.ts +1 -0
  431. package/dist/ports/project-identity-inspector.d.ts +24 -0
  432. package/dist/ports/project-identity-inspector.js +1 -0
  433. package/dist/ports/project-inventory-inspector.d.ts +21 -0
  434. package/dist/ports/project-inventory-inspector.js +1 -0
  435. package/dist/ports/project-module-discoverer.d.ts +26 -0
  436. package/dist/ports/project-module-discoverer.js +1 -0
  437. package/dist/ports/recorder-prompt.d.ts +10 -1
  438. package/dist/ports/runtime-backend.d.ts +89 -0
  439. package/dist/ports/runtime-backend.js +13 -0
  440. package/dist/ports/runtime-capability.d.ts +12 -0
  441. package/dist/ports/runtime-capability.js +20 -0
  442. package/dist/ports/runtime-session-ports.d.ts +25 -0
  443. package/dist/ports/runtime-session-ports.js +1 -0
  444. package/dist/ports/screenshot.d.ts +11 -0
  445. package/dist/ports/screenshot.js +1 -0
  446. package/dist/ports/skill-installer.d.ts +2 -2
  447. package/dist/ports/ui-snapshot.d.ts +35 -0
  448. package/dist/ports/ui-snapshot.js +1 -0
  449. package/dist/ports/ui-stability.d.ts +19 -0
  450. package/dist/ports/ui-stability.js +1 -0
  451. package/dist/ports/workspace-layout.d.ts +4 -0
  452. package/dist/ports/workspace-layout.js +1 -0
  453. package/dist/shared/errors.d.ts +2 -0
  454. package/dist/shared/errors.js +22 -0
  455. package/dist/shared/paths.d.ts +2 -0
  456. package/dist/shared/paths.js +24 -0
  457. package/dist/shared/strings.d.ts +2 -0
  458. package/dist/shared/strings.js +16 -0
  459. package/package.json +7 -2
  460. package/assets/skills/taphound-ai-journey/GUIDE.md +0 -862
  461. package/assets/skills/taphound-ai-journey/SKILL.md +0 -377
  462. package/assets/skills/taphound-ai-journey/prompts/analyze-project.md +0 -526
  463. package/assets/skills/taphound-ai-journey/prompts/generate-step.md +0 -109
  464. package/assets/skills/taphound-ai-journey/schemas/project-context.json +0 -81
  465. package/assets/skills/taphound-ai-journey/templates/project-context.example.json +0 -49
  466. package/dist/ports/android-cli.d.ts +0 -21
  467. /package/dist/{ports/android-cli.js → adapters/runtime/mobile-mcp/mobile-mcp-tools.js} +0 -0
@@ -0,0 +1,93 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readdir, realpath, stat } from "node:fs/promises";
3
+ import { basename, relative, resolve } from "node:path";
4
+ import { errnoCode } from "../../shared/errors.js";
5
+ import { isContained } from "../../shared/paths.js";
6
+ import { compareStrings } from "../../shared/strings.js";
7
+ const EXCLUDED_DIRECTORIES = new Set([
8
+ ".git",
9
+ ".gradle",
10
+ ".idea",
11
+ ".taphound",
12
+ "bin",
13
+ "build",
14
+ "out"
15
+ ]);
16
+ function matches(path, categories) {
17
+ const normalized = path.replaceAll("\\", "/");
18
+ return ((categories.has("manifests") && basename(normalized) === "AndroidManifest.xml")
19
+ || (categories.has("sources")
20
+ && (normalized.endsWith(".kt") || normalized.endsWith(".java")))
21
+ || (categories.has("layouts")
22
+ && /\/res\/layout[^/]*\/[^/]+\.xml$/.test(`/${normalized}`))
23
+ || (categories.has("navigation")
24
+ && /\/res\/navigation[^/]*\/[^/]+\.xml$/.test(`/${normalized}`)));
25
+ }
26
+ export class NodeProjectInventoryInspector {
27
+ inspectProjectInventory = async (input) => {
28
+ let root;
29
+ let moduleRoot;
30
+ try {
31
+ root = await realpath(input.projectRoot);
32
+ const rootStats = await stat(root);
33
+ if (!rootStats.isDirectory()) {
34
+ return { status: "rootNotDirectory" };
35
+ }
36
+ moduleRoot = await realpath(resolve(root, input.projectDir));
37
+ const moduleStats = await stat(moduleRoot);
38
+ if (!moduleStats.isDirectory()) {
39
+ return { status: "rootNotDirectory" };
40
+ }
41
+ }
42
+ catch (error) {
43
+ return errnoCode(error) === "ENOENT" || errnoCode(error) === "ENOTDIR"
44
+ ? { status: "rootNotFound" }
45
+ : { status: "rootUnreadable" };
46
+ }
47
+ if (!isContained(root, moduleRoot)) {
48
+ return { status: "escape" };
49
+ }
50
+ const categories = new Set(input.categories);
51
+ const paths = [];
52
+ const pending = [moduleRoot];
53
+ try {
54
+ while (pending.length > 0) {
55
+ const directory = pending.pop();
56
+ if (directory === undefined) {
57
+ continue;
58
+ }
59
+ const entries = await readdir(directory, { withFileTypes: true });
60
+ for (const entry of entries) {
61
+ const path = resolve(directory, entry.name);
62
+ if (entry.isSymbolicLink()) {
63
+ continue;
64
+ }
65
+ if (entry.isDirectory()) {
66
+ if (!EXCLUDED_DIRECTORIES.has(entry.name)) {
67
+ pending.push(path);
68
+ }
69
+ continue;
70
+ }
71
+ if (!entry.isFile()) {
72
+ continue;
73
+ }
74
+ const projectRelative = relative(root, path).replaceAll("\\", "/");
75
+ if (matches(projectRelative, categories)) {
76
+ paths.push(projectRelative);
77
+ }
78
+ }
79
+ }
80
+ }
81
+ catch {
82
+ return { status: "rootUnreadable" };
83
+ }
84
+ paths.sort((left, right) => compareStrings(left, right));
85
+ return {
86
+ status: "inspected",
87
+ paths,
88
+ pathSetSha256: createHash("sha256")
89
+ .update(paths.join("\n"))
90
+ .digest("hex")
91
+ };
92
+ };
93
+ }
@@ -0,0 +1,6 @@
1
+ import type { ProjectModuleDiscovery, ProjectModuleDiscoverer } from "../../ports/project-module-discoverer.js";
2
+ export declare class GradleProjectModuleDiscoverer implements ProjectModuleDiscoverer {
3
+ readonly discoverModules: (input: {
4
+ readonly projectRoot: string;
5
+ }) => Promise<ProjectModuleDiscovery>;
6
+ }
@@ -0,0 +1,158 @@
1
+ import { readFile, realpath, stat } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ import { errnoCode } from "../../shared/errors.js";
4
+ const GRADLE_PATH = "(:[A-Za-z0-9_-]+(?::[A-Za-z0-9_-]+)*)";
5
+ const QUOTED_PATH = new RegExp(`['"]${GRADLE_PATH}['"]`, "g");
6
+ const PAIR_PATTERN = new RegExp(`['"]${GRADLE_PATH}['"]\\s+to\\s+['"]([^'"]+)['"]`, "g");
7
+ const PROJECT_DIR_OVERRIDE = new RegExp(`project\\s*\\(\\s*['"]${GRADLE_PATH}['"]\\s*\\)\\s*\\.\\s*projectDir\\s*=\\s*(?:file|new\\s+File)\\s*\\(\\s*(?:rootDir\\s*,\\s*)?['"]([^'"]+)['"]\\s*\\)`, "g");
8
+ function gradlePathToProjectDir(gradlePath) {
9
+ return gradlePath.replace(/^:/, "").replace(/:/g, "/");
10
+ }
11
+ function stripRootDirPrefix(dir) {
12
+ return dir.replace(/^\$\{?rootDir\}?\/+/, "");
13
+ }
14
+ function parseIncludedModules(content) {
15
+ const moduleMap = new Map();
16
+ QUOTED_PATH.lastIndex = 0;
17
+ let match;
18
+ while ((match = QUOTED_PATH.exec(content)) !== null) {
19
+ const gradlePath = match[1];
20
+ if (gradlePath !== undefined) {
21
+ moduleMap.set(gradlePath, undefined);
22
+ }
23
+ }
24
+ PAIR_PATTERN.lastIndex = 0;
25
+ while ((match = PAIR_PATTERN.exec(content)) !== null) {
26
+ const gradlePath = match[1];
27
+ const dir = match[2];
28
+ if (gradlePath !== undefined && dir !== undefined) {
29
+ moduleMap.set(gradlePath, stripRootDirPrefix(dir));
30
+ }
31
+ }
32
+ PROJECT_DIR_OVERRIDE.lastIndex = 0;
33
+ while ((match = PROJECT_DIR_OVERRIDE.exec(content)) !== null) {
34
+ const gradlePath = match[1];
35
+ const dir = match[2];
36
+ if (gradlePath !== undefined && dir !== undefined) {
37
+ moduleMap.set(gradlePath, stripRootDirPrefix(dir));
38
+ }
39
+ }
40
+ return [...moduleMap.entries()].map(([gradlePath, explicitDir]) => ({
41
+ gradlePath,
42
+ ...(explicitDir === undefined ? {} : { explicitDir })
43
+ }));
44
+ }
45
+ async function findSettingsFile(projectRoot) {
46
+ for (const name of ["settings.gradle.kts", "settings.gradle"]) {
47
+ const candidate = resolve(projectRoot, name);
48
+ try {
49
+ const stats = await stat(candidate);
50
+ if (stats.isFile()) {
51
+ return candidate;
52
+ }
53
+ }
54
+ catch {
55
+ // continue
56
+ }
57
+ }
58
+ return undefined;
59
+ }
60
+ async function readBuildFile(moduleDir) {
61
+ for (const name of ["build.gradle.kts", "build.gradle"]) {
62
+ const candidate = resolve(moduleDir, name);
63
+ try {
64
+ const stats = await stat(candidate);
65
+ if (stats.isFile()) {
66
+ return await readFile(candidate, "utf8");
67
+ }
68
+ }
69
+ catch {
70
+ // continue
71
+ }
72
+ }
73
+ return undefined;
74
+ }
75
+ function detectKind(buildContent) {
76
+ if (/com\.android\.application/.test(buildContent)
77
+ || /id\s*\(\s*["'][^"']*android\.application[^"']*["']\s*\)/.test(buildContent)
78
+ || /apply\s*\(\s*plugin\s*=\s*["'][^"']*android\.application[^"']*["']\s*\)/.test(buildContent)
79
+ || /alias\s*\([^)]*android\.application[^)]*\)/.test(buildContent)) {
80
+ return "application";
81
+ }
82
+ if (/com\.android\.dynamic-feature/.test(buildContent)
83
+ || /id\s*\(\s*["'][^"']*android\.dynamic-feature[^"']*["']\s*\)/.test(buildContent)
84
+ || /alias\s*\([^)]*android\.dynamic-feature[^)]*\)/.test(buildContent)) {
85
+ return "feature";
86
+ }
87
+ return "library";
88
+ }
89
+ function parseDependencies(buildContent) {
90
+ const deps = [];
91
+ const pattern = new RegExp(`project\\s*\\(\\s*['"]${GRADLE_PATH}['"]\\s*\\)`, "g");
92
+ let match;
93
+ while ((match = pattern.exec(buildContent)) !== null) {
94
+ const dep = match[1];
95
+ if (dep !== undefined) {
96
+ deps.push(dep);
97
+ }
98
+ }
99
+ return [...new Set(deps)];
100
+ }
101
+ export class GradleProjectModuleDiscoverer {
102
+ discoverModules = async (input) => {
103
+ let root;
104
+ try {
105
+ root = await realpath(input.projectRoot);
106
+ const rootStats = await stat(root);
107
+ if (!rootStats.isDirectory()) {
108
+ return { status: "rootNotDirectory" };
109
+ }
110
+ }
111
+ catch (error) {
112
+ return errnoCode(error) === "ENOENT"
113
+ ? { status: "rootNotFound" }
114
+ : { status: "discoveryFailed", message: "Project root is not accessible" };
115
+ }
116
+ const settingsPath = await findSettingsFile(root);
117
+ if (settingsPath === undefined) {
118
+ return { status: "noSettingsFile" };
119
+ }
120
+ let settingsContent;
121
+ try {
122
+ settingsContent = await readFile(settingsPath, "utf8");
123
+ }
124
+ catch {
125
+ return { status: "discoveryFailed", message: "settings file is not readable" };
126
+ }
127
+ const includedModules = parseIncludedModules(settingsContent);
128
+ if (includedModules.length === 0) {
129
+ return { status: "noApplicationModule" };
130
+ }
131
+ const modules = [];
132
+ for (const { gradlePath, explicitDir } of includedModules) {
133
+ const projectDir = explicitDir ?? gradlePathToProjectDir(gradlePath);
134
+ const moduleDir = resolve(root, projectDir);
135
+ try {
136
+ const moduleStats = await stat(moduleDir);
137
+ if (!moduleStats.isDirectory()) {
138
+ continue;
139
+ }
140
+ }
141
+ catch {
142
+ continue;
143
+ }
144
+ const buildContent = await readBuildFile(moduleDir);
145
+ const kind = buildContent !== undefined
146
+ ? detectKind(buildContent)
147
+ : "library";
148
+ const dependsOn = buildContent !== undefined
149
+ ? parseDependencies(buildContent)
150
+ : [];
151
+ modules.push({ id: gradlePath, projectDir, kind, dependsOn });
152
+ }
153
+ if (!modules.some((module) => module.kind === "application")) {
154
+ return { status: "noApplicationModule" };
155
+ }
156
+ return { status: "discovered", modules };
157
+ };
158
+ }
@@ -4,7 +4,12 @@ export declare class SkillPayloadMissingError extends Error {
4
4
  readonly name = "SkillPayloadMissingError";
5
5
  constructor(payloadPath: string);
6
6
  }
7
+ export declare class NoSkillsFoundError extends Error {
8
+ readonly scannedDir: string;
9
+ readonly name = "NoSkillsFoundError";
10
+ constructor(scannedDir: string);
11
+ }
7
12
  export declare class FileSystemSkillInstaller implements SkillInstallerPort {
8
- findPayload(): Promise<string>;
9
- installTo(targetDir: string): Promise<SkillInstallResult>;
13
+ listSkillNames(): Promise<readonly string[]>;
14
+ installTo(skillName: string, targetDir: string): Promise<SkillInstallResult>;
10
15
  }
@@ -1,10 +1,9 @@
1
- import { access, cp, mkdir } from "node:fs/promises";
1
+ import { access, cp, mkdir, readdir, stat } from "node:fs/promises";
2
2
  import { constants } from "node:fs";
3
- import { dirname, resolve } from "node:path";
3
+ import { dirname, join, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { SKILL_DIRECTORY_NAME } from "../../domain/init.js";
6
5
  const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
7
- const PAYLOAD_PATH = resolve(PACKAGE_ROOT, "assets", "skills", SKILL_DIRECTORY_NAME);
6
+ const SKILLS_DIR = resolve(PACKAGE_ROOT, "assets", "skills");
8
7
  export class SkillPayloadMissingError extends Error {
9
8
  payloadPath;
10
9
  name = "SkillPayloadMissingError";
@@ -13,18 +12,48 @@ export class SkillPayloadMissingError extends Error {
13
12
  this.payloadPath = payloadPath;
14
13
  }
15
14
  }
15
+ export class NoSkillsFoundError extends Error {
16
+ scannedDir;
17
+ name = "NoSkillsFoundError";
18
+ constructor(scannedDir) {
19
+ super(`No skills (directories with SKILL.md) found under ${scannedDir}`);
20
+ this.scannedDir = scannedDir;
21
+ }
22
+ }
16
23
  export class FileSystemSkillInstaller {
17
- async findPayload() {
24
+ async listSkillNames() {
25
+ const entries = await readdir(SKILLS_DIR, { withFileTypes: true });
26
+ const skillNames = [];
27
+ for (const entry of entries) {
28
+ if (!entry.isDirectory()) {
29
+ continue;
30
+ }
31
+ const skillMd = join(SKILLS_DIR, entry.name, "SKILL.md");
32
+ try {
33
+ await access(skillMd, constants.R_OK);
34
+ skillNames.push(entry.name);
35
+ }
36
+ catch {
37
+ // Not a skill directory (no SKILL.md), skip
38
+ }
39
+ }
40
+ if (skillNames.length === 0) {
41
+ throw new NoSkillsFoundError(SKILLS_DIR);
42
+ }
43
+ return skillNames;
44
+ }
45
+ async installTo(skillName, targetDir) {
46
+ const payloadPath = resolve(SKILLS_DIR, skillName);
18
47
  try {
19
- await access(PAYLOAD_PATH, constants.R_OK);
48
+ await access(payloadPath, constants.R_OK);
49
+ const stats = await stat(payloadPath);
50
+ if (!stats.isDirectory()) {
51
+ throw new SkillPayloadMissingError(payloadPath);
52
+ }
20
53
  }
21
54
  catch {
22
- throw new SkillPayloadMissingError(PAYLOAD_PATH);
55
+ throw new SkillPayloadMissingError(payloadPath);
23
56
  }
24
- return PAYLOAD_PATH;
25
- }
26
- async installTo(targetDir) {
27
- const payloadPath = await this.findPayload();
28
57
  const resolvedTarget = resolve(targetDir);
29
58
  if (resolvedTarget === resolve(payloadPath)) {
30
59
  return { targetPath: resolvedTarget, skipped: true };
@@ -0,0 +1,7 @@
1
+ import type { WorkspaceLayoutPort } from "../../ports/workspace-layout.js";
2
+ export declare function ensureBuildIgnored(projectRoot: string): Promise<void>;
3
+ export declare function ensureBuildLayout(projectRoot: string): Promise<void>;
4
+ export declare class FileSystemWorkspaceLayout implements WorkspaceLayoutPort {
5
+ readonly ensureBuildIgnored: (projectRoot: string) => Promise<void>;
6
+ readonly ensureBuildLayout: (projectRoot: string) => Promise<void>;
7
+ }
@@ -0,0 +1,57 @@
1
+ import { constants, lstat, mkdir, open, realpath } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { BUILD_DIR, BUILD_IGNORE_CONTENT, BUILD_IGNORE_FILE, TAPHOUND_DIR } from "../../domain/workspace.js";
4
+ import { isErrnoException } from "../../shared/errors.js";
5
+ export async function ensureBuildIgnored(projectRoot) {
6
+ let handle;
7
+ try {
8
+ handle = await open(join(projectRoot, BUILD_IGNORE_FILE), constants.O_WRONLY
9
+ | constants.O_CREAT
10
+ | constants.O_EXCL
11
+ | constants.O_NOFOLLOW, 0o644);
12
+ await handle.writeFile(BUILD_IGNORE_CONTENT, "utf8");
13
+ await handle.sync();
14
+ }
15
+ catch (error) {
16
+ // A file the developer already owns is never rewritten.
17
+ if (!isErrnoException(error) || error.code !== "EEXIST") {
18
+ throw error;
19
+ }
20
+ }
21
+ finally {
22
+ await handle?.close();
23
+ }
24
+ }
25
+ async function createOrRequireDirectory(path) {
26
+ try {
27
+ await mkdir(path);
28
+ }
29
+ catch (error) {
30
+ if (!isErrnoException(error) || error.code !== "EEXIST") {
31
+ throw error;
32
+ }
33
+ }
34
+ const stats = await lstat(path);
35
+ if (!stats.isDirectory() || stats.isSymbolicLink()) {
36
+ throw new Error(`Workspace path is not a safe directory: ${path}`);
37
+ }
38
+ }
39
+ export async function ensureBuildLayout(projectRoot) {
40
+ const canonicalRoot = await realpath(projectRoot);
41
+ const rootStats = await lstat(canonicalRoot);
42
+ if (!rootStats.isDirectory() || rootStats.isSymbolicLink()) {
43
+ throw new Error(`Project root is not a safe directory: ${projectRoot}`);
44
+ }
45
+ const taphoundDirectory = join(canonicalRoot, TAPHOUND_DIR);
46
+ await createOrRequireDirectory(taphoundDirectory);
47
+ await createOrRequireDirectory(join(canonicalRoot, BUILD_DIR));
48
+ await ensureBuildIgnored(canonicalRoot);
49
+ }
50
+ export class FileSystemWorkspaceLayout {
51
+ ensureBuildIgnored = async (projectRoot) => {
52
+ await ensureBuildIgnored(projectRoot);
53
+ };
54
+ ensureBuildLayout = async (projectRoot) => {
55
+ await ensureBuildLayout(projectRoot);
56
+ };
57
+ }
@@ -0,0 +1,17 @@
1
+ import { type ChangeSet } from "../../domain/impact.js";
2
+ import type { GitDiffPort } from "../../ports/git-diff.js";
3
+ import type { ProcessRunner } from "../../ports/process-runner.js";
4
+ export declare class NodeGitDiff implements GitDiffPort {
5
+ private readonly runner;
6
+ constructor(runner: ProcessRunner);
7
+ diff(input: {
8
+ projectRoot: string;
9
+ base: string;
10
+ head: string;
11
+ signal?: AbortSignal | undefined;
12
+ }): Promise<ChangeSet>;
13
+ private readonly assertGitRoot;
14
+ private readonly assertRef;
15
+ private readonly runGit;
16
+ private readonly parse;
17
+ }
@@ -0,0 +1,114 @@
1
+ import { ChangeSetSchema } from "../../domain/impact.js";
2
+ const NAME_STATUS_RE = /^(\w+)\t(.*)$/;
3
+ function parseStatus(token) {
4
+ if (token.startsWith("A"))
5
+ return "added";
6
+ if (token.startsWith("D"))
7
+ return "deleted";
8
+ if (token.startsWith("R"))
9
+ return "renamed";
10
+ if (token.startsWith("C"))
11
+ return "added";
12
+ return "modified";
13
+ }
14
+ class GitDiffError extends Error {
15
+ code;
16
+ constructor(code, message) {
17
+ super(message);
18
+ this.name = "GitDiffError";
19
+ this.code = code;
20
+ }
21
+ }
22
+ function failed(result) {
23
+ if (result.exitCode !== 0 || result.timedOut || result.cancelled) {
24
+ return result.stderr.trim()
25
+ || result.spawnError
26
+ || "git command failed";
27
+ }
28
+ return undefined;
29
+ }
30
+ export class NodeGitDiff {
31
+ runner;
32
+ constructor(runner) {
33
+ this.runner = runner;
34
+ }
35
+ async diff(input) {
36
+ const root = input.projectRoot;
37
+ const signal = input.signal;
38
+ await this.assertGitRoot(root, signal);
39
+ await this.assertRef(root, input.base, signal);
40
+ const head = input.head;
41
+ if (head !== "WORKTREE") {
42
+ await this.assertRef(root, head, signal);
43
+ }
44
+ const diffArg = head === "WORKTREE"
45
+ ? input.base
46
+ : `${input.base}...${head}`;
47
+ const result = await this.runGit(root, ["diff", "--name-status", "-M", "--no-ext-diff", diffArg], signal);
48
+ const message = failed(result);
49
+ if (message !== undefined) {
50
+ throw new Error(message);
51
+ }
52
+ const files = this.parse(result.stdout);
53
+ if (files.length === 0) {
54
+ return { version: 1, base: input.base, head, files };
55
+ }
56
+ return ChangeSetSchema.parse({
57
+ version: 1,
58
+ base: input.base,
59
+ head,
60
+ files
61
+ });
62
+ }
63
+ assertGitRoot = async (root, signal) => {
64
+ const result = await this.runGit(root, ["rev-parse", "--is-inside-work-tree"], signal);
65
+ const message = failed(result);
66
+ if (message !== undefined) {
67
+ throw new GitDiffError("GIT_ROOT_NOT_FOUND", `${root} is not inside a Git work tree`);
68
+ }
69
+ };
70
+ assertRef = async (root, ref, signal) => {
71
+ const result = await this.runGit(root, ["rev-parse", "--verify", "--quiet", `${ref}^{commit}`], signal);
72
+ const message = failed(result);
73
+ if (message !== undefined) {
74
+ throw new GitDiffError("GIT_REF_INVALID", `${ref} is not a valid Git ref`);
75
+ }
76
+ };
77
+ runGit = (root, args, signal) => this.runner.run({
78
+ executable: "git",
79
+ args: ["-C", root, ...args],
80
+ ...(signal === undefined ? {} : { signal })
81
+ });
82
+ parse = (stdout) => {
83
+ const files = [];
84
+ for (const line of stdout.split("\n")) {
85
+ if (line === "")
86
+ continue;
87
+ const match = NAME_STATUS_RE.exec(line);
88
+ if (match === null) {
89
+ throw new Error(`git diff returned an unparsable line: ${line}`);
90
+ }
91
+ const statusToken = match[1];
92
+ const rest = match[2];
93
+ if (statusToken === undefined || rest === undefined) {
94
+ throw new Error(`git diff returned a malformed line: ${line}`);
95
+ }
96
+ const status = parseStatus(statusToken);
97
+ if (status === "renamed") {
98
+ const separator = rest.indexOf("\t");
99
+ if (separator === -1) {
100
+ throw new Error(`git diff returned a malformed rename line: ${line}`);
101
+ }
102
+ files.push({
103
+ path: rest.slice(separator + 1),
104
+ oldPath: rest.slice(0, separator),
105
+ status
106
+ });
107
+ }
108
+ else {
109
+ files.push({ path: rest, status });
110
+ }
111
+ }
112
+ return files;
113
+ };
114
+ }
@@ -0,0 +1,6 @@
1
+ import type { DetachedProcessInput, DetachedProcessLauncher } from "../../ports/detached-process-launcher.js";
2
+ export declare class NodeDetachedProcessLauncher implements DetachedProcessLauncher {
3
+ readonly launch: (input: DetachedProcessInput) => Promise<{
4
+ pid: number;
5
+ }>;
6
+ }
@@ -0,0 +1,96 @@
1
+ import { spawn } from "node:child_process";
2
+ import { mkdir, open } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
+ const DETACHED_SUPERVISOR = String.raw `
5
+ const { spawn } = require("node:child_process");
6
+ const { open, stat, writeFile } = require("node:fs/promises");
7
+ const input = JSON.parse(process.argv[1]);
8
+ const failure = async (exitCode, signal, message) => {
9
+ const output = {
10
+ status: "error",
11
+ exitCode: typeof exitCode === "number" && exitCode > 0 ? exitCode : 1,
12
+ failure: {
13
+ code: "DETACHED_PROCESS_CRASHED",
14
+ message,
15
+ details: { exitCode, signal }
16
+ }
17
+ };
18
+ const info = await stat(input.stdoutPath).catch(() => ({ size: 0 }));
19
+ if (info.size === 0) {
20
+ await writeFile(input.stdoutPath, JSON.stringify(output) + "\n", "utf8");
21
+ }
22
+ };
23
+ (async () => {
24
+ const stdout = await open(input.stdoutPath, "r+");
25
+ const stderr = await open(input.stderrPath, "r+");
26
+ let child;
27
+ try {
28
+ child = spawn(input.executable, input.args, {
29
+ cwd: input.cwd,
30
+ shell: false,
31
+ stdio: ["ignore", stdout.fd, stderr.fd]
32
+ });
33
+ const result = await new Promise((resolve) => {
34
+ child.once("error", (error) => resolve({ error }));
35
+ child.once("close", (exitCode, signal) => resolve({ exitCode, signal }));
36
+ });
37
+ if (result.error !== undefined) {
38
+ await failure(null, null, "Detached process failed to start: " + result.error.message);
39
+ } else if (result.exitCode !== 0 || result.signal !== null) {
40
+ await failure(
41
+ result.exitCode,
42
+ result.signal,
43
+ result.signal === null
44
+ ? "Detached process exited before producing a result"
45
+ : "Detached process terminated by " + result.signal
46
+ );
47
+ }
48
+ } finally {
49
+ await Promise.all([stdout.close(), stderr.close()]);
50
+ }
51
+ })().catch(async (error) => {
52
+ await failure(null, null, "Detached supervisor failed: " + (
53
+ error instanceof Error ? error.message : String(error)
54
+ ));
55
+ });
56
+ `;
57
+ export class NodeDetachedProcessLauncher {
58
+ launch = async (input) => {
59
+ await Promise.all([
60
+ mkdir(dirname(input.stdoutPath), { recursive: true }),
61
+ mkdir(dirname(input.stderrPath), { recursive: true })
62
+ ]);
63
+ const stdout = await open(input.stdoutPath, "wx");
64
+ let stderr;
65
+ try {
66
+ stderr = await open(input.stderrPath, "wx");
67
+ await Promise.all([stdout.close(), stderr.close()]);
68
+ stderr = undefined;
69
+ const child = spawn(process.execPath, [
70
+ "-e",
71
+ DETACHED_SUPERVISOR,
72
+ JSON.stringify(input)
73
+ ], {
74
+ cwd: input.cwd,
75
+ detached: true,
76
+ shell: false,
77
+ stdio: "ignore"
78
+ });
79
+ await new Promise((resolve, reject) => {
80
+ child.once("spawn", resolve);
81
+ child.once("error", reject);
82
+ });
83
+ if (child.pid === undefined) {
84
+ throw new Error("Detached generation process did not expose a PID");
85
+ }
86
+ child.unref();
87
+ return { pid: child.pid };
88
+ }
89
+ finally {
90
+ await Promise.all([
91
+ stdout.close().catch(() => undefined),
92
+ ...(stderr === undefined ? [] : [stderr.close()])
93
+ ]);
94
+ }
95
+ };
96
+ }