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

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
@@ -4,8 +4,9 @@ import { ParsedFeature, ParsedScenario, ParsedStep } from '../gherkin-parser';
4
4
  import { StepMapper } from './step-mapper';
5
5
  import { TestGeneratorAdapter, adapterRegistry } from './adapters';
6
6
  import { transformToRuntimeData } from './utils/runtime-data-transformer';
7
- import { isDbStep } from './patterns/database-patterns';
8
- import { resolveQuery, compileQuery } from '../../harness/query-catalog';
7
+ import { capabilityRegistry } from '../../capabilities/registry';
8
+ import { discoverAndRegisterCapabilities } from '../../capabilities/discover';
9
+ import { readCapabilities } from '../../harness/capability';
9
10
 
10
11
  /**
11
12
  * Filter base scenario steps for @extend: only keep Given→When steps.
@@ -88,6 +89,40 @@ function extractPassThroughTags(scenarioTags: string[], featureTags: string[]):
88
89
  return unique.map(t => `'${t}'`).join(', ');
89
90
  }
90
91
 
92
+ /**
93
+ * Derive a feature's unit from its source path: the catalog-resolution id (relative to qa/), the
94
+ * output subdir, and whether it is a UI flow. Recognizes the api-first project model:
95
+ * qa/screens/<screen>/features/… → id `<screen>` (subdir `<screen>`)
96
+ * qa/flows/<flow>/features/… → id `flows/<flow>` (subdir `flows/<flow>`, isFlow)
97
+ * qa/api/<area>/features/… → id `api/<area>` (subdir `api/<area>`)
98
+ * qa/api/flows/<flow>/features/… → id `api/flows/<flow>` (subdir `api/flows/<flow>`)
99
+ * api-first units are NOT UI flows (no cross-screen namespacing) — they resolve their own api/db catalogs.
100
+ */
101
+ function deriveUnitFromFeaturePath(sourceFile: string): { unitId: string; outputSubdir: string; isFlow: boolean } {
102
+ const parts = path.dirname(sourceFile).split(path.sep);
103
+ const qa = parts.lastIndexOf('qa');
104
+ if (qa >= 0 && parts[qa + 1] === 'api') {
105
+ if (parts[qa + 2] === 'flows' && parts[qa + 3]) {
106
+ const id = `api/flows/${parts[qa + 3]}`;
107
+ return { unitId: id, outputSubdir: id, isFlow: false };
108
+ }
109
+ if (parts[qa + 2]) {
110
+ const id = `api/${parts[qa + 2]}`;
111
+ return { unitId: id, outputSubdir: id, isFlow: false };
112
+ }
113
+ }
114
+ const flowsIndex = parts.indexOf('flows');
115
+ const screensIndex = parts.indexOf('screens');
116
+ if (flowsIndex >= 0 && flowsIndex < parts.length - 1) {
117
+ const f = parts[flowsIndex + 1];
118
+ return { unitId: `flows/${f}`, outputSubdir: `flows/${f}`, isFlow: true };
119
+ }
120
+ if (screensIndex >= 0 && screensIndex < parts.length - 1) {
121
+ return { unitId: parts[screensIndex + 1], outputSubdir: parts[screensIndex + 1], isFlow: false };
122
+ }
123
+ return { unitId: '', outputSubdir: '', isFlow: false };
124
+ }
125
+
91
126
  /**
92
127
  * Check for @screenshot:on-failure tag
93
128
  */
@@ -141,6 +176,19 @@ function isManual(tags: string[]): boolean {
141
176
  return tags.some(tag => tag === '@manual');
142
177
  }
143
178
 
179
+ /**
180
+ * Capabilities a scenario declares via `@requires:<cap>` (TQ-11) — automation-ready work that
181
+ * needs an opt-in driver (e.g. `@requires:db`, `@requires:api`). When the cap is enabled the
182
+ * scenario compiles to a real test; when absent it compiles to a clean `test.skip` stub (no
183
+ * driver imports) so the run never breaks and the case is visible as "pending capability".
184
+ */
185
+ function requiresCaps(tags: string[]): string[] {
186
+ return tags
187
+ .filter(t => /^@requires:/i.test(t))
188
+ .map(t => t.slice('@requires:'.length).trim().toLowerCase())
189
+ .filter(Boolean);
190
+ }
191
+
144
192
  /**
145
193
  * Check for multiple auth tags and log warning
146
194
  */
@@ -210,29 +258,16 @@ export class CodeGenerator {
210
258
  fileName = this.featureNameToFileName(feature.name);
211
259
  }
212
260
 
213
- // Extract screen/flow name from source file path for output subdirectory
214
- // qa/screens/{screenName}/features/{featureName}.feature -> screenName
215
- // qa/flows/{flowName}/features/{featureName}.feature -> flows/flowName
216
- let outputSubdir = '';
217
- if (feature.sourceFile) {
218
- const sourceDir = path.dirname(feature.sourceFile);
219
- const parts = sourceDir.split(path.sep);
220
- const flowsIndex = parts.indexOf('flows');
221
- const screensIndex = parts.indexOf('screens');
222
- if (flowsIndex >= 0 && flowsIndex < parts.length - 2) {
223
- outputSubdir = path.join('flows', parts[flowsIndex + 1]);
224
- } else if (screensIndex >= 0 && screensIndex < parts.length - 2) {
225
- outputSubdir = parts[screensIndex + 1];
226
- }
227
- }
261
+ // Output subdirectory from the source path (screens / flows / api-first areas+flows).
262
+ const outputSubdir = feature.sourceFile ? deriveUnitFromFeaturePath(feature.sourceFile).outputSubdir : '';
228
263
 
229
264
  // Build output path with subdirectory
230
265
  const filePath = outputSubdir
231
266
  ? path.join(outputDir, outputSubdir, fileName)
232
267
  : path.join(outputDir, fileName);
233
268
 
234
- // Compute relative path from output file back to specs/generated/
235
- const depth = outputSubdir ? outputSubdir.split(path.sep).length : 0;
269
+ // Compute relative path from output file back to specs/generated/ (subdir uses '/').
270
+ const depth = outputSubdir ? outputSubdir.split('/').length : 0;
236
271
  const basePath = depth > 0 ? Array(depth).fill('..').join('/') : '..';
237
272
 
238
273
  // Serial + @cleanup tags → need cleanupPage import from base
@@ -240,11 +275,17 @@ export class CodeGenerator {
240
275
  const hasCleanupTags = (feature.tags || []).some(t => t.startsWith('@cleanup:'));
241
276
  const needsCleanupImport = !isParallelFeature && hasCleanupTags;
242
277
 
243
- // Data Driver: if any step verifies DB state, import the `db` helper + emit specs/db.ts
244
- const needsDb = this.featureUsesDb(feature);
245
- if (needsDb) this.ensureDbFile(outputDir);
278
+ // Active capabilities for this feature (registry-driven): the default UI + any whose annotation
279
+ // tags appear (@query) or whose detectsStep matches (declarative DB steps). Each active
280
+ // capability emits its declared runtime helpers (db → specs/db.ts).
281
+ const activeCapabilityIds = this.activeCapabilityIds(feature);
282
+ const needsDb = activeCapabilityIds.includes('db');
283
+ const needsApi = activeCapabilityIds.includes('api');
284
+ for (const id of activeCapabilityIds) {
285
+ for (const h of capabilityRegistry.get(id)?.runtimeHelpers ?? []) this.syncGeneratedHelper(outputDir, h.file, h.template);
286
+ }
246
287
 
247
- const imports = this.adapter.renderImports({ runtimeData: this.options.runtimeData, basePath, needsCleanupImport, needsDb });
288
+ const imports = this.adapter.renderImports({ runtimeData: this.options.runtimeData, basePath, needsCleanupImport, needsDb, needsApi });
248
289
 
249
290
  // Generate test code (async now to support AI mapping)
250
291
  const testCode = await this.generateTestCode(feature);
@@ -300,64 +341,52 @@ export class CodeGenerator {
300
341
  /**
301
342
  * Ensure specs/base.ts exists in the output directory
302
343
  */
303
- /** True when any step (background or scenario) in the feature is a DB-verification step. */
304
- private featureUsesDb(feature: ParsedFeature): boolean {
344
+ /**
345
+ * Capabilities active for a feature (registry-driven): the default (UI) capability, plus any
346
+ * whose annotation tags appear on a scenario (e.g. `@query` → db) or whose `detectsStep`
347
+ * matches a step (db's declarative `User see [table] row where …`). Drives runtime-helper
348
+ * emission + the `db` import — replaces the hardcoded `featureUsesDb` check (R4).
349
+ */
350
+ private activeCapabilityIds(feature: ParsedFeature): string[] {
351
+ discoverAndRegisterCapabilities();
305
352
  const steps: ParsedStep[] = [];
306
353
  if (feature.background?.steps) steps.push(...feature.background.steps);
307
354
  for (const sc of feature.scenarios || []) if (sc.steps) steps.push(...sc.steps);
308
- if (steps.some((s) => s && typeof s.text === 'string' && isDbStep(s.text))) return true;
309
- // A scenario may carry only a @query:<name> post-condition tag (no DB step in its body).
310
- return (feature.scenarios || []).some((sc) => (sc.tags || []).some((t) => t.startsWith('@query:')));
355
+ const scenarioTags = (feature.scenarios || []).flatMap((sc) => sc.tags || []);
356
+ const ids = new Set<string>();
357
+ const def = capabilityRegistry.defaultCapabilityId();
358
+ if (def) ids.add(def);
359
+ for (const cap of capabilityRegistry.all()) {
360
+ const annoMatch = (cap.annotations ?? []).some((a) => scenarioTags.some((t) => t === a || t.startsWith(a + ':')));
361
+ const stepMatch = cap.detectsStep ? steps.some((s) => s && typeof s.text === 'string' && cap.detectsStep!(s.text)) : false;
362
+ if (annoMatch || stepMatch) ids.add(cap.id);
363
+ }
364
+ return [...ids];
311
365
  }
312
366
 
313
367
  /**
314
- * Build the precondition bind steps for a scenario's `@query:<name>[(p={{v}},…)]` tags.
315
- * Each runs the catalog query and binds the result array to a `{{name}}` variable.
368
+ * Precondition steps a scenario's capabilities inject (e.g. db `@query:<name>` → bind {{name}}).
369
+ * Each capability owns its annotation codegen via the SPI (`preconditionCodegen`); the compiler
370
+ * just composes + indents the returned statements (R4).
316
371
  */
317
- private buildQueryBinds(scenario: ParsedScenario): Array<{ comment?: string; code: string }> {
318
- const out: Array<{ comment?: string; code: string }> = [];
319
- const TAG = /^@query:([A-Za-z_][A-Za-z0-9_]*)(?:\((.*)\))?$/;
320
- for (const tag of scenario.tags || []) {
321
- const m = tag.match(TAG);
322
- if (!m) continue;
323
- const name = m[1];
324
- const overrides = this.parseQueryOverrides(m[2]);
325
- const entry = resolveQuery(name, this.queryScreenName); // throws (fail-fast) if missing/ambiguous
326
- const { sql, paramNames } = compileQuery(entry);
327
- const paramExprs = paramNames.map((p) =>
328
- p in overrides ? overrides[p] : `testData.get(${JSON.stringify(p)})`,
329
- );
330
- const label = JSON.stringify(entry.description ? `query "${name}" ${entry.description}` : `query "${name}"`);
331
- const ds = entry.datasource ? JSON.stringify(entry.datasource) : 'undefined';
332
- out.push({
333
- comment: `@query:${name} → bind {{${name}}} from ${entry.datasource || 'default datasource'}`,
334
- code: this.indentCode(
335
- `testData.bind(${JSON.stringify(name)}, await db.fetchQuery(${label}, ${JSON.stringify(sql)}, [${paramExprs.join(', ')}], ${ds}));`,
336
- 4,
337
- ),
338
- });
339
- }
340
- return out;
341
- }
342
-
343
- /** Parse `@query:name(a={{x}},b="lit",c=3)` overrides → { a: "testData.get('x')", … } JS exprs. */
344
- private parseQueryOverrides(raw?: string): Record<string, string> {
345
- const out: Record<string, string> = {};
346
- if (!raw) return out;
347
- for (const part of raw.split(',')) {
348
- const eq = part.indexOf('=');
349
- if (eq < 0) continue;
350
- const key = part.slice(0, eq).trim();
351
- const val = part.slice(eq + 1).trim();
352
- if (!key) continue;
353
- const v = val.match(/^\{\{\s*([^}]+?)\s*\}\}$/);
354
- const q = val.match(/^["'](.*)["']$/);
355
- if (v) out[key] = `testData.get(${JSON.stringify(v[1])})`;
356
- else if (q) out[key] = JSON.stringify(q[1]);
357
- else if (/^-?\d+(?:\.\d+)?$/.test(val)) out[key] = val;
358
- else out[key] = JSON.stringify(val);
359
- }
360
- return out;
372
+ private capabilityPreconditions(scenario: ParsedScenario): Array<{ comment?: string; code: string; boundVars?: string[] }> {
373
+ discoverAndRegisterCapabilities();
374
+ const tags = scenario.tags || [];
375
+ const out: Array<{ comment?: string; code: string; boundVars?: string[] }> = [];
376
+ for (const cap of capabilityRegistry.all()) {
377
+ if (!cap.preconditionCodegen) continue;
378
+ out.push(...cap.preconditionCodegen({ tags, screenName: this.queryScreenName, cwd: process.cwd() }));
379
+ }
380
+ // Order by each precondition's annotation position on the scenario (not capability-registry order),
381
+ // so a cross-capability sequence runs as authored — e.g. `@api:pay @concurrent:2 @query:charge_count`
382
+ // mutates THEN reads the DB (the idempotency cross-check). Stable: ties keep their original order.
383
+ const pos = (p: { boundVars?: string[] }): number => {
384
+ const name = p.boundVars?.[0];
385
+ if (!name) return tags.length;
386
+ const i = tags.findIndex((t) => new RegExp(`^@\\w+:${name}\\b`).test(t));
387
+ return i < 0 ? tags.length : i;
388
+ };
389
+ return out.map((p, i) => ({ p, i })).sort((a, b) => pos(a.p) - pos(b.p) || a.i - b.i).map((x) => x.p);
361
390
  }
362
391
 
363
392
  /**
@@ -378,11 +407,6 @@ export class CodeGenerator {
378
407
  console.log(`✓ ${exists ? 'Updated' : 'Created'}: specs/${fileName}`);
379
408
  }
380
409
 
381
- /** Ensure specs/db.ts is present and current (Data Driver runtime helper). */
382
- ensureDbFile(outputDir: string): void {
383
- this.syncGeneratedHelper(outputDir, 'db.ts', 'specs-db.ts');
384
- }
385
-
386
410
  ensureBaseFile(outputDir: string): void {
387
411
  this.syncGeneratedHelper(outputDir, 'base.ts', 'specs-base.ts');
388
412
  // base.ts depends on locale-fixture.ts — keep them paired.
@@ -403,27 +427,23 @@ export class CodeGenerator {
403
427
  featureName = this.featureNameToFileName(feature.name).replace('.spec.ts', '');
404
428
  }
405
429
 
406
- // Derive screen/flow name from source file path when not explicitly set
407
- // qa/screens/{screenName}/features/{featureName}.feature -> screenName
408
- // qa/flows/{flowName}/features/{featureName}.feature -> flowName
430
+ // Derive the unit from the source path when not explicitly set. `unitId` is the catalog/test-data
431
+ // resolution id relative to qa/ (`<screen>` · `flows/<flow>` · `api/<area>` · `api/flows/<flow>`);
432
+ // `effectiveScreenName` is the bare name for UI screen-context (selector/data namespacing).
409
433
  let effectiveScreenName = this.screenName;
410
434
  let isFlowFeature = !!this.options.flowMode;
435
+ let unitId = isFlowFeature ? `flows/${effectiveScreenName || ''}` : (effectiveScreenName || '');
411
436
  if (!this.screenName && feature.sourceFile) {
412
- const sourceDir = path.dirname(feature.sourceFile);
413
- const parts = sourceDir.split(path.sep);
414
- const flowsIndex = parts.indexOf('flows');
415
- const screensIndex = parts.indexOf('screens');
416
- if (flowsIndex >= 0 && flowsIndex < parts.length - 2) {
417
- effectiveScreenName = parts[flowsIndex + 1];
418
- isFlowFeature = true;
419
- } else if (screensIndex >= 0 && screensIndex < parts.length - 2) {
420
- effectiveScreenName = parts[screensIndex + 1];
421
- isFlowFeature = false;
437
+ const u = deriveUnitFromFeaturePath(feature.sourceFile);
438
+ if (u.unitId) {
439
+ unitId = u.unitId;
440
+ isFlowFeature = u.isFlow;
441
+ effectiveScreenName = u.unitId.split('/').pop() || u.unitId; // bare name (no api/ or flows/ prefix)
442
+ this.stepMapper.setScreenContext(effectiveScreenName);
422
443
  }
423
- this.stepMapper.setScreenContext(effectiveScreenName);
424
444
  }
425
- // Catalog-resolution screen name for @query binds (flows are prefixed `flows/`).
426
- this.queryScreenName = isFlowFeature ? `flows/${effectiveScreenName}` : (effectiveScreenName || '');
445
+ // Catalog + test-data resolution id (flows `flows/<flow>`, api-first `api/<area>` / `api/flows/<flow>`).
446
+ this.queryScreenName = unitId;
427
447
 
428
448
  // Reset flow mode per feature to prevent state leak in --all mode
429
449
  this.stepMapper.setFlowMode(isFlowFeature);
@@ -487,6 +507,10 @@ export class CodeGenerator {
487
507
  // Generate all scenarios with feature tags for inheritance
488
508
  // Skip scenarios tagged with @manual
489
509
  // Track auth role per scenario for grouping
510
+ // TQ-11 — enabled capabilities (qa/capabilities.yaml): a @requires:<cap> scenario whose cap is
511
+ // absent compiles to a skip stub instead of a real test (read once per feature).
512
+ const enabledCaps = new Set<string>(readCapabilities(process.cwd()).enabled.map(d => d.toLowerCase()));
513
+
490
514
  const renderedScenarios: Array<{ code: string; authRole?: string }> = [];
491
515
  for (const scenario of feature.scenarios) {
492
516
  if (isManual(scenario.tags)) {
@@ -501,6 +525,16 @@ export class CodeGenerator {
501
525
  continue;
502
526
  }
503
527
 
528
+ // TQ-11 — @requires:<cap> with the cap NOT enabled → emit a clean skip stub (no driver
529
+ // imports, so the spec still loads), visible as skipped-with-reason. With the cap enabled it
530
+ // falls through and compiles as a real test.
531
+ const reqAbsent = requiresCaps(scenario.tags).filter(c => !enabledCaps.has(c));
532
+ if (reqAbsent.length) {
533
+ if (this.options.verbose) console.log(` ⏸ Pending capability (${reqAbsent.join(', ')}): ${scenario.name}`);
534
+ renderedScenarios.push({ code: this.generateRequiresSkipStub(scenario.name, reqAbsent), authRole: undefined });
535
+ continue;
536
+ }
537
+
504
538
  // Resolve auth tags for @extend scenarios (same logic as generateScenario)
505
539
  let authFeatureTags = feature.tags || [];
506
540
  if (scenario.extendsName) {
@@ -574,7 +608,7 @@ export class CodeGenerator {
574
608
  cleanupConfig,
575
609
  screenshotOnFailure,
576
610
  runtimeData: this.options.runtimeData,
577
- screenName: isFlowFeature ? `flows/${effectiveScreenName}` : effectiveScreenName,
611
+ screenName: this.queryScreenName, // catalog/test-data id (screen · flows/<flow> · api/<area>)
578
612
  featureFileName: featureName,
579
613
  isParallel,
580
614
  flowMode: isFlowFeature,
@@ -636,6 +670,21 @@ export class CodeGenerator {
636
670
  return renderMap[hookType]();
637
671
  }
638
672
 
673
+ /**
674
+ * TQ-11 — a `@requires:<cap>` scenario whose capability is NOT enabled. Emits a real `test()`
675
+ * that skips itself with an actionable reason, and references NO driver runtime (so the spec
676
+ * loads even though the driver isn't installed). Once the cap is added + regenerated, the
677
+ * scenario compiles to its full automated body instead.
678
+ */
679
+ private generateRequiresSkipStub(name: string, caps: string[]): string {
680
+ const reason = `requires ${caps.join(' + ')} — run \`sungen capability add ${caps.join(' ')}\` to automate this`;
681
+ return [
682
+ ` test(${JSON.stringify(name)}, async () => {`,
683
+ ` test.skip(true, ${JSON.stringify(reason)});`,
684
+ ` });`,
685
+ ].join('\n');
686
+ }
687
+
639
688
  private async generateScenario(
640
689
  scenario: ParsedScenario,
641
690
  hasBackground: boolean,
@@ -683,18 +732,16 @@ export class CodeGenerator {
683
732
  for (const r of refs) this.stepMapper.registerCaptured(r);
684
733
  }
685
734
 
686
- // @query-bound vars: `{{name.col}}` / `{{name[2].col}}` / `{{name.count}}` exist only at
687
- // runtime (the bind fetches them) register as captured so they resolve to a runtime get()
688
- // instead of a compile-time YAML lookup that would fail.
689
- const queryNames = (scenario.tags || [])
690
- .map((t) => t.match(/^@query:([A-Za-z_][A-Za-z0-9_]*)/))
691
- .filter((m): m is RegExpMatchArray => !!m)
692
- .map((m) => m[1]);
693
- if (queryNames.length) {
735
+ // Capability preconditions (db `@query:<name>` bind {{name}}) are owned by the capability via
736
+ // the SPI. Their bound `{{name.*}}` vars exist only at runtime register them as captured so
737
+ // they resolve to a runtime get() instead of a compile-time YAML lookup that would fail.
738
+ const preconditions = this.capabilityPreconditions(scenario);
739
+ const boundVars = preconditions.flatMap((p) => p.boundVars || []);
740
+ if (boundVars.length) {
694
741
  for (const st of stepsToMap) {
695
742
  for (const mt of (st.text || '').matchAll(/\{\{\s*([^}]+?)\s*\}\}/g)) {
696
743
  const head = mt[1].split(/[.[]/)[0];
697
- if (queryNames.includes(head)) this.stepMapper.registerCaptured(mt[1]);
744
+ if (boundVars.includes(head)) this.stepMapper.registerCaptured(mt[1]);
698
745
  }
699
746
  }
700
747
  }
@@ -728,11 +775,11 @@ export class CodeGenerator {
728
775
  }
729
776
  }
730
777
 
731
- // @query:<name>[(p={{v}},…)] tags run the catalog query as a PRECONDITION and bind its
732
- // result to a `{{name}}` variable (scenario asserts on it with `expect …` + path access).
733
- // Prepended so the binds run before the scenario's own steps.
734
- const queryBinds = this.buildQueryBinds(scenario);
735
- if (queryBinds.length) steps.unshift(...queryBinds);
778
+ // Capability preconditions (db `@query:<name>` / api `@api:<name>` bind {{name}}; computed
779
+ // above) run BEFORE the scenario's own steps prepend them, indenting the capability statements.
780
+ if (preconditions.length) {
781
+ steps.unshift(...preconditions.map((p) => ({ comment: p.comment, code: this.indentCode(p.code, 4) })));
782
+ }
736
783
 
737
784
  // Extract pass-through tags (feature + scenario, excluding functional tags)
738
785
  const tags = extractPassThroughTags(scenario.tags, featureTags);
@@ -752,7 +799,12 @@ export class CodeGenerator {
752
799
  // global `testData` transform that runs next on the rest of the file leaves it alone.
753
800
  // The loop header's `testData.cases()/withRow()` are literal code (no markers) → untouched.
754
801
  if (casesDataset && this.options.runtimeData) {
755
- return transformToRuntimeData(rendered, 'rowData');
802
+ // AP-3: capability preconditions (`@api`/`@query`) and value assertions (`expect {{x}} is {{y}}`)
803
+ // emit LITERAL `testData.get/bind/set(…)` (not markers), so the marker transform above misses
804
+ // them. Rewrite those to the per-row `rowData` view — each row then fires its own `@api` call
805
+ // with that row's input and asserts that row's expected status/body (the success/failure matrix).
806
+ // `testData.cases()/withRow()` (the global loader, no `.get/.bind/.set`) is intentionally left alone.
807
+ return transformToRuntimeData(rendered, 'rowData').replace(/\btestData\.(get|bind|set)\(/g, 'rowData.$1(');
756
808
  }
757
809
  return rendered;
758
810
  }
@@ -1,18 +1,8 @@
1
1
  import { ParsedStep } from '../../gherkin-parser';
2
2
  import { MappedStep } from '../step-mapper';
3
3
  import { StepPattern, PatternContext } from './types';
4
- import { navigationPatterns } from './navigation-patterns';
5
- import { formPatterns } from './form-patterns';
6
- import { interactionPatterns } from './interaction-patterns';
7
- import { assertionPatterns } from './assertion-patterns';
8
- import { setupPatterns } from './setup-patterns';
9
- import { keyboardPatterns } from './keyboard-patterns';
10
- import { scrollPatterns } from './scroll-patterns';
11
- import { scopePatterns } from './scope-patterns';
12
- import { tablePatterns } from './table-patterns';
13
- import { capturePatterns } from './capture-patterns';
14
- import { databasePatterns } from './database-patterns';
15
- import { expectPatterns } from './expect-patterns';
4
+ import { capabilityRegistry } from '../../../capabilities/registry';
5
+ import { discoverAndRegisterCapabilities } from '../../../capabilities/discover';
16
6
 
17
7
  /**
18
8
  * Pattern Registry - manages all step patterns
@@ -28,18 +18,11 @@ export class PatternRegistry {
28
18
  * Register default patterns from all pattern modules
29
19
  */
30
20
  private registerDefaultPatterns(): void {
31
- this.patterns.push(...setupPatterns);
32
- this.patterns.push(...navigationPatterns);
33
- this.patterns.push(...formPatterns);
34
- this.patterns.push(...interactionPatterns);
35
- this.patterns.push(...assertionPatterns);
36
- this.patterns.push(...keyboardPatterns);
37
- this.patterns.push(...scrollPatterns);
38
- this.patterns.push(...scopePatterns);
39
- this.patterns.push(...tablePatterns);
40
- this.patterns.push(...capturePatterns);
41
- this.patterns.push(...databasePatterns);
42
- this.patterns.push(...expectPatterns);
21
+ // Patterns are composed from the capability registry (Capability SPI, R1) instead of a
22
+ // hardcoded push list. Built-ins (ui · db · core) register the same set as before, so the
23
+ // composed list + priority sort is behaviour-identical (golden snapshots are the contract).
24
+ discoverAndRegisterCapabilities();
25
+ this.patterns.push(...capabilityRegistry.patterns());
43
26
 
44
27
  // Sort by priority (higher first)
45
28
  this.patterns.sort((a, b) => (b.priority || 0) - (a.priority || 0));
@@ -159,15 +142,6 @@ export class PatternRegistry {
159
142
  }
160
143
  }
161
144
 
162
- // Export pattern modules for advanced usage
163
- export { setupPatterns } from './setup-patterns';
164
- export { navigationPatterns } from './navigation-patterns';
165
- export { formPatterns } from './form-patterns';
166
- export { interactionPatterns } from './interaction-patterns';
167
- export { assertionPatterns } from './assertion-patterns';
168
- export { keyboardPatterns } from './keyboard-patterns';
169
- export { scrollPatterns } from './scroll-patterns';
170
- export { scopePatterns } from './scope-patterns';
171
- export { tablePatterns } from './table-patterns';
172
- export { databasePatterns, isDbStep } from './database-patterns';
145
+ // The UI step patterns now live in @sungen/driver-ui (R5.4) and the DB patterns in @sungen/driver-db
146
+ // (R5.5); both are contributed via the capability registry, not re-exported here.
173
147
  export * from './types';
@@ -229,8 +229,8 @@ export class TemplateEngine {
229
229
  this.baseContext = {};
230
230
  }
231
231
 
232
- renderImports(options?: { runtimeData?: boolean; basePath?: string; isParallel?: boolean; needsCleanupImport?: boolean ; needsDb?: boolean }): string {
233
- return this.render('imports', { runtimeData: options?.runtimeData, basePath: options?.basePath || '..', isParallel: options?.isParallel, needsCleanupImport: options?.needsCleanupImport, needsDb: options?.needsDb });
232
+ renderImports(options?: { runtimeData?: boolean; basePath?: string; isParallel?: boolean; needsCleanupImport?: boolean ; needsDb?: boolean; needsApi?: boolean }): string {
233
+ return this.render('imports', { runtimeData: options?.runtimeData, basePath: options?.basePath || '..', isParallel: options?.isParallel, needsCleanupImport: options?.needsCleanupImport, needsDb: options?.needsDb, needsApi: options?.needsApi });
234
234
  }
235
235
 
236
236
  renderTestFile(data: {
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Shared annotation-override grammar for precondition annotations (`@query`/`@api`).
3
+ *
4
+ * Parses `name(a={{x}},b="lit",c=3)` overrides into a map of JS expressions, e.g.
5
+ * `{ a: "testData.get('x')", b: "\"lit\"", c: "3" }`. Used by the DB and API capability drivers'
6
+ * precondition codegen; lives in core so both drivers can share it. Gherkin tags carry no whitespace,
7
+ * so values are single tokens — flows thread a prior response via a whole-value ref, e.g.
8
+ * `@api:get_profile(token={{login.body.token}})`, with the auth scheme declared in the catalog header.
9
+ */
10
+ export function parseQueryOverrides(raw?: string): Record<string, string> {
11
+ const out: Record<string, string> = {};
12
+ if (!raw) return out;
13
+ for (const part of raw.split(',')) {
14
+ const eq = part.indexOf('=');
15
+ if (eq < 0) continue;
16
+ const key = part.slice(0, eq).trim();
17
+ const val = part.slice(eq + 1).trim();
18
+ if (!key) continue;
19
+ const v = val.match(/^\{\{\s*([^}]+?)\s*\}\}$/);
20
+ const q = val.match(/^["'](.*)["']$/);
21
+ if (v) out[key] = `testData.get(${JSON.stringify(v[1])})`;
22
+ else if (q) out[key] = JSON.stringify(q[1]);
23
+ else if (/^-?\d+(?:\.\d+)?$/.test(val)) out[key] = val;
24
+ else out[key] = JSON.stringify(val);
25
+ }
26
+ return out;
27
+ }