@sun-asterisk/sungen 2.7.0-beta.1 → 3.0.0-beta.72

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 (309) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/commands/add.js +3 -3
  3. package/dist/cli/commands/add.js.map +1 -1
  4. package/dist/cli/commands/audit.d.ts +3 -0
  5. package/dist/cli/commands/audit.d.ts.map +1 -0
  6. package/dist/cli/commands/audit.js +134 -0
  7. package/dist/cli/commands/audit.js.map +1 -0
  8. package/dist/cli/commands/blindspot.d.ts +3 -0
  9. package/dist/cli/commands/blindspot.d.ts.map +1 -0
  10. package/dist/cli/commands/blindspot.js +58 -0
  11. package/dist/cli/commands/blindspot.js.map +1 -0
  12. package/dist/cli/commands/capability.d.ts +3 -0
  13. package/dist/cli/commands/capability.d.ts.map +1 -0
  14. package/dist/cli/commands/capability.js +196 -0
  15. package/dist/cli/commands/capability.js.map +1 -0
  16. package/dist/cli/commands/challenge.d.ts +3 -0
  17. package/dist/cli/commands/challenge.d.ts.map +1 -0
  18. package/dist/cli/commands/challenge.js +102 -0
  19. package/dist/cli/commands/challenge.js.map +1 -0
  20. package/dist/cli/commands/feedback.d.ts +3 -0
  21. package/dist/cli/commands/feedback.d.ts.map +1 -0
  22. package/dist/cli/commands/feedback.js +72 -0
  23. package/dist/cli/commands/feedback.js.map +1 -0
  24. package/dist/cli/commands/flow-check.d.ts +3 -0
  25. package/dist/cli/commands/flow-check.d.ts.map +1 -0
  26. package/dist/cli/commands/flow-check.js +136 -0
  27. package/dist/cli/commands/flow-check.js.map +1 -0
  28. package/dist/cli/commands/generate.d.ts.map +1 -1
  29. package/dist/cli/commands/generate.js +50 -2
  30. package/dist/cli/commands/generate.js.map +1 -1
  31. package/dist/cli/commands/ledger.d.ts +3 -0
  32. package/dist/cli/commands/ledger.d.ts.map +1 -0
  33. package/dist/cli/commands/ledger.js +71 -0
  34. package/dist/cli/commands/ledger.js.map +1 -0
  35. package/dist/cli/commands/manifest.d.ts +3 -0
  36. package/dist/cli/commands/manifest.d.ts.map +1 -0
  37. package/dist/cli/commands/manifest.js +101 -0
  38. package/dist/cli/commands/manifest.js.map +1 -0
  39. package/dist/cli/commands/script-check.d.ts +3 -0
  40. package/dist/cli/commands/script-check.d.ts.map +1 -0
  41. package/dist/cli/commands/script-check.js +97 -0
  42. package/dist/cli/commands/script-check.js.map +1 -0
  43. package/dist/cli/commands/trace.d.ts +3 -0
  44. package/dist/cli/commands/trace.d.ts.map +1 -0
  45. package/dist/cli/commands/trace.js +110 -0
  46. package/dist/cli/commands/trace.js.map +1 -0
  47. package/dist/cli/commands/update.d.ts.map +1 -1
  48. package/dist/cli/commands/update.js +22 -9
  49. package/dist/cli/commands/update.js.map +1 -1
  50. package/dist/cli/index.js +20 -0
  51. package/dist/cli/index.js.map +1 -1
  52. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +1 -0
  53. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  54. package/dist/generators/test-generator/adapters/adapter-registry.d.ts +13 -0
  55. package/dist/generators/test-generator/adapters/adapter-registry.d.ts.map +1 -1
  56. package/dist/generators/test-generator/adapters/adapter-registry.js +73 -1
  57. package/dist/generators/test-generator/adapters/adapter-registry.js.map +1 -1
  58. package/dist/generators/test-generator/adapters/index.d.ts +1 -1
  59. package/dist/generators/test-generator/adapters/index.d.ts.map +1 -1
  60. package/dist/generators/test-generator/adapters/index.js +5 -1
  61. package/dist/generators/test-generator/adapters/index.js.map +1 -1
  62. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +1 -0
  63. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +7 -0
  64. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +6 -0
  65. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  66. package/dist/generators/test-generator/code-generator.js +6 -2
  67. package/dist/generators/test-generator/code-generator.js.map +1 -1
  68. package/dist/generators/test-generator/patterns/capture-patterns.d.ts +16 -0
  69. package/dist/generators/test-generator/patterns/capture-patterns.d.ts.map +1 -0
  70. package/dist/generators/test-generator/patterns/capture-patterns.js +54 -0
  71. package/dist/generators/test-generator/patterns/capture-patterns.js.map +1 -0
  72. package/dist/generators/test-generator/patterns/form-patterns.d.ts.map +1 -1
  73. package/dist/generators/test-generator/patterns/form-patterns.js +3 -1
  74. package/dist/generators/test-generator/patterns/form-patterns.js.map +1 -1
  75. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
  76. package/dist/generators/test-generator/patterns/index.js +2 -0
  77. package/dist/generators/test-generator/patterns/index.js.map +1 -1
  78. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  79. package/dist/generators/test-generator/step-mapper.js +1 -0
  80. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  81. package/dist/generators/test-generator/utils/data-resolver.d.ts +5 -0
  82. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  83. package/dist/generators/test-generator/utils/data-resolver.js +17 -0
  84. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  85. package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts.map +1 -1
  86. package/dist/generators/test-generator/utils/runtime-data-transformer.js +4 -0
  87. package/dist/generators/test-generator/utils/runtime-data-transformer.js.map +1 -1
  88. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  89. package/dist/generators/test-generator/utils/selector-resolver.js +12 -6
  90. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  91. package/dist/harness/audit.d.ts +24 -0
  92. package/dist/harness/audit.d.ts.map +1 -0
  93. package/dist/harness/audit.js +115 -0
  94. package/dist/harness/audit.js.map +1 -0
  95. package/dist/harness/blindspot.d.ts +15 -0
  96. package/dist/harness/blindspot.d.ts.map +1 -0
  97. package/dist/harness/blindspot.js +85 -0
  98. package/dist/harness/blindspot.js.map +1 -0
  99. package/dist/harness/capability-plan.d.ts +49 -0
  100. package/dist/harness/capability-plan.d.ts.map +1 -0
  101. package/dist/harness/capability-plan.js +215 -0
  102. package/dist/harness/capability-plan.js.map +1 -0
  103. package/dist/harness/capability.d.ts +23 -0
  104. package/dist/harness/capability.d.ts.map +1 -0
  105. package/dist/harness/capability.js +98 -0
  106. package/dist/harness/capability.js.map +1 -0
  107. package/dist/harness/catalog/drivers.yaml +57 -0
  108. package/dist/harness/catalog/universal-viewpoints.yaml +114 -0
  109. package/dist/harness/challenge.d.ts +21 -0
  110. package/dist/harness/challenge.d.ts.map +1 -0
  111. package/dist/harness/challenge.js +151 -0
  112. package/dist/harness/challenge.js.map +1 -0
  113. package/dist/harness/feedback.d.ts +29 -0
  114. package/dist/harness/feedback.d.ts.map +1 -0
  115. package/dist/harness/feedback.js +106 -0
  116. package/dist/harness/feedback.js.map +1 -0
  117. package/dist/harness/flow-check.d.ts +23 -0
  118. package/dist/harness/flow-check.d.ts.map +1 -0
  119. package/dist/harness/flow-check.js +132 -0
  120. package/dist/harness/flow-check.js.map +1 -0
  121. package/dist/harness/flow-plan.d.ts +23 -0
  122. package/dist/harness/flow-plan.d.ts.map +1 -0
  123. package/dist/harness/flow-plan.js +166 -0
  124. package/dist/harness/flow-plan.js.map +1 -0
  125. package/dist/harness/intent.d.ts +11 -0
  126. package/dist/harness/intent.d.ts.map +1 -0
  127. package/dist/harness/intent.js +86 -0
  128. package/dist/harness/intent.js.map +1 -0
  129. package/dist/harness/ledger.d.ts +42 -0
  130. package/dist/harness/ledger.d.ts.map +1 -0
  131. package/dist/harness/ledger.js +171 -0
  132. package/dist/harness/ledger.js.map +1 -0
  133. package/dist/harness/manifest.d.ts +42 -0
  134. package/dist/harness/manifest.d.ts.map +1 -0
  135. package/dist/harness/manifest.js +209 -0
  136. package/dist/harness/manifest.js.map +1 -0
  137. package/dist/harness/parse.d.ts +22 -0
  138. package/dist/harness/parse.d.ts.map +1 -0
  139. package/dist/harness/parse.js +163 -0
  140. package/dist/harness/parse.js.map +1 -0
  141. package/dist/harness/script-check.d.ts +39 -0
  142. package/dist/harness/script-check.d.ts.map +1 -0
  143. package/dist/harness/script-check.js +251 -0
  144. package/dist/harness/script-check.js.map +1 -0
  145. package/dist/harness/secret-scan.d.ts +8 -0
  146. package/dist/harness/secret-scan.d.ts.map +1 -0
  147. package/dist/harness/secret-scan.js +88 -0
  148. package/dist/harness/secret-scan.js.map +1 -0
  149. package/dist/harness/sensors.d.ts +88 -0
  150. package/dist/harness/sensors.d.ts.map +1 -0
  151. package/dist/harness/sensors.js +232 -0
  152. package/dist/harness/sensors.js.map +1 -0
  153. package/dist/harness/trace.d.ts +31 -0
  154. package/dist/harness/trace.d.ts.map +1 -0
  155. package/dist/harness/trace.js +173 -0
  156. package/dist/harness/trace.js.map +1 -0
  157. package/dist/orchestrator/ai-rules-updater.d.ts +1 -0
  158. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  159. package/dist/orchestrator/ai-rules-updater.js +55 -11
  160. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  161. package/dist/orchestrator/figma/spec-figma-renderer.d.ts +2 -2
  162. package/dist/orchestrator/figma/spec-figma-renderer.js +2 -2
  163. package/dist/orchestrator/figma/spec-figma-section-renderers.d.ts +1 -1
  164. package/dist/orchestrator/figma/spec-figma-section-renderers.js +1 -1
  165. package/dist/orchestrator/project-initializer.d.ts +5 -0
  166. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  167. package/dist/orchestrator/project-initializer.js +30 -6
  168. package/dist/orchestrator/project-initializer.js.map +1 -1
  169. package/dist/orchestrator/templates/ai-instructions/claude-agent-challenge.md +46 -0
  170. package/dist/orchestrator/templates/ai-instructions/claude-agent-discovery.md +32 -0
  171. package/dist/orchestrator/templates/ai-instructions/claude-agent-reviewer.md +37 -0
  172. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-flow.md +3 -3
  173. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +5 -5
  174. package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +36 -12
  175. package/dist/orchestrator/templates/ai-instructions/claude-cmd-design.md +12 -0
  176. package/dist/orchestrator/templates/ai-instructions/claude-cmd-feedback.md +36 -0
  177. package/dist/orchestrator/templates/ai-instructions/claude-cmd-review.md +27 -30
  178. package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +4 -1
  179. package/dist/orchestrator/templates/ai-instructions/claude-config.md +1 -4
  180. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-mode-figma-mcp.md +82 -0
  181. package/dist/orchestrator/templates/ai-instructions/{github-skill-sungen-figma-source.md → claude-skill-capture-mode-figma-pat.md} +14 -48
  182. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-mode-live.md +60 -0
  183. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-mode-local.md +38 -0
  184. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture.md +35 -0
  185. package/dist/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md +84 -0
  186. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +40 -1
  187. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +3 -3
  188. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +4 -4
  189. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +18 -10
  190. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-design.md +13 -0
  191. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-feedback.md +24 -0
  192. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-review.md +20 -30
  193. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +2 -1
  194. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +1 -4
  195. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-mcp.md +82 -0
  196. package/{src/orchestrator/templates/ai-instructions/claude-skill-figma-source.md → dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-pat.md} +14 -48
  197. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-live.md +60 -0
  198. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-local.md +38 -0
  199. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture.md +35 -0
  200. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +84 -0
  201. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +1 -1
  202. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +40 -1
  203. package/dist/orchestrator/templates/specs-test-data.ts +20 -6
  204. package/dist/tools/figma/figma-auth.d.ts +5 -2
  205. package/dist/tools/figma/figma-auth.d.ts.map +1 -1
  206. package/dist/tools/figma/figma-auth.js +19 -9
  207. package/dist/tools/figma/figma-auth.js.map +1 -1
  208. package/docs/orchestration-spec.md +267 -0
  209. package/package.json +12 -7
  210. package/src/cli/commands/add.ts +3 -3
  211. package/src/cli/commands/audit.ts +92 -0
  212. package/src/cli/commands/blindspot.ts +48 -0
  213. package/src/cli/commands/capability.ts +160 -0
  214. package/src/cli/commands/challenge.ts +55 -0
  215. package/src/cli/commands/feedback.ts +65 -0
  216. package/src/cli/commands/flow-check.ts +97 -0
  217. package/src/cli/commands/generate.ts +47 -2
  218. package/src/cli/commands/ledger.ts +61 -0
  219. package/src/cli/commands/manifest.ts +55 -0
  220. package/src/cli/commands/script-check.ts +50 -0
  221. package/src/cli/commands/trace.ts +60 -0
  222. package/src/cli/commands/update.ts +30 -10
  223. package/src/cli/index.ts +20 -0
  224. package/src/generators/test-generator/adapters/adapter-interface.ts +1 -0
  225. package/src/generators/test-generator/adapters/adapter-registry.ts +37 -0
  226. package/src/generators/test-generator/adapters/index.ts +4 -1
  227. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +1 -0
  228. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +7 -0
  229. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +6 -0
  230. package/src/generators/test-generator/code-generator.ts +6 -2
  231. package/src/generators/test-generator/patterns/capture-patterns.ts +59 -0
  232. package/src/generators/test-generator/patterns/form-patterns.ts +3 -1
  233. package/src/generators/test-generator/patterns/index.ts +2 -0
  234. package/src/generators/test-generator/step-mapper.ts +1 -0
  235. package/src/generators/test-generator/utils/data-resolver.ts +20 -0
  236. package/src/generators/test-generator/utils/runtime-data-transformer.ts +8 -0
  237. package/src/generators/test-generator/utils/selector-resolver.ts +13 -6
  238. package/src/harness/audit.ts +112 -0
  239. package/src/harness/blindspot.ts +51 -0
  240. package/src/harness/capability-plan.ts +180 -0
  241. package/src/harness/capability.ts +75 -0
  242. package/src/harness/catalog/drivers.yaml +57 -0
  243. package/src/harness/catalog/universal-viewpoints.yaml +114 -0
  244. package/src/harness/challenge.ts +131 -0
  245. package/src/harness/feedback.ts +84 -0
  246. package/src/harness/flow-check.ts +99 -0
  247. package/src/harness/flow-plan.ts +135 -0
  248. package/src/harness/intent.ts +58 -0
  249. package/src/harness/ledger.ts +155 -0
  250. package/src/harness/manifest.ts +173 -0
  251. package/src/harness/parse.ts +145 -0
  252. package/src/harness/script-check.ts +222 -0
  253. package/src/harness/secret-scan.ts +51 -0
  254. package/src/harness/sensors.ts +279 -0
  255. package/src/harness/trace.ts +138 -0
  256. package/src/orchestrator/ai-rules-updater.ts +57 -10
  257. package/src/orchestrator/figma/spec-figma-renderer.ts +2 -2
  258. package/src/orchestrator/figma/spec-figma-section-renderers.ts +1 -1
  259. package/src/orchestrator/project-initializer.ts +33 -7
  260. package/src/orchestrator/templates/ai-instructions/claude-agent-challenge.md +46 -0
  261. package/src/orchestrator/templates/ai-instructions/claude-agent-discovery.md +32 -0
  262. package/src/orchestrator/templates/ai-instructions/claude-agent-reviewer.md +37 -0
  263. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-flow.md +3 -3
  264. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +5 -5
  265. package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +36 -12
  266. package/src/orchestrator/templates/ai-instructions/claude-cmd-design.md +12 -0
  267. package/src/orchestrator/templates/ai-instructions/claude-cmd-feedback.md +36 -0
  268. package/src/orchestrator/templates/ai-instructions/claude-cmd-review.md +27 -30
  269. package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +4 -1
  270. package/src/orchestrator/templates/ai-instructions/claude-config.md +1 -4
  271. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-figma-mcp.md +82 -0
  272. package/{dist/orchestrator/templates/ai-instructions/copilot-skill-figma-source.md → src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-figma-pat.md} +14 -48
  273. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-live.md +60 -0
  274. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-local.md +38 -0
  275. package/src/orchestrator/templates/ai-instructions/claude-skill-capture.md +35 -0
  276. package/src/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md +84 -0
  277. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +40 -1
  278. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +3 -3
  279. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +4 -4
  280. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +18 -10
  281. package/src/orchestrator/templates/ai-instructions/copilot-cmd-design.md +13 -0
  282. package/src/orchestrator/templates/ai-instructions/copilot-cmd-feedback.md +24 -0
  283. package/src/orchestrator/templates/ai-instructions/copilot-cmd-review.md +20 -30
  284. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +2 -1
  285. package/src/orchestrator/templates/ai-instructions/copilot-config.md +1 -4
  286. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-mcp.md +82 -0
  287. package/{dist/orchestrator/templates/ai-instructions/claude-skill-figma-source.md → src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-pat.md} +14 -48
  288. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-live.md +60 -0
  289. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-local.md +38 -0
  290. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture.md +35 -0
  291. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +84 -0
  292. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +1 -1
  293. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +40 -1
  294. package/src/orchestrator/templates/specs-test-data.ts +20 -6
  295. package/src/tools/figma/figma-auth.ts +20 -9
  296. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +0 -142
  297. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +0 -112
  298. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +0 -73
  299. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +0 -142
  300. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +0 -112
  301. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +0 -73
  302. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +0 -142
  303. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +0 -112
  304. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +0 -73
  305. package/src/orchestrator/templates/ai-instructions/copilot-skill-figma-source.md +0 -151
  306. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +0 -142
  307. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +0 -112
  308. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +0 -73
  309. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-figma-source.md +0 -151
@@ -2,6 +2,9 @@ import { Command } from 'commander';
2
2
  import * as path from 'path';
3
3
  import * as fs from 'fs';
4
4
  import { CodeGenerator } from '../../generators/test-generator/code-generator';
5
+ import { adapterRegistry } from '../../generators/test-generator/adapters';
6
+ import { scanTestDataSecrets } from '../../harness/secret-scan';
7
+ import { readCapabilities, writeCapabilities, driverMeta, loadDriverCatalog } from '../../harness/capability';
5
8
 
6
9
  /**
7
10
  * Find .feature files recursively in a directory
@@ -78,7 +81,7 @@ export function registerGenerateCommand(program: Command): void {
78
81
  .option('-s, --screen <name>', 'Generate tests for a specific screen')
79
82
  .option('--flow <name>', 'Generate tests for a specific flow')
80
83
  .option('--all', 'Generate tests for all screens and flows')
81
- .option('--framework <name>', 'Test framework (default: playwright)', 'playwright')
84
+ .option('--framework <name>', 'Override the platform driver (else read from qa/capabilities.yaml)')
82
85
  .option('--inline-data', 'Hardcode test data at compile time instead of runtime loading')
83
86
  .action(async (options) => {
84
87
  try {
@@ -121,9 +124,33 @@ export function registerGenerateCommand(program: Command): void {
121
124
  // Output directory
122
125
  const outputDir = path.join(process.cwd(), 'specs', 'generated');
123
126
 
127
+ // Phase 2a — resolve the PLATFORM driver (no silent default to a runtime).
128
+ // Order: --framework → qa/capabilities.yaml → back-compat web (scaffold + notice).
129
+ const cwd = process.cwd();
130
+ let platform = options.framework as string | undefined;
131
+ if (!platform) {
132
+ const cap = readCapabilities(cwd);
133
+ if (cap.platform) {
134
+ platform = cap.platform;
135
+ } else {
136
+ platform = 'web';
137
+ writeCapabilities(cwd, { platform: 'web', enabled: ['web'] });
138
+ console.log('ℹ️ No qa/capabilities.yaml — using platform "web" (Playwright) and scaffolding it.');
139
+ console.log(' Mobile/API projects: see `sungen capability list`.\n');
140
+ }
141
+ }
142
+ // Ensure the platform adapter is available; load an external driver if needed.
143
+ if (!adapterRegistry.hasAdapter(platform)) {
144
+ const meta = driverMeta(platform);
145
+ if (!meta) {
146
+ throw new Error(`Unknown platform "${platform}". Known: ${Object.keys(loadDriverCatalog()).join(', ')}`);
147
+ }
148
+ adapterRegistry.loadDriver(meta.adapter || platform, meta.package, cwd); // throws DriverNotInstalledError if missing
149
+ }
150
+
124
151
  // Create generator and compile
125
152
  const generator = new CodeGenerator({
126
- framework: options.framework || 'playwright',
153
+ framework: platform,
127
154
  screenName: screenName || flowName,
128
155
  verbose: program.opts().verbose,
129
156
  runtimeData: !options.inlineData,
@@ -137,6 +164,24 @@ export function registerGenerateCommand(program: Command): void {
137
164
  );
138
165
 
139
166
  console.log(`\n${results.length} test file(s) generated.`);
167
+
168
+ // Security S0 — warn (never block) if test-data looks to hold a real secret.
169
+ const scanDirs: string[] = [];
170
+ if (screenName) scanDirs.push(path.join(process.cwd(), 'qa', 'screens', screenName));
171
+ else if (flowName) scanDirs.push(path.join(process.cwd(), 'qa', 'flows', flowName));
172
+ else {
173
+ for (const base of ['screens', 'flows']) {
174
+ const d = path.join(process.cwd(), 'qa', base);
175
+ if (fs.existsSync(d)) for (const n of fs.readdirSync(d)) scanDirs.push(path.join(d, n));
176
+ }
177
+ }
178
+ const secretHits = scanDirs.flatMap((d) => scanTestDataSecrets(d));
179
+ if (secretHits.length) {
180
+ console.log(`\n⚠️ Possible secret(s) in committed test-data (review — do NOT commit real credentials):`);
181
+ for (const h of secretHits.slice(0, 10)) console.log(` ${h.file}:${h.line} — ${h.reason}`);
182
+ console.log(` Move real secrets to an env overlay / CI secret; keep test-data placeholders only.`);
183
+ }
184
+
140
185
  console.log(`Next step: npx playwright test --ui\n`);
141
186
  } catch (error) {
142
187
  console.error('Error:', error instanceof Error ? error.message : error);
@@ -0,0 +1,61 @@
1
+ import { Command } from 'commander';
2
+ import { recordEvent, buildReport } from '../../harness/ledger';
3
+
4
+ export function registerLedgerCommand(program: Command): void {
5
+ const ledger = program
6
+ .command('ledger')
7
+ .description('Usage ledger: record AI resource per step + report efficiency');
8
+
9
+ ledger
10
+ .command('record')
11
+ .description('Append a step event to the ledger')
12
+ .requiredOption('-s, --screen <name>', 'Screen or flow name')
13
+ .requiredOption('--step <name>', 'Step name (discovery | viewpoint | gherkin | audit | repair:1 ...)')
14
+ .option('--run <id>', 'Run id — groups all phases of one create-test invocation (else auto-segmented by time gap)')
15
+ .option('--model <id>', 'Model id')
16
+ .option('--tokens-in <n>', 'Input tokens', (v) => parseInt(v, 10))
17
+ .option('--tokens-out <n>', 'Output tokens', (v) => parseInt(v, 10))
18
+ .option('--ms <n>', 'Duration in ms', (v) => parseInt(v, 10))
19
+ .option('--note <text>', 'Free note')
20
+ .action((o) => {
21
+ try {
22
+ recordEvent(o.screen, {
23
+ step: o.step, runId: o.run, model: o.model, tokensIn: o.tokensIn, tokensOut: o.tokensOut, ms: o.ms, note: o.note,
24
+ });
25
+ console.log(`✓ ledger: ${o.screen} · ${o.step}`);
26
+ } catch (e) {
27
+ console.error('Error:', e instanceof Error ? e.message : e);
28
+ process.exit(1);
29
+ }
30
+ });
31
+
32
+ ledger
33
+ .command('report')
34
+ .description('Summarise ledger + efficiency verdicts (pulls audit score if present)')
35
+ .requiredOption('-s, --screen <name>', 'Screen or flow name')
36
+ .option('--all-runs', 'Aggregate ALL runs (default: latest run only)')
37
+ .option('--json', 'Output raw JSON')
38
+ .action((o) => {
39
+ try {
40
+ const r = buildReport(o.screen, { allRuns: o.allRuns });
41
+ if (o.json) { console.log(JSON.stringify(r, null, 2)); return; }
42
+ const scope = r.runScope === 'all' ? `all ${r.runs} runs` : `latest run${r.runs > 1 ? ` of ${r.runs}` : ''}`;
43
+ console.log(`\n━━━ Usage Ledger: ${r.screen} (${r.events} events · ${scope}) ━━━`);
44
+ if (r.runScope === 'latest' && r.runs > 1) console.log(` (${r.runs} runs on file — use --all-runs to aggregate)`);
45
+ console.log(` total tokens: ${r.totalTokens} total time: ${(r.totalMs / 1000).toFixed(1)}s repair rounds: ${r.repairRounds}`);
46
+ if (r.totalTokens) console.log(` repair token share: ${(r.repairTokenPct * 100).toFixed(0)}%`);
47
+ if (r.tokensPerCoveredCritical != null) console.log(` tokens / covered-critical-viewpoint: ${r.tokensPerCoveredCritical}`);
48
+ if (r.tokensPerScenario != null) console.log(` tokens / scenario: ${r.tokensPerScenario}`);
49
+ console.log(' by step:');
50
+ for (const [step, v] of Object.entries(r.byStep)) {
51
+ console.log(` ${step.padEnd(14)} tokens=${v.tokens} ms=${v.ms} events=${v.count}`);
52
+ }
53
+ console.log(' verdict:');
54
+ for (const v of r.verdicts) console.log(` • ${v}`);
55
+ console.log('');
56
+ } catch (e) {
57
+ console.error('Error:', e instanceof Error ? e.message : e);
58
+ process.exit(1);
59
+ }
60
+ });
61
+ }
@@ -0,0 +1,55 @@
1
+ import { Command } from 'commander';
2
+ import * as path from 'path';
3
+ import * as fs from 'fs';
4
+ import { buildManifest, diffManifest, loadManifest, saveManifest } from '../../harness/manifest';
5
+
6
+ function findScreenDir(name: string): string | null {
7
+ const screen = path.join(process.cwd(), 'qa', 'screens', name);
8
+ if (fs.existsSync(screen)) return screen;
9
+ const flow = path.join(process.cwd(), 'qa', 'flows', name);
10
+ if (fs.existsSync(flow)) return flow;
11
+ return null;
12
+ }
13
+
14
+ export function registerManifestCommand(program: Command): void {
15
+ program
16
+ .command('manifest')
17
+ .description('Spec-fingerprint manifest: build a scenario↔spec-section map, or diff to plan keep/regenerate/retire')
18
+ .option('-s, --screen <name>', 'Screen or flow name')
19
+ .option('--diff', 'Compare current spec vs stored manifest → change plan')
20
+ .option('--json', 'Output raw JSON')
21
+ .action((options) => {
22
+ try {
23
+ const name = options.screen;
24
+ if (!name) throw new Error('Provide --screen <name>');
25
+ const dir = findScreenDir(name);
26
+ if (!dir) throw new Error(`Screen/flow not found: ${name}`);
27
+
28
+ if (options.diff) {
29
+ const manifest = loadManifest(name);
30
+ if (!manifest) throw new Error(`No manifest for "${name}". Run \`sungen manifest --screen ${name}\` first.`);
31
+ const plan = diffManifest(dir, name, manifest);
32
+ if (options.json) { console.log(JSON.stringify(plan, null, 2)); process.exit(0); }
33
+ console.log(`\n━━━ Spec-change plan: ${name} ━━━`);
34
+ console.log(` keep=${plan.summary.keep} regenerate=${plan.summary.regenerate} retire=${plan.summary.retire} newSections=${plan.summary.newSections}\n`);
35
+ for (const s of plan.scenarios.filter((x) => x.change !== 'keep')) {
36
+ console.log(` ${s.change === 'regenerate' ? '↻ REGENERATE' : '✗ RETIRE'}: ${s.scenario}`);
37
+ console.log(` ${s.reason}`);
38
+ }
39
+ if (plan.newSections.length) console.log(`\n ✚ NEW spec sections (no scenario yet): ${plan.newSections.join(', ')}`);
40
+ if (plan.summary.regenerate + plan.summary.retire + plan.summary.newSections === 0) console.log(' ✓ Spec unchanged — all scenarios still reflect the spec.');
41
+ console.log('');
42
+ return;
43
+ }
44
+
45
+ const manifest = buildManifest(dir, name);
46
+ const out = saveManifest(manifest);
47
+ if (options.json) { console.log(JSON.stringify(manifest, null, 2)); return; }
48
+ console.log(`\n✓ Manifest built: ${manifest.entries.length} scenarios across ${Object.keys(manifest.specSections).length} spec sections`);
49
+ console.log(` ${path.relative(process.cwd(), out)}\n`);
50
+ } catch (error) {
51
+ console.error('Error:', error instanceof Error ? error.message : error);
52
+ process.exit(1);
53
+ }
54
+ });
55
+ }
@@ -0,0 +1,50 @@
1
+ import { Command } from 'commander';
2
+ import * as path from 'path';
3
+ import * as fs from 'fs';
4
+ import { runScriptCheck } from '../../harness/script-check';
5
+
6
+ export function registerScriptCheckCommand(program: Command): void {
7
+ program
8
+ .command('script-check')
9
+ .description('Verify the generated Playwright spec is a faithful 1:1 of the Gherkin feature (no hand-edit / stale drift)')
10
+ .option('-s, --screen <name>', 'Screen or flow name')
11
+ .option('--json', 'Output raw JSON')
12
+ .action(async (options) => {
13
+ try {
14
+ const name = options.screen;
15
+ if (!name) throw new Error('Provide --screen <name>');
16
+ const screen = path.join(process.cwd(), 'qa', 'screens', name);
17
+ const flow = path.join(process.cwd(), 'qa', 'flows', name);
18
+ const flowMode = !fs.existsSync(screen) && fs.existsSync(flow);
19
+ const dir = fs.existsSync(screen) ? screen : (fs.existsSync(flow) ? flow : null);
20
+ if (!dir) throw new Error(`Screen/flow not found: ${name}`);
21
+
22
+ const r = await runScriptCheck(dir, name, flowMode);
23
+
24
+ const outDir = path.join(process.cwd(), '.sungen', 'reports');
25
+ fs.mkdirSync(outDir, { recursive: true });
26
+ fs.writeFileSync(path.join(outDir, `${name}-script-check.json`), JSON.stringify(r, null, 2), 'utf-8');
27
+
28
+ if (options.json) { console.log(JSON.stringify(r, null, 2)); process.exit(r.status === 'OK' ? 0 : 2); }
29
+
30
+ const L = console.log;
31
+ L('');
32
+ L(`━━━ Script-check: ${name} — Gherkin ↔ Playwright 1:1 ━━━`);
33
+ L('');
34
+ L(` status: ${r.status === 'OK' ? '✓ IN SYNC' : '✗ ' + (r.bypass ? 'BYPASS' : r.drift === 'drift' ? 'DRIFT' : 'MISMATCH')}`);
35
+ L(` scenarios: ${r.automatedScenarios} automated (+${r.manualScenarios} @manual) spec test() blocks: ${r.specTestBlocks} count-match: ${r.countMatch ? '✓' : '✗'}`);
36
+ L(` drift: ${r.drift}`);
37
+ if (r.driftHunks.length) { L(' differing lines (committed vs fresh regenerate):'); for (const h of r.driftHunks) L(h); }
38
+ L('');
39
+ if (r.findings.length) { L(' findings:'); for (const f of r.findings) L(` • ${f}`); }
40
+ else L(' ✓ The test code faithfully reflects the Gherkin (1:1).');
41
+ L('');
42
+ if (r.drift === 'drift') L(' → Fix: re-run `sungen generate --screen ' + name + '` (or /sungen:run-test) so the spec matches the feature. Never hand-edit generated specs.');
43
+ L('');
44
+ process.exit(r.status === 'OK' ? 0 : 2);
45
+ } catch (error) {
46
+ console.error('Error:', error instanceof Error ? error.message : error);
47
+ process.exit(1);
48
+ }
49
+ });
50
+ }
@@ -0,0 +1,60 @@
1
+ import { Command } from 'commander';
2
+ import * as path from 'path';
3
+ import * as fs from 'fs';
4
+ import { buildTrace } from '../../harness/trace';
5
+
6
+ export function registerTraceCommand(program: Command): void {
7
+ program
8
+ .command('trace')
9
+ .description('Visualise the executed test-design process (workflow/skill steps, repair loops), find bottlenecks, and show where to focus human review')
10
+ .option('-s, --screen <name>', 'Screen or flow name')
11
+ .option('--json', 'Output raw JSON')
12
+ .option('--mermaid', 'Print only the Mermaid flowchart')
13
+ .action((options) => {
14
+ try {
15
+ const name = options.screen;
16
+ if (!name) throw new Error('Provide --screen <name>');
17
+ const screen = path.join(process.cwd(), 'qa', 'screens', name);
18
+ const flow = path.join(process.cwd(), 'qa', 'flows', name);
19
+ const dir = fs.existsSync(screen) ? screen : (fs.existsSync(flow) ? flow : null);
20
+ if (!dir) throw new Error(`Screen/flow not found: ${name}`);
21
+
22
+ const r = buildTrace(dir, name);
23
+ if (options.json) { console.log(JSON.stringify(r, null, 2)); return; }
24
+ if (options.mermaid) { console.log(r.mermaid); return; }
25
+
26
+ const L = console.log;
27
+ L('');
28
+ L(`━━━ Process Trace: ${name} ━━━`);
29
+ L('');
30
+ L(` ① Executed process (from ledger${r.runs > 1 ? ` — latest of ${r.runs} runs` : ''})`);
31
+ if (r.ledger.length) {
32
+ for (const e of r.ledger) L(` → ${e.step.padEnd(12)} ${e.ms}ms`);
33
+ L(` repair rounds: ${r.repairRounds} total recorded: ${(r.totalMs / 1000).toFixed(1)}s`);
34
+ } else {
35
+ L(' (ledger empty — process not instrumented this run)');
36
+ }
37
+ if (r.missingSteps.length) L(` ⚠ phases not recorded: ${r.missingSteps.join(', ')}`);
38
+ L('');
39
+ L(' ② Quality signals');
40
+ if (r.audit) L(` audit score=${r.audit.score}/10 gate=${r.audit.gate} weakest=${r.audit.weakest} findings=${r.audit.findings}`);
41
+ else L(' (no audit report — run `sungen audit`)');
42
+ L(` script-check drift: ${r.drift ?? '(not run)'}`);
43
+ L('');
44
+ L(' ③ Bottlenecks / weak points');
45
+ for (const b of r.bottlenecks) L(` • ${b}`);
46
+ L('');
47
+ L(' ④ HUMAN-LOOP FOCUS — where you (the QA) must look');
48
+ for (const h of r.humanFocus) L(` ${h.startsWith(' ') ? h : '• ' + h}`);
49
+ L('');
50
+ L(' ⑤ Visual map (Mermaid — paste into mermaid.live or a Markdown viewer)');
51
+ L(' ```mermaid');
52
+ for (const line of r.mermaid.split('\n')) L(' ' + line);
53
+ L(' ```');
54
+ L('');
55
+ } catch (error) {
56
+ console.error('Error:', error instanceof Error ? error.message : error);
57
+ process.exit(1);
58
+ }
59
+ });
60
+ }
@@ -27,22 +27,35 @@ export function registerUpdateCommand(program: Command): void {
27
27
  program
28
28
  .command('update')
29
29
  .description(
30
- 'Reinstall @sun-asterisk/sungen@latest + refresh AI rules, commands, and skills',
30
+ 'Reinstall @sun-asterisk/sungen (stable by default, --beta for prerelease) + refresh AI rules, commands, skills',
31
31
  )
32
32
  .option('--dry-run', 'Show what would be updated without making changes')
33
+ .option(
34
+ '--beta',
35
+ 'Switch to the BETA channel (installs @beta — prerelease builds for testing)',
36
+ false,
37
+ )
38
+ .option(
39
+ '--tag <dist-tag>',
40
+ 'Install a specific npm dist-tag (e.g. latest, beta, next). Overrides --beta.',
41
+ )
33
42
  .option(
34
43
  '--skip-npm-install',
35
- 'Skip the `npm install -g @sun-asterisk/sungen@latest` step (refresh project AI assets only)',
44
+ 'Skip the npm install step (refresh project AI assets only)',
36
45
  false,
37
46
  )
38
- .action(async (options: { dryRun?: boolean; skipNpmInstall?: boolean }) => {
47
+ .action(async (options: { dryRun?: boolean; beta?: boolean; tag?: string; skipNpmInstall?: boolean }) => {
39
48
  try {
40
49
  const skipNpm =
41
50
  Boolean(options.skipNpmInstall) || process.env[SKIP_NPM_ENV] === '1';
42
51
 
52
+ // Channel resolution via npm dist-tags. Default `latest` = official/stable.
53
+ // `--beta` → beta channel; running plain `sungen update` later switches back to stable.
54
+ const channel = options.tag || (options.beta ? 'beta' : 'latest');
55
+
43
56
  if (!skipNpm) {
44
- reinstallLatestSungen();
45
- printCurrentVersion();
57
+ reinstallSungen(channel);
58
+ printCurrentVersion(channel);
46
59
  reExecUpdateForAIAssets(options.dryRun ?? false);
47
60
  return;
48
61
  }
@@ -57,22 +70,29 @@ export function registerUpdateCommand(program: Command): void {
57
70
  });
58
71
  }
59
72
 
60
- function reinstallLatestSungen(): void {
61
- console.log('📦 Installing @sun-asterisk/sungen@latest...');
62
- const result = spawnSync('npm', ['install', '-g', '@sun-asterisk/sungen@latest'], {
73
+ function reinstallSungen(channel: string): void {
74
+ const spec = `@sun-asterisk/sungen@${channel}`;
75
+ const label = channel === 'latest' ? 'stable (latest)' : `${channel} channel`;
76
+ console.log(`📦 Installing ${spec} → ${label}...`);
77
+ const result = spawnSync('npm', ['install', '-g', spec], {
63
78
  stdio: 'inherit',
64
79
  shell: true,
65
80
  });
66
81
  if (result.status !== 0) {
67
82
  throw new Error(
68
- 'npm install -g @sun-asterisk/sungen@latest failed. Run it manually or check your npm setup.',
83
+ `npm install -g ${spec} failed. Run it manually or check your npm setup.`,
69
84
  );
70
85
  }
71
86
  }
72
87
 
73
- function printCurrentVersion(): void {
88
+ function printCurrentVersion(channel: string): void {
74
89
  console.log('\n🔎 Installed version:');
75
90
  spawnSync('sungen', ['--version'], { stdio: 'inherit', shell: true });
91
+ if (channel === 'latest') {
92
+ console.log(' Channel: stable. To try prereleases: sungen update --beta');
93
+ } else {
94
+ console.log(` Channel: ${channel} (prerelease). To return to stable: sungen update`);
95
+ }
76
96
  console.log('');
77
97
  }
78
98
 
package/src/cli/index.ts CHANGED
@@ -14,6 +14,16 @@ import { registerDeliveryCommand } from './commands/delivery';
14
14
  import { registerFigmaCommand } from './commands/figma';
15
15
  import { registerAddFlowCommand } from './commands/add-flow';
16
16
  import { registerDashboardCommand } from './commands/dashboard';
17
+ import { registerAuditCommand } from './commands/audit';
18
+ import { registerManifestCommand } from './commands/manifest';
19
+ import { registerLedgerCommand } from './commands/ledger';
20
+ import { registerFeedbackCommand } from './commands/feedback';
21
+ import { registerScriptCheckCommand } from './commands/script-check';
22
+ import { registerTraceCommand } from './commands/trace';
23
+ import { registerChallengeCommand } from './commands/challenge';
24
+ import { registerBlindspotCommand } from './commands/blindspot';
25
+ import { registerCapabilityCommand } from './commands/capability';
26
+ import { registerFlowCheckCommand } from './commands/flow-check';
17
27
 
18
28
  // Read version from package.json so `--version` never drifts from the released version.
19
29
  const { version } = require('../../package.json') as { version: string };
@@ -40,6 +50,16 @@ async function main() {
40
50
  registerFigmaCommand(program);
41
51
  registerAddFlowCommand(program);
42
52
  registerDashboardCommand(program);
53
+ registerAuditCommand(program);
54
+ registerManifestCommand(program);
55
+ registerLedgerCommand(program);
56
+ registerFeedbackCommand(program);
57
+ registerScriptCheckCommand(program);
58
+ registerTraceCommand(program);
59
+ registerChallengeCommand(program);
60
+ registerBlindspotCommand(program);
61
+ registerCapabilityCommand(program);
62
+ registerFlowCheckCommand(program);
43
63
 
44
64
  await program.parseAsync(process.argv);
45
65
  }
@@ -22,6 +22,7 @@ export interface TestFileData {
22
22
  screenName?: string; // Screen name for TestDataLoader.load()
23
23
  featureFileName?: string; // Feature file name for TestDataLoader.load()
24
24
  isParallel?: boolean; // @parallel tag: fresh page per test (opt-out from serial default)
25
+ flowMode?: boolean; // flow: cross-screen → longer per-test timeout (bug #243)
25
26
  cleanup?: { overlay?: boolean; forms?: boolean; scroll?: boolean; storage?: boolean };
26
27
  backgroundSteps?: Array<{ comment?: string; code: string }>; // Raw background steps for serial beforeAll
27
28
  scenarios: string[];
@@ -1,5 +1,19 @@
1
+ import { createRequire } from 'module';
2
+ import * as path from 'path';
1
3
  import { TestGeneratorAdapter } from './adapter-interface';
2
4
 
5
+ /** Thrown when a platform/capability driver is selected but its package isn't installed. */
6
+ export class DriverNotInstalledError extends Error {
7
+ constructor(public adapterName: string, public packageName: string) {
8
+ super(
9
+ `No driver for "${adapterName}". Install it for this project:\n` +
10
+ ` sungen capability add ${adapterName}\n` +
11
+ ` (installs ${packageName}). Or keep affected scenarios @manual.`,
12
+ );
13
+ this.name = 'DriverNotInstalledError';
14
+ }
15
+ }
16
+
3
17
  /**
4
18
  * Adapter Registry
5
19
  * Manages available test framework adapters
@@ -50,6 +64,29 @@ export class AdapterRegistry {
50
64
  getAvailableAdapters(): string[] {
51
65
  return Array.from(this.adapters.keys());
52
66
  }
67
+
68
+ /**
69
+ * Load an EXTERNAL driver package (resolved from the PROJECT's node_modules) and
70
+ * let it register its adapter. Idempotent — a no-op if already registered.
71
+ * The driver package should export `activate(registry)`; some self-register on import.
72
+ * @throws DriverNotInstalledError when the package isn't installed in the project.
73
+ */
74
+ loadDriver(adapterName: string, packageName: string, cwd: string = process.cwd()): void {
75
+ const name = adapterName.toLowerCase();
76
+ if (this.adapters.has(name)) return;
77
+ const projectRequire = createRequire(path.join(cwd, 'package.json'));
78
+ let mod: any;
79
+ try {
80
+ mod = projectRequire(packageName);
81
+ } catch {
82
+ throw new DriverNotInstalledError(adapterName, packageName);
83
+ }
84
+ if (typeof mod?.activate === 'function') mod.activate(this);
85
+ if (!this.adapters.has(name)) {
86
+ // Package present but didn't register the expected adapter.
87
+ throw new DriverNotInstalledError(adapterName, packageName);
88
+ }
89
+ }
53
90
  }
54
91
 
55
92
  // Singleton instance
@@ -1,5 +1,5 @@
1
1
  export { TestGeneratorAdapter, TestFileData, ScenarioData, StepTemplateData, LocatorExpression } from './adapter-interface';
2
- export { AdapterRegistry, adapterRegistry } from './adapter-registry';
2
+ export { AdapterRegistry, adapterRegistry, DriverNotInstalledError } from './adapter-registry';
3
3
  export { PlaywrightAdapter } from './playwright/playwright-adapter';
4
4
 
5
5
  // Auto-register built-in adapters
@@ -7,3 +7,6 @@ import { adapterRegistry } from './adapter-registry';
7
7
  import { PlaywrightAdapter } from './playwright/playwright-adapter';
8
8
 
9
9
  adapterRegistry.register('playwright', () => new PlaywrightAdapter());
10
+ // Phase 2a: platform alias. `web` is the bundled Playwright adapter (back-compat
11
+ // baseline) until it is externalized to @sungen/driver-web in a later cut.
12
+ adapterRegistry.register('web', () => new PlaywrightAdapter());
@@ -0,0 +1 @@
1
+ testData.set('{{varName}}', ((await {{> locator}}.{{capture}}()) ?? '').trim());
@@ -0,0 +1,7 @@
1
+ {
2
+ const __items_{{stepCounter}} = await {{> locator}}.allInnerTexts();
3
+ expect(__items_{{stepCounter}}.length, 'Expected at least one [{{selectorRef}}]').toBeGreaterThan(0);
4
+ for (const __t_{{stepCounter}} of __items_{{stepCounter}}) {
5
+ expect(__t_{{stepCounter}}).toContain('{{expectedText}}');
6
+ }
7
+ }
@@ -13,6 +13,9 @@ const testData = TestDataLoader.load('{{screenName}}', '{{featureFileName}}');
13
13
 
14
14
  {{#if isParallel}}
15
15
  test.describe('{{featureName}}', () => {
16
+ {{#if flowMode}}
17
+ test.describe.configure({ timeout: 90_000 }); // cross-screen flow — allow for multi-page navigation
18
+ {{/if}}
16
19
  {{#if singleAuthRole}}
17
20
  test.use({ storageState: 'specs/.auth/{{singleAuthRole}}.json' });
18
21
 
@@ -65,6 +68,9 @@ test.describe('{{featureName}}', () => {
65
68
  });
66
69
  {{else}}
67
70
  test.describe.serial('{{featureName}}', () => {
71
+ {{#if flowMode}}
72
+ test.describe.configure({ timeout: 90_000 }); // cross-screen flow — allow for multi-page navigation
73
+ {{/if}}
68
74
  let page: Page;
69
75
  let context: BrowserContext;
70
76
 
@@ -176,8 +176,11 @@ export class CodeGenerator {
176
176
  this.options = options;
177
177
  this.screenName = options.screenName;
178
178
  this.stepMapper = new StepMapper(options);
179
- // Get adapter from registry (default: playwright)
180
- const frameworkName = options.framework || 'playwright';
179
+ // Get adapter from registry. Platform comes from the caller (resolved from
180
+ // qa/capabilities.yaml); default `web` = the bundled Playwright adapter
181
+ // (back-compat baseline). External platforms (e.g. mobile) are loaded by the
182
+ // command layer before construction; here we only resolve a registered adapter.
183
+ const frameworkName = options.framework || 'web';
181
184
  this.adapter = adapterRegistry.getAdapter(frameworkName);
182
185
 
183
186
  if (options.verbose) {
@@ -514,6 +517,7 @@ export class CodeGenerator {
514
517
  screenName: isFlowFeature ? `flows/${effectiveScreenName}` : effectiveScreenName,
515
518
  featureFileName: featureName,
516
519
  isParallel,
520
+ flowMode: isFlowFeature,
517
521
  cleanup,
518
522
  backgroundSteps,
519
523
  scenarios: needsGrouping ? [] : scenarios,
@@ -0,0 +1,59 @@
1
+ import { ParsedStep } from '../../gherkin-parser';
2
+ import { StepPattern, StepTemplateData } from './types';
3
+
4
+ /**
5
+ * Capture & collection patterns (P5) — enable cross-screen data consistency and
6
+ * filter-result correctness that plain single-element assertions can't express.
7
+ *
8
+ * 1. Capture: `User remember [Product Name] text as {{selected_product_name}}`
9
+ * → stores the element's text/value into a runtime variable so a
10
+ * later step (on another screen) can assert against it.
11
+ * REQUIRES runtime data mode (default) — emits `testData.set(...)`.
12
+ *
13
+ * 2. List: `User see all [Product Name] contain {{selected_category}}`
14
+ * → asserts EVERY matching element's text contains the value
15
+ * (e.g. all products belong to the selected category/brand).
16
+ */
17
+ export const capturePatterns: StepPattern[] = [
18
+ {
19
+ name: 'capture-variable',
20
+ matcher: (step: ParsedStep) =>
21
+ /\bremember\b/i.test(step.text) && /\bas\b/i.test(step.text) && !!step.selectorRef && !!step.dataRef,
22
+ resolver: (step, context): StepTemplateData => {
23
+ const resolved = context.selectorResolver.resolveSelector(
24
+ step.selectorRef!, undefined, step.elementType, step.nth,
25
+ );
26
+ const varName = step.dataRef!;
27
+ const isValue = /\bvalue\b/i.test(step.text);
28
+ // Register so later `{{varName}}` references resolve to testData.get(varName)
29
+ // and skip YAML validation.
30
+ context.dataResolver.registerCaptured(varName);
31
+ return {
32
+ templateName: 'capture-variable',
33
+ data: { ...resolved, varName, capture: isValue ? 'inputValue' : 'textContent' },
34
+ comment: `Remember ${step.selectorRef} ${isValue ? 'value' : 'text'} as ${varName}`,
35
+ };
36
+ },
37
+ priority: 35,
38
+ },
39
+ {
40
+ name: 'all-contain-assertion',
41
+ matcher: (step: ParsedStep) =>
42
+ /\b(see|sees)\b/i.test(step.text) &&
43
+ /\ball\b/i.test(step.text) &&
44
+ /(contain|contains|match|matches|belong)/i.test(step.text) &&
45
+ !!step.selectorRef && !!(step.value || step.dataRef),
46
+ resolver: (step, context): StepTemplateData => {
47
+ const resolved = context.selectorResolver.resolveSelector(
48
+ step.selectorRef!, undefined, step.elementType, step.nth,
49
+ );
50
+ const expectedText = step.value || context.dataResolver.resolveData(step.dataRef!, context.featureName);
51
+ return {
52
+ templateName: 'all-contain-assertion',
53
+ data: { ...resolved, expectedText, selectorRef: step.selectorRef, stepCounter: context.stepCounter },
54
+ comment: `Assert all ${step.selectorRef} contain "${step.value || step.dataRef}"`,
55
+ };
56
+ },
57
+ priority: 34,
58
+ },
59
+ ];
@@ -129,7 +129,9 @@ export const formPatterns: StepPattern[] = [
129
129
  data = { ...resolved, selectorRef: step.selectorRef };
130
130
  } else {
131
131
  const nativeSelectRoles = ['combobox', 'listbox', 'select'];
132
- const isNativeSelect = nativeSelectRoles.includes(resolved.role);
132
+ // A native <select> is detected by its ARIA role, or by an explicit `variant: native`
133
+ // on the selector entry (lets a CSS/#id-located select opt into .selectOption()).
134
+ const isNativeSelect = nativeSelectRoles.includes(resolved.role) || resolved.variant === 'native';
133
135
 
134
136
  if (isNativeSelect) {
135
137
  templateName = 'select-action';
@@ -10,6 +10,7 @@ import { keyboardPatterns } from './keyboard-patterns';
10
10
  import { scrollPatterns } from './scroll-patterns';
11
11
  import { scopePatterns } from './scope-patterns';
12
12
  import { tablePatterns } from './table-patterns';
13
+ import { capturePatterns } from './capture-patterns';
13
14
 
14
15
  /**
15
16
  * Pattern Registry - manages all step patterns
@@ -34,6 +35,7 @@ export class PatternRegistry {
34
35
  this.patterns.push(...scrollPatterns);
35
36
  this.patterns.push(...scopePatterns);
36
37
  this.patterns.push(...tablePatterns);
38
+ this.patterns.push(...capturePatterns);
37
39
 
38
40
  // Sort by priority (higher first)
39
41
  this.patterns.sort((a, b) => (b.priority || 0) - (a.priority || 0));
@@ -90,6 +90,7 @@ export class StepMapper {
90
90
  this.inRowScope = false;
91
91
  this.rowScopeTable = '';
92
92
  this.lastPrimaryKeyword = 'Given';
93
+ this.dataResolver.clearCaptured(); // captured vars are scenario-scoped
93
94
  this.templateEngine.resetBaseContext();
94
95
  }
95
96