swipium 1.4.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 (541) hide show
  1. package/CHANGELOG.md +313 -0
  2. package/README.md +175 -219
  3. package/THREAT_MODEL.md +223 -47
  4. package/dist/appMap/automationLink.js +39 -26
  5. package/dist/appMap/automationLink.js.map +1 -1
  6. package/dist/appMap/build.js +68 -20
  7. package/dist/appMap/build.js.map +1 -1
  8. package/dist/appMap/codeIndex.js +31 -5
  9. package/dist/appMap/codeIndex.js.map +1 -1
  10. package/dist/appMap/featureIndex.js +23 -5
  11. package/dist/appMap/featureIndex.js.map +1 -1
  12. package/dist/appMap/featureModel.js +44 -8
  13. package/dist/appMap/featureModel.js.map +1 -1
  14. package/dist/appMap/firstRunApply.js +24 -6
  15. package/dist/appMap/firstRunApply.js.map +1 -1
  16. package/dist/appMap/fsWalk.js +19 -2
  17. package/dist/appMap/fsWalk.js.map +1 -1
  18. package/dist/appMap/issues.js +10 -30
  19. package/dist/appMap/issues.js.map +1 -1
  20. package/dist/appMap/migrations.js +6 -6
  21. package/dist/appMap/migrations.js.map +1 -1
  22. package/dist/appMap/prelaunch.js +2 -2
  23. package/dist/appMap/prelaunch.js.map +1 -1
  24. package/dist/appMap/projectRegistry.js +83 -11
  25. package/dist/appMap/projectRegistry.js.map +1 -1
  26. package/dist/appMap/provenance.js +2 -2
  27. package/dist/appMap/provenance.js.map +1 -1
  28. package/dist/appMap/query.js +21 -5
  29. package/dist/appMap/query.js.map +1 -1
  30. package/dist/appMap/runtimeMerge.js +12 -5
  31. package/dist/appMap/runtimeMerge.js.map +1 -1
  32. package/dist/appMap/schema.js +1 -1
  33. package/dist/appMap/schema.js.map +1 -1
  34. package/dist/appMap/screenMatch.js +6 -2
  35. package/dist/appMap/screenMatch.js.map +1 -1
  36. package/dist/appMap/staticScan.js +151 -39
  37. package/dist/appMap/staticScan.js.map +1 -1
  38. package/dist/appMap/store.js +58 -85
  39. package/dist/appMap/store.js.map +1 -1
  40. package/dist/appMap/tsAstScan.js +34 -12
  41. package/dist/appMap/tsAstScan.js.map +1 -1
  42. package/dist/artifacts/bundletool.js +49 -12
  43. package/dist/artifacts/bundletool.js.map +1 -1
  44. package/dist/artifacts/resolve.js +41 -14
  45. package/dist/artifacts/resolve.js.map +1 -1
  46. package/dist/automation/capabilities.js +112 -30
  47. package/dist/automation/capabilities.js.map +1 -1
  48. package/dist/automation/gestures.js +6 -2
  49. package/dist/automation/gestures.js.map +1 -1
  50. package/dist/automation/plan.js +71 -23
  51. package/dist/automation/plan.js.map +1 -1
  52. package/dist/automation/report.js +15 -5
  53. package/dist/automation/report.js.map +1 -1
  54. package/dist/automation/selectors.js +22 -28
  55. package/dist/automation/selectors.js.map +1 -1
  56. package/dist/automation/types.js +1 -1
  57. package/dist/automation/types.js.map +1 -1
  58. package/dist/automation/waits.js +14 -8
  59. package/dist/automation/waits.js.map +1 -1
  60. package/dist/automation/webview.js +9 -3
  61. package/dist/automation/webview.js.map +1 -1
  62. package/dist/automationGen/appiumModel.js +16 -8
  63. package/dist/automationGen/appiumModel.js.map +1 -1
  64. package/dist/automationGen/ciEmitter.js +6 -6
  65. package/dist/automationGen/ciEmitter.js.map +1 -1
  66. package/dist/automationGen/identifiers.js +224 -0
  67. package/dist/automationGen/identifiers.js.map +1 -0
  68. package/dist/automationGen/jsEmitter.js +288 -121
  69. package/dist/automationGen/jsEmitter.js.map +1 -1
  70. package/dist/automationGen/packagePatch.js +2 -2
  71. package/dist/automationGen/packagePatch.js.map +1 -1
  72. package/dist/automationGen/platformResolve.js +36 -0
  73. package/dist/automationGen/platformResolve.js.map +1 -0
  74. package/dist/automationGen/projectProfile.js +76 -27
  75. package/dist/automationGen/projectProfile.js.map +1 -1
  76. package/dist/automationGen/pythonEmitter.js +278 -107
  77. package/dist/automationGen/pythonEmitter.js.map +1 -1
  78. package/dist/automationGen/readmeEmitter.js +7 -6
  79. package/dist/automationGen/readmeEmitter.js.map +1 -1
  80. package/dist/automationGen/run.js +388 -0
  81. package/dist/automationGen/run.js.map +1 -0
  82. package/dist/automationGen/suitePlan.js +17 -10
  83. package/dist/automationGen/suitePlan.js.map +1 -1
  84. package/dist/automationGen/validation.js +52 -12
  85. package/dist/automationGen/validation.js.map +1 -1
  86. package/dist/build/parseBuildLog.js +235 -31
  87. package/dist/build/parseBuildLog.js.map +1 -1
  88. package/dist/build/plan.js +39 -18
  89. package/dist/build/plan.js.map +1 -1
  90. package/dist/ci/preflight.js +13 -4
  91. package/dist/ci/preflight.js.map +1 -1
  92. package/dist/cli/gc.js +74 -0
  93. package/dist/cli/gc.js.map +1 -0
  94. package/dist/cli/init.js +217 -26
  95. package/dist/cli/init.js.map +1 -1
  96. package/dist/cli/main.js +50 -0
  97. package/dist/cli/main.js.map +1 -0
  98. package/dist/cli/report.js +218 -0
  99. package/dist/cli/report.js.map +1 -0
  100. package/dist/cli/scan.js +36 -15
  101. package/dist/cli/scan.js.map +1 -1
  102. package/dist/cli/suite.js +7 -7
  103. package/dist/cli/suite.js.map +1 -1
  104. package/dist/cli/verify.js +7 -7
  105. package/dist/cli/verify.js.map +1 -1
  106. package/dist/consent/consent.js +162 -5
  107. package/dist/consent/consent.js.map +1 -1
  108. package/dist/context/detect.js +55 -18
  109. package/dist/context/detect.js.map +1 -1
  110. package/dist/context/findApps.js +22 -8
  111. package/dist/context/findApps.js.map +1 -1
  112. package/dist/context/projectRoot.js +174 -19
  113. package/dist/context/projectRoot.js.map +1 -1
  114. package/dist/context/scan.js +1 -1
  115. package/dist/context/scan.js.map +1 -1
  116. package/dist/core/capabilityGroups.js +90 -0
  117. package/dist/core/capabilityGroups.js.map +1 -0
  118. package/dist/core/target.js +10 -8
  119. package/dist/core/target.js.map +1 -1
  120. package/dist/core/targetPlan.js +160 -39
  121. package/dist/core/targetPlan.js.map +1 -1
  122. package/dist/drivers/DirectDriver.js +317 -43
  123. package/dist/drivers/DirectDriver.js.map +1 -1
  124. package/dist/drivers/SimctlDriver.js +124 -8
  125. package/dist/drivers/SimctlDriver.js.map +1 -1
  126. package/dist/drivers/WdaDriver.js +365 -55
  127. package/dist/drivers/WdaDriver.js.map +1 -1
  128. package/dist/explore/candidates.js +7 -3
  129. package/dist/explore/candidates.js.map +1 -1
  130. package/dist/explore/graph.js +15 -6
  131. package/dist/explore/graph.js.map +1 -1
  132. package/dist/explore/memory.js +4 -1
  133. package/dist/explore/memory.js.map +1 -1
  134. package/dist/explore/planner.js +54 -9
  135. package/dist/explore/planner.js.map +1 -1
  136. package/dist/explore/policy.js +11 -6
  137. package/dist/explore/policy.js.map +1 -1
  138. package/dist/explore/runner.js +363 -190
  139. package/dist/explore/runner.js.map +1 -1
  140. package/dist/explore/signatures.js +4 -2
  141. package/dist/explore/signatures.js.map +1 -1
  142. package/dist/explore/suite.js +3 -1
  143. package/dist/explore/suite.js.map +1 -1
  144. package/dist/featureTesting/executionBootstrap.js +137 -29
  145. package/dist/featureTesting/executionBootstrap.js.map +1 -1
  146. package/dist/featureTesting/featureMap.js +6 -3
  147. package/dist/featureTesting/featureMap.js.map +1 -1
  148. package/dist/featureTesting/featureScope.js +101 -20
  149. package/dist/featureTesting/featureScope.js.map +1 -1
  150. package/dist/featureTesting/mapFeatureScope.js +44 -15
  151. package/dist/featureTesting/mapFeatureScope.js.map +1 -1
  152. package/dist/featureTesting/objectiveModel.js +15 -11
  153. package/dist/featureTesting/objectiveModel.js.map +1 -1
  154. package/dist/featureTesting/resultMerge.js +16 -8
  155. package/dist/featureTesting/resultMerge.js.map +1 -1
  156. package/dist/featureTesting/sources.js +8 -5
  157. package/dist/featureTesting/sources.js.map +1 -1
  158. package/dist/featureTesting/suiteBridge.js +8 -2
  159. package/dist/featureTesting/suiteBridge.js.map +1 -1
  160. package/dist/featureTesting/synonyms.js +32 -4
  161. package/dist/featureTesting/synonyms.js.map +1 -1
  162. package/dist/featureTesting/testCaseFactory.js +27 -15
  163. package/dist/featureTesting/testCaseFactory.js.map +1 -1
  164. package/dist/featureTesting/testPlan.js +34 -12
  165. package/dist/featureTesting/testPlan.js.map +1 -1
  166. package/dist/firstRun/authStateMachine.js +1 -1
  167. package/dist/firstRun/authStateMachine.js.map +1 -1
  168. package/dist/firstRun/classifyScreen.js +9 -6
  169. package/dist/firstRun/classifyScreen.js.map +1 -1
  170. package/dist/firstRun/firstRunPlanner.js +16 -13
  171. package/dist/firstRun/firstRunPlanner.js.map +1 -1
  172. package/dist/firstRun/firstRunRunner.js +92 -31
  173. package/dist/firstRun/firstRunRunner.js.map +1 -1
  174. package/dist/firstRun/generatedDataPolicy.js +25 -15
  175. package/dist/firstRun/generatedDataPolicy.js.map +1 -1
  176. package/dist/firstRun/inputPlanner.js +45 -11
  177. package/dist/firstRun/inputPlanner.js.map +1 -1
  178. package/dist/firstRun/onboardingStateMachine.js +1 -1
  179. package/dist/firstRun/onboardingStateMachine.js.map +1 -1
  180. package/dist/firstRun/paywallPolicy.js +3 -3
  181. package/dist/firstRun/paywallPolicy.js.map +1 -1
  182. package/dist/firstRun/types.js +1 -1
  183. package/dist/firstRun/types.js.map +1 -1
  184. package/dist/fixtures/catalog.js +72 -9
  185. package/dist/fixtures/catalog.js.map +1 -1
  186. package/dist/fixtures/load.js +50 -0
  187. package/dist/fixtures/load.js.map +1 -0
  188. package/dist/flows/discover.js +2 -2
  189. package/dist/flows/discover.js.map +1 -1
  190. package/dist/flows/generate.js +68 -17
  191. package/dist/flows/generate.js.map +1 -1
  192. package/dist/flows/lint.js +88 -14
  193. package/dist/flows/lint.js.map +1 -1
  194. package/dist/flows/pack.js +21 -5
  195. package/dist/flows/pack.js.map +1 -1
  196. package/dist/flows/paths.js +57 -0
  197. package/dist/flows/paths.js.map +1 -0
  198. package/dist/flows/repair.js +176 -37
  199. package/dist/flows/repair.js.map +1 -1
  200. package/dist/flows/run.js +308 -99
  201. package/dist/flows/run.js.map +1 -1
  202. package/dist/flows/schema.js +107 -25
  203. package/dist/flows/schema.js.map +1 -1
  204. package/dist/flows/seedExec.js +8 -4
  205. package/dist/flows/seedExec.js.map +1 -1
  206. package/dist/flows/templates.js +5 -5
  207. package/dist/index.js +50 -8
  208. package/dist/index.js.map +1 -1
  209. package/dist/ios/signing.js +32 -3
  210. package/dist/ios/signing.js.map +1 -1
  211. package/dist/issues/classify.js +28 -20
  212. package/dist/issues/classify.js.map +1 -1
  213. package/dist/issues/fingerprint.js +35 -9
  214. package/dist/issues/fingerprint.js.map +1 -1
  215. package/dist/issues/index.js +93 -46
  216. package/dist/issues/index.js.map +1 -1
  217. package/dist/issues/metrics.js +6 -5
  218. package/dist/issues/metrics.js.map +1 -1
  219. package/dist/issues/recurrence.js +35 -8
  220. package/dist/issues/recurrence.js.map +1 -1
  221. package/dist/issues/report.js +12 -8
  222. package/dist/issues/report.js.map +1 -1
  223. package/dist/issues/reportBridge.js +40 -9
  224. package/dist/issues/reportBridge.js.map +1 -1
  225. package/dist/issues/schema.js +2 -2
  226. package/dist/issues/schema.js.map +1 -1
  227. package/dist/issues/sourceRevision.js +2 -2
  228. package/dist/issues/sourceRevision.js.map +1 -1
  229. package/dist/issues/store.js +110 -32
  230. package/dist/issues/store.js.map +1 -1
  231. package/dist/lib/abortScope.js +48 -0
  232. package/dist/lib/abortScope.js.map +1 -0
  233. package/dist/lib/android.js +84 -8
  234. package/dist/lib/android.js.map +1 -1
  235. package/dist/lib/coordSpace.js +1 -1
  236. package/dist/lib/coordSpace.js.map +1 -1
  237. package/dist/lib/device.js +53 -12
  238. package/dist/lib/device.js.map +1 -1
  239. package/dist/lib/gestures.js +141 -0
  240. package/dist/lib/gestures.js.map +1 -0
  241. package/dist/lib/gitignore.js +4 -3
  242. package/dist/lib/gitignore.js.map +1 -1
  243. package/dist/lib/image.js +12 -3
  244. package/dist/lib/image.js.map +1 -1
  245. package/dist/lib/lockfile.js +399 -0
  246. package/dist/lib/lockfile.js.map +1 -0
  247. package/dist/lib/needsInput.js +9 -6
  248. package/dist/lib/needsInput.js.map +1 -1
  249. package/dist/lib/png.js +4 -3
  250. package/dist/lib/png.js.map +1 -1
  251. package/dist/lib/redact.js +170 -9
  252. package/dist/lib/redact.js.map +1 -1
  253. package/dist/lib/result.js +94 -10
  254. package/dist/lib/result.js.map +1 -1
  255. package/dist/lib/schemaHash.js +7 -5
  256. package/dist/lib/schemaHash.js.map +1 -1
  257. package/dist/lib/sensitive.js +6 -3
  258. package/dist/lib/sensitive.js.map +1 -1
  259. package/dist/lib/simctl.js +3 -3
  260. package/dist/lib/simctl.js.map +1 -1
  261. package/dist/lib/spawn.js +78 -17
  262. package/dist/lib/spawn.js.map +1 -1
  263. package/dist/lib/toolAnnotations.js +114 -0
  264. package/dist/lib/toolAnnotations.js.map +1 -0
  265. package/dist/lib/wda.js +321 -25
  266. package/dist/lib/wda.js.map +1 -1
  267. package/dist/lib/wdaConfig.js +12 -5
  268. package/dist/lib/wdaConfig.js.map +1 -1
  269. package/dist/lib/wdaTune.js +52 -23
  270. package/dist/lib/wdaTune.js.map +1 -1
  271. package/dist/mobileAudit/checks.js +150 -28
  272. package/dist/mobileAudit/checks.js.map +1 -1
  273. package/dist/mobileAudit/evidence.js +4 -4
  274. package/dist/mobileAudit/evidence.js.map +1 -1
  275. package/dist/mobileAudit/profiles.js +121 -20
  276. package/dist/mobileAudit/profiles.js.map +1 -1
  277. package/dist/mobileAudit/results.js +9 -4
  278. package/dist/mobileAudit/results.js.map +1 -1
  279. package/dist/mobileAudit/runner.js +214 -35
  280. package/dist/mobileAudit/runner.js.map +1 -1
  281. package/dist/oracle/auth.js +1 -1
  282. package/dist/oracle/auth.js.map +1 -1
  283. package/dist/oracle/failures.js +1085 -145
  284. package/dist/oracle/failures.js.map +1 -1
  285. package/dist/oracle/health.js +148 -26
  286. package/dist/oracle/health.js.map +1 -1
  287. package/dist/oracle/locator.js +148 -19
  288. package/dist/oracle/locator.js.map +1 -1
  289. package/dist/oracle/record.js +3 -3
  290. package/dist/oracle/record.js.map +1 -1
  291. package/dist/orchestration/envelope.js +2 -2
  292. package/dist/orchestration/envelope.js.map +1 -1
  293. package/dist/orchestration/goal.js +7 -13
  294. package/dist/orchestration/goal.js.map +1 -1
  295. package/dist/orchestration/testThis/execute.js +167 -0
  296. package/dist/orchestration/testThis/execute.js.map +1 -0
  297. package/dist/orchestration/testThis/pipeline.js +413 -0
  298. package/dist/orchestration/testThis/pipeline.js.map +1 -0
  299. package/dist/orchestration/testThis/plan.js +519 -0
  300. package/dist/orchestration/testThis/plan.js.map +1 -0
  301. package/dist/orchestration/testThis/sessionIntent.js +72 -0
  302. package/dist/orchestration/testThis/sessionIntent.js.map +1 -0
  303. package/dist/orchestration/testThis/terminal.js +136 -0
  304. package/dist/orchestration/testThis/terminal.js.map +1 -0
  305. package/dist/orchestration/testThis/types.js +12 -0
  306. package/dist/orchestration/testThis/types.js.map +1 -0
  307. package/dist/plan/plan.js +9 -9
  308. package/dist/plan/plan.js.map +1 -1
  309. package/dist/prompts/index.js +24 -24
  310. package/dist/prompts/index.js.map +1 -1
  311. package/dist/report/coverage.js +0 -1
  312. package/dist/report/coverage.js.map +1 -1
  313. package/dist/report/evidence.js.map +1 -1
  314. package/dist/report/export.js +449 -72
  315. package/dist/report/export.js.map +1 -1
  316. package/dist/report/findingsDedupe.js +41 -0
  317. package/dist/report/findingsDedupe.js.map +1 -0
  318. package/dist/report/flake.js +13 -14
  319. package/dist/report/flake.js.map +1 -1
  320. package/dist/report/history.js +72 -38
  321. package/dist/report/history.js.map +1 -1
  322. package/dist/report/policy.js +18 -7
  323. package/dist/report/policy.js.map +1 -1
  324. package/dist/report/qaLevel.js +10 -16
  325. package/dist/report/qaLevel.js.map +1 -1
  326. package/dist/report/readiness.js +42 -12
  327. package/dist/report/readiness.js.map +1 -1
  328. package/dist/report/sarifSources.js +160 -0
  329. package/dist/report/sarifSources.js.map +1 -0
  330. package/dist/report/summary.js +2 -2
  331. package/dist/report/summary.js.map +1 -1
  332. package/dist/report/testCatalog.js +4 -4
  333. package/dist/report/testCatalog.js.map +1 -1
  334. package/dist/report/timing.js +23 -23
  335. package/dist/report/timing.js.map +1 -1
  336. package/dist/report/toolHealth.js +124 -0
  337. package/dist/report/toolHealth.js.map +1 -0
  338. package/dist/server.js +501 -69
  339. package/dist/server.js.map +1 -1
  340. package/dist/services/automationGenerate.js +8 -2
  341. package/dist/services/automationGenerate.js.map +1 -1
  342. package/dist/services/build.js +10 -4
  343. package/dist/services/build.js.map +1 -1
  344. package/dist/services/flowGenerate.js +75 -0
  345. package/dist/services/flowGenerate.js.map +1 -0
  346. package/dist/services/preflight.js +40 -8
  347. package/dist/services/preflight.js.map +1 -1
  348. package/dist/services/prepareAndroid.js +92 -18
  349. package/dist/services/prepareAndroid.js.map +1 -1
  350. package/dist/services/prepareIos.js +94 -17
  351. package/dist/services/prepareIos.js.map +1 -1
  352. package/dist/services/report.js +271 -80
  353. package/dist/services/report.js.map +1 -1
  354. package/dist/services/smoke.js +68 -22
  355. package/dist/services/smoke.js.map +1 -1
  356. package/dist/services/suiteGenerate.js +43 -6
  357. package/dist/services/suiteGenerate.js.map +1 -1
  358. package/dist/services/testSuiteKnowledge.js +78 -33
  359. package/dist/services/testSuiteKnowledge.js.map +1 -1
  360. package/dist/session/attach.js +284 -4
  361. package/dist/session/attach.js.map +1 -1
  362. package/dist/session/processRegistry.js +433 -0
  363. package/dist/session/processRegistry.js.map +1 -0
  364. package/dist/session/progress.js +1 -1
  365. package/dist/session/progress.js.map +1 -1
  366. package/dist/session/retention.js +201 -0
  367. package/dist/session/retention.js.map +1 -0
  368. package/dist/session/store.js +459 -59
  369. package/dist/session/store.js.map +1 -1
  370. package/dist/snapshot/overlays.js +60 -10
  371. package/dist/snapshot/overlays.js.map +1 -1
  372. package/dist/snapshot/parse.js +30 -12
  373. package/dist/snapshot/parse.js.map +1 -1
  374. package/dist/snapshot/present.js +36 -6
  375. package/dist/snapshot/present.js.map +1 -1
  376. package/dist/snapshot/settle.js +39 -9
  377. package/dist/snapshot/settle.js.map +1 -1
  378. package/dist/state/consent.js +99 -0
  379. package/dist/state/consent.js.map +1 -0
  380. package/dist/state/profile.js +82 -11
  381. package/dist/state/profile.js.map +1 -1
  382. package/dist/suite/compile.js +15 -6
  383. package/dist/suite/compile.js.map +1 -1
  384. package/dist/suite/lint.js +22 -4
  385. package/dist/suite/lint.js.map +1 -1
  386. package/dist/suite/pom.js +75 -44
  387. package/dist/suite/pom.js.map +1 -1
  388. package/dist/suite/secretGuard.js +266 -0
  389. package/dist/suite/secretGuard.js.map +1 -0
  390. package/dist/suite/testcase.js +15 -11
  391. package/dist/suite/testcase.js.map +1 -1
  392. package/dist/testSuite/exporter.js +7 -3
  393. package/dist/testSuite/exporter.js.map +1 -1
  394. package/dist/testSuite/generator.js +43 -14
  395. package/dist/testSuite/generator.js.map +1 -1
  396. package/dist/testSuite/history.js +1 -1
  397. package/dist/testSuite/history.js.map +1 -1
  398. package/dist/testSuite/issueLinks.js +3 -3
  399. package/dist/testSuite/issueLinks.js.map +1 -1
  400. package/dist/testSuite/lint.js +4 -4
  401. package/dist/testSuite/lint.js.map +1 -1
  402. package/dist/testSuite/merge.js +13 -7
  403. package/dist/testSuite/merge.js.map +1 -1
  404. package/dist/testSuite/schema.js +21 -16
  405. package/dist/testSuite/schema.js.map +1 -1
  406. package/dist/testSuite/store.js +12 -4
  407. package/dist/testSuite/store.js.map +1 -1
  408. package/dist/testSuite/traceability.js +2 -2
  409. package/dist/testSuite/traceability.js.map +1 -1
  410. package/dist/tools/act.js +868 -174
  411. package/dist/tools/act.js.map +1 -1
  412. package/dist/tools/agent.js +521 -112
  413. package/dist/tools/agent.js.map +1 -1
  414. package/dist/tools/appControl.js +82 -19
  415. package/dist/tools/appControl.js.map +1 -1
  416. package/dist/tools/appMap.js +393 -189
  417. package/dist/tools/appMap.js.map +1 -1
  418. package/dist/tools/build.js +92 -51
  419. package/dist/tools/build.js.map +1 -1
  420. package/dist/tools/bundletool.js +141 -40
  421. package/dist/tools/bundletool.js.map +1 -1
  422. package/dist/tools/clearOverlay.js +98 -18
  423. package/dist/tools/clearOverlay.js.map +1 -1
  424. package/dist/tools/device.js +108 -19
  425. package/dist/tools/device.js.map +1 -1
  426. package/dist/tools/doctor.js +98 -35
  427. package/dist/tools/doctor.js.map +1 -1
  428. package/dist/tools/explore.js +110 -42
  429. package/dist/tools/explore.js.map +1 -1
  430. package/dist/tools/featureTesting.js +152 -130
  431. package/dist/tools/featureTesting.js.map +1 -1
  432. package/dist/tools/firstRun.js +94 -66
  433. package/dist/tools/firstRun.js.map +1 -1
  434. package/dist/tools/flow.js +347 -132
  435. package/dist/tools/flow.js.map +1 -1
  436. package/dist/tools/flowRepair.js +42 -10
  437. package/dist/tools/flowRepair.js.map +1 -1
  438. package/dist/tools/generate.js +199 -0
  439. package/dist/tools/generate.js.map +1 -0
  440. package/dist/tools/getArtifact.js +37 -8
  441. package/dist/tools/getArtifact.js.map +1 -1
  442. package/dist/tools/health.js +17 -12
  443. package/dist/tools/health.js.map +1 -1
  444. package/dist/tools/ios.js +148 -194
  445. package/dist/tools/ios.js.map +1 -1
  446. package/dist/tools/issues.js +256 -171
  447. package/dist/tools/issues.js.map +1 -1
  448. package/dist/tools/jobs.js +55 -11
  449. package/dist/tools/jobs.js.map +1 -1
  450. package/dist/tools/metro.js +84 -38
  451. package/dist/tools/metro.js.map +1 -1
  452. package/dist/tools/mobileAudit.js +83 -26
  453. package/dist/tools/mobileAudit.js.map +1 -1
  454. package/dist/tools/network.js +36 -14
  455. package/dist/tools/network.js.map +1 -1
  456. package/dist/tools/note.js +31 -17
  457. package/dist/tools/note.js.map +1 -1
  458. package/dist/tools/prepareIosTarget.js +31 -14
  459. package/dist/tools/prepareIosTarget.js.map +1 -1
  460. package/dist/tools/prepareTarget.js +193 -47
  461. package/dist/tools/prepareTarget.js.map +1 -1
  462. package/dist/tools/report.js +16 -8
  463. package/dist/tools/report.js.map +1 -1
  464. package/dist/tools/resolveArtifact.js +12 -12
  465. package/dist/tools/resolveArtifact.js.map +1 -1
  466. package/dist/tools/resolveTarget.js +81 -18
  467. package/dist/tools/resolveTarget.js.map +1 -1
  468. package/dist/tools/screenRecord.js +85 -20
  469. package/dist/tools/screenRecord.js.map +1 -1
  470. package/dist/tools/screenshot.js +42 -18
  471. package/dist/tools/screenshot.js.map +1 -1
  472. package/dist/tools/smoke.js +29 -12
  473. package/dist/tools/smoke.js.map +1 -1
  474. package/dist/tools/snapshot.js +157 -107
  475. package/dist/tools/snapshot.js.map +1 -1
  476. package/dist/tools/startSession.js +117 -84
  477. package/dist/tools/startSession.js.map +1 -1
  478. package/dist/tools/suite.js +391 -310
  479. package/dist/tools/suite.js.map +1 -1
  480. package/dist/tools/testSuite.js +112 -36
  481. package/dist/tools/testSuite.js.map +1 -1
  482. package/dist/tools/testThis.js +35 -802
  483. package/dist/tools/testThis.js.map +1 -1
  484. package/dist/tools/visual.js +698 -116
  485. package/dist/tools/visual.js.map +1 -1
  486. package/dist/tools/wait.js +9 -23
  487. package/dist/tools/wait.js.map +1 -1
  488. package/dist/tools/wda.js +312 -54
  489. package/dist/tools/wda.js.map +1 -1
  490. package/dist/version.js +37 -64
  491. package/dist/version.js.map +1 -1
  492. package/dist/visual/ocr.js +65 -8
  493. package/dist/visual/ocr.js.map +1 -1
  494. package/dist/visual/provider.js +87 -15
  495. package/dist/visual/provider.js.map +1 -1
  496. package/docs/README.md +24 -10
  497. package/docs/ci-reports.md +328 -0
  498. package/docs/concepts.md +227 -0
  499. package/docs/flows.md +153 -0
  500. package/docs/mcp-server.md +210 -104
  501. package/docs/physical-devices.md +84 -0
  502. package/docs/tools.md +808 -217
  503. package/package.json +16 -6
  504. package/dist/appMap/diff.js +0 -60
  505. package/dist/appMap/diff.js.map +0 -1
  506. package/dist/automation/maestroIr.js +0 -282
  507. package/dist/automation/maestroIr.js.map +0 -1
  508. package/dist/interop/maestro.js +0 -151
  509. package/dist/interop/maestro.js.map +0 -1
  510. package/dist/tools/assertVisual.js +0 -74
  511. package/dist/tools/assertVisual.js.map +0 -1
  512. package/dist/tools/automationGenerate.js +0 -394
  513. package/dist/tools/automationGenerate.js.map +0 -1
  514. package/dist/tools/capabilities.js +0 -209
  515. package/dist/tools/capabilities.js.map +0 -1
  516. package/dist/tools/detectContext.js +0 -41
  517. package/dist/tools/detectContext.js.map +0 -1
  518. package/dist/tools/flowGenerate.js +0 -55
  519. package/dist/tools/flowGenerate.js.map +0 -1
  520. package/dist/tools/history.js +0 -56
  521. package/dist/tools/history.js.map +0 -1
  522. package/dist/tools/idling.js +0 -104
  523. package/dist/tools/idling.js.map +0 -1
  524. package/dist/tools/inputCapabilities.js +0 -30
  525. package/dist/tools/inputCapabilities.js.map +0 -1
  526. package/dist/tools/locator.js +0 -57
  527. package/dist/tools/locator.js.map +0 -1
  528. package/dist/tools/maestro.js +0 -78
  529. package/dist/tools/maestro.js.map +0 -1
  530. package/dist/tools/permissions.js +0 -149
  531. package/dist/tools/permissions.js.map +0 -1
  532. package/dist/tools/plan.js +0 -49
  533. package/dist/tools/plan.js.map +0 -1
  534. package/dist/tools/screenInfo.js +0 -70
  535. package/dist/tools/screenInfo.js.map +0 -1
  536. package/dist/tools/seed.js +0 -101
  537. package/dist/tools/seed.js.map +0 -1
  538. package/dist/tools/state.js +0 -170
  539. package/dist/tools/state.js.map +0 -1
  540. package/dist/tools/visualText.js +0 -61
  541. package/dist/tools/visualText.js.map +0 -1
package/docs/README.md CHANGED
@@ -1,19 +1,33 @@
1
1
  # Swipium Docs
2
2
 
3
- This directory contains public documentation for the Swipium MCP server.
3
+ Public documentation for the Swipium MCP server. Start with the top-level [README](../README.md): what Swipium does, requirements, quickstart, environment variables, the CLI, and troubleshooting.
4
4
 
5
- ## Index
5
+ ## Guides
6
6
 
7
- - [MCP Server](mcp-server.md): server setup, client configuration, and agent integration.
8
- - [Tool Reference](tools.md): public MCP tools grouped by workflow.
7
+ - [Concepts](concepts.md): sessions and jobs, project root, consent, secrets and redaction, iOS modes (visual-only vs WebDriverAgent), and a glossary of terms such as app map, flow, POM suite, and `@eN` refs.
8
+ - [Flows](flows.md): writing, running, and repairing replayable flows, with an example, variables, and CI policy.
9
+ - [MCP Server](mcp-server.md): the server command and per-client setup (Claude Code, Codex, Gemini CLI, Cursor, VS Code, Claude Desktop, Windsurf), verification, and server behavior.
10
+ - [CI Reports](ci-reports.md): `swipium report` output formats (JUnit, SARIF, GitHub summary, Markdown, JSON), a GitHub Actions recipe, and the release-gate policy.
11
+
12
+ ## Reference
13
+
14
+ - [Tool Reference](tools.md): every public MCP tool, grouped by capability, with parameters, consent behavior, the [failure-code catalog](tools.md#failure-codes), and the [1.5.0 migration table](tools.md#migrating-from-150). This is the authoritative tool list.
15
+ - [Environment variables](../README.md#configuration--environment-variables): the complete list lives in the README.
16
+ - [Physical Devices](physical-devices.md): why real devices are refused today (`PHYSICAL_DEVICE_UNSUPPORTED`) and what supporting them would require.
17
+
18
+ ## Elsewhere in the repository
19
+
20
+ - Upgrading from 1.5: the [checklist in the README](../README.md#upgrading-from-15) and the [migration table in the CHANGELOG](../CHANGELOG.md#migrating-from-150).
21
+ - Security: [Threat Model](../THREAT_MODEL.md) and [Security Policy](../SECURITY.md).
22
+ - Release history: [CHANGELOG](../CHANGELOG.md).
23
+ - Development: [Contributing](../CONTRIBUTING.md). Getting help: [Support](../SUPPORT.md).
9
24
 
10
25
  ## Scope
11
26
 
12
- Swipium is simulator-only:
27
+ Swipium is a local stdio MCP server for emulators and simulators:
13
28
 
14
- - Android Emulator.
15
- - iOS Simulator.
16
- - Local MCP stdio server.
17
- - Local artifacts and app-map memory.
29
+ - Android Emulator on macOS and Linux (Windows is experimental and untested).
30
+ - iOS Simulator on macOS.
31
+ - Evidence, app maps, flows, and test suites stored locally.
18
32
 
19
- Real devices, Jira integration, external ticket workflows, cloud execution, and certification are outside the current public scope.
33
+ Real devices and cloud execution are outside the current scope.
@@ -0,0 +1,328 @@
1
+ # CI reports
2
+
3
+ `swipium report` turns a finished Swipium QA run into files that CI systems read:
4
+
5
+ - `junit`: test-report XML;
6
+ - `sarif`: SARIF 2.1.0 for GitHub code scanning;
7
+ - `github-summary`: Markdown for `$GITHUB_STEP_SUMMARY`;
8
+ - `markdown` and `json`.
9
+
10
+ A CI pipeline built on Swipium has three parts, and only one of them needs an agent:
11
+
12
+ | Part | Needs an agent? | What does it |
13
+ | --- | --- | --- |
14
+ | Boot an emulator, build the app, install it | No | Your CI (for example `reactivecircus/android-emulator-runner`, Gradle, `adb install`) |
15
+ | **Drive the app and decide what to check** | **Yes** | An MCP client running an LLM (for example Claude Code headless, `claude -p`) that calls Swipium tools and ends with `qa_report` |
16
+ | Render JUnit, SARIF and the summary, and fail the job on the release gate | No | `npx swipium report …`: a deterministic CLI with no device and no LLM |
17
+
18
+ Swipium is an MCP server, not a test runner. Without an MCP client calling its tools, nothing
19
+ exercises the app, and there is no standalone command that runs flows. To replay the same steps on
20
+ every run instead of letting the model decide what to check, have the agent step call `qa_flow_run`
21
+ on committed flows (see [flows.md](flows.md)); that still runs through an MCP client.
22
+
23
+ ## `swipium report`
24
+
25
+ ```text
26
+ swipium report --format <junit|sarif|github-summary|markdown|json>
27
+ [--root <dir>] [--latest | --session <id> | --report <file>]
28
+ [--out <file>] [--fail-on-gate]
29
+ ```
30
+
31
+ - **Which report it reads.** It renders the report JSON that `qa_report` (or the end of a
32
+ `qa_test_this` run) saved for a session. Session state lives under
33
+ `~/.swipium/runs/<project-hash>/<session>/`, so run the CLI on the same runner, as the same user,
34
+ after the agent step, and from the same project root the agent used (or pass `--root`).
35
+ - **`--latest`** is the default. It picks the newest session for `--root` (default: the current
36
+ directory) that has a saved report, and skips sessions that never produced one.
37
+ - **`--session <id>`** picks one session. **`--report <file>`** renders a report JSON you copied
38
+ yourself, for example from a downloaded artifact.
39
+ - **Output.** The file goes to stdout, or to `--out` (parent directories are created). The verdict
40
+ line `Release gate: PASS|BLOCK: <reason>` always goes to stderr.
41
+ - **Exit codes:**
42
+
43
+ | Code | Meaning |
44
+ | --- | --- |
45
+ | `0` | The file was written. The gate passed, or it blocked but `--fail-on-gate` was not set. |
46
+ | `1` | `--fail-on-gate` was set and the release-gate policy blocks. |
47
+ | `2` | Usage error, unknown format, no session or report found, or the file is not a Swipium report. |
48
+
49
+ - **Secrets.** Raw secret values are never written to disk, so the CLI can't scrub them later.
50
+ Instead, `qa_report` deep-redacts the whole report with the session's registered secrets before
51
+ saving it, and the CLI renders that redacted copy. Every string field is redacted, including step
52
+ summaries, workflow names and next steps.
53
+
54
+ ## GitHub Actions recipe (Android)
55
+
56
+ This recipe keeps every Swipium consent out of the agent's hands:
57
+
58
+ - The runner boots the emulator.
59
+ - The workflow installs the APK with `adb`, so it is your decision, made in the workflow.
60
+ - The agent then uses only tools that need no consent: attach to the running emulator, launch the
61
+ installed app, smoke it, and report.
62
+ - `SWIPIUM_REQUIRE_ELICITATION=1` plus `--permission-prompts none` means any unexpected consent
63
+ request is refused instead of approved by the model. The run then reports **blocked** and says
64
+ why.
65
+
66
+ Prerequisites:
67
+
68
+ - The repository builds a debug APK with Gradle. Adjust the build command, `APK` and `APP_ID` for
69
+ your project.
70
+ - `ANTHROPIC_API_KEY` is a repository secret. Claude Code uses it for the agent step, and
71
+ `--bare` requires an API key rather than a subscription login.
72
+ - Claude Code v2.1.259 or later, for `--permission-prompts`. `npm install -g` installs the latest.
73
+ - React Native and Expo projects: `qa_prepare_target` expects Metro to be serving before it
74
+ launches the app. In CI, build a variant with the JS bundle embedded (for example a release build
75
+ signed with a debug key), and add `allowLaunchWithoutMetro: true` to the `qa_prepare_target` call
76
+ in the prompt.
77
+ - For login flows, provide test accounts through `.swipium/fixtures.json` or the
78
+ `SWIPIUM_TEST_EMAIL` / `SWIPIUM_TEST_PASSWORD` secrets passed into the server's `env`. Without
79
+ them, the agent tests what it can reach before login.
80
+
81
+ `.github/workflows/swipium-qa.yml`:
82
+
83
+ ```yaml
84
+ name: Swipium QA
85
+ on: [pull_request]
86
+
87
+ permissions:
88
+ contents: read
89
+ checks: write # mikepenz/action-junit-report (add pull-requests: write only if you set comment: true)
90
+ security-events: write # github/codeql-action/upload-sarif
91
+ actions: read # upload-sarif on private repositories
92
+
93
+ jobs:
94
+ qa:
95
+ runs-on: ubuntu-latest
96
+ timeout-minutes: 45
97
+ env:
98
+ APK: app/build/outputs/apk/debug/app-debug.apk
99
+ APP_ID: com.example.app # your applicationId
100
+ steps:
101
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
102
+
103
+ - uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4.9.1
104
+ with: { distribution: temurin, java-version: '17' }
105
+
106
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
107
+ with: { node-version: '20' }
108
+
109
+ - name: Build debug APK
110
+ run: ./gradlew assembleDebug
111
+
112
+ - name: Install Claude Code
113
+ run: npm install -g @anthropic-ai/claude-code
114
+
115
+ # Hardware acceleration for the emulator (from the android-emulator-runner README).
116
+ - name: Enable KVM
117
+ run: |
118
+ echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
119
+ sudo udevadm control --reload-rules
120
+ sudo udevadm trigger --name-match=kvm
121
+
122
+ # The emulator only lives while `script` runs, so the agent step runs INSIDE it.
123
+ # android-emulator-runner runs each `script` line as its own shell command, so the
124
+ # logic lives in a script file.
125
+ - name: Agent QA run (needs an LLM)
126
+ uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2.38.0
127
+ env:
128
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
129
+ with:
130
+ api-level: 34
131
+ arch: x86_64
132
+ target: google_apis
133
+ disable-animations: true
134
+ script: bash .github/scripts/swipium-agent.sh
135
+
136
+ # Everything below is deterministic: no emulator, no LLM.
137
+ - name: Swipium JUnit
138
+ if: always()
139
+ run: npx -y swipium report --latest --format junit --out swipium/junit.xml
140
+
141
+ - name: Swipium SARIF
142
+ if: always()
143
+ run: npx -y swipium report --latest --format sarif --out swipium/results.sarif
144
+
145
+ - name: Swipium job summary
146
+ if: always()
147
+ run: npx -y swipium report --latest --format github-summary >> "$GITHUB_STEP_SUMMARY"
148
+
149
+ - name: Publish JUnit
150
+ if: always() && hashFiles('swipium/junit.xml') != ''
151
+ uses: mikepenz/action-junit-report@a9170d5795813c01ab4901ffb045b52bab4ab09d # v6.5.0
152
+ with:
153
+ report_paths: swipium/junit.xml
154
+ include_passed: true
155
+
156
+ - name: Upload SARIF to code scanning
157
+ if: always() && hashFiles('swipium/results.sarif') != ''
158
+ uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
159
+ with:
160
+ sarif_file: swipium/results.sarif
161
+ category: swipium
162
+
163
+ # Screenshots and recordings are pixels and are NOT redacted. Drop this step for apps
164
+ # that show real secrets on screen.
165
+ - name: Keep raw evidence
166
+ if: always()
167
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
168
+ with:
169
+ name: swipium-runs
170
+ path: /home/runner/.swipium/runs/ # $HOME on GitHub-hosted ubuntu runners
171
+ retention-days: 14
172
+
173
+ # Last step, so every report above is published before the job fails.
174
+ - name: Release gate
175
+ run: npx -y swipium report --latest --format json --out swipium/report.json --fail-on-gate
176
+ ```
177
+
178
+ `.github/scripts/swipium-agent.sh`:
179
+
180
+ ```bash
181
+ #!/usr/bin/env bash
182
+ set -euo pipefail
183
+
184
+ # 1. Install the app yourself. Swipium's own install path is consent-gated, and nobody can
185
+ # approve a consent in CI.
186
+ adb wait-for-device
187
+ adb install -r -g "$APK"
188
+
189
+ # 2. Swipium as the only MCP server. SWIPIUM_REQUIRE_ELICITATION=1: an unexpected consent is
190
+ # refused (CONSENT_CANCELLED / CONSENT_REFUSED) instead of self-approved by the model.
191
+ cat > "$RUNNER_TEMP/swipium-mcp.json" <<'JSON'
192
+ { "mcpServers": { "swipium": { "command": "npx", "args": ["-y", "swipium"],
193
+ "env": { "SWIPIUM_REQUIRE_ELICITATION": "1" } } } }
194
+ JSON
195
+
196
+ # 3. The agent step. Every call below works without consent on a booted emulator with the app
197
+ # already installed.
198
+ claude --bare -p "You are running unattended in CI. An Android emulator is booted and $APP_ID is installed.
199
+ Use only the swipium MCP tools. Always pass projectRoot=\"$GITHUB_WORKSPACE\" where a tool accepts it.
200
+ 1. qa_start_session { projectRoot, profile: \"full_smoke\" }.
201
+ 2. qa_prepare_target { sessionId, appId: \"$APP_ID\" }. If it returns a jobId, poll qa_job_status { sessionId, jobId, waitMs: 60000 } until it is no longer running.
202
+ 3. qa_smoke { sessionId }.
203
+ 4. If any result is requiresConsent, CONSENT_*, needs_input or blocked, do not try to work around it: record it with qa_note and go to step 5.
204
+ 5. qa_report { sessionId, format: \"summary\" } so the report is saved, then stop." \
205
+ --mcp-config "$RUNNER_TEMP/swipium-mcp.json" \
206
+ --allowedTools "mcp__swipium" \
207
+ --permission-mode dontAsk \
208
+ --permission-prompts none \
209
+ --output-format json > "$RUNNER_TEMP/claude-result.json" || true
210
+
211
+ # The agent's exit status does not gate the job. `swipium report --fail-on-gate` does.
212
+ jq -r '.result // empty' "$RUNNER_TEMP/claude-result.json" || true
213
+ ```
214
+
215
+ Notes on the agent step:
216
+
217
+ - **Claude Code flags.** `claude -p` runs Claude Code without an interactive session.
218
+ - `--bare` skips local hooks, plugins, skills, `CLAUDE.md` and any `.mcp.json` servers, so the
219
+ server from `--mcp-config` is the only MCP server.
220
+ - `--allowedTools "mcp__swipium"` pre-approves every Swipium tool.
221
+ - `--permission-mode dontAsk` denies anything else instead of prompting.
222
+ - `--permission-prompts none` tells Claude nobody can answer prompts, and cancels any MCP
223
+ elicitation request.
224
+
225
+ See the Claude Code docs: [headless](https://code.claude.com/docs/en/headless),
226
+ [CLI reference](https://code.claude.com/docs/en/cli-reference) and
227
+ [permissions](https://code.claude.com/docs/en/permissions).
228
+ - **Why the recipe avoids `qa_test_this` in CI.** Its execute mode requests one combined consent
229
+ for the privileged steps it plans, and every app install is among them. With nobody to approve,
230
+ it would end blocked. The low-level path above attaches to the running emulator, and an app that
231
+ is already installed skips the install consent.
232
+ - **Testing more.** `qa_explore { sessionId }` can follow the smoke. By default it skips
233
+ destructive-looking actions without asking. It asks for consent only when a call names one
234
+ explicit `destructiveCandidate` to run, which the settings above would refuse.
235
+ - **Timing** depends on the model and the app. Treat the first runs as calibration, and set
236
+ `timeout-minutes` from them.
237
+
238
+ ## What each file contains
239
+
240
+ **JUnit** has two suites. The first holds one testcase per recorded workflow, the second one per
241
+ finding.
242
+
243
+ - A workflow `fail` becomes `<failure>`. `blocked`, `skipped` and `not_applicable` become
244
+ `<skipped>`.
245
+ - A high-severity finding becomes `<failure>`. Medium and low findings pass, with the detail in
246
+ `<system-out>`.
247
+ - A failure that the release-gate policy only warns on (`warnOn`) or suppresses (`ignoreKnown`)
248
+ becomes `<skipped message="policy warned|suppressed (release gate not blocked): …">`, so a gate
249
+ PASS never shows up as a failed test.
250
+ - The gate verdict (`swipium.releaseGate`) and reason (`swipium.releaseGate.reason`) are testsuite
251
+ `<properties>`.
252
+ - Evidence URIs appear in each testcase's `<system-out>`.
253
+ - XML-illegal control characters, such as ANSI escapes and NULs from logcat, are replaced with
254
+ U+FFFD, so strict parsers accept the file.
255
+
256
+ **SARIF** has one result per finding and one per failed workflow. GitHub code scanning only shows
257
+ results that point at a file in the repository, so each result is anchored to the most specific
258
+ real file Swipium can justify:
259
+
260
+ 1. the app-map source file of the screen or workflow involved, when `.swipium/app-map.json` exists
261
+ (see `qa_app_map_update`);
262
+ 2. otherwise the project manifest, in this order: `app.json`, `package.json`, `pubspec.yaml`, the
263
+ Gradle app module, an iOS `Info.plist`, then `README.md`.
264
+
265
+ Other SARIF details:
266
+
267
+ - Paths are relative to the repository root, the nearest ancestor with `.git`. In a monorepo an
268
+ anchor reads `apps/mobile/app.json`. Each location has `uriBaseId: %SRCROOT%` and a region on
269
+ line 1.
270
+ - The level follows severity: high is `error`, medium is `warning`, low is `note`. Failed workflows
271
+ are `error`.
272
+ - Screenshots and other `swipium://` evidence go in `relatedLocations` and `properties`.
273
+ - Each result carries its own `partialFingerprints.primaryLocationLineHash`. Volatile ids and
274
+ timestamps are removed before hashing. This keeps distinct results on the same manifest line from
275
+ collapsing into one alert, and keeps alerts stable across runs.
276
+ - `invocations[0].executionSuccessful` is `true` whenever Swipium produced the file. The verdict is
277
+ in `runs[0].properties.releaseGateVerdict` (`pass` or `block`), with the full decision in
278
+ `releaseGate`.
279
+
280
+ See GitHub's
281
+ [SARIF support for code scanning](https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning)
282
+ and
283
+ [Uploading a SARIF file](https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/uploading-a-sarif-file-to-github).
284
+
285
+ **GitHub summary** contains the verdict, the gate line, finding counts, the top findings, failed or
286
+ blocked workflows, and links to the evidence. Text that comes from the run is escaped so it can't
287
+ inject Markdown or HTML: the app id, device name, policy reason and next action. GitHub rejects a
288
+ step summary over 1 MiB, so the summary stops at about 900 KB and ends with a truncation note.
289
+
290
+ ## From inside an agent session (no CLI)
291
+
292
+ `qa_report { sessionId, format: "junit" | "sarif" | "github-summary" }` writes the same file as a
293
+ session artifact and returns its `exportUri`. Read it with `qa_get_artifact { uri }`. This is useful
294
+ when the agent itself publishes the results. In CI, prefer the CLI: it doesn't depend on the agent
295
+ remembering to write files.
296
+
297
+ ## Release-gate policy
298
+
299
+ `.swipium/policy.json` decides the verdict in every export and the `--fail-on-gate` exit code. The
300
+ file's other settings, such as `ciAllowMutations`, are described in
301
+ [flows.md](flows.md#ci-policy). The gate looks at every failed workflow and every high-severity
302
+ finding:
303
+
304
+ - A failed workflow's code is the failure code of its first failing step, else its category, else
305
+ `UNKNOWN`.
306
+ - A finding's code is its failure code, else its kind.
307
+
308
+ ```json
309
+ {
310
+ "blockOn": ["native_crash", "anr", "error_boundary"],
311
+ "warnOn": ["missing_test_data", "assertion_failed"],
312
+ "ignoreKnown": []
313
+ }
314
+ ```
315
+
316
+ Each failure is checked in this order:
317
+
318
+ 1. **No policy file, or one that doesn't parse**: every failure blocks.
319
+ 2. **`ignoreKnown`**: an exact code match (case and punctuation ignored) is suppressed.
320
+ 3. **`blockOn`**: a matching code blocks. An empty or missing `blockOn` blocks every failure. The
321
+ token `failed_required_flow` matches every failure, so with it in `blockOn`, `warnOn` never
322
+ applies.
323
+ 4. **`warnOn`**: a matching code only warns.
324
+ 5. **Anything else blocks.** A failure the policy doesn't mention is not ignored.
325
+
326
+ Tokens match failure codes case-insensitively, with punctuation ignored (`native_crash` matches
327
+ `NATIVE_CRASH`). A few synonyms also work: `crash` means `NATIVE_CRASH`, `app_bug` and
328
+ `visual_diff` mean `ASSERTION_FAILED`. `ciAllowMutations` does not affect the gate.
@@ -0,0 +1,227 @@
1
+ # Concepts
2
+
3
+ The ideas that cut across Swipium's tools: sessions and jobs, how the project root is found, consent, secret handling, iOS modes, and device policy. Per-tool parameters and failure codes are in the [Tool Reference](tools.md); flow files are in [Flows](flows.md); environment variables are in the [README](../README.md#configuration--environment-variables).
4
+
5
+ **Contents**: [Sessions and jobs](#sessions-and-jobs) · [Project root](#project-root) · [Consent](#consent) · [Secrets and redaction](#secrets-and-redaction) · [iOS modes](#ios-modes) · [Devices](#devices) · [Glossary](#glossary)
6
+
7
+ ## Sessions and jobs
8
+
9
+ ### Sessions
10
+
11
+ A session holds one run's device, app, budget, recorded actions, findings, notes, and evidence. `qa_test_this` creates its own; the low-level tools need one from `qa_start_session`. Every tool that takes a `sessionId` returns `INVALID_ARGUMENT` (`Unknown sessionId "…"`) for an unknown id, with nothing run.
12
+
13
+ - **Location**: each session lives in `~/.swipium/runs/<project-hash>/<sessionId>/`, with `state.json` and artifact folders inside. Files are written atomically with mode 0600 (directories 0700). `~/.swipium/registry.json` lists up to 200 reloadable sessions.
14
+ - **What is persisted**: notes, findings, tool errors, jobs, environment changes, and mutations, all redacted with the session's secrets before writing. Recorded actions are persisted in their secret-safe form (see [Secrets and redaction](#secrets-and-redaction)). For fixtures only metadata is written: fixture values, field values, and seed specs never are, and secret generated values are stored as `<redacted>`.
15
+ - **What is never persisted**: secret values and the values of supplied inputs (credentials, OTPs). They live in memory only, so after a server restart a login run asks for them again even though the stored metadata still lists them.
16
+ - **Rehydration**: after a restart, a session reloads on first use. The device, app id, `driverKind`, `wdaUrl`, jobs, artifacts, findings, notes, and mutations are restored. The device is re-attached with the saved driver kind (WDA when the session had attached it and it is still reachable); an offline device is never replaced by a different one. Fixtures are re-read from `.swipium/fixtures.json`, which supplies the live values. A fixture that was passed inline to `qa_start_session` and is not in that file comes back as metadata only, **without its `value`, field values, or `seed`**; re-declare it (or add it to `fixtures.json`) if a run needs them. If the old state shows secret activity, the session is flagged `redactionDegraded`, because the secret set cannot be rebuilt.
17
+ - **Retention**: at startup, session directories are pruned when all of these hold: last activity older than `SWIPIUM_RETENTION_DAYS` (default 30; `0` or `off` disables the automatic prune), not in the registry, not live in this process, and not among the newest `SWIPIUM_RETENTION_KEEP` (default 20) sessions of that project. The prune fails closed: when `registry.json` exists but cannot be read or parsed, nothing is deleted. `swipium gc` applies the same rule on demand.
18
+ - **Artifacts** are addressed as `swipium://session/{sessionId}/{kind}/{name}` and read with `qa_get_artifact` or as MCP resources.
19
+
20
+ ### Jobs
21
+
22
+ Long operations run as background jobs and return a `jobId` at once: `qa_test_this` and `qa_test_feature` in `execute` or `interactive` mode, `qa_explore`, `qa_build` in `run` mode, `qa_bundletool`, and the boot and install steps of `qa_prepare_target`.
23
+
24
+ The usual loop:
25
+
26
+ 1. `qa_test_this {mode:"execute"}` (after any consent) returns `{sessionId, jobId, state:"running"}`.
27
+ 2. `qa_job_status {sessionId, jobId, waitMs:60000}` until `status` is no longer `running`.
28
+ 3. On `result.state:"completed"`, read the report with `qa_get_artifact {uri: result.reportUri}`.
29
+
30
+ **Job status and result state are different fields.**
31
+
32
+ | Field | Values | Meaning |
33
+ | --- | --- | --- |
34
+ | `status` (every job) | `running`, `done`, `failed`, `cancelled` | Where the job is. |
35
+ | `result.state` (`qa_test_this` jobs) | `completed`, `blocked`, `unsafe`, `needs_input` | How the run ended. `completed` and `needs_input` end the job `done`; `blocked` and `unsafe` end it `failed`. The envelope is in `result` either way. |
36
+
37
+ - **needs_input**: there is no `needs_input` job status. A job that stops on a question ends `done` with `result.state:"needs_input"`. `qa_test_this` can also return `state:"needs_input"` **directly, with no `jobId`**: in `interactive` mode, or with `stopOnNeedsInput` or `goal:"test_login"`, the credentials question is asked before any job starts when the project likely has a login and no credentials are available. Either way, relay the one question and make the returned resume call (`qa_continue_from_blocker`).
38
+ - **Polling**: `qa_job_status {sessionId, jobId, waitMs}` returns `{jobId, kind, status, progress, progressDetail, error, result, artifactUris}`. `waitMs` (default 0, capped at 120000) long-polls: the call returns as soon as the job leaves `running` and adds `waited:{waitedMs, timedOut}`. An unknown `jobId` is `INVALID_ARGUMENT`.
39
+ - **Blocking instead of polling**: `qa_test_this {waitForCompletion:true}` waits up to `timeoutMs` (default 120000) and returns the terminal result directly, or `state:"running"` with `timedOutWaiting:true`.
40
+ - **Cancelling**: `qa_job_cancel {sessionId, jobId}` returns `{jobId, cancelled}`; `cancelled:false` means the job had already finished or is unknown. It aborts child processes (build, boot, install, record). A cancelled job has status `cancelled` and no `result`. Side effects already applied are not rolled back, and a worker never overwrites a cancelled job's status.
41
+ - **Restarts**: jobs are persisted with the session. A job that was `running` when the server stopped is marked `failed` with `server restarted while job was running (child process gone)`.
42
+
43
+ ### Cancellation
44
+
45
+ When a tool call is cancelled (MCP `notifications/cancelled`) or its job is cancelled with `qa_job_cancel`, the interrupted work returns `failureCode:"CANCELLED"` with `retrySafe:true`. A call's cancel signal applies to that call only: cancelling an interactive call does not cancel a running job, and the other way round.
46
+
47
+ `CANCELLED` is not a failure. It is never recorded as a tool error, a snapshot failure, a finding, or a health verdict, and it never switches the session to visual-fallback. Side effects that already happened are not rolled back: a cancelled `qa_act` always reports `changedState:true`, and a cancelled `qa_explore` stops with `stoppedReason:"cancelled"` and records no finding for the screen it was observing.
48
+
49
+ ## Project root
50
+
51
+ Tools that need a project resolve it in this order; the first hit wins:
52
+
53
+ 1. The `projectRoot` argument. It must be an absolute, existing directory. An invalid value is an error, never silently replaced.
54
+ 2. MCP roots, when the client exposes a workspace. The first root with a project marker wins, else the first root that is not `/` or `$HOME`.
55
+ 3. `SWIPIUM_PROJECT_ROOT` from the server environment.
56
+ 4. `CLAUDE_PROJECT_DIR`, which Claude Code sets for stdio servers.
57
+ 5. The server's working directory, but never `/` or `$HOME`, and only when it contains a project marker.
58
+
59
+ **Marker rule**: a directory is a project when it contains `package.json`, `app.json`, `pubspec.yaml`, `build.gradle(.kts)`, `settings.gradle(.kts)`, `Podfile`, an `.xcodeproj` or `.xcworkspace`, or an `android/` or `ios/` directory. Only the MCP-roots preference and the working-directory fallback check markers; the `projectRoot` argument and the two environment variables are trusted as given (absolute and existing).
60
+
61
+ When nothing resolves, tools fail with `PROJECT_ROOT_UNRESOLVED`: pass an absolute `projectRoot`, or set `SWIPIUM_PROJECT_ROOT` in the MCP server config's `env`. `qa_test_this` returns `PROJECT_ROOT_EMPTY` for an empty directory and `NOT_MOBILE_PROJECT` when no supported app is found there.
62
+
63
+ **`rootSource`**: results built from a freshly resolved root carry `projectRoot` and `rootSource` (`arg`, `mcp-roots`, `env:SWIPIUM_PROJECT_ROOT`, `env:CLAUDE_PROJECT_DIR`, or `cwd`). When the root came from the working directory, the text also says `project root taken from server cwd: <path>; pass projectRoot to override`. Calls that reuse a session's root do not re-resolve it and carry no `rootSource`.
64
+
65
+ ## Consent
66
+
67
+ Privileged actions (build, boot, install, Metro start, data wipes, recordings, network changes, location spoofing, OCR, flow mutations, destructive exploration, remote WDA, writing into the project's test directory) are consent-gated. Nothing gated runs without approval.
68
+
69
+ ### Mechanisms
70
+
71
+ - **Elicitation**: when the client supports MCP form elicitation, the server asks the user directly and the tool continues on approval. The model never sees a `consentId`. The prompt times out after 10 minutes.
72
+ - **Consent envelope** (client assertion): otherwise the tool returns `{requiresConsent:true, consentId, action, risk, explain, exactCommand, affects}`. The agent shows it to the user and, only after they agree, re-calls the same tool with the same arguments plus `consentId` and `approve:true`. Only `qa_test_this`'s envelope also carries `sessionId`, so its approving re-call reuses the session without `projectRoot`; for every other tool, re-call with the `sessionId` you already passed.
73
+ - **Policy**: with `SWIPIUM_REQUIRE_ELICITATION=1` in the server environment, a client that cannot elicit gets `CONSENT_REFUSED` for every gated action, before the model sees a `consentId`, so the envelope path cannot be used. The setting has no effect on clients that support elicitation.
74
+
75
+ How each action was approved (`elicitation`, `client-assertion`, or `policy`) is recorded in the report's [mutation ledger](#glossary).
76
+
77
+ ### Outcomes
78
+
79
+ Nothing runs in any of these, and a `refused` row is written to the mutation ledger.
80
+
81
+ | Code | When | Retry-safe |
82
+ | --- | --- | --- |
83
+ | `CONSENT_DECLINED` | The user declined the prompt. | no |
84
+ | `CONSENT_CANCELLED` | The prompt was dismissed, timed out, failed in transport, or the call was aborted; also when the action changed while the user was deciding. Re-calling shows a fresh prompt. | yes |
85
+ | `CONSENT_REFUSED` | `SWIPIUM_REQUIRE_ELICITATION=1` is set and the client cannot elicit. | no |
86
+
87
+ Do not retry any of them without asking the user.
88
+
89
+ ### Rules
90
+
91
+ - **Single use and binding**: a `consentId` works once, for the same action and targets it was issued for. A consent issued in a session works only in that session: an id from session A cannot approve a call in session B, and a replay from B does not use up A's consent. A mismatched action or target is refused without burning the id.
92
+ - **Lifetime**: a `consentId` expires 30 minutes after it is issued. At most 200 consents can be pending; beyond that the oldest is dropped.
93
+ - **Stale ids**: an unknown, used, or expired `consentId` is refused, never silently replaced. `qa_test_this` issues a new challenge and says so in `consentNote` (with `previousConsentId`).
94
+ - **Prompt sanitising**: the elicitation prompt quotes every repository-derived value (flow names, queries, URLs, commands) as JSON, strips control characters, newlines, line separators, zero-width and bidirectional characters, and caps each field (at most 4 command steps, 2000 characters overall), so repository content cannot fake extra prompt lines.
95
+ - **Unreviewed sources**: commands and URLs that come from the repository (seed commands, `.swipium/config.json` providers, a configured WDA URL) are labelled as repository-supplied and unreviewed.
96
+
97
+ ### Consent actions and risk levels
98
+
99
+ | Action | Risk | Raised by |
100
+ | --- | --- | --- |
101
+ | `test_this_plan` | Highest of its steps: build high, external APK install medium, install from the project low, boot low | `qa_test_this` execute; `qa_test_feature` execute without a `sessionId` |
102
+ | `prepare_plan` | Medium for an external APK, else low | `qa_prepare_target` |
103
+ | `build_from_source` | High | `qa_build mode:"run"` |
104
+ | `install_app` | Medium (`qa_ios`, `qa_bundletool`); low or medium by path (`qa_prepare_ios_target`) | `qa_ios install`, `qa_bundletool install`, `qa_prepare_ios_target` |
105
+ | `erase_device` | High | `qa_ios erase` |
106
+ | `privacy_reset` | Low | `qa_ios privacy_reset` |
107
+ | `wda_build`, `wda_start` | Medium | `qa_wda` |
108
+ | `wda_non_loopback` | Medium | `qa_wda` with a remote URL |
109
+ | `app_clear_data`, `app_fresh_start` | High | `qa_app_control` |
110
+ | `start_metro` | Medium | `qa_metro start` |
111
+ | `network_change` | Medium | `qa_network`, `qa_mobile_audit` (resilience, release_gate) |
112
+ | `geo_set` | Medium | `qa_geolocation` |
113
+ | `screen_record` | Medium | `qa_screen_record start` |
114
+ | `ocr_run` | Medium | `qa_visual find_text` |
115
+ | `flow_mutation_run` | High for script seeds, else medium | `qa_flow_run` |
116
+ | `destructive_ui_candidate` | High | `qa_explore safeMode:"approved_destructive_candidate"` |
117
+ | `suite_fresh_state_replay` | Highest of its prepare and teardown steps | `qa_generate target:"suite" replay:"fresh_state"` |
118
+ | `automation_project_write` | Medium | `qa_generate target:"appium" integrateIntoProject:true` |
119
+
120
+ Booting a simulator with `qa_ios boot` is not gated (low-risk and reversible). Inside `qa_test_this` and `qa_prepare_target`, a boot is one step of the combined consent.
121
+
122
+ ## Secrets and redaction
123
+
124
+ ### What becomes a secret
125
+
126
+ - Text typed into a secure field (a password field, or a field whose label or id reads like password, OTP, PIN, CVV, card number, secret, token, or security code). This applies to native-selector typing too.
127
+ - Values answered through `qa_continue_from_blocker` whose field names match the credential-name rule below, plus any field listed in `secretFields` (which adds to the rule; it does not replace it).
128
+ - Resolved flow variables and `${SWIPIUM_*}` values from the environment whose names match the credential-name rule: a case-insensitive substring match on `pass`, `secret`, `token`, `otp`, `pin`, `cvv`, `key`, or `code` (so `SWIPIUM_VERIFICATION_CODE` is a secret).
129
+ - Fixture field values read from the environment (`fields.<name>.var`).
130
+ - A typed value that equals or contains a value already registered as a secret, even in an ordinary text field.
131
+
132
+ ### How redaction works
133
+
134
+ - Registered secrets are scrubbed from tool text, structured results, persisted state, reports, text artifacts, OCR text, and provider `stderr`. JSON- and XML-escaped spellings are matched too.
135
+ - Values of 4 or more characters are matched anywhere. A 3-character value, or an all-digit value shorter than 8 characters, is matched only as a standalone token, so a PIN does not scrub unrelated numbers.
136
+ - **Values shorter than 3 characters are not scrubbed.** A text artifact written while such a secret was registered reports `redaction:"partial"` with a `redactionNote`; other artifacts report `applied`, and binary files `not-applied`.
137
+ - **Screenshots and recordings are pixels and are never redacted.** `qa_screenshot` and `qa_visual` withhold captures while a password or OTP field is on screen (`CAPTURE_WITHHELD_SECURE`, unless `force:true`).
138
+ - `qa_act type` never echoes the typed value. `redacted:true` and `secret:true` appear only when the value was treated as a secret; ordinary text omits both, and the recorded step keeps the text, so a generated suite contains it.
139
+ - **Placeholders in `qa_act`**: `${SWIPIUM_*}` names are expanded (only that prefix), from session inputs first, then the server environment. The value is typed but never echoed (`placeholders` lists the names), and the action is recorded with the placeholder. A typed literal that equals a stored session input is recorded as that input's placeholder. An unresolvable placeholder returns `MISSING_TEST_DATA` before anything is tapped.
140
+
141
+ ### Environment access
142
+
143
+ Flows, fixture `fields.var`, and `qa_act` placeholders read the server environment only for `SWIPIUM_*` names. Other names are never read (flows fail the step with `MISSING_FIXTURE`; fixtures log a warning). Pass other values explicitly (`qa_flow_run variables`) or rename them. See [Flows: variables](flows.md#variables).
144
+
145
+ ### Generated output
146
+
147
+ Every `qa_generate` target, `qa_suite_generate`, and the persisted state rewrite recorded secrets into environment placeholders at emit time, marked as needing human data. The placeholder is `SWIPIUM_TEST_PASSWORD`, `SWIPIUM_TEST_OTP`, `SWIPIUM_TEST_TOKEN`, or `SWIPIUM_TEST_PIN` by field kind, else `SWIPIUM_SECRET_<n>`. An existing `${VAR}` is kept, prefixed with `SWIPIUM_` when needed.
148
+
149
+ Form data that `qa_explore` generates is recorded under `SWIPIUM_TEST_EMAIL`, `SWIPIUM_TEST_PASSWORD`, or `SWIPIUM_TEST_OTP`, else `SWIPIUM_GEN_<FIELD>` (a username is `SWIPIUM_GEN_USERNAME`). `qa_first_run` uses the same names, except that a username is `SWIPIUM_TEST_USERNAME`.
150
+
151
+ Registered secret values never reach `test-suite.json`, `TC-*.yaml`, or `state.json`. A final guard scans the output against the session's registered values (not only name heuristics); if one would still be written, generation fails with `SECRET_IN_GENERATED_OUTPUT` and nothing is written.
152
+
153
+ ### Sensitive sessions
154
+
155
+ `qa_start_session {sensitive:true}` refuses every screenshot, recording, log capture, and other on-screen evidence (`SENSITIVE_MODE_REFUSED`). Sensitive sessions are never listed as MCP resources.
156
+
157
+ ## iOS modes
158
+
159
+ Swipium drives iOS Simulators in one of two modes.
160
+
161
+ | Mode | When | What works |
162
+ | --- | --- | --- |
163
+ | **Structured (WDA)** | WebDriverAgent is reachable and attached. | `qa_snapshot`, `qa_act` by `@eN` ref, text, id, or native selector (`accessibility id`, `name`, `predicate string`, `class chain`), structured flows. |
164
+ | **Visual-only** | No reachable WDA. | `qa_screenshot`, `qa_visual` (assert, baseline, diff, OCR, image find), coordinate taps through `idb` when it is installed, flows in `mode: visual` or `auto`. `qa_snapshot`, `qa_act`, and structured flows return `BACKEND_UNSUPPORTED`. |
165
+
166
+ `qa_test_this` and `qa_prepare_ios_target` (`attachWda:"auto"`) fall back to visual-only automatically, with a recorded workaround, unless WDA was explicitly required. `qa_orientation`, `qa_geolocation`, and `qa_network` are not available on iOS in either mode.
167
+
168
+ - **Artifacts**: only simulator `.app` bundles install. A `.ipa` targets a real device and is refused with `IPA_NEEDS_REAL_DEVICE`.
169
+ - **Session capabilities**: every WDA session created for an app sends `shouldTerminateApp:false` (unless `ios.wda.capabilities` in `.swipium/config.json` sets it), because WDA tears down the previous session with that session's setting. Re-binding a resumed session after a restart, and recovering from an invalid-session error, also send `forceAppLaunch:false`, so the running app is reused; the latter adds a warning to verify the screen state.
170
+ - **Managed WDA lifetime**: `qa_wda start` spawns WDA (`xcodebuild test-without-building`) and records it in `~/.swipium/processes.json`; `qa_wda stop` terminates it, including one adopted from a previous server run. A normal server shutdown does not stop managed WDA, so a resumed iOS session can keep using it.
171
+ - **Adoption at startup**: a managed WDA from a previous run is adopted only when it is less than 12 hours old (from its original start) and `GET /status` reports ready; older or unhealthy ones are stopped, as are orphaned Metro bundlers and screen recorders. Orphaned emulators are adopted and left running. A process owned by another running Swipium server is never touched. Each registered process records its start time and full command line, and an orphan is signalled or adopted only when both still match, so a recycled pid is never touched. The startup sweep runs in the background after the server connects.
172
+ - **Remote WDA rule**: loopback means `localhost`, `127.0.0.0/8`, or `[::1]`. A non-loopback `webDriverAgentUrl` needs `allowNonLoopback:true` plus the `wda_non_loopback` consent, otherwise `DESTRUCTIVE_REFUSED`. The only pre-approval is user-level: `SWIPIUM_ALLOW_REMOTE_WDA`, a comma-separated list of exact WDA base URLs in the MCP server's environment. The repository's `.swipium/config.json` cannot pre-approve one: `ios.wda.allowNonLoopbackUrls` only adds a note, and a non-loopback `ios.wda.url` is labelled "configured by the repository (.swipium/config.json), unreviewed" in the prompt. `qa_prepare_ios_target` and `qa_test_this` never connect to a non-loopback configured URL on their own.
173
+
174
+ ## Devices
175
+
176
+ Swipium drives Android Emulators and iOS Simulators on the local machine. Physical devices are out of scope ([physical-devices.md](physical-devices.md)).
177
+
178
+ ### Physical devices
179
+
180
+ A connected phone is refused with `PHYSICAL_DEVICE_UNSUPPORTED` only when:
181
+
182
+ - it is requested explicitly (its serial or UDID as `device`),
183
+ - it is the only option (no emulator online, no AVD to boot, and it is the only device on the chosen platform), or
184
+ - `preferRealDevice` is set and a phone is visible.
185
+
186
+ Otherwise target selection (`qa_resolve_target`, `qa_test_this`) picks an emulator or simulator and only mentions the phone in the selection `reason` (for example `Physical device <serial> is visible but out of scope`). Two exceptions: `qa_test_this` refuses `preferRealDevice:true` even when no phone is visible, and refuses an artifact that installs only on a real iOS device; and `qa_prepare_target` acts on the online device rather than planning: a phone that is the only online device is refused, and with more than one device online (a phone included) and no `device` it returns `MULTIPLE_DEVICES`, so pass the emulator's serial. `qa_test_this` is the path that boots an AVD while a phone is connected.
187
+
188
+ ### Android
189
+
190
+ - **Emulator detection**: an `emulator-NNNN` serial is an emulator. Any other serial, such as an adb-over-TCP `localhost:5555` or `127.0.0.1:<port>`, counts as an emulator only when one `getprop` probe shows an emulator (`ro.kernel.qemu=1`, `ro.boot.qemu=1`, a goldfish or ranchu `ro.hardware`, or Genymotion). When a session binds a device whose properties cannot be read, it is refused as `DEVICE_NOT_READY`; if they show real hardware, `PHYSICAL_DEVICE_UNSUPPORTED`.
191
+ - **Booting**: when an AVD must be booted (headless by default), Swipium records the serials online before the boot and uses only a new serial that is a verified emulator, then waits for `sys.boot_completed` (up to 180 s). So when a phone is connected and an AVD exists, it boots the AVD and installs only on the new emulator. A boot that never comes up is `EMULATOR_BOOT_FAILED`; an online device that never finishes booting is `DEVICE_NOT_READY`.
192
+ - **Several devices**: with more than one device online (phones count) and no `device` argument, `qa_prepare_target` returns `MULTIPLE_DEVICES` with the online serials.
193
+ - **Binding**: a session is only re-bound to its own device. An offline device is never replaced by a different online one.
194
+ - **Toolchain**: a missing `adb` is `ADB_NOT_FOUND` from `qa_test_this`. `qa_network` needs Android 11+.
195
+
196
+ ### iOS Simulator
197
+
198
+ See [iOS modes](#ios-modes). `qa_ios` and `qa_prepare_ios_target` pick a simulator by UDID or name substring; `qa_wda attach` refuses to guess (`MULTIPLE_DEVICES`) when no device is given and none is bound to the session.
199
+
200
+ ## Glossary
201
+
202
+ | Term | Meaning |
203
+ | --- | --- |
204
+ | **`@eN` ref** | A handle such as `@e3` for one element of the latest `qa_snapshot`. Refs are invalid after navigation; an old one returns `STALE_REF`. |
205
+ | **App map** | Swipium's durable memory of the app in `.swipium/app-map.json`: features, screens, navigation, tests, and a code index, each with provenance. Built by `qa_app_map_build` and updated by runs. |
206
+ | **Approval mechanism** | How a consent was decided: `elicitation` (a real user prompt), `client-assertion` (the client re-called with `consentId` and `approve:true`), or `policy` (refused by `SWIPIUM_REQUIRE_ELICITATION=1`). Recorded in the mutation ledger. |
207
+ | **Budget** | A session's limits on time, actions, screenshots, consecutive snapshot failures, and no-change actions. A spent budget returns `{ok:true, stopped:true, reason}`. |
208
+ | **Canonical suite** | The durable, curated test-case catalog in `.swipium/test-suite.json`, managed by the `qa_suite_*` tools. Unlike per-run assets, it grows across runs. |
209
+ | **Capability group** | One of the groups the tools are organised into (start, setup, build, device, drive, run, app-map, feature, flows, generate, test-suite, issues, first-run). `qa_status` without a session returns them. |
210
+ | **Consent** | A user approval for one privileged action. See [Consent](#consent). |
211
+ | **Failure bucket** | How to triage a failure code: `app_bug`, `environment`, `missing_data`, `mcp_limitation`, or `unsafe_refused`. |
212
+ | **Failure owner** | Who fixes a failure: `app`, `environment`, `swipium`, or `user`. Independent of the bucket; `qa_explain_blocker` returns both. |
213
+ | **Finding** | A deterministic health observation (crash, ANR, error boundary, wrong foreground app, …) recorded in the session and report. |
214
+ | **Fixture** | A declared precondition (test account, saved record, disposable data), from `qa_start_session` or `.swipium/fixtures.json`. Unmet fixtures make a workflow blocked instead of failed. |
215
+ | **Flow** | A replayable YAML script of steps under `.swipium/flows/`. See [Flows](flows.md). |
216
+ | **Flow V2** | The current flow format: selector-bound input, visual steps, device-relative gestures, waits, setup and teardown, and `structured`, `visual`, or `auto` mode. |
217
+ | **Job** | A background operation with a `jobId`, polled with `qa_job_status`. See [Jobs](#jobs). |
218
+ | **Mutation ledger** | The report's audit trail of every state-changing action: tool, action, risk, target, consent (with its approval mechanism), and status (`requested`, `approved`, `executed`, `refused`, `blocked`, or `restored`). |
219
+ | **Pack** | A YAML list of flows run together, under `.swipium/packs/`. |
220
+ | **POM suite** | A generated page-object-model suite (page objects plus suites under `.swipium/`) that compiles into runnable flows. Produced by `qa_generate target:"suite"`. |
221
+ | **Response mode** | `compact`, `normal`, or `verbose`: how much of a result is repeated in the text channel. See [Response modes](tools.md#response-modes). |
222
+ | **Sensitive session** | A session started with `sensitive:true`; it refuses all pixel, video, and log capture. |
223
+ | **Stale client** | A client still running a pre-upgrade tool list; removed calls return `STALE_CLIENT`. |
224
+ | **Visual-fallback** | A per-screen switch to screenshot-based work after repeated failed UI-tree dumps (`VISUAL_ONLY_SCREEN`). The next successful structured observation switches back. |
225
+ | **Visual-only** | An iOS session without WDA. See [iOS modes](#ios-modes). |
226
+ | **WDA** | WebDriverAgent, the on-simulator automation server Appium uses. It gives iOS a UI tree. |
227
+ | **Workaround** | A safe fallback Swipium chose on its own (for example visual-only iOS), listed in the report. |