@sun-asterisk/sungen 3.1.0 → 3.1.2-beta.100

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 (228) 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 +51 -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 +48 -0
  17. package/dist/capabilities/discover.js.map +1 -0
  18. package/dist/capabilities/registry.d.ts +90 -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 +49 -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/challenge.d.ts.map +1 -1
  27. package/dist/cli/commands/challenge.js +9 -2
  28. package/dist/cli/commands/challenge.js.map +1 -1
  29. package/dist/cli/commands/delivery.d.ts.map +1 -1
  30. package/dist/cli/commands/delivery.js +3 -2
  31. package/dist/cli/commands/delivery.js.map +1 -1
  32. package/dist/cli/commands/generate.d.ts.map +1 -1
  33. package/dist/cli/commands/generate.js +12 -0
  34. package/dist/cli/commands/generate.js.map +1 -1
  35. package/dist/cli/index.js +10 -1
  36. package/dist/cli/index.js.map +1 -1
  37. package/dist/exporters/csv-exporter.d.ts.map +1 -1
  38. package/dist/exporters/csv-exporter.js +92 -76
  39. package/dist/exporters/csv-exporter.js.map +1 -1
  40. package/dist/exporters/spec-parser.d.ts.map +1 -1
  41. package/dist/exporters/spec-parser.js +6 -1
  42. package/dist/exporters/spec-parser.js.map +1 -1
  43. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +2 -0
  44. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  45. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +1 -0
  46. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  47. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  48. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  49. package/dist/generators/test-generator/adapters/playwright/templates/scenario.hbs +19 -1
  50. package/dist/generators/test-generator/code-generator.d.ts +21 -4
  51. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  52. package/dist/generators/test-generator/code-generator.js +118 -57
  53. package/dist/generators/test-generator/code-generator.js.map +1 -1
  54. package/dist/generators/test-generator/patterns/expect-patterns.d.ts +3 -0
  55. package/dist/generators/test-generator/patterns/expect-patterns.d.ts.map +1 -0
  56. package/dist/generators/test-generator/patterns/expect-patterns.js +54 -0
  57. package/dist/generators/test-generator/patterns/expect-patterns.js.map +1 -0
  58. package/dist/generators/test-generator/patterns/index.d.ts +0 -10
  59. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
  60. package/dist/generators/test-generator/patterns/index.js +10 -45
  61. package/dist/generators/test-generator/patterns/index.js.map +1 -1
  62. package/dist/generators/test-generator/step-mapper.d.ts +6 -0
  63. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  64. package/dist/generators/test-generator/step-mapper.js +8 -0
  65. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  66. package/dist/generators/test-generator/template-engine.d.ts +4 -0
  67. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  68. package/dist/generators/test-generator/template-engine.js +1 -1
  69. package/dist/generators/test-generator/template-engine.js.map +1 -1
  70. package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts +1 -1
  71. package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts.map +1 -1
  72. package/dist/generators/test-generator/utils/runtime-data-transformer.js +5 -5
  73. package/dist/generators/test-generator/utils/runtime-data-transformer.js.map +1 -1
  74. package/dist/harness/annotation-overrides.d.ts +9 -0
  75. package/dist/harness/annotation-overrides.d.ts.map +1 -0
  76. package/dist/harness/annotation-overrides.js +36 -0
  77. package/dist/harness/annotation-overrides.js.map +1 -0
  78. package/dist/harness/audit.d.ts.map +1 -1
  79. package/dist/harness/audit.js +35 -7
  80. package/dist/harness/audit.js.map +1 -1
  81. package/dist/harness/catalog/drivers.yaml +35 -12
  82. package/dist/harness/challenge.d.ts +1 -0
  83. package/dist/harness/challenge.d.ts.map +1 -1
  84. package/dist/harness/challenge.js +49 -2
  85. package/dist/harness/challenge.js.map +1 -1
  86. package/dist/harness/data-driven-lint.d.ts +7 -0
  87. package/dist/harness/data-driven-lint.d.ts.map +1 -0
  88. package/dist/harness/data-driven-lint.js +153 -0
  89. package/dist/harness/data-driven-lint.js.map +1 -0
  90. package/dist/harness/parse.d.ts +3 -0
  91. package/dist/harness/parse.d.ts.map +1 -1
  92. package/dist/harness/parse.js +25 -0
  93. package/dist/harness/parse.js.map +1 -1
  94. package/dist/harness/query-catalog.d.ts +48 -0
  95. package/dist/harness/query-catalog.d.ts.map +1 -0
  96. package/dist/harness/query-catalog.js +0 -0
  97. package/dist/harness/query-catalog.js.map +1 -0
  98. package/dist/harness/script-check.d.ts.map +1 -1
  99. package/dist/harness/script-check.js +7 -4
  100. package/dist/harness/script-check.js.map +1 -1
  101. package/dist/index.d.ts +20 -0
  102. package/dist/index.d.ts.map +1 -0
  103. package/dist/index.js +32 -0
  104. package/dist/index.js.map +1 -0
  105. package/dist/orchestrator/templates/ai-instructions/claude-agent-challenge.md +3 -2
  106. package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +4 -3
  107. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +41 -0
  108. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +22 -0
  109. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +1 -0
  110. package/dist/orchestrator/templates/ai-instructions/claude-skill-test-design-techniques.md +6 -0
  111. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +4 -3
  112. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +41 -0
  113. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +22 -0
  114. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +1 -0
  115. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-test-design-techniques.md +6 -0
  116. package/dist/orchestrator/templates/specs-api.d.ts +19 -0
  117. package/dist/orchestrator/templates/specs-api.d.ts.map +1 -0
  118. package/dist/orchestrator/templates/specs-api.js +128 -0
  119. package/dist/orchestrator/templates/specs-api.js.map +1 -0
  120. package/dist/orchestrator/templates/specs-api.ts +101 -0
  121. package/dist/orchestrator/templates/specs-db.d.ts +8 -0
  122. package/dist/orchestrator/templates/specs-db.d.ts.map +1 -1
  123. package/dist/orchestrator/templates/specs-db.js +22 -0
  124. package/dist/orchestrator/templates/specs-db.js.map +1 -1
  125. package/dist/orchestrator/templates/specs-db.ts +22 -0
  126. package/dist/orchestrator/templates/specs-test-data.ts +76 -15
  127. package/package.json +7 -30
  128. package/src/capabilities/builtins.ts +85 -0
  129. package/src/capabilities/context-router.ts +66 -0
  130. package/src/capabilities/context.ts +46 -0
  131. package/src/capabilities/discover.ts +42 -0
  132. package/src/capabilities/registry.ts +111 -0
  133. package/src/capabilities/sensor.ts +47 -0
  134. package/src/cli/commands/challenge.ts +6 -2
  135. package/src/cli/commands/delivery.ts +3 -2
  136. package/src/cli/commands/generate.ts +12 -0
  137. package/src/cli/index.ts +10 -1
  138. package/src/exporters/csv-exporter.ts +22 -6
  139. package/src/exporters/spec-parser.ts +6 -1
  140. package/src/generators/test-generator/adapters/adapter-interface.ts +2 -1
  141. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +1 -1
  142. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  143. package/src/generators/test-generator/adapters/playwright/templates/scenario.hbs +19 -1
  144. package/src/generators/test-generator/code-generator.ts +114 -59
  145. package/src/generators/test-generator/patterns/expect-patterns.ts +49 -0
  146. package/src/generators/test-generator/patterns/index.ts +9 -33
  147. package/src/generators/test-generator/step-mapper.ts +9 -0
  148. package/src/generators/test-generator/template-engine.ts +5 -2
  149. package/src/generators/test-generator/utils/runtime-data-transformer.ts +5 -5
  150. package/src/harness/annotation-overrides.ts +25 -0
  151. package/src/harness/audit.ts +37 -8
  152. package/src/harness/catalog/drivers.yaml +35 -12
  153. package/src/harness/challenge.ts +47 -2
  154. package/src/harness/data-driven-lint.ts +119 -0
  155. package/src/harness/parse.ts +17 -0
  156. package/src/harness/query-catalog.ts +0 -0
  157. package/src/harness/script-check.ts +8 -5
  158. package/src/index.ts +30 -0
  159. package/src/orchestrator/templates/ai-instructions/claude-agent-challenge.md +3 -2
  160. package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +4 -3
  161. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +41 -0
  162. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +22 -0
  163. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +1 -0
  164. package/src/orchestrator/templates/ai-instructions/claude-skill-test-design-techniques.md +6 -0
  165. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +4 -3
  166. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +41 -0
  167. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +22 -0
  168. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +1 -0
  169. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-test-design-techniques.md +6 -0
  170. package/src/orchestrator/templates/specs-api.ts +101 -0
  171. package/src/orchestrator/templates/specs-db.ts +22 -0
  172. package/src/orchestrator/templates/specs-test-data.ts +76 -15
  173. package/dist/generators/test-generator/patterns/assertion-patterns.d.ts +0 -7
  174. package/dist/generators/test-generator/patterns/assertion-patterns.d.ts.map +0 -1
  175. package/dist/generators/test-generator/patterns/assertion-patterns.js +0 -626
  176. package/dist/generators/test-generator/patterns/assertion-patterns.js.map +0 -1
  177. package/dist/generators/test-generator/patterns/capture-patterns.d.ts +0 -21
  178. package/dist/generators/test-generator/patterns/capture-patterns.d.ts.map +0 -1
  179. package/dist/generators/test-generator/patterns/capture-patterns.js +0 -87
  180. package/dist/generators/test-generator/patterns/capture-patterns.js.map +0 -1
  181. package/dist/generators/test-generator/patterns/database-patterns.d.ts +0 -5
  182. package/dist/generators/test-generator/patterns/database-patterns.d.ts.map +0 -1
  183. package/dist/generators/test-generator/patterns/database-patterns.js +0 -94
  184. package/dist/generators/test-generator/patterns/database-patterns.js.map +0 -1
  185. package/dist/generators/test-generator/patterns/form-patterns.d.ts +0 -6
  186. package/dist/generators/test-generator/patterns/form-patterns.d.ts.map +0 -1
  187. package/dist/generators/test-generator/patterns/form-patterns.js +0 -160
  188. package/dist/generators/test-generator/patterns/form-patterns.js.map +0 -1
  189. package/dist/generators/test-generator/patterns/interaction-patterns.d.ts +0 -6
  190. package/dist/generators/test-generator/patterns/interaction-patterns.d.ts.map +0 -1
  191. package/dist/generators/test-generator/patterns/interaction-patterns.js +0 -433
  192. package/dist/generators/test-generator/patterns/interaction-patterns.js.map +0 -1
  193. package/dist/generators/test-generator/patterns/keyboard-patterns.d.ts +0 -7
  194. package/dist/generators/test-generator/patterns/keyboard-patterns.d.ts.map +0 -1
  195. package/dist/generators/test-generator/patterns/keyboard-patterns.js +0 -47
  196. package/dist/generators/test-generator/patterns/keyboard-patterns.js.map +0 -1
  197. package/dist/generators/test-generator/patterns/navigation-patterns.d.ts +0 -6
  198. package/dist/generators/test-generator/patterns/navigation-patterns.d.ts.map +0 -1
  199. package/dist/generators/test-generator/patterns/navigation-patterns.js +0 -125
  200. package/dist/generators/test-generator/patterns/navigation-patterns.js.map +0 -1
  201. package/dist/generators/test-generator/patterns/scope-patterns.d.ts +0 -7
  202. package/dist/generators/test-generator/patterns/scope-patterns.d.ts.map +0 -1
  203. package/dist/generators/test-generator/patterns/scope-patterns.js +0 -36
  204. package/dist/generators/test-generator/patterns/scope-patterns.js.map +0 -1
  205. package/dist/generators/test-generator/patterns/scroll-patterns.d.ts +0 -7
  206. package/dist/generators/test-generator/patterns/scroll-patterns.d.ts.map +0 -1
  207. package/dist/generators/test-generator/patterns/scroll-patterns.js +0 -25
  208. package/dist/generators/test-generator/patterns/scroll-patterns.js.map +0 -1
  209. package/dist/generators/test-generator/patterns/setup-patterns.d.ts +0 -6
  210. package/dist/generators/test-generator/patterns/setup-patterns.d.ts.map +0 -1
  211. package/dist/generators/test-generator/patterns/setup-patterns.js +0 -72
  212. package/dist/generators/test-generator/patterns/setup-patterns.js.map +0 -1
  213. package/dist/generators/test-generator/patterns/table-patterns.d.ts +0 -19
  214. package/dist/generators/test-generator/patterns/table-patterns.d.ts.map +0 -1
  215. package/dist/generators/test-generator/patterns/table-patterns.js +0 -239
  216. package/dist/generators/test-generator/patterns/table-patterns.js.map +0 -1
  217. package/docs/orchestration-spec.md +0 -267
  218. package/src/generators/test-generator/patterns/assertion-patterns.ts +0 -691
  219. package/src/generators/test-generator/patterns/capture-patterns.ts +0 -97
  220. package/src/generators/test-generator/patterns/database-patterns.ts +0 -95
  221. package/src/generators/test-generator/patterns/form-patterns.ts +0 -167
  222. package/src/generators/test-generator/patterns/interaction-patterns.ts +0 -465
  223. package/src/generators/test-generator/patterns/keyboard-patterns.ts +0 -51
  224. package/src/generators/test-generator/patterns/navigation-patterns.ts +0 -140
  225. package/src/generators/test-generator/patterns/scope-patterns.ts +0 -40
  226. package/src/generators/test-generator/patterns/scroll-patterns.ts +0 -27
  227. package/src/generators/test-generator/patterns/setup-patterns.ts +0 -76
  228. package/src/generators/test-generator/patterns/table-patterns.ts +0 -279
@@ -17,6 +17,12 @@ Apply selectively — not every screen needs all four techniques. Use the techni
17
17
 
18
18
  **Rule:** These techniques determine **how many** and **which** scenarios to generate. `sungen-viewpoint` determines **which viewpoints** to cover.
19
19
 
20
+ **Implementing the data table → `@cases` (data-driven):** when EP classes / BVA boundary triples /
21
+ decision-table rows share the *same step shape* and differ only by input/expected values, encode
22
+ them as ONE `@cases:<dataset>` scenario (each class/boundary/rule = one row in the test-data list,
23
+ labelled by a `case` column) instead of N near-duplicate scenarios. The technique still decides the
24
+ rows; `@cases` is how you write them compactly. See `sungen-gherkin-syntax` → Data-driven.
25
+
20
26
  ---
21
27
 
22
28
  ## 1. Equivalence Partitioning (EP)
@@ -26,9 +26,10 @@ You are a **Senior QA Engineer**. You structure test cases by viewpoint categori
26
26
  **Screen**: Verify `qa/screens/${input:name}/` exists. If not → `/sungen-add-screen` first.
27
27
  2. Check if `.feature` already has scenarios.
28
28
  - If yes → summarize existing coverage and ask update mode (options depend on which tiers already exist — see `sungen-tc-generation` skill for details).
29
- - If no → fresh creation. Ask generation scope:
30
- - **1) Tier 1 — Critical & High priority** — ~10-15 scenarios/section covering happy paths, core validation, security basics **(Recommended)**
31
- - **2) Full coverage — All tiers at once** — generates Tier 1 + 2 + 3 in one run. Large output (~40-60 scenarios/section), best for experienced users who want complete coverage immediately
29
+ - If no → fresh creation. **Write the feature file incrementally** (successive writes/edits, ≈10-15 scenarios per call) — never emit the whole suite in one response, or it can exceed the model's output-token cap (`response exceeded the N output token maximum`). Ask generation scope:
30
+ - **1) Tier 1 — Critical & High priority** — ~10-15 scenarios/section: happy paths, core validation, security basics **(Recommended)**
31
+ - **2) Full coverage (incremental)** — Tier 1 + 2 + 3, written tier-by-tier in batches. Safe on any output-token budget.
32
+ - **3) Full coverage (single pass)** — everything in one go (~40-60 scenarios/section). Faster, but **only if you raised your output cap** (`CLAUDE_CODE_MAX_OUTPUT_TOKENS ≥ 64000`) — otherwise it errors mid-generation. For power users on a high-token model/config.
32
33
  3. **Read project context + screen requirements**
33
34
 
34
35
  **Project context** — check `qa/context.md` (project root, not screen-specific):
@@ -102,6 +102,22 @@ User see [Table] table match data:
102
102
 
103
103
  Row scope: `see [Ref] row in [Table] table with {{v}}` enters scope. Subsequent `see [Col] column with {{v}}` checks cell in that row. Use `table match data:` for multi-row verification.
104
104
 
105
+ ### Database verification (optional Data Driver)
106
+
107
+ Read-only DB-state checks. **Prefer named queries** — SQL lives in `qa/screens/<screen>/database/queries.yaml` (reviewed once, parameterized). Invoke with the `@query:<name>` annotation; it binds the result rows to `{{name}}`, then assert with `expect`:
108
+
109
+ ```gherkin
110
+ @query:active_user # precondition: run query, bind {{active_user}}
111
+ @query:orders(buyer={{email}}) # …with explicit param override
112
+ Scenario: ...
113
+ Then expect {{active_user.count}} is at least {{one}} # ≥1 row
114
+ And expect {{active_user.first.status}} is "active" # first row's column
115
+ And expect {{orders.count}} is {{expected}} # exact count
116
+ And User see [Total] text is {{orders.first.total}} # UI ↔ DB
117
+ ```
118
+
119
+ Path access on a bound result: `{{q.count}}`/`{{q.length}}`, `{{q.first.col}}`, `{{q.last.col}}`, `{{q[2].col}}`, `{{q.col}}` (= first row's col). `expect A is B` also supports `is at least` / `is at most` / `is not`. Tier-2 declarative (trivial inline, no catalog): `User see [<table>] row where [<col>] is {{v}} [has [<col2>] = "x"]`, `… no row where …`, `… count is {{n}}`. Full grammar + catalog/datasource/secret rules → **Advanced → Database** doc. Only emit DB steps when the project has a `database/` catalog / `datasources.yaml`.
120
+
105
121
  ### States
106
122
 
107
123
  `hidden` `visible` `disabled` `enabled` `checked` `unchecked` `focused` `empty` `loading` `selected` `sorted ascending` `sorted descending`
@@ -195,6 +211,31 @@ Options: `nth` `exact` `scope` `match` `variant` `frame` `contenteditable` `colu
195
211
  | `@afterEach` | Hook: runs after each test → `test.afterEach()` (custom cleanup) |
196
212
  | `@afterAll` | Hook: runs once after all tests → `test.afterAll()` |
197
213
  | `@flow` | Mark feature as E2E flow (cross-screen testing) |
214
+ | `@cases:dataset` | Data-driven: run the scenario once per row of the `dataset` LIST in test-data → one `test()` per row |
215
+ | `@query:name` | Database: run the named query from `database/queries.yaml` (precondition) and bind its rows to `{{name}}`; assert with `expect {{name.count}} …` + path access. Override params `@query:name(p={{v}})`. Repeatable. (Optional Data Driver — see Database verification above) |
216
+ | `@api:name` | API: run the named request from `api/apis.yaml` (precondition) and bind the response to `{{name}}`; assert with `expect {{name.status}} …` + path access (`{{name.body.<path>}}`). Override params `@api:name(p={{v}})`. Repeatable. (Optional API Driver) |
217
+
218
+ ### Data-driven scenarios (`@cases`)
219
+
220
+ For one test case × many inputs (email/format/boundary validation, decision tables), tag the
221
+ scenario `@cases:<dataset>` and reference each row's columns as `{{col}}`. Put the rows as a LIST
222
+ in test-data — NOT inline; data stays runtime + env-overlayable.
223
+
224
+ ```gherkin
225
+ @high @cases:email_validation
226
+ Scenario: VP-VAL-001 The email field rejects invalid formats
227
+ When User fill [Email] field with {{email}}
228
+ Then User see [Login Error] message with {{expected_error}}
229
+ ```
230
+ ```yaml
231
+ # test-data/<screen>.yaml
232
+ email_validation:
233
+ - { case: "no @", email: "plainaddress", expected_error: "Invalid email" }
234
+ - { case: "valid", email: "ok@x.com", expected_error: "" }
235
+ ```
236
+ An optional `case`/`name`/`label` column labels each run. Each row → its own pass/fail. Prefer
237
+ `@cases` over duplicating a scenario per value. (Gherkin `Scenario Outline`/`Examples` is NOT
238
+ supported — use `@cases`.)
198
239
 
199
240
  ### Pass-through tags (filter at runtime via Playwright --grep)
200
241
 
@@ -6,6 +6,9 @@ user-invocable: false
6
6
 
7
7
  ## ⚠️ Gotchas — read before generating
8
8
 
9
+ - **Write incrementally — never emit the whole suite in one response.** Build the `.feature` in batches via successive `Write`/`Edit` (≈10–15 scenarios per call). For **Full coverage**, write tier-by-tier: `Write` Tier 1 → `Edit` append Tier 2 → `Edit` append Tier 3.
10
+ → One huge `Write` can exceed the model's output-token cap → `API Error: Claude's response exceeded the N output token maximum`. Single-pass full coverage only fits when `CLAUDE_CODE_MAX_OUTPUT_TOKENS ≥ 64000`; otherwise batch. Batching also lets the audit/reviewer run per batch — higher quality.
11
+
9
12
  - `spec_figma.md` exists → read file only, **NEVER** call `mcp__figma__*`
10
13
  → PAT auth flow already done by `sungen-capture` (mode figma-pat); re-calling fails or duplicates work.
11
14
 
@@ -54,6 +57,25 @@ user-invocable: false
54
57
  OR condition: generate 1 scenario per branch where that branch alone triggers the outcome.
55
58
  → Happy-path only = missing the most common multi-condition implementation bug.
56
59
 
60
+ - **Many inputs, same steps → ONE data-driven scenario (`@cases`), not N copies:**
61
+ When a rule needs lots of inputs with the *same* step shape (email/format validation,
62
+ BVA boundary triples, EP classes, decision-table rows), tag one scenario `@cases:<dataset>`,
63
+ reference each row's columns as `{{col}}`, and put the rows as a LIST in test-data:
64
+ ```gherkin
65
+ @high @cases:email_validation
66
+ Scenario: VP-VAL-001 The email field rejects invalid formats
67
+ When User fill [Email] field with {{email}}
68
+ Then User see [Error] message with {{expected_error}}
69
+ ```
70
+ ```yaml
71
+ email_validation:
72
+ - { case: "no @", email: "plainaddress", expected_error: "Invalid email" }
73
+ - { case: "valid", email: "ok@x.com", expected_error: "" }
74
+ ```
75
+ → one `test()` per row, each labelled by `case`. Adding inputs = editing test-data (no recompile),
76
+ and env overlays apply. Prefer this over duplicating a scenario per value. (Gherkin
77
+ `Scenario Outline`/`Examples` is NOT supported — use `@cases`.)
78
+
57
79
  ---
58
80
 
59
81
  ## Tier System
@@ -120,6 +120,7 @@ Build a mapping table: for each applicable group, does the feature have a matchi
120
120
  - **EP**: keep only **one representative** per invalid class; same-class duplicates → flag as redundant.
121
121
  - **BVA**: spec defines min/max → cover `min-1`, `min`, `max`, `max+1` (Maxlength, counts…).
122
122
  - Error messages must match the spec **word-for-word**, not generic.
123
+ - **Data-driven (`@cases`)**: a `@cases:<dataset>` scenario legitimately covers many inputs in ONE scenario (one row per EP class / boundary / rule). Do **not** flag it as "too few negative cases" or as duplication — instead review the **dataset rows**: are all EP classes / boundary triples present, each labelled, expected values exact? N near-identical scenarios that differ only by input value → flag and recommend collapsing to `@cases`.
123
124
 
124
125
  ---
125
126
 
@@ -17,6 +17,12 @@ Apply selectively — not every screen needs all four techniques. Use the techni
17
17
 
18
18
  **Rule:** These techniques determine **how many** and **which** scenarios to generate. `sungen-viewpoint` determines **which viewpoints** to cover.
19
19
 
20
+ **Implementing the data table → `@cases` (data-driven):** when EP classes / BVA boundary triples /
21
+ decision-table rows share the *same step shape* and differ only by input/expected values, encode
22
+ them as ONE `@cases:<dataset>` scenario (each class/boundary/rule = one row in the test-data list,
23
+ labelled by a `case` column) instead of N near-duplicate scenarios. The technique still decides the
24
+ rows; `@cases` is how you write them compactly. See `sungen-gherkin-syntax` → Data-driven.
25
+
20
26
  ---
21
27
 
22
28
  ## 1. Equivalence Partitioning (EP)
@@ -0,0 +1,101 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * Sungen API Driver — runtime helper (auto-generated into specs/api.ts).
4
+ *
5
+ * Runs a catalog-defined HTTP request and returns { status, ok, body, headers } — bound to a
6
+ * `{{name}}` variable by the `@api:<name>` annotation, asserted with `expect {{name.status}} …` /
7
+ * `{{name.body.<path>}}`. Base URL + auth come from a `kind: api` datasource in datasources.yaml,
8
+ * with `${VAR}` resolved from .env.qa / process.env — never inline.
9
+ *
10
+ * Safety: a datasource flagged `env: production` is refused unless SUNGEN_ALLOW_PROD=1.
11
+ * DO NOT EDIT — regenerated by `sungen generate`.
12
+ */
13
+ import * as fs from 'fs';
14
+ import * as path from 'path';
15
+
16
+ interface ApiDataSource {
17
+ kind?: string;
18
+ base_url?: string;
19
+ baseUrl?: string;
20
+ env?: string;
21
+ headers?: Record<string, string>;
22
+ timeout_ms?: number;
23
+ }
24
+
25
+ function loadEnvQa(): void {
26
+ for (const name of ['.env.qa', `.env.qa.${process.env.SUNGEN_ENV || ''}`]) {
27
+ const p = path.join(process.cwd(), name);
28
+ if (!name.endsWith('.') && fs.existsSync(p)) {
29
+ for (const line of fs.readFileSync(p, 'utf8').split('\n')) {
30
+ const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/);
31
+ if (m && process.env[m[1]] === undefined) process.env[m[1]] = m[2].replace(/^["']|["']$/g, '');
32
+ }
33
+ }
34
+ }
35
+ }
36
+
37
+ function loadConfig(): Record<string, ApiDataSource> {
38
+ loadEnvQa();
39
+ const file = [path.join(process.cwd(), 'datasources.yaml'), path.join(process.cwd(), 'qa', 'datasources.yaml')].find((f) => fs.existsSync(f));
40
+ if (!file) throw new Error('API Driver: no datasources.yaml found (project root or qa/).');
41
+ const raw = fs.readFileSync(file, 'utf8').replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, k) => process.env[k] ?? '');
42
+ const { parse } = require('yaml');
43
+ const doc = parse(raw) || {};
44
+ return doc.datasources || {};
45
+ }
46
+
47
+ function substitute(text: string, params: Record<string, any>): string {
48
+ return text.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, (_m, p) => encodeURIComponent(String(params[p] ?? '')));
49
+ }
50
+
51
+ class ApiClient {
52
+ private configs: Record<string, ApiDataSource> | null = null;
53
+
54
+ private cfg(name?: string): { key: string; conf: ApiDataSource } {
55
+ if (!this.configs) this.configs = loadConfig();
56
+ const key = name || Object.keys(this.configs).find((k) => (this.configs![k].kind || 'api') === 'api') || Object.keys(this.configs)[0];
57
+ const conf = this.configs[key];
58
+ if (!conf) throw new Error(`API Driver: datasource "${key}" not found in datasources.yaml`);
59
+ if (conf.env === 'production' && process.env.SUNGEN_ALLOW_PROD !== '1') {
60
+ throw new Error(`API Driver: datasource "${key}" is env: production — refused (set SUNGEN_ALLOW_PROD=1 to override).`);
61
+ }
62
+ return { key, conf };
63
+ }
64
+
65
+ /** Run a catalog request and return the response. `req` is embedded at compile time; `params` bind at runtime. */
66
+ async call(
67
+ label: string,
68
+ req: { method: string; path: string; body?: unknown; datasource?: string },
69
+ params: Record<string, any> = {},
70
+ ): Promise<{ status: number; ok: boolean; body: any; headers: Record<string, string> }> {
71
+ const { conf } = this.cfg(req.datasource);
72
+ const base = (conf.base_url || conf.baseUrl || '').replace(/\/$/, '');
73
+ if (!base) throw new Error(`API Driver: ${label} — datasource has no base_url (set it in .env.qa).`);
74
+ const url = base + substitute(req.path, params);
75
+
76
+ let body: string | undefined;
77
+ const headers: Record<string, string> = { ...(conf.headers || {}) };
78
+ if (req.body !== undefined && req.body !== null) {
79
+ const resolved = JSON.parse(JSON.stringify(req.body).replace(/":([A-Za-z_][A-Za-z0-9_]*)"/g, (_m, p) => JSON.stringify(params[p] ?? null)));
80
+ body = JSON.stringify(resolved);
81
+ if (!headers['content-type'] && !headers['Content-Type']) headers['content-type'] = 'application/json';
82
+ }
83
+
84
+ const controller = new AbortController();
85
+ const timer = setTimeout(() => controller.abort(), conf.timeout_ms ?? 15000);
86
+ let res: Response;
87
+ try {
88
+ res = await fetch(url, { method: req.method, headers, body, signal: controller.signal });
89
+ } finally {
90
+ clearTimeout(timer);
91
+ }
92
+ const text = await res.text();
93
+ let parsed: any = text;
94
+ try { parsed = text ? JSON.parse(text) : null; } catch { /* non-JSON → keep text */ }
95
+ const outHeaders: Record<string, string> = {};
96
+ res.headers.forEach((v, k) => { outHeaders[k] = v; });
97
+ return { status: res.status, ok: res.ok, body: parsed, headers: outHeaders };
98
+ }
99
+ }
100
+
101
+ export const api = new ApiClient();
@@ -138,6 +138,28 @@ class DataSource {
138
138
  private sqlFor(conf: DataSourceConfig, sql: string): string {
139
139
  return conf.engine === 'sqlite' ? sql.replace(/\$\d+/g, '?') : sql;
140
140
  }
141
+
142
+ // --- Named queries (catalog-backed; SQL is resolved + embedded at compile time) -----------
143
+ /** Read-only guard (second layer): a named query must be a single SELECT/WITH statement. */
144
+ private assertSelectOnly(label: string, sql: string): void {
145
+ const s = sql.trim().replace(/;\s*$/, '');
146
+ if (!/^(SELECT|WITH)\b/i.test(s)) throw new Error(`Data Driver: ${label} is not a read-only SELECT — refused.`);
147
+ if (s.includes(';')) throw new Error(`Data Driver: ${label} contains multiple statements — refused.`);
148
+ if (/\b(INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|TRUNCATE|GRANT|REVOKE|MERGE|REPLACE|CALL|EXEC|EXECUTE|ATTACH|PRAGMA|VACUUM)\b/i.test(s)) {
149
+ throw new Error(`Data Driver: ${label} contains a write/DDL keyword — refused.`);
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Run a catalog query (read-only) and return its rows. The result is bound to a `{{name}}`
155
+ * variable via `testData.bind(...)`, so the scenario asserts on it with `expect …` steps and
156
+ * path access (`{{name.count}}`, `{{name.first.col}}`, `{{name[2].col}}`).
157
+ */
158
+ async fetchQuery(label: string, sql: string, params: any[], datasource?: string): Promise<any[]> {
159
+ this.assertSelectOnly(label, sql);
160
+ const { engine, conf } = await this.engine(datasource);
161
+ return engine.query(this.sqlFor(conf, sql), params);
162
+ }
141
163
  }
142
164
 
143
165
  function desc(filter: Record<string, any>): string {
@@ -5,6 +5,8 @@ import yaml from 'yaml';
5
5
 
6
6
  export class TestDataLoader {
7
7
  private data: Record<string, any>;
8
+ // Data-driven (@cases): when set (via withRow), get() prefers this row's columns.
9
+ private row?: Record<string, any>;
8
10
 
9
11
  private constructor(data: Record<string, any>) {
10
12
  this.data = data;
@@ -41,23 +43,56 @@ export class TestDataLoader {
41
43
  }
42
44
 
43
45
  get(key: string): string {
44
- // Captured/runtime vars (set() below) are stored under their literal — possibly
45
- // dotted — key (e.g. "cart.product_name"), so check the flat key first.
46
- let current: any = this.data[key];
47
- if (current === undefined || current === null) {
48
- // Fall back to nested navigation for YAML-structured keys (e.g. "cart.qty_two").
49
- current = this.data;
50
- for (const part of key.split('.')) {
51
- if (current == null || typeof current !== 'object') {
52
- throw new Error(`Test data key not found: ${key} (failed at '${part}')`);
53
- }
54
- current = current[part];
55
- }
56
- }
57
- if (current === undefined || current === null) {
46
+ const value = this.resolve(key);
47
+ if (value === undefined || value === null) {
58
48
  throw new Error(`Test data key not found: ${key}`);
59
49
  }
60
- return String(current);
50
+ return String(value);
51
+ }
52
+
53
+ /**
54
+ * Resolve a `{{...}}` reference to its raw value. Supports:
55
+ * - flat keys (incl. captured runtime vars stored under a literal dotted key);
56
+ * - `@cases` row columns (the current row wins);
57
+ * - structured paths over nested data AND `@query`-bound result arrays:
58
+ * `q.count` / `q.length` → number of rows
59
+ * `q.first.col` / `q.last.col` / `q[2].col` → a specific row's column
60
+ * `q.col` → shorthand for the first row's column
61
+ */
62
+ private resolve(key: string): any {
63
+ // 1. Exact flat key — captured vars (set()) live under a literal, possibly dotted, key.
64
+ if (this.row && key in this.row && this.row[key] !== undefined && this.row[key] !== null) {
65
+ return this.row[key];
66
+ }
67
+ if (this.data[key] !== undefined && this.data[key] !== null) {
68
+ return this.data[key];
69
+ }
70
+ // 2. Structured path: head from the row (cases) or shared data, then walk segments.
71
+ const tokens = String(key).replace(/\[(\d+)\]/g, '.$1').split('.');
72
+ let cur: any = (this.row && tokens[0] in this.row) ? this.row[tokens[0]] : this.data[tokens[0]];
73
+ for (let i = 1; i < tokens.length && cur != null; i++) cur = TestDataLoader.step(cur, tokens[i]);
74
+ return cur;
75
+ }
76
+
77
+ /** One navigation step over an array (with count/first/last/index/field-shorthand) or object. */
78
+ private static step(cur: any, token: string): any {
79
+ if (Array.isArray(cur)) {
80
+ if (token === 'count' || token === 'length') return cur.length;
81
+ if (token === 'first') return cur[0];
82
+ if (token === 'last') return cur[cur.length - 1];
83
+ if (/^\d+$/.test(token)) return cur[Number(token)];
84
+ return cur[0] == null ? undefined : cur[0][token]; // shorthand: first row's field
85
+ }
86
+ if (cur && typeof cur === 'object') return cur[token];
87
+ return undefined;
88
+ }
89
+
90
+ /**
91
+ * Bind a raw value (e.g. an `@query` result array) under `key` so `{{key.…}}` paths resolve.
92
+ * Unlike set(), the value is stored as-is (array/object), not coerced to a string.
93
+ */
94
+ bind(key: string, value: any): void {
95
+ this.data[key] = value;
61
96
  }
62
97
 
63
98
  /**
@@ -68,6 +103,32 @@ export class TestDataLoader {
68
103
  set(key: string, value: string): void {
69
104
  this.data[key] = value;
70
105
  }
106
+
107
+ /**
108
+ * Data-driven (@cases): return the list of rows at `key` (after env-overlay merge),
109
+ * each stamped with `__label` for the test title / report. Throws if missing or not a list.
110
+ */
111
+ cases(key: string): Array<Record<string, any>> {
112
+ const list = this.data[key];
113
+ if (!Array.isArray(list)) {
114
+ throw new Error(`@cases dataset "${key}" not found or not a list in test-data (got ${typeof list}).`);
115
+ }
116
+ return list.map((row: any, i: number) => {
117
+ const r: Record<string, any> = (row && typeof row === 'object' && !Array.isArray(row)) ? { ...row } : { value: row };
118
+ r.__label = String(r.case ?? r.name ?? r.label ?? `row ${i + 1}`);
119
+ return r;
120
+ });
121
+ }
122
+
123
+ /**
124
+ * Data-driven (@cases): a view whose get() prefers the given row's columns and falls
125
+ * back to the shared data. Used inside the per-row test() loop.
126
+ */
127
+ withRow(row: Record<string, any>): TestDataLoader {
128
+ const view = new TestDataLoader({ ...this.data }); // clone → per-row set() stays isolated
129
+ view.row = row;
130
+ return view;
131
+ }
71
132
  }
72
133
 
73
134
  function loadYamlSync(filePath: string): Record<string, any> | null {
@@ -1,7 +0,0 @@
1
- import { StepPattern } from './types';
2
- /**
3
- * Assertion patterns: visibility, text content, state, attributes
4
- * Uses template engine for framework-agnostic code generation
5
- */
6
- export declare const assertionPatterns: StepPattern[];
7
- //# sourceMappingURL=assertion-patterns.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"assertion-patterns.d.ts","sourceRoot":"","sources":["../../../../src/generators/test-generator/patterns/assertion-patterns.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAoB,MAAM,SAAS,CAAC;AAExD;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,WAAW,EA2qB1C,CAAC"}