@sun-asterisk/sungen 3.1.2-beta.93 → 3.1.2-beta.97

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 (150) 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/generate.d.ts.map +1 -1
  27. package/dist/cli/commands/generate.js +7 -3
  28. package/dist/cli/commands/generate.js.map +1 -1
  29. package/dist/cli/index.js +10 -1
  30. package/dist/cli/index.js.map +1 -1
  31. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +1 -0
  32. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  33. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +1 -0
  34. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  35. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  36. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  37. package/dist/generators/test-generator/code-generator.d.ts +11 -9
  38. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  39. package/dist/generators/test-generator/code-generator.js +53 -76
  40. package/dist/generators/test-generator/code-generator.js.map +1 -1
  41. package/dist/generators/test-generator/patterns/index.d.ts +0 -10
  42. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
  43. package/dist/generators/test-generator/patterns/index.js +10 -47
  44. package/dist/generators/test-generator/patterns/index.js.map +1 -1
  45. package/dist/generators/test-generator/template-engine.d.ts +1 -0
  46. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  47. package/dist/generators/test-generator/template-engine.js +1 -1
  48. package/dist/generators/test-generator/template-engine.js.map +1 -1
  49. package/dist/harness/annotation-overrides.d.ts +9 -0
  50. package/dist/harness/annotation-overrides.d.ts.map +1 -0
  51. package/dist/harness/annotation-overrides.js +36 -0
  52. package/dist/harness/annotation-overrides.js.map +1 -0
  53. package/dist/harness/audit.d.ts.map +1 -1
  54. package/dist/harness/audit.js +35 -7
  55. package/dist/harness/audit.js.map +1 -1
  56. package/dist/harness/catalog/drivers.yaml +35 -12
  57. package/dist/harness/parse.d.ts +1 -0
  58. package/dist/harness/parse.d.ts.map +1 -1
  59. package/dist/harness/parse.js +13 -4
  60. package/dist/harness/parse.js.map +1 -1
  61. package/dist/index.d.ts +20 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +32 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +1 -0
  66. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +1 -0
  67. package/dist/orchestrator/templates/specs-api.d.ts +19 -0
  68. package/dist/orchestrator/templates/specs-api.d.ts.map +1 -0
  69. package/dist/orchestrator/templates/specs-api.js +128 -0
  70. package/dist/orchestrator/templates/specs-api.js.map +1 -0
  71. package/dist/orchestrator/templates/specs-api.ts +101 -0
  72. package/package.json +7 -30
  73. package/src/capabilities/builtins.ts +85 -0
  74. package/src/capabilities/context-router.ts +66 -0
  75. package/src/capabilities/context.ts +46 -0
  76. package/src/capabilities/discover.ts +42 -0
  77. package/src/capabilities/registry.ts +111 -0
  78. package/src/capabilities/sensor.ts +47 -0
  79. package/src/cli/commands/generate.ts +7 -3
  80. package/src/cli/index.ts +10 -1
  81. package/src/generators/test-generator/adapters/adapter-interface.ts +1 -1
  82. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +1 -1
  83. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  84. package/src/generators/test-generator/code-generator.ts +51 -74
  85. package/src/generators/test-generator/patterns/index.ts +9 -35
  86. package/src/generators/test-generator/template-engine.ts +2 -2
  87. package/src/harness/annotation-overrides.ts +25 -0
  88. package/src/harness/audit.ts +37 -8
  89. package/src/harness/catalog/drivers.yaml +35 -12
  90. package/src/harness/parse.ts +7 -2
  91. package/src/index.ts +30 -0
  92. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +1 -0
  93. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +1 -0
  94. package/src/orchestrator/templates/specs-api.ts +101 -0
  95. package/dist/generators/test-generator/patterns/assertion-patterns.d.ts +0 -7
  96. package/dist/generators/test-generator/patterns/assertion-patterns.d.ts.map +0 -1
  97. package/dist/generators/test-generator/patterns/assertion-patterns.js +0 -626
  98. package/dist/generators/test-generator/patterns/assertion-patterns.js.map +0 -1
  99. package/dist/generators/test-generator/patterns/capture-patterns.d.ts +0 -21
  100. package/dist/generators/test-generator/patterns/capture-patterns.d.ts.map +0 -1
  101. package/dist/generators/test-generator/patterns/capture-patterns.js +0 -87
  102. package/dist/generators/test-generator/patterns/capture-patterns.js.map +0 -1
  103. package/dist/generators/test-generator/patterns/database-patterns.d.ts +0 -6
  104. package/dist/generators/test-generator/patterns/database-patterns.d.ts.map +0 -1
  105. package/dist/generators/test-generator/patterns/database-patterns.js +0 -95
  106. package/dist/generators/test-generator/patterns/database-patterns.js.map +0 -1
  107. package/dist/generators/test-generator/patterns/form-patterns.d.ts +0 -6
  108. package/dist/generators/test-generator/patterns/form-patterns.d.ts.map +0 -1
  109. package/dist/generators/test-generator/patterns/form-patterns.js +0 -160
  110. package/dist/generators/test-generator/patterns/form-patterns.js.map +0 -1
  111. package/dist/generators/test-generator/patterns/interaction-patterns.d.ts +0 -6
  112. package/dist/generators/test-generator/patterns/interaction-patterns.d.ts.map +0 -1
  113. package/dist/generators/test-generator/patterns/interaction-patterns.js +0 -433
  114. package/dist/generators/test-generator/patterns/interaction-patterns.js.map +0 -1
  115. package/dist/generators/test-generator/patterns/keyboard-patterns.d.ts +0 -7
  116. package/dist/generators/test-generator/patterns/keyboard-patterns.d.ts.map +0 -1
  117. package/dist/generators/test-generator/patterns/keyboard-patterns.js +0 -47
  118. package/dist/generators/test-generator/patterns/keyboard-patterns.js.map +0 -1
  119. package/dist/generators/test-generator/patterns/navigation-patterns.d.ts +0 -6
  120. package/dist/generators/test-generator/patterns/navigation-patterns.d.ts.map +0 -1
  121. package/dist/generators/test-generator/patterns/navigation-patterns.js +0 -125
  122. package/dist/generators/test-generator/patterns/navigation-patterns.js.map +0 -1
  123. package/dist/generators/test-generator/patterns/scope-patterns.d.ts +0 -7
  124. package/dist/generators/test-generator/patterns/scope-patterns.d.ts.map +0 -1
  125. package/dist/generators/test-generator/patterns/scope-patterns.js +0 -36
  126. package/dist/generators/test-generator/patterns/scope-patterns.js.map +0 -1
  127. package/dist/generators/test-generator/patterns/scroll-patterns.d.ts +0 -7
  128. package/dist/generators/test-generator/patterns/scroll-patterns.d.ts.map +0 -1
  129. package/dist/generators/test-generator/patterns/scroll-patterns.js +0 -25
  130. package/dist/generators/test-generator/patterns/scroll-patterns.js.map +0 -1
  131. package/dist/generators/test-generator/patterns/setup-patterns.d.ts +0 -6
  132. package/dist/generators/test-generator/patterns/setup-patterns.d.ts.map +0 -1
  133. package/dist/generators/test-generator/patterns/setup-patterns.js +0 -72
  134. package/dist/generators/test-generator/patterns/setup-patterns.js.map +0 -1
  135. package/dist/generators/test-generator/patterns/table-patterns.d.ts +0 -19
  136. package/dist/generators/test-generator/patterns/table-patterns.d.ts.map +0 -1
  137. package/dist/generators/test-generator/patterns/table-patterns.js +0 -239
  138. package/dist/generators/test-generator/patterns/table-patterns.js.map +0 -1
  139. package/docs/orchestration-spec.md +0 -267
  140. package/src/generators/test-generator/patterns/assertion-patterns.ts +0 -691
  141. package/src/generators/test-generator/patterns/capture-patterns.ts +0 -97
  142. package/src/generators/test-generator/patterns/database-patterns.ts +0 -96
  143. package/src/generators/test-generator/patterns/form-patterns.ts +0 -167
  144. package/src/generators/test-generator/patterns/interaction-patterns.ts +0 -465
  145. package/src/generators/test-generator/patterns/keyboard-patterns.ts +0 -51
  146. package/src/generators/test-generator/patterns/navigation-patterns.ts +0 -140
  147. package/src/generators/test-generator/patterns/scope-patterns.ts +0 -40
  148. package/src/generators/test-generator/patterns/scroll-patterns.ts +0 -27
  149. package/src/generators/test-generator/patterns/setup-patterns.ts +0 -76
  150. package/src/generators/test-generator/patterns/table-patterns.ts +0 -279
package/README.md CHANGED
@@ -1,431 +1,7 @@
1
- # Sungen v2 — Deterministic E2E Test Compiler
1
+ # @sun-asterisk/sungen
2
2
 
3
- **Version**: 2.2.3
4
- **Gherkin + Selector YAML + Test Data → Playwright .spec.ts**
3
+ Deterministic E2E test compiler — Gherkin + selector YAML + test-data → Playwright tests, with a Capability Planner and a Harness/audit layer.
5
4
 
6
- ---
5
+ This is the **core kernel**: Gherkin→intent IR, the selector contract, the harness/audit + planner, the CLI, and the **capability SPI** that drivers plug into. It embeds no test runtime — UI, DB, and API capabilities ship as separate `@sungen/driver-*` packages discovered at runtime.
7
6
 
8
- ## What is Sungen?
9
-
10
- Sungen is a deterministic compiler that converts Gherkin features into Playwright tests.
11
-
12
- **v2 architecture**: AI generates the input files (Gherkin + selectors), sungen compiles to tests. No AI inside sungen itself.
13
-
14
- ```
15
- AI (Copilot/Claude + MCP Playwright) → visits URL → generates files
16
- sungen generate → compiles .feature + selectors.yaml + data.yaml → .spec.ts
17
- ```
18
-
19
- ---
20
-
21
- ## Quick Start
22
-
23
- ```bash
24
- # Install
25
- npm install -g @sun-asterisk/sungen@latest
26
-
27
- # Initialize project (creates AI rules + directory structure)
28
- sungen init --base-url https://your-app.com
29
-
30
- # Use AI slash commands to generate tests
31
- # In GitHub Copilot Chat:
32
- /sungen-add-screen login /auth/login
33
- /sungen-create-test login
34
- /sungen-run-test login
35
- ```
36
-
37
- ---
38
-
39
- ## Generate from Figma (web-only, no desktop)
40
-
41
- Use a Figma design URL as the source for screen specs and test scaffolding — no live app needed.
42
-
43
- ### Quick start (5 steps)
44
-
45
- ```bash
46
- # 1. Authenticate once — stores PAT in .env (gitignored)
47
- sungen figma auth set
48
-
49
- # 2. Scaffold screen from a Figma frame URL
50
- sungen add --screen my-screen --figma "https://www.figma.com/design/<key>/<name>?node-id=<id>"
51
-
52
- # 3. Generate test cases (AI reads spec_figma.md + ui/*.png)
53
- # /sungen:create-test my-screen
54
-
55
- # 4. Review syntax and coverage
56
- # /sungen:review my-screen
57
-
58
- # 5. Generate selectors + compile + run (no live page needed for compilation)
59
- # /sungen:run-test my-screen
60
- ```
61
-
62
- ### PAT setup
63
-
64
- 1. Go to **figma.com → Settings → Security → Personal access tokens**
65
- 2. Click **Generate new token**
66
- 3. Set scopes: `file_content:read`, `file_metadata:read`
67
- 4. Copy the token — it is shown once
68
- 5. Run `sungen figma auth set` and paste when prompted
69
-
70
- ### Figma URL format
71
-
72
- ```
73
- https://www.figma.com/design/<fileKey>/<fileName>?node-id=<nodeId>
74
- ```
75
-
76
- Example:
77
- ```
78
- https://www.figma.com/design/AbCdEfGhIjKlMnOpQrSt/My-App?node-id=1-23
79
- ```
80
-
81
- The `node-id` selects a specific frame or component. Right-click any frame in Figma → "Copy link to selection" to get the correct URL.
82
-
83
- ### Flags
84
-
85
- | Flag | Default | Description |
86
- |------|---------|-------------|
87
- | `--refresh` | false | Re-fetch image even if cached |
88
- | `--scale <n>` | 2 | Export resolution multiplier (1–4) |
89
- | `--hi-res` | false | Alias for `--scale 4` |
90
-
91
- ### Cache
92
-
93
- Rendered PNGs are cached in `.sungen/figma-cache/` (gitignored). On re-run, cached images are used unless `--refresh` is passed. Cache entries are keyed by file version — a new Figma publish busts the cache automatically.
94
-
95
- ### Migration notes
96
-
97
- Existing screens are unaffected. To add Figma specs to an existing screen:
98
-
99
- ```bash
100
- sungen add --screen <existing-screen> --figma <figma-url>
101
- ```
102
-
103
- This adds `spec_figma.md` + `requirements/ui/*.png` without touching `spec.md` or `.feature` files (idempotent).
104
-
105
- ### Troubleshooting
106
-
107
- | Error | Cause | Fix |
108
- |-------|-------|-----|
109
- | `401 Unauthorized` | PAT invalid or expired | Re-run `sungen figma auth set` |
110
- | `403 Forbidden` | No access to file | Request access from file owner in Figma |
111
- | `429 Too Many Requests` | Rate limited | Automatic exponential backoff; wait and retry |
112
- | Expired S3 URL | Image URL expired (~30 days) | Re-run `sungen add --figma` — PNGs are downloaded locally so this is rare |
113
-
114
- ---
115
-
116
- ## CLI Commands
117
-
118
- | Command | Description |
119
- |---------|-------------|
120
- | `sungen init` | Scaffold project + generate AI rules |
121
- | `sungen add --screen <name> --path <path>` | Create screen with empty templates |
122
- | `sungen add --screen <name> --figma <url>` | Scaffold from Figma frame (alternative to `--path`) |
123
- | `sungen add --screen <name> --feature <name>` | Add feature file to existing screen |
124
- | `sungen generate --screen <name>` | Compile Gherkin → Playwright .spec.ts |
125
- | `sungen generate --all` | Compile all screens |
126
- | `sungen makeauth <role>` | Capture browser auth state (SSO support) |
127
- | `sungen figma auth set` | Store Figma PAT (one-time setup) |
128
- | `sungen figma auth check` | Verify PAT is stored and valid |
129
- | `sungen figma auth clear` | Remove stored PAT |
130
-
131
- ---
132
-
133
- ## AI Slash Commands
134
-
135
- | Action | GitHub Copilot (VS Code) | Claude Code |
136
- |--------|--------------------------|-------------|
137
- | Add screen | `/sungen-add-screen login /auth/login` | `/sungen:add-screen login /auth/login` |
138
- | Create test cases | `/sungen-create-test login` | `/sungen:create-test login` |
139
- | Compile & run tests | `/sungen-run-test login` | `/sungen:run-test login` |
140
-
141
- ### create-test — Create test cases
142
-
143
- AI acts as a **Senior QA Engineer**. Supports 3 input modes:
144
- - **Live page** — AI visits the page via MCP Playwright, analyzes UI elements
145
- - **Figma / screenshots** — AI generates tests from static designs
146
- - **Update mode** — AI detects existing tests and asks: add new, expand, or replace
147
-
148
- Output: `.feature` + `test-data.yaml` (no selectors — those are generated during run-test)
149
-
150
- ### run-test — Compile & run
151
-
152
- AI acts as a **Senior Developer**:
153
- 1. Explores live page → generates `selectors.yaml`
154
- 2. Compiles: `sungen generate --screen <name>`
155
- 3. Runs: `npx playwright test`
156
- 4. If fail → auto-fixes selectors/test-data → retry (up to 5 attempts)
157
-
158
- ---
159
-
160
- ## Workflow
161
-
162
- ### 1. Add a screen
163
-
164
- ```bash
165
- sungen add --screen awards --path /awards
166
- ```
167
-
168
- Creates:
169
- ```
170
- qa/screens/awards/
171
- ├── features/awards.feature # Gherkin template
172
- ├── selectors/awards.yaml # Selector YAML (page entry pre-filled)
173
- └── test-data/awards.yaml # Test data (empty)
174
- ```
175
-
176
- ### 2. AI generates Gherkin + selectors
177
-
178
- Use your AI assistant (GitHub Copilot or Claude Code) with MCP Playwright to:
179
- 1. Navigate to the page URL
180
- 2. Take an accessibility snapshot
181
- 3. Generate `.feature` + `selectors.yaml` + `test-data.yaml`
182
-
183
- The AI rules in `.github/prompts/` and `.claude/` (created by `sungen init`) teach the AI the correct syntax.
184
-
185
- ### 3. Compile + run
186
-
187
- ```bash
188
- sungen generate --screen awards
189
- npx playwright test
190
- ```
191
-
192
- ### 4. Fix failures (AI verify loop)
193
-
194
- When tests fail → AI reads error → fixes `selectors.yaml` → recompile → re-run.
195
-
196
- | Error | Fix in selectors.yaml |
197
- |---|---|
198
- | strict mode: resolved to N elements | Add `exact: true` or `scope` or `nth` |
199
- | element(s) not found | Fix `name`, `type`, or `value` |
200
- | getByText matched too many | Add `match: 'exact'` or `nth` |
201
-
202
- ---
203
-
204
- ## Gherkin Syntax
205
-
206
- **Pattern**: `[Keyword] User <Action> [Target Name] <Target Type> <with {{Value}}> <is State>`
207
-
208
- - `[Target]` → selector reference → lookup in `selectors/*.yaml`
209
- - `{{Value}}` → test data reference → lookup in `test-data/*.yaml`
210
-
211
- ### Example
212
-
213
- ```gherkin
214
- @feature_auth
215
- Feature: Login Screen
216
- Path: /auth/login
217
-
218
- @auto @smoke
219
- Scenario: User logs in successfully
220
- Given User is on [Login] page
221
- When User fill [Email] field with {{valid_email}}
222
- And User fill [Password] field with {{valid_password}}
223
- And User click [Login] button
224
- Then User see [Dashboard] page
225
- And User see [Welcome] heading with {{success_message}}
226
- And User see [Logout] button is enabled
227
- ```
228
-
229
- ---
230
-
231
- ## Pattern Shapes (18 categories)
232
-
233
- ### Actions
234
-
235
- | Pattern | Example |
236
- |---|---|
237
- | Simple click | `User click [Submit] button` |
238
- | Click dynamic list | `User click [Order] row with {{order_name}}` |
239
- | Fill field | `User fill [Email] field with {{email}}` |
240
- | Fill search | `User fill [Global] search with {{keyword}}` |
241
- | Check/Uncheck | `User check [Remember me] checkbox` / `User check [Notification] toggle` |
242
- | Select dropdown | `User select [Country] dropdown with {{country}}` |
243
- | Upload file | `User upload [Avatar] uploader with {{path}}` |
244
- | Double click | `User double click [Cell] element` |
245
- | Hover | `User hover [Info] icon` / `User hover [Order] row` |
246
- | Clear | `User clear [Search] field` |
247
-
248
- > **click + with rule**: `with {{Value}}` only for dynamic lists (`row`, `item`, `card`, `option`). Never for static elements (`button`, `link`, `icon`, `tab`).
249
-
250
- ### Browser Alert
251
-
252
- | Pattern | Example |
253
- |---|---|
254
- | Accept | `User click [OK] alert` (also: Accept, Yes, Confirm) |
255
- | Dismiss | `User click [Cancel] alert` (also: Dismiss, No) |
256
- | Fill prompt | `User fill [Name] alert with {{value}}` |
257
-
258
- > Alert steps must appear **before** the triggering action. Generates `page.once('dialog', ...)`.
259
-
260
- ### Keyboard
261
-
262
- | Pattern | Example |
263
- |---|---|
264
- | Global key | `User press Escape key` |
265
- | Key on element | `User press Enter on [Search] field` |
266
-
267
- ### Navigation
268
-
269
- | Pattern | Example |
270
- |---|---|
271
- | Open page | `User is on [Login] page` |
272
- | See page (URL) | `User see [Dashboard] page` |
273
-
274
- ### Wait
275
-
276
- | Pattern | Example |
277
- |---|---|
278
- | Wait timeout | `User wait for 3 seconds` |
279
- | Wait element | `User wait for [Modal] dialog` |
280
- | Wait hidden | `User wait for [Loading] spinner is hidden` |
281
- | Wait with data | `User wait for [Dialog] dialog with {{title}}` |
282
-
283
- ### Assertions (7 verify patterns)
284
-
285
- | Pattern | Assertion | Example |
286
- |---|---|---|
287
- | Visibility | `toBeVisible()` | `User see [Success] message` |
288
- | Hidden | `toBeHidden()` | `User see [Ads] modal is hidden` |
289
- | Text content | `toHaveText()` | `User see [Error] message with {{err_msg}}` |
290
- | Input value | `toHaveValue()` | `User see [Email] field with {{user_email}}` |
291
- | Partial text | `toContainText()` | `User see [Title] text contains {{partial}}` |
292
- | Component state | `toBeDisabled()` etc. | `User see [Submit] button is disabled` |
293
- | Page context | `toHaveURL()` | `User see [Dashboard] page` |
294
-
295
- **States**: `hidden`, `visible`, `disabled`, `enabled`, `checked`, `unchecked`, `focused`, `empty`, `loading`, `selected`, `sorted ascending`, `sorted descending`
296
-
297
- ### Table
298
-
299
- | Pattern | Example |
300
- |---|---|
301
- | Column exists | `User see [Email] column in [Users] table` |
302
- | Row exists | `User see [Username] row in [Users] table with {{name}}` |
303
- | Row hidden | `User see [Username] row in [Users] table with {{name}} is hidden` |
304
- | Row count | `User see [Users] table with {{count}}` |
305
- | Empty table | `User see [Users] table is empty` |
306
- | Cell value | `User see [Status] column with {{status}}` (row scoped) |
307
- | Action in row | `User click [Edit] button in [Users] table with {{name}}` |
308
-
309
- ### Scope
310
-
311
- | Pattern | Example |
312
- |---|---|
313
- | Scroll | `User scroll to [Footer] section` |
314
- | Frame enter | `User switch to [Payment] frame` |
315
- | Frame exit | `User switch to [main] frame` |
316
-
317
- ### Element Types
318
-
319
- | Group | Types |
320
- |---|---|
321
- | **Context** | `page` `dialog` `modal` `drawer` `tab` `alert` `overlay` `step` |
322
- | **Input** | `field` `textarea` `search` `dropdown` `option` `checkbox` `radio` `toggle` `uploader` `slider` `date-picker` |
323
- | **Trigger** | `button` `link` `icon` `menuitem` `tag` |
324
- | **Data** | `table` `row` `column` `cell` `list` `item` `card` `section` |
325
- | **Feedback** | `message` `header` `label` `text` `tooltip` `badge` `breadcrumb` `image` |
326
- | **System** | `key` `frame` `spinner` `progressbar` |
327
-
328
- ---
329
-
330
- ## Selector YAML v2 — NFC Key Format
331
-
332
- Sungen v2 uses **Unicode NFC normalization** for selector keys — not dot notation. Keys use spaces, support Vietnamese, Japanese, and all Unicode characters.
333
-
334
- ```yaml
335
- # NFC keys (spaces, not dots)
336
- email address:
337
- type: 'role'
338
- value: 'textbox'
339
- name: 'Email Address'
340
- exact: true
341
-
342
- # Japanese
343
- ログイン:
344
- type: 'role'
345
- value: 'button'
346
- name: 'ログイン'
347
-
348
- # Page navigation
349
- login:
350
- type: 'page'
351
- value: '/auth/login'
352
-
353
- # Scoped element
354
- header login:
355
- type: 'role'
356
- value: 'button'
357
- name: 'Login'
358
- scope: 'main navigation'
359
- ```
360
-
361
- **Locator priority**: `data-testid` > `role+name` > `label` > `text` > `CSS`
362
-
363
- **Types**: `testid`, `role`, `text`, `label`, `placeholder`, `locator`, `page`, `upload`, `frame`
364
-
365
- ---
366
-
367
- ## Tags
368
-
369
- | Tag | Level | Description |
370
- |-----|-------|-------------|
371
- | `@auto` | Scenario | Standard scenario, ready for automation |
372
- | `@manual` | Scenario | Skip in generation |
373
- | `@smoke` / `@regression` | Scenario | Test suite grouping |
374
- | `@auth:<role>` | Feature/Scenario | Use Playwright auth storage state |
375
- | `@no-auth` | Scenario | Disable inherited auth |
376
- | `@steps:<name>` | Scenario | Mark as reusable step block (base scenario) |
377
- | `@extend:<name>` | Scenario | Prepend Given→When from `@steps` block (skip Then) |
378
-
379
- **Auth precedence**: Scenario > Feature > None
380
-
381
- ### Reusable Steps
382
-
383
- ```gherkin
384
- @auth:user @steps:open-dialog
385
- Scenario: Open kudos dialog
386
- Given User is on [kudo] page
387
- When User click [Notifications] button
388
- And User click [Write Kudos] menuitem
389
- Then User see [panel] dialog with {{kudo_title}}
390
-
391
- @auth:user @extend:open-dialog
392
- Scenario: User sends a thank you message
393
- Given User is on [panel] dialog with {{kudo_title}}
394
- When User fill [Search] field with {{teammate_name}}
395
- And User click [Send] button
396
- Then User see [panel] dialog with {{kudo_title}} is hidden
397
- ```
398
-
399
- ---
400
-
401
- ## Project Structure
402
-
403
- ```
404
- qa/screens/<name>/
405
- ├── features/ # Gherkin .feature files
406
- ├── selectors/ # Element selector YAML mappings
407
- └── test-data/ # Test data YAML values
408
-
409
- specs/generated/<name>/
410
- └── <feature>.spec.ts # Generated Playwright tests
411
- ```
412
-
413
- ---
414
-
415
- ## Documentation
416
-
417
- Full documentation at **[sungen.sun-asterisk.vn](https://sungen.sun-asterisk.vn)**
418
-
419
- - **[Installation Guide](https://sun-asterisk.github.io/sungen/docs/guides/installation)** — Windows (Git Bash + VS Code + Copilot), macOS, Linux
420
- - **[Quick Start](https://sun-asterisk.github.io/sungen/docs/guides/quick-start)** — From zero to running tests
421
- - **[Selector YAML Guide](https://sun-asterisk.github.io/sungen/docs/guides/selector-override)** — NFC keys, auto-infer, Japanese/Vietnamese support
422
- - **[Tips & Troubleshooting](https://sun-asterisk.github.io/sungen/docs/guides/tips)** — When AI gets stuck fixing tests
423
- - **[Gherkin Patterns](https://sun-asterisk.github.io/sungen/docs/gherkin/patterns)** — Full grammar, all patterns, compiler rules
424
-
425
- ---
426
-
427
- ## License
428
-
429
- MIT License - see LICENSE file for details.
430
-
431
- **Made with ❤️ by eqe team (engineer & quality) — Sun Asterisk**
7
+ See the project README and docs site for usage.
@@ -0,0 +1,31 @@
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
+ /**
12
+ * core — generic, capability-agnostic steps (data-vs-data `expect`) + the advisory @cases/@query
13
+ * lint and the gate-level `verification` sensor. Stays in core (not a driver) — the always-present
14
+ * generic layer. All three real capabilities (ui/db/api) now ship as `@sungen/driver-*` packages.
15
+ */
16
+ export declare function registerCoreCapability(registry: CapabilityRegistry): void;
17
+ /**
18
+ * In-core driver manifest — now empty: `ui`, `db`, and `api` all ship as `@sungen/driver-*` packages
19
+ * loaded by discovery (`discover.ts`). Kept (empty) as the seam for any future in-core capability;
20
+ * `core` itself registers separately via `registerCoreCapability`.
21
+ */
22
+ export declare const LOCAL_DRIVERS: ReadonlyArray<{
23
+ capability: string;
24
+ register: (r: CapabilityRegistry) => void;
25
+ }>;
26
+ /**
27
+ * @deprecated Use `discoverAndRegisterCapabilities()` from `./discover`. Kept as a thin alias so
28
+ * existing call-sites/tests don't break during R5; removed once everything routes through discovery.
29
+ */
30
+ export declare function registerBuiltinCapabilities(): void;
31
+ //# sourceMappingURL=builtins.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../../src/capabilities/builtins.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAgDrD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,IAAI,CAMzE;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,aAAa,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,kBAAkB,KAAK,IAAI,CAAA;CAAE,CAAM,CAAC;AAElH;;;GAGG;AACH,wBAAgB,2BAA2B,IAAI,IAAI,CAGlD"}
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LOCAL_DRIVERS = void 0;
4
+ exports.registerCoreCapability = registerCoreCapability;
5
+ exports.registerBuiltinCapabilities = registerBuiltinCapabilities;
6
+ const data_driven_lint_1 = require("../harness/data-driven-lint");
7
+ const query_catalog_1 = require("../harness/query-catalog");
8
+ const expect_patterns_1 = require("../generators/test-generator/patterns/expect-patterns");
9
+ /** Advisory @cases/@query lint, exposed through the Sensor SPI (generate-time). */
10
+ const dataDrivenLintSensor = {
11
+ id: 'data-driven-lint',
12
+ capability: 'core',
13
+ kind: 'advisory',
14
+ run: ({ dir, cwd }) => (0, data_driven_lint_1.lintDataDriven)(dir, cwd).map((w) => ({
15
+ sensorId: 'data-driven-lint',
16
+ capability: 'core',
17
+ scenario: w.scenario,
18
+ message: w.message,
19
+ severity: 'warn',
20
+ })),
21
+ };
22
+ /**
23
+ * Generic `verification` gate sensor: every referenced named query (`@query`) must resolve in its
24
+ * catalog, and the catalog must lint clean. An unresolved/invalid reference is a gate-level error.
25
+ * (On a project with no `@query` refs it yields nothing.) `query-catalog` lives in core's harness —
26
+ * shared with the data-driven advisory lint. The `@api` counterpart now lives in
27
+ * `@sungen/driver-api`'s `api-verification` gate sensor. Message prefix unchanged → audit byte-identical.
28
+ */
29
+ const verificationGateSensor = {
30
+ id: 'verification',
31
+ capability: 'core',
32
+ kind: 'gate',
33
+ run: ({ screenName, scenarios, cwd }) => {
34
+ const findings = [];
35
+ const fail = (msg) => findings.push({ sensorId: 'verification', capability: 'core', message: `VERIFICATION-FAIL: ${msg}`, severity: 'error' });
36
+ const queryRefs = new Set();
37
+ for (const s of scenarios)
38
+ for (const r of s.queryRefs ?? [])
39
+ queryRefs.add(r);
40
+ for (const name of queryRefs) {
41
+ try {
42
+ (0, query_catalog_1.resolveQuery)(name, screenName, cwd);
43
+ }
44
+ catch (e) {
45
+ fail(e?.message || `query "${name}" does not resolve`);
46
+ }
47
+ }
48
+ if (queryRefs.size) {
49
+ try {
50
+ for (const err of (0, query_catalog_1.lintCatalog)(screenName, null, cwd).errors)
51
+ fail(err);
52
+ }
53
+ catch { /* no catalog */ }
54
+ }
55
+ return findings;
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
+ function registerCoreCapability(registry) {
64
+ registry.register({
65
+ id: 'core',
66
+ patterns: [...expect_patterns_1.expectPatterns],
67
+ sensors: [dataDrivenLintSensor, verificationGateSensor],
68
+ });
69
+ }
70
+ /**
71
+ * In-core driver manifest — now empty: `ui`, `db`, and `api` all ship as `@sungen/driver-*` packages
72
+ * loaded by discovery (`discover.ts`). Kept (empty) as the seam for any future in-core capability;
73
+ * `core` itself registers separately via `registerCoreCapability`.
74
+ */
75
+ exports.LOCAL_DRIVERS = [];
76
+ /**
77
+ * @deprecated Use `discoverAndRegisterCapabilities()` from `./discover`. Kept as a thin alias so
78
+ * existing call-sites/tests don't break during R5; removed once everything routes through discovery.
79
+ */
80
+ function registerBuiltinCapabilities() {
81
+ // Lazy import avoids a cycle (discover imports builtins).
82
+ require('./discover').discoverAndRegisterCapabilities();
83
+ }
84
+ //# sourceMappingURL=builtins.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builtins.js","sourceRoot":"","sources":["../../src/capabilities/builtins.ts"],"names":[],"mappings":";;;AA8DA,wDAMC;AAaD,kEAGC;AAzED,kEAA6D;AAC7D,4DAAqE;AACrE,2FAAuF;AAEvF,mFAAmF;AACnF,MAAM,oBAAoB,GAA8B;IACtD,EAAE,EAAE,kBAAkB;IACtB,UAAU,EAAE,MAAM;IAClB,IAAI,EAAE,UAAU;IAChB,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,CACpB,IAAA,iCAAc,EAAC,GAAG,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACnC,QAAQ,EAAE,kBAAkB;QAC5B,UAAU,EAAE,MAAM;QAClB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,QAAQ,EAAE,MAAe;KAC1B,CAAC,CAAC;CACN,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,sBAAsB,GAAsB;IAChD,EAAE,EAAE,cAAc;IAClB,UAAU,EAAE,MAAM;IAClB,IAAI,EAAE,MAAM;IACZ,GAAG,EAAE,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,EAAE,EAAE,EAAE;QACtC,MAAM,QAAQ,GAAG,EAA+B,CAAC;QACjD,MAAM,IAAI,GAAG,CAAC,GAAW,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,GAAG,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;QAEvJ,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;QACpC,KAAK,MAAM,CAAC,IAAI,SAAS;YAAE,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,IAAI,EAAE;gBAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC/E,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC7B,IAAI,CAAC;gBAAC,IAAA,4BAAY,EAAC,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;YAAC,CAAC;YAAC,OAAO,CAAM,EAAE,CAAC;gBAAC,IAAI,CAAC,CAAC,EAAE,OAAO,IAAI,UAAU,IAAI,oBAAoB,CAAC,CAAC;YAAC,CAAC;QACzH,CAAC;QACD,IAAI,SAAS,CAAC,IAAI,EAAE,CAAC;YACnB,IAAI,CAAC;gBAAC,KAAK,MAAM,GAAG,IAAI,IAAA,2BAAW,EAAC,UAAU,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,MAAM;oBAAE,IAAI,CAAC,GAAG,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,gBAAgB,CAAC,CAAC;QAC5G,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC;AAEF;;;;GAIG;AACH,SAAgB,sBAAsB,CAAC,QAA4B;IACjE,QAAQ,CAAC,QAAQ,CAAC;QAChB,EAAE,EAAE,MAAM;QACV,QAAQ,EAAE,CAAC,GAAG,gCAAc,CAAC;QAC7B,OAAO,EAAE,CAAC,oBAAoB,EAAE,sBAAsB,CAAC;KACxD,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACU,QAAA,aAAa,GAAqF,EAAE,CAAC;AAElH;;;GAGG;AACH,SAAgB,2BAA2B;IACzC,0DAA0D;IAC1D,OAAO,CAAC,YAAY,CAAC,CAAC,+BAA+B,EAAE,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,34 @@
1
+ export interface RouteTask {
2
+ /** What is being worked on now. */
3
+ target: {
4
+ kind: string;
5
+ id: string;
6
+ };
7
+ /** What artifact is being produced/checked: 'feature' | 'scenario' | 'selectors' | 'apis.yaml' | … */
8
+ artifact: string;
9
+ /** Capability tags present on the target (e.g. ['@query'], ['@api'], ['@cases']). */
10
+ tags?: string[];
11
+ }
12
+ export interface RoutePlan {
13
+ /** Applicable capability ids: the default/implicit one + any whose annotations match the tags. */
14
+ capabilities: string[];
15
+ /** Gate sensors to run for this task (ids). */
16
+ gateSensorIds: string[];
17
+ /** Advisory sensors to run for this task (ids). */
18
+ advisorySensorIds: string[];
19
+ /** Scope: the target slice + a token/context budget (null = unbounded for now). */
20
+ scope: {
21
+ target: RouteTask['target'];
22
+ budget: number | null;
23
+ };
24
+ }
25
+ declare class ContextRouter {
26
+ /** Which capabilities a set of tags activates: the default capability + any owning a present tag. */
27
+ capabilitiesFor(tags?: string[]): string[];
28
+ /** Compute the routing plan for a task (deterministic). */
29
+ route(task: RouteTask): RoutePlan;
30
+ }
31
+ /** Process-wide singleton. */
32
+ export declare const contextRouter: ContextRouter;
33
+ export {};
34
+ //# sourceMappingURL=context-router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-router.d.ts","sourceRoot":"","sources":["../../src/capabilities/context-router.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,SAAS;IACxB,mCAAmC;IACnC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACrC,sGAAsG;IACtG,QAAQ,EAAE,MAAM,CAAC;IACjB,qFAAqF;IACrF,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,kGAAkG;IAClG,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,+CAA+C;IAC/C,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,mDAAmD;IACnD,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,mFAAmF;IACnF,KAAK,EAAE;QAAE,MAAM,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CAC/D;AAED,cAAM,aAAa;IACjB,qGAAqG;IACrG,eAAe,CAAC,IAAI,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE;IAU9C,2DAA2D;IAC3D,KAAK,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS;CAclC;AAED,8BAA8B;AAC9B,eAAO,MAAM,aAAa,eAAsB,CAAC"}
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.contextRouter = void 0;
4
+ /**
5
+ * ContextRouter (Capability SPI, R1 — skeleton).
6
+ *
7
+ * The overload guard: for each unit of work it decides the minimal slice of Context + the relevant
8
+ * capabilities + the gate to load, so a project with many drivers doesn't gather everything for
9
+ * every artifact (cost scales by *target*, not *repo × drivers*). See
10
+ * `docs/spec/sungen_capability_spi_spec.md` §5.
11
+ *
12
+ * R1 scope (skeleton, deterministic, not yet enforced in the pipeline): it computes the routing
13
+ * plan — which capabilities apply (the default/implicit one + any whose annotation tags appear),
14
+ * and which sensors gate the task. Scope-trimming/token-budget enforcement is wired in a later step;
15
+ * for now `scope.budget` is a placeholder (null = unbounded) and the plan is advisory.
16
+ */
17
+ const registry_1 = require("./registry");
18
+ class ContextRouter {
19
+ /** Which capabilities a set of tags activates: the default capability + any owning a present tag. */
20
+ capabilitiesFor(tags = []) {
21
+ const ids = new Set();
22
+ const def = registry_1.capabilityRegistry.defaultCapabilityId();
23
+ if (def)
24
+ ids.add(def);
25
+ for (const cap of registry_1.capabilityRegistry.all()) {
26
+ if ((cap.annotations ?? []).some((a) => tags.includes(a)))
27
+ ids.add(cap.id);
28
+ }
29
+ return [...ids];
30
+ }
31
+ /** Compute the routing plan for a task (deterministic). */
32
+ route(task) {
33
+ const capabilities = this.capabilitiesFor(task.tags ?? []);
34
+ // Generic sensors (no capability, or the always-on `core`) run regardless of the task's tags;
35
+ // capability-specific sensors run only when their capability is in scope.
36
+ const inScope = (capId) => capId == null || capId === 'core' || capabilities.includes(capId);
37
+ const gateSensorIds = registry_1.capabilityRegistry.sensors('gate').filter((s) => inScope(s.capability)).map((s) => s.id);
38
+ const advisorySensorIds = registry_1.capabilityRegistry.sensors('advisory').filter((s) => inScope(s.capability)).map((s) => s.id);
39
+ return {
40
+ capabilities,
41
+ gateSensorIds,
42
+ advisorySensorIds,
43
+ scope: { target: task.target, budget: null },
44
+ };
45
+ }
46
+ }
47
+ /** Process-wide singleton. */
48
+ exports.contextRouter = new ContextRouter();
49
+ //# sourceMappingURL=context-router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-router.js","sourceRoot":"","sources":["../../src/capabilities/context-router.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;GAYG;AACH,yCAAgD;AAsBhD,MAAM,aAAa;IACjB,qGAAqG;IACrG,eAAe,CAAC,OAAiB,EAAE;QACjC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;QAC9B,MAAM,GAAG,GAAG,6BAAkB,CAAC,mBAAmB,EAAE,CAAC;QACrD,IAAI,GAAG;YAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACtB,KAAK,MAAM,GAAG,IAAI,6BAAkB,CAAC,GAAG,EAAE,EAAE,CAAC;YAC3C,IAAI,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;gBAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC7E,CAAC;QACD,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC;IAClB,CAAC;IAED,2DAA2D;IAC3D,KAAK,CAAC,IAAe;QACnB,MAAM,YAAY,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QAC3D,8FAA8F;QAC9F,0EAA0E;QAC1E,MAAM,OAAO,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,MAAM,IAAI,YAAY,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACtG,MAAM,aAAa,GAAG,6BAAkB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/G,MAAM,iBAAiB,GAAG,6BAAkB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACvH,OAAO;YACL,YAAY;YACZ,aAAa;YACb,iBAAiB;YACjB,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE;SAC7C,CAAC;IACJ,CAAC;CACF;AAED,8BAA8B;AACjB,QAAA,aAAa,GAAG,IAAI,aAAa,EAAE,CAAC"}