@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
@@ -14,6 +14,20 @@ export class DataResolver {
14
14
  private screenName?: string;
15
15
  private runtimeMode: boolean;
16
16
  private flowMode: boolean = false;
17
+ // Vars captured at runtime via `User remember [X] as {{var}}`. References to
18
+ // these skip YAML validation (they don't exist in test-data.yaml) and resolve
19
+ // to testData.get('var') — populated by the capture step's testData.set('var', …).
20
+ private capturedVars = new Set<string>();
21
+
22
+ /** Register a variable name produced by a capture step (scenario-scoped). */
23
+ registerCaptured(name: string): void {
24
+ this.capturedVars.add(name);
25
+ }
26
+
27
+ /** Clear captured vars — call at the start of each scenario. */
28
+ clearCaptured(): void {
29
+ this.capturedVars.clear();
30
+ }
17
31
 
18
32
  constructor(testDataDir?: string, screenName?: string, runtimeMode: boolean = false) {
19
33
  this.testDataDir = testDataDir || path.join(process.cwd(), 'qa', 'test-data');
@@ -36,6 +50,12 @@ export class DataResolver {
36
50
  * @returns The resolved value
37
51
  */
38
52
  resolveData(dataRef: string, featureName?: string): string {
53
+ // Captured vars exist only at runtime → emit a marker (→ testData.get) and
54
+ // skip YAML validation. Requires runtime mode (capture writes testData.set).
55
+ if (this.capturedVars.has(dataRef)) {
56
+ return DataResolver.encodeMarker(dataRef);
57
+ }
58
+
39
59
  if (this.runtimeMode) {
40
60
  this.validateDataRef(dataRef, featureName);
41
61
  return DataResolver.encodeMarker(dataRef);
@@ -39,6 +39,14 @@ export function transformToRuntimeData(code: string): string {
39
39
  }
40
40
  );
41
41
 
42
+ // Pass 3: Bare-identifier context — any marker left after the string/regex passes
43
+ // sits as a bare token in a numeric position (e.g. `toHaveCount(__marker__)` from the
44
+ // table/list count templates). testData.get() returns a string, so coerce with Number().
45
+ code = code.replace(
46
+ /__SUNGEN_TD_([A-Za-z0-9_]+)__/g,
47
+ (_, enc) => `Number(testData.get('${decodeKey(enc)}'))`
48
+ );
49
+
42
50
  return code;
43
51
  }
44
52
 
@@ -312,12 +312,19 @@ export class SelectorResolver {
312
312
  const key = SelectorResolver.generateKey(label);
313
313
  const selectorFile = this.loadNewSelectorFile(featureName);
314
314
 
315
+ // Flow refs are namespaced "Screen:Element". The namespace is ONLY for selector-key
316
+ // lookup (key keeps it) — it is NOT part of the DOM accessible name. Strip it for any
317
+ // name/label fallback (auto-infer or entry without an explicit `name`). Bug #243.
318
+ const displayLabel = this.flowMode && label.includes(':')
319
+ ? label.slice(label.indexOf(':') + 1).trim()
320
+ : label;
321
+
315
322
  // Try nth-suffixed key first: hay.gui.loi.cam.on--3
316
323
  if (nth && nth > 0) {
317
324
  const nthKey = `${key}--${nth}`;
318
325
  const nthEntry = selectorFile[nthKey];
319
326
  if (nthEntry) {
320
- return this.resolveFromEntry(nthEntry, label);
327
+ return this.resolveFromEntry(nthEntry, displayLabel);
321
328
  }
322
329
  }
323
330
 
@@ -327,7 +334,7 @@ export class SelectorResolver {
327
334
  const typedKey = `${key}--${normalizedType}`;
328
335
  const typedEntry = selectorFile[typedKey];
329
336
  if (typedEntry) {
330
- return this.resolveFromEntry(typedEntry, label);
337
+ return this.resolveFromEntry(typedEntry, displayLabel);
331
338
  }
332
339
 
333
340
  // If normalized differs from original, also try original as-is
@@ -335,7 +342,7 @@ export class SelectorResolver {
335
342
  const originalKey = `${key}--${elementType}`;
336
343
  const originalEntry = selectorFile[originalKey];
337
344
  if (originalEntry) {
338
- return this.resolveFromEntry(originalEntry, label);
345
+ return this.resolveFromEntry(originalEntry, displayLabel);
339
346
  }
340
347
  }
341
348
  }
@@ -343,12 +350,12 @@ export class SelectorResolver {
343
350
  // Fallback to base key
344
351
  const entry = selectorFile[key];
345
352
  if (entry) {
346
- return this.resolveFromEntry(entry, label);
353
+ return this.resolveFromEntry(entry, displayLabel);
347
354
  }
348
355
 
349
356
  // Auto-infer locator from elementType when no exact YAML entry exists
350
357
  if (elementType) {
351
- const inferred = SelectorResolver.inferFromElementType(elementType, label);
358
+ const inferred = SelectorResolver.inferFromElementType(elementType, displayLabel);
352
359
  if (inferred) {
353
360
  return inferred;
354
361
  }
@@ -357,7 +364,7 @@ export class SelectorResolver {
357
364
  // Last resort: find any suffixed key matching key--*
358
365
  const suffixedKeys = Object.keys(selectorFile).filter(k => k.startsWith(`${key}--`));
359
366
  if (suffixedKeys.length > 0) {
360
- return this.resolveFromEntry(selectorFile[suffixedKeys[0]], label);
367
+ return this.resolveFromEntry(selectorFile[suffixedKeys[0]], displayLabel);
361
368
  }
362
369
 
363
370
  const tried: string[] = [];
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Harness Audit — runs all sensors over a screen's test-design artifacts and
3
+ * produces a structured report + a business-weighted quality score.
4
+ *
5
+ * The score is INTENTIONALLY weighted toward business-critical coverage/depth
6
+ * (not breadth), so it surfaces the gaps a count-based view hides. See
7
+ * docs/orchestration-spec.md §5 and reports/sungen_home_gherkin_viewpoint_coverage_review.md.
8
+ */
9
+ import * as path from 'path';
10
+ import { loadScenarios, parseViewpointOverview, ScenarioInfo, ViewpointEntry } from './parse';
11
+ import {
12
+ loadCatalog, viewpointGate, assertionDepth, dataThemesFor, coverageBalance, duplicateClusters, traceability,
13
+ GateResult, DepthResult, BalanceResult, DuplicateResult, TraceResult,
14
+ } from './sensors';
15
+ import { readIntent, projectRootFromScreenDir, IntentProfile } from './intent';
16
+
17
+ export interface AuditReport {
18
+ screen: string;
19
+ scenarioCount: number;
20
+ gate: GateResult;
21
+ depth: DepthResult;
22
+ balance: BalanceResult;
23
+ duplicates: DuplicateResult;
24
+ trace: TraceResult;
25
+ score: {
26
+ overall: number; // 0..10, business-weighted
27
+ coverage: number; // 0..1
28
+ businessDepth: number; // 0..1
29
+ balance: number; // 0..1
30
+ traceability: number; // 0..1
31
+ formula: string;
32
+ };
33
+ gateStatus: 'PASS' | 'FAIL';
34
+ findings: string[]; // human-actionable, what the Repair loop would target
35
+ intent: IntentProfile; // P3 — the intent profile that drove the thresholds
36
+ }
37
+
38
+ export function runAudit(screenDir: string, screenName: string): AuditReport {
39
+ const featurePath = path.join(screenDir, 'features', `${screenName}.feature`);
40
+ const viewpointPath = path.join(screenDir, 'requirements', 'test-viewpoint.md');
41
+
42
+ const scenarios: ScenarioInfo[] = loadScenarios(featurePath);
43
+ const viewpoints: ViewpointEntry[] = parseViewpointOverview(viewpointPath);
44
+ const catalog = loadCatalog();
45
+
46
+ const gate = viewpointGate(scenarios, viewpoints, catalog);
47
+ // P3 — intent profile from qa/context.md drives the depth threshold (focus).
48
+ const intent = readIntent(projectRootFromScreenDir(screenDir));
49
+ const depth = assertionDepth(scenarios, dataThemesFor(catalog, gate.pageType), intent.focus);
50
+ const balance = coverageBalance(scenarios);
51
+ const duplicates = duplicateClusters(scenarios);
52
+ const trace = traceability(scenarios, viewpoints);
53
+
54
+ // Sub-scores
55
+ const coverage = gate.coverageRatio;
56
+ const businessDepth = depth.bcDepthRatio;
57
+ const balanceScore = balance.coreCount + balance.secondaryCount > 0
58
+ ? Math.min(1, balance.coreCount / Math.max(1, balance.secondaryCount))
59
+ : 1;
60
+ const traceScore = 0.5 * trace.withVpCodeRatio + 0.5 * trace.mappedRatio;
61
+
62
+ // Business-weighted overall (coverage + depth dominate)
63
+ const overall = (0.4 * coverage + 0.3 * businessDepth + 0.15 * balanceScore + 0.15 * traceScore) * 10;
64
+
65
+ const findings: string[] = [];
66
+ for (const g of gate.gaps) {
67
+ if (g.status === 'shallow') {
68
+ findings.push(`GATE: critical theme "${g.theme}" is covered only by SHALLOW scenarios (no data assertion) → deepen with \`... with {{value}}\` / \`table ... with {{value}}\` (count @manual cross-screen too).`);
69
+ } else {
70
+ findings.push(`GATE: critical theme "${g.theme}" for page-type "${gate.pageType}" has NO covering scenario → generate it (often cross-screen → consider add-flow).`);
71
+ }
72
+ }
73
+ if (depth.businessCriticalShallow > 0) {
74
+ const tag = depth.verdict === 'fail' ? 'DEPTH-FAIL' : depth.verdict === 'warn' ? 'DEPTH-WARN' : 'DEPTH';
75
+ findings.push(
76
+ `${tag}: ${depth.businessCriticalShallow}/${depth.businessCriticalTotal} data-correctness scenarios assert only visibility ` +
77
+ `(ratio ${depth.bcDepthRatio.toFixed(2)} < threshold ${depth.threshold.toFixed(2)} for focus "${depth.focus}") → ` +
78
+ `add data assertions (\`... with {{value}}\`, \`see all ... contain {{v}}\`) or, if cross-screen, defer to a flow with @manual + reason.`,
79
+ );
80
+ }
81
+ if (balance.imbalanced) {
82
+ findings.push(`BALANCE: ${balance.note} Stop expanding secondary viewpoints until business-core gaps are filled.`);
83
+ }
84
+ if (trace.mappedRatio < 0.5) {
85
+ findings.push(`TRACE: ${trace.note}`);
86
+ }
87
+ if (gate.universalGaps.length) {
88
+ findings.push(`UNIVERSAL: missing theme(s): ${gate.universalGaps.join(', ')} (low priority reminder).`);
89
+ }
90
+
91
+ // Gate now spans coverage (viewpoint themes) AND depth (data-correctness).
92
+ // A depth 'fail' (below the intent threshold) fails the gate; 'warn' does not.
93
+ const gateStatus: 'PASS' | 'FAIL' =
94
+ gate.gaps.length === 0 && depth.verdict !== 'fail' ? 'PASS' : 'FAIL';
95
+
96
+ return {
97
+ screen: screenName,
98
+ scenarioCount: scenarios.length,
99
+ gate, depth, balance, duplicates, trace,
100
+ score: {
101
+ overall: Math.round(overall * 10) / 10,
102
+ coverage: Math.round(coverage * 100) / 100,
103
+ businessDepth: Math.round(businessDepth * 100) / 100,
104
+ balance: Math.round(balanceScore * 100) / 100,
105
+ traceability: Math.round(traceScore * 100) / 100,
106
+ formula: 'overall = (0.4*coverage + 0.3*businessDepth + 0.15*balance + 0.15*traceability) * 10',
107
+ },
108
+ gateStatus,
109
+ findings,
110
+ intent,
111
+ };
112
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Blind-Spot Memory (harness-roadmap P5).
3
+ *
4
+ * A deterministic harness reliably repeats whatever its rules/viewpoints miss. When
5
+ * a QA (or the challenge critic) finds a recurring gap, don't just patch one test —
6
+ * promote it to a reusable PATTERN here. Loop 1 (generation pre-gate) and Loop 2
7
+ * (challenge critic) read these so the same blind spot doesn't come back.
8
+ *
9
+ * Store: .sungen/blindspots/blindspots.jsonl (append-only, project-local).
10
+ */
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+
14
+ export interface Blindspot {
15
+ ts: string;
16
+ pattern: string; // short name, e.g. "add-action-no-duplicate-check"
17
+ rule: string; // the generalised rule to apply next time
18
+ example?: string; // a concrete instance that motivated it
19
+ screen?: string; // where it was first noticed
20
+ source?: string; // 'qa' | 'challenge' | 'feedback'
21
+ }
22
+
23
+ function storePath(): string {
24
+ return path.join(process.cwd(), '.sungen', 'blindspots', 'blindspots.jsonl');
25
+ }
26
+
27
+ export function addBlindspot(entry: Omit<Blindspot, 'ts'> & { ts?: string }): string {
28
+ const p = storePath();
29
+ fs.mkdirSync(path.dirname(p), { recursive: true });
30
+ const full: Blindspot = { ts: entry.ts ?? new Date().toISOString(), ...entry };
31
+ fs.appendFileSync(p, JSON.stringify(full) + '\n', 'utf-8');
32
+ return p;
33
+ }
34
+
35
+ export function listBlindspots(): Blindspot[] {
36
+ const p = storePath();
37
+ if (!fs.existsSync(p)) return [];
38
+ return fs.readFileSync(p, 'utf-8').split('\n').filter(Boolean).map((l) => {
39
+ try { return JSON.parse(l) as Blindspot; } catch { return null; }
40
+ }).filter(Boolean) as Blindspot[];
41
+ }
42
+
43
+ /** Compact bullet list for injecting into generator / critic prompts. */
44
+ export function blindspotsForPrompt(): string {
45
+ const all = listBlindspots();
46
+ if (!all.length) return '';
47
+ // De-dupe by pattern (latest rule wins).
48
+ const byPattern = new Map<string, Blindspot>();
49
+ for (const b of all) byPattern.set(b.pattern, b);
50
+ return [...byPattern.values()].map((b) => `- [${b.pattern}] ${b.rule}`).join('\n');
51
+ }
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Capability Planner (Phase 2b) — deterministic, recommend-only.
3
+ *
4
+ * Classifies each scenario's execution mode + each @manual case by reason code
5
+ * (M1–M9), maps capability-reasons to drivers, and emits the manual-reason KPI.
6
+ * Never installs anything (that's `sungen capability add`). See
7
+ * reports/sungen_phase2b_spec.md.
8
+ */
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ import { loadDriverCatalog } from './capability';
12
+
13
+ export type ReasonClass = 'capability' | 'keep' | 'flow';
14
+ export interface ReasonDef { code: string; label: string; cls: ReasonClass; drivers: string[] }
15
+
16
+ export const MANUAL_REASONS: Record<string, ReasonDef> = {
17
+ M1: { code: 'M1', label: 'Missing data setup', cls: 'capability', drivers: ['data-factory'] },
18
+ M2: { code: 'M2', label: 'Missing API/DB assertion', cls: 'capability', drivers: ['api', 'db'] },
19
+ M3: { code: 'M3', label: 'Missing mock/network control', cls: 'capability', drivers: ['mock'] },
20
+ M4: { code: 'M4', label: 'Missing stable selector/test-id', cls: 'capability', drivers: [] }, // locator contract, no driver
21
+ M5: { code: 'M5', label: 'External dependency', cls: 'capability', drivers: ['mail-file', 'contract'] },
22
+ M6: { code: 'M6', label: 'Visual / UX judgment', cls: 'keep', drivers: ['specialized'] },
23
+ M7: { code: 'M7', label: 'Environment limitation', cls: 'capability', drivers: [] }, // testability, no driver
24
+ M8: { code: 'M8', label: 'Not worth automating', cls: 'keep', drivers: [] },
25
+ M9: { code: 'M9', label: 'True human judgment', cls: 'keep', drivers: [] },
26
+ // Not a single-screen driver gap: automatable cross-screen via a flow (/sungen:add-flow).
27
+ XS: { code: 'XS', label: 'Cross-screen (automate via flow)', cls: 'flow', drivers: [] },
28
+ };
29
+
30
+ // Keyword inference (best-effort retrofit). Order matters — first match wins.
31
+ // Cross-screen "deferred to a flow" is checked FIRST: it is not a driver gap on
32
+ // this screen, it is handled by a flow — so it must not recommend a driver.
33
+ const INFER: { code: string; re: RegExp }[] = [
34
+ { code: 'XS', re: /\b(deferred to a flow|cross[-\s]?screen|in a flow|via a flow)\b/i },
35
+ { code: 'M4', re: /\b(selector|test[-\s]?id|locator|data-testid)\b/i },
36
+ { code: 'M3', re: /\b(mock|stub|network|offline|slow network|intercept)\b/i },
37
+ { code: 'M2', re: /\b(api|endpoint|backend|db|database|server[-\s]?side|via api)\b/i },
38
+ { code: 'M1', re: /\b(data setup|dataset|seed|test data|empty (category|product|dataset|state)|zero products|forcing an empty|backend\/test data)\b/i },
39
+ { code: 'M5', re: /\b(external|third[-\s]?party|sandbox|email|mail|payment gateway|invoice|download)\b/i },
40
+ { code: 'M6', re: /\b(visual|responsive|layout|accessibilit|a11y|keyboard|screen reader|ux|breakpoint)\b/i },
41
+ { code: 'M7', re: /\b(environment|staging[-\s]?only|infra|env limitation)\b/i },
42
+ { code: 'M8', re: /\b(not worth|exploratory|one[-\s]?off)\b/i },
43
+ { code: 'M9', re: /\b(judgment|human|subjective|manual review)\b/i },
44
+ ];
45
+
46
+ interface ParsedScenario { name: string; tags: string[]; manual: boolean; reason: string }
47
+
48
+ /** Parse scenarios with their tags + the reason comment line above (for @manual). */
49
+ export function parseScenarios(featurePath: string): ParsedScenario[] {
50
+ if (!fs.existsSync(featurePath)) return [];
51
+ const lines = fs.readFileSync(featurePath, 'utf-8').split('\n');
52
+ const out: ParsedScenario[] = [];
53
+ for (let i = 0; i < lines.length; i++) {
54
+ const m = lines[i].match(/^\s*Scenario:\s*(.+)$/);
55
+ if (!m) continue;
56
+ const tags: string[] = [];
57
+ let reason = '';
58
+ // Scan UP over the ADJACENT block (tag line + a directly-above reason comment).
59
+ // Stop at a blank line so section-divider comments further up aren't captured.
60
+ for (let j = i - 1; j >= 0 && j >= i - 6; j--) {
61
+ const l = lines[j].trim();
62
+ if (l === '') break;
63
+ if (/^@/.test(l)) tags.unshift(...l.split(/\s+/).filter((t) => t.startsWith('@')));
64
+ else if (/^#/.test(l)) { if (!reason) reason = l.replace(/^#+\s*/, ''); }
65
+ else break;
66
+ }
67
+ // Scan DOWN for the reason comment placed as the first line inside the body.
68
+ for (let k = i + 1; k < lines.length && k <= i + 4; k++) {
69
+ const l = lines[k].trim();
70
+ if (/^#/.test(l)) { if (!reason) reason = l.replace(/^#+\s*/, ''); break; }
71
+ else if (l === '') continue;
72
+ else break; // a real step → stop
73
+ }
74
+ out.push({ name: m[1].trim(), tags, manual: tags.some((t) => /^@manual\b/i.test(t)), reason });
75
+ }
76
+ return out;
77
+ }
78
+
79
+ function explicitCode(tags: string[]): string | undefined {
80
+ for (const t of tags) {
81
+ const m = t.match(/^@(?:manual|reason):(M[1-9])$/i);
82
+ if (m) return m[1].toUpperCase();
83
+ }
84
+ return undefined;
85
+ }
86
+
87
+ export function inferReasonCode(tags: string[], reason: string): { code: string; explicit: boolean; unclassified: boolean } {
88
+ const ex = explicitCode(tags);
89
+ if (ex) return { code: ex, explicit: true, unclassified: false };
90
+ for (const r of INFER) if (r.re.test(reason)) return { code: r.code, explicit: false, unclassified: false };
91
+ return { code: 'M9', explicit: false, unclassified: true };
92
+ }
93
+
94
+ function classifyMode(tags: string[]): string {
95
+ const has = (re: RegExp) => tags.some((t) => re.test(t));
96
+ if (has(/^@manual\b/i)) return 'manual';
97
+ if (has(/^@hybrid$/i)) return 'hybrid';
98
+ if (has(/^@(api|apiassert)$/i)) return 'api';
99
+ if (has(/^@(mock|network)$/i)) return 'mock';
100
+ if (has(/^@dbassert$/i)) return 'dbAssert';
101
+ return 'ui';
102
+ }
103
+
104
+ export interface CapabilityPlan {
105
+ screen: string;
106
+ total: number;
107
+ modes: Record<string, number>;
108
+ byReason: Record<string, number>;
109
+ unclassified: { name: string; reason: string }[];
110
+ capabilityManual: number;
111
+ judgmentManual: number;
112
+ crossScreen: number; // automatable via a flow — not a single-screen driver gap
113
+ capabilityManualPct: number;
114
+ recommendations: { driver: string; pkg: string; reason: string; count: number; scenarios: string[] }[];
115
+ keep: { code: string; count: number }[];
116
+ }
117
+
118
+ export function buildPlan(screenDir: string, screenName: string): CapabilityPlan {
119
+ const featurePath = path.join(screenDir, 'features', `${screenName}.feature`);
120
+ const scenarios = parseScenarios(featurePath);
121
+ const catalog = loadDriverCatalog();
122
+
123
+ const modes: Record<string, number> = {};
124
+ const byReason: Record<string, number> = {};
125
+ const unclassified: CapabilityPlan['unclassified'] = [];
126
+ // driver → {reasonCode, scenarios}
127
+ const recByDriver = new Map<string, { reason: string; scenarios: string[] }>();
128
+ const keepCount: Record<string, number> = {};
129
+ let capabilityManual = 0, judgmentManual = 0, crossScreen = 0;
130
+
131
+ for (const s of scenarios) {
132
+ const mode = classifyMode(s.tags);
133
+ modes[mode] = (modes[mode] || 0) + 1;
134
+ if (mode !== 'manual') continue;
135
+
136
+ const { code, unclassified: unc } = inferReasonCode(s.tags, s.reason);
137
+ byReason[code] = (byReason[code] || 0) + 1;
138
+ if (unc) unclassified.push({ name: s.name, reason: s.reason || '(no reason comment)' });
139
+
140
+ const def = MANUAL_REASONS[code];
141
+ if (def.cls === 'flow') {
142
+ crossScreen++;
143
+ } else if (def.cls === 'capability') {
144
+ capabilityManual++;
145
+ for (const d of def.drivers) {
146
+ const cur = recByDriver.get(d) || { reason: code, scenarios: [] };
147
+ cur.scenarios.push(s.name);
148
+ recByDriver.set(d, cur);
149
+ }
150
+ } else {
151
+ judgmentManual++;
152
+ keepCount[code] = (keepCount[code] || 0) + 1;
153
+ }
154
+ }
155
+
156
+ const recommendations = [...recByDriver.entries()]
157
+ .map(([driver, v]) => ({
158
+ driver,
159
+ pkg: catalog[driver]?.package || `@sungen/driver-${driver}`,
160
+ reason: v.reason,
161
+ count: v.scenarios.length,
162
+ scenarios: v.scenarios,
163
+ }))
164
+ .sort((a, b) => b.count - a.count);
165
+
166
+ const manualTotal = modes['manual'] || 0;
167
+ return {
168
+ screen: screenName,
169
+ total: scenarios.length,
170
+ modes,
171
+ byReason,
172
+ unclassified,
173
+ capabilityManual,
174
+ judgmentManual,
175
+ crossScreen,
176
+ capabilityManualPct: manualTotal ? Math.round((capabilityManual / manualTotal) * 100) : 0,
177
+ recommendations,
178
+ keep: Object.entries(keepCount).map(([code, count]) => ({ code, count })).sort((a, b) => b.count - a.count),
179
+ };
180
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Capability profile + driver catalog (harness-roadmap Phase 2a).
3
+ *
4
+ * Sungen core is runtime-agnostic: a project declares its PLATFORM driver (web →
5
+ * Playwright, mobile → Appium) + any capability drivers in qa/capabilities.yaml.
6
+ * There is no default runtime — but for back-compat, an ABSENT profile is treated
7
+ * as `web` (the bundled adapter) with a one-time notice + scaffold.
8
+ *
9
+ * This module is metadata + profile I/O only. It never installs or imports a driver.
10
+ */
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
14
+
15
+ export interface CapabilityProfile {
16
+ platform?: string; // web | mobile | … (the runtime adapter)
17
+ enabled: string[]; // driver ids enabled for this project
18
+ source: 'capabilities.yaml' | 'absent';
19
+ }
20
+
21
+ export interface DriverMeta {
22
+ id: string;
23
+ kind: 'platform' | 'capability';
24
+ package: string;
25
+ runtime?: string;
26
+ adapter?: string; // registry adapter name (defaults to id)
27
+ capabilities: string[];
28
+ unblocks?: string[];
29
+ }
30
+
31
+ export function capabilitiesPath(cwd: string): string {
32
+ return path.join(cwd, 'qa', 'capabilities.yaml');
33
+ }
34
+
35
+ export function readCapabilities(cwd: string): CapabilityProfile {
36
+ const p = capabilitiesPath(cwd);
37
+ if (!fs.existsSync(p)) return { platform: undefined, enabled: [], source: 'absent' };
38
+ try {
39
+ const y = parseYaml(fs.readFileSync(p, 'utf-8')) || {};
40
+ return {
41
+ platform: typeof y.platform === 'string' ? y.platform : undefined,
42
+ enabled: Array.isArray(y.enabled) ? y.enabled.map(String) : [],
43
+ source: 'capabilities.yaml',
44
+ };
45
+ } catch {
46
+ return { platform: undefined, enabled: [], source: 'absent' };
47
+ }
48
+ }
49
+
50
+ export function writeCapabilities(cwd: string, profile: { platform?: string; enabled: string[] }): string {
51
+ const p = capabilitiesPath(cwd);
52
+ fs.mkdirSync(path.dirname(p), { recursive: true });
53
+ const body =
54
+ '# Sungen capability profile — which runtime/drivers this project uses.\n' +
55
+ '# platform: the runtime adapter (web → Playwright, mobile → Appium). No default.\n' +
56
+ '# enabled: drivers turned on (added via `sungen capability add <driver>`).\n\n' +
57
+ stringifyYaml({ platform: profile.platform, enabled: profile.enabled });
58
+ fs.writeFileSync(p, body, 'utf-8');
59
+ return p;
60
+ }
61
+
62
+ let _catalog: Record<string, DriverMeta> | null = null;
63
+ export function loadDriverCatalog(): Record<string, DriverMeta> {
64
+ if (_catalog) return _catalog;
65
+ const p = path.join(__dirname, 'catalog', 'drivers.yaml');
66
+ const y = parseYaml(fs.readFileSync(p, 'utf-8')) as { drivers: Record<string, Omit<DriverMeta, 'id'>> };
67
+ const out: Record<string, DriverMeta> = {};
68
+ for (const [id, meta] of Object.entries(y.drivers)) out[id] = { id, ...meta };
69
+ _catalog = out;
70
+ return out;
71
+ }
72
+
73
+ export function driverMeta(id: string): DriverMeta | undefined {
74
+ return loadDriverCatalog()[id];
75
+ }
@@ -0,0 +1,57 @@
1
+ # Driver Catalog (metadata only — NO driver code is bundled here).
2
+ # Lets Sungen RECOMMEND/RESOLVE a driver that may not be installed yet, and tells
3
+ # `sungen capability add` which package to install. See reports/sungen_phase2a_spec.md.
4
+ #
5
+ # kind: platform → the runtime/codegen adapter for a target (pick ONE per project)
6
+ # kind: capability → an extra ability added on top of a platform (Phase 3)
7
+ # unblocks: manual-reason codes (M1–M9) this driver can resolve (Phase 2b taxonomy)
8
+
9
+ drivers:
10
+ web:
11
+ kind: platform
12
+ package: "@sungen/driver-web" # Phase 2a: bundled Playwright adapter serves this (back-compat)
13
+ runtime: playwright
14
+ adapter: web # registry adapter name
15
+ capabilities: ["@ui"]
16
+ mobile:
17
+ kind: platform
18
+ package: "@sungen/driver-mobile"
19
+ runtime: appium
20
+ adapter: mobile
21
+ capabilities: ["@ui"]
22
+
23
+ api:
24
+ kind: capability
25
+ package: "@sungen/driver-api"
26
+ capabilities: ["@api", "@apiAssert", "@hybrid"]
27
+ unblocks: [M2]
28
+ data-factory:
29
+ kind: capability
30
+ package: "@sungen/driver-data-factory"
31
+ capabilities: ["@dataFactory"]
32
+ unblocks: [M1]
33
+ db:
34
+ kind: capability
35
+ package: "@sungen/driver-db"
36
+ capabilities: ["@dbAssert"]
37
+ unblocks: [M2]
38
+ mock:
39
+ kind: capability
40
+ package: "@sungen/driver-mock"
41
+ capabilities: ["@mock", "@network"]
42
+ unblocks: [M3]
43
+ mail-file:
44
+ kind: capability
45
+ package: "@sungen/driver-mail-file"
46
+ capabilities: ["@mail", "@file"]
47
+ unblocks: [M5]
48
+ contract:
49
+ kind: capability
50
+ package: "@sungen/driver-contract"
51
+ capabilities: ["@contract"]
52
+ unblocks: [M5]
53
+ specialized:
54
+ kind: capability
55
+ package: "@sungen/driver-specialized"
56
+ capabilities: ["@specialized"]
57
+ unblocks: [M6]