@sun-asterisk/sungen 3.2.32 → 3.2.34-beta.1

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 (503) hide show
  1. package/dist/cli/commands/audit.d.ts +5 -0
  2. package/dist/cli/commands/audit.d.ts.map +1 -1
  3. package/dist/cli/commands/audit.js +64 -11
  4. package/dist/cli/commands/audit.js.map +1 -1
  5. package/dist/cli/commands/capability.d.ts.map +1 -1
  6. package/dist/cli/commands/capability.js +40 -18
  7. package/dist/cli/commands/capability.js.map +1 -1
  8. package/dist/cli/commands/changed.d.ts.map +1 -1
  9. package/dist/cli/commands/changed.js +2 -3
  10. package/dist/cli/commands/changed.js.map +1 -1
  11. package/dist/cli/commands/dashboard.d.ts.map +1 -1
  12. package/dist/cli/commands/dashboard.js +2 -28
  13. package/dist/cli/commands/dashboard.js.map +1 -1
  14. package/dist/cli/commands/defect.d.ts +7 -0
  15. package/dist/cli/commands/defect.d.ts.map +1 -0
  16. package/dist/cli/commands/defect.js +68 -0
  17. package/dist/cli/commands/defect.js.map +1 -0
  18. package/dist/cli/commands/delivery.d.ts.map +1 -1
  19. package/dist/cli/commands/delivery.js +113 -59
  20. package/dist/cli/commands/delivery.js.map +1 -1
  21. package/dist/cli/commands/generate.d.ts.map +1 -1
  22. package/dist/cli/commands/generate.js +14 -7
  23. package/dist/cli/commands/generate.js.map +1 -1
  24. package/dist/cli/commands/results-merge.d.ts +9 -0
  25. package/dist/cli/commands/results-merge.d.ts.map +1 -1
  26. package/dist/cli/commands/results-merge.js +9 -12
  27. package/dist/cli/commands/results-merge.js.map +1 -1
  28. package/dist/cli/commands/results-sanitize.d.ts.map +1 -1
  29. package/dist/cli/commands/results-sanitize.js +2 -13
  30. package/dist/cli/commands/results-sanitize.js.map +1 -1
  31. package/dist/cli/commands/select.d.ts +12 -0
  32. package/dist/cli/commands/select.d.ts.map +1 -0
  33. package/dist/cli/commands/select.js +122 -0
  34. package/dist/cli/commands/select.js.map +1 -0
  35. package/dist/cli/commands/triage.d.ts +7 -0
  36. package/dist/cli/commands/triage.d.ts.map +1 -0
  37. package/dist/cli/commands/triage.js +98 -0
  38. package/dist/cli/commands/triage.js.map +1 -0
  39. package/dist/cli/commands/update.d.ts.map +1 -1
  40. package/dist/cli/commands/update.js +3 -0
  41. package/dist/cli/commands/update.js.map +1 -1
  42. package/dist/cli/commands/viewpoint.d.ts +7 -0
  43. package/dist/cli/commands/viewpoint.d.ts.map +1 -0
  44. package/dist/cli/commands/viewpoint.js +70 -0
  45. package/dist/cli/commands/viewpoint.js.map +1 -0
  46. package/dist/cli/index.js +13 -2
  47. package/dist/cli/index.js.map +1 -1
  48. package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
  49. package/dist/dashboard/snapshot-builder.js +6 -10
  50. package/dist/dashboard/snapshot-builder.js.map +1 -1
  51. package/dist/dashboard/templates/index.html +1 -1
  52. package/dist/dashboard/types.d.ts +1 -0
  53. package/dist/dashboard/types.d.ts.map +1 -1
  54. package/dist/exporters/csv-exporter.d.ts +4 -0
  55. package/dist/exporters/csv-exporter.d.ts.map +1 -1
  56. package/dist/exporters/csv-exporter.js +12 -11
  57. package/dist/exporters/csv-exporter.js.map +1 -1
  58. package/dist/exporters/feature-parser.d.ts +29 -0
  59. package/dist/exporters/feature-parser.d.ts.map +1 -1
  60. package/dist/exporters/feature-parser.js +44 -1
  61. package/dist/exporters/feature-parser.js.map +1 -1
  62. package/dist/exporters/json-exporter.d.ts.map +1 -1
  63. package/dist/exporters/json-exporter.js +7 -0
  64. package/dist/exporters/json-exporter.js.map +1 -1
  65. package/dist/exporters/locale-variants.d.ts +7 -1
  66. package/dist/exporters/locale-variants.d.ts.map +1 -1
  67. package/dist/exporters/locale-variants.js +23 -16
  68. package/dist/exporters/locale-variants.js.map +1 -1
  69. package/dist/exporters/manual-hybrid-oracle.d.ts +54 -0
  70. package/dist/exporters/manual-hybrid-oracle.d.ts.map +1 -0
  71. package/dist/exporters/manual-hybrid-oracle.js +114 -0
  72. package/dist/exporters/manual-hybrid-oracle.js.map +1 -0
  73. package/dist/exporters/matrix/build.d.ts.map +1 -1
  74. package/dist/exporters/matrix/build.js +68 -19
  75. package/dist/exporters/matrix/build.js.map +1 -1
  76. package/dist/exporters/matrix/carry-manual-results.d.ts +89 -0
  77. package/dist/exporters/matrix/carry-manual-results.d.ts.map +1 -0
  78. package/dist/exporters/matrix/carry-manual-results.js +302 -0
  79. package/dist/exporters/matrix/carry-manual-results.js.map +1 -0
  80. package/dist/exporters/matrix/export.d.ts +9 -1
  81. package/dist/exporters/matrix/export.d.ts.map +1 -1
  82. package/dist/exporters/matrix/export.js +21 -3
  83. package/dist/exporters/matrix/export.js.map +1 -1
  84. package/dist/exporters/matrix/gates.js +6 -0
  85. package/dist/exporters/matrix/gates.js.map +1 -1
  86. package/dist/exporters/matrix/render-csv.js +1 -1
  87. package/dist/exporters/matrix/render-csv.js.map +1 -1
  88. package/dist/exporters/matrix/render-testcase-sheet.d.ts.map +1 -1
  89. package/dist/exporters/matrix/render-testcase-sheet.js +10 -4
  90. package/dist/exporters/matrix/render-testcase-sheet.js.map +1 -1
  91. package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
  92. package/dist/exporters/matrix/render-xlsx.js +3 -0
  93. package/dist/exporters/matrix/render-xlsx.js.map +1 -1
  94. package/dist/exporters/matrix/types.d.ts +13 -1
  95. package/dist/exporters/matrix/types.d.ts.map +1 -1
  96. package/dist/exporters/matrix/types.js.map +1 -1
  97. package/dist/exporters/matrix/wording.d.ts +8 -1
  98. package/dist/exporters/matrix/wording.d.ts.map +1 -1
  99. package/dist/exporters/matrix/wording.js +37 -2
  100. package/dist/exporters/matrix/wording.js.map +1 -1
  101. package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
  102. package/dist/exporters/playwright-report-parser.js +1 -0
  103. package/dist/exporters/playwright-report-parser.js.map +1 -1
  104. package/dist/exporters/run-environment.d.ts +32 -0
  105. package/dist/exporters/run-environment.d.ts.map +1 -0
  106. package/dist/exporters/run-environment.js +125 -0
  107. package/dist/exporters/run-environment.js.map +1 -0
  108. package/dist/exporters/scenario-merger.d.ts +18 -0
  109. package/dist/exporters/scenario-merger.d.ts.map +1 -1
  110. package/dist/exporters/scenario-merger.js +49 -3
  111. package/dist/exporters/scenario-merger.js.map +1 -1
  112. package/dist/exporters/test-data-resolver.d.ts +0 -4
  113. package/dist/exporters/test-data-resolver.d.ts.map +1 -1
  114. package/dist/exporters/test-data-resolver.js +0 -7
  115. package/dist/exporters/test-data-resolver.js.map +1 -1
  116. package/dist/exporters/types.d.ts +5 -0
  117. package/dist/exporters/types.d.ts.map +1 -1
  118. package/dist/exporters/xlsx-exporter.d.ts +2 -0
  119. package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
  120. package/dist/exporters/xlsx-exporter.js +7 -2
  121. package/dist/exporters/xlsx-exporter.js.map +1 -1
  122. package/dist/generators/gherkin-parser/index.d.ts +11 -4
  123. package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
  124. package/dist/generators/gherkin-parser/index.js +49 -19
  125. package/dist/generators/gherkin-parser/index.js.map +1 -1
  126. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +4 -0
  127. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  128. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/reinstall-app-action.hbs +2 -2
  129. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +8 -2
  130. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-with-role-variable-assertion.hbs +7 -3
  131. package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +68 -4
  132. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/click-select-action.hbs +1 -1
  133. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +1 -1
  134. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/label-value-assertion.hbs +7 -1
  135. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +8 -4
  136. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/pick-option.hbs +13 -0
  137. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +5 -1
  138. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +16 -0
  139. package/dist/generators/test-generator/code-generator.d.ts +2 -1
  140. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  141. package/dist/generators/test-generator/code-generator.js +90 -5
  142. package/dist/generators/test-generator/code-generator.js.map +1 -1
  143. package/dist/generators/test-generator/diagnostics.d.ts +36 -5
  144. package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
  145. package/dist/generators/test-generator/diagnostics.js +35 -4
  146. package/dist/generators/test-generator/diagnostics.js.map +1 -1
  147. package/dist/generators/test-generator/patterns/index.d.ts +12 -2
  148. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
  149. package/dist/generators/test-generator/patterns/index.js +15 -9
  150. package/dist/generators/test-generator/patterns/index.js.map +1 -1
  151. package/dist/generators/test-generator/patterns/types.d.ts +15 -0
  152. package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
  153. package/dist/generators/test-generator/patterns/with-parent-scope.d.ts +5 -0
  154. package/dist/generators/test-generator/patterns/with-parent-scope.d.ts.map +1 -1
  155. package/dist/generators/test-generator/patterns/with-parent-scope.js +6 -0
  156. package/dist/generators/test-generator/patterns/with-parent-scope.js.map +1 -1
  157. package/dist/generators/test-generator/step-mapper.d.ts +46 -0
  158. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  159. package/dist/generators/test-generator/step-mapper.js +84 -9
  160. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  161. package/dist/generators/test-generator/utils/data-resolver.d.ts +25 -0
  162. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  163. package/dist/generators/test-generator/utils/data-resolver.js +39 -8
  164. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  165. package/dist/generators/test-generator/utils/selector-entry-fields.d.ts.map +1 -1
  166. package/dist/generators/test-generator/utils/selector-entry-fields.js +1 -0
  167. package/dist/generators/test-generator/utils/selector-entry-fields.js.map +1 -1
  168. package/dist/generators/test-generator/utils/selector-resolver.d.ts +37 -4
  169. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  170. package/dist/generators/test-generator/utils/selector-resolver.js +212 -79
  171. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  172. package/dist/harness/audit.d.ts +3 -0
  173. package/dist/harness/audit.d.ts.map +1 -1
  174. package/dist/harness/audit.js +60 -19
  175. package/dist/harness/audit.js.map +1 -1
  176. package/dist/harness/capability.d.ts +7 -1
  177. package/dist/harness/capability.d.ts.map +1 -1
  178. package/dist/harness/capability.js +15 -12
  179. package/dist/harness/capability.js.map +1 -1
  180. package/dist/harness/catalog/universal-viewpoints.yaml +5 -5
  181. package/dist/harness/config-file.d.ts +17 -0
  182. package/dist/harness/config-file.d.ts.map +1 -0
  183. package/dist/harness/config-file.js +100 -0
  184. package/dist/harness/config-file.js.map +1 -0
  185. package/dist/harness/counted-assertions.d.ts +2 -0
  186. package/dist/harness/counted-assertions.d.ts.map +1 -0
  187. package/dist/harness/counted-assertions.js +38 -0
  188. package/dist/harness/counted-assertions.js.map +1 -0
  189. package/dist/harness/data-driven-lint.d.ts.map +1 -1
  190. package/dist/harness/data-driven-lint.js +69 -27
  191. package/dist/harness/data-driven-lint.js.map +1 -1
  192. package/dist/harness/defect-draft.d.ts +14 -0
  193. package/dist/harness/defect-draft.d.ts.map +1 -0
  194. package/dist/harness/defect-draft.js +143 -0
  195. package/dist/harness/defect-draft.js.map +1 -0
  196. package/dist/harness/defects.d.ts +24 -0
  197. package/dist/harness/defects.d.ts.map +1 -0
  198. package/dist/harness/defects.js +118 -0
  199. package/dist/harness/defects.js.map +1 -0
  200. package/dist/harness/depth-lint.d.ts.map +1 -1
  201. package/dist/harness/depth-lint.js +2 -1
  202. package/dist/harness/depth-lint.js.map +1 -1
  203. package/dist/harness/flow-check.d.ts.map +1 -1
  204. package/dist/harness/flow-check.js +6 -2
  205. package/dist/harness/flow-check.js.map +1 -1
  206. package/dist/harness/keyword-match.d.ts +59 -0
  207. package/dist/harness/keyword-match.d.ts.map +1 -0
  208. package/dist/harness/keyword-match.js +86 -0
  209. package/dist/harness/keyword-match.js.map +1 -0
  210. package/dist/harness/manifest.d.ts.map +1 -1
  211. package/dist/harness/manifest.js +2 -2
  212. package/dist/harness/manifest.js.map +1 -1
  213. package/dist/harness/next-step.d.ts +2 -4
  214. package/dist/harness/next-step.d.ts.map +1 -1
  215. package/dist/harness/next-step.js +50 -5
  216. package/dist/harness/next-step.js.map +1 -1
  217. package/dist/harness/parse.d.ts +45 -0
  218. package/dist/harness/parse.d.ts.map +1 -1
  219. package/dist/harness/parse.js +142 -24
  220. package/dist/harness/parse.js.map +1 -1
  221. package/dist/harness/perf.d.ts +7 -1
  222. package/dist/harness/perf.d.ts.map +1 -1
  223. package/dist/harness/perf.js +13 -10
  224. package/dist/harness/perf.js.map +1 -1
  225. package/dist/harness/quality-gates.d.ts +9 -2
  226. package/dist/harness/quality-gates.d.ts.map +1 -1
  227. package/dist/harness/quality-gates.js +22 -8
  228. package/dist/harness/quality-gates.js.map +1 -1
  229. package/dist/harness/query-catalog.js +0 -0
  230. package/dist/harness/query-catalog.js.map +1 -1
  231. package/dist/harness/read-text.d.ts +21 -0
  232. package/dist/harness/read-text.d.ts.map +1 -1
  233. package/dist/harness/read-text.js +32 -1
  234. package/dist/harness/read-text.js.map +1 -1
  235. package/dist/harness/script-check.d.ts.map +1 -1
  236. package/dist/harness/script-check.js +7 -1
  237. package/dist/harness/script-check.js.map +1 -1
  238. package/dist/harness/secret-scan.d.ts +27 -1
  239. package/dist/harness/secret-scan.d.ts.map +1 -1
  240. package/dist/harness/secret-scan.js +57 -4
  241. package/dist/harness/secret-scan.js.map +1 -1
  242. package/dist/harness/select-by-ids.d.ts +31 -0
  243. package/dist/harness/select-by-ids.d.ts.map +1 -0
  244. package/dist/harness/select-by-ids.js +106 -0
  245. package/dist/harness/select-by-ids.js.map +1 -0
  246. package/dist/harness/sensors.d.ts +20 -1
  247. package/dist/harness/sensors.d.ts.map +1 -1
  248. package/dist/harness/sensors.js +133 -39
  249. package/dist/harness/sensors.js.map +1 -1
  250. package/dist/harness/spec-branches.d.ts +0 -18
  251. package/dist/harness/spec-branches.d.ts.map +1 -1
  252. package/dist/harness/spec-branches.js +24 -3
  253. package/dist/harness/spec-branches.js.map +1 -1
  254. package/dist/harness/spec-coverage.d.ts.map +1 -1
  255. package/dist/harness/spec-coverage.js +14 -4
  256. package/dist/harness/spec-coverage.js.map +1 -1
  257. package/dist/harness/triage-signals.d.ts +20 -0
  258. package/dist/harness/triage-signals.d.ts.map +1 -0
  259. package/dist/harness/triage-signals.js +30 -0
  260. package/dist/harness/triage-signals.js.map +1 -0
  261. package/dist/harness/triage-unit.d.ts +23 -0
  262. package/dist/harness/triage-unit.d.ts.map +1 -0
  263. package/dist/harness/triage-unit.js +142 -0
  264. package/dist/harness/triage-unit.js.map +1 -0
  265. package/dist/harness/triage.d.ts +28 -0
  266. package/dist/harness/triage.d.ts.map +1 -0
  267. package/dist/harness/triage.js +99 -0
  268. package/dist/harness/triage.js.map +1 -0
  269. package/dist/harness/ui-inventory.d.ts +40 -0
  270. package/dist/harness/ui-inventory.d.ts.map +1 -0
  271. package/dist/harness/ui-inventory.js +191 -0
  272. package/dist/harness/ui-inventory.js.map +1 -0
  273. package/dist/harness/viewpoint-baseline.d.ts.map +1 -1
  274. package/dist/harness/viewpoint-baseline.js +7 -1
  275. package/dist/harness/viewpoint-baseline.js.map +1 -1
  276. package/dist/harness/viewpoint-draft-header.d.ts +7 -0
  277. package/dist/harness/viewpoint-draft-header.d.ts.map +1 -0
  278. package/dist/harness/viewpoint-draft-header.js +100 -0
  279. package/dist/harness/viewpoint-draft-header.js.map +1 -0
  280. package/dist/harness/viewpoint-ledger.d.ts +13 -3
  281. package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
  282. package/dist/harness/viewpoint-ledger.js +106 -19
  283. package/dist/harness/viewpoint-ledger.js.map +1 -1
  284. package/dist/harness/viewpoint-mode.d.ts +9 -0
  285. package/dist/harness/viewpoint-mode.d.ts.map +1 -0
  286. package/dist/harness/viewpoint-mode.js +52 -0
  287. package/dist/harness/viewpoint-mode.js.map +1 -0
  288. package/dist/harness/viewpoint-priority.d.ts +19 -0
  289. package/dist/harness/viewpoint-priority.d.ts.map +1 -0
  290. package/dist/harness/viewpoint-priority.js +36 -0
  291. package/dist/harness/viewpoint-priority.js.map +1 -0
  292. package/dist/harness/viewpoint-review-open.d.ts +6 -0
  293. package/dist/harness/viewpoint-review-open.d.ts.map +1 -0
  294. package/dist/harness/viewpoint-review-open.js +90 -0
  295. package/dist/harness/viewpoint-review-open.js.map +1 -0
  296. package/dist/harness/viewpoint-scan-refs.d.ts +50 -0
  297. package/dist/harness/viewpoint-scan-refs.d.ts.map +1 -0
  298. package/dist/harness/viewpoint-scan-refs.js +200 -0
  299. package/dist/harness/viewpoint-scan-refs.js.map +1 -0
  300. package/dist/harness/viewpoint-scan.d.ts +41 -0
  301. package/dist/harness/viewpoint-scan.d.ts.map +1 -0
  302. package/dist/harness/viewpoint-scan.js +118 -0
  303. package/dist/harness/viewpoint-scan.js.map +1 -0
  304. package/dist/harness/write-atomic.d.ts +2 -0
  305. package/dist/harness/write-atomic.d.ts.map +1 -0
  306. package/dist/harness/write-atomic.js +62 -0
  307. package/dist/harness/write-atomic.js.map +1 -0
  308. package/dist/orchestrator/ai-skills/writer.d.ts.map +1 -1
  309. package/dist/orchestrator/ai-skills/writer.js +15 -7
  310. package/dist/orchestrator/ai-skills/writer.js.map +1 -1
  311. package/dist/orchestrator/assets-drift.d.ts.map +1 -1
  312. package/dist/orchestrator/assets-drift.js +11 -6
  313. package/dist/orchestrator/assets-drift.js.map +1 -1
  314. package/dist/orchestrator/flow-manager.d.ts +0 -1
  315. package/dist/orchestrator/flow-manager.d.ts.map +1 -1
  316. package/dist/orchestrator/flow-manager.js +3 -52
  317. package/dist/orchestrator/flow-manager.js.map +1 -1
  318. package/dist/orchestrator/project-deps-sync.d.ts.map +1 -1
  319. package/dist/orchestrator/project-deps-sync.js +22 -1
  320. package/dist/orchestrator/project-deps-sync.js.map +1 -1
  321. package/dist/orchestrator/project-initializer.d.ts +8 -0
  322. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  323. package/dist/orchestrator/project-initializer.js +49 -7
  324. package/dist/orchestrator/project-initializer.js.map +1 -1
  325. package/dist/orchestrator/screen-manager.d.ts.map +1 -1
  326. package/dist/orchestrator/screen-manager.js +8 -62
  327. package/dist/orchestrator/screen-manager.js.map +1 -1
  328. package/dist/orchestrator/security-overrides-catalog.d.ts +39 -0
  329. package/dist/orchestrator/security-overrides-catalog.d.ts.map +1 -0
  330. package/dist/orchestrator/security-overrides-catalog.js +63 -0
  331. package/dist/orchestrator/security-overrides-catalog.js.map +1 -0
  332. package/dist/orchestrator/security-overrides.d.ts +51 -0
  333. package/dist/orchestrator/security-overrides.d.ts.map +1 -0
  334. package/dist/orchestrator/security-overrides.js +165 -0
  335. package/dist/orchestrator/security-overrides.js.map +1 -0
  336. package/dist/orchestrator/templates/ai-src/agents/discovery.md +11 -3
  337. package/dist/orchestrator/templates/ai-src/agents/reviewer.md +8 -0
  338. package/dist/orchestrator/templates/ai-src/commands/create-test.md +32 -24
  339. package/dist/orchestrator/templates/ai-src/commands/review.md +2 -2
  340. package/dist/orchestrator/templates/ai-src/commands/run-test.md +78 -15
  341. package/dist/orchestrator/templates/ai-src/skills/sungen-capture/mode-local.md +2 -2
  342. package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -0
  343. package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +25 -1
  344. package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +26 -8
  345. package/dist/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +5 -1
  346. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +58 -14
  347. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +13 -1
  348. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +42 -2
  349. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +7 -0
  350. package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +18 -0
  351. package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/review-checklist.md +63 -0
  352. package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/viewpoint-template.md +158 -0
  353. package/dist/orchestrator/templates/playwright.config.ts +4 -2
  354. package/dist/orchestrator/templates/specs-locale-fixture.ts +21 -9
  355. package/dist/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
  356. package/dist/orchestrator/templates/specs-url-assert.d.ts +17 -1
  357. package/dist/orchestrator/templates/specs-url-assert.d.ts.map +1 -1
  358. package/dist/orchestrator/templates/specs-url-assert.js +38 -4
  359. package/dist/orchestrator/templates/specs-url-assert.js.map +1 -1
  360. package/dist/orchestrator/templates/specs-url-assert.ts +47 -4
  361. package/dist/orchestrator/templates/wdio.conf.ts +24 -1
  362. package/dist/orchestrator/trace-mode-install.d.ts +14 -0
  363. package/dist/orchestrator/trace-mode-install.d.ts.map +1 -0
  364. package/dist/orchestrator/trace-mode-install.js +132 -0
  365. package/dist/orchestrator/trace-mode-install.js.map +1 -0
  366. package/dist/orchestrator/viewpoint-pattern-catalog.d.ts +23 -0
  367. package/dist/orchestrator/viewpoint-pattern-catalog.d.ts.map +1 -0
  368. package/dist/orchestrator/viewpoint-pattern-catalog.js +48 -0
  369. package/dist/orchestrator/viewpoint-pattern-catalog.js.map +1 -0
  370. package/dist/orchestrator/viewpoint-scaffold.d.ts +3 -0
  371. package/dist/orchestrator/viewpoint-scaffold.d.ts.map +1 -0
  372. package/dist/orchestrator/viewpoint-scaffold.js +70 -0
  373. package/dist/orchestrator/viewpoint-scaffold.js.map +1 -0
  374. package/package.json +3 -3
  375. package/src/cli/commands/audit.ts +59 -12
  376. package/src/cli/commands/capability.ts +54 -19
  377. package/src/cli/commands/changed.ts +2 -4
  378. package/src/cli/commands/dashboard.ts +1 -26
  379. package/src/cli/commands/defect.ts +49 -0
  380. package/src/cli/commands/delivery.ts +106 -56
  381. package/src/cli/commands/generate.ts +15 -7
  382. package/src/cli/commands/results-merge.ts +9 -9
  383. package/src/cli/commands/results-sanitize.ts +2 -9
  384. package/src/cli/commands/select.ts +88 -0
  385. package/src/cli/commands/triage.ts +57 -0
  386. package/src/cli/commands/update.ts +3 -0
  387. package/src/cli/commands/viewpoint.ts +58 -0
  388. package/src/cli/index.ts +13 -2
  389. package/src/dashboard/snapshot-builder.ts +6 -8
  390. package/src/dashboard/templates/index.html +1 -1
  391. package/src/dashboard/types.ts +1 -0
  392. package/src/exporters/csv-exporter.ts +12 -11
  393. package/src/exporters/feature-parser.ts +44 -2
  394. package/src/exporters/json-exporter.ts +6 -1
  395. package/src/exporters/locale-variants.ts +23 -17
  396. package/src/exporters/manual-hybrid-oracle.ts +112 -0
  397. package/src/exporters/matrix/build.ts +71 -19
  398. package/src/exporters/matrix/carry-manual-results.ts +292 -0
  399. package/src/exporters/matrix/export.ts +24 -4
  400. package/src/exporters/matrix/gates.ts +6 -0
  401. package/src/exporters/matrix/render-csv.ts +1 -1
  402. package/src/exporters/matrix/render-testcase-sheet.ts +10 -4
  403. package/src/exporters/matrix/render-xlsx.ts +3 -0
  404. package/src/exporters/matrix/types.ts +13 -1
  405. package/src/exporters/matrix/wording.ts +36 -2
  406. package/src/exporters/playwright-report-parser.ts +1 -0
  407. package/src/exporters/run-environment.ts +89 -0
  408. package/src/exporters/scenario-merger.ts +51 -5
  409. package/src/exporters/test-data-resolver.ts +0 -6
  410. package/src/exporters/types.ts +5 -0
  411. package/src/exporters/xlsx-exporter.ts +7 -2
  412. package/src/generators/gherkin-parser/index.ts +50 -18
  413. package/src/generators/test-generator/adapters/adapter-interface.ts +6 -2
  414. package/src/generators/test-generator/adapters/appium/templates/steps/actions/reinstall-app-action.hbs +2 -2
  415. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +8 -2
  416. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-with-role-variable-assertion.hbs +7 -3
  417. package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +68 -4
  418. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/click-select-action.hbs +1 -1
  419. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +1 -1
  420. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/label-value-assertion.hbs +7 -1
  421. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +8 -4
  422. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/pick-option.hbs +13 -0
  423. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +5 -1
  424. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +16 -0
  425. package/src/generators/test-generator/code-generator.ts +77 -5
  426. package/src/generators/test-generator/diagnostics.ts +36 -5
  427. package/src/generators/test-generator/patterns/index.ts +15 -8
  428. package/src/generators/test-generator/patterns/types.ts +15 -0
  429. package/src/generators/test-generator/patterns/with-parent-scope.ts +6 -0
  430. package/src/generators/test-generator/step-mapper.ts +90 -8
  431. package/src/generators/test-generator/utils/data-resolver.ts +41 -12
  432. package/src/generators/test-generator/utils/selector-entry-fields.ts +1 -0
  433. package/src/generators/test-generator/utils/selector-resolver.ts +238 -82
  434. package/src/harness/audit.ts +69 -24
  435. package/src/harness/capability.ts +23 -12
  436. package/src/harness/catalog/universal-viewpoints.yaml +5 -5
  437. package/src/harness/config-file.ts +66 -0
  438. package/src/harness/counted-assertions.ts +33 -0
  439. package/src/harness/data-driven-lint.ts +82 -24
  440. package/src/harness/defect-draft.ts +115 -0
  441. package/src/harness/defects.ts +94 -0
  442. package/src/harness/depth-lint.ts +2 -1
  443. package/src/harness/flow-check.ts +6 -2
  444. package/src/harness/keyword-match.ts +85 -0
  445. package/src/harness/manifest.ts +2 -2
  446. package/src/harness/next-step.ts +47 -5
  447. package/src/harness/parse.ts +151 -27
  448. package/src/harness/perf.ts +15 -7
  449. package/src/harness/quality-gates.ts +22 -8
  450. package/src/harness/query-catalog.ts +0 -0
  451. package/src/harness/read-text.ts +32 -1
  452. package/src/harness/script-check.ts +7 -1
  453. package/src/harness/secret-scan.ts +60 -4
  454. package/src/harness/select-by-ids.ts +83 -0
  455. package/src/harness/sensors.ts +141 -40
  456. package/src/harness/spec-branches.ts +6 -3
  457. package/src/harness/spec-coverage.ts +14 -4
  458. package/src/harness/triage-signals.ts +46 -0
  459. package/src/harness/triage-unit.ts +113 -0
  460. package/src/harness/triage.ts +127 -0
  461. package/src/harness/ui-inventory.ts +164 -0
  462. package/src/harness/viewpoint-baseline.ts +7 -1
  463. package/src/harness/viewpoint-draft-header.ts +55 -0
  464. package/src/harness/viewpoint-ledger.ts +113 -25
  465. package/src/harness/viewpoint-mode.ts +49 -0
  466. package/src/harness/viewpoint-priority.ts +38 -0
  467. package/src/harness/viewpoint-review-open.ts +51 -0
  468. package/src/harness/viewpoint-scan-refs.ts +154 -0
  469. package/src/harness/viewpoint-scan.ts +111 -0
  470. package/src/harness/write-atomic.ts +22 -0
  471. package/src/orchestrator/ai-skills/writer.ts +18 -3
  472. package/src/orchestrator/assets-drift.ts +10 -5
  473. package/src/orchestrator/flow-manager.ts +3 -53
  474. package/src/orchestrator/project-deps-sync.ts +23 -1
  475. package/src/orchestrator/project-initializer.ts +54 -6
  476. package/src/orchestrator/screen-manager.ts +7 -61
  477. package/src/orchestrator/security-overrides-catalog.ts +69 -0
  478. package/src/orchestrator/security-overrides.ts +149 -0
  479. package/src/orchestrator/templates/ai-src/agents/discovery.md +11 -3
  480. package/src/orchestrator/templates/ai-src/agents/reviewer.md +8 -0
  481. package/src/orchestrator/templates/ai-src/commands/create-test.md +32 -24
  482. package/src/orchestrator/templates/ai-src/commands/review.md +2 -2
  483. package/src/orchestrator/templates/ai-src/commands/run-test.md +78 -15
  484. package/src/orchestrator/templates/ai-src/skills/sungen-capture/mode-local.md +2 -2
  485. package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -0
  486. package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +25 -1
  487. package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +26 -8
  488. package/src/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +5 -1
  489. package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +58 -14
  490. package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +13 -1
  491. package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +42 -2
  492. package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +7 -0
  493. package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +18 -0
  494. package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/review-checklist.md +63 -0
  495. package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/viewpoint-template.md +158 -0
  496. package/src/orchestrator/templates/playwright.config.ts +4 -2
  497. package/src/orchestrator/templates/specs-locale-fixture.ts +21 -9
  498. package/src/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
  499. package/src/orchestrator/templates/specs-url-assert.ts +47 -4
  500. package/src/orchestrator/templates/wdio.conf.ts +24 -1
  501. package/src/orchestrator/trace-mode-install.ts +94 -0
  502. package/src/orchestrator/viewpoint-pattern-catalog.ts +61 -0
  503. package/src/orchestrator/viewpoint-scaffold.ts +36 -0
@@ -6,9 +6,8 @@
6
6
  import { Command } from 'commander';
7
7
  import * as path from 'path';
8
8
  import * as fs from 'fs';
9
- import { execSync } from 'child_process';
10
9
  import { parse as parseYaml } from 'yaml';
11
- import { parseFeatureMetadata } from '../../exporters/feature-parser';
10
+ import { isHookScenario, parseFeatureMetadata } from '../../exporters/feature-parser';
12
11
  import { parseSpecFile } from '../../exporters/spec-parser';
13
12
  import { loadTestData } from '../../exporters/test-data-resolver';
14
13
  import { loadSelectorKeyMap } from '../../exporters/selector-key-resolver';
@@ -21,12 +20,12 @@ import {
21
20
  renderCsv,
22
21
  writeCsv,
23
22
  } from '../../exporters/csv-exporter';
24
- import { renderXlsxMultiSheet, writeXlsx, buildApiDetailRows, addApiDetailSheet } from '../../exporters/xlsx-exporter';
23
+ import { renderXlsxMultiSheet, writeXlsx, buildApiDetailRows, addApiDetailSheet, deliverableXlsxPath } from '../../exporters/xlsx-exporter';
25
24
  import { loadApiCatalog } from '../../exporters/api-catalog-loader';
26
25
  import { resolveApiEnvironment } from '../../exporters/api-environment';
27
- import { resolveWebBaseUrl } from '../../exporters/env-qa';
26
+ import { getEnvironment, platformOfResultFile, RunEnvironment } from '../../exporters/run-environment';
28
27
  import { resolvePlatformAppId } from '../../exporters/mobile-app-id';
29
- import { scanTestDataSecrets } from '../../harness/secret-scan';
28
+ import { scanTestDataSecrets, formatSecretScan, type SecretScanResult } from '../../harness/secret-scan';
30
29
  import { EnvironmentInfo, PreflightCheck, ScreenSummary, TestCaseRow, ApiCatalogEntry } from '../../exporters/types';
31
30
  import { mapFilePath } from '../../exporters/matrix/map-loader';
32
31
  import {
@@ -39,7 +38,9 @@ import {
39
38
  hasReview,
40
39
  } from '../../exporters/matrix/export';
41
40
  import { MatrixFinding } from '../../exporters/matrix/types';
41
+ import { applyManualCarry, ManualCarryReport, PriorManualResults, readPriorManualResults } from '../../exporters/matrix/carry-manual-results';
42
42
  import { formatTestOutcome, resultTotals } from '../../exporters/matrix/result-totals';
43
+ import { readCapabilities } from '../../harness/capability';
43
44
 
44
45
  const COLOR = {
45
46
  reset: '\x1b[0m',
@@ -84,6 +85,12 @@ export interface DeliveryTarget {
84
85
  isFlow: boolean;
85
86
  }
86
87
 
88
+ /** The command that regenerates a target's unit — flag by kind, the unit (not feature) name. */
89
+ function generateCommandFor(target: DeliveryTarget): string {
90
+ const flag = target.kind === 'flow' ? '--flow' : target.kind === 'api' ? '--api' : '--screen';
91
+ return `sungen generate ${flag} ${target.screen}`;
92
+ }
93
+
87
94
  function makeTarget(screen: string, featureBaseName: string, kind: UnitKind): DeliveryTarget {
88
95
  return { screen, featureBaseName, name: featureBaseName, kind, isFlow: kind === 'flow' };
89
96
  }
@@ -340,34 +347,27 @@ export function hasBlockingMissing(p: PreflightCheck): boolean {
340
347
  // Environment
341
348
  // ----------------------------------------------------------------------------
342
349
 
343
- function getEnvironment(cwd: string): EnvironmentInfo {
344
- let baseURL = '';
345
- let projectName = 'chromium';
346
- const configPath = path.join(cwd, 'playwright.config.ts');
347
- if (fs.existsSync(configPath)) {
348
- const c = fs.readFileSync(configPath, 'utf-8');
349
- // Both config shapes: the legacy literal `baseURL: 'x'` and the env-aware
350
- // `baseURL: resolveBaseUrl('x')` (the quoted value is the static fallback).
351
- const baseMatch = c.match(/baseURL:\s*(?:resolveBaseUrl\(\s*)?['"]([^'"]+)['"]/);
352
- if (baseMatch) baseURL = baseMatch[1];
353
- const projMatch = c.match(/name:\s*['"]([^'"]+)['"]/);
354
- if (projMatch) projectName = projMatch[1];
355
- }
356
- // SUNGEN_BASE_URL (shell > .env.qa.<SUNGEN_ENV> > .env.qa) overrides the static
357
- // value — the Test Environment column must show the host the run actually hit.
358
- baseURL = resolveWebBaseUrl(cwd, baseURL);
359
-
360
- let executor = process.env.CI_USER || process.env.USER || '';
361
- try {
362
- const gitUser = execSync('git config user.name', { cwd, encoding: 'utf-8' }).trim();
363
- if (gitUser) executor = gitUser;
364
- } catch {
365
- // ignore
366
- }
350
+ /** The unit's path for the Environment column — a flow records it in its page selector. */
351
+ function targetFeaturePath(target: DeliveryTarget, declared: string | undefined, selectorsFile: string): string | undefined {
352
+ return declared || (target.isFlow ? readFlowPagePath(selectorsFile) : undefined);
353
+ }
367
354
 
368
- return { baseURL, projectName, executor };
355
+ /**
356
+ * Executor + environment for the matrix's run-result rows (#850) — resolved exactly as the
357
+ * legacy workbook resolves them: the API datasource host for an api unit, and on mobile the
358
+ * app id of the OS the result file came from (`<f>-test-result.<os>[.<env>].json`).
359
+ */
360
+ function matrixRunEnv(cwd: string, target: DeliveryTarget, env: EnvironmentInfo, paths: MatrixTargetPaths): RunEnvironment {
361
+ const base = qaDir(cwd, target);
362
+ let declared: string | undefined;
363
+ try { declared = parseFeatureMetadata(paths.featureFile).featurePath; } catch { /* loadMatrixModel reports it */ }
364
+ const featurePath = targetFeaturePath(target, declared, path.join(base, 'selectors', `${target.featureBaseName}.yaml`));
365
+ const apiEnv = target.kind === 'api' ? resolveApiEnvironment(cwd, loadApiCatalog(cwd, target.kind, target.screen)) : null;
366
+ const os = platformOfResultFile(paths.resultsPath) ?? process.env.MOBILE_PLATFORM ?? process.env.SUNGEN_ENV;
367
+ return { ...env, ...(apiEnv ?? {}), featurePath: resolvePlatformAppId(featurePath, os) };
369
368
  }
370
369
 
370
+
371
371
  /**
372
372
  * True when this is a mobile project (`platform: mobile` in qa/capabilities.yaml).
373
373
  * Mobile results are written PER-OS (`<feature>-test-result.android|ios.json`) —
@@ -375,14 +375,11 @@ function getEnvironment(cwd: string): EnvironmentInfo {
375
375
  * emits one csv+xlsx per OS and skips the (empty/misleading) base aggregate.
376
376
  */
377
377
  function isMobileProject(cwd: string): boolean {
378
- try {
379
- const p = path.join(cwd, 'qa', 'capabilities.yaml');
380
- if (!fs.existsSync(p)) return false;
381
- const parsed = parseYaml(fs.readFileSync(p, 'utf-8')) as { platform?: string } | null;
382
- return parsed?.platform === 'mobile';
383
- } catch {
384
- return false;
385
- }
378
+ // Reads through `readCapabilities` rather than re-parsing the file: the third copy of this parse
379
+ // was the third place a malformed capabilities.yaml quietly read as "web". The shared reader
380
+ // reports the breakage once per process; here it still degrades to web, which is the safe
381
+ // direction (per-OS delivery is skipped, nothing is deleted).
382
+ return readCapabilities(cwd).platform === 'mobile';
386
383
  }
387
384
 
388
385
  /**
@@ -406,6 +403,18 @@ function removeBaseDeliverables(cwd: string, featureBaseName: string): void {
406
403
  // Per-screen export
407
404
  // ----------------------------------------------------------------------------
408
405
 
406
+ /**
407
+ * #810 — a manual oracle that lives only in unlabelled `Tester verifies:` prose is exported
408
+ * verbatim into Expected (never dropped), and the author is told to structure it.
409
+ */
410
+ function warnUnlabeledManualOracles(label: string, merged: ReturnType<typeof mergeFeatureAndSpec>): void {
411
+ const hits = merged.filter((m) => m.unlabeledManualOracle);
412
+ if (!hits.length) return;
413
+ log(`${COLOR.yellow}⚠ ${label}: manual oracle found only in an unlabeled comment (${hits.length})${COLOR.reset} — exported verbatim into Expected; write it as numbered "N. Observable: …" lines:`);
414
+ for (const m of hits.slice(0, 10)) log(` ${m.feature.name.slice(0, 90)}`);
415
+ if (hits.length > 10) log(` … and ${hits.length - 10} more`);
416
+ }
417
+
409
418
  async function exportTarget(
410
419
  cwd: string,
411
420
  target: DeliveryTarget,
@@ -424,14 +433,12 @@ async function exportTarget(
424
433
  const spec = parseSpecFile(specFile);
425
434
 
426
435
  const merged = mergeFeatureAndSpec(feature, spec);
436
+ warnUnlabeledManualOracles(label, merged);
427
437
  // Screens get their URL path from the .feature file directly. Flows record
428
438
  // it in selectors/<feature>.yaml under the page-selector block — fall back
429
439
  // to that so the Environment column shows a full URL for flows too.
430
- let featurePath = feature.featurePath;
431
440
  const selectorsFile = path.join(base, 'selectors', `${target.featureBaseName}.yaml`);
432
- if (!featurePath && target.isFlow) {
433
- featurePath = readFlowPagePath(selectorsFile);
434
- }
441
+ const featurePath = targetFeaturePath(target, feature.featurePath, selectorsFile);
435
442
  const selectorKeyMap = loadSelectorKeyMap(selectorsFile);
436
443
 
437
444
  const specLink = fs.existsSync(specMdFile) ? path.relative(cwd, specMdFile) : '';
@@ -463,6 +470,7 @@ async function exportTarget(
463
470
  if (isMobileProject(cwd)) removeBaseDeliverables(cwd, target.featureBaseName);
464
471
  const rows = buildTestCaseRows({
465
472
  screen: label,
473
+ generateCommand: generateCommandFor(target),
466
474
  featureName: feature.featureName,
467
475
  // OS-precise env column: a mobile dual-id Path resolves to just the
468
476
  // android package / ios bundle id for SUNGEN_ENV=android|ios.
@@ -521,6 +529,7 @@ async function exportTarget(
521
529
  for (const variant of osVariants) {
522
530
  const variantRows = buildTestCaseRows({
523
531
  screen: label,
532
+ generateCommand: generateCommandFor(target),
524
533
  featureName: feature.featureName,
525
534
  featurePath: resolvePlatformAppId(featurePath, variant.locale), // OS-precise app id
526
535
  merged,
@@ -573,6 +582,7 @@ async function exportTarget(
573
582
  const variantResults = variant.resultsPath ? loadPlaywrightReport(variant.resultsPath) : null;
574
583
  const variantRows = buildTestCaseRows({
575
584
  screen: label,
585
+ generateCommand: generateCommandFor(target),
576
586
  featureName: feature.featureName,
577
587
  // No-op for web (URL Path); for a mobile fall-through (a no-suffix result
578
588
  // json) this collapses the dual-id to the android package so the combined
@@ -674,17 +684,21 @@ async function exportMatrixTarget(
674
684
  cwd: string,
675
685
  target: DeliveryTarget,
676
686
  opts: { check?: boolean; preview?: boolean; format?: MatrixFormat },
687
+ env: EnvironmentInfo,
677
688
  ): Promise<MatrixRunSummary | 'skipped' | null> {
678
689
  const paths = matrixPathsFor(cwd, target);
690
+ paths.runEnv = matrixRunEnv(cwd, target, env, paths);
679
691
 
680
692
  // A feature whose scenarios are ALL @exploration probes has nothing to deliver —
681
693
  // skip it quietly (no map required) instead of demanding a reviewed grouping for
682
694
  // probes that are, by contract, outside the official suite.
683
695
  {
684
696
  const featureMeta = parseFeatureMetadata(paths.featureFile);
685
- const deliverable = featureMeta.scenarios.filter((s) => !s.tags.some((t) => /^@exploration\b/i.test(t)));
686
- if (featureMeta.scenarios.length > 0 && deliverable.length === 0) {
687
- log(`${COLOR.gray}${paths.label}: nothing to deliver — all ${featureMeta.scenarios.length} scenario(s) are @exploration probes (outside the official suite).${COLOR.reset}`);
697
+ // Hooks are setup/teardown, never test cases — they neither need nor make a deliverable (#880).
698
+ const scenarios = featureMeta.scenarios.filter((s) => !isHookScenario(s));
699
+ const deliverable = scenarios.filter((s) => !s.tags.some((t) => /^@exploration\b/i.test(t)));
700
+ if (scenarios.length > 0 && deliverable.length === 0) {
701
+ log(`${COLOR.gray}${paths.label}: nothing to deliver — all ${scenarios.length} scenario(s) are @exploration probes (outside the official suite).${COLOR.reset}`);
688
702
  return 'skipped';
689
703
  }
690
704
  }
@@ -704,6 +718,17 @@ async function exportMatrixTarget(
704
718
  return null;
705
719
  }
706
720
 
721
+ // The tester's recorded Manual results are part of the outcome: read them BEFORE anything is
722
+ // counted, so this line, the workbook's totals and the summary table all agree (#891). A file
723
+ // that cannot be read is reported where it matters — when it would be overwritten.
724
+ const format = opts.format ?? 'xlsx';
725
+ let prior: PriorManualResults | null = null;
726
+ let priorError: Error | null = null;
727
+ try { prior = await readPriorManualResults(deliverableXlsxPath(cwd, paths.unit)); }
728
+ // A CSV-only export overwrites no workbook, so an unreadable one only means "nothing to carry".
729
+ catch (e) { if (format !== 'csv') priorError = e as Error; }
730
+ applyManualCarry(model, prior);
731
+
707
732
  log(`${COLOR.bold}${paths.label}${COLOR.reset} — ${model.manifest.itemCount} delivery item(s), ${model.manifest.variantCount} coverage variant(s), review: ${model.manifest.approvalState} (design), test outcome: ${formatTestOutcome(resultTotals(model.items))}`);
708
733
  {
709
734
  const mocked = model.items.flatMap((it) => it.variants.filter((v) => v.mocked));
@@ -736,11 +761,21 @@ async function exportMatrixTarget(
736
761
  return null;
737
762
  }
738
763
 
739
- const { csvPath, xlsxPath } = await writeMatrixDeliverables(paths, model, opts.format ?? 'xlsx');
764
+ let deliverables: Awaited<ReturnType<typeof writeMatrixDeliverables>>;
765
+ try {
766
+ if (priorError) throw priorError;
767
+ deliverables = await writeMatrixDeliverables(paths, model, format, prior);
768
+ } catch (e) {
769
+ // The existing workbook could not be read, so writing would destroy its Manual results.
770
+ log(` ${COLOR.red}not written:${COLOR.reset} ${(e as Error).message}`);
771
+ return null;
772
+ }
773
+ const { csvPath, xlsxPath, manualCarry } = deliverables;
740
774
  const passed = model.items.reduce((a, i) => a + i.resultCounts.passed, 0);
741
775
  const failed = model.items.reduce((a, i) => a + i.resultCounts.failed, 0);
742
776
  const written = [xlsxPath, csvPath].filter((p): p is string => !!p).map((p) => path.relative(cwd, p));
743
777
  for (const rel of written) log(` ${COLOR.green}→ ${rel}${COLOR.reset}`);
778
+ if (manualCarry) printManualCarry(manualCarry);
744
779
  return {
745
780
  label: paths.label,
746
781
  items: model.manifest.itemCount,
@@ -751,6 +786,23 @@ async function exportMatrixTarget(
751
786
  };
752
787
  }
753
788
 
789
+ /** What happened to the tester's hand-entered Manual results on this write (#756). */
790
+ function printManualCarry(r: ManualCarryReport): void {
791
+ const kept = r.carried + r.unchecked;
792
+ if (kept + r.reset.length + r.notPlaced.length + r.duplicates.length === 0) return;
793
+ const list = (refs: string[]) => `${refs.slice(0, 4).join(', ')}${refs.length > 4 ? ', …' : ''}`;
794
+ log(` Manual results: ${kept} kept${r.unchecked > 0 ? ` (${r.unchecked} without a drift check — the file predates the fingerprint stamp)` : ''}`);
795
+ if (r.reset.length > 0) {
796
+ log(` ${COLOR.yellow}reset to Pending${COLOR.reset} — procedure changed since the result was recorded (${r.reset.length}): ${list(r.reset)}; the previous result is kept in Note`);
797
+ }
798
+ if (r.notPlaced.length > 0) {
799
+ log(` ${COLOR.yellow}not carried${COLOR.reset} — no matching manual case in the feature any more (${r.notPlaced.length}): ${list(r.notPlaced.map((n) => `${n.ref} was ${n.result}`))}`);
800
+ }
801
+ if (r.duplicates.length > 0) {
802
+ log(` ${COLOR.yellow}not carried${COLOR.reset} — a second row for the same case; only the first was kept (${r.duplicates.length}): ${list(r.duplicates.map((n) => `${n.ref} was ${n.result}`))}`);
803
+ }
804
+ }
805
+
754
806
  /**
755
807
  * One place that accounts for every target the run did NOT write, so a sweep is
756
808
  * never silently partial: scaffolds skipped by preflight and units whose gates /
@@ -934,12 +986,9 @@ export function registerDeliveryCommand(program: Command): void {
934
986
  // Deliverables are COMMITTED files; delivery is the last stop before a
935
987
  // hardcoded credential lands in one. Same scan `generate` runs.
936
988
  const scanDirs = [...new Set(toExport.map((t) => qaDir(cwd, t)))];
937
- const secretHits = scanDirs.flatMap((d) => scanTestDataSecrets(d));
938
- if (secretHits.length) {
939
- log(`\n⚠️ Possible secret(s) in committed test-data (review — do NOT commit real credentials):`);
940
- for (const h of secretHits.slice(0, 10)) log(` ${h.file}:${h.line} — ${h.reason}`);
941
- log(` Move real secrets to an env overlay / CI secret; keep test-data placeholders only.\n`);
942
- }
989
+ const secretScan = scanDirs.map((d) => scanTestDataSecrets(d))
990
+ .reduce<SecretScanResult>((a, r) => ({ hits: [...a.hits, ...r.hits], unscanned: [...a.unscanned, ...r.unscanned] }), { hits: [], unscanned: [] });
991
+ for (const line of formatSecretScan(secretScan)) log(line);
943
992
 
944
993
  // 3. Export
945
994
  const legacy = options.legacy || options.full;
@@ -955,9 +1004,9 @@ export function registerDeliveryCommand(program: Command): void {
955
1004
  // Same contract as the export path: an all-@exploration feature has no
956
1005
  // deliverable variants, so there is no map to approve — skip, not an error.
957
1006
  {
958
- const featureMeta = parseFeatureMetadata(paths.featureFile);
959
- if (featureMeta.scenarios.length > 0
960
- && featureMeta.scenarios.every((s) => s.tags.some((t) => /^@exploration\b/i.test(t)))) {
1007
+ const scenarios = parseFeatureMetadata(paths.featureFile).scenarios.filter((s) => !isHookScenario(s));
1008
+ if (scenarios.length > 0
1009
+ && scenarios.every((s) => s.tags.some((t) => /^@exploration\b/i.test(t)))) {
961
1010
  log(`${COLOR.gray}${paths.label}: nothing to approve — all scenario(s) are @exploration probes.${COLOR.reset}`);
962
1011
  continue;
963
1012
  }
@@ -982,10 +1031,11 @@ export function registerDeliveryCommand(program: Command): void {
982
1031
 
983
1032
  const matrixSummaries: MatrixRunSummary[] = [];
984
1033
  const notExported: string[] = [];
1034
+ const env = getEnvironment(cwd);
985
1035
  for (const target of toExport) {
986
1036
  const s = await exportMatrixTarget(cwd, target, {
987
1037
  check: options.check, preview: options.preview, format: format as MatrixFormat,
988
- });
1038
+ }, env);
989
1039
  if (s === 'skipped') continue; // all-@exploration feature — neither exported nor a failure
990
1040
  if (s) matrixSummaries.push(s);
991
1041
  else notExported.push(target.isFlow ? `flow/${target.featureBaseName}` : target.featureBaseName);
@@ -3,7 +3,7 @@ import * as path from 'path';
3
3
  import * as fs from 'fs';
4
4
  import { CodeGenerator } from '../../generators/test-generator/code-generator';
5
5
  import { adapterRegistry } from '../../generators/test-generator/adapters';
6
- import { scanTestDataSecrets } from '../../harness/secret-scan';
6
+ import { scanTestDataSecrets, formatSecretScan, type SecretScanResult } from '../../harness/secret-scan';
7
7
  import { capabilityRegistry } from '../../capabilities/registry';
8
8
  import { discoverAndRegisterCapabilities } from '../../capabilities/discover';
9
9
  import { readCapabilities, writeCapabilities, driverMeta, loadDriverCatalog } from '../../harness/capability';
@@ -160,6 +160,17 @@ export function registerGenerateCommand(program: Command): void {
160
160
  const cap = readCapabilities(cwd);
161
161
  if (cap.platform) {
162
162
  platform = cap.platform;
163
+ } else if (cap.source === 'unreadable') {
164
+ // The file EXISTS and we failed to parse it. Scaffolding the web default here would
165
+ // overwrite whatever the user declared — a mobile project's platform, drivers and
166
+ // verification scope, gone, while the notice claimed the file was missing. Refuse:
167
+ // the one thing we know is that we do not know what this project is.
168
+ throw new Error(
169
+ `qa/capabilities.yaml exists but could not be parsed — fix the YAML and re-run.\n` +
170
+ ` Refusing to scaffold a default over it: that would discard the platform, drivers ` +
171
+ `and verification scope it declares.\n` +
172
+ ` To compile without reading it, pass --framework <web|mobile|…>.`,
173
+ );
163
174
  } else {
164
175
  platform = 'web';
165
176
  writeCapabilities(cwd, { platform: 'web', enabled: ['web'] });
@@ -225,12 +236,9 @@ export function registerGenerateCommand(program: Command): void {
225
236
  if (fs.existsSync(d)) for (const n of fs.readdirSync(d)) scanDirs.push(path.join(d, n));
226
237
  }
227
238
  }
228
- const secretHits = scanDirs.flatMap((d) => scanTestDataSecrets(d));
229
- if (secretHits.length) {
230
- console.log(`\n⚠️ Possible secret(s) in committed test-data (review — do NOT commit real credentials):`);
231
- for (const h of secretHits.slice(0, 10)) console.log(` ${h.file}:${h.line} — ${h.reason}`);
232
- console.log(` Move real secrets to an env overlay / CI secret; keep test-data placeholders only.`);
233
- }
239
+ const secretScan = scanDirs.map((d) => scanTestDataSecrets(d))
240
+ .reduce<SecretScanResult>((a, r) => ({ hits: [...a.hits, ...r.hits], unscanned: [...a.unscanned, ...r.unscanned] }), { hits: [], unscanned: [] });
241
+ for (const line of formatSecretScan(secretScan)) console.log(line);
234
242
 
235
243
  // Advisory harness sensors (e.g. @cases/@query lint) — run via the Capability SPI,
236
244
  // never block generation. Each capability registers its own; the kernel just runs them.
@@ -13,6 +13,7 @@ import { detectReportRoot, sanitizeReportPaths } from '../../exporters/report-pa
13
13
  import { registerResultsSanitizeCommand } from './results-sanitize';
14
14
  import { resolveResultsPath } from '../../exporters/playwright-report-parser';
15
15
  import { parseSpecFile } from '../../exporters/spec-parser';
16
+ import { writeFileAtomic } from '../../harness/write-atomic';
16
17
 
17
18
  interface MergeCliOptions {
18
19
  out?: string;
@@ -38,7 +39,7 @@ function readReport(file: string, label: string): any {
38
39
  * (flows → screens → api → api flows); the same basename found in more than one
39
40
  * place is an ambiguity error, never a silent pick.
40
41
  */
41
- function resolveFeatureDir(cwd: string, name: string): string {
42
+ export function resolveFeatureDir(cwd: string, name: string): string {
42
43
  const roots = [
43
44
  path.join(cwd, 'specs', 'generated', 'flows'),
44
45
  path.join(cwd, 'specs', 'generated'),
@@ -88,6 +89,12 @@ export function runResultsMerge(baseArg: string | undefined, newArg: string | un
88
89
  if (!baseFile || path.dirname(baseFile) !== dir) {
89
90
  throw new Error(`results merge: no per-target result file for "${opts.screen}" in ${path.relative(cwd, dir)} — nothing to merge into (a first full run writes it directly).`);
90
91
  }
92
+ // Under SUNGEN_ENV the candidate order falls back to the base-locale file when the env file
93
+ // does not exist yet; merging a `vi` subset there would overwrite `ja`/base results in silence.
94
+ const env = process.env.SUNGEN_ENV;
95
+ if (env && !path.basename(baseFile).endsWith(`.${env}.json`)) {
96
+ throw new Error(`results merge: SUNGEN_ENV=${env} but "${opts.screen}" has no ${env} result file yet (found ${path.basename(baseFile)}) — run the full ${env} suite first; it writes the file directly.`);
97
+ }
91
98
  // Ghost/foreign-title checks need the CURRENT spec titles.
92
99
  const specFile = path.join(dir, `${opts.screen}.spec.ts`);
93
100
  try { specTitles = parseSpecFile(specFile).tests.map((t) => t.testTitle); } catch { specTitles = null; }
@@ -125,14 +132,7 @@ export function runResultsMerge(baseArg: string | undefined, newArg: string | un
125
132
  // that file is the single source of execution truth for delivery/dashboard.
126
133
  // (Same-dir rename = same device; concurrent merges are last-rename-wins.)
127
134
  const outFile = opts.out ?? baseFile;
128
- const tmp = `${outFile}.tmp-${process.pid}`;
129
- try {
130
- fs.writeFileSync(tmp, JSON.stringify(merged, null, 2));
131
- fs.renameSync(tmp, outFile);
132
- } catch (err) {
133
- try { fs.rmSync(tmp, { force: true }); } catch { /* best-effort cleanup */ }
134
- throw err;
135
- }
135
+ writeFileAtomic(outFile, JSON.stringify(merged, null, 2));
136
136
 
137
137
  console.log(`✓ merged ${path.basename(newFile)} into ${path.relative(cwd, outFile)} — ${replaced.length} updated, ${added.length} added, ${kept.length} kept`);
138
138
  return outFile;
@@ -16,6 +16,7 @@ import * as fs from 'fs';
16
16
  import * as path from 'path';
17
17
  import fg from 'fast-glob';
18
18
  import { detectReportRoot, sanitizeReportPaths } from '../../exporters/report-path-sanitizer';
19
+ import { writeFileAtomic } from '../../harness/write-atomic';
19
20
 
20
21
  /** Where a project keeps the JSON files that carry paths. */
21
22
  export const DEFAULT_REPORT_GLOBS = [
@@ -50,15 +51,7 @@ export function runResultsSanitize(files: string[] | undefined, cwd: string = pr
50
51
  const root = detectReportRoot(doc) ?? nearestProjectRoot(path.dirname(file)) ?? cwd;
51
52
  const { doc: clean, rewritten } = sanitizeReportPaths(doc, { root });
52
53
  if (rewritten > 0) {
53
- // Atomic write, same as `results merge`: a crash mid-write must never leave a truncated report.
54
- const tmp = `${file}.tmp-${process.pid}`;
55
- try {
56
- fs.writeFileSync(tmp, JSON.stringify(clean, null, 2) + (raw.endsWith('\n') ? '\n' : ''));
57
- fs.renameSync(tmp, file);
58
- } catch (err) {
59
- try { fs.rmSync(tmp, { force: true }); } catch { /* best-effort cleanup */ }
60
- throw err;
61
- }
54
+ writeFileAtomic(file, JSON.stringify(clean, null, 2) + (raw.endsWith('\n') ? '\n' : ''));
62
55
  }
63
56
  out.push({ file: path.relative(cwd, file), rewritten });
64
57
  }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * `sungen select` — which tests does a list of test-case IDs name? (#835)
3
+ *
4
+ * Backs `run-test --ids`. Like `sungen changed` for `--change-only`, the selection is computed here
5
+ * so it is deterministic and testable; the workflow only runs it and merges the subset result into
6
+ * the existing per-feature result file.
7
+ *
8
+ * Fails loud: an ID that selects nothing runnable exits non-zero, because a 0-test run reports green.
9
+ */
10
+ import { Command } from 'commander';
11
+ import * as path from 'path';
12
+ import { findUnitDir } from '../resolve-unit';
13
+ import { loadUnitScenarios } from '../../harness/parse';
14
+ import { parseIdList, selectByIds } from '../../harness/select-by-ids';
15
+ import { resolveFeatureDir } from './results-merge';
16
+ import { readCapabilities } from '../../harness/capability';
17
+ import { projectRootFromScreenDir } from '../../harness/intent';
18
+
19
+ export function registerSelectCommand(program: Command): void {
20
+ program
21
+ .command('select')
22
+ .description('List the tests a set of test-case IDs selects, grouped per feature (backs run-test --ids)')
23
+ .option('-s, --screen <name>', 'Screen or flow name')
24
+ .option('--api <name>', 'API-first area or api flow (qa/api/<name>)')
25
+ .option('--area <name>', 'Alias of --api')
26
+ .requiredOption('--ids <list>', 'Test-case IDs, comma- or space-separated (e.g. VP-UI-001,VP-UI-002)')
27
+ .option('--json', 'Output the selection as JSON (per feature: spec, ids, titles, grep)')
28
+ .option('--grep', 'Output ONLY the regex for -g / --mochaOpts.grep (selection must lie in one feature)')
29
+ .action((options) => {
30
+ try {
31
+ const name = options.screen || options.api || options.area;
32
+ if (!name) throw new Error('Provide --screen <name> (or --api <area>)');
33
+ const dir = findUnitDir(name);
34
+ if (!dir) throw new Error(`Not found: qa/screens|flows|api/${name}`);
35
+ const ids = parseIdList(options.ids);
36
+ if (ids.length === 0) throw new Error('--ids is empty — pass at least one test-case ID');
37
+
38
+ const cwd = process.cwd();
39
+ // Same capability set `generate` compiles against: a @requires:<cap> scenario is a skip without it.
40
+ const enabledCaps = new Set(readCapabilities(projectRootFromScreenDir(path.resolve(dir))).enabled.map((c) => c.toLowerCase()));
41
+ const selection = selectByIds(loadUnitScenarios(dir, name), ids, name, enabledCaps);
42
+ // Resolve each feature's compiled spec: a test cannot run before `sungen generate`.
43
+ const features = selection.features.map((g) => {
44
+ let spec: string | null = null;
45
+ try { spec = path.relative(cwd, path.join(resolveFeatureDir(cwd, g.feature), `${g.feature}.spec.ts`)); } catch { /* reported below */ }
46
+ return { ...g, spec };
47
+ });
48
+ const notCompiled = features.filter((f) => !f.spec).map((f) => f.feature);
49
+ const failed = selection.unknown.length > 0 || selection.notRunnable.length > 0 || notCompiled.length > 0 || features.length === 0;
50
+
51
+ if (options.json) {
52
+ console.log(JSON.stringify({ unit: name, features, unknown: selection.unknown, notRunnable: selection.notRunnable, notCompiled }, null, 2));
53
+ if (failed) process.exitCode = 1;
54
+ return;
55
+ }
56
+ if (options.grep) {
57
+ // Only a clean single-feature selection prints a regex; anything else must not be run
58
+ // as if it were complete, so it prints nothing and exits non-zero.
59
+ if (failed || features.length !== 1) {
60
+ console.error(features.length > 1
61
+ ? `select: the IDs span ${features.length} features (${features.map((f) => f.feature).join(', ')}) — use --json and run each feature on its own.`
62
+ : 'select: nothing runnable for these IDs — run without --grep to see why.');
63
+ process.exitCode = 1;
64
+ return;
65
+ }
66
+ console.log(features[0].grep);
67
+ return;
68
+ }
69
+
70
+ const selected = features.reduce((n, f) => n + f.titles.length, 0);
71
+ // A @cases scenario expands to one test per dataset row, so this counts scenarios.
72
+ console.log(`\n ${selected} scenario(s) selected for ${ids.length} ID(s) in ${name}:\n`);
73
+ for (const f of features) {
74
+ console.log(` ${f.feature} → ${f.spec ?? '(not compiled — run sungen generate first)'}`);
75
+ for (const t of f.titles) console.log(` ▶ ${t}`);
76
+ }
77
+ for (const u of selection.unknown) {
78
+ console.log(` ✗ ${u.id} — no scenario has this ID${u.didYouMean.length ? ` (did you mean ${u.didYouMean.join(', ')}?)` : ''}`);
79
+ }
80
+ for (const n of selection.notRunnable) console.log(` ○ ${n.id} — ${n.reason}`);
81
+ console.log('');
82
+ if (failed) process.exitCode = 1;
83
+ } catch (err) {
84
+ console.error(err instanceof Error ? err.message : String(err));
85
+ process.exitCode = 1;
86
+ }
87
+ });
88
+ }
@@ -0,0 +1,57 @@
1
+ import { Command } from 'commander';
2
+ import * as path from 'path';
3
+ import { resolveUnit } from '../resolve-unit';
4
+ import { triageUnit, writeTriageReport } from '../../harness/triage-unit';
5
+ import type { TriageClass } from '../../harness/triage';
6
+
7
+ /** Display order: what needs a decision first, green last. */
8
+ const ORDER: TriageClass[] = ['product-candidate', 'unclassified', 'test-defect', 'environment', 'blocked-capability', 'known-defect', 'flaky'];
9
+
10
+ /**
11
+ * `sungen triage` — classify every red or flaky scenario of the latest run (see harness/triage.ts).
12
+ * Reports, never gates: always exits 0 once the unit is found.
13
+ */
14
+ export function registerTriageCommand(program: Command): void {
15
+ program
16
+ .command('triage')
17
+ .description('Classify each red or flaky scenario of the latest run: known-defect, blocked, environment, test-defect, product-candidate, unclassified')
18
+ .option('-s, --screen <name>', 'Screen or flow name')
19
+ .option('--api <name>', 'API-first area or api flow (qa/api/<name>)')
20
+ .option('--area <name>', 'Alias of --api')
21
+ .option('--json', 'Output the raw JSON report')
22
+ .action((o: { screen?: string; api?: string; area?: string; json?: boolean }) => {
23
+ try {
24
+ const name = o.screen || o.api || o.area;
25
+ if (!name) throw new Error('Provide --screen <name> (or --api <area>)');
26
+ const cwd = process.cwd();
27
+ const unit = resolveUnit(name, cwd);
28
+ if (!unit) throw new Error(`Not found: qa/screens/${name}, qa/flows/${name}, or qa/api/${name}`);
29
+
30
+ const report = triageUnit(cwd, unit);
31
+ if (report.resultFiles.length === 0) {
32
+ if (o.json) console.log(JSON.stringify(report, null, 2));
33
+ else console.log(`\n ${name}: no results — run the tests first.\n`);
34
+ return;
35
+ }
36
+ const saved = writeTriageReport(cwd, report);
37
+ if (o.json) { console.log(JSON.stringify(report, null, 2)); return; }
38
+
39
+ const L = console.log;
40
+ L(`\n━━━ Triage: ${name} ━━━`);
41
+ if (report.rows.length === 0) L(' ✓ every scenario passed on its first attempt');
42
+ for (const cls of ORDER) {
43
+ const rows = report.rows.filter((r) => r.class === cls);
44
+ if (rows.length === 0) continue;
45
+ L(`\n ${cls} (${rows.length})`);
46
+ for (const r of rows) {
47
+ L(` • ${r.scenario}${r.variant ? ` [${r.variant}]` : ''}${r.defectId ? ` → ${r.defectId}` : ''}`);
48
+ L(` ${r.ruleId}: ${r.evidence}`);
49
+ }
50
+ }
51
+ L(`\n Report: ${path.relative(cwd, saved)}\n`);
52
+ } catch (error) {
53
+ console.error('Error:', error instanceof Error ? error.message : error);
54
+ process.exitCode = 1;
55
+ }
56
+ });
57
+ }
@@ -103,6 +103,9 @@ export function registerUpdateCommand(program: Command): void {
103
103
  // playwright.config.ts is written once at init and never re-synced — patch the reporter in.
104
104
  const { installReportSanitizeReporter } = require('../../orchestrator/report-sanitize-reporter-install');
105
105
  installReportSanitizeReporter(process.cwd(), options.dryRun ?? false);
106
+ // …and move the old `trace: 'on-first-retry'` default, which never records a trace off CI.
107
+ const { installTraceMode } = require('../../orchestrator/trace-mode-install');
108
+ installTraceMode(process.cwd(), options.dryRun ?? false);
106
109
  } catch (error) {
107
110
  console.error('❌ Update failed:', error);
108
111
  process.exit(1);
@@ -0,0 +1,58 @@
1
+ import { Command } from 'commander';
2
+ import { resolveUnit } from '../resolve-unit';
3
+ import { scanViewpointUnit, ViewpointScan } from '../../harness/viewpoint-scan';
4
+
5
+ const SHOWN = 10;
6
+
7
+ /** `a, b, c … +N more` — a list long enough to scroll past is a count plus a sample. */
8
+ const sample = (xs: string[]): string => (xs.length <= SHOWN ? xs.join(', ') : `${xs.slice(0, SHOWN).join(', ')} … +${xs.length - SHOWN} more`);
9
+
10
+ function printScan(name: string, s: ViewpointScan): void {
11
+ const L = console.log;
12
+ L(`\n━━━ Viewpoint scan: ${name} ━━━`);
13
+ L(` mode ${s.mode} (${s.modeReason}) — ${s.mode === 2 ? 'the AI drafts / revises its own draft' : 'review only, the file is never edited'}`);
14
+ L(` ${s.vpRows.length} rows (${s.distinctIds} distinct ids) without Kỳ vọng: ${s.rowsWithoutExpected.length} manual: ${s.manualRows.length}`);
15
+ if (s.note) L(` ⚠ ${s.note}`);
16
+ if (s.rowsWithoutExpected.length) L(` no Kỳ vọng: ${sample(s.rowsWithoutExpected)}`);
17
+ if (s.manualRows.length) L(` manual: ${sample(s.manualRows)}`);
18
+ if (s.itemSource === 'none') L(' item source: none (the viewpoint cites no spec id, spec item or UI-NNN)');
19
+ else L(` item source: ${s.itemSource} uncited items: ${s.uncitedItems.length}`);
20
+ if (s.uncitedItems.length) L(` ${sample(s.uncitedItems.map((i) => (i.name ? `${i.ref} (${i.name})` : i.ref)))}`);
21
+ if (s.withoutScenario === null) L(' scenarios: no feature file yet');
22
+ else L(` VP ids with no scenario: ${s.withoutScenario.length}${s.withoutScenario.length ? `\n ${sample(s.withoutScenario)}` : ''}`);
23
+ L('');
24
+ }
25
+
26
+ /**
27
+ * `sungen viewpoint scan` — ids and item refs only. Read-only: no audit finding, no score, no
28
+ * baseline effect; the file is never edited. What the rows MEAN is the review skill's job.
29
+ */
30
+ export function registerViewpointCommand(program: Command): void {
31
+ const viewpoint = program
32
+ .command('viewpoint')
33
+ .description('Read a unit\'s requirements/test-viewpoint.md');
34
+
35
+ viewpoint
36
+ .command('scan')
37
+ .description('List VP rows (full-id first cell), the item numbers they cite, uncited spec items and VP ids no scenario carries — read-only')
38
+ .option('-s, --screen <name>', 'Screen (or flow) name')
39
+ .option('--flow <name>', 'Flow name (qa/flows/<name>)')
40
+ .option('--json', 'Output the scan as JSON')
41
+ .action((o: { screen?: string; flow?: string; json?: boolean }) => {
42
+ try {
43
+ if (o.screen && o.flow) throw new Error('Provide --screen <name> or --flow <name>, not both');
44
+ const name = o.screen || o.flow;
45
+ if (!name) throw new Error('Provide --screen <name> or --flow <name>');
46
+ const unit = resolveUnit(name);
47
+ if (!unit) throw new Error(`Not found: qa/screens/${name} or qa/flows/${name}`);
48
+ if (o.flow && unit.kind !== 'flow') throw new Error(`Not a flow: ${name} resolves to qa/${unit.unitId} (use --screen)`);
49
+
50
+ const scan = scanViewpointUnit(unit.dir, name);
51
+ if (o.json) console.log(JSON.stringify(scan, null, 2));
52
+ else printScan(name, scan);
53
+ } catch (e) {
54
+ console.error('Error:', e instanceof Error ? e.message : e);
55
+ process.exit(1);
56
+ }
57
+ });
58
+ }