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,223 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { lstat, readdir, readFile, realpath, rename, unlink, writeFile } from "node:fs/promises";
3
+ import { basename, dirname, join, relative, resolve } from "node:path";
4
+ import { AnchorDefinitionSchema, KnowledgeBundleIndexSchema, ScreenDefinitionSchema, hashKnowledge } from "../../domain/knowledge.js";
5
+ import { KNOWLEDGE_DIR, KNOWLEDGE_INDEX_PATH } from "../../domain/workspace.js";
6
+ import { isErrnoException } from "../../shared/errors.js";
7
+ import { isContained } from "../../shared/paths.js";
8
+ const MAX_KNOWLEDGE_FILE_BYTES = 1024 * 1024;
9
+ function sha256(bytes) {
10
+ return createHash("sha256").update(bytes).digest("hex");
11
+ }
12
+ function serializedDocument(value) {
13
+ return `${JSON.stringify(value, null, 2)}\n`;
14
+ }
15
+ async function readBoundedJson(path) {
16
+ const before = await lstat(path);
17
+ if (!before.isFile() || before.isSymbolicLink()) {
18
+ throw new Error(`Knowledge document is not a regular file: ${path}`);
19
+ }
20
+ if (before.size > MAX_KNOWLEDGE_FILE_BYTES) {
21
+ throw new Error(`Knowledge document exceeds the size limit: ${path}`);
22
+ }
23
+ const bytes = await readFile(path);
24
+ const after = await lstat(path);
25
+ if (before.dev !== after.dev
26
+ || before.ino !== after.ino
27
+ || before.size !== after.size
28
+ || after.isSymbolicLink()) {
29
+ throw new Error(`Knowledge document changed while loading: ${path}`);
30
+ }
31
+ let value;
32
+ try {
33
+ value = JSON.parse(bytes.toString("utf8"));
34
+ }
35
+ catch (error) {
36
+ throw new Error(`Knowledge document is not valid JSON: ${path}`, {
37
+ cause: error
38
+ });
39
+ }
40
+ return { bytes, value };
41
+ }
42
+ async function atomicWrite(path, value) {
43
+ const temporary = join(dirname(path), `.${basename(path)}.${randomUUID()}.tmp`);
44
+ await writeFile(temporary, serializedDocument(value), {
45
+ encoding: "utf8",
46
+ mode: 0o644,
47
+ flag: "wx"
48
+ });
49
+ try {
50
+ await rename(temporary, path);
51
+ }
52
+ finally {
53
+ await unlink(temporary).catch(() => undefined);
54
+ }
55
+ }
56
+ function expectedPath(kind, id) {
57
+ return `${KNOWLEDGE_DIR}/${kind}/${id}.json`;
58
+ }
59
+ function assertReferencePath(kind, id, path) {
60
+ if (path !== expectedPath(kind, id)) {
61
+ throw new Error(`Knowledge reference ${id} must use ${expectedPath(kind, id)}`);
62
+ }
63
+ }
64
+ function assertBundleReferences(input) {
65
+ const anchorsById = new Map(input.anchors.map((anchor) => [anchor.id, anchor]));
66
+ for (const screen of input.screens) {
67
+ const references = [
68
+ ...screen.requiredAnchors,
69
+ ...screen.optionalAnchors,
70
+ ...screen.forbiddenAnchors,
71
+ ...screen.predicates
72
+ .filter((predicate) => predicate.kind !== "activityIs")
73
+ .map((predicate) => predicate.anchorId)
74
+ ];
75
+ for (const anchorId of references) {
76
+ if (!anchorsById.has(anchorId)) {
77
+ throw new Error(`Screen ${screen.id} references unknown Anchor ${anchorId}`);
78
+ }
79
+ }
80
+ for (const predicate of screen.predicates) {
81
+ if (predicate.kind === "windowPresent"
82
+ && anchorsById.get(predicate.anchorId)?.identity.kind !== "window") {
83
+ throw new Error(`Screen ${screen.id} window predicate requires a window Anchor`);
84
+ }
85
+ }
86
+ }
87
+ }
88
+ function bundleHash(indexSha256, index) {
89
+ return hashKnowledge({
90
+ indexSha256,
91
+ anchors: index.anchors.map(({ id, sha256: hash }) => ({ id, sha256: hash })),
92
+ screens: index.screens.map(({ id, sha256: hash }) => ({ id, sha256: hash }))
93
+ });
94
+ }
95
+ function sameReferences(left, right) {
96
+ return JSON.stringify(left) === JSON.stringify(right);
97
+ }
98
+ /**
99
+ * Knowledge is a committed, hand- or agent-authored library of semantic
100
+ * Anchors and Screens. `index.json` binds every document by content hash;
101
+ * `rehash` is the only writer and rebuilds the index from the documents.
102
+ */
103
+ export class FileSystemKnowledgeRegistry {
104
+ load = async (projectRoot) => {
105
+ const canonicalProject = await realpath(projectRoot);
106
+ const indexPath = resolve(canonicalProject, KNOWLEDGE_INDEX_PATH);
107
+ if (!isContained(canonicalProject, indexPath)) {
108
+ throw new Error("Knowledge index escapes the project root");
109
+ }
110
+ const loadedIndex = await readBoundedJson(indexPath);
111
+ const index = KnowledgeBundleIndexSchema.parse(loadedIndex.value);
112
+ const anchors = await this.readDocuments(canonicalProject, "anchors", index.anchors, AnchorDefinitionSchema);
113
+ const screens = await this.readDocuments(canonicalProject, "screens", index.screens, ScreenDefinitionSchema);
114
+ assertBundleReferences({ anchors, screens });
115
+ const indexSha256 = sha256(loadedIndex.bytes);
116
+ return {
117
+ index,
118
+ indexSha256,
119
+ knowledgeHash: bundleHash(indexSha256, index),
120
+ anchors,
121
+ screens
122
+ };
123
+ };
124
+ rehash = async (projectRoot, packageName) => {
125
+ const canonicalProject = await realpath(projectRoot);
126
+ const indexPath = resolve(canonicalProject, KNOWLEDGE_INDEX_PATH);
127
+ let existing;
128
+ try {
129
+ existing = KnowledgeBundleIndexSchema.parse((await readBoundedJson(indexPath)).value);
130
+ }
131
+ catch (error) {
132
+ if (!isErrnoException(error) || error.code !== "ENOENT")
133
+ throw error;
134
+ }
135
+ if (existing !== undefined && existing.packageName !== packageName) {
136
+ throw new Error(`Knowledge package ${existing.packageName} does not match configured package ${packageName}`);
137
+ }
138
+ const anchors = await this.scanDocuments(canonicalProject, "anchors", AnchorDefinitionSchema);
139
+ const screens = await this.scanDocuments(canonicalProject, "screens", ScreenDefinitionSchema);
140
+ assertBundleReferences({
141
+ anchors: anchors.map(({ document }) => document),
142
+ screens: screens.map(({ document }) => document)
143
+ });
144
+ const references = {
145
+ anchors: anchors.map(({ reference }) => reference),
146
+ screens: screens.map(({ reference }) => reference)
147
+ };
148
+ const changed = existing === undefined
149
+ || !sameReferences(existing.anchors, references.anchors)
150
+ || !sameReferences(existing.screens, references.screens);
151
+ const index = KnowledgeBundleIndexSchema.parse({
152
+ version: 1,
153
+ packageName,
154
+ revision: existing === undefined
155
+ ? 1
156
+ : existing.revision + (changed ? 1 : 0),
157
+ ...references
158
+ });
159
+ if (changed) {
160
+ await atomicWrite(indexPath, index);
161
+ }
162
+ const indexSha256 = sha256(await readFile(indexPath));
163
+ return {
164
+ indexPath: relative(canonicalProject, indexPath).replaceAll("\\", "/"),
165
+ knowledgeHash: bundleHash(indexSha256, index),
166
+ revision: index.revision,
167
+ changed,
168
+ anchors: index.anchors.length,
169
+ screens: index.screens.length
170
+ };
171
+ };
172
+ async scanDocuments(canonicalProject, kind, schema) {
173
+ const directory = resolve(canonicalProject, KNOWLEDGE_DIR, kind);
174
+ let names;
175
+ try {
176
+ names = await readdir(directory);
177
+ }
178
+ catch (error) {
179
+ if (isErrnoException(error) && error.code === "ENOENT")
180
+ return [];
181
+ throw error;
182
+ }
183
+ const scanned = [];
184
+ for (const name of names.filter((entry) => entry.endsWith(".json")).sort()) {
185
+ const loaded = await readBoundedJson(join(directory, name));
186
+ const document = schema.parse(loaded.value);
187
+ if (`${document.id}.json` !== name) {
188
+ throw new Error(`Knowledge document ${KNOWLEDGE_DIR}/${kind}/${name} must be named ${document.id}.json`);
189
+ }
190
+ scanned.push({
191
+ document,
192
+ reference: {
193
+ id: document.id,
194
+ path: expectedPath(kind, document.id),
195
+ sha256: sha256(loaded.bytes),
196
+ status: document.status
197
+ }
198
+ });
199
+ }
200
+ return scanned;
201
+ }
202
+ async readDocuments(canonicalProject, kind, references, schema) {
203
+ const documents = [];
204
+ for (const reference of references) {
205
+ assertReferencePath(kind, reference.id, reference.path);
206
+ const path = resolve(canonicalProject, reference.path);
207
+ if (!isContained(canonicalProject, path)) {
208
+ throw new Error(`Knowledge reference escapes the project: ${reference.id}`);
209
+ }
210
+ const loaded = await readBoundedJson(path);
211
+ if (sha256(loaded.bytes) !== reference.sha256) {
212
+ throw new Error(`Knowledge document is stale: ${reference.path} (run taphound knowledge rehash)`);
213
+ }
214
+ const document = schema.parse(loaded.value);
215
+ if (document.id !== reference.id
216
+ || document.status !== reference.status) {
217
+ throw new Error(`Knowledge reference identity mismatch: ${reference.path}`);
218
+ }
219
+ documents.push(document);
220
+ }
221
+ return documents;
222
+ }
223
+ }
@@ -1,12 +1,10 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { constants, lstat, mkdir, open, realpath, rename, unlink } from "node:fs/promises";
3
3
  import { basename, isAbsolute, relative, resolve, sep } from "node:path";
4
+ import { isErrnoException } from "../../shared/errors.js";
4
5
  function sameIdentity(left, right) {
5
6
  return left.dev === right.dev && left.ino === right.ino;
6
7
  }
7
- function isNodeError(error) {
8
- return error instanceof Error && "code" in error;
9
- }
10
8
  async function captureDirectory(path) {
11
9
  const stats = await lstat(path, { bigint: true });
12
10
  if (!stats.isDirectory() || stats.isSymbolicLink()) {
@@ -31,7 +29,7 @@ async function captureOptionalFile(path) {
31
29
  return await captureFile(path);
32
30
  }
33
31
  catch (error) {
34
- if (isNodeError(error) && error.code === "ENOENT") {
32
+ if (isErrnoException(error) && error.code === "ENOENT") {
35
33
  return null;
36
34
  }
37
35
  throw error;
@@ -129,7 +127,7 @@ async function prepareBoundOutput(input, createParents) {
129
127
  }
130
128
  catch (error) {
131
129
  if (!createParents
132
- || !isNodeError(error)
130
+ || !isErrnoException(error)
133
131
  || error.code !== "ENOENT") {
134
132
  throw error;
135
133
  }
@@ -1,5 +1,7 @@
1
1
  import { type ProjectFileInspection, type ProjectFileInspector } from "../../ports/project-file-inspector.js";
2
2
  export declare class NodeProjectFileInspector implements ProjectFileInspector {
3
+ private rootCache;
4
+ private readonly resolveRoot;
3
5
  readonly inspectProjectFile: (input: {
4
6
  projectRoot: string;
5
7
  relativePath: string;
@@ -1,22 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { constants } from "node:fs";
3
3
  import { open, realpath, stat } from "node:fs/promises";
4
- import { isAbsolute, relative, resolve, sep } from "node:path";
5
- function errorCode(error) {
6
- return (typeof error === "object"
7
- && error !== null
8
- && "code" in error
9
- && typeof error.code === "string")
10
- ? error.code
11
- : undefined;
12
- }
13
- function isContained(root, path) {
14
- const fromRoot = relative(root, path);
15
- return (fromRoot === ""
16
- || (fromRoot !== ".."
17
- && !fromRoot.startsWith(`..${sep}`)
18
- && !isAbsolute(fromRoot)));
19
- }
4
+ import { relative, resolve } from "node:path";
5
+ import { errnoCode } from "../../shared/errors.js";
6
+ import { isContained } from "../../shared/paths.js";
20
7
  function sameIdentity(left, right) {
21
8
  return (left.dev === right.dev
22
9
  && left.ino === right.ino
@@ -48,40 +35,78 @@ function normalizedRelativePath(root, path) {
48
35
  return relative(root, path).replaceAll("\\", "/");
49
36
  }
50
37
  function evidenceFailure(error) {
51
- return errorCode(error) === "ENOENT" || errorCode(error) === "ENOTDIR"
38
+ return errnoCode(error) === "ENOENT" || errnoCode(error) === "ENOTDIR"
52
39
  ? { status: "notFound" }
53
40
  : { status: "unreadable" };
54
41
  }
55
42
  export class NodeProjectFileInspector {
56
- inspectProjectFile = async (input) => {
57
- let canonicalRoot;
43
+ rootCache;
44
+ resolveRoot = async (projectRoot) => {
45
+ const cached = this.rootCache?.get(projectRoot);
46
+ if (cached !== undefined) {
47
+ try {
48
+ const stats = await stat(cached.canonical, { bigint: true });
49
+ if (stats.isDirectory()) {
50
+ const identity = {
51
+ dev: stats.dev,
52
+ ino: stats.ino,
53
+ size: stats.size,
54
+ mtimeNs: stats.mtimeNs,
55
+ ctimeNs: stats.ctimeNs
56
+ };
57
+ if (sameIdentity(cached.identity, identity)) {
58
+ return {
59
+ status: "resolved",
60
+ canonical: cached.canonical,
61
+ identity: cached.identity
62
+ };
63
+ }
64
+ }
65
+ }
66
+ catch {
67
+ // cache stale, re-resolve below
68
+ }
69
+ }
70
+ let canonical;
58
71
  try {
59
- canonicalRoot = await realpath(input.projectRoot);
72
+ canonical = await realpath(projectRoot);
60
73
  }
61
74
  catch (error) {
62
- return errorCode(error) === "ENOENT" || errorCode(error) === "ENOTDIR"
75
+ return errnoCode(error) === "ENOENT" || errnoCode(error) === "ENOTDIR"
63
76
  ? { status: "rootNotFound" }
64
77
  : { status: "rootUnreadable" };
65
78
  }
66
- let rootBefore;
67
79
  try {
68
- const rootStats = await stat(canonicalRoot, { bigint: true });
80
+ const rootStats = await stat(canonical, { bigint: true });
69
81
  if (!rootStats.isDirectory()) {
70
82
  return { status: "rootNotDirectory" };
71
83
  }
72
- rootBefore = {
84
+ const identity = {
73
85
  dev: rootStats.dev,
74
86
  ino: rootStats.ino,
75
87
  size: rootStats.size,
76
88
  mtimeNs: rootStats.mtimeNs,
77
89
  ctimeNs: rootStats.ctimeNs
78
90
  };
91
+ if (this.rootCache === undefined) {
92
+ this.rootCache = new Map();
93
+ }
94
+ this.rootCache.set(projectRoot, { canonical, identity });
95
+ return { status: "resolved", canonical, identity };
79
96
  }
80
97
  catch (error) {
81
- return errorCode(error) === "ENOENT"
98
+ return errnoCode(error) === "ENOENT"
82
99
  ? { status: "rootNotFound" }
83
100
  : { status: "rootUnreadable" };
84
101
  }
102
+ };
103
+ inspectProjectFile = async (input) => {
104
+ const root = await this.resolveRoot(input.projectRoot);
105
+ if (root.status !== "resolved") {
106
+ return root;
107
+ }
108
+ const canonicalRoot = root.canonical;
109
+ const rootBefore = root.identity;
85
110
  const normalizedPath = input.relativePath.replaceAll("\\", "/");
86
111
  if (normalizedPath.startsWith("/")
87
112
  || /^[A-Za-z]:/.test(normalizedPath)
@@ -113,7 +138,7 @@ export class NodeProjectFileInspector {
113
138
  handle = await open(canonicalBefore, openFlags);
114
139
  }
115
140
  catch (error) {
116
- return errorCode(error) === "ELOOP"
141
+ return errnoCode(error) === "ELOOP"
117
142
  ? { status: "changedIdentity", resolvedRelativePath }
118
143
  : evidenceFailure(error);
119
144
  }
@@ -142,6 +167,7 @@ export class NodeProjectFileInspector {
142
167
  }
143
168
  const hash = createHash("sha256");
144
169
  const buffer = Buffer.allocUnsafe(64 * 1024);
170
+ const chunks = [];
145
171
  let totalBytes = 0;
146
172
  let tooLarge = false;
147
173
  while (totalBytes <= input.maximumBytes) {
@@ -152,7 +178,9 @@ export class NodeProjectFileInspector {
152
178
  break;
153
179
  }
154
180
  totalBytes += readResult.bytesRead;
155
- hash.update(buffer.subarray(0, readResult.bytesRead));
181
+ const chunk = Buffer.from(buffer.subarray(0, readResult.bytesRead));
182
+ hash.update(chunk);
183
+ chunks.push(chunk);
156
184
  if (totalBytes > input.maximumBytes) {
157
185
  tooLarge = true;
158
186
  break;
@@ -185,7 +213,8 @@ export class NodeProjectFileInspector {
185
213
  : {
186
214
  status: "inspected",
187
215
  resolvedRelativePath,
188
- sha256: hash.digest("hex")
216
+ sha256: hash.digest("hex"),
217
+ bytes: Buffer.concat(chunks)
189
218
  };
190
219
  }
191
220
  catch {
@@ -0,0 +1,7 @@
1
+ import type { ProjectIdentityInspection, ProjectIdentityInspector } from "../../ports/project-identity-inspector.js";
2
+ export declare class AndroidProjectIdentityInspector implements ProjectIdentityInspector {
3
+ readonly inspectIdentity: (input: {
4
+ readonly projectRoot: string;
5
+ readonly moduleDir: string;
6
+ }) => Promise<ProjectIdentityInspection>;
7
+ }
@@ -0,0 +1,215 @@
1
+ import { readFile, realpath, stat } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ import { errnoCode } from "../../shared/errors.js";
4
+ const PACKAGE_PATTERN = /package\s*=\s*"([^"]+)"/;
5
+ const MAIN_ACTION = "android.intent.action.MAIN";
6
+ const LAUNCHER_CATEGORY = "android.intent.category.LAUNCHER";
7
+ async function readBuildFile(moduleDir) {
8
+ for (const name of ["build.gradle.kts", "build.gradle"]) {
9
+ const candidate = resolve(moduleDir, name);
10
+ try {
11
+ const stats = await stat(candidate);
12
+ if (stats.isFile()) {
13
+ return await readFile(candidate, "utf8");
14
+ }
15
+ }
16
+ catch {
17
+ // continue
18
+ }
19
+ }
20
+ return undefined;
21
+ }
22
+ function escapeRegExp(value) {
23
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
24
+ }
25
+ function parseApplicationIdLiteral(buildContent) {
26
+ const groovyMatch = buildContent.match(/applicationId\s+['"]([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+)['"]/);
27
+ if (groovyMatch !== null) {
28
+ return groovyMatch[1];
29
+ }
30
+ const kotlinMatch = buildContent.match(/applicationId\s*=\s*['"]([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+)['"]/);
31
+ if (kotlinMatch !== null) {
32
+ return kotlinMatch[1];
33
+ }
34
+ return undefined;
35
+ }
36
+ function parseApplicationIdVariable(buildContent) {
37
+ const varMatch = buildContent.match(/applicationId\s*=\s*([A-Za-z_$][\w$]*)[ \t]*(?:\/\/.*)?$/m);
38
+ if (varMatch === null) {
39
+ return undefined;
40
+ }
41
+ const variableName = varMatch[1];
42
+ if (variableName === undefined) {
43
+ return undefined;
44
+ }
45
+ const propPattern = new RegExp(`val\\s+${escapeRegExp(variableName)}\\s*=\\s*providers\\.gradleProperty\\s*\\(\\s*["']([^"']+)["']\\s*\\)\\.get\\(\\)`);
46
+ const propMatch = buildContent.match(propPattern);
47
+ return propMatch?.[1];
48
+ }
49
+ async function readGradleProperty(projectRoot, propertyName) {
50
+ const propertiesPath = resolve(projectRoot, "gradle.properties");
51
+ let content;
52
+ try {
53
+ content = await readFile(propertiesPath, "utf8");
54
+ }
55
+ catch {
56
+ return undefined;
57
+ }
58
+ for (const line of content.split(/\r?\n/)) {
59
+ const trimmed = line.trim();
60
+ if (trimmed.startsWith("#") || trimmed.startsWith("!")) {
61
+ continue;
62
+ }
63
+ const eqIndex = trimmed.indexOf("=");
64
+ if (eqIndex === -1) {
65
+ continue;
66
+ }
67
+ const key = trimmed.slice(0, eqIndex).trim();
68
+ if (key === propertyName) {
69
+ return trimmed.slice(eqIndex + 1).trim();
70
+ }
71
+ }
72
+ return undefined;
73
+ }
74
+ async function resolveApplicationId(buildContent, projectRoot) {
75
+ if (buildContent === undefined) {
76
+ return { applicationId: undefined, namespace: undefined };
77
+ }
78
+ const literal = parseApplicationIdLiteral(buildContent);
79
+ if (literal !== undefined) {
80
+ const namespace = parseNamespace(buildContent);
81
+ return { applicationId: literal, namespace };
82
+ }
83
+ const propertyName = parseApplicationIdVariable(buildContent);
84
+ if (propertyName !== undefined) {
85
+ const value = await readGradleProperty(projectRoot, propertyName);
86
+ if (value !== undefined) {
87
+ const namespace = parseNamespace(buildContent);
88
+ return { applicationId: value, namespace };
89
+ }
90
+ }
91
+ const namespace = parseNamespace(buildContent);
92
+ return { applicationId: namespace, namespace };
93
+ }
94
+ function parseNamespace(buildContent) {
95
+ const namespaceMatch = buildContent.match(/namespace\s*=?\s*['"]([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+)['"]/);
96
+ return namespaceMatch?.[1];
97
+ }
98
+ function parseManifest(manifestContent) {
99
+ const packageMatch = manifestContent.match(PACKAGE_PATTERN);
100
+ const packageName = packageMatch?.[1];
101
+ const activityBlocks = [];
102
+ const activityPattern = /<activity\b[^>]*>/g;
103
+ let activityMatch;
104
+ while ((activityMatch = activityPattern.exec(manifestContent)) !== null) {
105
+ const tag = activityMatch[0];
106
+ const nameMatch = tag.match(/android:name\s*=\s*"([^"]+)"/);
107
+ if (nameMatch === null) {
108
+ continue;
109
+ }
110
+ const name = nameMatch[1];
111
+ if (name === undefined) {
112
+ continue;
113
+ }
114
+ const filters = [];
115
+ if (!tag.endsWith("/>")) {
116
+ const filterStart = activityMatch.index + tag.length;
117
+ const closingIndex = manifestContent.indexOf("</activity>", filterStart);
118
+ const activityBody = closingIndex === -1
119
+ ? manifestContent.slice(filterStart)
120
+ : manifestContent.slice(filterStart, closingIndex);
121
+ const filterPattern = /<intent-filter\b[\s\S]*?<\/intent-filter>/g;
122
+ let filterMatch;
123
+ while ((filterMatch = filterPattern.exec(activityBody)) !== null) {
124
+ filters.push(filterMatch[0]);
125
+ }
126
+ }
127
+ activityBlocks.push({ name, filters });
128
+ }
129
+ let launchActivity;
130
+ for (const block of activityBlocks) {
131
+ for (const filter of block.filters) {
132
+ if (filter.includes(MAIN_ACTION) && filter.includes(LAUNCHER_CATEGORY)) {
133
+ launchActivity = block.name;
134
+ break;
135
+ }
136
+ }
137
+ if (launchActivity !== undefined) {
138
+ break;
139
+ }
140
+ }
141
+ return { packageName, launchActivity };
142
+ }
143
+ function resolveActivity(packageName, activity) {
144
+ if (activity.startsWith(".")) {
145
+ return `${packageName}${activity}`;
146
+ }
147
+ if (!activity.includes(".")) {
148
+ return `${packageName}.${activity}`;
149
+ }
150
+ return activity;
151
+ }
152
+ export class AndroidProjectIdentityInspector {
153
+ inspectIdentity = async (input) => {
154
+ let root;
155
+ try {
156
+ root = await realpath(input.projectRoot);
157
+ const rootStats = await stat(root);
158
+ if (!rootStats.isDirectory()) {
159
+ return { status: "rootNotDirectory" };
160
+ }
161
+ }
162
+ catch (error) {
163
+ return errnoCode(error) === "ENOENT"
164
+ ? { status: "rootNotFound" }
165
+ : { status: "identityNotFound", message: "Project root is not accessible" };
166
+ }
167
+ const modulePath = resolve(root, input.moduleDir);
168
+ let moduleCanonical;
169
+ try {
170
+ moduleCanonical = await realpath(modulePath);
171
+ const moduleStats = await stat(moduleCanonical);
172
+ if (!moduleStats.isDirectory()) {
173
+ return { status: "moduleNotFound" };
174
+ }
175
+ }
176
+ catch (error) {
177
+ return errnoCode(error) === "ENOENT"
178
+ ? { status: "moduleNotFound" }
179
+ : { status: "identityNotFound", message: "Module directory is not accessible" };
180
+ }
181
+ const buildContent = await readBuildFile(moduleCanonical);
182
+ const { applicationId, namespace } = await resolveApplicationId(buildContent, root);
183
+ const manifestPath = resolve(moduleCanonical, "src", "main", "AndroidManifest.xml");
184
+ let manifestContent;
185
+ try {
186
+ manifestContent = await readFile(manifestPath, "utf8");
187
+ }
188
+ catch (error) {
189
+ if (errnoCode(error) === "ENOENT") {
190
+ return { status: "manifestNotFound" };
191
+ }
192
+ return { status: "manifestUnreadable" };
193
+ }
194
+ const { packageName: manifestPackage, launchActivity } = parseManifest(manifestContent);
195
+ const packageName = applicationId ?? manifestPackage;
196
+ if (packageName === undefined) {
197
+ return {
198
+ status: "identityNotFound",
199
+ message: "Unable to resolve applicationId or package from build file or manifest"
200
+ };
201
+ }
202
+ if (launchActivity === undefined) {
203
+ return {
204
+ status: "identityNotFound",
205
+ message: "Unable to resolve launch activity from manifest"
206
+ };
207
+ }
208
+ const activityBase = namespace ?? manifestPackage ?? packageName;
209
+ return {
210
+ status: "inspected",
211
+ packageName,
212
+ launchActivity: resolveActivity(activityBase, launchActivity)
213
+ };
214
+ };
215
+ }
@@ -0,0 +1,8 @@
1
+ import type { ProjectInventoryCategory, ProjectInventoryInspection, ProjectInventoryInspector } from "../../ports/project-inventory-inspector.js";
2
+ export declare class NodeProjectInventoryInspector implements ProjectInventoryInspector {
3
+ readonly inspectProjectInventory: (input: {
4
+ projectRoot: string;
5
+ projectDir: string;
6
+ categories: ProjectInventoryCategory[];
7
+ }) => Promise<ProjectInventoryInspection>;
8
+ }