swipium 1.5.0 → 2.0.0

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 (498) hide show
  1. package/CHANGELOG.md +171 -0
  2. package/README.md +173 -280
  3. package/THREAT_MODEL.md +223 -47
  4. package/dist/appMap/automationLink.js +30 -27
  5. package/dist/appMap/automationLink.js.map +1 -1
  6. package/dist/appMap/build.js +47 -21
  7. package/dist/appMap/build.js.map +1 -1
  8. package/dist/appMap/codeIndex.js +2 -2
  9. package/dist/appMap/codeIndex.js.map +1 -1
  10. package/dist/appMap/featureIndex.js +3 -3
  11. package/dist/appMap/featureIndex.js.map +1 -1
  12. package/dist/appMap/featureModel.js +2 -2
  13. package/dist/appMap/featureModel.js.map +1 -1
  14. package/dist/appMap/firstRunApply.js +2 -2
  15. package/dist/appMap/firstRunApply.js.map +1 -1
  16. package/dist/appMap/issues.js +10 -10
  17. package/dist/appMap/issues.js.map +1 -1
  18. package/dist/appMap/migrations.js +5 -5
  19. package/dist/appMap/migrations.js.map +1 -1
  20. package/dist/appMap/prelaunch.js +2 -2
  21. package/dist/appMap/prelaunch.js.map +1 -1
  22. package/dist/appMap/projectRegistry.js +77 -11
  23. package/dist/appMap/projectRegistry.js.map +1 -1
  24. package/dist/appMap/provenance.js +2 -2
  25. package/dist/appMap/provenance.js.map +1 -1
  26. package/dist/appMap/query.js +2 -2
  27. package/dist/appMap/query.js.map +1 -1
  28. package/dist/appMap/runtimeMerge.js +3 -3
  29. package/dist/appMap/runtimeMerge.js.map +1 -1
  30. package/dist/appMap/schema.js +1 -1
  31. package/dist/appMap/schema.js.map +1 -1
  32. package/dist/appMap/screenMatch.js +1 -1
  33. package/dist/appMap/screenMatch.js.map +1 -1
  34. package/dist/appMap/staticScan.js +17 -17
  35. package/dist/appMap/staticScan.js.map +1 -1
  36. package/dist/appMap/store.js +55 -7
  37. package/dist/appMap/store.js.map +1 -1
  38. package/dist/appMap/tsAstScan.js +3 -3
  39. package/dist/appMap/tsAstScan.js.map +1 -1
  40. package/dist/artifacts/bundletool.js +5 -5
  41. package/dist/artifacts/bundletool.js.map +1 -1
  42. package/dist/artifacts/resolve.js +10 -10
  43. package/dist/artifacts/resolve.js.map +1 -1
  44. package/dist/automation/capabilities.js +3 -3
  45. package/dist/automation/capabilities.js.map +1 -1
  46. package/dist/automation/gestures.js +1 -1
  47. package/dist/automation/gestures.js.map +1 -1
  48. package/dist/automation/plan.js +7 -7
  49. package/dist/automation/plan.js.map +1 -1
  50. package/dist/automation/report.js +3 -3
  51. package/dist/automation/report.js.map +1 -1
  52. package/dist/automation/selectors.js +14 -25
  53. package/dist/automation/selectors.js.map +1 -1
  54. package/dist/automation/types.js +1 -1
  55. package/dist/automation/types.js.map +1 -1
  56. package/dist/automation/waits.js +5 -5
  57. package/dist/automation/waits.js.map +1 -1
  58. package/dist/automation/webview.js +2 -2
  59. package/dist/automation/webview.js.map +1 -1
  60. package/dist/automationGen/appiumModel.js +13 -7
  61. package/dist/automationGen/appiumModel.js.map +1 -1
  62. package/dist/automationGen/ciEmitter.js +5 -5
  63. package/dist/automationGen/ciEmitter.js.map +1 -1
  64. package/dist/automationGen/identifiers.js +224 -0
  65. package/dist/automationGen/identifiers.js.map +1 -0
  66. package/dist/automationGen/jsEmitter.js +265 -106
  67. package/dist/automationGen/jsEmitter.js.map +1 -1
  68. package/dist/automationGen/packagePatch.js +2 -2
  69. package/dist/automationGen/packagePatch.js.map +1 -1
  70. package/dist/automationGen/platformResolve.js +36 -0
  71. package/dist/automationGen/platformResolve.js.map +1 -0
  72. package/dist/automationGen/projectProfile.js +71 -26
  73. package/dist/automationGen/projectProfile.js.map +1 -1
  74. package/dist/automationGen/pythonEmitter.js +276 -105
  75. package/dist/automationGen/pythonEmitter.js.map +1 -1
  76. package/dist/automationGen/readmeEmitter.js +7 -6
  77. package/dist/automationGen/readmeEmitter.js.map +1 -1
  78. package/dist/{tools/automationGenerate.js → automationGen/run.js} +72 -113
  79. package/dist/automationGen/run.js.map +1 -0
  80. package/dist/automationGen/suitePlan.js +6 -4
  81. package/dist/automationGen/suitePlan.js.map +1 -1
  82. package/dist/automationGen/validation.js +20 -8
  83. package/dist/automationGen/validation.js.map +1 -1
  84. package/dist/build/parseBuildLog.js +2 -2
  85. package/dist/build/parseBuildLog.js.map +1 -1
  86. package/dist/build/plan.js +7 -7
  87. package/dist/build/plan.js.map +1 -1
  88. package/dist/ci/preflight.js +13 -4
  89. package/dist/ci/preflight.js.map +1 -1
  90. package/dist/cli/gc.js +74 -0
  91. package/dist/cli/gc.js.map +1 -0
  92. package/dist/cli/init.js +217 -26
  93. package/dist/cli/init.js.map +1 -1
  94. package/dist/cli/main.js +50 -0
  95. package/dist/cli/main.js.map +1 -0
  96. package/dist/cli/report.js +218 -0
  97. package/dist/cli/report.js.map +1 -0
  98. package/dist/cli/scan.js +35 -14
  99. package/dist/cli/scan.js.map +1 -1
  100. package/dist/cli/suite.js +6 -6
  101. package/dist/cli/suite.js.map +1 -1
  102. package/dist/cli/verify.js +7 -7
  103. package/dist/cli/verify.js.map +1 -1
  104. package/dist/consent/consent.js +162 -5
  105. package/dist/consent/consent.js.map +1 -1
  106. package/dist/context/detect.js +45 -14
  107. package/dist/context/detect.js.map +1 -1
  108. package/dist/context/findApps.js +6 -6
  109. package/dist/context/findApps.js.map +1 -1
  110. package/dist/context/projectRoot.js +174 -19
  111. package/dist/context/projectRoot.js.map +1 -1
  112. package/dist/context/scan.js +1 -1
  113. package/dist/context/scan.js.map +1 -1
  114. package/dist/core/capabilityGroups.js +90 -0
  115. package/dist/core/capabilityGroups.js.map +1 -0
  116. package/dist/core/target.js +3 -3
  117. package/dist/core/target.js.map +1 -1
  118. package/dist/core/targetPlan.js +65 -62
  119. package/dist/core/targetPlan.js.map +1 -1
  120. package/dist/drivers/DirectDriver.js +300 -36
  121. package/dist/drivers/DirectDriver.js.map +1 -1
  122. package/dist/drivers/SimctlDriver.js +120 -7
  123. package/dist/drivers/SimctlDriver.js.map +1 -1
  124. package/dist/drivers/WdaDriver.js +350 -50
  125. package/dist/drivers/WdaDriver.js.map +1 -1
  126. package/dist/explore/candidates.js +2 -2
  127. package/dist/explore/candidates.js.map +1 -1
  128. package/dist/explore/graph.js +5 -5
  129. package/dist/explore/graph.js.map +1 -1
  130. package/dist/explore/policy.js +5 -5
  131. package/dist/explore/policy.js.map +1 -1
  132. package/dist/explore/runner.js +301 -284
  133. package/dist/explore/runner.js.map +1 -1
  134. package/dist/explore/signatures.js +1 -1
  135. package/dist/featureTesting/executionBootstrap.js +29 -30
  136. package/dist/featureTesting/executionBootstrap.js.map +1 -1
  137. package/dist/featureTesting/featureMap.js +2 -2
  138. package/dist/featureTesting/featureMap.js.map +1 -1
  139. package/dist/featureTesting/featureScope.js +12 -12
  140. package/dist/featureTesting/featureScope.js.map +1 -1
  141. package/dist/featureTesting/mapFeatureScope.js +7 -7
  142. package/dist/featureTesting/mapFeatureScope.js.map +1 -1
  143. package/dist/featureTesting/objectiveModel.js +10 -10
  144. package/dist/featureTesting/objectiveModel.js.map +1 -1
  145. package/dist/featureTesting/resultMerge.js +6 -6
  146. package/dist/featureTesting/resultMerge.js.map +1 -1
  147. package/dist/featureTesting/sources.js +4 -4
  148. package/dist/featureTesting/sources.js.map +1 -1
  149. package/dist/featureTesting/suiteBridge.js +1 -1
  150. package/dist/featureTesting/suiteBridge.js.map +1 -1
  151. package/dist/featureTesting/synonyms.js +1 -1
  152. package/dist/featureTesting/synonyms.js.map +1 -1
  153. package/dist/featureTesting/testCaseFactory.js +11 -11
  154. package/dist/featureTesting/testCaseFactory.js.map +1 -1
  155. package/dist/featureTesting/testPlan.js +3 -3
  156. package/dist/featureTesting/testPlan.js.map +1 -1
  157. package/dist/firstRun/authStateMachine.js +1 -1
  158. package/dist/firstRun/authStateMachine.js.map +1 -1
  159. package/dist/firstRun/classifyScreen.js +5 -5
  160. package/dist/firstRun/classifyScreen.js.map +1 -1
  161. package/dist/firstRun/firstRunPlanner.js +11 -11
  162. package/dist/firstRun/firstRunPlanner.js.map +1 -1
  163. package/dist/firstRun/firstRunRunner.js +8 -8
  164. package/dist/firstRun/firstRunRunner.js.map +1 -1
  165. package/dist/firstRun/generatedDataPolicy.js +9 -9
  166. package/dist/firstRun/generatedDataPolicy.js.map +1 -1
  167. package/dist/firstRun/inputPlanner.js +5 -5
  168. package/dist/firstRun/inputPlanner.js.map +1 -1
  169. package/dist/firstRun/onboardingStateMachine.js +1 -1
  170. package/dist/firstRun/onboardingStateMachine.js.map +1 -1
  171. package/dist/firstRun/paywallPolicy.js +3 -3
  172. package/dist/firstRun/paywallPolicy.js.map +1 -1
  173. package/dist/firstRun/types.js +1 -1
  174. package/dist/firstRun/types.js.map +1 -1
  175. package/dist/fixtures/catalog.js +33 -3
  176. package/dist/fixtures/catalog.js.map +1 -1
  177. package/dist/fixtures/load.js +50 -0
  178. package/dist/fixtures/load.js.map +1 -0
  179. package/dist/flows/discover.js +2 -2
  180. package/dist/flows/discover.js.map +1 -1
  181. package/dist/flows/generate.js +20 -10
  182. package/dist/flows/generate.js.map +1 -1
  183. package/dist/flows/pack.js +3 -3
  184. package/dist/flows/pack.js.map +1 -1
  185. package/dist/flows/paths.js +57 -0
  186. package/dist/flows/paths.js.map +1 -0
  187. package/dist/flows/repair.js +111 -23
  188. package/dist/flows/repair.js.map +1 -1
  189. package/dist/flows/run.js +153 -57
  190. package/dist/flows/run.js.map +1 -1
  191. package/dist/flows/schema.js +50 -5
  192. package/dist/flows/schema.js.map +1 -1
  193. package/dist/flows/seedExec.js +4 -4
  194. package/dist/flows/seedExec.js.map +1 -1
  195. package/dist/flows/templates.js +5 -5
  196. package/dist/index.js +50 -8
  197. package/dist/index.js.map +1 -1
  198. package/dist/issues/classify.js +16 -16
  199. package/dist/issues/classify.js.map +1 -1
  200. package/dist/issues/fingerprint.js +24 -4
  201. package/dist/issues/fingerprint.js.map +1 -1
  202. package/dist/issues/index.js +94 -31
  203. package/dist/issues/index.js.map +1 -1
  204. package/dist/issues/metrics.js +4 -4
  205. package/dist/issues/metrics.js.map +1 -1
  206. package/dist/issues/recurrence.js +35 -8
  207. package/dist/issues/recurrence.js.map +1 -1
  208. package/dist/issues/report.js +4 -4
  209. package/dist/issues/report.js.map +1 -1
  210. package/dist/issues/reportBridge.js +4 -4
  211. package/dist/issues/reportBridge.js.map +1 -1
  212. package/dist/issues/schema.js +2 -2
  213. package/dist/issues/schema.js.map +1 -1
  214. package/dist/issues/sourceRevision.js +2 -2
  215. package/dist/issues/sourceRevision.js.map +1 -1
  216. package/dist/issues/store.js +110 -32
  217. package/dist/issues/store.js.map +1 -1
  218. package/dist/lib/abortScope.js +48 -0
  219. package/dist/lib/abortScope.js.map +1 -0
  220. package/dist/lib/android.js +75 -7
  221. package/dist/lib/android.js.map +1 -1
  222. package/dist/lib/coordSpace.js +1 -1
  223. package/dist/lib/coordSpace.js.map +1 -1
  224. package/dist/lib/device.js +32 -7
  225. package/dist/lib/device.js.map +1 -1
  226. package/dist/lib/gestures.js +141 -0
  227. package/dist/lib/gestures.js.map +1 -0
  228. package/dist/lib/gitignore.js +4 -3
  229. package/dist/lib/gitignore.js.map +1 -1
  230. package/dist/lib/image.js +1 -1
  231. package/dist/lib/image.js.map +1 -1
  232. package/dist/lib/lockfile.js +370 -31
  233. package/dist/lib/lockfile.js.map +1 -1
  234. package/dist/lib/needsInput.js +3 -3
  235. package/dist/lib/needsInput.js.map +1 -1
  236. package/dist/lib/png.js +2 -2
  237. package/dist/lib/png.js.map +1 -1
  238. package/dist/lib/redact.js +170 -9
  239. package/dist/lib/redact.js.map +1 -1
  240. package/dist/lib/result.js +79 -10
  241. package/dist/lib/result.js.map +1 -1
  242. package/dist/lib/schemaHash.js +3 -3
  243. package/dist/lib/schemaHash.js.map +1 -1
  244. package/dist/lib/sensitive.js +3 -2
  245. package/dist/lib/sensitive.js.map +1 -1
  246. package/dist/lib/simctl.js +3 -3
  247. package/dist/lib/simctl.js.map +1 -1
  248. package/dist/lib/spawn.js +52 -10
  249. package/dist/lib/spawn.js.map +1 -1
  250. package/dist/lib/toolAnnotations.js +114 -0
  251. package/dist/lib/toolAnnotations.js.map +1 -0
  252. package/dist/lib/wda.js +294 -15
  253. package/dist/lib/wda.js.map +1 -1
  254. package/dist/mobileAudit/checks.js +12 -12
  255. package/dist/mobileAudit/checks.js.map +1 -1
  256. package/dist/mobileAudit/evidence.js +4 -4
  257. package/dist/mobileAudit/evidence.js.map +1 -1
  258. package/dist/mobileAudit/profiles.js +1 -1
  259. package/dist/mobileAudit/profiles.js.map +1 -1
  260. package/dist/mobileAudit/results.js +3 -3
  261. package/dist/mobileAudit/results.js.map +1 -1
  262. package/dist/mobileAudit/runner.js +72 -18
  263. package/dist/mobileAudit/runner.js.map +1 -1
  264. package/dist/oracle/auth.js +1 -1
  265. package/dist/oracle/auth.js.map +1 -1
  266. package/dist/oracle/failures.js +219 -36
  267. package/dist/oracle/failures.js.map +1 -1
  268. package/dist/oracle/health.js +63 -17
  269. package/dist/oracle/health.js.map +1 -1
  270. package/dist/oracle/locator.js +13 -13
  271. package/dist/oracle/locator.js.map +1 -1
  272. package/dist/oracle/record.js +3 -3
  273. package/dist/oracle/record.js.map +1 -1
  274. package/dist/orchestration/envelope.js +2 -2
  275. package/dist/orchestration/envelope.js.map +1 -1
  276. package/dist/orchestration/goal.js +4 -4
  277. package/dist/orchestration/goal.js.map +1 -1
  278. package/dist/orchestration/testThis/execute.js +62 -21
  279. package/dist/orchestration/testThis/execute.js.map +1 -1
  280. package/dist/orchestration/testThis/pipeline.js +119 -30
  281. package/dist/orchestration/testThis/pipeline.js.map +1 -1
  282. package/dist/orchestration/testThis/plan.js +162 -73
  283. package/dist/orchestration/testThis/plan.js.map +1 -1
  284. package/dist/orchestration/testThis/sessionIntent.js +72 -0
  285. package/dist/orchestration/testThis/sessionIntent.js.map +1 -0
  286. package/dist/orchestration/testThis/terminal.js +34 -16
  287. package/dist/orchestration/testThis/terminal.js.map +1 -1
  288. package/dist/orchestration/testThis/types.js +1 -1
  289. package/dist/orchestration/testThis/types.js.map +1 -1
  290. package/dist/plan/plan.js +9 -9
  291. package/dist/plan/plan.js.map +1 -1
  292. package/dist/prompts/index.js +22 -22
  293. package/dist/prompts/index.js.map +1 -1
  294. package/dist/report/export.js +392 -37
  295. package/dist/report/export.js.map +1 -1
  296. package/dist/report/findingsDedupe.js +41 -0
  297. package/dist/report/findingsDedupe.js.map +1 -0
  298. package/dist/report/policy.js +7 -4
  299. package/dist/report/policy.js.map +1 -1
  300. package/dist/report/qaLevel.js +8 -8
  301. package/dist/report/qaLevel.js.map +1 -1
  302. package/dist/report/sarifSources.js +160 -0
  303. package/dist/report/sarifSources.js.map +1 -0
  304. package/dist/report/summary.js +2 -2
  305. package/dist/report/summary.js.map +1 -1
  306. package/dist/report/testCatalog.js +4 -4
  307. package/dist/report/testCatalog.js.map +1 -1
  308. package/dist/report/toolHealth.js +124 -0
  309. package/dist/report/toolHealth.js.map +1 -0
  310. package/dist/server.js +472 -45
  311. package/dist/server.js.map +1 -1
  312. package/dist/services/automationGenerate.js +8 -2
  313. package/dist/services/automationGenerate.js.map +1 -1
  314. package/dist/services/build.js +2 -2
  315. package/dist/services/build.js.map +1 -1
  316. package/dist/{tools → services}/flowGenerate.js +29 -13
  317. package/dist/services/flowGenerate.js.map +1 -0
  318. package/dist/services/preflight.js +4 -4
  319. package/dist/services/preflight.js.map +1 -1
  320. package/dist/services/prepareAndroid.js +46 -8
  321. package/dist/services/prepareAndroid.js.map +1 -1
  322. package/dist/services/prepareIos.js +24 -9
  323. package/dist/services/prepareIos.js.map +1 -1
  324. package/dist/services/report.js +100 -45
  325. package/dist/services/report.js.map +1 -1
  326. package/dist/services/smoke.js +32 -16
  327. package/dist/services/smoke.js.map +1 -1
  328. package/dist/services/suiteGenerate.js +28 -3
  329. package/dist/services/suiteGenerate.js.map +1 -1
  330. package/dist/services/testSuiteKnowledge.js +40 -27
  331. package/dist/services/testSuiteKnowledge.js.map +1 -1
  332. package/dist/session/attach.js +270 -6
  333. package/dist/session/attach.js.map +1 -1
  334. package/dist/session/processRegistry.js +317 -48
  335. package/dist/session/processRegistry.js.map +1 -1
  336. package/dist/session/progress.js +1 -1
  337. package/dist/session/retention.js +201 -0
  338. package/dist/session/retention.js.map +1 -0
  339. package/dist/session/store.js +275 -48
  340. package/dist/session/store.js.map +1 -1
  341. package/dist/snapshot/overlays.js +53 -8
  342. package/dist/snapshot/overlays.js.map +1 -1
  343. package/dist/snapshot/parse.js +28 -4
  344. package/dist/snapshot/parse.js.map +1 -1
  345. package/dist/snapshot/present.js +11 -5
  346. package/dist/snapshot/present.js.map +1 -1
  347. package/dist/snapshot/settle.js +39 -9
  348. package/dist/snapshot/settle.js.map +1 -1
  349. package/dist/state/consent.js +99 -0
  350. package/dist/state/consent.js.map +1 -0
  351. package/dist/state/profile.js +46 -2
  352. package/dist/state/profile.js.map +1 -1
  353. package/dist/suite/compile.js +9 -5
  354. package/dist/suite/compile.js.map +1 -1
  355. package/dist/suite/lint.js +4 -4
  356. package/dist/suite/lint.js.map +1 -1
  357. package/dist/suite/pom.js +50 -41
  358. package/dist/suite/pom.js.map +1 -1
  359. package/dist/suite/secretGuard.js +266 -0
  360. package/dist/suite/secretGuard.js.map +1 -0
  361. package/dist/suite/testcase.js +9 -7
  362. package/dist/suite/testcase.js.map +1 -1
  363. package/dist/testSuite/exporter.js +4 -4
  364. package/dist/testSuite/exporter.js.map +1 -1
  365. package/dist/testSuite/generator.js +13 -9
  366. package/dist/testSuite/generator.js.map +1 -1
  367. package/dist/testSuite/history.js +1 -1
  368. package/dist/testSuite/history.js.map +1 -1
  369. package/dist/testSuite/issueLinks.js +3 -3
  370. package/dist/testSuite/issueLinks.js.map +1 -1
  371. package/dist/testSuite/lint.js +4 -4
  372. package/dist/testSuite/lint.js.map +1 -1
  373. package/dist/testSuite/merge.js +5 -5
  374. package/dist/testSuite/merge.js.map +1 -1
  375. package/dist/testSuite/schema.js +5 -5
  376. package/dist/testSuite/schema.js.map +1 -1
  377. package/dist/testSuite/store.js +3 -3
  378. package/dist/testSuite/store.js.map +1 -1
  379. package/dist/testSuite/traceability.js +2 -2
  380. package/dist/testSuite/traceability.js.map +1 -1
  381. package/dist/tools/act.js +794 -164
  382. package/dist/tools/act.js.map +1 -1
  383. package/dist/tools/agent.js +451 -128
  384. package/dist/tools/agent.js.map +1 -1
  385. package/dist/tools/appControl.js +69 -19
  386. package/dist/tools/appControl.js.map +1 -1
  387. package/dist/tools/appMap.js +188 -130
  388. package/dist/tools/appMap.js.map +1 -1
  389. package/dist/tools/build.js +32 -42
  390. package/dist/tools/build.js.map +1 -1
  391. package/dist/tools/bundletool.js +21 -31
  392. package/dist/tools/bundletool.js.map +1 -1
  393. package/dist/tools/clearOverlay.js +55 -12
  394. package/dist/tools/clearOverlay.js.map +1 -1
  395. package/dist/tools/device.js +76 -24
  396. package/dist/tools/device.js.map +1 -1
  397. package/dist/tools/doctor.js +68 -28
  398. package/dist/tools/doctor.js.map +1 -1
  399. package/dist/tools/explore.js +41 -47
  400. package/dist/tools/explore.js.map +1 -1
  401. package/dist/tools/featureTesting.js +31 -48
  402. package/dist/tools/featureTesting.js.map +1 -1
  403. package/dist/tools/firstRun.js +34 -41
  404. package/dist/tools/firstRun.js.map +1 -1
  405. package/dist/tools/flow.js +183 -103
  406. package/dist/tools/flow.js.map +1 -1
  407. package/dist/tools/flowRepair.js +25 -13
  408. package/dist/tools/flowRepair.js.map +1 -1
  409. package/dist/tools/generate.js +52 -89
  410. package/dist/tools/generate.js.map +1 -1
  411. package/dist/tools/getArtifact.js +16 -5
  412. package/dist/tools/getArtifact.js.map +1 -1
  413. package/dist/tools/health.js +15 -12
  414. package/dist/tools/health.js.map +1 -1
  415. package/dist/tools/ios.js +45 -223
  416. package/dist/tools/ios.js.map +1 -1
  417. package/dist/tools/issues.js +251 -35
  418. package/dist/tools/issues.js.map +1 -1
  419. package/dist/tools/jobs.js +41 -16
  420. package/dist/tools/jobs.js.map +1 -1
  421. package/dist/tools/metro.js +36 -35
  422. package/dist/tools/metro.js.map +1 -1
  423. package/dist/tools/mobileAudit.js +50 -45
  424. package/dist/tools/mobileAudit.js.map +1 -1
  425. package/dist/tools/network.js +31 -14
  426. package/dist/tools/network.js.map +1 -1
  427. package/dist/tools/note.js +26 -26
  428. package/dist/tools/note.js.map +1 -1
  429. package/dist/tools/prepareIosTarget.js +10 -16
  430. package/dist/tools/prepareIosTarget.js.map +1 -1
  431. package/dist/tools/prepareTarget.js +117 -50
  432. package/dist/tools/prepareTarget.js.map +1 -1
  433. package/dist/tools/report.js +11 -22
  434. package/dist/tools/report.js.map +1 -1
  435. package/dist/tools/resolveArtifact.js +11 -15
  436. package/dist/tools/resolveArtifact.js.map +1 -1
  437. package/dist/tools/resolveTarget.js +78 -24
  438. package/dist/tools/resolveTarget.js.map +1 -1
  439. package/dist/tools/screenRecord.js +23 -21
  440. package/dist/tools/screenRecord.js.map +1 -1
  441. package/dist/tools/screenshot.js +23 -16
  442. package/dist/tools/screenshot.js.map +1 -1
  443. package/dist/tools/smoke.js +20 -16
  444. package/dist/tools/smoke.js.map +1 -1
  445. package/dist/tools/snapshot.js +155 -118
  446. package/dist/tools/snapshot.js.map +1 -1
  447. package/dist/tools/startSession.js +104 -116
  448. package/dist/tools/startSession.js.map +1 -1
  449. package/dist/tools/suite.js +72 -43
  450. package/dist/tools/suite.js.map +1 -1
  451. package/dist/tools/testSuite.js +30 -20
  452. package/dist/tools/testSuite.js.map +1 -1
  453. package/dist/tools/testThis.js +24 -28
  454. package/dist/tools/testThis.js.map +1 -1
  455. package/dist/tools/visual.js +686 -210
  456. package/dist/tools/visual.js.map +1 -1
  457. package/dist/tools/wait.js +9 -38
  458. package/dist/tools/wait.js.map +1 -1
  459. package/dist/tools/wda.js +187 -57
  460. package/dist/tools/wda.js.map +1 -1
  461. package/dist/version.js +31 -22
  462. package/dist/version.js.map +1 -1
  463. package/dist/visual/ocr.js +53 -6
  464. package/dist/visual/ocr.js.map +1 -1
  465. package/dist/visual/provider.js +80 -12
  466. package/dist/visual/provider.js.map +1 -1
  467. package/docs/README.md +24 -10
  468. package/docs/ci-reports.md +328 -0
  469. package/docs/concepts.md +227 -0
  470. package/docs/flows.md +153 -0
  471. package/docs/mcp-server.md +194 -128
  472. package/docs/physical-devices.md +84 -0
  473. package/docs/tools.md +812 -152
  474. package/package.json +3 -2
  475. package/dist/tools/assertVisual.js +0 -84
  476. package/dist/tools/assertVisual.js.map +0 -1
  477. package/dist/tools/automationGenerate.js.map +0 -1
  478. package/dist/tools/capabilities.js +0 -179
  479. package/dist/tools/capabilities.js.map +0 -1
  480. package/dist/tools/detectContext.js +0 -41
  481. package/dist/tools/detectContext.js.map +0 -1
  482. package/dist/tools/flowGenerate.js.map +0 -1
  483. package/dist/tools/history.js +0 -86
  484. package/dist/tools/history.js.map +0 -1
  485. package/dist/tools/locator.js +0 -85
  486. package/dist/tools/locator.js.map +0 -1
  487. package/dist/tools/permissions.js +0 -174
  488. package/dist/tools/permissions.js.map +0 -1
  489. package/dist/tools/plan.js +0 -52
  490. package/dist/tools/plan.js.map +0 -1
  491. package/dist/tools/screenInfo.js +0 -76
  492. package/dist/tools/screenInfo.js.map +0 -1
  493. package/dist/tools/seed.js +0 -133
  494. package/dist/tools/seed.js.map +0 -1
  495. package/dist/tools/state.js +0 -217
  496. package/dist/tools/state.js.map +0 -1
  497. package/dist/tools/visualText.js +0 -106
  498. package/dist/tools/visualText.js.map +0 -1
@@ -1,4 +1,4 @@
1
- // qa_flow_check + qa_flow_run (PHASE3-PLAN §3.4) — turn exploration into repeatable QA.
1
+ // qa_flow_check + qa_flow_run: turn exploration into repeatable QA.
2
2
  // Flows live as .swipium/flows/*.yaml. check = parse + static validation (no device).
3
3
  // run (mode:"run", default) = the orchestrator (src/flows/run.ts), reporting the exact failing
4
4
  // step + evidence. run (mode:"plan", 1.5.0 consolidation) = read-only execution preview:
@@ -7,11 +7,14 @@ import { z } from 'zod';
7
7
  import { createHash } from 'node:crypto';
8
8
  import { existsSync, readFileSync } from 'node:fs';
9
9
  import { join, isAbsolute } from 'node:path';
10
- import { qaOk, qaError, qaStop, qaAnnotate } from '../lib/result.js';
11
- import { parseFlow } from '../flows/schema.js';
10
+ import { qaOk, qaError, qaStop, qaAnnotate, unknownSessionError } from '../lib/result.js';
11
+ import { FLOW_ENV_PREFIX, isMutatingFlowStep, lookupFlowVar, parseFlow, SECRET_VAR_NAME, } from '../flows/schema.js';
12
+ import { seedExactCommand } from '../flows/seedExec.js';
13
+ import { withinRootOrNull } from '../flows/paths.js';
14
+ import { makeRedactor } from '../lib/redact.js';
12
15
  import { lintFlowObjectWithOptions } from '../flows/lint.js';
13
16
  import { runFlow } from '../flows/run.js';
14
- import { getDriver } from '../session/attach.js';
17
+ import { blockedDeviceResult, getDriver } from '../session/attach.js';
15
18
  import { loadPolicy } from '../report/policy.js';
16
19
  import { classifyFlakeResults } from '../report/flake.js';
17
20
  import { validateCiMutationPolicy, validateCiVariables } from '../ci/preflight.js';
@@ -23,14 +26,15 @@ import { resolveMaskProvider, resolveVisualProvider } from '../visual/provider.j
23
26
  import { backendCapabilities, backendForDriverKind } from '../automation/capabilities.js';
24
27
  import { compileAutomationPlan } from '../automation/plan.js';
25
28
  import { buildReadiness } from '../automation/report.js';
29
+ import { resolveProjectRoot } from '../context/projectRoot.js';
26
30
  const ALL_BACKENDS = ['android-direct', 'ios-raw-simulator', 'ios-wda', 'appium-uiautomator2', 'appium-xcuitest'];
27
31
  const STATUS_ICON = { ready: '✅', candidate: '🟡', blocked: '⛔' };
28
32
  /** Resolve a flow's YAML from explicit text, an absolute/relative path, or a name under .swipium/flows. */
29
- function loadFlowSource(root, flow, flowYaml) {
33
+ export function loadFlowSource(root, flow, flowYaml) {
30
34
  if (flowYaml && flowYaml.trim())
31
35
  return { yamlText: flowYaml, source: 'inline' };
32
36
  if (!flow)
33
- return { error: 'Provide a flow name, a path, or flowYaml.' };
37
+ return { error: 'Provide a flow name, a path, or flowYaml.', errorCode: 'INVALID_ARGUMENT' };
34
38
  const candidates = [];
35
39
  if (isAbsolute(flow))
36
40
  candidates.push(flow);
@@ -45,13 +49,39 @@ function loadFlowSource(root, flow, flowYaml) {
45
49
  return { yamlText: readFileSync(p, 'utf8'), source: p };
46
50
  }
47
51
  catch (e) {
48
- return { error: `Could not read ${p}: ${String(e)}` };
52
+ return { error: `Could not read ${p}: ${String(e)}`, errorCode: 'INVALID_FLOW' };
49
53
  }
50
54
  }
51
55
  }
52
- return { error: `Flow not found. Looked for: ${candidates.join(', ') || '(no project root)'}` };
56
+ return {
57
+ error: `Flow not found. Looked for: ${candidates.join(', ') || '(no project root; pass projectRoot or set SWIPIUM_PROJECT_ROOT)'}`,
58
+ errorCode: 'FLOW_NOT_FOUND',
59
+ };
60
+ }
61
+ /**
62
+ * The project root a flow tool reads from: explicit projectRoot arg > the session's root >
63
+ * resolveProjectRoot (MCP roots > SWIPIUM_PROJECT_ROOT/CLAUDE_PROJECT_DIR > cwd marker), like
64
+ * every other root-aware tool. Undefined only when nothing resolves (inline flowYaml still works).
65
+ */
66
+ async function flowRoot(server, sessionRoot, projectRoot) {
67
+ if (!projectRoot && sessionRoot)
68
+ return sessionRoot;
69
+ const resolved = await resolveProjectRoot(server, projectRoot);
70
+ return resolved.root ?? sessionRoot;
71
+ }
72
+ function flowSourceError(src) {
73
+ return qaError({
74
+ what: src.error ?? 'Flow could not be loaded',
75
+ changedState: false,
76
+ retrySafe: true,
77
+ failureCode: src.errorCode ?? 'FLOW_NOT_FOUND',
78
+ nextSteps: [
79
+ src.errorCode === 'INVALID_ARGUMENT'
80
+ ? 'Pass flow="<name>" (under .swipium/flows), a .yaml path, or inline flowYaml.'
81
+ : 'Pass an existing flow name/path or inline flowYaml; pass projectRoot (or set SWIPIUM_PROJECT_ROOT) if the flow lives in another project.',
82
+ ],
83
+ });
53
84
  }
54
- const MUTATING_FLOW_STEPS = new Set(['networkOffline', 'networkOnline', 'seed', 'restartApp']);
55
85
  function stable(value) {
56
86
  if (Array.isArray(value))
57
87
  return value.map(stable);
@@ -72,15 +102,41 @@ function hash(value) {
72
102
  function allFlowSteps(flow) {
73
103
  return [...flow.setup, ...flow.steps, ...flow.teardown];
74
104
  }
75
- function flowMutationAffects(flow, session, source, repeat, externalProviders = []) {
105
+ /** Where a seed's command came from, stated in consent: fixtures are loaded from the repo's
106
+ * .swipium/fixtures.json (or qa_start_session fixtures). Swipium never reviewed them. */
107
+ const SEED_ORIGIN = 'declared fixture (.swipium/fixtures.json or qa_start_session fixtures), repo-supplied, UNREVIEWED';
108
+ /** `${VAR}` URL preview for consent: the URL as it will be opened, with credential-like variable
109
+ * values and registered session secrets masked, plus where each variable is read from. */
110
+ function openUrlPreview(url, vars, secrets) {
111
+ const variables = [];
112
+ const resolved = url.replace(/\$\{([^}]+)\}/g, (_, name) => {
113
+ const v = lookupFlowVar(name, vars);
114
+ variables.push({ name, source: v == null ? 'missing' : vars[name] != null ? 'variables' : 'env' });
115
+ if (v == null)
116
+ return `\${${name}}`;
117
+ return SECRET_VAR_NAME.test(name) ? `«${name}»` : v;
118
+ });
119
+ return { url, resolvedUrl: makeRedactor(secrets)(resolved) ?? resolved, variables };
120
+ }
121
+ function flowMutationAffects(flow, session, source, repeat, externalProviders = [], vars = {}) {
76
122
  const mutations = allFlowSteps(flow)
77
123
  .map((step, index) => {
78
- if (!MUTATING_FLOW_STEPS.has(step.kind))
124
+ if (!isMutatingFlowStep(step))
79
125
  return null;
80
126
  if (step.kind === 'seed') {
81
- const seedType = session.fixtures.find((f) => f.name === step.fixture)?.seed?.type ?? 'unknown';
82
- return { step: index + 1, kind: step.kind, fixture: step.fixture, seedType };
127
+ const seed = session.fixtures.find((f) => f.name === step.fixture)?.seed;
128
+ return {
129
+ step: index + 1,
130
+ kind: step.kind,
131
+ fixture: step.fixture,
132
+ seedType: seed?.type ?? 'unknown',
133
+ // The EXACT argv / URL the seed will run. Consent must show what executes.
134
+ command: seed ? seedExactCommand(seed) : null,
135
+ origin: SEED_ORIGIN,
136
+ };
83
137
  }
138
+ if (step.kind === 'openUrl')
139
+ return { step: index + 1, kind: step.kind, ...openUrlPreview(step.url, vars, session.secrets ?? []) };
84
140
  return { step: index + 1, kind: step.kind };
85
141
  })
86
142
  .filter((x) => !!x);
@@ -94,11 +150,21 @@ function flowMutationAffects(flow, session, source, repeat, externalProviders =
94
150
  externalProviders,
95
151
  };
96
152
  }
153
+ /** One line per mutating step for the consent's exactCommand (seeds show their argv/URL). */
154
+ function mutationCommandLines(affects) {
155
+ return affects.mutations.map((m) => {
156
+ if (m.kind === 'seed' && 'fixture' in m)
157
+ return `${m.step}:seed ${m.fixture} [${m.seedType}] > ${m.command ?? '(no seed spec, step will fail)'} (repo-supplied, unreviewed)`;
158
+ if (m.kind === 'openUrl' && 'resolvedUrl' in m)
159
+ return `${m.step}:openUrl ${m.resolvedUrl} (variables: ${m.variables.map((v) => `${v.name}←${v.source}`).join(', ')})`;
160
+ return `${m.step}:${m.kind}`;
161
+ });
162
+ }
97
163
  function flowMutationRisk(affects) {
98
164
  if (affects.mutations.some((m) => m.kind === 'seed' && m.seedType === 'script'))
99
165
  return 'high';
100
166
  if (affects.externalProviders.length ||
101
- affects.mutations.some((m) => m.kind === 'networkOffline' || m.kind === 'networkOnline' || m.kind === 'seed'))
167
+ affects.mutations.some((m) => m.kind === 'networkOffline' || m.kind === 'networkOnline' || m.kind === 'seed' || m.kind === 'openUrl'))
102
168
  return 'medium';
103
169
  return 'low';
104
170
  }
@@ -111,39 +177,46 @@ function ocrSteps(flow) {
111
177
  });
112
178
  return out;
113
179
  }
180
+ const LOCATOR_FAILURES = new Set(['ELEMENT_NOT_FOUND', 'AMBIGUOUS_SELECTOR', 'STALE_REF', 'INVALID_SELECTOR', 'ASSERTION_FAILED']);
181
+ /** nextSteps for a failed qa_flow_run: point at qa_flow_repair for the failing step. */
182
+ export function failedFlowNextSteps(name, flowArg, failedAtStep, failureCode) {
183
+ const out = [];
184
+ // A missing variable is not locator drift: qa_flow_repair cannot fix it (and a cancelled run is
185
+ // not a failure to repair).
186
+ if (failedAtStep != null && failureCode !== 'MISSING_FIXTURE' && failureCode !== 'CANCELLED') {
187
+ const target = flowArg ? `flow:"${flowArg}"` : 'flowYaml:<same YAML>';
188
+ out.push(`${failureCode && !LOCATOR_FAILURES.has(failureCode) ? 'If this is locator drift (renamed/moved control), ' : ''}call qa_flow_repair { ${target}, failedStep:${failedAtStep} } for a reviewable locator fix (review before apply).`);
189
+ }
190
+ if (failureCode === 'MISSING_FIXTURE')
191
+ out.push(`Pass missing values via qa_flow_run { variables } (flows read process.env only for ${FLOW_ENV_PREFIX}* names).`);
192
+ out.push(`If the app itself is wrong, record it with qa_note { workflow:"${name}", outcome:"fail" } with the step evidence.`);
193
+ return out;
194
+ }
114
195
  export function registerFlow(server, sessions) {
115
196
  server.registerTool('qa_flow_check', {
116
197
  title: 'Check a flow',
117
- description: 'Parse and statically validate a Swipium flow (a .swipium/flows/*.yaml authored as name + steps) WITHOUT running it. Reports syntax/schema errors with the offending step, plus warnings (e.g. selector resolvability is only known at run time). Provide `flow` (a name under .swipium/flows, or a path) or `flowYaml` (inline). A sessionId lets it resolve names against that session\'s project root. Related: qa_flow_check is the STATIC LINT of the YAML; qa_flow_run mode:"plan" is the read-only EXECUTION PREVIEW (which backends can run it, step-by-step support); qa_flow_run mode:"run" executes.',
198
+ description: 'Statically validate a flow (.swipium/flows/*.yaml: name + steps) without running it: syntax/schema errors with the offending ' +
199
+ 'step, plus warnings. Pass flow (name or path) or flowYaml. For an execution preview per backend use qa_flow_run mode:"plan".',
118
200
  inputSchema: {
119
201
  sessionId: z.string().optional(),
202
+ projectRoot: z.string().optional().describe('Absolute app root (default: session root > MCP roots > env > cwd).'),
120
203
  flow: z.string().optional().describe('Flow name under .swipium/flows, or a path to a .yaml file.'),
121
204
  flowYaml: z.string().optional().describe('Inline flow YAML (instead of a file).'),
122
- platform: z
123
- .enum(['android', 'ios', 'cross-platform'])
124
- .optional()
125
- .describe('Optional authoring target for platform-aware warnings.'),
126
- ci: z
127
- .boolean()
128
- .optional()
129
- .describe('When true, add CI preflight warnings such as missing variables and mutating steps not allowed by policy.'),
205
+ platform: z.enum(['android', 'ios', 'cross-platform']).optional().describe('Authoring target for platform-aware warnings.'),
206
+ ci: z.boolean().optional().describe('Add CI preflight warnings (missing variables, mutating steps policy forbids).'),
130
207
  },
131
- }, async ({ sessionId, flow, flowYaml, platform, ci }) => {
132
- const root = sessionId ? sessions.get(sessionId)?.root : undefined;
208
+ }, async ({ sessionId, projectRoot, flow, flowYaml, platform, ci }) => {
209
+ const root = await flowRoot(server, sessionId ? sessions.get(sessionId)?.root : undefined, projectRoot);
133
210
  const src = loadFlowSource(root, flow, flowYaml);
134
211
  if (src.error)
135
- return qaError({
136
- what: src.error,
137
- changedState: false,
138
- retrySafe: true,
139
- nextSteps: ['Pass an existing flow name/path or inline flowYaml.'],
140
- });
212
+ return flowSourceError(src);
141
213
  const { flow: parsed, errors } = parseFlow(src.yamlText);
142
214
  if (errors.length || !parsed) {
143
215
  return qaError({
144
216
  what: `Flow is invalid (${errors.length} error${errors.length === 1 ? '' : 's'})`,
145
217
  changedState: false,
146
218
  retrySafe: true,
219
+ failureCode: 'INVALID_FLOW',
147
220
  nextSteps: ['Fix the listed errors and re-check.'],
148
221
  }, { source: src.source, errors });
149
222
  }
@@ -151,11 +224,11 @@ export function registerFlow(server, sessions) {
151
224
  const STRUCTURED_KINDS = new Set(['tap', 'assertVisible', 'assertNotVisible', 'scrollTo', 'waitForVisible', 'inputText']);
152
225
  const warnings = [];
153
226
  if (!parsed.appId)
154
- warnings.push("No appId — a prepareTarget step will rely on the session's prepared appId.");
227
+ warnings.push("No appId, so a prepareTarget step will rely on the session's prepared appId.");
155
228
  if (allSteps.some((s) => s.kind === 'tap' && s.selector.startsWith('@')))
156
- warnings.push('Uses @ref selectors — refs are run-time only; prefer text/id selectors for durable flows.');
229
+ warnings.push('Uses @ref selectors. Refs are run-time only; prefer text/id selectors for durable flows.');
157
230
  if (src.yamlText.includes('${'))
158
- warnings.push('Uses ${VARIABLES} — provide them via qa_flow_run { variables } or the environment.');
231
+ warnings.push(`Uses \${VARIABLES}. Provide them via qa_flow_run { variables }, stored session inputs, or ${FLOW_ENV_PREFIX}* environment variables (other env names are never read).`);
159
232
  const flowOcrSteps = ocrSteps(parsed);
160
233
  if (flowOcrSteps.length) {
161
234
  warnings.push(`Uses OCR visual-provider steps (${flowOcrSteps.map((s) => `${s.step}:${s.kind}`).join(', ')}); qa_flow_run will require provider consent and a configured ocrCommand.`);
@@ -167,10 +240,10 @@ export function registerFlow(server, sessions) {
167
240
  const inferredPlatform = platform ?? (driverKind === 'simulator' || driverKind === 'wda' ? 'ios' : driverKind === 'direct' ? 'android' : undefined);
168
241
  const usesStructured = allSteps.some((s) => STRUCTURED_KINDS.has(s.kind));
169
242
  if (driverKind === 'simulator' && parsed.mode === 'structured') {
170
- warnings.push('mode:structured on the iOS simulator backend — tap/assertVisible/inputText need a UI tree (unavailable). Use mode: visual with image/visual steps.');
243
+ warnings.push('mode:structured on the iOS simulator backend. tap/assertVisible/inputText need a UI tree (unavailable). Use mode: visual with image/visual steps.');
171
244
  }
172
245
  if (parsed.mode === 'visual' && usesStructured) {
173
- warnings.push('mode:visual but the flow uses structured steps (tap/assertVisible/…) — those need a UI tree and will fail on a visual-only screen.');
246
+ warnings.push('mode:visual but the flow uses structured steps (tap/assertVisible/…). Those need a UI tree and will fail on a visual-only screen.');
174
247
  }
175
248
  for (const s of allSteps) {
176
249
  const text = s.kind === 'tap'
@@ -212,8 +285,10 @@ export function registerFlow(server, sessions) {
212
285
  if (root) {
213
286
  for (const s of allSteps) {
214
287
  if (s.kind === 'tapImage' || s.kind === 'assertImage') {
215
- const p = isAbsolute(s.template) ? s.template : join(root, s.template);
216
- if (!existsSync(p))
288
+ const p = withinRootOrNull(root, s.template);
289
+ if (!p)
290
+ warnings.push(`image template outside the project root: ${s.template}; qa_flow_run will refuse it.`);
291
+ else if (!existsSync(p))
217
292
  warnings.push(`image template not found: ${s.template} (resolve relative to the project root).`);
218
293
  }
219
294
  }
@@ -231,45 +306,35 @@ export function registerFlow(server, sessions) {
231
306
  fixtures: parsed.fixtures,
232
307
  warnings,
233
308
  lintFindings,
234
- }, `✅ flow "${parsed.name}" is valid — ${parsed.steps.length} steps (mode=${parsed.mode}${parsed.setup.length ? `, ${parsed.setup.length} setup` : ''}${parsed.teardown.length ? `, ${parsed.teardown.length} teardown` : ''})${warnings.length ? `\nwarnings:\n - ${warnings.join('\n - ')}` : ''}`);
309
+ }, `✅ flow "${parsed.name}" is valid: ${parsed.steps.length} steps (mode=${parsed.mode}${parsed.setup.length ? `, ${parsed.setup.length} setup` : ''}${parsed.teardown.length ? `, ${parsed.teardown.length} teardown` : ''})${warnings.length ? `\nwarnings:\n - ${warnings.join('\n - ')}` : ''}`);
235
310
  });
236
311
  server.registerTool('qa_flow_run', {
237
312
  title: 'Run a flow (or preview its execution plan)',
238
- description: 'Run a Swipium flow against the prepared app and report the result — or, with mode:"plan", preview the execution WITHOUT touching a device. mode:"run" (default) drives steps server-side: tap/tapAt/tapImage, inputText (incl. { into, text } to focus a named field), assertVisible/assertNotVisible/assertImage/assertVisual/assertDiff, swipe (device-relative { direction, area, distance }), scrollTo, press, openUrl, wait/waitForIdle/waitForVisible, clearOverlay, networkOffline/Online, restartApp, seed, note, screenshot — with optional setup/teardown (teardown always runs) and flow mode structured|visual|auto. Fail-fast, NO auto-retry of mutating steps; on failure returns the exact failing step + screenshot + typed failureCode + health. mode:"plan" (read-only) compiles the flow into a deterministic backend-specific automation plan: per backend, can it run, each step classified native | supported_with_fallback | visual_only | unsupported with the missing capability, and an agent-repeatable ready | candidate | blocked message. Provide `flow` (name/path) or `flowYaml`, plus `variables` for any ${VAR}. Related: qa_flow_check is the STATIC LINT of the YAML; mode:"plan" here is the read-only execution preview.',
313
+ description: 'Run a flow on the prepared app (mode:"run", default) or preview it without a device (mode:"plan"). run executes steps ' +
314
+ 'server-side with setup/teardown, fail-fast, no auto-retry of mutating steps (consent-gated, like OCR steps); a failure ' +
315
+ 'returns the step, screenshot, failureCode, and health. plan reports, per backend (Android, iOS simulator, iOS WDA, ' +
316
+ 'Appium), whether each step is native / fallback / visual_only / unsupported. Pass flow (name/path) or flowYaml, plus ' +
317
+ 'variables for ${VAR}. Steps: docs/tools.md#flow-steps.',
239
318
  inputSchema: {
240
- mode: z
241
- .enum(['plan', 'run'])
242
- .optional()
243
- .describe("run (default): execute the flow on the session's prepared device. plan: READ-ONLY execution preview — no device is touched; compiles the flow against backend capabilities (Android direct, iOS raw simulator, iOS WDA, Appium UiAutomator2/XCUITest) and reports what would run, what falls back, and what is blocked."),
244
- sessionId: z
319
+ mode: z.enum(['plan', 'run']).optional(),
320
+ sessionId: z.string().optional().describe('Required for run; plan: resolves names + marks the attached backend.'),
321
+ projectRoot: z
245
322
  .string()
246
323
  .optional()
247
- .describe('REQUIRED for mode:"run" (needs a prepared device). Optional for mode:"plan" — it resolves flow names against the session\'s project root and marks the attached backend.'),
248
- flow: z.string().optional().describe('Flow name under .swipium/flows, or a path to a .yaml file.'),
249
- flowYaml: z.string().optional().describe('Inline flow YAML (instead of a file).'),
324
+ .describe('plan: absolute app root for flow names (default: session root > MCP roots > env > cwd).'),
325
+ flow: z.string().optional().describe('Flow name under .swipium/flows, or a .yaml path.'),
326
+ flowYaml: z.string().optional().describe('Inline flow YAML.'),
250
327
  variables: z
251
328
  .record(z.string())
252
329
  .optional()
253
- .describe('(mode:"run" only) Values for ${VAR} placeholders (merged over process.env). Credential-looking names are auto-redacted.'),
254
- repeat: z
255
- .number()
256
- .int()
257
- .min(1)
258
- .max(10)
259
- .optional()
260
- .describe('(mode:"run" only) Run the flow N times for flake detection (default 1). Reports pass-rate + a deterministic-pass | deterministic-fail | flaky classification.'),
261
- consentId: z
262
- .string()
263
- .optional()
264
- .describe('(mode:"run" only) Consent token returned when the flow contains mutating steps or external visual-provider steps such as OCR.'),
265
- approve: z
266
- .boolean()
267
- .optional()
268
- .describe('(mode:"run" only) Set true with consentId to execute after reviewing the exact flow hash, mutation list, and external provider command if present.'),
330
+ .describe('run: ${VAR} values (win over stored session inputs, then SWIPIUM_* env only); credential-like names redacted.'),
331
+ repeat: z.number().int().min(1).max(10).optional().describe('run: repeat N times for flake classification (default 1).'),
332
+ consentId: z.string().optional().describe('run: consent for mutating / OCR steps.'),
333
+ approve: z.boolean().optional(),
269
334
  backend: z
270
335
  .enum(['android-direct', 'ios-raw-simulator', 'ios-wda', 'appium-uiautomator2', 'appium-xcuitest'])
271
336
  .optional()
272
- .describe('(mode:"plan" only) Plan only for this backend. Omit to plan across all five (the pre-run "can this run on Android and iOS?" answer).'),
337
+ .describe('plan: only this backend (default all five).'),
273
338
  appium: z
274
339
  .object({
275
340
  automationName: z.string().optional(),
@@ -277,12 +342,12 @@ export function registerFlow(server, sessions) {
277
342
  webviewContextsAvailable: z.boolean().optional(),
278
343
  })
279
344
  .optional()
280
- .describe('(mode:"plan" only) Appium session hints that refine the Appium backend capability picture (driver family, WebView contexts).'),
345
+ .describe('plan: Appium session hints.'),
281
346
  },
282
- }, async ({ mode, sessionId, flow, flowYaml, variables, repeat, consentId, approve, backend, appium }) => {
347
+ }, async ({ mode, sessionId, projectRoot, flow, flowYaml, variables, repeat, consentId, approve, backend, appium }) => {
283
348
  const effectiveMode = mode ?? 'run';
284
349
  const notes = [];
285
- // ---- mode:"plan" — read-only execution preview (merged twin, 1.5.0). ----
350
+ // ---- mode:"plan": read-only execution preview (merged twin, 1.5.0). ----
286
351
  if (effectiveMode === 'plan') {
287
352
  const ignored = [
288
353
  variables !== undefined && 'variables',
@@ -291,30 +356,26 @@ export function registerFlow(server, sessions) {
291
356
  approve !== undefined && 'approve',
292
357
  ].filter((x) => !!x);
293
358
  if (ignored.length)
294
- notes.push(`ignored parameter(s) not applicable to mode:"plan": ${ignored.join(', ')} — re-run with mode:"run" to execute`);
359
+ notes.push(`ignored parameter(s) not applicable to mode:"plan": ${ignored.join(', ')}; re-run with mode:"run" to execute`);
295
360
  const session = sessionId ? sessions.get(sessionId) : undefined;
296
- const root = session?.root;
361
+ const root = await flowRoot(server, session?.root, projectRoot);
297
362
  const src = loadFlowSource(root, flow, flowYaml);
298
363
  if (src.error)
299
- return qaAnnotate(qaError({
300
- what: src.error,
301
- changedState: false,
302
- retrySafe: true,
303
- nextSteps: ['Pass an existing flow name/path or inline flowYaml.'],
304
- }), notes);
364
+ return qaAnnotate(flowSourceError(src), notes);
305
365
  const { flow: parsed, errors } = parseFlow(src.yamlText);
306
366
  if (errors.length || !parsed) {
307
367
  return qaAnnotate(qaError({
308
- what: `Flow is invalid (${errors.length} error${errors.length === 1 ? '' : 's'}) — run qa_flow_check`,
368
+ what: `Flow is invalid (${errors.length} error${errors.length === 1 ? '' : 's'}), run qa_flow_check`,
309
369
  changedState: false,
310
370
  retrySafe: true,
371
+ failureCode: 'INVALID_FLOW',
311
372
  nextSteps: ['Fix the listed errors and re-plan.'],
312
373
  }, { source: src.source, errors }), notes);
313
374
  }
314
375
  const appiumHints = appium ? { ...appium } : undefined;
315
376
  const attachedBackend = session?.driver?.kind ? backendForDriverKind(session.driver.kind, appiumHints) : undefined;
316
377
  // Which backends to plan for: an explicit one, else all concrete backends (so the answer covers
317
- // Android + iOS even with no device attached — the pre-run "can this run?" product question).
378
+ // Android + iOS even with no device attached: the pre-run "can this run?" product question).
318
379
  const targets = backend ? [backend] : [...ALL_BACKENDS];
319
380
  if (attachedBackend && attachedBackend !== 'unknown' && !targets.includes(attachedBackend))
320
381
  targets.unshift(attachedBackend);
@@ -346,19 +407,28 @@ export function registerFlow(server, sessions) {
346
407
  };
347
408
  });
348
409
  const summaryLines = plans.map((p) => {
349
- const head = `${STATUS_ICON[p.status] ?? '•'} ${p.backend}${p.attached ? ' (attached)' : ''}: ${p.status} — ${p.supportedSteps} supported, ${p.unsupportedSteps} unsupported`;
410
+ const head = `${STATUS_ICON[p.status] ?? '•'} ${p.backend}${p.attached ? ' (attached)' : ''}: ${p.status}, ${p.supportedSteps} supported, ${p.unsupportedSteps} unsupported`;
350
411
  return `${head}\n ${p.agentMessage}`;
351
412
  });
352
- const summary = `flow "${parsed.name}" — automation plan across ${plans.length} backend(s) (mode:"plan" — read-only, nothing executed):\n${summaryLines.join('\n')}`;
413
+ const summary = `flow "${parsed.name}": automation plan across ${plans.length} backend(s) (mode:"plan", read-only, nothing executed):\n${summaryLines.join('\n')}`;
353
414
  return qaAnnotate(qaOk({ flow: parsed.name, appId: parsed.appId ?? null, mode: parsed.mode, source: src.source, plans }, summary), notes);
354
415
  }
355
- // ---- mode:"run" — execute on the prepared device. ----
356
- const runIgnored = [backend !== undefined && 'backend', appium !== undefined && 'appium'].filter((x) => !!x);
416
+ // ---- mode:"run": execute on the prepared device. ----
417
+ const runIgnored = [
418
+ backend !== undefined && 'backend',
419
+ appium !== undefined && 'appium',
420
+ projectRoot !== undefined && 'projectRoot',
421
+ ].filter((x) => !!x);
357
422
  if (runIgnored.length)
358
- notes.push(`ignored parameter(s) not applicable to mode:"run": ${runIgnored.join(', ')} — they refine the mode:"plan" preview`);
423
+ notes.push(`ignored parameter(s) not applicable to mode:"run": ${runIgnored.join(', ')}; they refine the mode:"plan" preview`);
359
424
  const session = sessionId ? sessions.get(sessionId) : undefined;
360
- const { driver } = session ? await getDriver(session) : { driver: undefined };
425
+ if (sessionId && !session)
426
+ return qaAnnotate(unknownSessionError(sessionId), notes);
427
+ const { driver, blocked } = session ? await getDriver(session) : { driver: undefined, blocked: undefined };
361
428
  if (!session || !driver) {
429
+ const refused = blockedDeviceResult(blocked);
430
+ if (refused)
431
+ return qaAnnotate(refused, notes);
362
432
  return qaAnnotate(qaError({
363
433
  what: sessionId
364
434
  ? 'No device attached to this session'
@@ -370,22 +440,18 @@ export function registerFlow(server, sessions) {
370
440
  }
371
441
  const src = loadFlowSource(session.root, flow, flowYaml);
372
442
  if (src.error)
373
- return qaAnnotate(qaError({
374
- what: src.error,
375
- changedState: false,
376
- retrySafe: true,
377
- nextSteps: ['Pass an existing flow name/path or inline flowYaml.'],
378
- }), notes);
443
+ return qaAnnotate(flowSourceError(src), notes);
379
444
  const { flow: parsed, errors } = parseFlow(src.yamlText);
380
445
  if (errors.length || !parsed) {
381
446
  return qaAnnotate(qaError({
382
- what: `Flow is invalid — run qa_flow_check first`,
447
+ what: `Flow is invalid. Run qa_flow_check first`,
383
448
  changedState: false,
384
449
  retrySafe: true,
450
+ failureCode: 'INVALID_FLOW',
385
451
  nextSteps: ['Fix the flow and re-run.'],
386
452
  }, { errors }), notes);
387
453
  }
388
- // Backend/mode gate (NEXT-PLAN: catch unsupported combos before running). A structured-mode
454
+ // Backend/mode gate (catch unsupported combos before running). A structured-mode
389
455
  // flow needs a UI tree, which the iOS simulator and a visual-fallback session don't have.
390
456
  if (parsed.mode === 'structured' && (driver.kind === 'simulator' || session.mode === 'visual-fallback')) {
391
457
  return qaAnnotate(qaError({
@@ -449,7 +515,14 @@ export function registerFlow(server, sessions) {
449
515
  }), notes);
450
516
  }
451
517
  }
452
- const affects = flowMutationAffects(parsed, session, src.source, runs, externalProviders);
518
+ // Stored session inputs (qa_agent needs_input / first-run credentials) fill ${VAR}s so a
519
+ // generated flow replays without the agent re-sending raw secrets; explicit args win.
520
+ const sessionInputs = sessions.inputVariables(session);
521
+ const runVariables = { ...sessionInputs, ...(variables ?? {}) };
522
+ const usedSessionInputs = Object.keys(sessionInputs).filter((k) => variables?.[k] == null);
523
+ if (usedSessionInputs.length)
524
+ notes.push(`using stored session input(s): ${usedSessionInputs.join(', ')} (values not shown)`);
525
+ const affects = flowMutationAffects(parsed, session, src.source, runs, externalProviders, runVariables);
453
526
  const mutationRisk = flowMutationRisk(affects);
454
527
  let mutationConsent = {
455
528
  required: false,
@@ -475,11 +548,17 @@ export function registerFlow(server, sessions) {
475
548
  return qaAnnotate(requireConsent({
476
549
  action: 'flow_mutation_run',
477
550
  risk: mutationRisk,
478
- exactCommand: `qa_flow_run ${parsed.name} (${[...affects.mutations.map((m) => `${m.step}:${m.kind}`), ...externalProviderSteps.map((s) => `${s.step}:${s.kind}`)].join(', ')})${externalCommand}`,
551
+ exactCommand: `qa_flow_run ${parsed.name} (${[...affects.mutations.map((m) => `${m.step}:${m.kind}`), ...externalProviderSteps.map((s) => `${s.step}:${s.kind}`)].join(', ')})${externalCommand}${mutationCommandLines(affects).length ? `\n${mutationCommandLines(affects).join('\n')}` : ''}`,
479
552
  affects,
480
- explain: affects.externalProviders.length
553
+ explain: (affects.externalProviders.length
481
554
  ? `Run flow "${parsed.name}" with external visual provider steps? OCR steps pass ${runs * externalProviderSteps.length} screenshot(s) to the configured provider and any mutating steps can change app/device/test state.`
482
- : `Run mutating flow "${parsed.name}"? Mutating steps can change app/device/test state and will be recorded in the mutation ledger.`,
555
+ : `Run mutating flow "${parsed.name}"? Mutating steps can change app/device/test state and will be recorded in the mutation ledger.`) +
556
+ (affects.mutations.some((m) => m.kind === 'seed')
557
+ ? " Seed steps run the exact command/URL shown, taken from the repo's fixture declarations. These are repo-supplied and UNREVIEWED by Swipium; approve only if you trust them."
558
+ : '') +
559
+ (affects.mutations.some((m) => m.kind === 'openUrl')
560
+ ? ' openUrl steps interpolate variables into a URL that leaves the test harness, so check the destination.'
561
+ : ''),
483
562
  }), notes);
484
563
  }
485
564
  mutationConsent = { required: true, consentId, approved: true, payloadHash };
@@ -499,7 +578,7 @@ export function registerFlow(server, sessions) {
499
578
  const b = sessions.budgetStop(session);
500
579
  if (b)
501
580
  break;
502
- results.push(await runFlow(sessions, session, driver, parsed, { variables, mutationConsent }));
581
+ results.push(await runFlow(sessions, session, driver, parsed, { variables: runVariables, mutationConsent }));
503
582
  }
504
583
  const flake = classifyFlakeResults(results, runs);
505
584
  return qaAnnotate(qaOk({
@@ -516,17 +595,18 @@ export function registerFlow(server, sessions) {
516
595
  reason: r.reason,
517
596
  failureCode: r.failureCode,
518
597
  })),
519
- }, `flow "${parsed.name}" × ${results.length}: ${flake.passed} passed, ${flake.failed} failed → ${flake.classification === 'flaky' ? '⚠ FLAKY' : flake.classification === 'deterministic-pass' ? '✅ deterministic-pass' : '❌ deterministic-fail'} (${flake.passRate}% pass-rate); triage=${flake.triage.likelyCause}`), notes);
598
+ }, `flow "${parsed.name}" × ${results.length}: ${flake.passed} passed, ${flake.failed} failed > ${flake.classification === 'flaky' ? '⚠ FLAKY' : flake.classification === 'deterministic-pass' ? '✅ deterministic-pass' : '❌ deterministic-fail'} (${flake.passRate}% pass-rate); triage=${flake.triage.likelyCause}`), notes);
520
599
  }
521
- const result = await runFlow(sessions, session, driver, parsed, { variables, mutationConsent });
600
+ const result = await runFlow(sessions, session, driver, parsed, { variables: runVariables, mutationConsent });
522
601
  const head = `flow "${result.name}" ${result.passed ? '✅ PASSED' : `❌ FAILED at step ${result.failedAtStep} (${result.reason})`} ` +
523
- `— ${result.steps.filter((s) => s.ok).length}/${result.steps.length} steps in ${Math.round(result.durationMs / 100) / 10}s` +
602
+ `: ${result.steps.filter((s) => s.ok).length}/${result.steps.length} steps in ${Math.round(result.durationMs / 100) / 10}s` +
524
603
  (result.appHealth ? `\nhealth: native=${result.nativeHealth} app=${result.appHealth}` : '');
525
604
  const stepLines = result.steps
526
- .map((s) => `${s.ok ? '✓' : '✗'} ${s.index}. ${s.summary}${s.detail ? ` — ${s.detail}` : ''}${s.screenshotUri ? `\n evidence: ${s.screenshotUri}` : ''}`)
605
+ .map((s) => `${s.ok ? '✓' : '✗'} ${s.index}. ${s.summary}${s.detail ? `: ${s.detail}` : ''}${s.screenshotUri ? `\n evidence: ${s.screenshotUri}` : ''}`)
527
606
  .join('\n');
528
- // A failed flow is a structured result, not a protocol error — the agent should record it.
529
- return qaAnnotate(qaOk({ ...result }, `${head}\n${stepLines}`), notes);
607
+ // A failed flow is a structured result, not a protocol error. The agent should record it.
608
+ const nextSteps = result.passed ? undefined : failedFlowNextSteps(parsed.name, flow, result.failedAtStep, result.failureCode);
609
+ return qaAnnotate(qaOk({ ...result, ...(nextSteps ? { nextSteps } : {}) }, `${head}\n${stepLines}${nextSteps ? `\nnext:\n - ${nextSteps.join('\n - ')}` : ''}`), notes);
530
610
  });
531
611
  }
532
612
  //# sourceMappingURL=flow.js.map