@sun-asterisk/sungen 3.2.33 → 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 (346) hide show
  1. package/dist/cli/commands/audit.d.ts +4 -0
  2. package/dist/cli/commands/audit.d.ts.map +1 -1
  3. package/dist/cli/commands/audit.js +54 -10
  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 +5 -1
  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 +75 -42
  20. package/dist/cli/commands/delivery.js.map +1 -1
  21. package/dist/cli/commands/results-merge.d.ts +9 -0
  22. package/dist/cli/commands/results-merge.d.ts.map +1 -1
  23. package/dist/cli/commands/results-merge.js +7 -0
  24. package/dist/cli/commands/results-merge.js.map +1 -1
  25. package/dist/cli/commands/select.d.ts +12 -0
  26. package/dist/cli/commands/select.d.ts.map +1 -0
  27. package/dist/cli/commands/select.js +122 -0
  28. package/dist/cli/commands/select.js.map +1 -0
  29. package/dist/cli/commands/triage.d.ts +7 -0
  30. package/dist/cli/commands/triage.d.ts.map +1 -0
  31. package/dist/cli/commands/triage.js +98 -0
  32. package/dist/cli/commands/triage.js.map +1 -0
  33. package/dist/cli/commands/update.d.ts.map +1 -1
  34. package/dist/cli/commands/update.js +3 -0
  35. package/dist/cli/commands/update.js.map +1 -1
  36. package/dist/cli/commands/viewpoint.d.ts +7 -0
  37. package/dist/cli/commands/viewpoint.d.ts.map +1 -0
  38. package/dist/cli/commands/viewpoint.js +70 -0
  39. package/dist/cli/commands/viewpoint.js.map +1 -0
  40. package/dist/cli/index.js +13 -2
  41. package/dist/cli/index.js.map +1 -1
  42. package/dist/dashboard/templates/index.html +1 -1
  43. package/dist/dashboard/types.d.ts +1 -0
  44. package/dist/dashboard/types.d.ts.map +1 -1
  45. package/dist/exporters/csv-exporter.d.ts +4 -0
  46. package/dist/exporters/csv-exporter.d.ts.map +1 -1
  47. package/dist/exporters/csv-exporter.js +12 -11
  48. package/dist/exporters/csv-exporter.js.map +1 -1
  49. package/dist/exporters/feature-parser.d.ts +8 -0
  50. package/dist/exporters/feature-parser.d.ts.map +1 -1
  51. package/dist/exporters/feature-parser.js +12 -0
  52. package/dist/exporters/feature-parser.js.map +1 -1
  53. package/dist/exporters/json-exporter.d.ts.map +1 -1
  54. package/dist/exporters/json-exporter.js +7 -0
  55. package/dist/exporters/json-exporter.js.map +1 -1
  56. package/dist/exporters/manual-hybrid-oracle.d.ts +54 -0
  57. package/dist/exporters/manual-hybrid-oracle.d.ts.map +1 -0
  58. package/dist/exporters/manual-hybrid-oracle.js +114 -0
  59. package/dist/exporters/manual-hybrid-oracle.js.map +1 -0
  60. package/dist/exporters/matrix/build.d.ts.map +1 -1
  61. package/dist/exporters/matrix/build.js +55 -12
  62. package/dist/exporters/matrix/build.js.map +1 -1
  63. package/dist/exporters/matrix/carry-manual-results.d.ts +19 -0
  64. package/dist/exporters/matrix/carry-manual-results.d.ts.map +1 -1
  65. package/dist/exporters/matrix/carry-manual-results.js +76 -6
  66. package/dist/exporters/matrix/carry-manual-results.js.map +1 -1
  67. package/dist/exporters/matrix/export.d.ts +8 -2
  68. package/dist/exporters/matrix/export.d.ts.map +1 -1
  69. package/dist/exporters/matrix/export.js +15 -3
  70. package/dist/exporters/matrix/export.js.map +1 -1
  71. package/dist/exporters/matrix/gates.js +6 -0
  72. package/dist/exporters/matrix/gates.js.map +1 -1
  73. package/dist/exporters/matrix/render-csv.js +1 -1
  74. package/dist/exporters/matrix/render-csv.js.map +1 -1
  75. package/dist/exporters/matrix/render-testcase-sheet.d.ts.map +1 -1
  76. package/dist/exporters/matrix/render-testcase-sheet.js +10 -4
  77. package/dist/exporters/matrix/render-testcase-sheet.js.map +1 -1
  78. package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
  79. package/dist/exporters/matrix/render-xlsx.js +3 -0
  80. package/dist/exporters/matrix/render-xlsx.js.map +1 -1
  81. package/dist/exporters/matrix/types.d.ts +13 -1
  82. package/dist/exporters/matrix/types.d.ts.map +1 -1
  83. package/dist/exporters/matrix/types.js.map +1 -1
  84. package/dist/exporters/matrix/wording.d.ts +1 -1
  85. package/dist/exporters/matrix/wording.d.ts.map +1 -1
  86. package/dist/exporters/matrix/wording.js +10 -1
  87. package/dist/exporters/matrix/wording.js.map +1 -1
  88. package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
  89. package/dist/exporters/playwright-report-parser.js +1 -0
  90. package/dist/exporters/playwright-report-parser.js.map +1 -1
  91. package/dist/exporters/run-environment.d.ts +32 -0
  92. package/dist/exporters/run-environment.d.ts.map +1 -0
  93. package/dist/exporters/run-environment.js +125 -0
  94. package/dist/exporters/run-environment.js.map +1 -0
  95. package/dist/exporters/scenario-merger.d.ts +18 -0
  96. package/dist/exporters/scenario-merger.d.ts.map +1 -1
  97. package/dist/exporters/scenario-merger.js +49 -3
  98. package/dist/exporters/scenario-merger.js.map +1 -1
  99. package/dist/exporters/test-data-resolver.d.ts +0 -4
  100. package/dist/exporters/test-data-resolver.d.ts.map +1 -1
  101. package/dist/exporters/test-data-resolver.js +0 -7
  102. package/dist/exporters/test-data-resolver.js.map +1 -1
  103. package/dist/exporters/types.d.ts +5 -0
  104. package/dist/exporters/types.d.ts.map +1 -1
  105. package/dist/generators/gherkin-parser/index.d.ts +11 -4
  106. package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
  107. package/dist/generators/gherkin-parser/index.js +49 -19
  108. package/dist/generators/gherkin-parser/index.js.map +1 -1
  109. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +4 -0
  110. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  111. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/click-select-action.hbs +1 -1
  112. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +1 -1
  113. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/pick-option.hbs +13 -0
  114. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +16 -0
  115. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  116. package/dist/generators/test-generator/code-generator.js +23 -2
  117. package/dist/generators/test-generator/code-generator.js.map +1 -1
  118. package/dist/generators/test-generator/diagnostics.d.ts +3 -1
  119. package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
  120. package/dist/generators/test-generator/diagnostics.js +3 -1
  121. package/dist/generators/test-generator/diagnostics.js.map +1 -1
  122. package/dist/harness/audit.d.ts +3 -0
  123. package/dist/harness/audit.d.ts.map +1 -1
  124. package/dist/harness/audit.js +48 -15
  125. package/dist/harness/audit.js.map +1 -1
  126. package/dist/harness/counted-assertions.d.ts +2 -0
  127. package/dist/harness/counted-assertions.d.ts.map +1 -0
  128. package/dist/harness/counted-assertions.js +38 -0
  129. package/dist/harness/counted-assertions.js.map +1 -0
  130. package/dist/harness/data-driven-lint.d.ts.map +1 -1
  131. package/dist/harness/data-driven-lint.js +69 -27
  132. package/dist/harness/data-driven-lint.js.map +1 -1
  133. package/dist/harness/defect-draft.d.ts +14 -0
  134. package/dist/harness/defect-draft.d.ts.map +1 -0
  135. package/dist/harness/defect-draft.js +143 -0
  136. package/dist/harness/defect-draft.js.map +1 -0
  137. package/dist/harness/defects.d.ts +24 -0
  138. package/dist/harness/defects.d.ts.map +1 -0
  139. package/dist/harness/defects.js +118 -0
  140. package/dist/harness/defects.js.map +1 -0
  141. package/dist/harness/manifest.d.ts.map +1 -1
  142. package/dist/harness/manifest.js +2 -2
  143. package/dist/harness/manifest.js.map +1 -1
  144. package/dist/harness/next-step.d.ts +2 -4
  145. package/dist/harness/next-step.d.ts.map +1 -1
  146. package/dist/harness/next-step.js +50 -5
  147. package/dist/harness/next-step.js.map +1 -1
  148. package/dist/harness/parse.d.ts +45 -0
  149. package/dist/harness/parse.d.ts.map +1 -1
  150. package/dist/harness/parse.js +142 -24
  151. package/dist/harness/parse.js.map +1 -1
  152. package/dist/harness/quality-gates.d.ts.map +1 -1
  153. package/dist/harness/quality-gates.js +6 -4
  154. package/dist/harness/quality-gates.js.map +1 -1
  155. package/dist/harness/read-text.d.ts +15 -0
  156. package/dist/harness/read-text.d.ts.map +1 -1
  157. package/dist/harness/read-text.js +25 -0
  158. package/dist/harness/read-text.js.map +1 -1
  159. package/dist/harness/script-check.d.ts.map +1 -1
  160. package/dist/harness/script-check.js +7 -1
  161. package/dist/harness/script-check.js.map +1 -1
  162. package/dist/harness/select-by-ids.d.ts +31 -0
  163. package/dist/harness/select-by-ids.d.ts.map +1 -0
  164. package/dist/harness/select-by-ids.js +106 -0
  165. package/dist/harness/select-by-ids.js.map +1 -0
  166. package/dist/harness/sensors.d.ts +8 -1
  167. package/dist/harness/sensors.d.ts.map +1 -1
  168. package/dist/harness/sensors.js +92 -21
  169. package/dist/harness/sensors.js.map +1 -1
  170. package/dist/harness/spec-branches.js +1 -1
  171. package/dist/harness/spec-branches.js.map +1 -1
  172. package/dist/harness/triage-signals.d.ts +20 -0
  173. package/dist/harness/triage-signals.d.ts.map +1 -0
  174. package/dist/harness/triage-signals.js +30 -0
  175. package/dist/harness/triage-signals.js.map +1 -0
  176. package/dist/harness/triage-unit.d.ts +23 -0
  177. package/dist/harness/triage-unit.d.ts.map +1 -0
  178. package/dist/harness/triage-unit.js +142 -0
  179. package/dist/harness/triage-unit.js.map +1 -0
  180. package/dist/harness/triage.d.ts +28 -0
  181. package/dist/harness/triage.d.ts.map +1 -0
  182. package/dist/harness/triage.js +99 -0
  183. package/dist/harness/triage.js.map +1 -0
  184. package/dist/harness/ui-inventory.d.ts +40 -0
  185. package/dist/harness/ui-inventory.d.ts.map +1 -0
  186. package/dist/harness/ui-inventory.js +191 -0
  187. package/dist/harness/ui-inventory.js.map +1 -0
  188. package/dist/harness/viewpoint-baseline.d.ts.map +1 -1
  189. package/dist/harness/viewpoint-baseline.js +7 -1
  190. package/dist/harness/viewpoint-baseline.js.map +1 -1
  191. package/dist/harness/viewpoint-draft-header.d.ts +7 -0
  192. package/dist/harness/viewpoint-draft-header.d.ts.map +1 -0
  193. package/dist/harness/viewpoint-draft-header.js +100 -0
  194. package/dist/harness/viewpoint-draft-header.js.map +1 -0
  195. package/dist/harness/viewpoint-ledger.d.ts +13 -3
  196. package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
  197. package/dist/harness/viewpoint-ledger.js +104 -18
  198. package/dist/harness/viewpoint-ledger.js.map +1 -1
  199. package/dist/harness/viewpoint-mode.d.ts +9 -0
  200. package/dist/harness/viewpoint-mode.d.ts.map +1 -0
  201. package/dist/harness/viewpoint-mode.js +52 -0
  202. package/dist/harness/viewpoint-mode.js.map +1 -0
  203. package/dist/harness/viewpoint-priority.d.ts +19 -0
  204. package/dist/harness/viewpoint-priority.d.ts.map +1 -0
  205. package/dist/harness/viewpoint-priority.js +36 -0
  206. package/dist/harness/viewpoint-priority.js.map +1 -0
  207. package/dist/harness/viewpoint-review-open.d.ts +6 -0
  208. package/dist/harness/viewpoint-review-open.d.ts.map +1 -0
  209. package/dist/harness/viewpoint-review-open.js +90 -0
  210. package/dist/harness/viewpoint-review-open.js.map +1 -0
  211. package/dist/harness/viewpoint-scan-refs.d.ts +50 -0
  212. package/dist/harness/viewpoint-scan-refs.d.ts.map +1 -0
  213. package/dist/harness/viewpoint-scan-refs.js +200 -0
  214. package/dist/harness/viewpoint-scan-refs.js.map +1 -0
  215. package/dist/harness/viewpoint-scan.d.ts +41 -0
  216. package/dist/harness/viewpoint-scan.d.ts.map +1 -0
  217. package/dist/harness/viewpoint-scan.js +118 -0
  218. package/dist/harness/viewpoint-scan.js.map +1 -0
  219. package/dist/orchestrator/flow-manager.d.ts +0 -1
  220. package/dist/orchestrator/flow-manager.d.ts.map +1 -1
  221. package/dist/orchestrator/flow-manager.js +3 -52
  222. package/dist/orchestrator/flow-manager.js.map +1 -1
  223. package/dist/orchestrator/screen-manager.d.ts.map +1 -1
  224. package/dist/orchestrator/screen-manager.js +8 -62
  225. package/dist/orchestrator/screen-manager.js.map +1 -1
  226. package/dist/orchestrator/templates/ai-src/agents/discovery.md +11 -3
  227. package/dist/orchestrator/templates/ai-src/agents/reviewer.md +8 -0
  228. package/dist/orchestrator/templates/ai-src/commands/create-test.md +32 -24
  229. package/dist/orchestrator/templates/ai-src/commands/review.md +2 -2
  230. package/dist/orchestrator/templates/ai-src/commands/run-test.md +78 -15
  231. package/dist/orchestrator/templates/ai-src/skills/sungen-capture/mode-local.md +2 -2
  232. package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +22 -0
  233. package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +18 -9
  234. package/dist/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +5 -1
  235. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +58 -14
  236. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +42 -2
  237. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +7 -0
  238. package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +18 -0
  239. package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/review-checklist.md +63 -0
  240. package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/viewpoint-template.md +158 -0
  241. package/dist/orchestrator/templates/playwright.config.ts +4 -2
  242. package/dist/orchestrator/templates/specs-url-assert.d.ts.map +1 -1
  243. package/dist/orchestrator/templates/specs-url-assert.js +24 -2
  244. package/dist/orchestrator/templates/specs-url-assert.js.map +1 -1
  245. package/dist/orchestrator/templates/specs-url-assert.ts +23 -2
  246. package/dist/orchestrator/trace-mode-install.d.ts +14 -0
  247. package/dist/orchestrator/trace-mode-install.d.ts.map +1 -0
  248. package/dist/orchestrator/trace-mode-install.js +132 -0
  249. package/dist/orchestrator/trace-mode-install.js.map +1 -0
  250. package/dist/orchestrator/viewpoint-pattern-catalog.d.ts +23 -0
  251. package/dist/orchestrator/viewpoint-pattern-catalog.d.ts.map +1 -0
  252. package/dist/orchestrator/viewpoint-pattern-catalog.js +48 -0
  253. package/dist/orchestrator/viewpoint-pattern-catalog.js.map +1 -0
  254. package/dist/orchestrator/viewpoint-scaffold.d.ts +3 -0
  255. package/dist/orchestrator/viewpoint-scaffold.d.ts.map +1 -0
  256. package/dist/orchestrator/viewpoint-scaffold.js +70 -0
  257. package/dist/orchestrator/viewpoint-scaffold.js.map +1 -0
  258. package/package.json +3 -3
  259. package/src/cli/commands/audit.ts +49 -10
  260. package/src/cli/commands/capability.ts +3 -1
  261. package/src/cli/commands/changed.ts +2 -4
  262. package/src/cli/commands/dashboard.ts +1 -26
  263. package/src/cli/commands/defect.ts +49 -0
  264. package/src/cli/commands/delivery.ts +70 -42
  265. package/src/cli/commands/results-merge.ts +7 -1
  266. package/src/cli/commands/select.ts +88 -0
  267. package/src/cli/commands/triage.ts +57 -0
  268. package/src/cli/commands/update.ts +3 -0
  269. package/src/cli/commands/viewpoint.ts +58 -0
  270. package/src/cli/index.ts +13 -2
  271. package/src/dashboard/templates/index.html +1 -1
  272. package/src/dashboard/types.ts +1 -0
  273. package/src/exporters/csv-exporter.ts +12 -11
  274. package/src/exporters/feature-parser.ts +12 -0
  275. package/src/exporters/json-exporter.ts +6 -1
  276. package/src/exporters/manual-hybrid-oracle.ts +112 -0
  277. package/src/exporters/matrix/build.ts +57 -11
  278. package/src/exporters/matrix/carry-manual-results.ts +70 -7
  279. package/src/exporters/matrix/export.ts +16 -2
  280. package/src/exporters/matrix/gates.ts +6 -0
  281. package/src/exporters/matrix/render-csv.ts +1 -1
  282. package/src/exporters/matrix/render-testcase-sheet.ts +10 -4
  283. package/src/exporters/matrix/render-xlsx.ts +3 -0
  284. package/src/exporters/matrix/types.ts +13 -1
  285. package/src/exporters/matrix/wording.ts +10 -1
  286. package/src/exporters/playwright-report-parser.ts +1 -0
  287. package/src/exporters/run-environment.ts +89 -0
  288. package/src/exporters/scenario-merger.ts +51 -5
  289. package/src/exporters/test-data-resolver.ts +0 -6
  290. package/src/exporters/types.ts +5 -0
  291. package/src/generators/gherkin-parser/index.ts +50 -18
  292. package/src/generators/test-generator/adapters/adapter-interface.ts +4 -0
  293. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/click-select-action.hbs +1 -1
  294. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +1 -1
  295. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/pick-option.hbs +13 -0
  296. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +16 -0
  297. package/src/generators/test-generator/code-generator.ts +23 -2
  298. package/src/generators/test-generator/diagnostics.ts +3 -1
  299. package/src/harness/audit.ts +52 -17
  300. package/src/harness/counted-assertions.ts +33 -0
  301. package/src/harness/data-driven-lint.ts +82 -24
  302. package/src/harness/defect-draft.ts +115 -0
  303. package/src/harness/defects.ts +94 -0
  304. package/src/harness/manifest.ts +2 -2
  305. package/src/harness/next-step.ts +47 -5
  306. package/src/harness/parse.ts +151 -27
  307. package/src/harness/quality-gates.ts +6 -4
  308. package/src/harness/read-text.ts +25 -0
  309. package/src/harness/script-check.ts +7 -1
  310. package/src/harness/select-by-ids.ts +83 -0
  311. package/src/harness/sensors.ts +91 -21
  312. package/src/harness/spec-branches.ts +1 -1
  313. package/src/harness/triage-signals.ts +46 -0
  314. package/src/harness/triage-unit.ts +113 -0
  315. package/src/harness/triage.ts +127 -0
  316. package/src/harness/ui-inventory.ts +164 -0
  317. package/src/harness/viewpoint-baseline.ts +7 -1
  318. package/src/harness/viewpoint-draft-header.ts +55 -0
  319. package/src/harness/viewpoint-ledger.ts +111 -24
  320. package/src/harness/viewpoint-mode.ts +49 -0
  321. package/src/harness/viewpoint-priority.ts +38 -0
  322. package/src/harness/viewpoint-review-open.ts +51 -0
  323. package/src/harness/viewpoint-scan-refs.ts +154 -0
  324. package/src/harness/viewpoint-scan.ts +111 -0
  325. package/src/orchestrator/flow-manager.ts +3 -53
  326. package/src/orchestrator/screen-manager.ts +7 -61
  327. package/src/orchestrator/templates/ai-src/agents/discovery.md +11 -3
  328. package/src/orchestrator/templates/ai-src/agents/reviewer.md +8 -0
  329. package/src/orchestrator/templates/ai-src/commands/create-test.md +32 -24
  330. package/src/orchestrator/templates/ai-src/commands/review.md +2 -2
  331. package/src/orchestrator/templates/ai-src/commands/run-test.md +78 -15
  332. package/src/orchestrator/templates/ai-src/skills/sungen-capture/mode-local.md +2 -2
  333. package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +22 -0
  334. package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +18 -9
  335. package/src/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +5 -1
  336. package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +58 -14
  337. package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +42 -2
  338. package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +7 -0
  339. package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +18 -0
  340. package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/review-checklist.md +63 -0
  341. package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/viewpoint-template.md +158 -0
  342. package/src/orchestrator/templates/playwright.config.ts +4 -2
  343. package/src/orchestrator/templates/specs-url-assert.ts +23 -2
  344. package/src/orchestrator/trace-mode-install.ts +94 -0
  345. package/src/orchestrator/viewpoint-pattern-catalog.ts +61 -0
  346. 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,10 +20,10 @@ 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
28
  import { scanTestDataSecrets, formatSecretScan, type SecretScanResult } from '../../harness/secret-scan';
30
29
  import { EnvironmentInfo, PreflightCheck, ScreenSummary, TestCaseRow, ApiCatalogEntry } from '../../exporters/types';
@@ -39,7 +38,7 @@ import {
39
38
  hasReview,
40
39
  } from '../../exporters/matrix/export';
41
40
  import { MatrixFinding } from '../../exporters/matrix/types';
42
- import { ManualCarryReport } from '../../exporters/matrix/carry-manual-results';
41
+ import { applyManualCarry, ManualCarryReport, PriorManualResults, readPriorManualResults } from '../../exporters/matrix/carry-manual-results';
43
42
  import { formatTestOutcome, resultTotals } from '../../exporters/matrix/result-totals';
44
43
  import { readCapabilities } from '../../harness/capability';
45
44
 
@@ -86,6 +85,12 @@ export interface DeliveryTarget {
86
85
  isFlow: boolean;
87
86
  }
88
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
+
89
94
  function makeTarget(screen: string, featureBaseName: string, kind: UnitKind): DeliveryTarget {
90
95
  return { screen, featureBaseName, name: featureBaseName, kind, isFlow: kind === 'flow' };
91
96
  }
@@ -342,34 +347,27 @@ export function hasBlockingMissing(p: PreflightCheck): boolean {
342
347
  // Environment
343
348
  // ----------------------------------------------------------------------------
344
349
 
345
- function getEnvironment(cwd: string): EnvironmentInfo {
346
- let baseURL = '';
347
- let projectName = 'chromium';
348
- const configPath = path.join(cwd, 'playwright.config.ts');
349
- if (fs.existsSync(configPath)) {
350
- const c = fs.readFileSync(configPath, 'utf-8');
351
- // Both config shapes: the legacy literal `baseURL: 'x'` and the env-aware
352
- // `baseURL: resolveBaseUrl('x')` (the quoted value is the static fallback).
353
- const baseMatch = c.match(/baseURL:\s*(?:resolveBaseUrl\(\s*)?['"]([^'"]+)['"]/);
354
- if (baseMatch) baseURL = baseMatch[1];
355
- const projMatch = c.match(/name:\s*['"]([^'"]+)['"]/);
356
- if (projMatch) projectName = projMatch[1];
357
- }
358
- // SUNGEN_BASE_URL (shell > .env.qa.<SUNGEN_ENV> > .env.qa) overrides the static
359
- // value — the Test Environment column must show the host the run actually hit.
360
- baseURL = resolveWebBaseUrl(cwd, baseURL);
361
-
362
- let executor = process.env.CI_USER || process.env.USER || '';
363
- try {
364
- const gitUser = execSync('git config user.name', { cwd, encoding: 'utf-8' }).trim();
365
- if (gitUser) executor = gitUser;
366
- } catch {
367
- // ignore
368
- }
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
+ }
369
354
 
370
- 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) };
371
368
  }
372
369
 
370
+
373
371
  /**
374
372
  * True when this is a mobile project (`platform: mobile` in qa/capabilities.yaml).
375
373
  * Mobile results are written PER-OS (`<feature>-test-result.android|ios.json`) —
@@ -405,6 +403,18 @@ function removeBaseDeliverables(cwd: string, featureBaseName: string): void {
405
403
  // Per-screen export
406
404
  // ----------------------------------------------------------------------------
407
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
+
408
418
  async function exportTarget(
409
419
  cwd: string,
410
420
  target: DeliveryTarget,
@@ -423,14 +433,12 @@ async function exportTarget(
423
433
  const spec = parseSpecFile(specFile);
424
434
 
425
435
  const merged = mergeFeatureAndSpec(feature, spec);
436
+ warnUnlabeledManualOracles(label, merged);
426
437
  // Screens get their URL path from the .feature file directly. Flows record
427
438
  // it in selectors/<feature>.yaml under the page-selector block — fall back
428
439
  // to that so the Environment column shows a full URL for flows too.
429
- let featurePath = feature.featurePath;
430
440
  const selectorsFile = path.join(base, 'selectors', `${target.featureBaseName}.yaml`);
431
- if (!featurePath && target.isFlow) {
432
- featurePath = readFlowPagePath(selectorsFile);
433
- }
441
+ const featurePath = targetFeaturePath(target, feature.featurePath, selectorsFile);
434
442
  const selectorKeyMap = loadSelectorKeyMap(selectorsFile);
435
443
 
436
444
  const specLink = fs.existsSync(specMdFile) ? path.relative(cwd, specMdFile) : '';
@@ -462,6 +470,7 @@ async function exportTarget(
462
470
  if (isMobileProject(cwd)) removeBaseDeliverables(cwd, target.featureBaseName);
463
471
  const rows = buildTestCaseRows({
464
472
  screen: label,
473
+ generateCommand: generateCommandFor(target),
465
474
  featureName: feature.featureName,
466
475
  // OS-precise env column: a mobile dual-id Path resolves to just the
467
476
  // android package / ios bundle id for SUNGEN_ENV=android|ios.
@@ -520,6 +529,7 @@ async function exportTarget(
520
529
  for (const variant of osVariants) {
521
530
  const variantRows = buildTestCaseRows({
522
531
  screen: label,
532
+ generateCommand: generateCommandFor(target),
523
533
  featureName: feature.featureName,
524
534
  featurePath: resolvePlatformAppId(featurePath, variant.locale), // OS-precise app id
525
535
  merged,
@@ -572,6 +582,7 @@ async function exportTarget(
572
582
  const variantResults = variant.resultsPath ? loadPlaywrightReport(variant.resultsPath) : null;
573
583
  const variantRows = buildTestCaseRows({
574
584
  screen: label,
585
+ generateCommand: generateCommandFor(target),
575
586
  featureName: feature.featureName,
576
587
  // No-op for web (URL Path); for a mobile fall-through (a no-suffix result
577
588
  // json) this collapses the dual-id to the android package so the combined
@@ -673,17 +684,21 @@ async function exportMatrixTarget(
673
684
  cwd: string,
674
685
  target: DeliveryTarget,
675
686
  opts: { check?: boolean; preview?: boolean; format?: MatrixFormat },
687
+ env: EnvironmentInfo,
676
688
  ): Promise<MatrixRunSummary | 'skipped' | null> {
677
689
  const paths = matrixPathsFor(cwd, target);
690
+ paths.runEnv = matrixRunEnv(cwd, target, env, paths);
678
691
 
679
692
  // A feature whose scenarios are ALL @exploration probes has nothing to deliver —
680
693
  // skip it quietly (no map required) instead of demanding a reviewed grouping for
681
694
  // probes that are, by contract, outside the official suite.
682
695
  {
683
696
  const featureMeta = parseFeatureMetadata(paths.featureFile);
684
- const deliverable = featureMeta.scenarios.filter((s) => !s.tags.some((t) => /^@exploration\b/i.test(t)));
685
- if (featureMeta.scenarios.length > 0 && deliverable.length === 0) {
686
- 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}`);
687
702
  return 'skipped';
688
703
  }
689
704
  }
@@ -703,6 +718,17 @@ async function exportMatrixTarget(
703
718
  return null;
704
719
  }
705
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
+
706
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))}`);
707
733
  {
708
734
  const mocked = model.items.flatMap((it) => it.variants.filter((v) => v.mocked));
@@ -737,7 +763,8 @@ async function exportMatrixTarget(
737
763
 
738
764
  let deliverables: Awaited<ReturnType<typeof writeMatrixDeliverables>>;
739
765
  try {
740
- deliverables = await writeMatrixDeliverables(paths, model, opts.format ?? 'xlsx');
766
+ if (priorError) throw priorError;
767
+ deliverables = await writeMatrixDeliverables(paths, model, format, prior);
741
768
  } catch (e) {
742
769
  // The existing workbook could not be read, so writing would destroy its Manual results.
743
770
  log(` ${COLOR.red}not written:${COLOR.reset} ${(e as Error).message}`);
@@ -977,9 +1004,9 @@ export function registerDeliveryCommand(program: Command): void {
977
1004
  // Same contract as the export path: an all-@exploration feature has no
978
1005
  // deliverable variants, so there is no map to approve — skip, not an error.
979
1006
  {
980
- const featureMeta = parseFeatureMetadata(paths.featureFile);
981
- if (featureMeta.scenarios.length > 0
982
- && 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)))) {
983
1010
  log(`${COLOR.gray}${paths.label}: nothing to approve — all scenario(s) are @exploration probes.${COLOR.reset}`);
984
1011
  continue;
985
1012
  }
@@ -1004,10 +1031,11 @@ export function registerDeliveryCommand(program: Command): void {
1004
1031
 
1005
1032
  const matrixSummaries: MatrixRunSummary[] = [];
1006
1033
  const notExported: string[] = [];
1034
+ const env = getEnvironment(cwd);
1007
1035
  for (const target of toExport) {
1008
1036
  const s = await exportMatrixTarget(cwd, target, {
1009
1037
  check: options.check, preview: options.preview, format: format as MatrixFormat,
1010
- });
1038
+ }, env);
1011
1039
  if (s === 'skipped') continue; // all-@exploration feature — neither exported nor a failure
1012
1040
  if (s) matrixSummaries.push(s);
1013
1041
  else notExported.push(target.isFlow ? `flow/${target.featureBaseName}` : target.featureBaseName);
@@ -39,7 +39,7 @@ function readReport(file: string, label: string): any {
39
39
  * (flows → screens → api → api flows); the same basename found in more than one
40
40
  * place is an ambiguity error, never a silent pick.
41
41
  */
42
- function resolveFeatureDir(cwd: string, name: string): string {
42
+ export function resolveFeatureDir(cwd: string, name: string): string {
43
43
  const roots = [
44
44
  path.join(cwd, 'specs', 'generated', 'flows'),
45
45
  path.join(cwd, 'specs', 'generated'),
@@ -89,6 +89,12 @@ export function runResultsMerge(baseArg: string | undefined, newArg: string | un
89
89
  if (!baseFile || path.dirname(baseFile) !== dir) {
90
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).`);
91
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
+ }
92
98
  // Ghost/foreign-title checks need the CURRENT spec titles.
93
99
  const specFile = path.join(dir, `${opts.screen}.spec.ts`);
94
100
  try { specTitles = parseSpecFile(specFile).tests.map((t) => t.testTitle); } catch { specTitles = null; }
@@ -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
+ }
package/src/cli/index.ts CHANGED
@@ -24,6 +24,7 @@ import { registerIngestCommand } from './commands/ingest';
24
24
  import { registerEvalCommand } from './commands/eval';
25
25
  import { registerManifestCommand } from './commands/manifest';
26
26
  import { registerChangedCommand } from './commands/changed';
27
+ import { registerSelectCommand } from './commands/select';
27
28
  import { registerLedgerCommand } from './commands/ledger';
28
29
  import { registerFeedbackCommand } from './commands/feedback';
29
30
  import { registerQaFeedbackCommand } from './commands/qa-feedback';
@@ -36,8 +37,11 @@ import { registerCapabilityCommand } from './commands/capability';
36
37
  import { registerFlowCheckCommand } from './commands/flow-check';
37
38
  import { registerContextCommand } from './commands/context';
38
39
  import { registerRepairCommand } from './commands/repair';
40
+ import { registerTriageCommand } from './commands/triage';
41
+ import { registerDefectCommand } from './commands/defect';
39
42
  import { registerResultsCommand } from './commands/results-merge';
40
43
  import { registerLoginCommand } from './commands/login';
44
+ import { registerViewpointCommand } from './commands/viewpoint';
41
45
  import { capabilityRegistry } from '../capabilities/registry';
42
46
  import { discoverAndRegisterCapabilities } from '../capabilities/discover';
43
47
  import { warnAssetsDrift } from '../orchestrator/assets-drift';
@@ -75,6 +79,7 @@ registerSetupMobileEnvCommand(program);
75
79
  registerGateCommand(program);
76
80
  registerManifestCommand(program);
77
81
  registerChangedCommand(program);
82
+ registerSelectCommand(program);
78
83
  registerLedgerCommand(program);
79
84
  registerFeedbackCommand(program);
80
85
  registerQaFeedbackCommand(program);
@@ -87,10 +92,13 @@ registerSetupMobileEnvCommand(program);
87
92
  registerFlowCheckCommand(program);
88
93
  registerContextCommand(program);
89
94
  registerRepairCommand(program);
95
+ registerTriageCommand(program);
96
+ registerDefectCommand(program);
90
97
  registerResultsCommand(program);
91
98
  registerIngestCommand(program);
92
99
  registerEvalCommand(program);
93
100
  registerLoginCommand(program);
101
+ registerViewpointCommand(program);
94
102
 
95
103
  // Capability-contributed CLI commands (Capability SPI): drivers own their authoring commands
96
104
  // (e.g. @sungen/driver-api → `sungen api import`). Discover, then register each.
@@ -123,12 +131,15 @@ registerSetupMobileEnvCommand(program);
123
131
  const opts = actionCommand.opts() as Record<string, unknown>;
124
132
  const unit = [opts.screen, opts.flow, opts.area].find((v) => typeof v === 'string' && v)
125
133
  ?? (typeof actionCommand.args?.[0] === 'string' ? actionCommand.args[0] : undefined);
126
- handBack = { unit: unit as string | undefined, json: opts.json === true };
134
+ // `--grep` is machine output too: callers capture it with `$(…)`, and a footer on stdout
135
+ // became part of the regex — the last alternative of `changed --grep` silently matched
136
+ // nothing, so `--change-only` skipped its last scenario (#836).
137
+ handBack = { unit: unit as string | undefined, json: opts.json === true || opts.grep === true };
127
138
  });
128
139
  process.on('exit', (code) => {
129
140
  if (!handBack || code === 1) return;
130
141
  const line = `\nNext: sungen next${handBack.unit ? ` ${handBack.unit}` : ''}`;
131
- // stderr under --json so the hand-back never lands in the parsed payload.
142
+ // stderr under --json / --grep so the hand-back never lands in the captured payload.
132
143
  if (handBack.json) process.stderr.write(`${line}\n`);
133
144
  else process.stdout.write(`${line}\n`);
134
145
  });