@sun-asterisk/sungen 3.1.2 → 3.2.0-beta.141

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 (290) hide show
  1. package/README.md +4 -428
  2. package/dist/capabilities/builtins.d.ts +31 -0
  3. package/dist/capabilities/builtins.d.ts.map +1 -0
  4. package/dist/capabilities/builtins.js +84 -0
  5. package/dist/capabilities/builtins.js.map +1 -0
  6. package/dist/capabilities/context-router.d.ts +34 -0
  7. package/dist/capabilities/context-router.d.ts.map +1 -0
  8. package/dist/capabilities/context-router.js +49 -0
  9. package/dist/capabilities/context-router.js.map +1 -0
  10. package/dist/capabilities/context.d.ts +68 -0
  11. package/dist/capabilities/context.d.ts.map +1 -0
  12. package/dist/capabilities/context.js +17 -0
  13. package/dist/capabilities/context.js.map +1 -0
  14. package/dist/capabilities/discover.d.ts +2 -0
  15. package/dist/capabilities/discover.d.ts.map +1 -0
  16. package/dist/capabilities/discover.js +109 -0
  17. package/dist/capabilities/discover.js.map +1 -0
  18. package/dist/capabilities/registry.d.ts +92 -0
  19. package/dist/capabilities/registry.d.ts.map +1 -0
  20. package/dist/capabilities/registry.js +43 -0
  21. package/dist/capabilities/registry.js.map +1 -0
  22. package/dist/capabilities/sensor.d.ts +52 -0
  23. package/dist/capabilities/sensor.d.ts.map +1 -0
  24. package/dist/capabilities/sensor.js +3 -0
  25. package/dist/capabilities/sensor.js.map +1 -0
  26. package/dist/cli/commands/audit.d.ts.map +1 -1
  27. package/dist/cli/commands/audit.js +17 -11
  28. package/dist/cli/commands/audit.js.map +1 -1
  29. package/dist/cli/commands/capability.d.ts.map +1 -1
  30. package/dist/cli/commands/capability.js +57 -5
  31. package/dist/cli/commands/capability.js.map +1 -1
  32. package/dist/cli/commands/context.d.ts +9 -0
  33. package/dist/cli/commands/context.d.ts.map +1 -0
  34. package/dist/cli/commands/context.js +91 -0
  35. package/dist/cli/commands/context.js.map +1 -0
  36. package/dist/cli/commands/delivery.d.ts.map +1 -1
  37. package/dist/cli/commands/delivery.js +42 -30
  38. package/dist/cli/commands/delivery.js.map +1 -1
  39. package/dist/cli/commands/generate.d.ts.map +1 -1
  40. package/dist/cli/commands/generate.js +35 -8
  41. package/dist/cli/commands/generate.js.map +1 -1
  42. package/dist/cli/commands/ledger.d.ts.map +1 -1
  43. package/dist/cli/commands/ledger.js +15 -5
  44. package/dist/cli/commands/ledger.js.map +1 -1
  45. package/dist/cli/commands/manifest.d.ts.map +1 -1
  46. package/dist/cli/commands/manifest.js +10 -9
  47. package/dist/cli/commands/manifest.js.map +1 -1
  48. package/dist/cli/commands/repair.d.ts +8 -0
  49. package/dist/cli/commands/repair.d.ts.map +1 -0
  50. package/dist/cli/commands/repair.js +97 -0
  51. package/dist/cli/commands/repair.js.map +1 -0
  52. package/dist/cli/commands/script-check.d.ts.map +1 -1
  53. package/dist/cli/commands/script-check.js +13 -9
  54. package/dist/cli/commands/script-check.js.map +1 -1
  55. package/dist/cli/commands/trace.d.ts.map +1 -1
  56. package/dist/cli/commands/trace.js +7 -4
  57. package/dist/cli/commands/trace.js.map +1 -1
  58. package/dist/cli/index.js +14 -1
  59. package/dist/cli/index.js.map +1 -1
  60. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +1 -0
  61. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  62. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +1 -0
  63. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  64. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  65. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  66. package/dist/generators/test-generator/code-generator.d.ts +18 -9
  67. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  68. package/dist/generators/test-generator/code-generator.js +162 -115
  69. package/dist/generators/test-generator/code-generator.js.map +1 -1
  70. package/dist/generators/test-generator/patterns/index.d.ts +0 -10
  71. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
  72. package/dist/generators/test-generator/patterns/index.js +10 -47
  73. package/dist/generators/test-generator/patterns/index.js.map +1 -1
  74. package/dist/generators/test-generator/template-engine.d.ts +1 -0
  75. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  76. package/dist/generators/test-generator/template-engine.js +1 -1
  77. package/dist/generators/test-generator/template-engine.js.map +1 -1
  78. package/dist/harness/annotation-overrides.d.ts +11 -0
  79. package/dist/harness/annotation-overrides.d.ts.map +1 -0
  80. package/dist/harness/annotation-overrides.js +38 -0
  81. package/dist/harness/annotation-overrides.js.map +1 -0
  82. package/dist/harness/audit.d.ts +9 -1
  83. package/dist/harness/audit.d.ts.map +1 -1
  84. package/dist/harness/audit.js +140 -10
  85. package/dist/harness/audit.js.map +1 -1
  86. package/dist/harness/capability-plan.d.ts +14 -0
  87. package/dist/harness/capability-plan.d.ts.map +1 -1
  88. package/dist/harness/capability-plan.js +63 -1
  89. package/dist/harness/capability-plan.js.map +1 -1
  90. package/dist/harness/catalog/drivers.yaml +35 -12
  91. package/dist/harness/data-driven-lint.d.ts.map +1 -1
  92. package/dist/harness/data-driven-lint.js +23 -0
  93. package/dist/harness/data-driven-lint.js.map +1 -1
  94. package/dist/harness/flow-check.d.ts +9 -0
  95. package/dist/harness/flow-check.d.ts.map +1 -1
  96. package/dist/harness/flow-check.js +13 -6
  97. package/dist/harness/flow-check.js.map +1 -1
  98. package/dist/harness/intent.d.ts +6 -0
  99. package/dist/harness/intent.d.ts.map +1 -1
  100. package/dist/harness/intent.js +20 -4
  101. package/dist/harness/intent.js.map +1 -1
  102. package/dist/harness/ledger.d.ts.map +1 -1
  103. package/dist/harness/ledger.js +3 -2
  104. package/dist/harness/ledger.js.map +1 -1
  105. package/dist/harness/manifest.d.ts.map +1 -1
  106. package/dist/harness/manifest.js +3 -2
  107. package/dist/harness/manifest.js.map +1 -1
  108. package/dist/harness/parse.d.ts +2 -0
  109. package/dist/harness/parse.d.ts.map +1 -1
  110. package/dist/harness/parse.js +16 -4
  111. package/dist/harness/parse.js.map +1 -1
  112. package/dist/harness/quality-gates.js +1 -1
  113. package/dist/harness/quality-gates.js.map +1 -1
  114. package/dist/harness/query-catalog.d.ts.map +1 -1
  115. package/dist/harness/query-catalog.js +0 -0
  116. package/dist/harness/query-catalog.js.map +1 -1
  117. package/dist/harness/repair.d.ts +20 -0
  118. package/dist/harness/repair.d.ts.map +1 -0
  119. package/dist/harness/repair.js +111 -0
  120. package/dist/harness/repair.js.map +1 -0
  121. package/dist/harness/script-check.d.ts +3 -1
  122. package/dist/harness/script-check.d.ts.map +1 -1
  123. package/dist/harness/script-check.js +22 -8
  124. package/dist/harness/script-check.js.map +1 -1
  125. package/dist/harness/sensors.d.ts +40 -0
  126. package/dist/harness/sensors.d.ts.map +1 -1
  127. package/dist/harness/sensors.js +54 -2
  128. package/dist/harness/sensors.js.map +1 -1
  129. package/dist/harness/trace.d.ts.map +1 -1
  130. package/dist/harness/trace.js +4 -3
  131. package/dist/harness/trace.js.map +1 -1
  132. package/dist/harness/unit-paths.d.ts +3 -0
  133. package/dist/harness/unit-paths.d.ts.map +1 -0
  134. package/dist/harness/unit-paths.js +52 -0
  135. package/dist/harness/unit-paths.js.map +1 -0
  136. package/dist/index.d.ts +22 -0
  137. package/dist/index.d.ts.map +1 -0
  138. package/dist/index.js +36 -0
  139. package/dist/index.js.map +1 -0
  140. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  141. package/dist/orchestrator/ai-rules-updater.js +2 -0
  142. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  143. package/dist/orchestrator/context-discovery.d.ts +12 -0
  144. package/dist/orchestrator/context-discovery.d.ts.map +1 -0
  145. package/dist/orchestrator/context-discovery.js +46 -0
  146. package/dist/orchestrator/context-discovery.js.map +1 -0
  147. package/dist/orchestrator/templates/ai-instructions/claude-agent-reviewer.md +7 -1
  148. package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +10 -5
  149. package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +18 -1
  150. package/dist/orchestrator/templates/ai-instructions/claude-skill-api-design.md +62 -0
  151. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +1 -0
  152. package/dist/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md +2 -1
  153. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +19 -2
  154. package/dist/orchestrator/templates/ai-instructions/claude-skill-viewpoint.md +14 -0
  155. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +10 -5
  156. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +11 -1
  157. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-api-design.md +62 -0
  158. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +1 -0
  159. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +2 -1
  160. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +19 -2
  161. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint.md +14 -0
  162. package/dist/orchestrator/templates/specs-api.d.ts +55 -0
  163. package/dist/orchestrator/templates/specs-api.d.ts.map +1 -0
  164. package/dist/orchestrator/templates/specs-api.js +171 -0
  165. package/dist/orchestrator/templates/specs-api.js.map +1 -0
  166. package/dist/orchestrator/templates/specs-api.ts +154 -0
  167. package/dist/orchestrator/templates/specs-db.d.ts +3 -0
  168. package/dist/orchestrator/templates/specs-db.d.ts.map +1 -1
  169. package/dist/orchestrator/templates/specs-db.js +78 -1
  170. package/dist/orchestrator/templates/specs-db.js.map +1 -1
  171. package/dist/orchestrator/templates/specs-db.ts +78 -1
  172. package/dist/orchestrator/templates/specs-test-data.ts +2 -1
  173. package/package.json +7 -30
  174. package/src/capabilities/builtins.ts +85 -0
  175. package/src/capabilities/context-router.ts +66 -0
  176. package/src/capabilities/context.ts +65 -0
  177. package/src/capabilities/discover.ts +62 -0
  178. package/src/capabilities/registry.ts +113 -0
  179. package/src/capabilities/sensor.ts +47 -0
  180. package/src/cli/commands/audit.ts +15 -9
  181. package/src/cli/commands/capability.ts +53 -5
  182. package/src/cli/commands/context.ts +52 -0
  183. package/src/cli/commands/delivery.ts +40 -31
  184. package/src/cli/commands/generate.ts +37 -8
  185. package/src/cli/commands/ledger.ts +13 -5
  186. package/src/cli/commands/manifest.ts +9 -7
  187. package/src/cli/commands/repair.ts +57 -0
  188. package/src/cli/commands/script-check.ts +12 -8
  189. package/src/cli/commands/trace.ts +7 -4
  190. package/src/cli/index.ts +14 -1
  191. package/src/generators/test-generator/adapters/adapter-interface.ts +1 -1
  192. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +1 -1
  193. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  194. package/src/generators/test-generator/code-generator.ts +163 -111
  195. package/src/generators/test-generator/patterns/index.ts +9 -35
  196. package/src/generators/test-generator/template-engine.ts +2 -2
  197. package/src/harness/annotation-overrides.ts +27 -0
  198. package/src/harness/audit.ts +141 -12
  199. package/src/harness/capability-plan.ts +51 -1
  200. package/src/harness/catalog/drivers.yaml +35 -12
  201. package/src/harness/data-driven-lint.ts +20 -0
  202. package/src/harness/flow-check.ts +15 -6
  203. package/src/harness/intent.ts +25 -4
  204. package/src/harness/ledger.ts +3 -2
  205. package/src/harness/manifest.ts +3 -2
  206. package/src/harness/parse.ts +11 -2
  207. package/src/harness/quality-gates.ts +1 -1
  208. package/src/harness/query-catalog.ts +0 -0
  209. package/src/harness/repair.ts +75 -0
  210. package/src/harness/script-check.ts +25 -8
  211. package/src/harness/sensors.ts +71 -2
  212. package/src/harness/trace.ts +4 -3
  213. package/src/harness/unit-paths.ts +14 -0
  214. package/src/index.ts +32 -0
  215. package/src/orchestrator/ai-rules-updater.ts +2 -0
  216. package/src/orchestrator/context-discovery.ts +50 -0
  217. package/src/orchestrator/templates/ai-instructions/claude-agent-reviewer.md +7 -1
  218. package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +10 -5
  219. package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +18 -1
  220. package/src/orchestrator/templates/ai-instructions/claude-skill-api-design.md +62 -0
  221. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +1 -0
  222. package/src/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md +2 -1
  223. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +19 -2
  224. package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint.md +14 -0
  225. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +10 -5
  226. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +11 -1
  227. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-api-design.md +62 -0
  228. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +1 -0
  229. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +2 -1
  230. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +19 -2
  231. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint.md +14 -0
  232. package/src/orchestrator/templates/specs-api.ts +154 -0
  233. package/src/orchestrator/templates/specs-db.ts +78 -1
  234. package/src/orchestrator/templates/specs-test-data.ts +2 -1
  235. package/dist/generators/test-generator/patterns/assertion-patterns.d.ts +0 -7
  236. package/dist/generators/test-generator/patterns/assertion-patterns.d.ts.map +0 -1
  237. package/dist/generators/test-generator/patterns/assertion-patterns.js +0 -626
  238. package/dist/generators/test-generator/patterns/assertion-patterns.js.map +0 -1
  239. package/dist/generators/test-generator/patterns/capture-patterns.d.ts +0 -21
  240. package/dist/generators/test-generator/patterns/capture-patterns.d.ts.map +0 -1
  241. package/dist/generators/test-generator/patterns/capture-patterns.js +0 -87
  242. package/dist/generators/test-generator/patterns/capture-patterns.js.map +0 -1
  243. package/dist/generators/test-generator/patterns/database-patterns.d.ts +0 -6
  244. package/dist/generators/test-generator/patterns/database-patterns.d.ts.map +0 -1
  245. package/dist/generators/test-generator/patterns/database-patterns.js +0 -95
  246. package/dist/generators/test-generator/patterns/database-patterns.js.map +0 -1
  247. package/dist/generators/test-generator/patterns/form-patterns.d.ts +0 -6
  248. package/dist/generators/test-generator/patterns/form-patterns.d.ts.map +0 -1
  249. package/dist/generators/test-generator/patterns/form-patterns.js +0 -160
  250. package/dist/generators/test-generator/patterns/form-patterns.js.map +0 -1
  251. package/dist/generators/test-generator/patterns/interaction-patterns.d.ts +0 -6
  252. package/dist/generators/test-generator/patterns/interaction-patterns.d.ts.map +0 -1
  253. package/dist/generators/test-generator/patterns/interaction-patterns.js +0 -433
  254. package/dist/generators/test-generator/patterns/interaction-patterns.js.map +0 -1
  255. package/dist/generators/test-generator/patterns/keyboard-patterns.d.ts +0 -7
  256. package/dist/generators/test-generator/patterns/keyboard-patterns.d.ts.map +0 -1
  257. package/dist/generators/test-generator/patterns/keyboard-patterns.js +0 -47
  258. package/dist/generators/test-generator/patterns/keyboard-patterns.js.map +0 -1
  259. package/dist/generators/test-generator/patterns/navigation-patterns.d.ts +0 -6
  260. package/dist/generators/test-generator/patterns/navigation-patterns.d.ts.map +0 -1
  261. package/dist/generators/test-generator/patterns/navigation-patterns.js +0 -125
  262. package/dist/generators/test-generator/patterns/navigation-patterns.js.map +0 -1
  263. package/dist/generators/test-generator/patterns/scope-patterns.d.ts +0 -7
  264. package/dist/generators/test-generator/patterns/scope-patterns.d.ts.map +0 -1
  265. package/dist/generators/test-generator/patterns/scope-patterns.js +0 -36
  266. package/dist/generators/test-generator/patterns/scope-patterns.js.map +0 -1
  267. package/dist/generators/test-generator/patterns/scroll-patterns.d.ts +0 -7
  268. package/dist/generators/test-generator/patterns/scroll-patterns.d.ts.map +0 -1
  269. package/dist/generators/test-generator/patterns/scroll-patterns.js +0 -25
  270. package/dist/generators/test-generator/patterns/scroll-patterns.js.map +0 -1
  271. package/dist/generators/test-generator/patterns/setup-patterns.d.ts +0 -6
  272. package/dist/generators/test-generator/patterns/setup-patterns.d.ts.map +0 -1
  273. package/dist/generators/test-generator/patterns/setup-patterns.js +0 -72
  274. package/dist/generators/test-generator/patterns/setup-patterns.js.map +0 -1
  275. package/dist/generators/test-generator/patterns/table-patterns.d.ts +0 -19
  276. package/dist/generators/test-generator/patterns/table-patterns.d.ts.map +0 -1
  277. package/dist/generators/test-generator/patterns/table-patterns.js +0 -239
  278. package/dist/generators/test-generator/patterns/table-patterns.js.map +0 -1
  279. package/docs/orchestration-spec.md +0 -267
  280. package/src/generators/test-generator/patterns/assertion-patterns.ts +0 -691
  281. package/src/generators/test-generator/patterns/capture-patterns.ts +0 -97
  282. package/src/generators/test-generator/patterns/database-patterns.ts +0 -96
  283. package/src/generators/test-generator/patterns/form-patterns.ts +0 -167
  284. package/src/generators/test-generator/patterns/interaction-patterns.ts +0 -465
  285. package/src/generators/test-generator/patterns/keyboard-patterns.ts +0 -51
  286. package/src/generators/test-generator/patterns/navigation-patterns.ts +0 -140
  287. package/src/generators/test-generator/patterns/scope-patterns.ts +0 -40
  288. package/src/generators/test-generator/patterns/scroll-patterns.ts +0 -27
  289. package/src/generators/test-generator/patterns/setup-patterns.ts +0 -76
  290. package/src/generators/test-generator/patterns/table-patterns.ts +0 -279
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@sun-asterisk/sungen",
3
- "version": "3.1.2",
3
+ "version": "3.2.0-beta.141",
4
4
  "description": "Deterministic E2E Test Compiler - Gherkin + Selectors → Playwright tests",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
5
+ "main": "src/index.ts",
6
+ "types": "src/index.ts",
7
7
  "bin": {
8
8
  "sungen": "./bin/sungen.js"
9
9
  },
10
10
  "scripts": {
11
- "build": "tsc && npm run copy-templates",
11
+ "build": "rm -rf dist && tsc && npm run copy-templates",
12
12
  "copy-templates": "mkdir -p dist/generators/test-generator/adapters/playwright/templates/steps && mkdir -p dist/generators/test-generator/templates && mkdir -p dist/orchestrator/templates && mkdir -p dist/dashboard/templates && cp -r src/generators/test-generator/adapters/playwright/templates/*.hbs dist/generators/test-generator/adapters/playwright/templates/ 2>/dev/null || true && cp -r src/generators/test-generator/adapters/playwright/templates/steps dist/generators/test-generator/adapters/playwright/templates/ && cp src/generators/test-generator/templates/*.hbs dist/generators/test-generator/templates/ 2>/dev/null || true && cp -r src/orchestrator/templates/* dist/orchestrator/templates/ && cp src/dashboard/templates/index.html dist/dashboard/templates/index.html && mkdir -p dist/harness/catalog && cp src/harness/catalog/*.yaml dist/harness/catalog/",
13
- "build:dashboard": "cd dashboard && npm install --silent && npm run build && cd .. && cp dashboard/dist/index.html src/dashboard/templates/index.html",
13
+ "build:dashboard": "cd ../../dashboard && npm install --silent && npm run build && cd - && cp ../../dashboard/dist/index.html src/dashboard/templates/index.html",
14
14
  "dev": "tsx src/cli/index.ts",
15
- "test": "tsx tests/golden/run.ts && tsx tests/audit/run.ts && tsx tests/ingest/run.ts && tsx tests/eval/run.ts && tsx tests/exporter/run.ts",
15
+ "test": "tsx tests/golden/run.ts && tsx tests/audit/run.ts && tsx tests/ingest/run.ts && tsx tests/eval/run.ts && tsx tests/exporter/run.ts && tsx tests/capabilities/run.ts && tsx tests/openapi/run.ts && tsx tests/packaging/run.ts",
16
16
  "test:update": "tsx tests/golden/run.ts --update && tsx tests/audit/run.ts --update && tsx tests/ingest/run.ts --update",
17
17
  "prepublishOnly": "npm run build:dashboard && npm run build"
18
18
  },
@@ -33,6 +33,7 @@
33
33
  "node": ">=18.0.0"
34
34
  },
35
35
  "dependencies": {
36
+ "@sungen/driver-ui": "3.2.0-beta.141",
36
37
  "@anthropic-ai/sdk": "^0.71.0",
37
38
  "@babel/parser": "^7.28.5",
38
39
  "@babel/traverse": "^7.28.5",
@@ -52,37 +53,13 @@
52
53
  "yaml": "^2.8.2",
53
54
  "zod": "^4.1.13"
54
55
  },
55
- "devDependencies": {
56
- "@playwright/test": "^1.57.0",
57
- "@testing-library/jest-dom": "^6.9.1",
58
- "@testing-library/react": "^16.3.0",
59
- "@types/jsdom": "^27.0.0",
60
- "@types/node": "^20",
61
- "jsdom": "^27.3.0",
62
- "react": "^19.2.3",
63
- "react-dom": "^19.2.3",
64
- "typescript": "^5"
65
- },
66
56
  "peerDependencies": {
67
57
  "@playwright/test": "^1.57.0"
68
58
  },
69
- "overrides": {
70
- "brace-expansion": "^5.0.6",
71
- "uuid": "^11.1.1",
72
- "tmp": "^0.2.6",
73
- "esbuild": "^0.28.1"
74
- },
75
- "resolutions": {
76
- "brace-expansion": "^5.0.6",
77
- "uuid": "^11.1.1",
78
- "tmp": "^0.2.6",
79
- "esbuild": "^0.28.1"
80
- },
81
59
  "files": [
82
60
  "dist",
83
61
  "bin",
84
62
  "src",
85
- "docs/orchestration-spec.md",
86
63
  "README.md",
87
64
  "LICENSE"
88
65
  ]
@@ -0,0 +1,85 @@
1
+ /**
2
+ * In-core capability registrations (Capability SPI).
3
+ *
4
+ * `ui` has moved to `@sungen/driver-ui` (R5.4); `db` and `api` still register from here via
5
+ * `LOCAL_DRIVERS` (they relocate in R5.5/R5.6), and `core` (the generic expect/lint/verification
6
+ * layer) always stays in core. Discovery (`discover.ts`) loads the external driver(s) first, then
7
+ * these, then `core` — preserving the historical pattern composition order, so compiled output is
8
+ * byte-identical (golden + audit snapshots are the proof).
9
+ */
10
+ import type { CapabilityRegistry } from './registry';
11
+ import type { Sensor, AdvisoryScanInput, GateInput } from './sensor';
12
+ import { lintDataDriven } from '../harness/data-driven-lint';
13
+ import { resolveQuery, lintCatalog } from '../harness/query-catalog';
14
+ import { expectPatterns } from '../generators/test-generator/patterns/expect-patterns';
15
+
16
+ /** Advisory @cases/@query lint, exposed through the Sensor SPI (generate-time). */
17
+ const dataDrivenLintSensor: Sensor<AdvisoryScanInput> = {
18
+ id: 'data-driven-lint',
19
+ capability: 'core',
20
+ kind: 'advisory',
21
+ run: ({ dir, cwd }) =>
22
+ lintDataDriven(dir, cwd).map((w) => ({
23
+ sensorId: 'data-driven-lint',
24
+ capability: 'core',
25
+ scenario: w.scenario,
26
+ message: w.message,
27
+ severity: 'warn' as const,
28
+ })),
29
+ };
30
+
31
+ /**
32
+ * Generic `verification` gate sensor: every referenced named query (`@query`) must resolve in its
33
+ * catalog, and the catalog must lint clean. An unresolved/invalid reference is a gate-level error.
34
+ * (On a project with no `@query` refs it yields nothing.) `query-catalog` lives in core's harness —
35
+ * shared with the data-driven advisory lint. The `@api` counterpart now lives in
36
+ * `@sungen/driver-api`'s `api-verification` gate sensor. Message prefix unchanged → audit byte-identical.
37
+ */
38
+ const verificationGateSensor: Sensor<GateInput> = {
39
+ id: 'verification',
40
+ capability: 'core',
41
+ kind: 'gate',
42
+ run: ({ screenName, scenarios, cwd }) => {
43
+ const findings = [] as ReturnType<Sensor['run']>;
44
+ const fail = (msg: string) => findings.push({ sensorId: 'verification', capability: 'core', message: `VERIFICATION-FAIL: ${msg}`, severity: 'error' });
45
+
46
+ const queryRefs = new Set<string>();
47
+ for (const s of scenarios) for (const r of s.queryRefs ?? []) queryRefs.add(r);
48
+ for (const name of queryRefs) {
49
+ try { resolveQuery(name, screenName, cwd); } catch (e: any) { fail(e?.message || `query "${name}" does not resolve`); }
50
+ }
51
+ if (queryRefs.size) {
52
+ try { for (const err of lintCatalog(screenName, null, cwd).errors) fail(err); } catch { /* no catalog */ }
53
+ }
54
+ return findings;
55
+ },
56
+ };
57
+
58
+ /**
59
+ * core — generic, capability-agnostic steps (data-vs-data `expect`) + the advisory @cases/@query
60
+ * lint and the gate-level `verification` sensor. Stays in core (not a driver) — the always-present
61
+ * generic layer. All three real capabilities (ui/db/api) now ship as `@sungen/driver-*` packages.
62
+ */
63
+ export function registerCoreCapability(registry: CapabilityRegistry): void {
64
+ registry.register({
65
+ id: 'core',
66
+ patterns: [...expectPatterns],
67
+ sensors: [dataDrivenLintSensor, verificationGateSensor],
68
+ });
69
+ }
70
+
71
+ /**
72
+ * In-core driver manifest — now empty: `ui`, `db`, and `api` all ship as `@sungen/driver-*` packages
73
+ * loaded by discovery (`discover.ts`). Kept (empty) as the seam for any future in-core capability;
74
+ * `core` itself registers separately via `registerCoreCapability`.
75
+ */
76
+ export const LOCAL_DRIVERS: ReadonlyArray<{ capability: string; register: (r: CapabilityRegistry) => void }> = [];
77
+
78
+ /**
79
+ * @deprecated Use `discoverAndRegisterCapabilities()` from `./discover`. Kept as a thin alias so
80
+ * existing call-sites/tests don't break during R5; removed once everything routes through discovery.
81
+ */
82
+ export function registerBuiltinCapabilities(): void {
83
+ // Lazy import avoids a cycle (discover imports builtins).
84
+ require('./discover').discoverAndRegisterCapabilities();
85
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * ContextRouter (Capability SPI, R1 — skeleton).
3
+ *
4
+ * The overload guard: for each unit of work it decides the minimal slice of Context + the relevant
5
+ * capabilities + the gate to load, so a project with many drivers doesn't gather everything for
6
+ * every artifact (cost scales by *target*, not *repo × drivers*). See
7
+ * `docs/spec/sungen_capability_spi_spec.md` §5.
8
+ *
9
+ * R1 scope (skeleton, deterministic, not yet enforced in the pipeline): it computes the routing
10
+ * plan — which capabilities apply (the default/implicit one + any whose annotation tags appear),
11
+ * and which sensors gate the task. Scope-trimming/token-budget enforcement is wired in a later step;
12
+ * for now `scope.budget` is a placeholder (null = unbounded) and the plan is advisory.
13
+ */
14
+ import { capabilityRegistry } from './registry';
15
+
16
+ export interface RouteTask {
17
+ /** What is being worked on now. */
18
+ target: { kind: string; id: string };
19
+ /** What artifact is being produced/checked: 'feature' | 'scenario' | 'selectors' | 'apis.yaml' | … */
20
+ artifact: string;
21
+ /** Capability tags present on the target (e.g. ['@query'], ['@api'], ['@cases']). */
22
+ tags?: string[];
23
+ }
24
+
25
+ export interface RoutePlan {
26
+ /** Applicable capability ids: the default/implicit one + any whose annotations match the tags. */
27
+ capabilities: string[];
28
+ /** Gate sensors to run for this task (ids). */
29
+ gateSensorIds: string[];
30
+ /** Advisory sensors to run for this task (ids). */
31
+ advisorySensorIds: string[];
32
+ /** Scope: the target slice + a token/context budget (null = unbounded for now). */
33
+ scope: { target: RouteTask['target']; budget: number | null };
34
+ }
35
+
36
+ class ContextRouter {
37
+ /** Which capabilities a set of tags activates: the default capability + any owning a present tag. */
38
+ capabilitiesFor(tags: string[] = []): string[] {
39
+ const ids = new Set<string>();
40
+ const def = capabilityRegistry.defaultCapabilityId();
41
+ if (def) ids.add(def);
42
+ for (const cap of capabilityRegistry.all()) {
43
+ if ((cap.annotations ?? []).some((a) => tags.includes(a))) ids.add(cap.id);
44
+ }
45
+ return [...ids];
46
+ }
47
+
48
+ /** Compute the routing plan for a task (deterministic). */
49
+ route(task: RouteTask): RoutePlan {
50
+ const capabilities = this.capabilitiesFor(task.tags ?? []);
51
+ // Generic sensors (no capability, or the always-on `core`) run regardless of the task's tags;
52
+ // capability-specific sensors run only when their capability is in scope.
53
+ const inScope = (capId?: string) => capId == null || capId === 'core' || capabilities.includes(capId);
54
+ const gateSensorIds = capabilityRegistry.sensors('gate').filter((s) => inScope(s.capability)).map((s) => s.id);
55
+ const advisorySensorIds = capabilityRegistry.sensors('advisory').filter((s) => inScope(s.capability)).map((s) => s.id);
56
+ return {
57
+ capabilities,
58
+ gateSensorIds,
59
+ advisorySensorIds,
60
+ scope: { target: task.target, budget: null },
61
+ };
62
+ }
63
+ }
64
+
65
+ /** Process-wide singleton. */
66
+ export const contextRouter = new ContextRouter();
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Orchestration phase-hook SPIs (Capability SPI, R1 — phase hooks).
3
+ *
4
+ * The kernel owns the pipeline (Plan → Discover → Contextualize → Generate → Gate → Repair →
5
+ * Deliver); a capability fills the domain-specific phases via these hooks:
6
+ * - `DiscoveryProvider` — sources → a slice of the normalized `Context`.
7
+ * - `ContextMapper` — `Context` → generation units + the capability's MODES
8
+ * (ui: screen|flow; api: matrix|flow|async; db: query-catalog).
9
+ *
10
+ * R1 scope: these are **declared** (the contract) and attachable to a CapabilityDescriptor, but
11
+ * the kernel does not consume them yet — the pipeline still runs as before. R2 (driver-ui) is the
12
+ * first conformer that implements them; the orchestration is rewired to call them then.
13
+ * See `docs/spec/sungen_capability_spi_spec.md` §3–§6 and `sungen_ui_driver_spec.md`.
14
+ */
15
+
16
+ /** Normalized, capability-tagged discovery output. Lazy/sliced — never materialized whole. */
17
+ export interface Context {
18
+ target: { kind: string; id: string; capability?: string };
19
+ /** Lazy source handles (spec/viewpoint/openapi/routes/schema/liveSnapshot/figma/…). */
20
+ sources: Record<string, unknown>;
21
+ /** Capability-specific normalized facts (ui: elements/states/pageType; api: endpoints; db: tables). */
22
+ facts: Record<string, unknown>;
23
+ /** For run-time-connected capabilities (api/db): reachability probe result. */
24
+ connectivity?: { reachable: boolean; baseUrl?: string; probedAt?: string };
25
+ }
26
+
27
+ /** One unit of generation work the kernel will drive through Generate → Gate → Repair. */
28
+ export interface GenerationUnit {
29
+ /** Capability-defined mode: 'screen' | 'flow' | 'matrix' | 'async' | 'query-catalog' | … */
30
+ mode: string;
31
+ /** The slice of Context this unit covers (a screen, an endpoint group, a query set, …). */
32
+ targetSlice: unknown;
33
+ /** Band weight hint (source-aware) — the kernel supplies the band targets. */
34
+ weight?: number;
35
+ }
36
+
37
+ export interface DiscoveryProvider {
38
+ appliesTo(target: Context['target']): boolean;
39
+ /** Produce this capability's slice of the Context for the given target + requested scope. */
40
+ discover(target: Context['target'], scope: unknown): Promise<Partial<Context>>;
41
+ }
42
+
43
+ export interface ContextMapper {
44
+ /** Turn the Context into generation units (+ modes), honouring the kernel's band targets. */
45
+ decompose(ctx: Context, bands?: Record<string, number>): GenerationUnit[];
46
+ }
47
+
48
+ /**
49
+ * Repair phase hook — a capability's deterministic fix catalog. Each rule matches a finding (audit
50
+ * gap) or a runtime failure message and proposes a concrete fix. `sungen repair` gathers the
51
+ * unit-capability's rules and turns the findings/failures into an actionable plan; the AI repair
52
+ * loop and a human get the same proposals. Drivers/projects extend the catalog by adding rules.
53
+ */
54
+ export interface RepairRule {
55
+ /** Stable id, e.g. 'api-error' | 'api-auth'. */
56
+ id: string;
57
+ /** Matches a finding or failure message. */
58
+ match: RegExp;
59
+ /** The concrete repair instruction to apply. */
60
+ fix: string;
61
+ }
62
+
63
+ export interface RepairProvider {
64
+ rules: RepairRule[];
65
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Capability discovery (R5 packaging seam).
3
+ *
4
+ * Core registers capabilities without importing any driver package statically. External
5
+ * `@sungen/driver-*` packages are loaded at runtime via their `sungenDriver.register` entry point;
6
+ * the capabilities still in core (`db`, `api`) register from `LOCAL_DRIVERS`; the generic `core`
7
+ * capability registers last. Idempotent (guards on `registry.isPopulated()`).
8
+ *
9
+ * Ordering is deterministic and behaviour-preserving: external drivers (currently `@sungen/driver-ui`)
10
+ * load first, then `LOCAL_DRIVERS` (db → api), then `core` — i.e. the historical ui → db → api → core
11
+ * composition order, so pattern composition / compiled output is byte-identical.
12
+ *
13
+ * A driver that isn't installed is simply skipped (capabilities are opt-in). Errors thrown *inside* a
14
+ * present driver propagate — only a genuinely absent package is swallowed (resolve-then-require).
15
+ */
16
+ import { createRequire } from 'module';
17
+ import * as path from 'path';
18
+ import { capabilityRegistry } from './registry';
19
+ import { LOCAL_DRIVERS, registerCoreCapability } from './builtins';
20
+
21
+ /**
22
+ * Driver packages relocated out of core, loaded before the in-core `LOCAL_DRIVERS`. As `db`/`api`
23
+ * move to their packages (R5.5/R5.6) they join this list and leave `LOCAL_DRIVERS`; eventually this
24
+ * becomes a scan of installed `@sungen/driver-*` packages (R5.7).
25
+ */
26
+ const EXTERNAL_DRIVERS = ['@sungen/driver-ui', '@sungen/driver-db', '@sungen/driver-api'];
27
+
28
+ function loadExternalDriver(name: string): void {
29
+ // Resolve from the user's PROJECT first, then from core's own location. Opt-in drivers
30
+ // (`sungen capability add db|api`) install into the PROJECT's node_modules — a globally-installed
31
+ // core (`npm i -g`) wouldn't see them via a core-anchored `require`. The bundled @sungen/driver-ui
32
+ // is a core dependency (co-located with core), so the second anchor finds it.
33
+ for (const anchor of [path.join(process.cwd(), 'package.json'), __filename]) {
34
+ let req: NodeRequire;
35
+ try { req = createRequire(anchor); } catch { continue; }
36
+ let resolved: string;
37
+ try { resolved = req.resolve(name); } catch { continue; } // not installed at this anchor → try next
38
+ try {
39
+ const mod = req(resolved);
40
+ const register: ((r: typeof capabilityRegistry) => void) | undefined = mod?.sungenDriver?.register ?? mod?.register;
41
+ if (typeof register === 'function') register(capabilityRegistry);
42
+ } catch (e) {
43
+ // A present-but-BROKEN driver (e.g. a stale/incompatible leftover whose own core dep doesn't
44
+ // resolve) must never crash the whole CLI — `sungen --version`, `capability remove`, etc. must
45
+ // still work. Warn + skip so the user can recover.
46
+ console.warn(
47
+ `⚠ sungen: capability driver "${name}" is installed but failed to load — skipping.\n` +
48
+ ` ${(e as Error)?.message || e}\n` +
49
+ ` Fix: reinstall it (\`sungen capability add ${name.replace(/^@sungen\/driver-/, '')}\`) or remove it.`,
50
+ );
51
+ }
52
+ return; // found at this anchor (loaded or warned) — don't fall through to other anchors
53
+ }
54
+ // not installed at any anchor — capabilities are opt-in, so skip silently
55
+ }
56
+
57
+ export function discoverAndRegisterCapabilities(): void {
58
+ if (capabilityRegistry.isPopulated()) return;
59
+ for (const name of EXTERNAL_DRIVERS) loadExternalDriver(name); // ui (external) — first, default capability
60
+ for (const driver of LOCAL_DRIVERS) driver.register(capabilityRegistry); // db, api (in-core for now)
61
+ registerCoreCapability(capabilityRegistry); // generic layer, always present, registered last
62
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Capability registry — the extension seam for the microkernel (Capability SPI, R1).
3
+ *
4
+ * Replaces hardcoded `this.patterns.push(...)` wiring with **registration**: each capability
5
+ * (ui · db · api · …) declares what it contributes; the kernel composes it. This is the
6
+ * "shared framework, pull in what you use" mechanism (`docs/spec/sungen_capability_spi_spec.md`).
7
+ *
8
+ * R1 scope (behaviour-preserving): only the **patterns** slice is wired through here — the set of
9
+ * registered patterns is identical to the old hardcoded list, so compiled output is unchanged
10
+ * (golden + audit snapshots are the contract). The remaining SPI slices (discovery, contextMapper,
11
+ * sensors, adapter, viewpoints, runtimeHelpers) are declared on the descriptor for the upcoming
12
+ * steps but are not consumed yet.
13
+ */
14
+ import type { StepPattern } from '../generators/test-generator/patterns/types';
15
+ import type { Sensor } from './sensor';
16
+ import type { DiscoveryProvider, ContextMapper, RepairProvider } from './context';
17
+
18
+ export interface CapabilityDescriptor {
19
+ /** Stable id: 'ui' | 'db' | 'api' | 'core' | … */
20
+ id: string;
21
+ /** The implicit/default capability (a scenario with no capability tag). UI sets this. */
22
+ default?: boolean;
23
+ /** Annotation tags this capability owns (e.g. ['@query'], ['@api','@hybrid']). */
24
+ annotations?: string[];
25
+ /** Step patterns this capability contributes to the compiler. */
26
+ patterns?: StepPattern[];
27
+ /** Harness sensors this capability contributes (advisory and/or gate). */
28
+ sensors?: Sensor[];
29
+ /** Orchestration phase hooks — declared in R1, consumed by the pipeline from R2 onward. */
30
+ discovery?: DiscoveryProvider; // sources → Context slice
31
+ contextMapper?: ContextMapper; // Context → generation units + modes
32
+ /** Repair phase: the capability's deterministic fix catalog (audit/runtime finding → fix). */
33
+ repair?: RepairProvider;
34
+ /** Provider for this capability's viewpoint catalog (UI: the 17-pattern universal catalog). */
35
+ viewpoints?: () => unknown;
36
+ /**
37
+ * Score-bearing gate computation owned by this capability (UI: viewpoint coverage + assertion
38
+ * depth). The audit engine calls it and assembles the score from the result. Typed generically
39
+ * so the registry stays decoupled from harness internals; the caller knows the concrete shape.
40
+ */
41
+ gateProvider?: (input: unknown) => unknown;
42
+ /** Runtime helper file(s) this capability emits into specs/ when it is active for a feature
43
+ * (db → specs/db.ts). Emitted via the same sync-when-changed mechanism. */
44
+ runtimeHelpers?: Array<{ file: string; template: string }>;
45
+ /** Optional step detector: the capability is "active" for a feature if any step matches —
46
+ * for steps that carry no annotation tag (db's declarative `User see [t] row where …`). */
47
+ detectsStep?: (stepText: string) => boolean;
48
+ /**
49
+ * Precondition codegen for this capability's annotations on a scenario (db's `@query:<name>`
50
+ * → `testData.bind(name, await db.fetchQuery(...))`). Returns raw statements (the compiler
51
+ * indents) + `boundVars` (the `{{name}}` variables it binds, so the compiler registers them as
52
+ * runtime-resolved). Keeps `@query` codegen owned by `db`, not hardcoded in the compiler.
53
+ */
54
+ preconditionCodegen?: (input: { tags: string[]; screenName: string; cwd: string }) =>
55
+ Array<{ comment?: string; code: string; boundVars?: string[] }>;
56
+
57
+ /**
58
+ * CLI commands this capability contributes. The CLI calls each with the commander `program`
59
+ * (typed generically so the registry stays decoupled from commander; the driver knows the shape).
60
+ * Lets a driver own its authoring commands (api → `sungen api import`) instead of hardcoding them
61
+ * in core's CLI. Invoked once, after discovery, in `src/cli/index.ts`.
62
+ */
63
+ cliCommands?: Array<(program: unknown) => void>;
64
+
65
+ // --- Declared for later R-steps; not consumed yet (kept here so the SPI shape is visible). ---
66
+ // adapter?: string; // codegen adapter id (existing adapterRegistry)
67
+ }
68
+
69
+ export class CapabilityRegistry {
70
+ private caps = new Map<string, CapabilityDescriptor>();
71
+
72
+ /** Register (or replace, by id) a capability descriptor. Idempotent by id. */
73
+ register(descriptor: CapabilityDescriptor): void {
74
+ this.caps.set(descriptor.id, descriptor);
75
+ }
76
+
77
+ get(id: string): CapabilityDescriptor | undefined {
78
+ return this.caps.get(id);
79
+ }
80
+
81
+ /** True once any capability is registered — discovery guards on this (replaces the builtins boolean). */
82
+ isPopulated(): boolean {
83
+ return this.caps.size > 0;
84
+ }
85
+
86
+ all(): CapabilityDescriptor[] {
87
+ return [...this.caps.values()];
88
+ }
89
+
90
+ /** The id of the implicit/default capability (UI), if registered. */
91
+ defaultCapabilityId(): string | undefined {
92
+ return this.all().find((c) => c.default)?.id;
93
+ }
94
+
95
+ /** All step patterns contributed by registered capabilities (composition order = registration order). */
96
+ patterns(): StepPattern[] {
97
+ return this.all().flatMap((c) => c.patterns ?? []);
98
+ }
99
+
100
+ /** All registered sensors, optionally filtered by kind ('advisory' | 'gate'). */
101
+ sensors(kind?: Sensor['kind']): Sensor[] {
102
+ const all = this.all().flatMap((c) => c.sensors ?? []);
103
+ return kind ? all.filter((s) => s.kind === kind) : all;
104
+ }
105
+
106
+ /** Test seam: drop all registrations. */
107
+ _reset(): void {
108
+ this.caps.clear();
109
+ }
110
+ }
111
+
112
+ /** Process-wide singleton (mirrors `adapterRegistry`). */
113
+ export const capabilityRegistry = new CapabilityRegistry();
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Harness Sensor SPI (Capability SPI, R1 — Sensor step).
3
+ *
4
+ * A sensor is a deterministic harness check a capability contributes. The kernel collects all
5
+ * registered sensors and runs them; findings merge into the harness output. Two kinds:
6
+ * - `advisory` — surfaced as warnings (e.g. at `sungen generate`), never blocks.
7
+ * - `gate` — feeds the audit scorecard / pass-fail (migrated from the hardcoded `audit.ts`
8
+ * calls in the next sub-step; gate sensors carry an intent-aware threshold).
9
+ *
10
+ * Generic over the input so the same registry holds both generate-time advisory sensors
11
+ * (which scan a screen/flow dir) and audit-time gate sensors (which read parsed scenarios) —
12
+ * each sensor declares the input it consumes; callers filter by `kind` and pass the right input.
13
+ */
14
+ export interface SensorFinding {
15
+ sensorId: string;
16
+ capability?: string;
17
+ scenario?: string;
18
+ message: string;
19
+ severity?: 'info' | 'warn' | 'error';
20
+ }
21
+
22
+ export interface Sensor<I = unknown> {
23
+ id: string;
24
+ capability?: string;
25
+ kind: 'gate' | 'advisory';
26
+ run(input: I): SensorFinding[];
27
+ }
28
+
29
+ /** Input for generate-time advisory sensors that scan a screen/flow directory. */
30
+ export interface AdvisoryScanInput {
31
+ dir: string;
32
+ cwd: string;
33
+ }
34
+
35
+ /**
36
+ * Input for audit-time gate sensors. Intentionally minimal/structural (not the full ScenarioInfo
37
+ * type) so `src/capabilities` doesn't depend on harness internals — the audit caller passes the
38
+ * parsed scenarios it already has.
39
+ */
40
+ export interface GateInput {
41
+ screenName: string;
42
+ cwd: string;
43
+ featureText: string;
44
+ scenarios: Array<{ name: string; queryRefs?: string[]; apiRefs?: string[]; casesDataset?: string; stepsText?: string; manual?: boolean }>;
45
+ /** UI: universal-viewpoint theme gaps the coverage gate found (generic string list). */
46
+ universalGaps?: string[];
47
+ }
@@ -2,12 +2,16 @@ import { Command } from 'commander';
2
2
  import * as path from 'path';
3
3
  import * as fs from 'fs';
4
4
  import { runAudit, AuditReport } from '../../harness/audit';
5
+ import { reportSlug } from '../../harness/unit-paths';
5
6
 
6
7
  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;
8
+ // `name` may be a bare screen/flow/area, or an api unit id (`api/<area>`, `api/flows/<flow>`).
9
+ const candidates = [
10
+ path.join(process.cwd(), 'qa', 'screens', name),
11
+ path.join(process.cwd(), 'qa', 'flows', name),
12
+ path.join(process.cwd(), 'qa', 'api', name), // qa/api/<area> or qa/api/flows/<flow>
13
+ ];
14
+ for (const c of candidates) if (fs.existsSync(c)) return c;
11
15
  return null;
12
16
  }
13
17
 
@@ -88,20 +92,22 @@ export function registerAuditCommand(program: Command): void {
88
92
  .command('audit')
89
93
  .description('Harness: measure test-design quality (viewpoint gate, depth, balance, duplicates, traceability)')
90
94
  .option('-s, --screen <name>', 'Screen or flow name to audit')
95
+ .option('--api <name>', 'API-first area or api flow to audit (e.g. orders, flows/signup)')
96
+ .option('--area <name>', 'Alias of --api — an API-first area (qa/api/<name>)')
91
97
  .option('--json', 'Output the raw JSON report')
92
98
  .action((options) => {
93
99
  try {
94
- const name = options.screen;
95
- if (!name) throw new Error('Provide --screen <name>');
100
+ const name = options.screen || options.api || options.area;
101
+ if (!name) throw new Error('Provide --screen <name> (or --api <area>)');
96
102
  const dir = findScreenDir(name);
97
- if (!dir) throw new Error(`Screen/flow not found: qa/screens/${name} or qa/flows/${name}`);
103
+ if (!dir) throw new Error(`Not found: qa/screens/${name}, qa/flows/${name}, or qa/api/${name}`);
98
104
 
99
105
  const report = runAudit(dir, name);
100
106
 
101
- // Persist report under .sungen/reports/
107
+ // Persist report under .sungen/reports/ (flat slug: api flow `flows/x` → `flows-x-audit.json`).
102
108
  const outDir = path.join(process.cwd(), '.sungen', 'reports');
103
109
  fs.mkdirSync(outDir, { recursive: true });
104
- const outPath = path.join(outDir, `${name}-audit.json`);
110
+ const outPath = path.join(outDir, `${reportSlug(name)}-audit.json`);
105
111
  fs.writeFileSync(outPath, JSON.stringify(report, null, 2), 'utf-8');
106
112
 
107
113
  if (options.json) {
@@ -1,5 +1,6 @@
1
1
  import { Command } from 'commander';
2
2
  import { spawnSync } from 'child_process';
3
+ import { createRequire } from 'module';
3
4
  import * as fs from 'fs';
4
5
  import * as path from 'path';
5
6
  import {
@@ -8,6 +9,42 @@ import {
8
9
  import { buildPlan, MANUAL_REASONS } from '../../harness/capability-plan';
9
10
  import { adapterRegistry } from '../../generators/test-generator/adapters';
10
11
 
12
+ /** The running CLI's (core) version — opt-in drivers install in lockstep with it (not `@latest`). */
13
+ function coreVersion(): string {
14
+ try { return require('../../../package.json').version || 'latest'; } catch { return 'latest'; }
15
+ }
16
+
17
+ /**
18
+ * Verify a capability driver (db/api/…) installed into the PROJECT exposes the capability SPI entry
19
+ * (`sungenDriver.register`). Resolved from the project's node_modules so it works with a globally
20
+ * installed core. Capability drivers are NOT codegen adapters — don't route them through loadDriver.
21
+ */
22
+ function verifyCapabilityDriver(pkg: string, cwd: string): void {
23
+ const req = createRequire(path.join(cwd, 'package.json'));
24
+ // 1) Installed? (resolve only — distinguishes "not installed" from "installed but broken")
25
+ try {
26
+ req.resolve(pkg);
27
+ } catch {
28
+ throw new Error(`${pkg} is not installed in this project — run \`npm install -D ${pkg}\` here (a package.json must exist in this directory).`);
29
+ }
30
+ // 2) Loadable? Surface the REAL error — a failure here is almost always a stale/wrong
31
+ // @sun-asterisk/sungen on the module path (e.g. leftover node_modules), NOT a missing driver.
32
+ let mod: any;
33
+ try {
34
+ mod = req(pkg);
35
+ } catch (e: any) {
36
+ throw new Error(
37
+ `${pkg} is installed but failed to load:\n ${e?.message || e}\n` +
38
+ ' This usually means a stale @sun-asterisk/sungen is on the module path. Run in a clean,\n' +
39
+ ' dedicated project directory and remove stray installs (e.g. rm -rf ~/node_modules).',
40
+ );
41
+ }
42
+ const register = mod?.sungenDriver?.register ?? mod?.register;
43
+ if (typeof register !== 'function') {
44
+ throw new Error(`${pkg} is installed but is not a sungen capability driver (missing sungenDriver.register).`);
45
+ }
46
+ }
47
+
11
48
  function findScreenDir(name: string): string | null {
12
49
  const s = path.join(process.cwd(), 'qa', 'screens', name);
13
50
  if (fs.existsSync(s)) return s;
@@ -111,16 +148,27 @@ export function registerCapabilityCommand(program: Command): void {
111
148
  const bundled = adapterRegistry.hasAdapter(adapterName); // e.g. `web` is bundled in Phase 2a
112
149
 
113
150
  if (!bundled && !o.skipInstall) {
114
- console.log(`📦 Installing ${meta.package} (dev dependency)...`);
115
- const r = spawnSync('npm', ['install', '-D', meta.package], { stdio: 'inherit', shell: true });
116
- if (r.status !== 0) throw new Error(`npm install -D ${meta.package} failed.`);
151
+ // Pin the driver to the RUNNING core's exact version (lockstep). Without this, plain
152
+ // `npm install @sungen/driver-x` resolves the package's `latest` dist-tag which on a
153
+ // beta channel can lag (e.g. a prerelease first-published-as-latest), pulling a driver
154
+ // whose core dep resolves to the wrong @sun-asterisk/sungen and fails to load.
155
+ const spec = `${meta.package}@${coreVersion()}`;
156
+ console.log(`📦 Installing ${spec} (dev dependency)...`);
157
+ const r = spawnSync('npm', ['install', '-D', spec], { stdio: 'inherit', shell: true });
158
+ if (r.status !== 0) throw new Error(`npm install -D ${spec} failed.`);
117
159
  } else if (bundled) {
118
160
  console.log(`✓ ${driver} is built-in (no install needed).`);
119
161
  }
120
162
 
121
- // Verify it can load (skip for bundled — already registered).
163
+ // Verify it loaded (skip for bundled — already registered). Two driver shapes:
164
+ // - platform driver (web/mobile): a codegen adapter, loaded via the adapter registry (mod.activate)
165
+ // - capability driver (db/api/…): a capability-SPI package (mod.sungenDriver.register) — NOT an adapter
122
166
  if (!bundled) {
123
- adapterRegistry.loadDriver(adapterName, meta.package, cwd); // throws if not loadable
167
+ if (meta.kind === 'platform') {
168
+ adapterRegistry.loadDriver(adapterName, meta.package, cwd); // throws if not loadable
169
+ } else {
170
+ verifyCapabilityDriver(meta.package, cwd); // capability SPI entry, resolved from the project
171
+ }
124
172
  }
125
173
 
126
174
  const profile = readCapabilities(cwd);