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
@@ -1,9 +1,39 @@
1
- // Report 2.0 exporters (PHASE3-PLAN §3.5). Turn the assembled report into formats a developer
1
+ // Report 2.0 exporters. Turn the assembled report into formats a developer
2
2
  // can use without the agent transcript: an issue-ready Markdown doc and a CI-ready JUnit XML.
3
- // (a third-party-format exporter is deferred — out of scope here.)
3
+ // (a third-party-format exporter is deferred and out of scope here.)
4
4
  //
5
- // These consume the already-redacted report object, so no secret ever reaches an export.
5
+ // Callers pass the report through redactReportData() FIRST (B9): every string field (steps,
6
+ // nextStep, missingPrecondition, workflow names, …) is scrubbed with the session redactor before
7
+ // any format escapes it, so XML/JSON/Markdown escaping can never hide a secret from redaction.
8
+ import { createHash } from 'node:crypto';
6
9
  import { evidenceTaxonomyForNotes } from './evidence.js';
10
+ import { FAILURES } from '../oracle/failures.js';
11
+ import { scrubVolatile } from '../issues/fingerprint.js';
12
+ import { uniqueKey } from '../lib/redact.js';
13
+ /**
14
+ * Deep-redact a report (B9): returns a copy where EVERY string value at any depth (and every string
15
+ * object key) has been passed through the session redactor. Run it once on the assembled report,
16
+ * before persisting it or rendering any export format. Escaping (`&` > `&`, `"` > `\"`) would
17
+ * otherwise turn a secret like `P@ss&w0rd"1` into text the redactor no longer matches.
18
+ */
19
+ export function redactReportData(value, redact) {
20
+ const walk = (v) => {
21
+ if (typeof v === 'string')
22
+ return redact(v) ?? v;
23
+ if (Array.isArray(v))
24
+ return v.map(walk);
25
+ if (v && typeof v === 'object' && Object.getPrototypeOf(v) === Object.prototype) {
26
+ const out = {};
27
+ // Two keys that redact to the same spelling (e.g. "hunter22" and a literal "«redacted»")
28
+ // are disambiguated with a `#2` suffix instead of one silently overwriting the other.
29
+ for (const [k, val] of Object.entries(v))
30
+ out[uniqueKey(out, redact(k) ?? k)] = walk(val);
31
+ return out;
32
+ }
33
+ return v;
34
+ };
35
+ return walk(value);
36
+ }
7
37
  function playwrightStatus(outcome) {
8
38
  if (outcome === 'pass')
9
39
  return 'passed';
@@ -12,8 +42,7 @@ function playwrightStatus(outcome) {
12
42
  return 'skipped';
13
43
  }
14
44
  function playwrightDuration(n) {
15
- const sec = n.steps?.reduce((sum, s) => sum + (s.durationSec ?? 0), 0) ?? 0;
16
- return Math.round(sec * 1000);
45
+ return Math.round(n.steps?.reduce((sum, s) => sum + (s.durationMs ?? 0), 0) ?? 0);
17
46
  }
18
47
  function playwrightAttachments(n) {
19
48
  return (n.artifactUris ?? []).map((uri, i) => ({
@@ -34,11 +63,13 @@ export function toPlaywrightJson(r) {
34
63
  title: n.workflow,
35
64
  ok: status === 'passed',
36
65
  tags: [n.category, n.outcome, evidence?.kind, evidence?.authority].filter(Boolean),
37
- tests: [{
66
+ tests: [
67
+ {
38
68
  timeout: 0,
39
69
  expectedStatus: 'passed',
40
70
  projectName: r.automationBackend?.mode ?? 'swipium',
41
- results: [{
71
+ results: [
72
+ {
42
73
  workerIndex: 0,
43
74
  status,
44
75
  duration: playwrightDuration(n),
@@ -48,13 +79,15 @@ export function toPlaywrightJson(r) {
48
79
  stderr: [],
49
80
  retry: 0,
50
81
  startTime: generatedAt,
51
- }],
82
+ },
83
+ ],
52
84
  status,
53
85
  annotations: [
54
86
  ...(status === 'skipped' ? [{ type: n.outcome, description: message }] : []),
55
87
  ...(evidence?.warning ? [{ type: 'evidence', description: evidence.warning }] : []),
56
88
  ],
57
- }],
89
+ },
90
+ ],
58
91
  file: `swipium://${r.sessionId}/${n.workflow}`,
59
92
  line: 1,
60
93
  column: 1,
@@ -64,24 +97,35 @@ export function toPlaywrightJson(r) {
64
97
  title: `${f.failureCode ?? f.kind}: ${f.detail}`,
65
98
  ok: f.severity !== 'high',
66
99
  tags: [f.bucket, f.severity, f.layer].filter(Boolean),
67
- tests: [{
100
+ tests: [
101
+ {
68
102
  timeout: 0,
69
103
  expectedStatus: 'passed',
70
104
  projectName: r.automationBackend?.mode ?? 'swipium',
71
- results: [{
105
+ results: [
106
+ {
72
107
  workerIndex: 0,
73
108
  status: f.severity === 'high' ? 'failed' : 'passed',
74
109
  duration: 0,
75
- errors: f.severity === 'high' ? [{ message: f.detail, location: { file: f.screenshotUri ?? `swipium://${r.sessionId}/finding/${i + 1}`, line: 1, column: 1 } }] : [],
110
+ errors: f.severity === 'high'
111
+ ? [
112
+ {
113
+ message: f.detail,
114
+ location: { file: f.screenshotUri ?? `swipium://${r.sessionId}/finding/${i + 1}`, line: 1, column: 1 },
115
+ },
116
+ ]
117
+ : [],
76
118
  attachments: f.screenshotUri ? [{ name: 'screenshot', contentType: 'image/*', path: f.screenshotUri }] : [],
77
119
  stdout: [],
78
120
  stderr: [],
79
121
  retry: 0,
80
122
  startTime: generatedAt,
81
- }],
123
+ },
124
+ ],
82
125
  status: f.severity === 'high' ? 'failed' : 'passed',
83
126
  annotations: f.nextStep ? [{ type: 'nextStep', description: f.nextStep }] : [],
84
- }],
127
+ },
128
+ ],
85
129
  file: f.screenshotUri ?? `swipium://${r.sessionId}/finding/${i + 1}`,
86
130
  line: 1,
87
131
  column: 1,
@@ -113,17 +157,19 @@ export function toPlaywrightJson(r) {
113
157
  },
114
158
  projects: [{ name: r.automationBackend?.mode ?? 'swipium' }],
115
159
  },
116
- suites: [{
160
+ suites: [
161
+ {
117
162
  title: `Swipium ${r.appId ?? 'app'}`,
118
163
  file: `swipium://${r.sessionId}/report`,
119
164
  specs: allSpecs,
120
- }],
165
+ },
166
+ ],
121
167
  stats: {
122
168
  expected: allSpecs.length - failed - skipped,
123
169
  skipped,
124
170
  unexpected: failed,
125
171
  flaky: r.flakeClassification?.classification === 'flaky' ? 1 : 0,
126
- duration: Math.round((r.phaseTimings.totalSec ?? 0) * 1000),
172
+ duration: Math.round(r.phaseTimings.totalMs ?? 0),
127
173
  },
128
174
  errors: failed ? [{ message: r.executiveSummary.nextAction }] : [],
129
175
  };
@@ -185,7 +231,7 @@ function tableCell(s) {
185
231
  }
186
232
  function compactValue(value, maxLength = 180) {
187
233
  if (value == null)
188
- return '—';
234
+ return '-';
189
235
  let raw;
190
236
  try {
191
237
  raw = typeof value === 'string' ? value : JSON.stringify(value);
@@ -194,11 +240,11 @@ function compactValue(value, maxLength = 180) {
194
240
  raw = String(value);
195
241
  }
196
242
  if (!raw)
197
- return '—';
243
+ return '-';
198
244
  return raw.length > maxLength ? `${raw.slice(0, Math.max(0, maxLength - 1))}…` : raw;
199
245
  }
200
246
  function mutationTime(at) {
201
- return typeof at === 'number' && Number.isFinite(at) ? new Date(at).toISOString() : '—';
247
+ return typeof at === 'number' && Number.isFinite(at) ? new Date(at).toISOString() : '-';
202
248
  }
203
249
  function mutationConsentSummary(m) {
204
250
  if (!m.consent)
@@ -213,11 +259,13 @@ function mutationEvidenceSummary(m) {
213
259
  m.ledgerUri ? `[ledger](${m.ledgerUri})` : '',
214
260
  m.consent?.payloadHash ? `payload ${m.consent.payloadHash.slice(0, 12)}` : '',
215
261
  ].filter(Boolean);
216
- return parts.length ? parts.join('; ') : '—';
262
+ return parts.length ? parts.join('; ') : '-';
217
263
  }
218
264
  export function inlinePrSummary(r) {
219
265
  const problem = firstReportProblem(r);
220
- const reason = problem ? `${problem.workflow ? `${problem.workflow}: ` : ''}${problem.reason}` : r.executiveSummary.reasons[0] ?? 'No blocking failures detected.';
266
+ const reason = problem
267
+ ? `${problem.workflow ? `${problem.workflow}: ` : ''}${problem.reason}`
268
+ : (r.executiveSummary.reasons[0] ?? 'No blocking failures detected.');
221
269
  const category = likelyReportCategory(problem, r);
222
270
  const lines = [
223
271
  `Swipium: ${statusWord(r.executiveSummary.risk)}`,
@@ -227,7 +275,7 @@ export function inlinePrSummary(r) {
227
275
  if (r.automationBackend)
228
276
  lines.push(`Backend: ${r.automationBackend.description}.`);
229
277
  if (r.readiness?.length)
230
- lines.push(`Readiness: ${r.readiness.join(' → ')}.`);
278
+ lines.push(`Readiness: ${r.readiness.join(' > ')}.`);
231
279
  if (r.automationReadiness)
232
280
  lines.push(`Automation readiness: ${r.automationReadiness.grade} (${r.automationReadiness.score}/100).`);
233
281
  lines.push(`Likely category: ${category}.`, `Evidence: ${evidenceSummary(r, problem)}.`, `Next action: ${r.executiveSummary.nextAction}`);
@@ -236,7 +284,7 @@ export function inlinePrSummary(r) {
236
284
  export function toMarkdown(r) {
237
285
  const L = [];
238
286
  const evidenceTaxonomy = r.evidenceTaxonomy ?? evidenceTaxonomyForNotes(r.testOutcomes);
239
- L.push(`# QA report — ${r.appId ?? 'app'}`);
287
+ L.push(`# QA report: ${r.appId ?? 'app'}`);
240
288
  L.push('');
241
289
  L.push('## PR summary', '');
242
290
  L.push('```text');
@@ -260,7 +308,7 @@ export function toMarkdown(r) {
260
308
  for (const reason of r.executiveSummary.reasons)
261
309
  L.push(`- ${reason}`);
262
310
  }
263
- // Issue memory (SWIPIUM-REQ-07): durable, cross-run issue ledger summary + recurrence warnings.
311
+ // Issue memory: durable, cross-run issue ledger summary + recurrence warnings.
264
312
  if (r.issuesMarkdown) {
265
313
  L.push('');
266
314
  L.push(r.issuesMarkdown);
@@ -272,7 +320,7 @@ export function toMarkdown(r) {
272
320
  }
273
321
  if (r.readiness?.length) {
274
322
  L.push('', '## Capability readiness', '');
275
- L.push(`- Labels: ${r.readiness.join(' → ')}.`);
323
+ L.push(`- Labels: ${r.readiness.join(' > ')}.`);
276
324
  L.push(`- Highest: ${r.readiness.at(-1)}.`);
277
325
  }
278
326
  if (r.wda) {
@@ -323,11 +371,16 @@ export function toMarkdown(r) {
323
371
  L.push('| Workflow | Outcome | Notes |');
324
372
  L.push('| --- | --- | --- |');
325
373
  for (const n of r.testOutcomes) {
326
- const detail = [n.category, n.reason, n.missingPrecondition ? `missing: ${n.missingPrecondition}` : '', n.recommendedSetup ? `setup: ${n.recommendedSetup}` : '']
374
+ const detail = [
375
+ n.category,
376
+ n.reason,
377
+ n.missingPrecondition ? `missing: ${n.missingPrecondition}` : '',
378
+ n.recommendedSetup ? `setup: ${n.recommendedSetup}` : '',
379
+ ]
327
380
  .filter(Boolean)
328
381
  .join('; ')
329
382
  .replace(/\|/g, '\\|');
330
- L.push(`| ${n.workflow} | ${OUTCOME_MARK[n.outcome] ?? ''} ${n.outcome} | ${detail || '—'} |`);
383
+ L.push(`| ${n.workflow} | ${OUTCOME_MARK[n.outcome] ?? ''} ${n.outcome} | ${detail || '-'} |`);
331
384
  }
332
385
  L.push('', '## Evidence quality', '');
333
386
  L.push(`- Deterministic structured locator: ${evidenceTaxonomy.counts.structured_locator}.`);
@@ -337,7 +390,7 @@ export function toMarkdown(r) {
337
390
  L.push('', '| Workflow | Evidence | Authority | Notes |');
338
391
  L.push('| --- | --- | --- | --- |');
339
392
  for (const ev of evidenceTaxonomy.assessments) {
340
- L.push(`| ${tableCell(ev.workflow)} | ${ev.kind} | ${ev.authority} | ${ev.warning ? tableCell(ev.warning) : '—'} |`);
393
+ L.push(`| ${tableCell(ev.workflow)} | ${ev.kind} | ${ev.authority} | ${ev.warning ? tableCell(ev.warning) : '-'} |`);
341
394
  }
342
395
  }
343
396
  if (r.flakeClassification && r.flakeClassification.repeat > 1) {
@@ -353,7 +406,7 @@ export function toMarkdown(r) {
353
406
  L.push('| Fixture | Field | Generator | Variable | Value | Evidence |');
354
407
  L.push('| --- | --- | --- | --- | --- | --- |');
355
408
  for (const g of r.generatedValues) {
356
- L.push(`| ${tableCell(g.fixture)} | ${tableCell(g.field)} | ${tableCell(g.generator)} | \`${tableCell(g.varName)}\` | ${g.secret ? '<redacted>' : tableCell(g.value)} | ${g.artifactUri ? `[artifact](${g.artifactUri})` : '—'} |`);
409
+ L.push(`| ${tableCell(g.fixture)} | ${tableCell(g.field)} | ${tableCell(g.generator)} | \`${tableCell(g.varName)}\` | ${g.secret ? '<redacted>' : tableCell(g.value)} | ${g.artifactUri ? `[artifact](${g.artifactUri})` : '-'} |`);
357
410
  }
358
411
  }
359
412
  if (r.findings.length) {
@@ -363,7 +416,7 @@ export function toMarkdown(r) {
363
416
  const bucket = f.bucket ? ` ${f.bucket}` : '';
364
417
  const retry = f.retrySafe == null ? '' : ` retrySafe=${f.retrySafe}`;
365
418
  const next = f.nextStep ? ` Next: ${f.nextStep}` : '';
366
- L.push(`- **[${f.severity}]**${code}${bucket}${retry} ${f.layer ?? '?'}/${f.kind}: ${f.detail}${f.evidence ? ` — _"${f.evidence}"_` : ''}${f.screenshotUri ? ` ([screenshot](${f.screenshotUri}))` : ''}${next}`);
419
+ L.push(`- **[${f.severity}]**${code}${bucket}${retry} ${f.layer ?? '?'}/${f.kind}: ${f.detail}${f.count && f.count > 1 ? ` (×${f.count})` : ''}${f.evidence ? `: _"${f.evidence}"_` : ''}${f.screenshotUri ? ` ([screenshot](${f.screenshotUri}))` : ''}${next}`);
367
420
  }
368
421
  }
369
422
  if (r.environmentChanges.length || r.guardrailOverrides.length) {
@@ -384,33 +437,36 @@ export function toMarkdown(r) {
384
437
  L.push('| Time | Tool | Action | Risk | Status | Consent | Evidence | Target / detail |');
385
438
  L.push('| --- | --- | --- | --- | --- | --- | --- | --- |');
386
439
  for (const m of r.mutationLedger) {
387
- const targetDetail = [compactValue(m.target), m.detail ? compactValue(m.detail) : '']
388
- .filter((part) => part && part !== '—')
389
- .join('; ') || '—';
440
+ const targetDetail = [compactValue(m.target), m.detail ? compactValue(m.detail) : ''].filter((part) => part && part !== '-').join('; ') || '-';
390
441
  L.push(`| ${tableCell(mutationTime(m.at))} | ${tableCell(m.tool)} | ${tableCell(m.action)} | ${tableCell(m.risk)} | ${tableCell(m.status)} | ${tableCell(mutationConsentSummary(m))} | ${tableCell(mutationEvidenceSummary(m))} | ${tableCell(targetDetail)} |`);
391
442
  }
392
443
  }
444
+ if (r.redactionDegraded) {
445
+ L.push('', '## Redaction notice', '');
446
+ L.push('- ⚠️ This session was restored after a server restart. Secrets registered before the restart are no longer in the redaction set, so artifacts written after the restart may contain those values unredacted.');
447
+ }
393
448
  L.push('', '## Timing & artifacts', '');
394
- L.push(`- Total ${r.phaseTimings.totalSec}s · setup ${r.phaseTimings.setupSec}s · active ${r.phaseTimings.activeSec}s${r.phaseTimings.timeToLoginSec != null ? ` · to-login ${r.phaseTimings.timeToLoginSec}s` : ''}`);
449
+ const fmtS = (ms) => `${Math.round((ms ?? 0) / 1000)}s`;
450
+ L.push(`- Total ${fmtS(r.phaseTimings.totalMs)} · setup ${fmtS(r.phaseTimings.setupMs)} · active ${fmtS(r.phaseTimings.activeMs)}${r.phaseTimings.timeToLoginMs != null ? ` · to-login ${fmtS(r.phaseTimings.timeToLoginMs)}` : ''}`);
395
451
  const d = r.phaseTimings.diagnostics;
396
452
  if (d) {
397
453
  const parts = [
398
- d.simulatorBootSec != null ? `boot ${d.simulatorBootSec}s` : '',
399
- d.appInstallSec != null ? `install ${d.appInstallSec}s` : '',
400
- d.appLaunchSec != null ? `launch ${d.appLaunchSec}s` : '',
401
- d.wdaBuildSec != null ? `WDA build ${d.wdaBuildSec}s` : '',
402
- d.wdaStartSec != null ? `WDA start ${d.wdaStartSec}s` : '',
403
- d.wdaReuseCheckSec != null ? `WDA reuse check ${d.wdaReuseCheckSec}s` : '',
404
- d.wdaStartupWaitSec != null ? `WDA ready wait ${d.wdaStartupWaitSec}s` : '',
405
- d.wdaSessionCreateSec != null ? `WDA session ${d.wdaSessionCreateSec}s` : '',
406
- d.wdaSourceSec != null ? `WDA source ${d.wdaSourceSec}s` : '',
407
- d.wdaFindElementSec != null ? `WDA find ${d.wdaFindElementSec}s` : '',
408
- d.wdaTapSec != null ? `WDA tap ${d.wdaTapSec}s` : '',
409
- d.wdaTypeSec != null ? `WDA type ${d.wdaTypeSec}s` : '',
410
- d.wdaClearSec != null ? `WDA clear ${d.wdaClearSec}s` : '',
411
- d.wdaScreenshotSec != null ? `WDA screenshot ${d.wdaScreenshotSec}s` : '',
412
- d.flowRuntimeSec != null ? `flows ${d.flowRuntimeSec}s` : '',
413
- d.waitSec != null ? `wait ${d.waitSec}s` : '',
454
+ d.simulatorBootMs != null ? `boot ${fmtS(d.simulatorBootMs)}` : '',
455
+ d.appInstallMs != null ? `install ${fmtS(d.appInstallMs)}` : '',
456
+ d.appLaunchMs != null ? `launch ${fmtS(d.appLaunchMs)}` : '',
457
+ d.wdaBuildMs != null ? `WDA build ${fmtS(d.wdaBuildMs)}` : '',
458
+ d.wdaStartMs != null ? `WDA start ${fmtS(d.wdaStartMs)}` : '',
459
+ d.wdaReuseCheckMs != null ? `WDA reuse check ${fmtS(d.wdaReuseCheckMs)}` : '',
460
+ d.wdaStartupWaitMs != null ? `WDA ready wait ${fmtS(d.wdaStartupWaitMs)}` : '',
461
+ d.wdaSessionCreateMs != null ? `WDA session ${fmtS(d.wdaSessionCreateMs)}` : '',
462
+ d.wdaSourceMs != null ? `WDA source ${fmtS(d.wdaSourceMs)}` : '',
463
+ d.wdaFindElementMs != null ? `WDA find ${fmtS(d.wdaFindElementMs)}` : '',
464
+ d.wdaTapMs != null ? `WDA tap ${fmtS(d.wdaTapMs)}` : '',
465
+ d.wdaTypeMs != null ? `WDA type ${fmtS(d.wdaTypeMs)}` : '',
466
+ d.wdaClearMs != null ? `WDA clear ${fmtS(d.wdaClearMs)}` : '',
467
+ d.wdaScreenshotMs != null ? `WDA screenshot ${fmtS(d.wdaScreenshotMs)}` : '',
468
+ d.flowRuntimeMs != null ? `flows ${fmtS(d.flowRuntimeMs)}` : '',
469
+ d.waitMs != null ? `wait ${fmtS(d.waitMs)}` : '',
414
470
  d.screenshotCount != null ? `screenshots ${d.screenshotCount}` : '',
415
471
  ].filter(Boolean);
416
472
  if (parts.length)
@@ -422,35 +478,356 @@ export function toMarkdown(r) {
422
478
  L.push('', '---', '_Generated by Swipium._');
423
479
  return L.join('\n');
424
480
  }
425
- function xmlEscape(s) {
426
- return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
481
+ /** Characters XML 1.0 forbids even as character references (https://www.w3.org/TR/xml/#charsets):
482
+ * C0 controls except TAB/LF/CR, lone UTF-16 surrogates, and U+FFFE/U+FFFF. Logcat/Metro output
483
+ * routinely carries ANSI escapes (\x1b) and NULs; one such byte makes CI reporters reject the whole
484
+ * JUnit file, so each is replaced with U+FFFD (visible, and legal). */
485
+ const XML_ILLEGAL =
486
+ // eslint-disable-next-line no-control-regex
487
+ /[\u0000-\u0008\u000B\u000C\u000E-\u001F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
488
+ export function stripXmlIllegal(s) {
489
+ return s.replace(XML_ILLEGAL, '\uFFFD');
490
+ }
491
+ /** Escape a string for XML text or attribute position. Newlines are entity-escaped too, so a
492
+ * multi-line failure reason survives attribute position and round-trips through any XML parser. */
493
+ export function escapeXml(s) {
494
+ return stripXmlIllegal(s)
495
+ .replace(/&/g, '&amp;')
496
+ .replace(/</g, '&lt;')
497
+ .replace(/>/g, '&gt;')
498
+ .replace(/"/g, '&quot;')
499
+ .replace(/\r/g, '&#13;')
500
+ .replace(/\n/g, '&#10;');
501
+ }
502
+ /** Map the report's failures onto policy FlowVerdicts (src/report/policy.ts): every recorded
503
+ * workflow outcome plus every high-severity finding. This is the seam that lets the release-gate
504
+ * policy (.swipium/policy.json blockOn/warnOn/ignoreKnown) decide the CI pass/fail verdict. */
505
+ export function reportPolicyVerdicts(r) {
506
+ const verdicts = r.testOutcomes.map((n) => ({
507
+ flow: n.workflow,
508
+ passed: n.outcome !== 'fail',
509
+ failureCode: n.outcome === 'fail' ? workflowFailureCode(n) : undefined,
510
+ }));
511
+ for (const f of r.findings) {
512
+ if (f.severity === 'high')
513
+ verdicts.push({ flow: `finding:${f.kind}`, passed: false, failureCode: f.failureCode ?? f.kind });
514
+ }
515
+ return verdicts;
516
+ }
517
+ function junitTestcase(lines, name, classname, body, systemOut) {
518
+ if (!body.length && !systemOut.length) {
519
+ lines.push(` <testcase name="${escapeXml(name)}" classname="${escapeXml(classname)}"></testcase>`);
520
+ return;
521
+ }
522
+ lines.push(` <testcase name="${escapeXml(name)}" classname="${escapeXml(classname)}">`);
523
+ lines.push(...body);
524
+ if (systemOut.length)
525
+ lines.push(` <system-out>${escapeXml(systemOut.join('\n'))}</system-out>`);
526
+ lines.push(` </testcase>`);
527
+ }
528
+ /** The policy key reportPolicyVerdicts/applyPolicy use for a failing workflow / high finding
529
+ * (`${flow}: ${code}`), so exporters can tell which failures the release gate did NOT block. */
530
+ function workflowFailureCode(n) {
531
+ return n.steps?.find((s) => s.failureCode)?.failureCode ?? n.category ?? 'UNKNOWN';
532
+ }
533
+ function workflowPolicyKey(n) {
534
+ return `${n.workflow}: ${workflowFailureCode(n)}`;
535
+ }
536
+ function findingPolicyKey(f) {
537
+ return `finding:${f.kind}: ${f.failureCode ?? f.kind}`;
427
538
  }
428
- export function toJUnit(r) {
429
- // One <testcase> per recorded outcome. fail → <failure>; blocked/skipped/not_applicable → <skipped>.
539
+ /** Failures the gate let through (warnOn / ignoreKnown), keyed like PolicyDecision entries. */
540
+ function nonBlockingFailures(policy) {
541
+ const out = new Map();
542
+ for (const k of policy?.warned ?? [])
543
+ out.set(k, 'warned');
544
+ for (const k of policy?.suppressed ?? [])
545
+ out.set(k, 'suppressed');
546
+ return out;
547
+ }
548
+ /** JUnit XML for CI test-report sinks. Two suites: recorded workflow outcomes (fail > <failure>;
549
+ * blocked/skipped/not_applicable > <skipped>) and findings (high > <failure>, medium/low pass with
550
+ * the detail in system-out). Evidence URIs are listed in each testcase's system-out. When a
551
+ * release-gate PolicyDecision is provided, it is recorded as testsuite <properties> AND it shapes
552
+ * the verdicts (D5): a failure the policy only warns on or ignores (warnOn / ignoreKnown) is
553
+ * emitted as <skipped message="policy …"> rather than <failure>, so "gate PASS" never fails CI. */
554
+ export function toJUnit(r, policy) {
430
555
  const cases = r.testOutcomes;
431
- const failures = cases.filter((n) => n.outcome === 'fail').length;
432
- const skipped = cases.filter((n) => n.outcome === 'blocked' || n.outcome === 'skipped' || n.outcome === 'not_applicable').length;
556
+ const lenient = nonBlockingFailures(policy);
557
+ const caseVerdict = (n) => {
558
+ if (n.outcome === 'pass')
559
+ return 'pass';
560
+ if (n.outcome !== 'fail')
561
+ return 'skipped';
562
+ return lenient.has(workflowPolicyKey(n)) ? 'skipped' : 'failure';
563
+ };
564
+ const findingVerdict = (f) => f.severity !== 'high' ? 'pass' : lenient.has(findingPolicyKey(f)) ? 'skipped' : 'failure';
565
+ const failures = cases.filter((n) => caseVerdict(n) === 'failure').length;
566
+ const skipped = cases.filter((n) => caseVerdict(n) === 'skipped').length;
567
+ const findingFailures = r.findings.filter((f) => findingVerdict(f) === 'failure').length;
568
+ const findingSkipped = r.findings.filter((f) => findingVerdict(f) === 'skipped').length;
433
569
  const suiteName = `swipium.${r.appId ?? 'app'}`;
570
+ const timeSec = (r.phaseTimings.totalMs ?? 0) / 1000;
434
571
  const lines = ['<?xml version="1.0" encoding="UTF-8"?>'];
435
- lines.push(`<testsuite name="${xmlEscape(suiteName)}" tests="${cases.length}" failures="${failures}" skipped="${skipped}" time="${r.phaseTimings.totalSec ?? 0}">`);
572
+ lines.push(`<testsuites name="${escapeXml(suiteName)}" tests="${cases.length + r.findings.length}" failures="${failures + findingFailures}" skipped="${skipped + findingSkipped}" time="${timeSec}">`);
573
+ lines.push(` <testsuite name="${escapeXml(suiteName)}" tests="${cases.length}" failures="${failures}" skipped="${skipped}" time="${timeSec}">`);
574
+ if (policy) {
575
+ lines.push(' <properties>');
576
+ lines.push(` <property name="swipium.releaseGate" value="${policy.block ? 'fail' : 'pass'}"></property>`);
577
+ lines.push(` <property name="swipium.releaseGate.reason" value="${escapeXml(policy.reason)}"></property>`);
578
+ lines.push(' </properties>');
579
+ }
436
580
  for (const n of cases) {
437
- const name = xmlEscape(n.workflow);
438
- const msg = xmlEscape([n.category, n.reason, n.missingPrecondition].filter(Boolean).join(': ') || n.outcome);
439
- if (n.outcome === 'fail') {
440
- lines.push(` <testcase name="${name}" classname="${xmlEscape(suiteName)}">`);
441
- lines.push(` <failure message="${msg}"></failure>`);
442
- lines.push(` </testcase>`);
443
- }
444
- else if (n.outcome === 'blocked' || n.outcome === 'skipped' || n.outcome === 'not_applicable') {
445
- lines.push(` <testcase name="${name}" classname="${xmlEscape(suiteName)}">`);
446
- lines.push(` <skipped message="${msg}"></skipped>`);
447
- lines.push(` </testcase>`);
581
+ const text = [n.category, n.reason, n.missingPrecondition].filter(Boolean).join(': ') || n.outcome;
582
+ const body = [];
583
+ const verdict = caseVerdict(n);
584
+ if (verdict === 'failure')
585
+ body.push(` <failure message="${escapeXml(text)}">${escapeXml(text)}</failure>`);
586
+ else if (verdict === 'skipped') {
587
+ const why = n.outcome === 'fail' ? `policy ${lenient.get(workflowPolicyKey(n))} (release gate not blocked): ${text}` : text;
588
+ body.push(` <skipped message="${escapeXml(why)}"></skipped>`);
448
589
  }
449
- else {
450
- lines.push(` <testcase name="${name}" classname="${xmlEscape(suiteName)}"></testcase>`);
590
+ junitTestcase(lines, n.workflow, suiteName, body, n.artifactUris ?? []);
591
+ }
592
+ lines.push(' </testsuite>');
593
+ if (r.findings.length) {
594
+ const findingsSuite = `${suiteName}.findings`;
595
+ lines.push(` <testsuite name="${escapeXml(findingsSuite)}" tests="${r.findings.length}" failures="${findingFailures}" skipped="${findingSkipped}" time="0">`);
596
+ for (const f of r.findings) {
597
+ const name = `[${f.severity}] ${f.layer ?? '?'}/${f.kind}`;
598
+ const detail = `${f.detail}${f.evidence ? `: "${f.evidence}"` : ''}${f.nextStep ? ` Next: ${f.nextStep}` : ''}`;
599
+ const body = [];
600
+ const systemOut = f.screenshotUri ? [f.screenshotUri] : [];
601
+ const verdict = findingVerdict(f);
602
+ if (verdict === 'failure')
603
+ body.push(` <failure message="${escapeXml(f.failureCode ?? f.kind)}">${escapeXml(detail)}</failure>`);
604
+ else if (verdict === 'skipped') {
605
+ body.push(` <skipped message="${escapeXml(`policy ${lenient.get(findingPolicyKey(f))} (release gate not blocked): ${f.failureCode ?? f.kind}`)}"></skipped>`);
606
+ systemOut.unshift(detail);
607
+ }
608
+ else
609
+ systemOut.unshift(detail);
610
+ junitTestcase(lines, name, findingsSuite, body, systemOut);
451
611
  }
612
+ lines.push(' </testsuite>');
452
613
  }
453
- lines.push('</testsuite>');
614
+ lines.push('</testsuites>');
454
615
  return lines.join('\n');
455
616
  }
617
+ function sarifLevel(severity) {
618
+ return severity === 'high' ? 'error' : severity === 'medium' ? 'warning' : 'note';
619
+ }
620
+ /** Stable 16-hex digest (volatile ids/timestamps scrubbed) for SARIF partialFingerprints. */
621
+ function stableHash(...parts) {
622
+ return createHash('sha256')
623
+ .update(parts.map((p) => scrubVolatile(p.toLowerCase())).join('|'))
624
+ .digest('hex')
625
+ .slice(0, 16);
626
+ }
627
+ /** SARIF 2.1.0 for code-scanning sinks (GitHub `upload-sarif`). Findings > results with
628
+ * ruleId = failureCode/kind and level from severity (high > error, medium > warning, low > note); failed
629
+ * workflows are results too. Per GitHub's SARIF requirements
630
+ * (https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning):
631
+ * - every result has locations[0] > a REAL repo-relative file (`uriBaseId: %SRCROOT%`, full
632
+ * region at line 1): the app-map source of the screen/workflow when known, else the project
633
+ * manifest (`options.sources`, built by resolveSarifSources). Results without one are dropped
634
+ * by code scanning ("at least one location is required").
635
+ * - partialFingerprints carry a stable per-result hash (own `primaryLocationLineHash`, so results
636
+ * anchored to the same manifest line don't collapse into one alert) plus `swipium/v1`.
637
+ * - rules carry short/full descriptions + help from the failure catalog.
638
+ * swipium:// evidence URIs stay in relatedLocations / properties. invocations[0].executionSuccessful
639
+ * means "the tool ran" (always true here); the release-gate verdict lives in run.properties. */
640
+ export function toSarif(r, policy, options = {}) {
641
+ const sources = options.sources ?? { defaultUri: 'package.json' };
642
+ const anchor = (file) => ({
643
+ physicalLocation: {
644
+ artifactLocation: { uri: file, uriBaseId: '%SRCROOT%' },
645
+ region: { startLine: 1, startColumn: 1, endLine: 1, endColumn: 2 },
646
+ },
647
+ });
648
+ const evidenceLocations = (uris) => uris.length
649
+ ? {
650
+ relatedLocations: uris.map((uri, i) => ({
651
+ id: i + 1,
652
+ physicalLocation: { artifactLocation: { uri } },
653
+ message: { text: 'Swipium evidence artifact (resolve with qa_get_artifact)' },
654
+ })),
655
+ }
656
+ : {};
657
+ const seen = new Map();
658
+ const fingerprints = (ruleId, ...identity) => {
659
+ const base = stableHash(ruleId, ...identity);
660
+ // Two genuinely identical results in one run still need distinct alerts-per-occurrence ids.
661
+ const n = (seen.get(base) ?? 0) + 1;
662
+ seen.set(base, n);
663
+ return { primaryLocationLineHash: `${base}:${n}`, 'swipium/v1': `${base}:${n}` };
664
+ };
665
+ const results = r.findings.map((f) => {
666
+ const ruleId = f.failureCode ?? f.kind;
667
+ const file = (f.screen && sources.byScreen?.[f.screen.toLowerCase()]) || sources.defaultUri;
668
+ return {
669
+ ruleId,
670
+ level: sarifLevel(f.severity),
671
+ message: { text: `${f.detail}${f.evidence ? `: "${f.evidence}"` : ''}${f.nextStep ? ` Next: ${f.nextStep}` : ''}` },
672
+ locations: [anchor(file)],
673
+ partialFingerprints: fingerprints(ruleId, f.kind, f.screen ?? '', f.detail),
674
+ ...evidenceLocations(f.screenshotUri ? [f.screenshotUri] : []),
675
+ properties: {
676
+ severity: f.severity,
677
+ ...(f.layer ? { layer: f.layer } : {}),
678
+ ...(f.bucket ? { bucket: f.bucket } : {}),
679
+ ...(f.screen ? { screen: f.screen } : {}),
680
+ ...(f.retrySafe == null ? {} : { retrySafe: f.retrySafe }),
681
+ ...(f.screenshotUri ? { evidenceUris: [f.screenshotUri] } : {}),
682
+ },
683
+ };
684
+ });
685
+ for (const n of r.testOutcomes) {
686
+ if (n.outcome !== 'fail')
687
+ continue;
688
+ const ruleId = n.steps?.find((s) => s.failureCode)?.failureCode ?? n.category ?? 'WORKFLOW_FAILED';
689
+ const file = sources.byWorkflow?.[n.workflow.toLowerCase()] ?? sources.defaultUri;
690
+ results.push({
691
+ ruleId,
692
+ level: 'error',
693
+ message: { text: `${n.workflow}: ${[n.reason, n.missingPrecondition].filter(Boolean).join('; ') || 'workflow failed'}` },
694
+ locations: [anchor(file)],
695
+ partialFingerprints: fingerprints(ruleId, 'workflow', n.workflow),
696
+ ...evidenceLocations(n.artifactUris ?? []),
697
+ properties: {
698
+ workflow: n.workflow,
699
+ outcome: n.outcome,
700
+ ...(n.category ? { category: n.category } : {}),
701
+ ...(n.artifactUris?.length ? { evidenceUris: n.artifactUris } : {}),
702
+ },
703
+ });
704
+ }
705
+ const catalog = FAILURES;
706
+ const rules = [...new Set(results.map((res) => res.ruleId))].map((id) => {
707
+ const info = catalog[id];
708
+ const short = info?.summary ?? `Swipium ${id.replace(/_/g, ' ').toLowerCase()}`;
709
+ const full = info ? `${info.summary}. Bucket: ${info.bucket}.` : `Swipium mobile QA result "${id}" observed on a device run.`;
710
+ const help = info?.recovery ?? 'Open the linked Swipium evidence (screenshots, logs) for this run and reproduce on a device.';
711
+ return {
712
+ id,
713
+ name: id,
714
+ shortDescription: { text: short.slice(0, 1024) },
715
+ fullDescription: { text: full.slice(0, 1024) },
716
+ help: { text: help, markdown: help },
717
+ properties: { tags: ['swipium', 'mobile-qa', ...(info?.bucket ? [info.bucket] : [])] },
718
+ };
719
+ });
720
+ const sarif = {
721
+ $schema: 'https://json.schemastore.org/sarif-2.1.0.json',
722
+ version: '2.1.0',
723
+ runs: [
724
+ {
725
+ tool: { driver: { name: 'swipium', informationUri: 'https://github.com/swipium/swipium', rules } },
726
+ automationDetails: { id: `swipium/${r.appId ?? 'app'}/` },
727
+ originalUriBaseIds: {
728
+ '%SRCROOT%': { description: { text: 'The project root Swipium ran against (repository checkout root in CI).' } },
729
+ },
730
+ invocations: [{ executionSuccessful: true }],
731
+ results,
732
+ properties: {
733
+ sessionId: r.sessionId,
734
+ appId: r.appId,
735
+ device: r.device,
736
+ releaseRisk: r.executiveSummary.risk,
737
+ nextAction: r.executiveSummary.nextAction,
738
+ releaseGateVerdict: policy ? (policy.block ? 'block' : 'pass') : r.executiveSummary.risk === 'block' ? 'block' : 'pass',
739
+ ...(policy ? { releaseGate: policy } : {}),
740
+ },
741
+ },
742
+ ],
743
+ };
744
+ return JSON.stringify(sarif, null, 2);
745
+ }
746
+ /** Longest free-text cell in the job summary; one giant log dump must not eat the 1 MiB budget. */
747
+ const SUMMARY_CELL_MAX = 2000;
748
+ function summaryCell(s) {
749
+ const clipped = s.length > SUMMARY_CELL_MAX ? `${s.slice(0, SUMMARY_CELL_MAX - 1)}…` : s;
750
+ return tableCell(mdText(clipped)).replace(/\r?\n/g, '<br>');
751
+ }
752
+ /** Neutralize Markdown/HTML in free text shown in the job summary (app ids, device names, policy
753
+ * reasons, next actions come from the device / project and are not trusted Markdown). */
754
+ export function mdText(s) {
755
+ return s
756
+ .replace(/&/g, '&amp;')
757
+ .replace(/</g, '&lt;')
758
+ .replace(/>/g, '&gt;')
759
+ .replace(/([\\`*_[\]#|~])/g, '\\$1');
760
+ }
761
+ /** Inline-code span that survives backticks/newlines in the value. */
762
+ function mdCode(s) {
763
+ const flat = s.replace(/\r?\n/g, ' ');
764
+ return flat.includes('`') ? `\`\` ${flat.replace(/``+/g, '`')} \`\`` : `\`${flat}\``;
765
+ }
766
+ /** GitHub caps a step summary at 1 MiB (larger uploads are rejected with an error annotation). We
767
+ * stop at ~900 KB so the verdict survives, and say so. */
768
+ export const GITHUB_SUMMARY_MAX_BYTES = 900 * 1024;
769
+ /** Concise GitHub Actions job-summary Markdown (write it to $GITHUB_STEP_SUMMARY): verdict,
770
+ * counts by severity, top findings table, workflow outcomes, and evidence links. Capped at
771
+ * GITHUB_SUMMARY_MAX_BYTES with a truncation note. */
772
+ export function toGithubSummary(r, policy) {
773
+ const L = [];
774
+ L.push(`## Swipium QA: ${RISK_BADGE[r.executiveSummary.risk] ?? mdText(r.executiveSummary.risk.toUpperCase())}`);
775
+ L.push('');
776
+ L.push(`App ${mdCode(r.appId ?? 'unknown')} on ${mdCode(r.device ?? 'device')}, session ${mdCode(r.sessionId)}.`);
777
+ L.push('');
778
+ if (policy)
779
+ L.push(`**Release gate (policy):** ${policy.block ? '❌ FAIL' : '✅ PASS'}: ${mdText(policy.reason)}`, '');
780
+ L.push(`**Next action:** ${mdText(r.executiveSummary.nextAction)}`);
781
+ L.push('');
782
+ const sevCount = (sev) => r.findings.filter((f) => f.severity === sev).length;
783
+ const outcomes = Object.entries(r.outcomeTally)
784
+ .map(([k, v]) => `${OUTCOME_MARK[k] ?? ''} ${mdText(k)}=${v}`)
785
+ .join(' · ');
786
+ L.push(`Findings: **${sevCount('high')} high** · ${sevCount('medium')} medium · ${sevCount('low')} low. Workflows: ${outcomes || 'none recorded'}.`);
787
+ if (r.findings.length) {
788
+ L.push('', '### Top findings', '', '| Severity | Code | Detail | Evidence |', '| --- | --- | --- | --- |');
789
+ const bySeverity = { high: 0, medium: 1, low: 2 };
790
+ const top = [...r.findings].sort((a, b) => (bySeverity[a.severity] ?? 3) - (bySeverity[b.severity] ?? 3)).slice(0, 10);
791
+ for (const f of top) {
792
+ const evidence = f.screenshotUri ? `[screenshot](${f.screenshotUri})` : '-';
793
+ const times = f.count && f.count > 1 ? ` (×${f.count})` : '';
794
+ L.push(`| ${summaryCell(f.severity)} | ${tableCell(mdCode(f.failureCode ?? f.kind))} | ${summaryCell(f.detail)}${times} | ${evidence} |`);
795
+ }
796
+ }
797
+ const problems = r.testOutcomes.filter((n) => n.outcome !== 'pass');
798
+ if (problems.length) {
799
+ L.push('', '### Failed / blocked workflows', '', '| Workflow | Outcome | Reason | Evidence |', '| --- | --- | --- | --- |');
800
+ for (const n of problems) {
801
+ const reason = [n.category, n.reason, n.missingPrecondition].filter(Boolean).join(': ') || '-';
802
+ const evidence = (n.artifactUris ?? []).map((uri, i) => `[${i + 1}](${uri})`).join(' ') || '-';
803
+ L.push(`| ${summaryCell(n.workflow)} | ${OUTCOME_MARK[n.outcome] ?? ''} ${n.outcome} | ${summaryCell(reason)} | ${evidence} |`);
804
+ }
805
+ }
806
+ const evidenceArtifacts = r.artifacts.filter((a) => ['screenshot', 'recording', 'report'].includes(a.kind)).slice(0, 10);
807
+ if (evidenceArtifacts.length) {
808
+ L.push('', '### Evidence', '');
809
+ for (const a of evidenceArtifacts)
810
+ L.push(`- ${mdCode(a.uri)} (${mdText(a.kind)}${a.label ? `: ${mdText(a.label)}` : ''})`);
811
+ }
812
+ L.push('', '_Generated by Swipium._');
813
+ return capSummary(L);
814
+ }
815
+ /** Join lines, dropping trailing ones (whole lines, never mid-table-row) past the byte cap. */
816
+ function capSummary(lines) {
817
+ const full = lines.join('\n');
818
+ if (Buffer.byteLength(full, 'utf8') <= GITHUB_SUMMARY_MAX_BYTES)
819
+ return full;
820
+ const note = `\n\n> ⚠️ Summary truncated at ${Math.round(GITHUB_SUMMARY_MAX_BYTES / 1024)} KB (GitHub's step-summary limit is 1 MiB). See the JUnit/SARIF/Markdown exports for the full report.`;
821
+ const budget = GITHUB_SUMMARY_MAX_BYTES - Buffer.byteLength(note, 'utf8');
822
+ const kept = [];
823
+ let used = 0;
824
+ for (const line of lines) {
825
+ const size = Buffer.byteLength(line, 'utf8') + 1;
826
+ if (used + size > budget)
827
+ break;
828
+ kept.push(line);
829
+ used += size;
830
+ }
831
+ return kept.join('\n') + note;
832
+ }
456
833
  //# sourceMappingURL=export.js.map