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
package/docs/tools.md CHANGED
@@ -1,219 +1,879 @@
1
1
  # Tool Reference
2
2
 
3
- Swipium 1.5.0 exposes 60 public MCP tools. The intended default entry point is `qa_test_this`.
3
+ Swipium exposes 55 public MCP tools, 5 MCP prompts, and 3 MCP resource templates. It tests apps on local Android Emulators and iOS Simulators only; physical devices are out of scope (see [physical-devices.md](physical-devices.md)).
4
4
 
5
- This is the production public surface. Lower-level visual, seeded-state, report-history, and issue-lifecycle helpers from earlier development builds were merged into canonical workflows or deferred from public MCP registration; use the grouped tools below as the supported entry points.
5
+ This page is the reference: conventions, one section per tool, the failure-code catalog, and the 1.5.0 migration table. Cross-cutting ideas (sessions and jobs, project root, consent, secrets, iOS modes, devices, glossary) are in [concepts.md](concepts.md); the flow file format and CI policy are in [flows.md](flows.md); environment variables are in the [README](../README.md#configuration--environment-variables).
6
6
 
7
- ## Start
7
+ **Contents**
8
+
9
+ - [Entry points](#entry-points) and [Conventions](#conventions): annotations, common parameters, response modes, the result envelope, and argument checking.
10
+ - [Tool index](#tool-index): one row per tool.
11
+ - Tool reference by capability group: [Start](#start), [Setup](#setup), [Build](#build), [Device](#device), [Drive](#drive), [Run](#run), [App map](#app-map), [Feature](#feature), [Flows](#flows), [Generate](#generate), [Test suite](#test-suite), [Issues](#issues), [First run](#first-run).
12
+ - [MCP resources and prompts](#mcp-resources-and-prompts), [Failure codes](#failure-codes), and [Migrating from 1.5.0](#migrating-from-150).
13
+
14
+ ## Entry points
15
+
16
+ The default entry point is `qa_test_this`. The server sends the same rules as MCP `instructions` when a client connects, and `qa_status` without a `sessionId` returns them as structured data (see [qa_status](#qa_status)). The polling loop, job states, and `needs_input` handling are in [Sessions and jobs](concepts.md#sessions-and-jobs).
17
+
18
+ | User intent | First call |
19
+ | --- | --- |
20
+ | "Test it" | `qa_test_this {mode:"execute"}` |
21
+ | "How do I use Swipium?" | `qa_status` with no arguments |
22
+ | "Check my setup" | `qa_doctor` |
23
+ | "Test the X feature" | `qa_app_map_feature_scope`, then `qa_test_feature` |
24
+ | "Run a release gate" | `qa_test_this {mode:"execute", goal:"release_gate"}` or `qa_mobile_audit {profile:"release_gate"}` |
25
+ | "Drive the app myself" | `qa_start_session`, then `qa_prepare_target` or `qa_prepare_ios_target` |
26
+ | "Find or build an artifact" | `qa_resolve_artifact`, then `qa_build` |
27
+ | "Turn this run into automation" | `qa_generate` with `target:"flow"`, `"suite"`, or `"appium"` |
28
+ | "Export results for CI" | `qa_report` with `format`, or the `swipium report` CLI |
8
29
 
9
- Use these tools to orient the agent, start autopilot work, poll jobs, handle blockers, and fetch artifacts.
30
+ ## Conventions
10
31
 
11
- | Tool | What it does | Use when |
32
+ ### Annotations
33
+
34
+ Every tool carries explicit MCP annotations, so clients can auto-approve the read-only ones. `openWorldHint` is `false` everywhere, because Swipium only talks to local simulators, local toolchains, and the local project.
35
+
36
+ | Kind | Annotations | Tools |
12
37
  | --- | --- | --- |
13
- | `qa_agent_brief` | Returns the recommended orchestration rules for agents. | The agent needs the correct first call, polling behavior, report behavior, or blocker handling rules. |
14
- | `qa_capabilities` | Lists the public tool surface grouped by purpose. | The agent or user needs to discover available Swipium capabilities. |
15
- | `qa_test_this` | Autopilot for "test it": resolves the project, finds or builds an artifact, prepares a simulator, runs smoke or exploration, reports results, and can generate suite output. | The user gives a low-context request such as "test this app". |
16
- | `qa_job_status` | Polls a long-running job started by `qa_test_this` or prepare tools. | A tool returns a `jobId` with status `running`. |
17
- | `qa_status` | Returns compact session status and recommended next action. | The agent needs to recover context during a session. |
18
- | `qa_explain_blocker` | Explains a typed blocker, likely owner, and recovery path. | A run stops with a blocker and the user needs a concise explanation. |
19
- | `qa_continue_from_blocker` | Resumes after user input and registers secret values for redaction. | A blocker asks for credentials, OTP, target choice, or approval data. |
20
- | `qa_next_best_action` | Returns the single best next tool to call (with args) and why, deterministically. | The agent wants the orchestration sequence decided for it. |
21
- | `qa_get_artifact` | Fetches artifact metadata or contents by `swipium://` URI. | A report, screenshot, dump, log, or generated file must be read. |
22
- | `qa_job_cancel` | Cancels a running job and aborts its spawned children. | A long-running job must be stopped early. |
38
+ | Read-only | `readOnlyHint:true, openWorldHint:false` | Marked **RO** in the [index](#tool-index). They do not change the device, the app, the project tree, or durable project memory. In-process session bookkeeping (counters, the last snapshot, health findings) does not count as a change. |
39
+ | Write | `readOnlyHint:false, destructiveHint:false, idempotentHint:false, openWorldHint:false` | Everything not listed elsewhere. |
40
+ | Idempotent write | `readOnlyHint:false, destructiveHint:false, idempotentHint:true, openWorldHint:false` | `qa_job_cancel`, `qa_orientation`, `qa_geolocation`, `qa_network`, `qa_flow_compile`. |
41
+ | Destructive | `readOnlyHint:false, destructiveHint:true, idempotentHint:false, openWorldHint:false` | `qa_ios` (`erase`, `privacy_reset`), `qa_app_control` (`clear_data`, `fresh_start`), `qa_app_map_update` (overwrites entries), `qa_suite_update` (`replace_generated` rewrites curated cases). |
42
+
43
+ Annotations describe the worst case of a tool. [Consent](concepts.md#consent) gates are what actually stop a mutation from running unapproved.
44
+
45
+ ### Common parameters
46
+
47
+ - **`sessionId`**: returned by `qa_test_this` or `qa_start_session`. An unknown `sessionId` returns `INVALID_ARGUMENT` (`Unknown sessionId "…"`) with nothing run.
48
+ - **`projectRoot`**: an absolute path. Tools that work before a session exists (build, artifact, app map, suite, issue ledger, flow check, feature plan) accept it. See [Project root](concepts.md#project-root).
49
+ - **`consentId` / `approve`**: the re-call half of a consent request. See [Consent](concepts.md#consent).
50
+ - **`mode:"plan"`**: on `qa_test_this`, `qa_build`, `qa_generate`, `qa_flow_run`, `qa_test_feature`, `qa_first_run`, and `qa_mobile_audit`, the plan mode is a side-effect-free preview. It is the default everywhere except `qa_generate` and `qa_flow_run`.
51
+
52
+ ### Response modes
53
+
54
+ `responseMode` controls the **text** channel only. `structuredContent` always carries the complete payload in every mode.
55
+
56
+ | Mode | Text channel |
57
+ | --- | --- |
58
+ | `compact` | The summary line plus any `swipium://` URIs (`artifactUri`, `artifactUris`, `screenshotUri`, `reportUri`). No JSON. |
59
+ | `normal` (default) | The summary plus one block of JSON. Fields the summary already rendered are left out of that JSON, and a `renderedAbove` key lists them: `elements` and `diff` for `qa_snapshot`; `elements`, `removed`, `hint`, and `stateChanged` for `qa_act`. `renderedAbove` exists only in the text; it is never in `structuredContent`. |
60
+ | `verbose` | The summary plus the full payload as JSON, including the fields `normal` leaves out (so there is no `renderedAbove`). |
61
+
62
+ The mode is a session setting, set by `qa_start_session` or `qa_test_this` (`responseMode`). Calls without a `sessionId` use the `responseMode` argument when the tool has one, else `normal`. The mode is chosen before a call runs, so `qa_test_this {sessionId, responseMode}` on an existing session takes effect from the next call. Errors always show their heading lines; compact mode drops only the JSON.
63
+
64
+ ### Result envelope
65
+
66
+ A success is `{ok:true, …payload}`. A budget stop is also a success: `{ok:true, stopped:true, reason}` (for example `action budget reached (20/20)`). Some results add `notes[]` (non-fatal remarks, such as parameters ignored for the chosen mode) or `warnings[]`.
67
+
68
+ An error has `isError:true` and this `structuredContent`:
69
+
70
+ | Field | Meaning |
71
+ | --- | --- |
72
+ | `ok` | Always `false`. |
73
+ | `failureCode` | A code from the [catalog](#failure-codes). `UNKNOWN` when the error is not classified. |
74
+ | `what` | One sentence: what went wrong. |
75
+ | `changedState` | Whether anything on the device, app, or project changed before the failure. |
76
+ | `retrySafe` | Whether re-calling unchanged is safe. |
77
+ | `nextSteps` | Concrete recovery steps, usually exact calls. |
78
+ | `commandAttempted`, `artifactUri`, `clientHint` | Optional: the command that failed, evidence (for example a build log), and the stale-client hint. |
79
+
80
+ Triage fields (`bucket`, `owner`, `canSwipiumFix`) are not part of the error contract: get them from `qa_explain_blocker {failureCode}`, which returns them for any code. A `qa_test_this` terminal result lists its `blockers[]` with `failureCode`, `owner`, `retrySafe`, `canSwipiumFix`, `whatItMeans`, and `howToFix` (no `bucket`).
81
+
82
+ The text channel renders the same error as `❌ <what>`, then `changedState=… retrySafe=…`, `next: …`, and `hint: …` lines.
83
+
84
+ ### Unknown arguments and stale clients
85
+
86
+ - **Unknown arguments**: every tool rejects top-level arguments its input schema does not declare, before anything runs. The call returns `INVALID_ARGUMENT` with `unknownArguments` and `acceptedParameters`. For example, `qa_app_control {action:"force_stop", appId:"…"}` is refused: `appId` is not a parameter, and the action always targets the session's app. Deprecated aliases that are still declared (`qa_wda udid`, `qa_suite_generate creativityLevel`, `qa_issue_log until`) are accepted. Nested objects are not checked this way.
87
+ - **Stale clients**: a client started before an upgrade may still send 1.5-era calls. Removed tool names, `qa_ios` with `action:"screenshot"` or `action:"wda_*"`, and `qa_wait` with `for:"job_done"` return `failureCode:"STALE_CLIENT"` with `removedCall`, `replacement` (the call to use), and `clientHint` (restart the client so it reloads the tool list). See [Migrating from 1.5.0](#migrating-from-150). `qa_doctor` with `expectedVersion`, `expectedToolCount`, or `expectedSchemaHash` detects the same condition.
88
+
89
+ ### Jobs and cancellation
90
+
91
+ Long operations return a `jobId` to poll with [qa_job_status](#qa_job_status); cancelled work returns `CANCELLED`. The job lifecycle, status versus `result.state`, long-polling, and cancellation rules are in [Sessions and jobs](concepts.md#sessions-and-jobs).
92
+
93
+ ## Tool index
94
+
95
+ Hints: **RO** read-only, **D** destructive, **I** idempotent write, blank for other writes. **Consent** marks tools with at least one consent-gated action.
96
+
97
+ | Tool | Group | Hints | Consent | Summary |
98
+ | --- | --- | --- | --- | --- |
99
+ | `qa_test_this` | start | | yes | Autopilot: resolve, build, prepare, smoke, explore, report, generate. |
100
+ | `qa_status` | start | RO | | Orientation without a session; session state and `nextBestAction` with one. |
101
+ | `qa_job_status` | start | RO | | Poll or long-poll a background job. |
102
+ | `qa_job_cancel` | start | I | | Cancel a running job and its child processes. |
103
+ | `qa_explain_blocker` | start | RO | | Explain a `failureCode`: meaning, owner, retry safety, fix. |
104
+ | `qa_continue_from_blocker` | start | | | Answer a `needs_input` question and get the resume call. |
105
+ | `qa_get_artifact` | start | RO | | Read a `swipium://` artifact (metadata or contents). |
106
+ | `qa_doctor` | setup | RO | | Check Node, Android, iOS, WDA, and client freshness. |
107
+ | `qa_start_session` | setup | | | Open a session for low-level tools (budget, fixtures, sensitive mode). |
108
+ | `qa_prepare_target` | setup | | yes | Android Emulator: bind or boot, Metro, install, launch. |
109
+ | `qa_prepare_ios_target` | setup | | yes | iOS Simulator: boot, install `.app`, launch, WDA or visual-only. |
110
+ | `qa_ios` | setup | D | yes | iOS Simulator lifecycle (list, boot, install, launch, logs, erase, …). |
111
+ | `qa_wda` | setup | | yes | Diagnose, attach, build, start, or stop WebDriverAgent. |
112
+ | `qa_resolve_target` | build | RO | | Pick a device or simulator; optional project context and workflow plan. |
113
+ | `qa_resolve_artifact` | build | RO | | Find the best installable `.apk`, `.aab`, `.ipa`, or `.app`. |
114
+ | `qa_build` | build | | yes | Propose build commands, or build from source as a job. |
115
+ | `qa_bundletool` | build | | yes | Convert an `.aab` into an installable APK or APK set. |
116
+ | `qa_device_info` | device | RO | | Device facts: model, SDK, ABIs, locale, screen, packages. |
117
+ | `qa_orientation` | device | I | | Set portrait, landscape, or auto rotation (Android). |
118
+ | `qa_geolocation` | device | I | yes | Spoof the GPS location (Android Emulator). |
119
+ | `qa_network` | device | I | yes | Airplane mode on or off, with automatic restore (Android 11+). |
120
+ | `qa_metro` | device | | yes | Status, diagnose, start, or stop the RN/Expo Metro bundler. |
121
+ | `qa_app_control` | device | D | yes | Launch, foreground, background, stop, restart, or wipe the app. |
122
+ | `qa_screen_record` | device | | yes | Record the screen to an mp4 artifact. |
123
+ | `qa_snapshot` | drive | RO | | Structured UI elements (`@eN` refs) with a quality verdict. |
124
+ | `qa_inspect` | drive | RO | | Full attributes of one `@eN` element. |
125
+ | `qa_act` | drive | | | One UI action (tap, type, scroll, …), then observe. |
126
+ | `qa_clear_overlay` | drive | | | Dismiss the keyboard, dialogs, LogBox, sheets, or toasts. |
127
+ | `qa_check_health` | drive | RO | | Crash, ANR, error-boundary, and foreground check. |
128
+ | `qa_screenshot` | drive | | | Screenshot artifact with coordinate-space metadata. |
129
+ | `qa_note` | drive | | | Record a workflow outcome for the report. |
130
+ | `qa_visual` | drive | | yes | Screenshot checks: assert, baseline, diff, OCR find, image find. |
131
+ | `qa_wait` | drive | RO | | Wait for `device_online` or `metro_ready`. |
132
+ | `qa_smoke` | run | | | Launch, baseline health, evidence, and every saved flow. |
133
+ | `qa_explore` | run | | yes | Bounded, safe-by-default exploration job; builds a screen graph. |
134
+ | `qa_report` | run | | | Session report, plus optional CI exports. |
135
+ | `qa_app_map_build` | app-map | | | Build or update `.swipium/app-map.json`. |
136
+ | `qa_app_map_read` | app-map | RO | | Read one compact app-map section. |
137
+ | `qa_app_map_query` | app-map | RO | | Ranked search over features, screens, tests, and code. |
138
+ | `qa_app_map_feature_scope` | app-map | RO | | Turn a feature name into a focused test scope. |
139
+ | `qa_app_map_update` | app-map | D | | Targeted, provenance-tracked app-map edits. |
140
+ | `qa_test_feature` | feature | | yes | Plan or execute a test of one named feature. |
141
+ | `qa_flow_check` | flows | RO | | Statically validate a flow. |
142
+ | `qa_flow_run` | flows | | yes | Run a flow, or preview it per backend. |
143
+ | `qa_flow_compile` | flows | I | | Compile a POM suite on disk into runnable flows. |
144
+ | `qa_flow_repair` | flows | | | Suggest or apply a stronger locator for a failed step. |
145
+ | `qa_generate` | generate | | yes | Flow, page objects, POM suite, test cases, or Appium code from a run. |
146
+ | `qa_suite_read` | test-suite | RO | | Read the canonical suite `.swipium/test-suite.json`. |
147
+ | `qa_suite_update` | test-suite | D | | Merge cases into the canonical suite. |
148
+ | `qa_suite_generate` | test-suite | | | Generate canonical cases from a recorded run. |
149
+ | `qa_suite_export` | test-suite | | | Export the suite as markdown, yaml, json, or junit. |
150
+ | `qa_suite_lint` | test-suite | RO | | Lint the suite and generated page objects. |
151
+ | `qa_issue_log` | issues | | | The project issue ledger: history, log, fix, verify, suppress, metrics. |
152
+ | `qa_mobile_audit` | issues | | yes | Plan or execute a named release audit profile. |
153
+ | `qa_first_run` | first-run | | | Get past login, sign-up, OTP, onboarding, permission, or paywall screens. |
154
+
155
+ ## Start
156
+
157
+ Autopilot, orientation, job polling, blockers, and artifacts.
158
+
159
+ ### qa_test_this
160
+
161
+ Autopilot for a low-context request such as "test this app". It resolves the project, finds or builds an artifact, picks a simulator, then plans or executes prepare > smoke > (explore) > report > (suite).
162
+
163
+ - **`mode`**: `plan` (default) has no side effects and returns the plan, preconditions, and any consent it will need. `execute` returns `state:"running"` and a `jobId` at once. `interactive` asks the credentials question up front (when the project likely has a login and no credentials are available) and then runs as a job like `execute`. `waitForCompletion:true` blocks up to `timeoutMs` (default 120000) and returns the terminal result directly.
164
+ - **`goal`** sets default flags; explicit `explore`, `generateSuite`, and `stopOnNeedsInput` win.
165
+
166
+ | goal | Explore | Suite | Stops for input | Notes |
167
+ | --- | --- | --- | --- | --- |
168
+ | (none) | no | attempted | no | Smoke, then an attempt at a POM suite (skipped honestly when no actions were recorded). Not the fastest path. |
169
+ | `smoke` | no | no | no | The fastest path. Same as `fastSmoke:true`. |
170
+ | `explore` | yes | no | no | Maps reachable workflows. |
171
+ | `create_automation_suite` | yes | yes | no | |
172
+ | `release_gate` | yes | no | no | Adds the readiness and release-gate summary. |
173
+ | `test_login` | no | no | yes | Stops for credentials when none are available. |
174
+ | `reproduce_bug` | yes | no | no | Focus with `goalText`. |
175
+
176
+ - **Other parameters**: `platform` (`android` or `ios`, default inferred), `device`, `buildIfNeeded` (default true), `allowOutsideRoot`, `fastSmoke`, `responseMode`, `consentId`/`approve`. `preferRealDevice:true` always returns `PHYSICAL_DEVICE_UNSUPPORTED`, even when no phone is connected (see [Devices](concepts.md#devices)).
177
+ - **Consent**: one combined `test_this_plan` consent covers build, boot, and install; its risk is the highest of its steps. Its envelope carries `sessionId`, so the approving re-call reuses the session.
178
+ - **Terminal states** (in the `qa_job_status` result): `completed`, `blocked`, `unsafe`, or `needs_input`. Every terminal state writes a report. The result keeps the report compact (`reportSummary`, `reportUri`, suite and app-map counts) and adds `attempted`, `workaroundsAttempted`, `artifactChoice`, `targetChoice`, `blockers[]`, and `nextRecommendedAction`.
179
+ - **needs_input**: the run stopped on one question it was asked to stop for (`stopOnNeedsInput`, `goal:"test_login"`, or `interactive`), such as a login form that needs credentials. The result carries `needsInput` (the question, its fields, and a `resume` call), and `nextRecommendedAction` is that call. When the question can be asked before any work starts, `qa_test_this` returns `state:"needs_input"` directly, with no `jobId`. Without those flags, the run completes with pre-login coverage and returns the question as `optionalQuestion`. Answering "test pre-login only" sets `loginOutOfScope:true` for the session.
180
+ - **Resumes**: a blocker resume replays the original `goal`, `goalText`, and flags. Plan steps that route back through `qa_test_this` (a build or an `.aab` conversion) carry `mode:"execute"` and the original goal.
181
+ - **iOS without WebDriverAgent**: the default run skips suite generation and exploration, records a workaround, and runs a visual-only smoke. Only explicitly requested WDA work fails with `WDA_UNREACHABLE`: the `generateSuite` or `explore` flags, or goals `create_automation_suite`, `explore`, `reproduce_bug`, and `test_login`. Its `nextSteps` include a `goal:"smoke"` call.
182
+ - **Consent retries**: an unknown, used, or expired `consentId` is never silently replaced. The result says `consent <id> unknown or expired; new challenge issued` in `consentNote` (with `previousConsentId`) and returns a new challenge.
183
+ - **Failure codes**: `PROJECT_ROOT_UNRESOLVED`, `PROJECT_ROOT_EMPTY`, `NOT_MOBILE_PROJECT`, `NO_BUILD_ARTIFACT`, `BUILD_FAILED` and the typed build codes, `PHYSICAL_DEVICE_UNSUPPORTED`, `ADB_NOT_FOUND`, `NO_DEVICE`, `WDA_UNREACHABLE`, `IPA_NEEDS_REAL_DEVICE`.
184
+
185
+ ### qa_status
186
+
187
+ - **Without `sessionId`**: first-call orientation, the same rules the server sends as MCP `instructions`: `{orientation:true, swipiumVersion, tools, prompts, firstCall, polling{tool, args, until, terminalStates}, report, goals, rules{needsInput, blocker, consent, stop, iosVisualOnly, appMap}, capabilityGroups[{group, purpose, tools}], nextBestAction}`. Call it when the client dropped the server instructions or the agent lost context.
188
+ - **With `sessionId`**: `{sessionId, root, device, appId, mode, budgetRemaining, counters, recordedActions, findings, notes, workarounds, inputsProvided, readiness, lastJob, nextBestAction}`. `goal` biases the recommendation. After a restart with no live driver, `mode` and the platform come from the persisted `driverKind`.
189
+ - **`nextBestAction`** is `{tool, args, why}`. The first matching rung wins, and every rung checks state that the recommended call changes, so following it never loops:
190
+ 1. A job is running: `qa_job_status`.
191
+ 2. The last `qa_test_this` job ended and nothing ran after it. Blocked or unsafe and not yet explained: `qa_explain_blocker {failureCode, sessionId}`. `needs_input` already answered with `qa_continue_from_blocker`: `qa_test_this` again. Otherwise the job's own `nextRecommendedAction`, unless it is already done.
192
+ 3. No device is bound: `qa_test_this`.
193
+ 4. No app is running: `qa_prepare_ios_target` (iOS) or `qa_prepare_target`.
194
+ 5. No smoke has run and no actions are recorded: `qa_smoke`. A smoke counts once its milestone is persisted, not by counting actions.
195
+ 6. Findings exist and no report is newer: `qa_report`.
196
+ 7. A clean run has recorded actions but no generated assets: `qa_generate {target:"suite"}`.
197
+ 8. No report is newer than the last activity: `qa_report`.
198
+ 9. Otherwise: `qa_get_artifact` on the latest report. The run is done.
199
+
200
+ ### qa_job_status
201
+
202
+ Polls a job. Parameters: `sessionId`, `jobId`, `waitMs` (0 to 120000; long-polls until the job leaves `running`). Returns `{jobId, kind, status, progress, progressDetail, error, result, artifactUris}`, plus `waited:{waitedMs, timedOut}` when `waitMs` is set. See [Sessions and jobs](concepts.md#jobs).
203
+
204
+ ### qa_job_cancel
205
+
206
+ Cancels a running job and aborts its child processes. Returns `{jobId, cancelled}`; `cancelled:false` means the job had already finished or is unknown. See [Cancellation](concepts.md#cancellation).
207
+
208
+ ### qa_explain_blocker
209
+
210
+ Explains any code in the catalog: `{failureCode, bucket, owner, severity, retrySafe, canSwipiumFix, whatItMeans, whoFixesIt, howToFix, context}`. This is the source for a code's bucket, owner, and `canSwipiumFix`. Parameters: `failureCode` (required), `context` (free text, echoed back), and `sessionId` (marks the blocker as explained, so `qa_status` moves past it). An unknown code is an error.
211
+
212
+ ### qa_continue_from_blocker
213
+
214
+ Answers a `needs_input` question. Parameters: `sessionId`, `kind` (for example `credentials` or `monorepo_target`), `values` (a map of field to value), and `secretFields`.
215
+
216
+ - A value is secret when its field name looks like a credential (`pass`, `secret`, `token`, `otp`, `pin`, `cvv`, `key`, or `code`) **or** the field is listed in `secretFields`, which adds to that rule and never replaces it.
217
+ - Secret values join the redaction set immediately and are never echoed or logged. They are held in memory only, so after a server restart a login run asks again.
218
+ - Non-secret choices (`platform`, `device`, `target`, `allowOutsideRoot`) map onto the re-invocation's arguments.
219
+ - Returns `accepted`, `ignored[]` (each with how to apply it), `nextAction`, and `projectRoot` for a `monorepo_target` answer.
220
+ - A `monorepo_target` must be an existing directory inside the project root (one of the offered candidates). `/`, `~`, or any path outside the root is `INVALID_ARGUMENT`.
221
+
222
+ ### qa_get_artifact
223
+
224
+ Reads a `swipium://session/<id>/<kind>/<name>` artifact, for clients without MCP resources. `mode` defaults to `metadata` for images and `inline` for text. A text artifact whose redaction was `partial` reports `redaction:"partial"` plus `redactionNote` (see [Secrets and redaction](concepts.md#secrets-and-redaction)).
23
225
 
24
226
  ## Setup
25
227
 
26
- Use these tools to verify the local environment, create sessions, and prepare simulator targets.
228
+ Check the toolchain, open a session, and prepare a simulator.
229
+
230
+ ### qa_doctor
231
+
232
+ Checks Node, the Android SDK and emulator, Xcode and `simctl`, WDA, and client freshness. `platform` is `android`, `ios`, or `both` (default `both` on macOS, where it is ready if either platform is; `android` elsewhere). `client` (`claude`, `gemini`, `codex`, `cursor`, or `vscode`) tailors registration hints. `expectedVersion`, `expectedToolCount`, and `expectedSchemaHash` add a `client-freshness` check that reports a stale client.
233
+
234
+ ### qa_start_session
235
+
236
+ Opens a session. Only needed for low-level tools; `qa_test_this` creates its own.
237
+
238
+ - **`budget`**: defaults to 8 minutes, 20 actions, 8 screenshots, 3 consecutive snapshot failures (`maxSnapshotFailures`), and 3 no-change actions (`maxNoChangeActions`). `profile` sets the time budget: `guardrail` 8 min, `login_smoke` 10, `full_smoke` 15, `install_smoke` 20. Once a budget is spent, tools return a budget stop.
239
+ - **`responseMode`**: see [Response modes](#response-modes).
240
+ - **`sensitive:true`**: refuses every screenshot, recording, log capture, and other on-screen evidence (`SENSITIVE_MODE_REFUSED`). Sensitive sessions are never listed as MCP resources.
241
+ - **`fixtures`**: declared preconditions, merged with `.swipium/fixtures.json`, so unmet ones report as blocked instead of failed. The schema advertises only `{name, …}`; the full shape is validated server-side, and a bad shape returns `INVALID_ARGUMENT`. Values passed here are held in memory: after a server restart, a fixture that is not also in `.swipium/fixtures.json` comes back without its `value`, field values, or `seed` (see [Sessions](concepts.md#sessions)). The full shape:
242
+
243
+ ```jsonc
244
+ {
245
+ "name": "saved_flight", // required
246
+ "description": "…",
247
+ "requiredState": "at least one saved flight",
248
+ "recommendedSetup": "…",
249
+ "testAccount": "…",
250
+ "apkPath": "…",
251
+ "value": "BA123", // non-secret test input for exploration text entry
252
+ "disposable": true, // only for data destructive QA may mutate or delete
253
+ "environment": "test",
254
+ "fields": {
255
+ // typed catalog for form entry, matched by label, id, or role
256
+ "email": { "var": "SWIPIUM_TEST_EMAIL", "secret": false }, // var: SWIPIUM_* names only
257
+ "name": { "generator": "full_name" }, // email, person_name, full_name, number, text, city, country, color, phone, date, …
258
+ },
259
+ "seed": {
260
+ // opt-in, consent-gated way to create the precondition
261
+ "type": "script", // deeplink | script | api
262
+ "command": ["node", "scripts/seed.js"],
263
+ "idempotent": true,
264
+ "cleanup": { "type": "api", "url": "http://localhost:3000/reset", "method": "POST" },
265
+ },
266
+ }
267
+ ```
27
268
 
28
- | Tool | What it does | Use when |
269
+ ### qa_prepare_target
270
+
271
+ Prepares an Android Emulator in order: device > Metro > install > launch, then verifies the foreground.
272
+
273
+ - **Parameters**: `sessionId`, `apk`, `appId`, `avd`, `device` (required when more than one device is online), `headless` (default true), `force`, `bindOnly` (bind or boot plus `adb reverse` only, which breaks a device/Metro deadlock), `allowLaunchWithoutMetro` (launch a debug RN/Expo build without Metro; it may show a RedBox), `consentId`/`approve`.
274
+ - **Consent**: one combined `prepare_plan` consent for the privileged steps. Every install is gated: an APK inside the project root is risk low, an external one medium (the prompt shows its sha256). Inside-ness is decided on resolved real paths, so `<root>/../x.apk` and symlinks that leave the root count as external. An already-installed app launches without a prompt.
275
+ - **Device selection**: it acts on the online device rather than planning one. With several devices online (a phone included) and no `device`, it returns `MULTIPLE_DEVICES` listing the online serials; a phone that is the only online device is refused. See [Devices](concepts.md#devices). Boot and install run as a job and return a `jobId`.
276
+ - **Failure codes**: `PHYSICAL_DEVICE_UNSUPPORTED`, `MULTIPLE_DEVICES`, `NO_DEVICE`, `EMULATOR_BOOT_FAILED`, `DEVICE_NOT_READY`, `NO_ARTIFACT` (no APK to install), `INSTALL_FAILED`, `APK_ARCH_INCOMPATIBLE`, `ANDROID_MIN_SDK_INCOMPATIBLE`, `ANDROID_SIGNATURE_CONFLICT`, `METRO_REQUIRED`, `APP_LAUNCH_FAILED`, `INVALID_ARGUMENT` (for example a malformed app id).
277
+
278
+ ### qa_prepare_ios_target
279
+
280
+ Prepares an iOS Simulator: picks and boots one, installs a simulator `.app`, launches `bundleId`, verifies the foreground, and reports whether WDA structured automation is available or the session is visual-only.
281
+
282
+ - **Parameters**: `sessionId`, `app` (absolute or project-relative), `bundleId`, `device` (UDID or name substring), `launch`, `attachWda`, `consentId`/`approve`.
283
+ - **`attachWda`**: `auto` (default) probes WDA and stays visual-only, with a recorded workaround, when it is unreachable, non-loopback, or session creation fails. `required` fails instead (`WDA_UNREACHABLE`, `WDA_SESSION_FAILED`, or `DESTRUCTIVE_REFUSED` for a non-loopback URL). `skip` does not probe.
284
+ - **Consent**: `install_app`, risk low for an app inside the project root, medium outside it.
285
+ - **Failure codes**: `IPA_NEEDS_REAL_DEVICE` (a `.ipa` is refused), `IOS_SIMULATOR_APP_MISSING`, `IOS_APP_WRONG_ARCH`, `SIMULATOR_RUNTIME_MISSING`, `SIMULATOR_BOOT_FAILED`, `SIMULATOR_BOOT_TIMEOUT`, `BUNDLE_ID_NOT_FOUND`.
286
+
287
+ ### qa_ios
288
+
289
+ Direct iOS Simulator control (macOS only). `action` is one of:
290
+
291
+ | action | Parameters | Consent |
29
292
  | --- | --- | --- |
30
- | `qa_doctor` | Checks Node, Android Emulator readiness, iOS Simulator readiness, WDA status, and stale-client symptoms. Accepts `platform:"android"`, `"ios"`, or `"both"`. | Before the first run or when setup fails. |
31
- | `qa_start_session` | Opens a project QA session with budget, response mode, fixtures, and sensitive mode. | Running lower-level tools directly instead of `qa_test_this`. |
32
- | `qa_detect_context` | Detects framework, project readiness, artifacts, devices, and likely blockers. | The agent needs a preflight view before selecting a path. |
33
- | `qa_plan` | Produces a safe workflow plan before acting. | The user asks for a plan or the agent needs a low-risk next step. |
34
- | `qa_prepare_target` | Prepares an Android Emulator target, installs or launches an APK, and binds the session. | Testing Android on an emulator. |
35
- | `qa_prepare_ios_target` | Boots an iOS Simulator, installs a simulator `.app` when provided, launches a bundle id, and reports visual or WDA mode. | Testing iOS on a simulator. |
36
- | `qa_ios` | Runs iOS Simulator lifecycle operations such as boot, install, launch, screenshot, logs, privacy reset, and erase. | Direct iOS simulator control is needed. |
37
- | `qa_wda` | Checks, builds, or starts WebDriverAgent for structured iOS simulator automation. | iOS needs structured UI tree access instead of visual-only checks. |
293
+ | `list` | | |
294
+ | `boot` | `device` (UDID or name substring). Binds the simulator to the session. | none (low-risk, reversible) |
295
+ | `install` | `app` (a `.app`, absolute or project-relative) | `install_app`, medium |
296
+ | `launch`, `terminate` | `bundleId` | |
297
+ | `openurl` | `url` (deep link) | |
298
+ | `logs` | `last` (default `5m`) | |
299
+ | `privacy_reset` | `bundleId`, `service` (for example `location`, `photos`, `camera`, `all`) | low |
300
+ | `erase` | `device`. Wipes the simulator. | `erase_device`, high |
301
+
302
+ Screenshots go through `qa_screenshot`, and WebDriverAgent through `qa_wda`. The old `wda_*` and `screenshot` actions return `STALE_CLIENT`.
303
+
304
+ ### qa_wda
305
+
306
+ Diagnoses, attaches, or manages WebDriverAgent for structured iOS automation. Without WDA, iOS stays visual-only and `qa_visual` does the checking (see [iOS modes](concepts.md#ios-modes)).
307
+
308
+ - **`action`**: `status`, `doctor`, `diagnose`, `logs`, and `tune` inspect an existing setup. `attach` connects to an external WDA at `webDriverAgentUrl` (default `http://127.0.0.1:8100`). `build` and `start` manage one (consent `wda_build` / `wda_start`, medium) from `wdaProjectPath` (default: an installed Appium WebDriverAgent when one is found), with `derivedDataPath` and `scheme` (default `WebDriverAgentRunner`); build and start output is captured as artifacts. `stop` terminates it.
309
+ - **`device`**: the simulator UDID behind this WDA (default: the session device). `udid` is a deprecated alias. `bundleId` defaults to the session's app. A non-loopback URL needs `allowNonLoopback:true` plus consent (see [iOS modes](concepts.md#ios-modes)).
310
+ - **Failure codes**:
311
+ - `attach`: `MULTIPLE_DEVICES` whenever no `device` is given and none is bound to the session (it never guesses), `WDA_UNREACHABLE`, `WDA_SESSION_FAILED`, `STALE_WDA_DEVICE`, `DESTRUCTIVE_REFUSED` (non-loopback URL without approval).
312
+ - `build` and `start`: `NO_DEVICE` (no UDID given or bound), `BACKEND_UNSUPPORTED` (no Xcode command line tools), `NO_ARTIFACT` (no WebDriverAgent project found), `WDA_BUILD_FAILED`, `WDA_SIGNING_FAILED`, `WDA_START_FAILED` (with `managedPid` while a managed WDA is still running: attach to it or `stop` it first).
38
313
 
39
314
  ## Build
40
315
 
41
- Use these tools to resolve a device and an installable artifact, or build one from source locally. `qa_build` with `mode:"run"` is consent-gated; everything else is side-effect free.
316
+ Pick a target, find an artifact, or build one. Only `qa_build mode:"run"` and `qa_bundletool install:true` have side effects on the machine or device.
42
317
 
43
- | Tool | What it does | Use when |
44
- | --- | --- | --- |
45
- | `qa_resolve_target` | Picks the best device or simulator (prefers online over needing a boot; honors a requested platform or device). | The agent needs to choose where to run. |
46
- | `qa_resolve_artifact` | Finds the best installable `.apk`/`.aab`/`.ipa`/`.app` and explains where it looked. | An artifact path is unknown or ambiguous. |
47
- | `qa_build` | `mode:"plan"` (default) proposes the exact build commands per framework and platform without running them; `mode:"run"` builds from source as a consent-gated job, captures a build log, and re-resolves the artifact. | The agent needs to know how the app would be built, or no reusable artifact exists and the project must be compiled. |
48
- | `qa_bundletool` | Converts an `.aab` to an installable APK: a universal `.apk` or a device-specific APK set. | Only an Android App Bundle is available. |
318
+ ### qa_resolve_target
319
+
320
+ Picks the best device or simulator deterministically and boots nothing. It honors `platform`, `device` (adb serial, simulator UDID or name, or AVD name), and a platform-specific artifact, prefers an online emulator or simulator, and otherwise plans a boot. Returns `selected`, `reason`, `alternatives`, `preconditions`, and `willBoot`.
321
+
322
+ A physical device returns `PHYSICAL_DEVICE_UNSUPPORTED` only when it is requested as `device`, when it is the only option on the chosen platform, or when `preferRealDevice` is set and a phone is visible. Otherwise an emulator or simulator is selected and the phone is mentioned in `reason`. See [Devices](concepts.md#devices).
323
+
324
+ `include` adds sections:
325
+
326
+ - **`context`**: framework (`expo`, `bare-react-native`, `native-android`, `native-ios`, `flutter`, or `unknown`), monorepo location, prebuilt artifacts, online Android devices, AVDs, booted and available iOS simulators (`iosBooted`, `iosAvailable`; macOS only), toolchain (`adb`, `emulator`, `java`, `aapt2`, `xcodebuild`), and blockers. A usable iOS simulator means a missing `adb` is not reported as a blocker.
327
+ - **`plan`**: READY workflows (with a budget profile and satisfied preconditions), BLOCKED workflows (`missing_device`, `missing_artifact`, `missing_test_data`, or `missing_toolchain`, with the required state and how to unblock), and UNSAFE workflows (with a reason, for example `bundle_cache_loss` for `fresh_start` on a debug RN/Expo build). A booted or bootable simulator counts as a device. With `sessionId`, the session's fixtures, observed auth, and prepared app inform the plan; without one, `.swipium/fixtures.json` does.
328
+
329
+ When target selection itself fails, the requested sections are still attached to the error.
330
+
331
+ ### qa_resolve_artifact
332
+
333
+ Finds the best installable build in Gradle, Flutter, and Xcode outputs. Parameters: `platform` (`android`, `ios`, `any`), `buildType` (`debug`, `release`, `any`), `path` (short-circuits the search), `allowOutsideRoot`, `requireInstallableOn` (`android-emulator`, `android-real`, `ios-simulator`, `ios-real`). Xcode's DerivedData (`~/Library/Developer/Xcode/DerivedData`) is outside the project, so it is searched only with `allowOutsideRoot:true`. Returns ranked candidates (build type, installability, app id, ABIs, warnings) and the exact locations searched. Failure codes: `NO_BUILD_ARTIFACT` (with a `qa_build` next step), `AAB_NEEDS_BUNDLETOOL`, `ARTIFACT_OUTSIDE_ROOT_REQUIRES_APPROVAL`.
334
+
335
+ ### qa_build
336
+
337
+ - **`mode:"plan"`** (default, no side effects): the detected framework (Expo, React Native, native, Flutter), exact prerequisite and build commands, working directory, expected artifact globs, toolchain status, and a cost estimate. Works with `projectRoot` alone.
338
+ - **`mode:"run"`**: needs `sessionId`. Consent `build_from_source` (high). Runs as a job, stores a build-log artifact, and re-resolves the produced artifact. `timeoutMs` is per step (default 1200000).
339
+ - Parameters: `platform` (required: `android` or `ios`), `variant` (`debug` or `release`).
340
+ - **Failure codes** (a build failure is not a test failure): `BUILD_COMMAND_UNAVAILABLE`, `DEPENDENCY_INSTALL_REQUIRED`, `EXPO_PREBUILD_REQUIRED`, `GRADLE_FAILED`, `XCODEBUILD_FAILED`, `FLUTTER_BUILD_FAILED`, `BUILD_FAILED`, `BUILD_TIMED_OUT`, `BUILD_ARTIFACT_UNRESOLVED_AFTER_SUCCESS`.
341
+
342
+ ### qa_bundletool
343
+
344
+ Converts an `.aab` (not directly installable) into an installable APK, cached under `.swipium/artifacts/`, before `qa_prepare_target`. Runs as a job.
345
+
346
+ - Default: a universal `.apk` signed with the debug keystore. `aab` defaults to the best `.aab` in the project; `force` rebuilds a cached one.
347
+ - `connectedDevice:true` builds a device-specific APK set; with `install:true` it also installs it on `device` (consent `install_app`, medium).
348
+ - **Failure codes**: a missing bundletool returns `AAB_NEEDS_BUNDLETOOL` at once, before any job starts (`BUNDLETOOL_MISSING` is effectively unreachable from this tool). The job can end with `AAB_BUILD_APKS_FAILED`, `AAB_DEVICE_SPEC_FAILED` (no connected device for a device-specific set), or `ANDROID_SIGNING_FAILED`, and an install with `ANDROID_SIGNATURE_CONFLICT`, `ANDROID_MIN_SDK_INCOMPATIBLE`, `APK_ARCH_INCOMPATIBLE`, or `AAB_INSTALL_FAILED`. No `.aab` in the project is `NO_BUILD_ARTIFACT`.
49
349
 
50
350
  ## Device
51
351
 
52
- Use these tools to inspect and control the device and app environment without raw `adb` or `simctl`. Mutating actions are consent-gated and recorded as environment changes; network changes are auto-restored at report end.
352
+ Inspect and control the device and app without raw `adb` or `simctl`. Mutating actions are logged as environment changes and appear in the report.
53
353
 
54
- | Tool | What it does | Use when |
55
- | --- | --- | --- |
56
- | `qa_device_info` | Reports model, SDK, ABIs, locale, screen, orientation, and installed apps (read-only). | The agent needs device context before testing. |
57
- | `qa_orientation` | Sets portrait, landscape, or auto orientation. | A screen must be tested in a specific orientation. |
58
- | `qa_geolocation` | Spoofs a GPS location on the emulator. Consent-gated. | Testing map or location-aware screens. |
59
- | `qa_network` | Reports, sets offline/online, or restores airplane-mode state. Consent-gated and auto-restored. | Testing offline behavior or network errors. |
60
- | `qa_metro` | Reports, starts, stops, or diagnoses the RN/Expo Metro bundler with RedBox detection. | A debug RN/Expo build needs Metro. |
61
- | `qa_app_control` | Runs launch, foreground, background, force_stop, restart, clear_data, or fresh_start. Destructive actions are guarded. | The app lifecycle must be controlled directly. |
62
- | `qa_screen_record` | Records a screen video to an mp4 artifact (start/stop). Consent-gated with sensitive-screen warnings. | A run needs a video of the reproduction. |
354
+ ### qa_device_info
355
+
356
+ Read-only, no consent. On Android: `props` (manufacturer, model, SDK, release, ABIs, locale, timezone), `screen` (size and density), `orientation`/`rotation`/`autoRotate`, `installedThirdPartyCount`, and `packages[]` with `listPackages:true` (filter with `packageFilter`). On iOS: `platform:"ios"`, the simulator's name, runtime, and state, the screen in points, `orientation:"unknown"`, and a list of unsupported fields.
357
+
358
+ ### qa_orientation
359
+
360
+ `orientation`: `portrait`, `landscape`, or `auto` (re-enables auto-rotate). Android only; iOS returns `BACKEND_UNSUPPORTED`. Logged as an environment change.
361
+
362
+ ### qa_geolocation
363
+
364
+ Spoofs the GPS location on an Android Emulator (`adb emu geo fix <lng> <lat>`). `lat` and `lng` are required decimal degrees. Consent `geo_set` (medium). iOS returns `BACKEND_UNSUPPORTED`.
365
+
366
+ ### qa_network
367
+
368
+ Airplane mode via `cmd connectivity airplane-mode`, Android 11+ only. `action`: `status`, `offline`, `online`, or `restore`. `offline` and `online` need consent `network_change` (medium). The original state is recorded on the first change and restored at `qa_report`, on `restore`, and on server shutdown. On iOS it returns `BACKEND_UNSUPPORTED` (simulators have no airplane-mode control). If the device rejects the toggle, nothing is recorded as changed.
369
+
370
+ ### qa_metro
371
+
372
+ The Metro bundler (port 8081) for debug React Native and Expo builds. `action`:
373
+
374
+ - `status`: Metro, `adb reverse`, and serving state.
375
+ - `diagnose`: adds RedBox detection, logcat evidence, and recovery steps.
376
+ - `start`: consent `start_metro` (medium). Runs `adb reverse tcp:8081 tcp:8081`, spawns Metro detached with a log artifact, and tracks the process. Relaunch the app with `qa_prepare_target` afterwards.
377
+ - `stop`: stops the whole process group and removes the reverse.
378
+
379
+ `qa_metro` errors are typed: `MULTIPLE_DEVICES` or `NO_DEVICE` when it can't pick a device, and `METRO_FAILED` when `adb reverse` fails. It does not return `METRO_REQUIRED`; that code comes from `qa_prepare_target` and `qa_test_this` when a debug build needs a Metro server that isn't running.
380
+
381
+ ### qa_app_control
382
+
383
+ `action`: `launch`, `foreground`, `background`, `force_stop`, `restart` (force-stop plus launch, for persistence checks), `clear_data`, or `fresh_start`. The action always targets the session's app. After `background`, the result reports the app that is actually in the foreground.
384
+
385
+ A success returns `{packageName, action, changedState:true, processKilled, foreground, foregroundIsApp}`. On an error, `changedState` reflects what actually ran: a driver call that failed before any mutation reports `changedState:false`.
386
+
387
+ `clear_data` and `fresh_start` wipe app data: consent `app_clear_data` / `app_fresh_start` (high). On debug RN/Expo builds they also need `acknowledgeBundleRisk:true`, because a wipe can remove the cached JS bundle; without it the result is `BUNDLE_LOSS_REFUSED`.
388
+
389
+ ### qa_screen_record
390
+
391
+ Records to an mp4 artifact on Android (`adb screenrecord`, auto-stops after about 3 minutes) and the iOS Simulator (`simctl io recordVideo`). `action`: `start` (consent `screen_record`, medium; it captures whatever is on screen, so avoid password and OTP screens), `status`, `stop`. `save:"on_failure"` on `start`, plus `failed:false` on `stop`, discards the video of a passing run. One recording per session. Refused in sensitive sessions.
63
392
 
64
393
  ## Drive
65
394
 
66
- Use these tools to observe the UI, act on it, collect evidence, and record results.
395
+ Observe, act, assert, and collect evidence.
67
396
 
68
- | Tool | What it does | Use when |
69
- | --- | --- | --- |
70
- | `qa_snapshot` | Captures compact structured UI elements where the backend supports it. | The agent needs selectors, visible text, or UI state. |
71
- | `qa_inspect` | Returns the full attributes of a single `@eN` element from the latest snapshot. | One element's details are needed without dumping the whole tree. |
72
- | `qa_act` | Taps, types, clears, swipes, scrolls, presses keys, opens URLs, waits, and observes after action. | The agent needs to drive the app step by step. |
73
- | `qa_clear_overlay` | Attempts to dismiss common overlays blocking a target. | Popups, permissions, modals, or sheets block the next action. |
74
- | `qa_check_health` | Checks foreground app status, crash signals, ANR, error boundaries, and native health. | The agent needs to distinguish app bugs from environment issues. |
75
- | `qa_screenshot` | Captures a screenshot artifact with coordinate-space metadata. | Visual evidence is required. |
76
- | `qa_note` | Records a structured QA outcome in the session. | The agent needs to log pass, fail, blocked, skipped, or finding details. |
77
- | `qa_assert_visual` | Captures a visual assertion with evidence. | The agent needs to document that a visual condition is true or false. |
78
- | `qa_wait` | Waits without a shell for `device_online`, `metro_ready`, or `job_done`. | The agent needs to block on a condition without raw `adb`/`sleep`. |
397
+ ### qa_snapshot
79
398
 
80
- ## Run
399
+ Captures the screen as compact, addressable elements (`@e1`, `@e2`, …) with a `snapshotQuality` verdict. Interactive elements only, with no screenshot. Busy screens are capped; `filter` (a substring of text, label, id, or role) finds capped elements, and `diff:true` returns only what changed since the previous snapshot. Refs are invalid after navigation.
81
400
 
82
- Use these tools to run broader QA workflows and produce reports.
401
+ - **iOS with WDA**: an element's `id` is its accessibility identifier (WDA's `name` when it differs from the label). `TextField`, `SecureTextField`, `SearchField`, and `TextView` are `text-field` elements that show their typed value; secure fields stay masked.
402
+ - **Overlays**: banners and snackbars are reported only with an overlay signal (an overlay-like class or id, a dismiss control, or banner wording). Navigation-bar titles, text fields, and list rows are never reported as overlays.
403
+ - **Visual-fallback**: after `maxSnapshotFailures` consecutive failed dumps (default 3), the session switches to visual-fallback (`VISUAL_ONLY_SCREEN`) for that screen. Each later `qa_snapshot` and `qa_act` still tries one bounded structured dump; the first success switches back (`modeRecovered:true`) and resets the count.
404
+ - **iOS without WDA**: there is no UI tree; the result is `BACKEND_UNSUPPORTED`. Use `qa_visual`.
405
+ - **Failure codes**: `SNAPSHOT_FAILED`, `VISUAL_ONLY_SCREEN`, `BACKEND_UNSUPPORTED`, `NO_DEVICE`.
83
406
 
84
- | Tool | What it does | Use when |
85
- | --- | --- | --- |
86
- | `qa_smoke` | Runs launch smoke, baseline health, screenshot evidence, and saved flows. | The app is prepared and the agent needs a deterministic smoke pass. |
87
- | `qa_explore` | Performs bounded guided exploration, builds a screen graph, and records evidence. | The agent needs to discover reachable workflows or collect runtime app-map data. |
88
- | `qa_report` | Generates a session report with findings, blockers, evidence, mutations, workarounds, next actions, and separate app and coverage verdicts. | A run should be summarized or exported. |
407
+ ### qa_inspect
408
+
409
+ Full attributes of one `@eN` from the latest snapshot: class, id, content description, text, bounds, interaction flags, and raw attributes. Secrets are redacted, and a secure field's value is shown as `«secure»`. A ref from an older screen is `STALE_REF`.
89
410
 
90
- ## App Map
411
+ ### qa_act
91
412
 
92
- Use these tools to build and read Swipium's durable app knowledge map.
413
+ Performs one action, waits for the screen to settle, and observes: `changed`, `settled`, snapshot quality, a health check, and post-action elements.
93
414
 
94
- | Tool | What it does | Use when |
415
+ | action | Required | Optional |
95
416
  | --- | --- | --- |
96
- | `qa_app_map_build` | Builds or updates the app map from static analysis and runtime observations. | The project needs durable QA memory. |
97
- | `qa_app_map_read` | Reads compact app-map sections such as features, screens, auth, automation, or test suite. | The agent needs app context without flooding the transcript. |
98
- | `qa_app_map_query` | Searches features, screens, tests, and code links with ranked results. | The user asks about a feature, screen, or test surface. |
99
- | `qa_app_map_feature_scope` | Resolves a feature id or free-text query into a focused testing scope: ranked candidates, code symbols, screens, existing tests, an objective model, and a recommended plan. Read-only; works before a map exists. | The user names a feature and the agent needs its scope. |
100
- | `qa_app_map_update` | Applies targeted, provenance-tracked updates (note, test cases, automation suite, environment, feature coverage) without a full rebuild. | The map needs a small correction or annotation. |
417
+ | `tap` | `target` | `durationMs` (press length; coordinate taps default to about 100 ms), `ignoreOverlay` |
418
+ | `type` | `target`, `text` | `mode` (`replace`, the default, clears first; or `append`), `submit` (press enter after) |
419
+ | `clear` | `target` | |
420
+ | `swipe` | `direction` | `target` (start point) |
421
+ | `scroll` | `direction` | `untilVisible` (a target), `maxScrolls` (default 8) |
422
+ | `press` | `key` (`back`, `home`, `enter`) | |
423
+ | `open_url` | `url` | |
424
+ | `wait` | | `for` (`{settled:true}` by default, or an element), `timeoutMs` (default 8000) |
101
425
 
102
- ## Feature Testing
426
+ Every action also takes `observe` and `timeoutMs` (the settle-wait cap).
103
427
 
104
- Use these tools to test a specific feature by name, backed by the app knowledge map.
428
+ - **Targets**: an `@eN` ref, `text`, `id`, a native `selector` on WDA (`accessibility id`, `name`, `predicate string`, or `class chain`), or `x`/`y` coordinates.
429
+ - **Observe**: `diff` (the default once a snapshot exists) returns added and removed elements; `full` returns the capped list; `none` returns verdicts only. When more than half of the post-action elements are new (a navigation), `diff` returns the full capped list with `diffAsFull:true`, `addedCount`, and `removedCount`.
430
+ - **`changed`**: true when elements appeared or disappeared, when positions moved (so a scroll that only shifts content counts), or when a checked, selected, or value state changed. State changes are listed in `stateChanged`, so a toggle is never retried as a press (which would toggle it back).
431
+ - **Keyboard**: if a tap target's center is inside the soft keyboard's frame, Swipium hides the keyboard (never a blind BACK), waits, re-resolves the target, and taps only once it is uncovered; success adds `keyboardHidden:true`. If the keyboard cannot be hidden, the result is `KEYBOARD_OBSTRUCTION` with `changedState:false`. If it was hidden but the target is gone or still covered, `KEYBOARD_OBSTRUCTION` with `changedState:true` and `keyboardHidden:true`. Nothing is tapped in either case. Targets above the keyboard (an accessory toolbar, suggestion chips) are tapped without hiding it. When the keyboard's area is unknown (no frame, or a frame taller than 55% of the screen), Swipium taps without hiding it and warns `keyboard is up; could not determine its area`.
432
+ - **Other overlays**: an element drawn over the target returns `OVERLAY_OBSTRUCTION` with `blockedByOverlay` instead of a blind tap. `ignoreOverlay:true` skips both checks. Coordinate taps are always treated as deliberate.
433
+ - **Scroll**: a plain `scroll` performs exactly one swipe. With `untilVisible`, visibility is checked before the first swipe, and swiping repeats up to `maxScrolls`. The result reports `swipes`, `untilVisibleFound`, and `endOfList:true` when a swipe no longer changes the screen. Each swipe is anchored inside the largest scrollable container on screen (Android `scrollable="true"`, iOS ScrollView, Table, or CollectionView), 10% inside its edges, so it never starts on a sticky app bar; `anchoredIn` is `scrollable` or `screen`. A match counts as found only when its center is on screen and not under the keyboard.
434
+ - **Back on iOS**: iOS has no back key. On WDA, `press key:"back"` taps the navigation bar's back button when one is on screen, otherwise it swipes from the left edge. `backVia` reports `nav_button` or `edge_swipe`. Without either, `BACKEND_UNSUPPORTED`.
435
+ - **Typing on Android**: text is escaped for the device shell (spaces, braces, brackets, glob characters, a literal `%s`). Characters `adb input text` cannot deliver (non-ASCII or control characters) return `TEXT_INPUT_UNSUPPORTED` with `changedState:false`; the value is checked before the field is focused or cleared.
436
+ - **Placeholders**: `type` expands `${SWIPIUM_*}` placeholders from session inputs (for example credentials given to `qa_continue_from_blocker`), else from the server environment. An unresolvable placeholder returns `MISSING_TEST_DATA` before anything is tapped. Secret handling of typed values is in [Secrets and redaction](concepts.md#secrets-and-redaction).
437
+ - **Warnings**: non-fatal caveats come back in `warnings[]`, for example `WDA session was re-created (requested without relaunching the app); verify the screen state`.
438
+ - **Mode recovery**: a successful observation switches a visual-fallback session back to structured mode (`modeRecovered:true`). An observation that never reaches idle switches it to visual-fallback for that screen only.
439
+ - **Device binding**: a device still booting is `DEVICE_NOT_READY`; a physical device is `PHYSICAL_DEVICE_UNSUPPORTED`. A session is only re-bound to its own device: a resumed iOS session re-attaches its simulator (WDA when it had attached WDA and it is reachable, else the simulator backend). An offline device is never replaced by a different online one.
440
+ - **Failure codes**: `INVALID_ARGUMENT` (missing field or target), `STALE_REF`, `ELEMENT_NOT_FOUND`, `AMBIGUOUS_SELECTOR`, `KEYBOARD_OBSTRUCTION`, `OVERLAY_OBSTRUCTION`, `TEXT_INPUT_UNSUPPORTED`, `MISSING_TEST_DATA`, `BACKEND_UNSUPPORTED`, `NO_DEVICE`.
105
441
 
106
- | Tool | What it does | Use when |
442
+ ### qa_clear_overlay
443
+
444
+ Clears what blocks the screen. `strategy`: `auto` (default, the topmost overlay), `hide_keyboard`, `press_back`, `tap_outside`, `minimize_logbox`, `dismiss_logbox`, `allow_permission`, `deny_permission`, or `dismiss_toast_if_possible`. `targetRef` reports whether that element was obstructed before and after. Returns what was cleared and what remains. A keyboard the backend cannot dismiss (for example WDA's "Did not know how to dismiss the keyboard") returns `KEYBOARD_NOT_DISMISSIBLE` (not retry-safe) with next steps: press enter, tap the app's Done button, or `tap_outside`.
445
+
446
+ ### qa_check_health
447
+
448
+ Deterministic health check of the current screen: native crash, ANR, framework error boundary or RedBox, error surfaces, and whether the app is still in the foreground. High-severity findings are real bugs, not flakes. `qa_act` runs it after every action.
449
+
450
+ ### qa_screenshot
451
+
452
+ Captures the screen as a session artifact and returns its `swipium://` URI with coordinate-space metadata (not inline bytes). `reason` is shown in the report. Counts against the screenshot budget. Withheld with `CAPTURE_WITHHELD_SECURE` when a password or OTP field is on screen, unless `force:true` (pixels cannot be redacted).
453
+
454
+ ### qa_note
455
+
456
+ Records a structured outcome for one workflow, so the report is honest about what was verified.
457
+
458
+ - `workflow` and `outcome` (`pass`, `fail`, `blocked`, `skipped`, `not_applicable`) are required.
459
+ - `category` (`app_bug`, `mcp_limitation`, `missing_test_data`, `intentionally_skipped`, `destructive_refused`, `other`) says why and is independent of the outcome. A failing note without a category is recorded as `app_bug`, which also lands in the issue ledger as an app-owned `app_bug` with medium severity. Pass `category:"mcp_limitation"` for tool problems.
460
+ - Use `outcome:"blocked"` with `missingPrecondition`, `requiredState`, and `recommendedSetup` instead of a false failure.
461
+ - Attach evidence in `artifactUris`. For a screenshot-verified check, use `qa_visual mode:"assert"`.
462
+
463
+ ### qa_visual
464
+
465
+ Screenshot-based checks for screens without a usable UI tree (maps, canvases, games, iOS without WDA) and for visual regression.
466
+
467
+ | mode | Required | What it does |
107
468
  | --- | --- | --- |
108
- | `qa_test_feature` | Tests a named feature: `mode:"plan"` (default) returns the read-only test plan (scope, objective, generated cases, fixtures, execution plan); `mode:"execute"` runs targeted exploration toward the feature, records cases, then updates the feature map and report. | The user asks to plan or run a test of a specific feature. |
469
+ | `assert` | `assertion` | Records a visual assertion: screenshot evidence plus a `qa_note` with `verifiedVisually:true`. `pass` defaults to true; `pass:false` means the expected thing is not visible. `reason` adds detail. Returns `{mode, assertion, pass, screenshotUri, coordinateSpace, secureFieldCheck}`. |
470
+ | `baseline` | `name` | Saves the screen as `<repo>/.swipium/baselines/<name>.png` (commit it or ignore it) plus a session artifact. |
471
+ | `diff` | `name` | Compares the screen to a baseline. Returns the changed ratio, the changed box (screenshot and device space), and an evidence artifact. `pass` means within `threshold` (default 0.02). |
472
+ | `find_text` | `query` | OCR through a locally configured provider (none is bundled). Consent `ocr_run` (medium). `minConfidence` defaults to 0.8. OCR text is secret-redacted. |
473
+ | `find_image` | `template` | Template-matches a PNG. `minScore` defaults to 0.85. |
474
+
475
+ A mode called without its required argument returns `INVALID_ARGUMENT`.
476
+
477
+ - **Paths**: `name` must match `[A-Za-z0-9._-]{1,64}` and must not start with `.`. `template` must be inside the project root (after resolving symlinks) or a `swipium://` artifact of the same project. Escapes and symlinked baselines return `VISUAL_PATH_REFUSED`.
478
+ - **Coordinates**: finds return a bounding box in screenshot pixels and a tappable `devicePoint`: points on iOS (WDA and `idb`), pixels on Android. Every result declares its `coordinateSpace`.
479
+ - **Tapping**: `tap:true` taps the found point through the driver. On a simulator without WDA it uses `idb ui tap` when `idb` is on PATH, else it returns the coordinates with install steps. Taps are recorded as session actions (for `qa_generate`) and count against the action budget.
480
+ - **Budgets**: every capture counts as a screenshot. Nothing runs once a budget is spent.
481
+ - **Secure screens**: every mode is withheld (`CAPTURE_WITHHELD_SECURE`) when a password or OTP field is on screen, unless `force:true`. When the cached UI tree is missing or older than a visual tap, Android and WDA re-dump it to check. With no UI tree at all (a simulator without WDA) in a session that has handled credentials, only `baseline` is withheld, because it persists the capture in the repository; `diff` and `assert` still run but save no screenshot (`currentUri`/`screenshotUri` is `null`, `captureWithheld:true`), and `find_image` returns coordinates only. `find_text` screens the OCR text itself and withholds a screen that reads like a password, OTP, or payment screen. Results carry `secureFieldCheck`: `clear`, `secure`, `forced`, `ocr` (only the OCR text was checked), or `unverified`; the last two come with a warning.
482
+ - **OCR provider contract**: set `ocrCommand` in `.swipium/config.json` (an argv array with an `{image}` placeholder, or `{command, io:"json", timeoutMs}`), or the `SWIPIUM_OCR_CMD` environment variable; the project config wins. The command runs with the project root as its working directory, gets a symlink-resolved PNG path (already masked when `visualMaskCommand` is configured), and must print JSON to stdout: `[{"text":"Log in","confidence":0.97,"bbox":{"x":53,"y":182,"width":104,"height":38}}]` or `{"regions":[…]}`, with the box in screenshot pixels, confidence from 0 to 1, and one region per text line. With `io:"json"` it also receives a `swipium.visual.provider.v1` JSON line on stdin. The default timeout is 30 s. Provider images go to a private per-call temporary directory (mode 0700) that is removed afterwards.
483
+ - **Provider failures**: a non-zero exit or a timeout returns `OCR_PROVIDER_FAILED` with `provider`, `exitCode`, `timedOut`, and the trimmed, secret-redacted `stderr`, never `found:false`. A Git executable as the provider is refused with `GIT_SCOPE_FORBIDDEN`. Without a provider, `find_text` returns `OCR_NOT_CONFIGURED` with an `exampleProvider`: a tesseract script to save as `.swipium/ocr_tesseract.py` (needs `brew install tesseract`), used with `"ocrCommand": ["python3", ".swipium/ocr_tesseract.py", "{image}"]`.
484
+ - **Mask command**: `visualMaskCommand` in `.swipium/config.json` (or `SWIPIUM_VISUAL_MASK_CMD`; the project config wins) follows the same contract and failure rules. The `ocr_run` consent prompt shows every command that will run, the mask command first, each with its argv and where it came from; a command from the repository's `.swipium/config.json` is labelled "unreviewed". `affects` carries `maskArgv` too.
485
+
486
+ ### qa_wait
487
+
488
+ Blocks (bounded) until a setup condition holds, instead of a shell `sleep`. `for`: `device_online` (default timeout 180000 ms) or `metro_ready` (60000 ms). Returns `satisfied`, `timedOut`, and the current state. To wait for a job, use `qa_job_status` with `waitMs`.
489
+
490
+ ## Run
491
+
492
+ Smoke checks, exploration, and reports.
493
+
494
+ ### qa_smoke
495
+
496
+ Server-side smoke on a prepared device: optionally launches (`launch`, default true with an app id), runs the baseline (snapshot quality, health, an evidence screenshot, skipped in sensitive sessions), then every saved flow in `.swipium/flows` (`runFlows`, default true) with `variables`. Records a `qa_note` per workflow; call `qa_report` afterwards.
497
+
498
+ Repository flows are untrusted, so `qa_smoke` never runs a flow with mutating steps or an external OCR or visual provider implicitly. Such a flow is recorded as `blocked` (category `destructive_refused`) with a pointer to `qa_flow_run` and its consent.
499
+
500
+ ### qa_explore
501
+
502
+ Bounded, safe-by-default exploration of the launched app, as a job. It observes screens, taps ranked safe actions, checks health after each, and builds a screen graph (JSON and Markdown, `graphUri` in the job result). Taps are recorded for `qa_generate`, and the app map is updated.
503
+
504
+ - **Bounds**: `depth` (default 3), `maxActions` (20), `maxScreens` (12), `maxDurationMs` (360000). `goal` is a natural-language focus.
505
+ - **`strategy`**: `crawl` (default, deterministic), `task_planner` (infer QA tasks first), or `hybrid`.
506
+ - **`safeMode`**: `strict` (default); `balanced` (unknown-risk actions allowed); `dry_run_destructive` (lists destructive candidates without tapping); `approved_destructive_candidate` (taps exactly one candidate from a dry run). `approved_destructive` is refused with `DESTRUCTIVE_REFUSED`.
507
+ - **`approved_destructive_candidate` requirements**: an exact `destructiveCandidate` copied from the dry run (else `DESTRUCTIVE_REFUSED`); disposable test state, meaning a fixture with `disposable:true` or `environment:"test"` (else `MISSING_FIXTURE`); `confirmHighImpact:true` for payment, send, permission, account-delete, and bulk-delete candidates; and the `destructive_ui_candidate` consent (high).
508
+ - **Text and data**: `includeTextEntry` (default false) types into fields that have a value source (fixtures). `allowGeneratedData` permits generated disposable test data; `accountCycle` additionally permits logout, only on a disposable generated account. Generated values are recorded under `SWIPIUM_TEST_*` or `SWIPIUM_GEN_<FIELD>` names (see [Secrets and redaction](concepts.md#generated-output)).
509
+ - **Auth**: `stopOnAuth` (default true) returns `needs_input` at an auth wall without credentials.
510
+ - **`generateSuite:true`** also writes and compiles a POM suite from the promoted paths. `suitePromotion` scoring is always returned.
511
+
512
+ ### qa_report
513
+
514
+ Assembles the session report: executive summary (release risk ship, caution, or block, plus the next action), health, outcomes by workflow, findings, evidence links, environment changes and their restoration, the mutation ledger, and workarounds. Saves the full report as an artifact and returns a summary plus `reportUri`, `manifestUri`, and `dumpUri`. `qa_test_this` calls it automatically.
515
+
516
+ - **`baseline`** (a baseline `report.json` path) adds comparison links; **`trendRoot`** (a project root with `.swipium/runs` history) adds trend and flake context.
517
+ - **Verdicts**: the app verdict, the coverage verdict, and the tool verdict are separate, and tool status never changes the app verdict. The tool verdict (`toolVerdict.status`) is:
518
+ - `BLOCKED` when a workflow was limited by a Swipium or MCP capability (a `qa_note` with category `mcp_limitation`);
519
+ - `DEGRADED` when tools returned typed driver or Swipium errors (for example a WDA 404 or a snapshot failure);
520
+ - `PASS` otherwise.
521
+
522
+ Recorded tool errors are listed in `toolErrorsByCode` with `degradingCount`, `uncodedCount`, and `probingCount`. Uncoded errors (`UNKNOWN`, mostly deliberate refusals and guard messages) and agent-probing codes (`ELEMENT_NOT_FOUND`, `STALE_REF`, `AMBIGUOUS_SELECTOR`, `INVALID_ARGUMENT`) are counted but never make the verdict `DEGRADED` on their own. Consent refusals, missing test data, and `CANCELLED` are not recorded as tool errors.
523
+ - **Deduplicated findings**: identical findings (same failure code and kind, severity, layer, screen or foreground, and message) are reported once, with `count`, `firstAt`/`lastAt`, and every distinct `screenshotUris` entry. `findingOccurrences` keeps the raw total; text and markdown show repeats as `(×N)`.
524
+ - **Network restore**: a network change made during the session is restored when the report is generated.
525
+ - **`format`** adds an export artifact (`exportUri`, `exportFormat`). The CI formats carry the `.swipium/policy.json` release-gate verdict ([CI policy](flows.md#ci-policy)). The same files can be rendered outside the agent with the `swipium report` CLI; recipes and exit codes are in [ci-reports.md](ci-reports.md).
526
+
527
+ | Format | Notes |
528
+ | --- | --- |
529
+ | `summary` (default) | No export. |
530
+ | `markdown`, `json` | The full report. The saved report is deep-redacted with the session's secrets before it is written. |
531
+ | `junit` | Failed workflows and high-severity findings are `<failure>`. Blocked, skipped, and not-applicable outcomes are `<skipped>`. Failures the policy treats as lenient (`warnOn` or `ignoreKnown`) are `<skipped message="policy …">`, so a passing gate never fails CI. |
532
+ | `sarif` | SARIF 2.1.0. Every result is anchored to a real repository file (a `%SRCROOT%`-relative `physicalLocation`, line 1), as GitHub code scanning requires: the app-map source file of the screen or workflow, else the first project manifest that exists (`app.json`, `package.json`, `pubspec.yaml`, Gradle files, `Info.plist`, `README.md`). `swipium://` evidence stays in related locations and properties. `invocations[0].executionSuccessful` is always `true`, because the run itself worked. The gate verdict is `runs[0].properties.releaseGateVerdict` (`pass` or `block`). |
533
+ | `github-summary` | Markdown for `$GITHUB_STEP_SUMMARY`, capped at 900 KB (below GitHub's 1 MiB limit) so the verdict always survives. Truncation is stated in the output. |
534
+ | `playwright` | Playwright JSON-reporter-style results: one spec per workflow outcome, with evidence attachments. |
535
+ | `flow` | The recorded actions as Flow V2 YAML. Needs recorded actions. |
536
+
537
+ ## App map
538
+
539
+ The durable app knowledge map in `.swipium/app-map.json`, also served as MCP resources. It is project memory: read it before feature work.
540
+
541
+ ### qa_app_map_build
542
+
543
+ Builds or updates the map: a framework-aware static scan (Expo Router, React Navigation, the Android manifest, SwiftUI and UIKit, Flutter) plus, with `sessionId`, a merge of the latest exploration screen graph. `mode`: `static_only`, `runtime_merge`, or `full` (default). `includeCodeIndex` (default true) persists a code-symbol index for queries; `forceRescan` rescans even when the map is current. Returns a summary and the map URI. Does not commit.
544
+
545
+ ### qa_app_map_read
546
+
547
+ Reads one compact section: `summary` (default), `screens`, `features`, `auth`, `automation`, `testSuite`, or `full`. `featureId` or `screenId` drills into one node. Large sections come back as a resource URI. Without a map: `NO_APP_MAP`.
548
+
549
+ ### qa_app_map_query
550
+
551
+ Searches the feature index, static topology, runtime graph, and tests for a natural-language `query` (for example "checkout flow"). `intent` (`feature`, `screen`, `code`, `test`, `freeform`) biases the search; `limit` caps it. Each result has provenance, confidence, source files, screens, and the recommended next Swipium call.
552
+
553
+ ### qa_app_map_feature_scope
554
+
555
+ Resolves a feature (`featureId`, or a free-text `query`) into a focused test scope: code symbols, static and runtime screens, existing tests, objective, coverage gaps, strategy, and ranked candidates. It asks one disambiguation question only on a genuine tie. Works without a map (it falls back to a code scan). `includeCode` (default true) and `limit` (default 8 per list) shape query mode; `sessionId` adds runtime evidence.
556
+
557
+ ### qa_app_map_update
558
+
559
+ Targeted, provenance-tracked edits without a rebuild: `note` (a user note), `testCases`, `automationSuite` (link a suite to features and screens), `environment` (for example `test` or `staging`), or `featureCoverage` (override a feature's coverage). Existing entries with the same id or path are overwritten, which is why the tool is marked destructive.
560
+
561
+ ## Feature
562
+
563
+ ### qa_test_feature
564
+
565
+ A focused test of one named `feature` (natural language).
566
+
567
+ - **`mode:"plan"`** (default, read-only): scope, objective, generated cases, required fixtures, and an ordered plan.
568
+ - **`mode:"execute"`**: a job that explores toward the feature, records pass, fail, or blocked per case, updates the app map, and writes a report (see the `qa_job_status` result). `interactive` runs until the first question.
569
+ - **Without `sessionId`**, `execute` bootstraps a device from `projectRoot` (optionally `platform` and `device`) with one consent for boot, install, and launch.
570
+ - A feature behind auth, a paywall, a permission, or a missing fixture is **blocked** with setup guidance, not failed.
571
+ - Other parameters: `creativity` (`conservative`, `standard`, `creative`, `adversarial`) with `allowAdversarial`, `maxScreens` (default 8), `maxActions` (default 20), `timeoutMs`, `generateCases` (default true), `includeCode`, `limit`.
109
572
 
110
573
  ## Flows
111
574
 
112
- Use these tools to create, validate, run, compile, and repair reusable flows.
575
+ Validate, run, compile, and repair reusable Flow V2 files under `.swipium/flows/`. The file format, step reference, variables, and CI policy are in [flows.md](flows.md).
113
576
 
114
- | Tool | What it does | Use when |
115
- | --- | --- | --- |
116
- | `qa_flow_check` | Parses and statically validates a Swipium flow (static lint of the YAML). | A flow file should be checked before execution. |
117
- | `qa_flow_run` | `mode:"run"` (default) executes a Swipium flow against a prepared simulator session; `mode:"plan"` is a read-only execution preview against backend capabilities without touching a device. | A saved flow needs to run against the app, or its feasibility should be checked before a run. |
118
- | `qa_flow_compile` | Compiles a generated POM suite into runnable Flow V2 for `qa_flow_run` and `swipium ci`. | A generated suite needs executable flow output. |
119
- | `qa_flow_repair` | Suggests or patches a stronger locator for a failed flow step from the current screen. | A flow step fails on a brittle locator. |
577
+ Without a session, `qa_flow_check` and `qa_flow_run mode:"plan"` resolve flow names against `projectRoot`, then the session root, then MCP roots, then `SWIPIUM_PROJECT_ROOT` / `CLAUDE_PROJECT_DIR`, then a server working directory that looks like an app. A flow that does not exist returns `FLOW_NOT_FOUND` with the paths checked; a call with neither `flow` nor `flowYaml` returns `INVALID_ARGUMENT`; YAML that does not parse returns `INVALID_FLOW`.
578
+
579
+ ### qa_flow_check
580
+
581
+ Statically validates a flow (a lint of the YAML) without running it: syntax and schema errors with the offending step, plus warnings. Pass `flow` (a name under `.swipium/flows` or a path) or `flowYaml`. `platform` (`android`, `ios`, `cross-platform`) adds platform-aware locator warnings. `ci:true` adds CI preflight warnings: variables a CI run cannot resolve, and mutating steps the [CI policy](flows.md#ci-policy) does not allow.
582
+
583
+ ### qa_flow_run
584
+
585
+ - **`mode:"run"`** (default) executes a flow on the prepared session, server-side, with setup and teardown, fail-fast, and no automatic retry of mutating steps. `repeat` (1 to 10) runs it several times to classify flakes. A failure returns the failed step (`failedAtStep`), a screenshot, the `failureCode`, health, and `nextSteps` pointing at `qa_flow_repair {flow, failedStep}` (except when the cause is a missing variable, which is not locator drift).
586
+ - **`mode:"plan"`** previews without a device: per backend (`android-direct`, `ios-raw-simulator`, `ios-wda`, `appium-uiautomator2`, `appium-xcuitest`; `backend` narrows it), whether each step is `native`, `fallback`, `visual_only`, or `unsupported`. `appium` passes Appium session hints.
587
+ - **Variables**: `variables` win over the session's stored inputs, then `SWIPIUM_*` environment variables; no other environment name is ever read (`MISSING_FIXTURE`). See [Flows: variables](flows.md#variables).
588
+ - **Structured flows** (`mode: structured`, the default) on an iOS Simulator without WDA, or in a visual-fallback session, are refused with `BACKEND_UNSUPPORTED`. Use `mode: visual` or `auto`, or attach WDA.
589
+ - **Consent**: mutating steps and OCR steps need the `flow_mutation_run` consent (risk high for script seeds, otherwise medium). The prompt's `exactCommand` and `affects` show each seed's exact argv or URL, labelled as repository-supplied and unreviewed, and each variable `openUrl` destination with credential-like values masked. Which steps count is in [Flows: step reference](flows.md#step-reference).
590
+ - **Refusals**: OCR steps are refused in sensitive sessions (`UNSAFE_ACTION_REFUSED`) and report `VISUAL_ONLY_SCREEN` when no OCR provider is configured. Image templates and baselines outside the project root fail with `UNSAFE_ACTION_REFUSED`.
591
+
592
+ ### Flow steps
593
+
594
+ Moved to [Flows: step reference](flows.md#step-reference), with the file format and a full example.
595
+
596
+ ### qa_flow_compile
597
+
598
+ Compiles an existing POM suite on disk (`suite`, relative to `.swipium/`, default `suites/smoke.yaml`) into runnable Flow V2: it resolves page-object refs to selectors, carries variables, writes `.swipium/flows/<slug>.yaml` (plus a copy under `.swipium/compiled/`), and validates each flow. It needs no session or recorded actions, which makes it the path for committed or hand-edited suites and CI (`swipium suite`). `qa_generate target:"suite"` already compiles the suite it generates.
599
+
600
+ ### qa_flow_repair
601
+
602
+ Given a failed step (`failedStep`, zero-based, from `qa_flow_run`) and the current screen, suggests a stronger locator plus app code changes (such as adding `accessibilityIdentifier` or `testID`). An exact id, label, or text match on the current screen is high confidence. Otherwise candidates are restricted to the failed target's role (a tap stays on a button, an `inputText` stays on a text field) and ranked by text similarity (medium for a contained match, low for a similar one), so a button renamed from "Sign in" to "Log in" is never repaired to the "Email" field. `apply:true` patches simple YAML selector steps in a flow file only at high or medium confidence, and records the patch in the mutation ledger; at low confidence it returns the proposal with `applied:false` and a note, and it never patches inline `flowYaml`. The flow must resolve inside the project root, otherwise `UNSAFE_ACTION_REFUSED`.
120
603
 
121
604
  ## Generate
122
605
 
123
- Use `qa_generate` to turn a session's recorded actions into reusable, per-run test assets. Pick the output with `target`; `mode:"plan"` gives a read-only preview. For the durable repo-level test suite that grows across runs, use `qa_suite_generate` instead.
606
+ ### qa_generate
124
607
 
125
- | Tool | What it does | Use when |
608
+ Turns the actions recorded in a session (`qa_act`, `qa_smoke`, `qa_explore`) into per-run assets. For the durable repository suite, use `qa_suite_generate`. `mode:"plan"` is a read-only preview. Parameters for other targets are ignored with a note. Every target returns `NO_RECORDED_ACTIONS` when the session has recorded nothing (for `appium`, `bootstrap` records a run first).
609
+
610
+ | target | Output | Target-specific parameters |
126
611
  | --- | --- | --- |
127
- | `qa_generate` | Generates test assets from recorded actions: `target:"flow"` (repeatable Flow V2 YAML), `target:"pom"` (page objects + locator audit), `target:"suite"` (full per-run `.swipium/` POM suite with compile/replay gates), `target:"testcases"` (test-case catalog docs), or `target:"appium"` (runnable Appium POM code in JS/TS/Python, with bootstrap from `projectRoot`). `mode:"plan"` previews without writing; for `target:"appium"` it returns the full automation plan with blockers. | A manual or exploratory run should become a reusable flow, page objects, a structured suite, test-case docs, or automation code. |
612
+ | `flow` | Flow V2 YAML for `qa_flow_run` | `budgetProfile` |
613
+ | `pom` | Page objects and a locator audit | |
614
+ | `suite` | The full per-run POM suite under `.swipium/`, compiled to runnable flows, with a replay gate | `compile` (default true), `replay` (`none`, `dry_run` (default), `same_session`, `fresh_state`), `stateProfile` (for `fresh_state`, which is consent-gated) |
615
+ | `testcases` | A `TC-xxx` catalog | `format` (`yaml`, `markdown`, `both` (default)) |
616
+ | `appium` | A runnable WebdriverIO (TypeScript or JavaScript) or Python Appium suite | `language` (default auto), `platform` (`auto`, `android`, `ios`, `both`), `backend`, `projectRoot`, `bootstrap`, `feature`, `device`, `integrateIntoProject` (consent `automation_project_write`; never overwrites), `includeCi`, `candidateOnly`, `brittleThreshold` (default 40) |
128
617
 
129
- ## Persistent Test Suite
618
+ `name` sets the asset name (default from the app id). `save` writes files (default true for `suite` and `appium`, false otherwise). `sessionId` is required except for `appium`, which can plan or `bootstrap` (smoke plus explore) from `projectRoot`.
130
619
 
131
- Use these tools to grow and maintain a canonical test suite that persists across runs in `.swipium/test-suite.json`. This is the durable repo-level suite — distinct from the per-run assets `qa_generate` emits.
620
+ - **Appium code**: every recorded step becomes real code. Swipes and scrolls are real gestures (bounded scroll-until-visible loops). A step that cannot be expressed fails generation with `UNEMITTABLE_STEP` instead of emitting a silent no-op. Class, method, and file names are sanitized, so they are always valid identifiers in the target language.
621
+ - **Platform**: the generated suite's default `SWIPIUM_PLATFORM` is resolved from the explicit `platform` argument, then the platform of the session's device, then the project profile, then Android. `ios` means Appium XCUITest and `android` means UiAutomator2. The plan reports `primaryPlatform` and `platformSource`. Generated suites also read `SWIPIUM_NO_RESET` at run time.
622
+ - **Visual assertions**: a `qa_visual mode:"assert"` step is free-form prose, not on-screen text, so it becomes a clearly marked manual checkpoint: a `TODO(manual visual check, not automated)` comment in code, a `visualCheck` POM step, an evidence-capturing `assertVisual` step in compiled flows, and "MANUAL visual check" in test cases. Only real text assertions become `assertTextVisible`.
623
+ - **Secrets**: recorded secrets become `${SWIPIUM_*}` placeholders (see [Generated output](concepts.md#generated-output)). If a registered secret value would still be written, generation fails with `SECRET_IN_GENERATED_OUTPUT` and nothing is written. The Appium `validation.secretsClean` check scans every generated file, comments included.
132
624
 
133
- | Tool | What it does | Use when |
134
- | --- | --- | --- |
135
- | `qa_suite_read` | Reads the canonical suite, filtered by functionality or status, as summary, json, or markdown. | The agent needs the durable suite without re-deriving it. |
136
- | `qa_suite_update` | Merges cases into the persistent suite, deduping by feature, objective, and steps. | A run produced cases to fold into the suite. |
137
- | `qa_suite_generate` | Generates or refreshes canonical cases from a recorded run and exploration, merging into the durable suite (not per-run assets — use `qa_generate` for those). | The durable suite needs to be (re)built from observed behavior. |
138
- | `qa_suite_export` | Exports the persistent suite to markdown, a yaml directory, json, or junit. | The suite must be shared or fed to CI. |
139
- | `qa_suite_lint` | Validates the durable suite (missing expected/actual, stale map links, duplicate ids) and, when `.swipium/pages` exists, also lints generated page objects for brittle locators. | The suite and generated pages must be trusted before a release sign-off. |
625
+ ## Test suite
140
626
 
141
- ## Issue Memory and Mobile Audit
627
+ The canonical suite in `.swipium/test-suite.json`. It persists and grows across runs, unlike the per-run assets from `qa_generate`. All five tools accept `sessionId` or `projectRoot`.
142
628
 
143
- Use these tools for a durable, per-project issue ledger and executable mobile-QA audit profiles. The ledger lives in `.swipium/issues-log.jsonl`; fingerprints let later runs detect regressions of previously fixed issues.
629
+ ### qa_suite_read
144
630
 
145
- | Tool | What it does | Use when |
146
- | --- | --- | --- |
147
- | `qa_issue_log` | Lists the durable issue ledger with counts, recurrence candidates, and linked evidence. | The agent needs the project's known issues. |
148
- | `qa_mobile_audit` | Plans or executes a named mobile-QA profile (smoke, account_cycle, store_compliance, resilience, release_gate). | A structured, repeatable audit is needed; execution records issues and evidence. |
631
+ Reads the suite, filtered by `functionality` or `status` (`active`, `draft`, `deprecated`, `blocked`, `manual_only`). `format`: `summary` (counts and ids), `json`, or `markdown`. Returns a resource URI for the full suite.
149
632
 
150
- ## First Run
633
+ ### qa_suite_update
151
634
 
152
- Use this tool for login, account creation, onboarding, permissions, OTP, and paywall screens.
635
+ Merges `cases` into the suite. `source` (required) is `report`, `exploration`, `feature`, `ticket`, `manual`, `generate`, or `suite`, with an optional `sourceUri`. A case matching feature, objective, and normalized steps is updated, not duplicated; new cases get stable ids (`TC-<FEATURE>-NNN`). `mergeMode`: `append`, `update`, or `replace_generated` (overwrites generated fields over curated ones). Returns created, updated, and deprecated ids plus conflicts.
153
636
 
154
- | Tool | What it does | Use when |
637
+ ### qa_suite_generate
638
+
639
+ Generates or refreshes canonical cases from the session's recorded actions, outcomes, and exploration coverage, and merges them into the suite (re-running updates cases, with no duplicates). `feature` labels the generated flow case. `creativity` (`conservative`, `standard` (default), `creative`, `adversarial` for negative and abuse cases) sets how far cases go beyond the happy path; `creativityLevel` is a deprecated alias. `includeManualOnly` keeps manual-only cases. Returns generated cases, skipped or blocked features, and map-coverage gaps.
640
+
641
+ ### qa_suite_export
642
+
643
+ Exports the suite as `markdown` (review-ready), `yaml` (a per-functionality directory), `json`, or `junit`. `save:true` writes it under `.swipium/test-suite-export/`.
644
+
645
+ ### qa_suite_lint
646
+
647
+ Lints the suite (missing expected or actual results, unlinked or stale feature and screen links, duplicate ids, brittle automation, adversarial cases without safety metadata) and, when `.swipium/pages` exists, generated page objects (coordinate-only, locale-fragile, or dynamic locators). `brittleThreshold` is `C` or `D`. `liveFeatureIds` enables the stale-link rule. Returns errors and warnings.
648
+
649
+ ## Issues
650
+
651
+ ### qa_issue_log
652
+
653
+ The durable project issue ledger in `.swipium/issues-log.jsonl`, an append-only event log plus an index. Fingerprints let later runs detect regressions of issues that were already fixed.
654
+
655
+ | mode | Parameters | What it does |
155
656
  | --- | --- | --- |
156
- | `qa_first_run` | `mode:"plan"` (default) classifies the current first-run screen and creates a safe plan without acting; `mode:"continue"` executes bounded first-run steps (`until: one_step`/`until_gate`/`until_home`) with safe generated data when allowed and stops at gates. | The agent reaches login, signup, onboarding, permission, OTP, paywall, or home screens. |
657
+ | `history` (default) | filters `state`, `category`, `severity`, `platform`, `since`, `includeSuppressed` | Lists issues with counts and recurrence. |
658
+ | `log` | `title` (required, descriptive), `summary`, `failureCode`, `category`, `severity`, `platform`, `evidenceUris` | Records an observation. |
659
+ | `mark_fixed` | `issueId` or `fingerprint`; `fixedInCommit`, `fixedInVersion`, `howFixed`, `fixedBy` | Marks an active issue fixed. |
660
+ | `verify_fixed` | `issueId` or `fingerprint`; evidence: `reportUri`, `testCaseId`, `auditCheckId`, or `evidenceUris` | Confirms a fix with current-run evidence. |
661
+ | `suppress` | `issueId` or `fingerprint`; `suppressionReason`, `suppressedUntil` (alias `until`), `suppressionScope`, `unsuppress` | Hides an issue as expected noise. |
662
+ | `metrics` | `since`, `until`, `groupBy` (default `week`), `includeSuppressed` | Trends from the event log. |
157
663
 
158
- ## Detailed Reference: Device, Build, and Feature Tools
664
+ - **Identity**: a manually logged issue's identity is its normalized title (ids and numbers scrubbed), plus `failureCode` or category, plus platform. The app id is left out because the ledger is already project-scoped, so the same title logged with a `sessionId` or a `projectRoot` lands on the same issue. A title with no identifying words left after scrubbing is refused with `ISSUE_LOG_TOO_VAGUE`.
665
+ - **Issues logged before 2.0.0** keep their old fingerprint (platform and `failureCode` only), and new logs never merge into them. Close them with `mark_fixed`, or hide them with `suppress`.
666
+ - **Lifecycle**: re-observing a fixed issue reopens it with a message that quotes `howFixed` and `fixedInCommit`. `mark_fixed` only applies to an active issue, otherwise `ISSUE_STATE_INVALID`. `verify_fixed` needs a fixed issue plus evidence, otherwise `ISSUE_EVIDENCE_REQUIRED`. An unknown key is `ISSUE_NOT_FOUND`.
667
+ - **Suppression**: `suppressedUntil` (an ISO timestamp) makes it expire automatically, after which the issue returns to its previous state; without it the suppression is open-ended. `unsuppress:true` lifts it early. Suppressed issues are hidden from `history` unless `includeSuppressed:true`, and show as known noise in reports.
668
+ - **Redaction**: with a `sessionId`, registered session secrets are scrubbed from `title`, `summary`, `howFixed`, `suppressionReason`, and `fixedBy` before they reach the committed ledger.
159
669
 
160
- Technical detail for retained public device, build, feature, app-map, and agent-helper tools. Most take a `sessionId` from `qa_start_session`; the build, artifact, and read-only feature tools also accept a `projectRoot` so they work before a session exists. Mutating actions accept `consentId` + `approve` and are recorded in the report's mutation ledger.
670
+ ### qa_mobile_audit
161
671
 
162
- ### Device and app environment
672
+ Plans or executes a named release audit. `profile` (required):
163
673
 
164
- - **`qa_device_info`** — read-only Android introspection. *Inputs:* `listPackages?`, `packageFilter?`. *Outputs:* `props` (manufacturer, model, SDK, release, ABIs, locale, timezone), `screen` (`width`/`height`/`density`), `orientation`/`rotation`/`autoRotate`, `installedThirdPartyCount`, optional `packages[]`. No consent.
165
- - **`qa_orientation`** — set rotation. *Inputs:* `orientation: portrait | landscape | auto`. *Outputs:* resulting `orientation`/`rotation`/`autoRotate`. Non-destructive; logged as an environment change.
166
- - **`qa_geolocation`** — spoof GPS via `adb emu geo fix <lng> <lat>`. *Inputs:* `lat`, `lng` (decimal degrees). *Outputs:* `{ lat, lng, set }`. Consent-gated (medium). Emulator/`direct` backend only; iOS and real devices return `BACKEND_UNSUPPORTED`.
167
- - **`qa_network`** — offline/online via `cmd connectivity airplane-mode` (Android 11+). *Inputs:* `action: status | offline | online | restore`. *Outputs:* `network` (`online`/`offline`), `restoreAvailable`. `offline`/`online` consent-gated (medium); the original airplane state is recorded on first change and auto-restored at `qa_report`, on `restore`, and on server shutdown.
168
- - **`qa_metro`** — RN/Expo Metro lifecycle. *Inputs:* `action: status | diagnose | start | stop`. *Outputs:* `framework`, `metroListening`, `reverseSet`, `serving`, `ready`, `metroPid`, plus `redBox` + `recovery[]` (+ logcat artifact) for `diagnose`. `start` is consent-gated (low): it runs `adb reverse tcp:8081 tcp:8081` and spawns Metro (`npx expo start --dev-client` or `npx react-native start`) detached, logging to an artifact and tracking the PID; `stop` signals the whole process group.
169
- - **`qa_app_control`** — app lifecycle. *Inputs:* `action: launch | foreground | background | force_stop | restart | clear_data | fresh_start`, `acknowledgeBundleRisk?`. *Outputs:* `packageName`, `action`, `processKilled`, `foreground`, `foregroundIsApp`. `clear_data`/`fresh_start` are destructive → consent-gated (high); on debug RN/Expo builds they additionally require `acknowledgeBundleRisk:true` because a data wipe can remove the cached JS bundle.
170
- - **`qa_screen_record`** — screen video. *Inputs:* `action: start | status | stop`, `save?: always | on_failure`, `failed?`. *Outputs:* `recording`/`capturing`/`autoStopped`, `seconds`, and on stop a `uri` (mp4 artifact) + `bytes`. Consent-gated (medium); refused on sensitive sessions. Android uses `adb screenrecord --time-limit 180`; iOS uses `simctl io recordVideo`. One recording per session.
674
+ | profile | Checks |
675
+ | --- | --- |
676
+ | `smoke` | Launch and baseline checks. |
677
+ | `account_cycle` | Create > logout > login > forgot-password on a disposable generated account. Needs `allowGeneratedData`. |
678
+ | `store_compliance` | Privacy policy, terms, account deletion, subscription, and paywall. |
679
+ | `resilience` | Offline, relaunch, and rotation. |
680
+ | `release_gate` | All of the above, plus locator readiness and issue recurrence. |
681
+
682
+ `mode:"plan"` (default) returns the checklist and safety contract without a device. `mode:"execute"` needs a prepared session, runs every check, logs failed and blocked checks to the issue ledger with evidence, and returns the release impact. It always runs to completion. No check passes without evidence. `allowTestAccountDeletion` permits deleting disposable test accounts (never a real account). `offlineMode` hints that resilience checks should drive offline state; `targetApp` and `sourceRevision` (`{commit, buildVersion, branch}`) identify what was audited.
171
683
 
172
- ### Build and artifact resolution
684
+ Executing `resilience` or `release_gate` (which runs all four other profiles) needs the `network_change` consent (medium), the same gate as `qa_network`, because it toggles airplane mode. The tool returns the consent request before running anything. With consent, the original airplane state is recorded first and restored afterwards, even if a check fails.
173
685
 
174
- - **`qa_resolve_target`** — choose the best device or simulator, deterministically and side-effect free. *Inputs:* `sessionId?`, `projectRoot?`, `platform?` (`android`/`ios`), `device?` (name/serial/udid), `preferRealDevice?`. *Outputs:* `selection` (kind + id), a human `reason`, `alternatives[]`, `preconditions` (e.g. WDA/signing), and `willBoot`. Does not boot anything — `qa_prepare_target`/`qa_ios` do that.
175
- - **`qa_resolve_artifact`** — find the best installable artifact and explain the search. *Inputs:* `sessionId?`, `projectRoot?`, `platform?` (`android`/`ios`/`any`), `buildType?` (`debug`/`release`/`any`), `path?` (explicit artifact), `allowOutsideRoot?`, `requireInstallableOn?` (`android-emulator`/`android-real`/`ios-simulator`/`ios-real`). *Outputs:* the resolved `.apk`/`.aab`/`.ipa`/`.app` and its type; on failure, the exact globs searched plus a `qa_build { mode:"plan" }` → `qa_build { mode:"run" }` next step. Read-only.
176
- - **`qa_build`** — plan or run a from-source build. *Inputs:* `mode?` (`plan` (default) / `run`), `platform` (`android`/`ios`), `variant?` (`debug`/`release`); `sessionId?`/`projectRoot?` for `mode:"plan"`; `sessionId` (required), `timeoutMs?`, `consentId?`/`approve?` for `mode:"run"`. *Outputs:* in `plan` mode the detected `framework`, the exact build commands, the expected artifact path, and a cost estimate — side-effect free, with a typed error when no supported framework (Expo, bare React Native, native Android/iOS, Flutter) is detected; in `run` mode a `jobId` to poll with `qa_job_status`, and on completion a build-log artifact and the re-resolved artifact path. `mode:"run"` is consent-gated (it compiles the app).
177
- - **`qa_bundletool`** — convert an Android App Bundle to an installable APK. *Inputs:* `sessionId` (required), `aab?` (default: the best `.aab` under the project), `force?`, `connectedDevice?` (device-specific APK set vs a universal APK), `install?` (also run `install-apks`), `device?` (adb serial), `consentId?`/`approve?`. *Outputs:* the generated APK or APK-set path. `install` is consent-gated because it installs app code on a device/emulator.
686
+ ## First run
178
687
 
179
- ### Feature-focused testing
688
+ ### qa_first_run
180
689
 
181
- - **`qa_app_map_feature_scope`** — map a feature to the app, read-only. *Inputs:* `query` (free text, e.g. "weather analysis") or `featureId` (exact map feature id), `sessionId?` (adds runtime screen-graph evidence), `projectRoot?` (static code scope), `platform?`, `includeCode?` (default true), `limit?`. *Outputs:* in query mode, ranked `candidates` each tagged by `source` (`code`/`screen`/`route`/`runtime`/`test`) with confidence, plus a test `objective` and `strategy`; in featureId mode, the map feature's source files, screens, coverage, blockers, and recommended plan. No consent. Query mode works before a map exists; returns `found:false` with guidance when nothing matches.
182
- - **`qa_test_feature`** — run a focused test toward a named feature. *Inputs:* `feature` (required), `sessionId` (required), `mode?` (`plan` (default) / `execute` (focused run as a job) / `interactive` (run until the first question)), `platform?`, `device?`, `creativity?`, `allowAdversarial?`, `maxScreens?`, `maxActions?`, `timeoutMs?`, `generateCases?`, `consentId?`/`approve?`. *Outputs:* in `plan` mode the read-only test plan (scope, objective, generated cases, fixtures, execution plan); in `execute` mode a `jobId` whose run performs targeted exploration toward the feature, records cases, updates the feature map, and emits a report. `execute`/`interactive` are consent-gated.
690
+ Gets past a first-run gate: login, sign-up, OTP, onboarding, permissions, or a paywall. Needs a prepared device.
183
691
 
184
- ### App-map maintenance and agent helpers
692
+ - **`mode:"plan"`** (default, read-only) classifies the current screen and returns the safe plan. `until`, `maxSteps`, and `maxDurationMs` are ignored with a note.
693
+ - **`mode:"continue"`** runs bounded steps. `until`: `one_step` (default), `until_gate` (stop at a paywall, OTP, or permission), or `until_home`. In test or staging environments it fills forms with generated data (the password is kept secret), advances onboarding, and records paywalls without purchasing. It refuses sign-up in production-like environments and returns one `needs_input` question on an OTP.
694
+ - `testDataPolicyPath` (default `.swipium/test-data-policy.json`) and `allowGeneratedAccount` control whether a throwaway account may be created.
185
695
 
186
- - **`qa_inspect`** — full attributes of one element. *Inputs:* `sessionId`, `ref` (e.g. `@e3`). *Outputs:* `class`, `id`, `contentDesc`, `text`, `bounds`, the interaction flags (`clickable`/`scrollable`/`focused`/`enabled`/…), and the raw `attrs`. Secret values are redacted and a secure field's value is masked as `«secure»`. Read-only; refs come from the most recent `qa_snapshot` and invalidate after navigation.
187
- - **`qa_next_best_action`** — deterministic next-step recommendation. *Inputs:* `sessionId`, `goal?` (`smoke`/`explore`/`create_automation_suite`/`release_gate`/`test_login`/`reproduce_bug`). *Outputs:* `nextBestAction` (the `tool`, suggested `args`, and `why`). Read-only.
188
- - **`qa_app_map_update`** — targeted, provenance-tracked map edits. *Inputs:* `projectRoot?`/`sessionId?` plus any of `note`, `testCases[]`, `automationSuite`, `environment`, `featureCoverage`. *Outputs:* `applied[]` and the `appMapUri`. Recomputes confidence and coverage and persists. Requires an existing map (`qa_app_map_build` first).
696
+ ## MCP resources and prompts
189
697
 
190
- ## Recommended Entry Points
698
+ **Resource templates** (read by URI; `qa_get_artifact` and `qa_app_map_read` are the fallbacks for clients without resources):
191
699
 
192
- | User intent | First tool |
700
+ | Template | Content |
701
+ | --- | --- |
702
+ | `swipium://session/{sessionId}/{kind}/{name}` | Session artifacts: screenshots, dumps, reports, logs, videos. |
703
+ | `swipium://project/{projectId}/app-map` | The complete app map JSON. |
704
+ | `swipium://project/{projectId}/app-map/{kind}/{id}` | One app-map section: a feature, screen, or test-suite entry. |
705
+
706
+ `resources/list` is scoped to the current client's project roots (its MCP roots plus the roots of sessions used in this server process), so one client never browses another project's artifacts. Each listing shows the newest 100 entries and says so on the last entry when it is capped; the rest stay readable by URI. Artifacts of sensitive sessions are never listed.
707
+
708
+ **Prompts**:
709
+
710
+ | Prompt | Purpose |
711
+ | --- | --- |
712
+ | `swipium_setup_check` | Verify Swipium can test a project and report what is blocking it. |
713
+ | `swipium_guardrail_validation` | Confirm Swipium refuses an unsafe bundle-loss wipe on a debug RN/Expo build. |
714
+ | `swipium_full_smoke` | Plan, prepare, run the top ready workflows, and produce a report. |
715
+ | `swipium_bug_repro` | Drive to a described bug, capture deterministic evidence, and record it as a structured outcome. |
716
+ | `swipium_convert_run_to_flow` | Draft a `.swipium/flows/*.yaml` from the steps just performed, then validate it. |
717
+
718
+ ## Moved topics
719
+
720
+ These sections used to live on this page:
721
+
722
+ - Project root resolution: [concepts.md#project-root](concepts.md#project-root).
723
+ - Consent, elicitation, and the consent-action table: [concepts.md#consent](concepts.md#consent).
724
+ - Sessions, persistence, retention, jobs, and cancellation: [concepts.md#sessions-and-jobs](concepts.md#sessions-and-jobs).
725
+ - Secrets and redaction: [concepts.md#secrets-and-redaction](concepts.md#secrets-and-redaction).
726
+ - Devices, iOS Simulator, and WebDriverAgent: [concepts.md#devices](concepts.md#devices) and [concepts.md#ios-modes](concepts.md#ios-modes).
727
+ - Flow steps and CI policy: [flows.md](flows.md).
728
+ - Report formats: [qa_report](#qa_report) and [ci-reports.md](ci-reports.md).
729
+ - Environment variables: [README](../README.md#configuration--environment-variables).
730
+
731
+ ## Failure codes
732
+
733
+ Every code a tool can return is in the catalog, and `qa_explain_blocker` explains any of them. Each code has a **bucket** (how to triage it: `app_bug`, `environment`, `missing_data`, `mcp_limitation`, or `unsafe_refused`), an **owner** (who fixes it: `app`, `environment`, `swipium`, or `user`), a severity, and a default retry safety. The tables below are grouped by bucket; owner is per code.
734
+
735
+ Codes marked **reserved** are defined for classifying evidence, reports, and policy rules (so `blockOn`, `warnOn`, `ignoreKnown`, and report consumers can name them stably), but no tool returns them in 2.0.0. A reserved code may start being returned in a minor release. Codes marked **finding** appear as health findings in reports rather than as tool errors.
736
+
737
+ ### Bucket: app_bug
738
+
739
+ | Code | Owner | Meaning |
740
+ | --- | --- | --- |
741
+ | `NATIVE_CRASH` | app | The native process crashed. Finding. |
742
+ | `ANR` | app | App not responding. Finding. |
743
+ | `ERROR_BOUNDARY` | app | An app error screen, error boundary, or WebView error. Finding. |
744
+ | `REDBOX` | app | A framework red-box error. Finding. |
745
+ | `LOGBOX` | app | A framework warning overlay. Finding. |
746
+ | `BACKEND_ERROR` | app | An error surface shown to the user. Finding. |
747
+ | `ASSERTION_FAILED` | app | Expected UI was not present. |
748
+ | `WDA_MAIN_THREAD_BUSY` | app | The app's main thread appears busy during WDA automation. |
749
+ | `BLANK_SCREEN`, `INFINITE_SPINNER` | app | Blank screen; stuck loading indicator. Reserved. |
750
+
751
+ ### Bucket: environment
752
+
753
+ Device, simulator, and WebDriverAgent:
754
+
755
+ | Code | Owner | Meaning |
756
+ | --- | --- | --- |
757
+ | `NO_DEVICE` | environment | No online device or bootable emulator, or no device UDID for a WDA build or start. |
758
+ | `ADB_NOT_FOUND` | environment | `adb` is not installed or not on PATH. |
759
+ | `DEVICE_NOT_READY` | environment | The device exists but is not ready (booting, or properties unreadable). |
760
+ | `EMULATOR_BOOT_FAILED` | environment | The emulator failed to boot. |
761
+ | `SIMULATOR_RUNTIME_MISSING`, `SIMULATOR_BOOT_FAILED`, `SIMULATOR_BOOT_TIMEOUT` | environment | No usable iOS runtime; simulator boot failed or timed out. |
762
+ | `MULTIPLE_DEVICES` | environment | More than one device matches, or a WDA attach has no device; pass one explicitly. |
763
+ | `WRONG_FOREGROUND`, `PERMISSION_DIALOG`, `NATIVE_ALERT` | environment | Another app, a permission dialog, or a native alert is in front. Finding. |
764
+ | `WDA_UNREACHABLE`, `WDA_BUILD_FAILED`, `WDA_SIGNING_FAILED`, `WDA_START_FAILED`, `WDA_SESSION_FAILED`, `WDA_PORT_CONFLICT` | environment | WebDriverAgent could not be reached, built, signed, started, or given a session, or its port is taken. |
765
+ | `STALE_WDA_DEVICE` | environment | WDA appears bound to a different device than the session. |
766
+ | `DEV_SERVER_DOWN`, `NETWORK_SERVICE_UNAVAILABLE`, `NETWORK_OFFLINE` | environment | Reserved. |
767
+ | `DEVICE_BOOT_FAILED` | swipium | Reserved. |
768
+
769
+ Project, artifact, install, and build:
770
+
771
+ | Code | Owner | Meaning |
772
+ | --- | --- | --- |
773
+ | `PROJECT_ROOT_UNRESOLVED`, `PROJECT_ROOT_EMPTY` | user | No usable project root; the root is empty. |
774
+ | `NOT_MOBILE_PROJECT`, `UNSUPPORTED_FRAMEWORK` | user | No supported mobile project at the root; unsupported framework. |
775
+ | `NO_BUILD_ARTIFACT` | swipium | No installable artifact found (Swipium can often build one). |
776
+ | `NO_ARTIFACT` | environment | A required file is missing (an APK to install, a WebDriverAgent project, an app id for a flow). |
777
+ | `ARTIFACT_OUTSIDE_ROOT_REQUIRES_APPROVAL` | user | The best artifact is outside the project root; pass `allowOutsideRoot`. |
778
+ | `AAB_NEEDS_BUNDLETOOL`, `BUNDLETOOL_MISSING`, `AAB_BUILD_APKS_FAILED`, `AAB_DEVICE_SPEC_FAILED`, `AAB_INSTALL_FAILED` | environment | `.aab` conversion problems. |
779
+ | `INSTALL_FAILED`, `WRONG_ARCH`, `APK_ARCH_INCOMPATIBLE`, `ANDROID_MIN_SDK_INCOMPATIBLE`, `ANDROID_SIGNING_FAILED` | environment | Android install and signing problems. |
780
+ | `ANDROID_SIGNATURE_CONFLICT` | swipium | An installed app with a different signature blocks the install. |
781
+ | `IPA_NEEDS_REAL_DEVICE` | user | A `.ipa` targets a real device; build a simulator `.app`. |
782
+ | `IPA_INSTALL_UNSUPPORTED`, `IOS_APP_WRONG_ARCH` | environment | iOS artifact problems. |
783
+ | `IOS_SIMULATOR_APP_MISSING` | swipium | No simulator `.app` found (Swipium can often build one). |
784
+ | `BUNDLE_ID_NOT_FOUND` | environment | The bundle id is not installed on the device. |
785
+ | `APP_LAUNCH_FAILED` | environment | Installed, but the app did not launch. |
786
+ | `BUILD_FAILED`, `GRADLE_FAILED`, `XCODEBUILD_FAILED`, `FLUTTER_BUILD_FAILED` | app | Build from source failed. |
787
+ | `BUILD_COMMAND_UNAVAILABLE`, `DEPENDENCY_INSTALL_REQUIRED`, `BUILD_TIMED_OUT` | environment | Build prerequisites and outcomes. |
788
+ | `EXPO_PREBUILD_REQUIRED`, `BUILD_ARTIFACT_UNRESOLVED_AFTER_SUCCESS` | swipium | Expo prebuild needed; the build succeeded but its artifact was not found. |
789
+ | `METRO_REQUIRED` | swipium | A debug RN/Expo build needs Metro. |
790
+ | `METRO_FAILED` | environment | Metro failed. |
791
+ | `INVALID_FLOW` | environment | A flow is invalid. |
792
+ | `FLOW_NOT_FOUND` | user | A flow does not exist. |
793
+ | `SEED_FAILED` | environment | Seeding a precondition failed (setup, not an app bug). |
794
+ | `OCR_NOT_CONFIGURED`, `OCR_PROVIDER_FAILED` | user | No OCR provider; the OCR or mask provider failed or timed out. |
795
+ | `ARTIFACT_PATH_UNWRITABLE`, `REPORT_UPLOAD_SKIPPED` | environment | Reserved. |
796
+ | `MONOREPO_TARGET_AMBIGUOUS`, `MULTIPLE_ARTIFACTS_AMBIGUOUS`, `IPA_SIGNING_REQUIRED`, `REAL_DEVICE_NOT_CONNECTED`, `REAL_DEVICE_UDID_NOT_PROVISIONED`, `REAL_DEVICE_BUNDLE_ID_MISMATCH`, `REAL_DEVICE_TEAM_MISMATCH` | user | Reserved. |
797
+
798
+ ### Bucket: missing_data
799
+
800
+ | Code | Owner | Meaning |
801
+ | --- | --- | --- |
802
+ | `AUTH_GATE` | user | Login required and no usable credentials. |
803
+ | `MISSING_FIXTURE` | user | A required precondition, fixture, disposable test state, or flow variable is absent. |
804
+ | `MISSING_TEST_DATA` | user | Required test data (for example an unresolvable `${SWIPIUM_*}` placeholder). |
805
+ | `ISSUE_LOG_TOO_VAGUE`, `ISSUE_NOT_FOUND`, `ISSUE_EVIDENCE_REQUIRED` | user | Issue-ledger input problems. |
806
+ | `NO_APP_MAP` | swipium | No app map yet; build it with `qa_app_map_build`. |
807
+ | `NO_RECORDED_ACTIONS` | swipium | No recorded actions to generate from. |
808
+ | `MISSING_SECRET`, `AUTH_REQUIRED` | user | Reserved. |
809
+
810
+ ### Bucket: mcp_limitation
811
+
812
+ | Code | Owner | Meaning |
813
+ | --- | --- | --- |
814
+ | `VISUAL_ONLY_SCREEN` | swipium | No usable UI tree; the session is in visual-fallback for this screen. |
815
+ | `SNAPSHOT_FAILED` | swipium | The UI tree could not be captured. |
816
+ | `STALE_REF` | swipium | The `@eN` ref is from an older screen. |
817
+ | `ELEMENT_NOT_FOUND`, `ELEMENT_NOT_HITTABLE` | swipium | No match; a match that cannot be tapped. |
818
+ | `AMBIGUOUS_SELECTOR`, `INVALID_SELECTOR` | swipium | The selector matched several elements, or is malformed. |
819
+ | `KEYBOARD_OBSTRUCTION`, `KEYBOARD_NOT_DISMISSIBLE`, `OVERLAY_OBSTRUCTION` | swipium | The keyboard or an overlay covers the target, or the keyboard cannot be dismissed. |
820
+ | `TEXT_INPUT_UNSUPPORTED` | swipium | The backend cannot type this text. |
821
+ | `BACKEND_UNSUPPORTED` | swipium | The operation is not supported on this backend (for example iOS rotation, or iOS without WDA). |
822
+ | `UI_IDLE_TIMEOUT`, `ANIMATION_IDLE_BLOCKED` | swipium | The UI did not settle. |
823
+ | `WDA_SOURCE_SLOW`, `WDA_APP_NOT_IDLE`, `WDA_HIERARCHY_TOO_LARGE`, `WDA_XPATH_REFUSED` | swipium | WDA performance and locator limits. |
824
+ | `WEBVIEW_UNAVAILABLE` | swipium | WebView content is not reachable by native automation. |
825
+ | `VISUAL_LOCATOR_DRIFT` | swipium | A visual or OCR locator drifted. |
826
+ | `UNEMITTABLE_STEP` | swipium | A recorded step cannot be expressed as Appium code. |
827
+ | `STALE_CLIENT` | swipium | The client runs an old tool list; restart it. |
828
+ | `UNKNOWN` | swipium | Unclassified. |
829
+ | `MISSING_DURABLE_LOCATOR` | app | An element has no durable locator (`testID`, `accessibilityIdentifier`, resource-id). |
830
+ | `COORDINATE_ONLY_FLOW` | app | A flow relies on coordinate taps. |
831
+ | `SNAPSHOT_TOO_DEEP`, `NO_CHANGE_LOOP`, `VISUAL_ONLY_ASSERTION` | swipium | Reserved. |
832
+ | `VISUAL_MASKING_STATUS_MISSING`, `EVIDENCE_RETENTION_UNDECLARED` | user | Reserved. |
833
+
834
+ ### Bucket: unsafe_refused
835
+
836
+ Expected guardrails, not bugs.
837
+
838
+ | Code | Owner | Meaning |
839
+ | --- | --- | --- |
840
+ | `INVALID_ARGUMENT` | user | A malformed, missing, or undeclared argument, or an unknown `sessionId` or `jobId`. Nothing ran. |
841
+ | `CANCELLED` | user | The call or job was cancelled. Not a failure. |
842
+ | `CONSENT_DECLINED`, `CONSENT_CANCELLED`, `CONSENT_REFUSED` | user | See [Consent](concepts.md#consent). |
843
+ | `DESTRUCTIVE_REFUSED` | user | A destructive action without approval (including a remote WDA URL). |
844
+ | `UNSAFE_ACTION_REFUSED` | user | An unsafe action or a path outside the project root. |
845
+ | `BUNDLE_LOSS_REFUSED` | user | A wipe that would remove a debug build's JS bundle, without `acknowledgeBundleRisk`. |
846
+ | `PHYSICAL_DEVICE_UNSUPPORTED` | user | Physical devices are out of scope. |
847
+ | `CAPTURE_WITHHELD_SECURE` | user | A password or OTP field is on screen; pass `force:true` to capture anyway. |
848
+ | `SENSITIVE_MODE_REFUSED` | user | The session is in sensitive mode. |
849
+ | `VISUAL_PATH_REFUSED` | user | A baseline name or template path escapes the allowed directory. |
850
+ | `GIT_SCOPE_FORBIDDEN` | user | Git commands are outside Swipium's scope (for example a Git executable as OCR provider). |
851
+ | `ISSUE_STATE_INVALID` | user | The issue transition is not allowed from its current state. |
852
+ | `SECRET_IN_GENERATED_OUTPUT` | swipium | Generated output would contain a secret; nothing was written. |
853
+ | `SECRET_ARTIFACT_IN_EVIDENCE` | user | Reserved. |
854
+
855
+ ## Migrating from 1.5.0
856
+
857
+ A client still running a pre-upgrade tool list, or an agent that remembers old names, gets a typed `STALE_CLIENT` error with `replacement` and `clientHint` instead of a raw "Tool not found" (see [Unknown arguments and stale clients](#unknown-arguments-and-stale-clients)). Removed tools and actions are gone from the schemas; two renamed arguments are still listed as deprecated aliases: `qa_wda.udid` and `qa_suite_generate.creativityLevel`.
858
+
859
+ | Removed | Use instead |
193
860
  | --- | --- |
194
- | "Test it" | `qa_test_this` |
195
- | "Check setup" | `qa_doctor` |
196
- | "Start a manual run" | `qa_start_session` |
197
- | "Launch Android" | `qa_prepare_target` |
198
- | "Launch iOS" | `qa_prepare_ios_target` |
199
- | "Smoke test" | `qa_smoke` |
200
- | "Explore the app" | `qa_explore` |
201
- | "Generate report" | `qa_report` |
202
- | "Read app memory" | `qa_app_map_read` |
203
- | "Test the X feature" | `qa_test_feature` |
204
- | "Find/build an artifact" | `qa_resolve_artifact` / `qa_build` |
205
- | "Create a flow" | `qa_generate` with `target:"flow"` |
206
- | "Generate automation" | `qa_generate` with `target:"appium"` |
207
-
208
- ## Extension Pattern
209
-
210
- When adding tools in future releases, document each tool with:
211
-
212
- - Name.
213
- - Group.
214
- - What it does.
215
- - When to use it.
216
- - Main inputs.
217
- - Main outputs.
218
- - Scope limits.
219
- - Consent or mutation behavior.
861
+ | `qa_agent_brief` | Server `instructions`, or `qa_status` without `sessionId` |
862
+ | `qa_capabilities` | `qa_status` without `sessionId` (`capabilityGroups`) |
863
+ | `qa_next_best_action {sessionId, goal}` | `qa_status {sessionId, goal}` > `nextBestAction` |
864
+ | `qa_detect_context {projectRoot}` | `qa_resolve_target {projectRoot, include:["context"]}` |
865
+ | `qa_plan {sessionId}` | `qa_resolve_target {sessionId, include:["plan"]}` |
866
+ | `qa_assert_visual {assertion, pass}` | `qa_visual {mode:"assert", assertion, pass}` |
867
+ | `qa_ios` `wda_status` / `wda_attach` | `qa_wda` `status` / `attach` (with `device`) |
868
+ | `qa_ios` `screenshot` | `qa_screenshot` |
869
+ | `qa_wait {for:"job_done", jobId}` | `qa_job_status {jobId, waitMs}` |
870
+ | `qa_wda {udid}` | `qa_wda {device}` (`udid` still accepted) |
871
+ | `qa_suite_generate {creativityLevel}` | `qa_suite_generate {creativity}` (`creativityLevel` still accepted) |
872
+ | `qa_mobile_audit {waitForCompletion}` | removed; passing it returns `INVALID_ARGUMENT` |
873
+
874
+ Other 2.0.0 changes an upgrading agent should know:
875
+
876
+ - Undeclared arguments are rejected with `INVALID_ARGUMENT` instead of being silently ignored.
877
+ - Manually logged issues use a new identity (see [qa_issue_log](#qa_issue_log)); 1.5.x issues keep their old fingerprint.
878
+
879
+ Adding or changing a tool: see [CONTRIBUTING.md](../CONTRIBUTING.md#adding-a-tool).