@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
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Which create-test mode a unit's test-viewpoint.md calls for — the table in create-test.md,
3
+ * computed so the agent reads it from `sungen viewpoint scan` instead of inferring it.
4
+ *
5
+ * mode 2 (AI drafts / re-reviews its draft): file missing · untouched scaffold · first line
6
+ * `drafted-by: ai` · VP rows exist but none has a Kỳ vọng
7
+ * mode 1 (review only, never edit): anything else — a QA-written file, with or without VP ids
8
+ *
9
+ * "Untouched scaffold" is checked on RAW lines against the template the scaffold is written from:
10
+ * apart from the first `# ` line (the title), every non-blank line must be a template line —
11
+ * comments included, so a QA note inside `<!-- -->` or under its own `# ` heading makes the file
12
+ * the QA's (owner decision 2026-10-14: a QA file is never overwritten). Every doubt falls to
13
+ * mode 1: a scaffold from an older sungen (other template lines) or an unreadable template.
14
+ */
15
+ import { isAiDraft } from './viewpoint-draft-header';
16
+ import { viewpointScaffold } from '../orchestrator/viewpoint-scaffold';
17
+
18
+ export type ViewpointMode = 1 | 2;
19
+
20
+ /** Trimmed non-blank lines without the title (the first `# ` line), BOM and CRLF ignored. */
21
+ function bodyLines(text: string): string[] {
22
+ const lines = text.replace(/^/, '').replace(/\r\n?/g, '\n').split('\n').map((l) => l.trim()).filter(Boolean);
23
+ const title = lines.findIndex((l) => l.startsWith('# '));
24
+ if (title >= 0) lines.splice(title, 1);
25
+ return lines;
26
+ }
27
+
28
+ let templateLines: Set<string> | null = null;
29
+ function isUntouchedScaffold(raw: string): boolean {
30
+ if (!templateLines) {
31
+ try {
32
+ templateLines = new Set([false, true].flatMap((mobile) => bodyLines(viewpointScaffold('', mobile))));
33
+ } catch {
34
+ return false; // no template to compare with → treat the file as the QA's
35
+ }
36
+ }
37
+ const known = templateLines;
38
+ return bodyLines(raw).every((l) => known.has(l));
39
+ }
40
+
41
+ /** `raw` is the file as written (null when missing); `rows` are its VP rows from the scan. */
42
+ export function viewpointMode(raw: string | null, rows: { hasExpected: boolean }[]): { mode: ViewpointMode; reason: string } {
43
+ if (raw === null) return { mode: 2, reason: 'file missing' };
44
+ if (isAiDraft(raw)) return { mode: 2, reason: 'drafted-by: ai (re-review)' };
45
+ if (rows.length > 0) {
46
+ return rows.some((r) => r.hasExpected) ? { mode: 1, reason: 'QA-written' } : { mode: 2, reason: 'VP rows without Kỳ vọng' };
47
+ }
48
+ return isUntouchedScaffold(raw) ? { mode: 2, reason: 'untouched scaffold' } : { mode: 1, reason: 'QA-written, no VP ids' };
49
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * One reading of a priority — a viewpoint row's cell or a scenario's tag.
3
+ *
4
+ * The overview parser, the ledger's uniform-priority check and PRIORITY-CONTRADICTED each read a
5
+ * priority their own way (`/high/i` substrings, raw words, a one-off medium→normal), so the same
6
+ * row could be `Medium` to one and `Unknown` to another — `Med`, which QA writes, was `Unknown`.
7
+ * The vocabulary is `high | normal | low`; `critical`, `medium` and `med` are read as aliases.
8
+ */
9
+ export type PriorityLevel = 'high' | 'normal' | 'low' | 'deferred' | 'skip' | 'unknown';
10
+
11
+ const WORD: Record<string, PriorityLevel> = {
12
+ critical: 'high', high: 'high',
13
+ medium: 'normal', med: 'normal', normal: 'normal',
14
+ low: 'low', deferred: 'deferred',
15
+ };
16
+ /** "Declared not applicable" — one level, so every consumer checks one word. */
17
+ const NOT_APPLICABLE = /^(?:skip(?:ped)?|n\/a|none|not applicable)$/;
18
+
19
+ /**
20
+ * A priority cell or tag → its level. The whole cell being a word is the clean case; otherwise the
21
+ * overview has always read prose by SUBSTRING in precedence order (`High — complex form`,
22
+ * `Medium-High`), and keeps doing so. The substring words stay unbounded for `high|medium|low`
23
+ * (existing behaviour); the newer `critical|normal|med` need word boundaries (`Medical` is not `Med`).
24
+ */
25
+ export function parsePriority(cell: string): PriorityLevel {
26
+ const w = cell.replace(/[*_`]/g, '').replace(/\.+$/, '').trim().toLowerCase();
27
+ if (WORD[w]) return WORD[w];
28
+ if (NOT_APPLICABLE.test(w)) return 'skip';
29
+ if (/high|\bcritical\b/.test(w)) return 'high';
30
+ if (/medium|\bnormal\b|\bmed\b/.test(w)) return 'normal';
31
+ if (/low/.test(w)) return 'low';
32
+ return 'unknown';
33
+ }
34
+
35
+ /** The public `ViewpointEntry.priority` spelling of a level — the exported type is unchanged. */
36
+ export function toEntryPriority(level: PriorityLevel): 'High' | 'Medium' | 'Low' | 'Unknown' {
37
+ return level === 'high' ? 'High' : level === 'normal' ? 'Medium' : level === 'low' ? 'Low' : 'Unknown';
38
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * VIEWPOINT-REVIEW-OPEN (info) — the audit's one line about `requirements/viewpoint-review.md`.
3
+ *
4
+ * create-test reviews test-viewpoint.md with the sungen-viewpoint checklist and writes each point a
5
+ * QA should look at as a table row whose last cell is `[ ]`; the QA ticks it `[x]` or deletes the
6
+ * row. While rows stay unticked the audit says how many, once. Advisory only: no score, no gate, no
7
+ * baseline effect. A missing or unreadable file says nothing — the review is optional input.
8
+ */
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ import { stripHtmlComments } from './read-text';
12
+
13
+ export const VIEWPOINT_REVIEW_FILE = 'viewpoint-review.md';
14
+
15
+ const SEPARATOR = /^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)*\|?\s*$/;
16
+ /** A table separator, not a `---` rule under a row. */
17
+ const isSeparator = (line: string): boolean => SEPARATOR.test(line) && line.includes('|');
18
+ const UNTICKED = /^\[\s?\]$/;
19
+
20
+ /** The last cell of a markdown table row, or null for a line that is not one. */
21
+ function lastCell(line: string): string | null {
22
+ const t = line.trim();
23
+ if (!t.startsWith('|') || !t.endsWith('|') || t.length < 2) return null;
24
+ const cells = t.slice(1, -1).split(/(?<!\\)\|/);
25
+ return cells[cells.length - 1].trim();
26
+ }
27
+
28
+ /** Body rows whose last cell is `[ ]`. The header row (`… | [ ] |` above `|---|`) is not a point. */
29
+ export function openReviewPoints(text: string): number {
30
+ const lines = stripHtmlComments(text.replace(/\r\n?/g, '\n')).split('\n');
31
+ let open = 0;
32
+ for (let i = 0; i < lines.length; i++) {
33
+ if (isSeparator(lines[i]) || isSeparator(lines[i + 1] ?? '')) continue;
34
+ const last = lastCell(lines[i]);
35
+ if (last !== null && UNTICKED.test(last)) open++;
36
+ }
37
+ return open;
38
+ }
39
+
40
+ /** The info finding for one unit's `requirements/` directory; empty when nothing is open. */
41
+ export function viewpointReviewOpenFindings(requirementsDir: string): string[] {
42
+ let text: string;
43
+ try {
44
+ text = fs.readFileSync(path.join(requirementsDir, VIEWPOINT_REVIEW_FILE), 'utf-8');
45
+ } catch {
46
+ return [];
47
+ }
48
+ const open = openReviewPoints(text);
49
+ if (open === 0) return [];
50
+ return [`VIEWPOINT-REVIEW-OPEN (info): ${open} open point(s) in requirements/${VIEWPOINT_REVIEW_FILE} — the checklist review of test-viewpoint.md is waiting for QA. QA: fix the viewpoint where you agree, then tick the row \`[x]\` or delete it. Not scored; an agent does not tick rows for the QA.`];
51
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Building blocks of `sungen viewpoint scan` (viewpoint-scan.ts): the markdown table walker, the
3
+ * item-reference reader and the item universe. Pure, read-only, format-tolerant by design — a
4
+ * QA-written file is never judged here, only its ids and item numbers are read.
5
+ */
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ import { readTextFile, stripHtmlComments } from './read-text';
9
+ import { parseUiInventory } from './ui-inventory';
10
+ import { parseSpecClauses } from './spec-coverage';
11
+
12
+ export interface TableRow {
13
+ /** 1-based line in the (comment-stripped, newline-preserving) text. */
14
+ line: number;
15
+ raw: string;
16
+ /** Cells with `**`, backticks and edge spaces removed. */
17
+ cells: string[];
18
+ /** The table's header cells; null for a table with no separator line. */
19
+ header: string[] | null;
20
+ /** Text of every heading enclosing the row (outermost first) — where a `# MANUAL/ENV` mark may sit. */
21
+ section: string;
22
+ }
23
+
24
+ const SEPARATOR = /^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)*\|?\s*$/;
25
+ const splitCells = (line: string): string[] => line.trim().replace(/^\|/, '').replace(/\|$/, '')
26
+ .split(/(?<!\\)\|/).map((c) => c.replace(/\*\*|`/g, '').trim());
27
+
28
+ /** Every body row of every markdown table, with its header and enclosing headings. */
29
+ export function tableRows(text: string): TableRow[] {
30
+ const lines = text.split('\n');
31
+ const out: TableRow[] = [];
32
+ const headings: string[] = []; // index = level − 1
33
+ let header: string[] | null = null;
34
+ for (let i = 0; i < lines.length; i++) {
35
+ const raw = lines[i];
36
+ const h = raw.match(/^(#{1,6})\s+(.*)$/);
37
+ if (h) { headings.length = h[1].length - 1; headings[h[1].length - 1] = h[2]; header = null; continue; }
38
+ if (!raw.includes('|')) { header = null; continue; }
39
+ if (SEPARATOR.test(raw)) continue;
40
+ if (i + 1 < lines.length && SEPARATOR.test(lines[i + 1])) { header = splitCells(raw); continue; }
41
+ out.push({ line: i + 1, raw, cells: splitCells(raw), header, section: headings.join(' ') });
42
+ }
43
+ return out;
44
+ }
45
+
46
+ const NUM = String.raw`(?<!\d)\d{1,3}(?:\.\d{1,3})*(?![\d])`;
47
+ const LIST = new RegExp(`(?:\\bitems?|項目|(?<![\\w-])UI[\\s-]*#?|#)\\s*(${NUM}(?:\\s*(?:[-–—~〜~,;&/]|(?<![\\p{L}\\p{N}])(?:và|and)(?![\\p{L}\\p{N}]))\\s*${NUM})*)`, 'giu');
48
+
49
+ /** `009` → `9`, `4.03` → `4.3`; null when the text is not a plain (dotted) number. */
50
+ export function normRef(s: string): string | null {
51
+ const t = s.replace(/[[\]§#]/g, '').trim();
52
+ return /^\d+(\.\d+)*$/.test(t) ? t.split('.').map((p) => String(Number(p))).join('.') : null;
53
+ }
54
+
55
+ /**
56
+ * The refs strictly between `a` and `b` (normalised): `4`–`6` → `5`, `5.4`–`5.8` → `5.5 … 5.7`.
57
+ * Only the last level steps, so both ends need the same prefix; `5.4–6.2` or `3–5.2` cannot be
58
+ * enumerated without the item list and give nothing.
59
+ */
60
+ function between(a: string, b: string): string[] {
61
+ const pa = a.split('.');
62
+ const pb = b.split('.');
63
+ if (pa.length !== pb.length || pa.slice(0, -1).join('.') !== pb.slice(0, -1).join('.')) return [];
64
+ const lo = Number(pa[pa.length - 1]);
65
+ const hi = Number(pb[pb.length - 1]);
66
+ if (!(hi > lo && hi - lo < 200)) return [];
67
+ const prefix = pa.slice(0, -1).map((p) => `${p}.`).join('');
68
+ return Array.from({ length: hi - lo - 1 }, (_, k) => `${prefix}${lo + 1 + k}`);
69
+ }
70
+
71
+ /**
72
+ * Item numbers a cell cites: `item 9`, `item 2, 3, 7`, `item 4–6`, `item 5.4–5.8`, `項目 4.3〜4.6`, `UI #9`,
73
+ * `UI-009`, `[9]`, `§4.3.2`. Prose (`Auth guard`), message codes (`M02`) and question ids
74
+ * (`Q-01`) are not refs.
75
+ */
76
+ export function extractRefs(text: string): string[] {
77
+ const refs = new Set<string>();
78
+ const add = (s: string): void => { const r = normRef(s); if (r) refs.add(r); };
79
+ for (const m of text.matchAll(LIST)) {
80
+ let prev: string | null = null;
81
+ let range = false;
82
+ for (const tok of m[1].matchAll(new RegExp(`(${NUM})|([-–—~〜~])`, 'g'))) {
83
+ if (tok[2]) { range = prev !== null; continue; }
84
+ const n = normRef(tok[1]);
85
+ if (!n) continue;
86
+ if (range && prev) for (const r of between(prev, n)) refs.add(r);
87
+ refs.add(n); prev = n; range = false;
88
+ }
89
+ }
90
+ for (const m of text.matchAll(/\[(\d{1,3})\]/g)) add(m[1]);
91
+ for (const m of text.matchAll(/§\s*(\d{1,3}(?:\.\d{1,3})*)/g)) add(m[1]);
92
+ return [...refs];
93
+ }
94
+
95
+ export type ItemSource = 'spec-ids' | 'spec-table' | 'spec-ids+spec-table' | 'ui-inventory' | 'none';
96
+ export interface UniverseItem { ref: string; name: string }
97
+ /** Every list a citation could point at, each in its own notation. */
98
+ export interface ItemUniverses { specIds: UniverseItem[]; specTable: UniverseItem[]; inventory: UniverseItem[] }
99
+
100
+ const NO_COL = /^(no\.?|#|stt|№|番号)$/i;
101
+ const TYPE_COL = /種別|\b(type|kind)\b|loại/i;
102
+ const NAME_COL = /item|name|tên|項目|名|field|label|trường|thành phần|component/i;
103
+
104
+ /**
105
+ * The unit's lists, by where each id is DECLARED: requirement ids spec.md declares (`**FR-BC-03**:`),
106
+ * the spec's item table (`No` + name + type columns), and ui-inventory ids (`UI-NNN`). An id's shape
107
+ * says nothing about which list it belongs to — `item 9` on a mockup and `UI-009` in the inventory
108
+ * number different things.
109
+ */
110
+ export function itemUniverses(reqDir: string): ItemUniverses {
111
+ const read = (f: string): string | null => {
112
+ const p = path.join(reqDir, f);
113
+ return fs.existsSync(p) ? stripHtmlComments(readTextFile(p)) : null;
114
+ };
115
+ const out: ItemUniverses = { specIds: [], specTable: [], inventory: [] };
116
+ const spec = read('spec.md');
117
+ if (spec) {
118
+ out.specIds = parseSpecClauses(path.join(reqDir, 'spec.md')).frs.map((f) => ({ ref: f.id, name: f.text.slice(0, 80) }));
119
+ for (const row of tableRows(spec)) {
120
+ const h = row.header;
121
+ const no = h?.findIndex((c) => NO_COL.test(c)) ?? -1;
122
+ const type = h?.findIndex((c) => TYPE_COL.test(c)) ?? -1;
123
+ const name = h?.findIndex((c, i) => i !== no && i !== type && NAME_COL.test(c)) ?? -1;
124
+ if (no < 0 || type < 0 || name < 0) continue;
125
+ const ref = normRef(row.cells[no] ?? '');
126
+ if (ref) out.specTable.push({ ref, name: row.cells[name] ?? '' });
127
+ }
128
+ }
129
+ const inv = read('ui-inventory.md');
130
+ if (inv) {
131
+ out.inventory = parseUiInventory(inv).items.flatMap((i) => {
132
+ const ref = normRef(i.id.replace(/^UI-/i, ''));
133
+ return ref ? [{ ref, name: i.item }] : [];
134
+ });
135
+ }
136
+ return out;
137
+ }
138
+
139
+ /**
140
+ * The list(s) the viewpoint actually cites — a list nobody cites says nothing about gaps. The spec's
141
+ * ids and its item table, each when cited (both when both are: the notations cannot collide, and
142
+ * dropping one would under-report); the table also when nothing is cited at all (then every item is
143
+ * uncited, which is true); the inventory only when the viewpoint writes `UI-NNN`; else none.
144
+ */
145
+ export function citedUniverse(u: ItemUniverses, cited: ReadonlySet<string>, writesInventoryIds: boolean): { source: ItemSource; items: UniverseItem[] } {
146
+ const hits = (items: UniverseItem[]): boolean => items.some((i) => cited.has(i.ref));
147
+ const ids = hits(u.specIds);
148
+ const table = u.specTable.length > 0 && (cited.size === 0 || hits(u.specTable));
149
+ if (ids && table) return { source: 'spec-ids+spec-table', items: [...u.specIds, ...u.specTable] };
150
+ if (ids) return { source: 'spec-ids', items: u.specIds };
151
+ if (table) return { source: 'spec-table', items: u.specTable };
152
+ if (writesInventoryIds && hits(u.inventory)) return { source: 'ui-inventory', items: u.inventory };
153
+ return { source: 'none', items: [] };
154
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * `sungen viewpoint scan` — the code half of reviewing a QA-written test-viewpoint.md.
3
+ *
4
+ * It does set arithmetic on ids and nothing else: which VP rows exist, what each cites, which spec
5
+ * items nobody cites, which ids no scenario carries. It never judges meaning, never edits the file,
6
+ * and feeds no audit finding, score or baseline.
7
+ *
8
+ * A VP row is a markdown table row whose FIRST cell is a full id (`VP-UI-001`, `FL-LOGIN-3`).
9
+ * Priority-table rows (`VP-LOGIN (Auth)`), bullets and appendix text are not rows. This is
10
+ * deliberately NOT `parseViewpointItems` (viewpoint-ledger.ts), which also reads id-less rows.
11
+ */
12
+ import * as fs from 'fs';
13
+ import * as path from 'path';
14
+ import { readViewpointText, readTextFile } from './read-text';
15
+ import { featureFilesFor } from './unit-paths';
16
+ import { tableRows, extractRefs, itemUniverses, citedUniverse, ItemSource } from './viewpoint-scan-refs';
17
+ import { viewpointMode, ViewpointMode } from './viewpoint-mode';
18
+
19
+ export interface VpRow {
20
+ id: string;
21
+ line: number;
22
+ /** Item numbers the row cites (normalised: `009` → `9`), and spec-declared ids it cites (`FR-BC-03`). */
23
+ refs: string[];
24
+ /** `# MANUAL/ENV` or `@manual` on the row or an enclosing heading. */
25
+ manual: boolean;
26
+ /** The Kỳ vọng / Expected cell has text (best effort when the table has no such header). */
27
+ hasExpected: boolean;
28
+ }
29
+
30
+ export interface ViewpointScan {
31
+ /** The create-test mode this file calls for (viewpoint-mode.ts): 2 = AI drafts, 1 = review only. */
32
+ mode: ViewpointMode;
33
+ modeReason: string;
34
+ vpRows: VpRow[];
35
+ /** Distinct VP ids among `vpRows` — a reused id is several rows but one id. */
36
+ distinctIds: number;
37
+ rowsWithoutExpected: string[];
38
+ manualRows: string[];
39
+ itemSource: ItemSource;
40
+ /** Spec/inventory items no VP row cites — a hint for the review, not a verdict. */
41
+ uncitedItems: { ref: string; name: string }[];
42
+ /** VP ids no feature file mentions; null when the unit has no feature file yet. */
43
+ withoutScenario: string[] | null;
44
+ /** Why `vpRows` is empty, else null. */
45
+ note: string | null;
46
+ }
47
+
48
+ const VP_ID = /^(VP|FL)-[A-Z][A-Z0-9]*(-[A-Z0-9]+)*-\d+$/;
49
+ const MANUAL = /\bMANUAL\/ENV\b|@manual\b/i;
50
+ const EXPECTED_COL = /kỳ vọng|kết quả mong đợi|expected|期待/i;
51
+ /** Whole-word match (Unicode-aware: `\b` fails on `ồ`) so `Resource` / `Datasource` are not a source column. */
52
+ const SOURCE_COL = /(?<![\p{L}\p{N}])(?:nguồn|source|tham chiếu|ref|出典|根拠)(?![\p{L}\p{N}])/iu;
53
+ const PRIORITY = /^(critical|high|med(?:ium)?|low|cao|trung bình|thấp)$/i;
54
+ const BLANK = /^[\s\-–—…]*$/;
55
+ const SPEC_ID = /(?<![\w-])[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)*-\d+[A-Z]?(?![\w-])/gi;
56
+ export const NO_VP_IDS_NOTE = 'file has no VP ids; review falls back to AI-only';
57
+
58
+ /**
59
+ * VP rows of one viewpoint text (already comment-stripped, as `readViewpointText` returns it).
60
+ * `specIds`: the requirement ids spec.md declares — only those count as spec citations.
61
+ */
62
+ export function scanViewpointText(text: string, specIds: ReadonlySet<string> = new Set()): VpRow[] {
63
+ const rows: VpRow[] = [];
64
+ for (const r of tableRows(text)) {
65
+ const id = r.cells[0];
66
+ if (!VP_ID.test(id)) continue;
67
+ const exp = r.header?.findIndex((c) => EXPECTED_COL.test(c)) ?? -1;
68
+ const src = r.header?.findIndex((c) => SOURCE_COL.test(c)) ?? -1;
69
+ const cites = src > 0 ? r.cells[src] ?? '' : r.cells.slice(1).join(' | ');
70
+ rows.push({
71
+ id,
72
+ line: r.line,
73
+ refs: [...extractRefs(cites), ...[...cites.matchAll(SPEC_ID)].map((m) => m[0].toUpperCase()).filter((id) => specIds.has(id))],
74
+ manual: MANUAL.test(r.raw) || MANUAL.test(r.section),
75
+ // Without a Kỳ vọng header: any later cell with text that is neither a priority word nor a ref.
76
+ hasExpected: exp > 0
77
+ ? !BLANK.test(r.cells[exp] ?? '')
78
+ : r.cells.slice(2).some((c) => !BLANK.test(c) && !PRIORITY.test(c) && extractRefs(c).length === 0),
79
+ });
80
+ }
81
+ return rows;
82
+ }
83
+
84
+ /** Scan one unit directory (`qa/screens/<n>`, `qa/flows/<n>`, …); `unit` names its feature files. */
85
+ export function scanViewpointUnit(unitDir: string, unit: string): ViewpointScan {
86
+ const reqDir = path.join(unitDir, 'requirements');
87
+ const vpPath = path.join(reqDir, 'test-viewpoint.md');
88
+ const vpText = readViewpointText(vpPath);
89
+ const universes = itemUniverses(reqDir);
90
+ const vpRows = scanViewpointText(vpText, new Set(universes.specIds.map((i) => i.ref)));
91
+ const { mode, reason } = viewpointMode(fs.existsSync(vpPath) ? readTextFile(vpPath) : null, vpRows);
92
+ const cited = new Set(vpRows.flatMap((r) => r.refs));
93
+ const { source, items } = citedUniverse(universes, cited, /(?<![\w-])UI-\d{1,3}\b/i.test(vpText));
94
+ const features = featureFilesFor(unitDir, unit).filter((f) => fs.existsSync(f));
95
+ // Same rule as the ledger (viewpoint-ledger.ts): an id is carried when the feature text contains it.
96
+ const featureText = features.map((f) => readTextFile(f).toLowerCase()).join('\n');
97
+
98
+ return {
99
+ mode,
100
+ modeReason: reason,
101
+ vpRows,
102
+ distinctIds: new Set(vpRows.map((r) => r.id)).size,
103
+ rowsWithoutExpected: vpRows.filter((r) => !r.hasExpected).map((r) => r.id),
104
+ manualRows: vpRows.filter((r) => r.manual).map((r) => r.id),
105
+ itemSource: source,
106
+ uncitedItems: items.filter((i) => !cited.has(i.ref)),
107
+ withoutScenario: features.length === 0 ? null : vpRows.filter((r) => !featureText.includes(r.id.toLowerCase())).map((r) => r.id),
108
+ note: vpRows.length > 0 ? null
109
+ : fs.existsSync(vpPath) ? NO_VP_IDS_NOTE : 'requirements/test-viewpoint.md is missing',
110
+ };
111
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Write a file so that a crash mid-write can never leave a truncated one behind: write a sibling
3
+ * tmp, then rename. Same directory = same device, so the rename is atomic; a concurrent writer is
4
+ * last-rename-wins. On failure the tmp is removed and the error rethrown — the caller still learns.
5
+ *
6
+ * `results merge` and `results sanitize` each grew this block inline, for the same reason and with
7
+ * the same comment. The third caller (`ai-skills/writer.ts`) did not, and its truncated manifest is
8
+ * precisely the corrupt input that #782's readers then absorbed in silence — so the two halves of
9
+ * that defect are one file apart.
10
+ */
11
+ import * as fs from 'fs';
12
+
13
+ export function writeFileAtomic(file: string, data: string): void {
14
+ const tmp = `${file}.tmp-${process.pid}`;
15
+ try {
16
+ fs.writeFileSync(tmp, data);
17
+ fs.renameSync(tmp, file);
18
+ } catch (err) {
19
+ try { fs.rmSync(tmp, { force: true }); } catch { /* best-effort cleanup — the real error is below */ }
20
+ throw err;
21
+ }
22
+ }
@@ -16,6 +16,8 @@ import * as path from 'path';
16
16
  import * as crypto from 'crypto';
17
17
  import { BuiltFile } from './build';
18
18
  import { AdapterKey } from './adapters';
19
+ import { readJsonConfig, warnUnreadableConfig } from '../../harness/config-file';
20
+ import { writeFileAtomic } from '../../harness/write-atomic';
19
21
 
20
22
  export interface ApplyOptions { force?: boolean; dryRun?: boolean; version: string }
21
23
  export interface ApplyReport {
@@ -42,10 +44,21 @@ function adapterOf(relPath: string): AdapterKey | null {
42
44
  return null;
43
45
  }
44
46
 
47
+ /**
48
+ * The manifest is what makes orphan cleanup possible: it is the only record of which files a
49
+ * PREVIOUS version managed. An unreadable one used to read as an empty one — so cleanup found
50
+ * nothing to remove, and the stale files it failed to remove are absent from the NEW manifest too.
51
+ * No later run can ever find them again. Say it, then carry on with what can still be done.
52
+ */
45
53
  function readManifest(cwd: string): Manifest {
46
54
  const p = path.join(cwd, MANIFEST_REL);
47
- if (!fs.existsSync(p)) return { version: '', managed: {} };
48
- try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return { version: '', managed: {} }; }
55
+ const read = readJsonConfig<Manifest>(p);
56
+ if (read.state === 'unreadable') {
57
+ warnUnreadableConfig(p, read.error,
58
+ 'orphan cleanup is skipped this run, so files managed by an older version stay in place');
59
+ return { version: '', managed: {} };
60
+ }
61
+ return read.state === 'ok' ? read.value : { version: '', managed: {} };
49
62
  }
50
63
 
51
64
  export function applyOutputs(cwd: string, outputs: BuiltFile[], opts: ApplyOptions): ApplyReport {
@@ -121,7 +134,9 @@ export function applyOutputs(cwd: string, outputs: BuiltFile[], opts: ApplyOptio
121
134
  Object.assign(mergedManaged, newManaged);
122
135
  const mPath = path.join(cwd, MANIFEST_REL);
123
136
  fs.mkdirSync(path.dirname(mPath), { recursive: true });
124
- fs.writeFileSync(mPath, JSON.stringify({ version: opts.version, managed: mergedManaged }, null, 2));
137
+ // Atomic: an interrupted `sungen init`/`update` must not leave a half-written manifest, because
138
+ // the reader above can only report that damage — it cannot undo it.
139
+ writeFileAtomic(mPath, JSON.stringify({ version: opts.version, managed: mergedManaged }, null, 2));
125
140
  }
126
141
  return r;
127
142
  }
@@ -1,6 +1,7 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { getPackageVersion } from '../exporters/package-info';
4
+ import { readJsonConfig, warnUnreadableConfig } from '../harness/config-file';
4
5
 
5
6
  /**
6
7
  * Are this project's AI assets (commands + skills under `.claude/` etc.) the ones
@@ -27,12 +28,16 @@ export function checkAssetsDrift(cwd: string): AssetsDrift | null {
27
28
  const manifestPath = path.join(cwd, '.sungen', 'manifest.json');
28
29
  if (!fs.existsSync(manifestPath)) return null; // not a sungen project (yet)
29
30
 
30
- let manifest: { version?: string; managed?: Record<string, string> };
31
- try {
32
- manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
33
- } catch {
34
- return null; // unreadable → say nothing
31
+ const read = readJsonConfig<{ version?: string; managed?: Record<string, string> }>(manifestPath);
32
+ if (read.state === 'unreadable') {
33
+ // This function IS the drift alarm. Staying silent on a broken manifest disables the only
34
+ // warning that a project's .claude/ assets have gone stale — and a truncated manifest is the
35
+ // likeliest way to get one (it is written non-atomically by `ai-skills/writer.ts`).
36
+ warnUnreadableConfig(manifestPath, read.error, 'stale-assets drift cannot be checked — re-run `sungen init` to rewrite it');
37
+ return null;
35
38
  }
39
+ if (read.state === 'absent') return null;
40
+ const manifest = read.value;
36
41
  const recordedVersion = manifest.version ?? '';
37
42
  const runningVersion = getPackageVersion();
38
43
  const managed = manifest.managed ?? {};
@@ -8,6 +8,7 @@ import * as fs from 'fs';
8
8
  import * as path from 'path';
9
9
  import { readCapabilities } from '../harness/capability';
10
10
  import { testDataHeader, ensureTestDataGuideFiles } from './test-data-guide';
11
+ import { viewpointScaffold } from './viewpoint-scaffold';
11
12
 
12
13
  export interface FlowOptions {
13
14
  name: string;
@@ -143,7 +144,7 @@ export class FlowManager {
143
144
  fs.writeFileSync(specPath, this.generateSpecTemplate(options, flowName, isMobile), 'utf-8');
144
145
 
145
146
  const viewpointPath = path.join(requirementsDir, 'test-viewpoint.md');
146
- fs.writeFileSync(viewpointPath, this.generateViewpointTemplate(options), 'utf-8');
147
+ fs.writeFileSync(viewpointPath, viewpointScaffold(`${options.name} Flow`, isMobile), 'utf-8');
147
148
 
148
149
  console.log(`Created files:`);
149
150
  console.log(` ${path.relative(this.cwd, featurePath)}`);
@@ -217,7 +218,7 @@ export class FlowManager {
217
218
  restored.push(file);
218
219
  };
219
220
  ensure(path.join(paths.requirementsDir, 'spec.md'), () => this.generateSpecTemplate(options, flowName, isMobile));
220
- ensure(path.join(paths.requirementsDir, 'test-viewpoint.md'), () => this.generateViewpointTemplate(options));
221
+ ensure(path.join(paths.requirementsDir, 'test-viewpoint.md'), () => viewpointScaffold(`${options.name} Flow`, isMobile));
221
222
  ensure(paths.featurePath, () => this.generateFeatureTemplate(options, flowName, isMobile));
222
223
  return restored;
223
224
  }
@@ -287,57 +288,6 @@ export class FlowManager {
287
288
  `;
288
289
  }
289
290
 
290
- private generateViewpointTemplate(options: FlowOptions): string {
291
- return [
292
- `# ${options.name} Flow — Test Viewpoints`,
293
- '',
294
- '## Edge Cases',
295
- '',
296
- '<!-- Sample — replace with actual edge cases for this flow:',
297
- '- Network drops between screen transitions — should resume or show error?',
298
- '- Session expires mid-flow — should redirect to login and preserve progress?',
299
- '- Browser back button at step 3 — should return to step 2 with data intact?',
300
- '- Complete flow twice rapidly — should prevent duplicate submissions',
301
- '-->',
302
- '',
303
- '## Known Issues',
304
- '',
305
- '<!-- Sample — replace with actual known issues:',
306
- '- [BUG-001] Form data lost if user navigates away and returns',
307
- '- [BUG-002] Confirmation page shows stale data on slow connections',
308
- '-->',
309
- '',
310
- '## Design Decisions',
311
- '',
312
- '<!-- Sample — replace with actual design decisions:',
313
- '- Flow requires completing all steps in order (no skip)',
314
- '- Draft is auto-saved at each step transition',
315
- '- Cancel at any step discards the entire flow',
316
- '-->',
317
- '',
318
- '## Cross-Screen Concerns',
319
- '',
320
- '<!-- Sample — replace with actual cross-screen concerns:',
321
- '- Auth role must remain consistent across all screens',
322
- '- Data entered in step 1 must be visible in confirmation (step 3)',
323
- '- Error in step 2 should allow returning to step 1 without re-entering data',
324
- '-->',
325
- '',
326
- '## Priority Viewpoints',
327
- '',
328
- '<!-- Rate importance for this flow (High / Medium / Low / Skip):',
329
- '',
330
- '| VP | Priority | Reason |',
331
- '|---|----------|--------|',
332
- '| VP-UI | Medium | Standard forms across screens |',
333
- '| VP-VAL | High | Cross-screen validation rules |',
334
- '| VP-LOGIC | High | Multi-step business logic, state transitions |',
335
- '| VP-SEC | Medium | Auth must persist, role-based access at each screen |',
336
- '-->',
337
- '',
338
- ].join('\n');
339
- }
340
-
341
291
  private generateFeatureTemplate(options: FlowOptions, flowName: string, isMobile: boolean): string {
342
292
  const description = options.description || `complete the ${options.name} flow`;
343
293
 
@@ -2,6 +2,7 @@ import { spawnSync } from 'child_process';
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
4
  import { resolveDriverVersion } from './lockstep-version';
5
+ import { ensureSecurityOverrides, reportSecurityOverrides } from './security-overrides';
5
6
 
6
7
  /**
7
8
  * Project-local sync of the sungen package family after a global `sungen update`.
@@ -143,8 +144,29 @@ export function syncProjectSungenDeps(cwd: string, core: string, dryRun: boolean
143
144
  }
144
145
  for (const s of plan.skipped) console.log(` – skipped ${s.pkg} — ${s.why}`);
145
146
 
147
+ // A project scaffolded before #764 carries no security overrides, and npm will never pick them up
148
+ // from sungen's own manifest — `sungen update` is the one pass that can put them there. Write them
149
+ // before any install below, since npm reads `overrides` when it resolves.
150
+ const overridePlan = ensureSecurityOverrides(path.join(cwd, 'package.json'), dryRun);
151
+ reportSecurityOverrides(overridePlan, dryRun);
152
+
146
153
  if (!plan.specs.length) {
147
- console.log(` ✓ already on ${core}: ${plan.found.map((f) => f.pkg).join(', ')}\n`);
154
+ console.log(` ✓ already on ${core}: ${plan.found.map((f) => f.pkg).join(', ')}`);
155
+ // Family already in lockstep, so nothing would re-resolve — but freshly written overrides only
156
+ // reach the tree through a resolution pass. Run one.
157
+ if (overridePlan?.add.length) {
158
+ if (dryRun) {
159
+ console.log(' [dry-run] would run: npm install (re-resolve under the new security overrides)\n');
160
+ return true;
161
+ }
162
+ console.log(' 📦 npm install (re-resolving under the new security overrides)');
163
+ const r = spawnSync('npm', ['install'], { cwd, stdio: 'inherit', shell: true });
164
+ if (r.status !== 0) {
165
+ console.warn('⚠️ Overrides were written to package.json but `npm install` failed — run it by hand.\n');
166
+ return false;
167
+ }
168
+ }
169
+ console.log('');
148
170
  return true;
149
171
  }
150
172