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,14 +1,19 @@
1
- // Report service (Phase 3.2 Milestone B) — the session-report assembly, extracted from the
1
+ // Report service (Phase 3.2 Milestone B): the session-report assembly, extracted from the
2
2
  // qa_report tool so qa_test_this execute can produce a real report artifact in EVERY terminal
3
3
  // state (completed or blocked), not just tell the agent to call qa_report. qa_report is now a thin
4
4
  // wrapper around generateSessionReport().
5
5
  import { existsSync, writeFileSync } from 'node:fs';
6
- import { makeRedactor } from '../lib/redact.js';
6
+ import { makeRedactor, redactDeep } from '../lib/redact.js';
7
+ import { inputBindings } from '../suite/secretGuard.js';
8
+ import { dedupeFindings, repeatSuffix } from '../report/findingsDedupe.js';
9
+ import { toolVerdictFor } from '../report/toolHealth.js';
7
10
  import { getDriver } from '../session/attach.js';
8
11
  import { restoreNetwork } from '../tools/network.js';
9
12
  import { releaseAssessment } from '../report/summary.js';
10
13
  import { FAILURES, failureForFindingKind, bucketForNoteCategory, ALL_BUCKETS } from '../oracle/failures.js';
11
- import { inlinePrSummary, toMarkdown, toJUnit, toPlaywrightJson } from '../report/export.js';
14
+ import { inlinePrSummary, redactReportData, reportPolicyVerdicts, toGithubSummary, toJUnit, toMarkdown, toPlaywrightJson, toSarif, } from '../report/export.js';
15
+ import { applyPolicy, loadPolicy as loadReleasePolicy } from '../report/policy.js';
16
+ import { resolveSarifSources } from '../report/sarifSources.js';
12
17
  import { generateFlow } from '../flows/generate.js';
13
18
  import { loadProjectConfig } from '../cli/scan.js';
14
19
  import { activeRecording } from '../tools/screenRecord.js';
@@ -32,11 +37,15 @@ import { resolveSourceRevision } from '../issues/sourceRevision.js';
32
37
  import { loadPolicy as loadIssuePolicy, getIndex as getIssueIndex, readEvents as readIssueEvents } from '../issues/store.js';
33
38
  import { computeIssueMetrics } from '../issues/metrics.js';
34
39
  import { verifyFixed } from '../issues/index.js';
35
- import { linkIssueToCase, linkRunIssue, relationshipForCase, runRelationshipFor, verifiedFixedIssuesForRun } from '../testSuite/issueLinks.js';
40
+ import { linkIssueToCase, linkRunIssue, relationshipForCase, runRelationshipFor, verifiedFixedIssuesForRun, } from '../testSuite/issueLinks.js';
36
41
  import { saveSuite } from '../testSuite/store.js';
37
42
  import { pomForSession } from './suiteGenerate.js';
38
43
  import { generateCanonicalCases } from '../testSuite/generator.js';
39
44
  import { applyMerge, loadSuite, suiteDelta, runIdFromNow } from '../testSuite/store.js';
45
+ /** Render an *Ms structured value as human-readable seconds for text summaries. */
46
+ function secText(ms) {
47
+ return `${Math.round((ms ?? 0) / 1000)}s`;
48
+ }
40
49
  export async function generateSessionReport(sessions, session, options = {}) {
41
50
  const { format, baseline, trendRoot } = options;
42
51
  const includeCurrentDump = options.includeCurrentDump ?? true;
@@ -48,7 +57,7 @@ export async function generateSessionReport(sessions, session, options = {}) {
48
57
  if (driver)
49
58
  networkRestore = (await restoreNetwork(sessions, session, driver)) ?? undefined;
50
59
  else
51
- networkRestore = 'PENDING — Swipium changed the network but no device is attached to restore it';
60
+ networkRestore = 'PENDING: Swipium changed the network but no device is attached to restore it';
52
61
  }
53
62
  // Attach a redacted current dump (best-effort) for triage.
54
63
  let dumpUri;
@@ -63,6 +72,7 @@ export async function generateSessionReport(sessions, session, options = {}) {
63
72
  }
64
73
  const finalNetwork = driver ? ((await driver.airplaneOn().catch(() => false)) ? 'offline' : 'online') : 'unknown';
65
74
  const high = session.findings.filter((f) => f.severity === 'high');
75
+ const uniqueFindings = dedupeFindings(session.findings);
66
76
  const nativeFindings = session.findings.filter((f) => f.layer === 'native');
67
77
  const appFindings = session.findings.filter((f) => f.layer === 'app');
68
78
  const nativeHealth = nativeFindings.some((f) => f.severity === 'high') ? 'error' : 'OK';
@@ -79,7 +89,9 @@ export async function generateSessionReport(sessions, session, options = {}) {
79
89
  (a[n.outcome] ??= {})[cat] = (a[n.outcome][cat] ?? 0) + 1;
80
90
  return a;
81
91
  }, {});
82
- const visualVerifications = notes.filter((n) => n.verifiedVisually).map((n) => ({
92
+ const visualVerifications = notes
93
+ .filter((n) => n.verifiedVisually)
94
+ .map((n) => ({
83
95
  workflow: n.workflow,
84
96
  outcome: n.outcome,
85
97
  evidenceKind: n.evidenceKind,
@@ -93,9 +105,9 @@ export async function generateSessionReport(sessions, session, options = {}) {
93
105
  const authState = a.loginPerformed
94
106
  ? 'login_performed'
95
107
  : a.authedAtStart === true && !a.loginScreenSeen
96
- ? 'logged_in_at_start (persisted session — login skipped)'
108
+ ? 'logged_in_at_start (persisted session, login skipped)'
97
109
  : a.loginScreenSeen
98
- ? 'auth_required_not_completed (login screen seen, no login performed — credentials may be missing)'
110
+ ? 'auth_required_not_completed (login screen seen, no login performed, credentials may be missing)'
99
111
  : 'unknown (no auth signal observed)';
100
112
  const phaseTimings = phaseTimingsForSession(session);
101
113
  const ec = session.envChanges;
@@ -137,7 +149,13 @@ export async function generateSessionReport(sessions, session, options = {}) {
137
149
  bucketExamples[bucket] ??= `${n.workflow}: ${n.reason ? redact(n.reason) : n.outcome}`;
138
150
  }
139
151
  const dominantBucket = ALL_BUCKETS.filter((b) => bucketCounts[b] > 0).sort((x, y) => bucketCounts[y] - bucketCounts[x])[0] ?? null;
140
- const failureBuckets = { counts: bucketCounts, total: totalFailures, classifiedPct: totalFailures ? Math.round((classified / totalFailures) * 100) : 100, dominant: dominantBucket, examples: bucketExamples };
152
+ const failureBuckets = {
153
+ counts: bucketCounts,
154
+ total: totalFailures,
155
+ classifiedPct: totalFailures ? Math.round((classified / totalFailures) * 100) : 100,
156
+ dominant: dominantBucket,
157
+ examples: bucketExamples,
158
+ };
141
159
  const blockedCount = outcomeTally.blocked ?? 0;
142
160
  const APP_OWNED_FAIL_CATEGORIES = new Set(['app_bug', 'other']);
143
161
  const appFailNotes = notes.filter((n) => n.outcome === 'fail' && (n.category == null || APP_OWNED_FAIL_CATEGORIES.has(n.category)));
@@ -155,14 +173,21 @@ export async function generateSessionReport(sessions, session, options = {}) {
155
173
  blockedCount: blockedCount + toolLimitationFailCount,
156
174
  overrideCount: session.envChanges.filter((c) => /OVERRIDE|GUARDRAIL|acknowledgeBundleRisk|allowLaunchWithoutMetro/i.test(c)).length,
157
175
  topHighFinding: topHigh ? redact(topHigh.detail) : undefined,
158
- topFail: topAppFailNote ? { workflow: topAppFailNote.workflow, reason: topAppFailNote.reason ? redact(topAppFailNote.reason) : undefined } : undefined,
176
+ topFail: topAppFailNote
177
+ ? { workflow: topAppFailNote.workflow, reason: topAppFailNote.reason ? redact(topAppFailNote.reason) : undefined }
178
+ : undefined,
159
179
  topBlocked: topBlockedNote ? { workflow: topBlockedNote.workflow, recommendedSetup: topBlockedNote.recommendedSetup } : undefined,
160
180
  });
161
181
  const outcomesByWorkflow = notes.reduce((acc, n) => {
162
- acc[n.workflow] = { outcome: n.outcome, category: n.category, reason: n.reason ? redact(n.reason) : undefined, artifactUris: n.artifactUris ?? [] };
182
+ acc[n.workflow] = {
183
+ outcome: n.outcome,
184
+ category: n.category,
185
+ reason: n.reason ? redact(n.reason) : undefined,
186
+ artifactUris: n.artifactUris ?? [],
187
+ };
163
188
  return acc;
164
189
  }, {});
165
- const appId = session.appId ?? (loadProjectConfig(session.root)?.appId ?? null);
190
+ const appId = session.appId ?? loadProjectConfig(session.root)?.appId ?? null;
166
191
  const wdaConfig = driver instanceof WdaDriver ? loadWdaConfig(session.root) : null;
167
192
  const wdaReport = driver instanceof WdaDriver && wdaConfig
168
193
  ? {
@@ -174,11 +199,14 @@ export async function generateSessionReport(sessions, session, options = {}) {
174
199
  tuning: { timings: wdaTimingSummary(session), recommendations: wdaRecommendations(wdaConfig, session) },
175
200
  }
176
201
  : null;
177
- const readiness = orderReadinessLabels([...readinessForSession(session, {
202
+ const readiness = orderReadinessLabels([
203
+ ...readinessForSession(session, {
178
204
  suiteRunnable: options.suite?.suiteRunnable,
179
205
  suiteReplayed: false,
180
206
  ciReady: false,
181
- }), ...(options.suite?.readinessLabels ?? [])]);
207
+ }),
208
+ ...(options.suite?.readinessLabels ?? []),
209
+ ]);
182
210
  const automationReadiness = automationReadinessForSession(session, {
183
211
  suiteRunnable: options.suite?.suiteRunnable,
184
212
  suiteReplayed: false,
@@ -188,8 +216,15 @@ export async function generateSessionReport(sessions, session, options = {}) {
188
216
  const automationBackend = automationBackendForSession(session);
189
217
  const passNotes = notes.filter((n) => n.outcome === 'pass').length;
190
218
  const appVerdict = (() => {
191
- if (nativeHealth === 'error' || appHealth === 'error' || high.some((f) => f.layer === 'app' || f.layer === 'native') || appFailCount > 0) {
192
- const summary = topHigh ? (redact(topHigh.detail) ?? topHigh.detail) : topAppFailNote?.reason ? (redact(topAppFailNote.reason) ?? topAppFailNote.reason) : 'High severity app/native finding or failed app workflow observed.';
219
+ if (nativeHealth === 'error' ||
220
+ appHealth === 'error' ||
221
+ high.some((f) => f.layer === 'app' || f.layer === 'native') ||
222
+ appFailCount > 0) {
223
+ const summary = topHigh
224
+ ? (redact(topHigh.detail) ?? topHigh.detail)
225
+ : topAppFailNote?.reason
226
+ ? (redact(topAppFailNote.reason) ?? topAppFailNote.reason)
227
+ : 'High severity app/native finding or failed app workflow observed.';
193
228
  return { status: 'BLOCKED', summary };
194
229
  }
195
230
  if (appHealth === 'degraded')
@@ -198,27 +233,26 @@ export async function generateSessionReport(sessions, session, options = {}) {
198
233
  })();
199
234
  const coverageVerdict = (() => {
200
235
  if (blockedCount > 0 || toolLimitationNotes.length > 0 || session.exploration?.state === 'blocked') {
201
- const summary = topBlockedNote?.recommendedSetup
202
- ?? (toolLimitationNotes.length ? `A Swipium/tool limitation blocked ${toolLimitationNotes.length} workflow(s); see toolVerdict.` : 'A setup or precondition blocker limited coverage.');
236
+ const summary = topBlockedNote?.recommendedSetup ??
237
+ (toolLimitationNotes.length
238
+ ? `A Swipium/tool limitation blocked ${toolLimitationNotes.length} workflow(s); see toolVerdict.`
239
+ : 'A setup or precondition blocker limited coverage.');
203
240
  return { status: 'BLOCKED', summary };
204
241
  }
205
242
  if (!automationBackend.structured) {
206
- return { status: 'PARTIAL', summary: 'Coverage was limited because the active backend is visual-only or lacks structured UI automation.' };
243
+ return {
244
+ status: 'PARTIAL',
245
+ summary: 'Coverage was limited because the active backend is visual-only or lacks structured UI automation.',
246
+ };
207
247
  }
208
248
  if (passNotes === 0 && !session.exploration && session.recordedActions.length === 0) {
209
249
  return { status: 'SMOKE_ONLY', summary: 'Only launch/setup evidence was collected.' };
210
250
  }
211
251
  return { status: 'COVERED', summary: 'Structured smoke/workflow evidence was collected.' };
212
252
  })();
213
- const toolVerdict = (() => {
214
- if (toolLimitationNotes.length === 0)
215
- return { status: 'PASS', summary: 'No Swipium/MCP limitations recorded in this run.' };
216
- const first = toolLimitationNotes[0];
217
- return {
218
- status: 'BLOCKED',
219
- summary: `${toolLimitationNotes.length} workflow(s) limited by Swipium/MCP capabilities (for example ${first.workflow}${first.reason ? `: ${redact(first.reason) ?? first.reason}` : ''}). These are tool limitations, not app defects.`,
220
- };
221
- })();
253
+ // Tool status reflects recorded tool errors (WDA 404, UNKNOWN…) as well as mcp_limitation notes,
254
+ // never "PASS" after tool errors. Tool health only: it does not change the app verdict.
255
+ const toolVerdict = toolVerdictFor(toolLimitationNotes, session.toolErrors, redact);
222
256
  // Single product-facing QA level (Deliverable 6 / §3.11). Derived from the same evidence the
223
257
  // report already trusts: readiness labels and health. Honest by construction.
224
258
  const qaLevel = deriveQaLevel({
@@ -232,7 +266,7 @@ export async function generateSessionReport(sessions, session, options = {}) {
232
266
  ciReplayed: readiness.includes('ci_ready'),
233
267
  });
234
268
  // Universal run test catalog (Deliverable 4 / review #6): documents executed, skipped, blocked,
235
- // and exploration-promoted workflows for EVERY run — even when no suite was generated.
269
+ // and exploration-promoted workflows for EVERY run, even when no suite was generated.
236
270
  const suiteReplayStatus = readiness.includes('ci_ready')
237
271
  ? 'fresh_state'
238
272
  : readiness.includes('replayed')
@@ -252,7 +286,7 @@ export async function generateSessionReport(sessions, session, options = {}) {
252
286
  method: n.method,
253
287
  verifiedVisually: n.verifiedVisually,
254
288
  })),
255
- findings: session.findings.map((f) => ({ kind: f.kind, severity: f.severity, detail: redact(f.detail) ?? f.detail })),
289
+ findings: uniqueFindings.map((f) => ({ kind: f.kind, severity: f.severity, detail: redact(f.detail) ?? f.detail })),
256
290
  exploration: session.exploration
257
291
  ? {
258
292
  screensVisited: session.exploration.summary.screensVisited,
@@ -268,9 +302,9 @@ export async function generateSessionReport(sessions, session, options = {}) {
268
302
  suiteReplayStatus,
269
303
  observed: readiness.includes('observed'),
270
304
  });
271
- // Persistent QA test suite (SWIPIUM-REQ-06): a report run observes QA knowledge, so update the
305
+ // Persistent QA test suite: a report run observes QA knowledge, so update the
272
306
  // canonical suite under .swipium/test-suite.json and embed the per-run delta + canonical case ids.
273
- // Best-effort — a suite-merge failure must never break report generation.
307
+ // Best-effort: a suite-merge failure must never break report generation.
274
308
  let persistentSuite = null;
275
309
  let mergedSuite;
276
310
  try {
@@ -288,7 +322,8 @@ export async function generateSessionReport(sessions, session, options = {}) {
288
322
  now,
289
323
  replayStatus: suiteReplayStatus,
290
324
  });
291
- const applied = applyMerge(session.root, incoming, { source: 'report', mode: 'update', now, runId: runIdFromNow(now) }, appId ?? undefined);
325
+ const applied = applyMerge(session.root, redactDeep(incoming, redact), // defense in depth: no registered secret reaches test-suite.json / TC-*.yaml
326
+ { source: 'report', mode: 'update', now, runId: runIdFromNow(now) }, appId ?? undefined);
292
327
  mergedSuite = applied.result.suite;
293
328
  const delta = suiteDelta(applied.result.suite, applied.result);
294
329
  persistentSuite = {
@@ -301,13 +336,23 @@ export async function generateSessionReport(sessions, session, options = {}) {
301
336
  else {
302
337
  const suite = loadSuite(session.root, appId ?? undefined);
303
338
  mergedSuite = suite;
304
- persistentSuite = { totalCases: suite.cases.length, created: [], updated: [], deprecated: [], failed: [], blocked: [], newlyAutomated: [], caseIds: suite.cases.map((c) => c.id) };
339
+ persistentSuite = {
340
+ totalCases: suite.cases.length,
341
+ created: [],
342
+ updated: [],
343
+ deprecated: [],
344
+ failed: [],
345
+ blocked: [],
346
+ newlyAutomated: [],
347
+ caseIds: suite.cases.map((c) => c.id),
348
+ };
305
349
  }
306
350
  }
307
351
  catch {
308
352
  persistentSuite = null;
309
353
  }
310
354
  const screenshots = session.artifacts.filter((art) => art.kind === 'screenshot');
355
+ const toolErrors = session.toolErrors ?? [];
311
356
  const report = {
312
357
  sessionId: session.id,
313
358
  executiveSummary,
@@ -333,6 +378,8 @@ export async function generateSessionReport(sessions, session, options = {}) {
333
378
  })),
334
379
  workaroundsAttempted: session.workarounds,
335
380
  inputsProvided: session.inputs.map((i) => ({ varName: i.varName, secret: i.secret, source: i.source })),
381
+ // Disclose degraded redaction after a server restart (secrets are never persisted).
382
+ redactionDegraded: session.redactionDegraded === true,
336
383
  generatedValues: session.generatedValues.map((g) => ({
337
384
  ...g,
338
385
  value: g.secret ? '<redacted>' : redact(g.value),
@@ -343,15 +390,32 @@ export async function generateSessionReport(sessions, session, options = {}) {
343
390
  finalNetwork,
344
391
  networkRestore: networkRestore ?? (session.network ? 'no change pending' : 'unchanged'),
345
392
  activeRecording: activeRecording(session.id) ?? null,
346
- findings: session.findings.map((f) => {
393
+ // Identical findings (same code/kind + screen + message) collapse into one entry with a count.
394
+ findings: dedupeFindings(session.findings).map((f) => {
347
395
  const code = f.failureCode ?? failureForFindingKind(f.kind);
348
396
  const info = FAILURES[code] ?? FAILURES.UNKNOWN;
349
- return { ...f, failureCode: code, bucket: info.bucket, retrySafe: info.retrySafe, nextStep: info.recovery, detail: redact(f.detail), evidence: f.evidence ? redact(f.evidence) : undefined };
397
+ return {
398
+ ...f,
399
+ failureCode: code,
400
+ bucket: info.bucket,
401
+ retrySafe: info.retrySafe,
402
+ nextStep: info.recovery,
403
+ detail: redact(f.detail),
404
+ evidence: f.evidence ? redact(f.evidence) : undefined,
405
+ };
350
406
  }),
407
+ findingOccurrences: session.findings.length,
408
+ toolErrors: toolErrors.slice(-50).map((t) => ({ ...t, message: redact(t.message) ?? t.message })),
351
409
  highSeverityCount: high.length,
352
410
  nativeHealth,
353
411
  appHealth,
354
- appHealthFindings: appFindings.map((f) => ({ kind: f.kind, severity: f.severity, evidence: f.evidence ? redact(f.evidence) : undefined, screen: f.screen, screenshotUri: f.screenshotUri })),
412
+ appHealthFindings: appFindings.map((f) => ({
413
+ kind: f.kind,
414
+ severity: f.severity,
415
+ evidence: f.evidence ? redact(f.evidence) : undefined,
416
+ screen: f.screen,
417
+ screenshotUri: f.screenshotUri,
418
+ })),
355
419
  testOutcomes: notes.map((n) => ({ ...n, reason: n.reason ? redact(n.reason) : undefined })),
356
420
  outcomeTally,
357
421
  categoryTally,
@@ -367,7 +431,7 @@ export async function generateSessionReport(sessions, session, options = {}) {
367
431
  wda: wdaReport,
368
432
  ci: { env: !!process.env.CI },
369
433
  visualOnly: session.mode === 'visual-fallback',
370
- // Generated suite (Phase 3.2.1): paths, compiled flows, runnable status — so the report (not
434
+ // Generated suite (Phase 3.2.1): paths, compiled flows, runnable status, so the report (not
371
435
  // just the job result) records whether a runnable automation suite was produced.
372
436
  generatedSuite: options.suite ?? null,
373
437
  // Guided exploration (Phase 3.3 §9.2): screen graph + coverage counts.
@@ -388,16 +452,36 @@ export async function generateSessionReport(sessions, session, options = {}) {
388
452
  artifacts: session.artifacts.map((art) => ({ uri: art.uri, kind: art.kind, mime: art.mime, label: art.label })),
389
453
  counters: session.counters,
390
454
  };
391
- // Issue memory (SWIPIUM-REQ-07): fold this run's findings + app-bug outcomes into the durable
455
+ // Issue memory: fold this run's findings + app-bug outcomes into the durable
392
456
  // `.swipium/issues-log.jsonl`, reopen previously-fixed fingerprints, and attach the issue-memory
393
- // section (+ recurrence markdown) to the report. Best-effort — the ledger never breaks a report.
457
+ // section (+ recurrence markdown) to the report. Best-effort: the ledger never breaks a report.
394
458
  try {
395
459
  const platform = driver instanceof WdaDriver ? 'ios' : driver ? 'android' : 'unknown';
396
460
  const environment = session.headless !== undefined ? (platform === 'ios' ? 'simulator' : 'emulator') : process.env.CI ? 'ci' : 'unknown';
397
461
  const issuePolicy = loadIssuePolicy(session.root);
398
- const sourceRevision = resolveSourceRevision({ env: process.env, allowGitMetadataRead: issuePolicy.allowGitMetadataRead, root: session.root });
399
- const bridgeFindings = session.findings.map((f) => ({ severity: f.severity, kind: f.kind, detail: redact(f.detail) ?? f.detail, layer: f.layer, evidence: f.evidence ? redact(f.evidence) : undefined, screen: f.screen, screenshotUri: f.screenshotUri, failureCode: f.failureCode }));
400
- const bridgeNotes = notes.map((n) => ({ workflow: n.workflow, outcome: n.outcome, category: n.category, reason: n.reason ? redact(n.reason) : undefined, failureCode: n.failureCode, artifactUris: n.artifactUris }));
462
+ const sourceRevision = resolveSourceRevision({
463
+ env: process.env,
464
+ allowGitMetadataRead: issuePolicy.allowGitMetadataRead,
465
+ root: session.root,
466
+ });
467
+ const bridgeFindings = session.findings.map((f) => ({
468
+ severity: f.severity,
469
+ kind: f.kind,
470
+ detail: redact(f.detail) ?? f.detail,
471
+ layer: f.layer,
472
+ evidence: f.evidence ? redact(f.evidence) : undefined,
473
+ screen: f.screen,
474
+ screenshotUri: f.screenshotUri,
475
+ failureCode: f.failureCode,
476
+ }));
477
+ const bridgeNotes = notes.map((n) => ({
478
+ workflow: n.workflow,
479
+ outcome: n.outcome,
480
+ category: n.category,
481
+ reason: n.reason ? redact(n.reason) : undefined,
482
+ failureCode: n.failureCode,
483
+ artifactUris: n.artifactUris,
484
+ }));
401
485
  const issuesNow = new Date().toISOString();
402
486
  const bridge = foldRunIntoLedger(session.root, bridgeFindings, bridgeNotes, issuesNow, {
403
487
  appId: appId ?? undefined,
@@ -407,14 +491,16 @@ export async function generateSessionReport(sessions, session, options = {}) {
407
491
  sourceRevision,
408
492
  });
409
493
  // Link recorded issues to persistent test cases, and verify fixed issues that a PASSING case
410
- // covers this run (REQ-08). The case→workflow key is the workflow string the suite generator
494
+ // covers this run. The case > workflow key is the workflow string the suite generator
411
495
  // folds into actualResult.summary.
412
496
  let verifiedFixedIds = new Set();
413
497
  if (mergedSuite) {
414
498
  const idx = getIssueIndex(session.root, issuesNow, appId ?? undefined);
415
499
  const recordById = new Map(idx.records.map((r) => [r.issueId, r]));
416
500
  const matchCase = (workflow) => workflow
417
- ? mergedSuite.cases.filter((c) => c.actualResult.summary?.toLowerCase().includes(workflow.toLowerCase()) || c.functionality.toLowerCase() === workflow.toLowerCase() || c.title.toLowerCase().includes(workflow.toLowerCase()))
501
+ ? mergedSuite.cases.filter((c) => c.actualResult.summary?.toLowerCase().includes(workflow.toLowerCase()) ||
502
+ c.functionality.toLowerCase() === workflow.toLowerCase() ||
503
+ c.title.toLowerCase().includes(workflow.toLowerCase()))
418
504
  : [];
419
505
  for (const rec of bridge.recorded) {
420
506
  const record = recordById.get(rec.issueId);
@@ -441,14 +527,14 @@ export async function generateSessionReport(sessions, session, options = {}) {
441
527
  verifiedFixedIds = new Set(verified);
442
528
  saveSuite(session.root, mergedSuite);
443
529
  }
444
- // Rebuild the section honoring verified-fixed evidence (REQ-08 honest fix verification).
530
+ // Rebuild the section honoring verified-fixed evidence (honest fix verification).
445
531
  const finalIdx = getIssueIndex(session.root, issuesNow, appId ?? undefined);
446
532
  const finalSection = buildReportIssuesSection(finalIdx.records, new Set(bridge.recordedIssueIds), verifiedFixedIds);
447
533
  report.issues = finalSection;
448
534
  report.issuesMarkdown = issuesSectionToMarkdown(finalSection);
449
535
  if (bridge.recurrences.length)
450
536
  report.issueRecurrences = bridge.recurrences;
451
- // Compact quality metrics when enough history exists (REQ-08): this run's new/reopened/verified
537
+ // Compact quality metrics when enough history exists: this run's new/reopened/verified
452
538
  // counts + open blocker/high totals + reopen/fix-verification rates.
453
539
  const allEvents = readIssueEvents(session.root);
454
540
  if (allEvents.length >= 2) {
@@ -464,15 +550,24 @@ export async function generateSessionReport(sessions, session, options = {}) {
464
550
  avgAgeDays: metrics.avgAgeDays,
465
551
  };
466
552
  }
467
- // Refresh app-map issue summaries from the freshly-updated ledger (REQ-08) — only when a map
468
- // already exists, and WITHOUT a static rescan (runtime_merge load → apply summaries → save).
553
+ // Refresh app-map issue summaries from the freshly-updated ledger, only when a map
554
+ // already exists, and WITHOUT a static rescan (runtime_merge load > apply summaries > save).
469
555
  if (existsSync(appMapPath(session.root))) {
470
556
  buildAppMap(session.root, { mode: 'runtime_merge', at: issuesNow, includeCodeIndex: false, persist: true });
471
557
  }
472
558
  }
473
559
  catch {
474
- /* best-effort — issue ledger is additive, never blocks the report */
560
+ /* best-effort: issue ledger is additive, never blocks the report */
475
561
  }
562
+ // B9: deep-redact the WHOLE report (steps, nextStep, missingPrecondition, workflow names, issue
563
+ // markdown, …) with the session redactor before it is persisted or rendered into any export.
564
+ // Escaping in JUnit/SARIF/Markdown would otherwise turn a secret into text redaction can't match.
565
+ // Replace the fields rather than merge: a top-level key that itself redacts to a different
566
+ // spelling must not survive in its raw form next to the redacted copy.
567
+ const redactedReport = redactReportData(report, redact);
568
+ for (const k of Object.keys(report))
569
+ delete report[k];
570
+ Object.assign(report, redactedReport);
476
571
  const prSummary = { text: inlinePrSummary(report) };
477
572
  report.prSummary = prSummary;
478
573
  const reportUri = sessions.saveArtifact(session, 'report', `report-${Date.now()}.json`, JSON.stringify(report, null, 2), 'application/json');
@@ -481,12 +576,26 @@ export async function generateSessionReport(sessions, session, options = {}) {
481
576
  if ((baseline || trendRoot) && reportRec) {
482
577
  try {
483
578
  const pr = buildPrSummary(reportRec.path, { baselinePath: baseline, trendRoot: trendRoot ?? session.root });
484
- reportLinks = { current: reportUri, currentPath: reportRec.path, baseline: baseline ?? null, trendRoot: trendRoot ?? session.root, comparison: pr.comparison ?? null, knownFlaky: pr.knownFlaky, prSummary: pr };
579
+ reportLinks = {
580
+ current: reportUri,
581
+ currentPath: reportRec.path,
582
+ baseline: baseline ?? null,
583
+ trendRoot: trendRoot ?? session.root,
584
+ comparison: pr.comparison ?? null,
585
+ knownFlaky: pr.knownFlaky,
586
+ prSummary: pr,
587
+ };
485
588
  report.reportLinks = reportLinks;
486
589
  writeFileSync(reportRec.path, JSON.stringify(report, null, 2));
487
590
  }
488
591
  catch (e) {
489
- reportLinks = { current: reportUri, currentPath: reportRec.path, baseline: baseline ?? null, trendRoot: trendRoot ?? session.root, error: String(e.message ?? e) };
592
+ reportLinks = {
593
+ current: reportUri,
594
+ currentPath: reportRec.path,
595
+ baseline: baseline ?? null,
596
+ trendRoot: trendRoot ?? session.root,
597
+ error: String(e.message ?? e),
598
+ };
490
599
  report.reportLinks = reportLinks;
491
600
  writeFileSync(reportRec.path, JSON.stringify(report, null, 2));
492
601
  }
@@ -502,15 +611,49 @@ export async function generateSessionReport(sessions, session, options = {}) {
502
611
  flowExportSkipped = true;
503
612
  }
504
613
  else {
505
- const gen = generateFlow(session.recordedActions, { name: `${(appId ?? 'app').split('.').pop()}-recorded`, appId: appId ?? undefined });
614
+ const gen = generateFlow(session.recordedActions, {
615
+ name: `${(appId ?? 'app').split('.').pop()}-recorded`,
616
+ appId: appId ?? undefined,
617
+ secrets: session.secrets,
618
+ inputs: inputBindings(session),
619
+ });
506
620
  exportUri = sessions.saveArtifact(session, 'flow', `recorded-${Date.now()}.yaml`, gen.yaml, 'text/yaml', `flow drafted from the run (durability ${gen.durability.grade})`);
507
621
  }
508
622
  }
509
623
  else {
510
624
  const data = report;
511
- const body = format === 'markdown' ? toMarkdown(data) : format === 'playwright' ? toPlaywrightJson(data) : toJUnit(data);
512
- const ext = format === 'markdown' ? 'md' : format === 'playwright' ? 'playwright.json' : 'xml';
513
- const mime = format === 'markdown' ? 'text/markdown' : format === 'playwright' ? 'application/json' : 'application/xml';
625
+ // CI formats (junit/sarif/github-summary) carry a release-gate verdict: map the report's
626
+ // failures onto policy FlowVerdicts, then let .swipium/policy.json (blockOn/warnOn/
627
+ // ignoreKnown) decide pass/fail. With no policy file, ANY failure blocks (applyPolicy).
628
+ const ciFormat = format === 'junit' || format === 'sarif' || format === 'github-summary';
629
+ const policy = ciFormat
630
+ ? applyPolicy(reportPolicyVerdicts(data), loadReleasePolicy(session.root))
631
+ : undefined;
632
+ const body = format === 'markdown'
633
+ ? toMarkdown(data)
634
+ : format === 'playwright'
635
+ ? toPlaywrightJson(data)
636
+ : format === 'sarif'
637
+ ? toSarif(data, policy, { sources: resolveSarifSources(session.root, data) })
638
+ : format === 'github-summary'
639
+ ? toGithubSummary(data, policy)
640
+ : toJUnit(data, policy);
641
+ const ext = format === 'markdown'
642
+ ? 'md'
643
+ : format === 'playwright'
644
+ ? 'playwright.json'
645
+ : format === 'sarif'
646
+ ? 'sarif.json'
647
+ : format === 'github-summary'
648
+ ? 'summary.md'
649
+ : 'xml';
650
+ const mime = format === 'markdown' || format === 'github-summary'
651
+ ? 'text/markdown'
652
+ : format === 'playwright'
653
+ ? 'application/json'
654
+ : format === 'sarif'
655
+ ? 'application/sarif+json'
656
+ : 'application/xml';
514
657
  exportUri = sessions.saveArtifact(session, 'report', `report-${Date.now()}.${ext}`, body, mime);
515
658
  }
516
659
  }
@@ -518,55 +661,92 @@ export async function generateSessionReport(sessions, session, options = {}) {
518
661
  const manifestUri = sessions.saveArtifact(session, 'manifest', `manifest-${Date.now()}.json`, JSON.stringify(manifest, null, 2), 'application/json', 'artifact manifest with SHA-256 hashes');
519
662
  const noteSummaryLines = notes.map((n) => {
520
663
  const ev = evidenceByWorkflow.get(n.workflow);
521
- return ` • ${n.workflow}: ${n.outcome}${ev ? ` (evidence=${ev.kind}/${ev.authority})` : ''}${n.category ? ` [${n.category}]` : ''}${n.missingPrecondition ? ` — missing: ${n.missingPrecondition}` : n.reason ? ` — ${redact(n.reason)}` : ''}${n.recommendedSetup ? ` · setup: ${n.recommendedSetup}` : ''}`;
664
+ return ` • ${n.workflow}: ${n.outcome}${ev ? ` (evidence=${ev.kind}/${ev.authority})` : ''}${n.category ? ` [${n.category}]` : ''}${n.missingPrecondition ? ` (missing: ${n.missingPrecondition})` : n.reason ? `: ${redact(n.reason)}` : ''}${n.recommendedSetup ? ` · setup: ${n.recommendedSetup}` : ''}`;
522
665
  });
523
666
  const lines = [
524
- `Report for ${session.id} — app=${report.appId} device=${report.device}`,
525
- `RELEASE RISK: ${executiveSummary.risk.toUpperCase()} — ${executiveSummary.nextAction}`,
667
+ `Report for ${session.id}: app=${report.appId} device=${report.device}`,
668
+ `RELEASE RISK: ${executiveSummary.risk.toUpperCase()}: ${executiveSummary.nextAction}`,
526
669
  `App status: ${appVerdict.status} - ${appVerdict.summary}`,
527
670
  `Coverage status: ${coverageVerdict.status} - ${coverageVerdict.summary}`,
528
671
  `Tool status: ${toolVerdict.status} - ${toolVerdict.summary}`,
529
672
  `PR summary:\n${prSummary.text}`,
530
673
  `backend: ${report.automationBackend.description} (${report.automationBackend.mode})`,
531
- `QA level: ${qaLevel.level.toUpperCase()} — ${qaLevel.rationale}${qaLevel.next ? ` · next: ${qaLevel.next} (${qaLevel.nextRequirement})` : ''}`,
674
+ `QA level: ${qaLevel.level.toUpperCase()}: ${qaLevel.rationale}${qaLevel.next ? ` · next: ${qaLevel.next} (${qaLevel.nextRequirement})` : ''}`,
532
675
  qaLevel.notes.length ? `QA level notes:\n ${qaLevel.notes.join('\n ')}` : '',
533
676
  `automation readiness: ${automationReadiness.grade} (${automationReadiness.score}/100, durable locators ${automationReadiness.locatorCoverage.durablePct}%)`,
534
- `test catalog: ${runTestCatalog.total} case(s) — ${Object.entries(runTestCatalog.counts).filter(([, v]) => v).map(([k, v]) => `${k}=${v}`).join(' ') || 'none'}`,
535
- automationReadiness.topFixes.length ? `top readiness fixes:\n ${automationReadiness.topFixes.join('\n ')}` : 'top readiness fixes: none',
536
- `${report.coverage}${report.visualOnly ? ' · VISUAL-ONLY (structured snapshots were poor — see screenshots)' : ''}`,
677
+ `test catalog: ${runTestCatalog.total} case(s): ${Object.entries(runTestCatalog.counts)
678
+ .filter(([, v]) => v)
679
+ .map(([k, v]) => `${k}=${v}`)
680
+ .join(' ') || 'none'}`,
681
+ automationReadiness.topFixes.length
682
+ ? `top readiness fixes:\n ${automationReadiness.topFixes.join('\n ')}`
683
+ : 'top readiness fixes: none',
684
+ `${report.coverage}${report.visualOnly ? ' · VISUAL-ONLY (structured snapshots were poor, see screenshots)' : ''}`,
537
685
  `emulator display: ${report.emulatorDisplay}`,
538
686
  `network: ${finalNetwork}${networkRestore ? ` (${networkRestore})` : session.network?.changed ? ' ⚠ Swipium changed it and did NOT restore' : ''}`,
539
- report.activeRecording ? `A screen recording is still active (${report.activeRecording.backend}, ~${report.activeRecording.seconds}s). It will be stopped on shutdown.` : '',
687
+ report.activeRecording
688
+ ? `A screen recording is still active (${report.activeRecording.backend}, ~${report.activeRecording.seconds}s). It will be stopped on shutdown.`
689
+ : '',
540
690
  session.envChanges.length ? `environment changes:\n ${session.envChanges.join('\n ')}` : 'environment changes: none',
541
- session.mutations.length ? `mutation ledger: ${session.mutations.length} record(s), ${session.mutations.filter((m) => m.status === 'executed').length} executed, ${session.mutations.filter((m) => m.status === 'refused' || m.status === 'blocked').length} refused/blocked` : 'mutation ledger: none',
542
- report.guardrailOverrides.length ? `⚠ guardrail overrides used:\n ${report.guardrailOverrides.join('\n ')}` : 'guardrail overrides: none',
691
+ session.mutations.length
692
+ ? `mutation ledger: ${session.mutations.length} record(s), ${session.mutations.filter((m) => m.status === 'executed').length} executed, ${session.mutations.filter((m) => m.status === 'refused' || m.status === 'blocked').length} refused/blocked`
693
+ : 'mutation ledger: none',
694
+ report.guardrailOverrides.length
695
+ ? `⚠ guardrail overrides used:\n ${report.guardrailOverrides.join('\n ')}`
696
+ : 'guardrail overrides: none',
543
697
  session.workarounds.length ? `workarounds attempted:\n ${session.workarounds.join('\n ')}` : '',
544
- session.inputs.length ? `inputs provided (redacted): ${session.inputs.map((i) => `${i.varName}${i.secret ? '🔒' : ''}`).join(', ')}` : '',
545
- session.generatedValues.length ? `generated test data: ${session.generatedValues.map((g) => `${g.fixture}.${g.field}=${g.secret ? '<redacted>' : redact(g.value)} (${g.generator})`).join(', ')}` : '',
698
+ session.inputs.length
699
+ ? `inputs provided (redacted): ${session.inputs.map((i) => `${i.varName}${i.secret ? '🔒' : ''}`).join(', ')}`
700
+ : '',
701
+ session.redactionDegraded
702
+ ? '⚠ redaction degraded: this session was restored after a server restart. Secrets registered before the restart are no longer in the redaction set, so artifacts written since may contain those values unredacted'
703
+ : '',
704
+ session.generatedValues.length
705
+ ? `generated test data: ${session.generatedValues.map((g) => `${g.fixture}.${g.field}=${g.secret ? '<redacted>' : redact(g.value)} (${g.generator})`).join(', ')}`
706
+ : '',
546
707
  options.suite
547
708
  ? options.suite.generated
548
- ? `generated suite "${options.suite.name}": ${(options.suite.compiledFlows ?? []).filter((c) => c.ok).length}/${(options.suite.compiledFlows ?? []).length} runnable flow(s), runnable=${options.suite.suiteRunnable}, readiness=${(options.suite.readinessLabels ?? []).join(' → ') || 'unknown'} (${(options.suite.written ?? []).length} files)`
709
+ ? `generated suite "${options.suite.name}": ${(options.suite.compiledFlows ?? []).filter((c) => c.ok).length}/${(options.suite.compiledFlows ?? []).length} runnable flow(s), runnable=${options.suite.suiteRunnable}, readiness=${(options.suite.readinessLabels ?? []).join(' > ') || 'unknown'} (${(options.suite.written ?? []).length} files)`
549
710
  : `suite generation skipped: ${options.suite.skippedReason}`
550
711
  : '',
551
712
  session.exploration
552
713
  ? `exploration: ${session.exploration.summary.screensVisited} screens, ${session.exploration.summary.workflowsFound} transitions, ${session.exploration.summary.visualOnlyScreens} visual-only, ${session.exploration.summary.unsafeActionsSkipped} unsafe skipped, ${session.exploration.summary.appErrors} app errors (graph: ${session.exploration.graphUri})`
553
714
  : '',
554
715
  `destructive/bundle-risk guardrail: ${destructiveGuardrail.status}`,
555
- `health — native: ${nativeHealth === 'OK' ? '✅ OK' : '❌ error'} · app: ${appHealth === 'OK' ? '✅ OK' : appHealth === 'degraded' ? '⚠ degraded' : '❌ error'}`,
556
- wdaReport ? `wda: ${wdaReport.status.reachable ? 'reachable' : 'unreachable'} ${wdaReport.webDriverAgentUrl} device=${wdaReport.device ?? 'unknown'}` : '',
557
- `findings: ${session.findings.length} (high: ${high.length})`,
558
- totalFailures ? `failures by bucket: ${ALL_BUCKETS.filter((b) => bucketCounts[b]).map((b) => `${b}=${bucketCounts[b]}`).join(' ')} (${failureBuckets.classifiedPct}% classified${dominantBucket ? `, mostly ${dominantBucket}` : ''})` : '',
559
- notes.length ? `evidence taxonomy: structured=${evidenceTaxonomy.counts.structured_locator} ocr=${evidenceTaxonomy.counts.ocr_locator} visual=${evidenceTaxonomy.counts.visual_match} ai_visual=${evidenceTaxonomy.counts.ai_visual_evidence} manual=${evidenceTaxonomy.counts.manual_review} calibration=${evidenceTaxonomy.calibration.status}` : '',
560
- ...session.findings.map((f) => ` [${f.severity}] ${f.layer ?? '?'}/${f.kind}: ${redact(f.detail)}${f.evidence ? ` — "${redact(f.evidence)}"` : ''}${f.screenshotUri ? ` (${f.screenshotUri})` : ''}`),
561
- notes.length ? `test outcomes: ${Object.entries(outcomeTally).map(([k, v]) => `${k}=${v}`).join(' ')}` : 'test outcomes: none recorded (qa_note)',
716
+ `health: native: ${nativeHealth === 'OK' ? '✅ OK' : '❌ error'} · app: ${appHealth === 'OK' ? '✅ OK' : appHealth === 'degraded' ? '⚠ degraded' : '❌ error'}`,
717
+ wdaReport
718
+ ? `wda: ${wdaReport.status.reachable ? 'reachable' : 'unreachable'} ${wdaReport.webDriverAgentUrl} device=${wdaReport.device ?? 'unknown'}`
719
+ : '',
720
+ `findings: ${uniqueFindings.length} unique (${session.findings.length} occurrence(s), high: ${high.length})`,
721
+ toolErrors.length
722
+ ? `tool errors: ${toolVerdict.toolErrorCount}: ${Object.entries(toolVerdict.toolErrorsByCode)
723
+ .map(([c, n]) => `${c}×${n}`)
724
+ .join(', ')}`
725
+ : '',
726
+ totalFailures
727
+ ? `failures by bucket: ${ALL_BUCKETS.filter((b) => bucketCounts[b])
728
+ .map((b) => `${b}=${bucketCounts[b]}`)
729
+ .join(' ')} (${failureBuckets.classifiedPct}% classified${dominantBucket ? `, mostly ${dominantBucket}` : ''})`
730
+ : '',
731
+ notes.length
732
+ ? `evidence taxonomy: structured=${evidenceTaxonomy.counts.structured_locator} ocr=${evidenceTaxonomy.counts.ocr_locator} visual=${evidenceTaxonomy.counts.visual_match} ai_visual=${evidenceTaxonomy.counts.ai_visual_evidence} manual=${evidenceTaxonomy.counts.manual_review} calibration=${evidenceTaxonomy.calibration.status}`
733
+ : '',
734
+ ...uniqueFindings.map((f) => ` [${f.severity}] ${f.layer ?? '?'}/${f.kind}: ${redact(f.detail)}${f.evidence ? `: "${redact(f.evidence)}"` : ''}${f.screenshotUri ? ` (${f.screenshotUri})` : ''}${repeatSuffix(f)}`),
735
+ notes.length
736
+ ? `test outcomes: ${Object.entries(outcomeTally)
737
+ .map(([k, v]) => `${k}=${v}`)
738
+ .join(' ')}`
739
+ : 'test outcomes: none recorded (qa_note)',
562
740
  ...noteSummaryLines,
563
741
  persistentSuite
564
- ? `persistent suite: ${persistentSuite.totalCases} case(s) — +${persistentSuite.created.length} created, ~${persistentSuite.updated.length} updated, -${persistentSuite.deprecated.length} deprecated, ${persistentSuite.failed.length} failed, ${persistentSuite.blocked.length} blocked, ${persistentSuite.newlyAutomated.length} newly automated`
742
+ ? `persistent suite: ${persistentSuite.totalCases} case(s): +${persistentSuite.created.length} created, ~${persistentSuite.updated.length} updated, -${persistentSuite.deprecated.length} deprecated, ${persistentSuite.failed.length} failed, ${persistentSuite.blocked.length} blocked, ${persistentSuite.newlyAutomated.length} newly automated`
565
743
  : '',
566
744
  `auth: ${authState}`,
567
745
  session.fixtures.length ? `declared preconditions: ${session.fixtures.map((f) => f.name).join(', ')}` : '',
568
- `timing: total=${phaseTimings.totalSec}s setup=${phaseTimings.setupSec}s active=${phaseTimings.activeSec}s${phaseTimings.timeToLoginSec != null ? ` toLogin=${phaseTimings.timeToLoginSec}s` : ''}${phaseTimings.diagnostics.waitSec != null ? ` wait=${phaseTimings.diagnostics.waitSec}s` : ''}${phaseTimings.diagnostics.flowRuntimeSec != null ? ` flows=${phaseTimings.diagnostics.flowRuntimeSec}s` : ''}${session.budgetProfile ? ` (profile=${session.budgetProfile})` : ''}`,
569
- reportLinks ? `report links: baseline=${baseline ?? 'none'} trend=${trendRoot ?? session.root}${typeof reportLinks.comparison === 'string' ? ` comparison=${reportLinks.comparison}` : ''}` : '',
746
+ `timing: total=${secText(phaseTimings.totalMs)} setup=${secText(phaseTimings.setupMs)} active=${secText(phaseTimings.activeMs)}${phaseTimings.timeToLoginMs != null ? ` toLogin=${secText(phaseTimings.timeToLoginMs)}` : ''}${phaseTimings.diagnostics.waitMs != null ? ` wait=${secText(phaseTimings.diagnostics.waitMs)}` : ''}${phaseTimings.diagnostics.flowRuntimeMs != null ? ` flows=${secText(phaseTimings.diagnostics.flowRuntimeMs)}` : ''}${session.budgetProfile ? ` (profile=${session.budgetProfile})` : ''}`,
747
+ reportLinks
748
+ ? `report links: baseline=${baseline ?? 'none'} trend=${trendRoot ?? session.root}${typeof reportLinks.comparison === 'string' ? ` comparison=${reportLinks.comparison}` : ''}`
749
+ : '',
570
750
  `artifacts: ${session.artifacts.length} (${screenshots.length} screenshots)`,
571
751
  ...session.artifacts.map((art) => ` ${art.uri} (${art.kind}${art.label ? `: ${art.label}` : ''})`),
572
752
  dumpUri ? `current dump: ${dumpUri}` : '',
@@ -574,6 +754,17 @@ export async function generateSessionReport(sessions, session, options = {}) {
574
754
  `manifest: ${manifestUri}`,
575
755
  exportUri && exportUri !== reportUri ? `${format} export: ${exportUri}` : '',
576
756
  ].filter(Boolean);
577
- return { report, reportUri, manifestUri, manifest, dumpUri, reportLinks, exportUri, exportFormat: exportUri ? format : undefined, summaryText: lines.join('\n'), flowExportSkipped };
757
+ return {
758
+ report,
759
+ reportUri,
760
+ manifestUri,
761
+ manifest,
762
+ dumpUri,
763
+ reportLinks,
764
+ exportUri,
765
+ exportFormat: exportUri ? format : undefined,
766
+ summaryText: redact(lines.join('\n')) ?? '',
767
+ flowExportSkipped,
768
+ };
578
769
  }
579
770
  //# sourceMappingURL=report.js.map