@sun-asterisk/sungen 2.4.5 → 2.5.0

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 (201) hide show
  1. package/dist/cli/commands/delivery.d.ts +7 -0
  2. package/dist/cli/commands/delivery.d.ts.map +1 -0
  3. package/dist/cli/commands/delivery.js +348 -0
  4. package/dist/cli/commands/delivery.js.map +1 -0
  5. package/dist/cli/commands/generate.d.ts.map +1 -1
  6. package/dist/cli/commands/generate.js +2 -0
  7. package/dist/cli/commands/generate.js.map +1 -1
  8. package/dist/cli/commands/update.d.ts.map +1 -1
  9. package/dist/cli/commands/update.js +64 -1
  10. package/dist/cli/commands/update.js.map +1 -1
  11. package/dist/cli/index.js +4 -2
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/exporters/csv-exporter.d.ts +32 -0
  14. package/dist/exporters/csv-exporter.d.ts.map +1 -0
  15. package/dist/exporters/csv-exporter.js +311 -0
  16. package/dist/exporters/csv-exporter.js.map +1 -0
  17. package/dist/exporters/feature-parser.d.ts +48 -0
  18. package/dist/exporters/feature-parser.d.ts.map +1 -0
  19. package/dist/exporters/feature-parser.js +178 -0
  20. package/dist/exporters/feature-parser.js.map +1 -0
  21. package/dist/exporters/package-info.d.ts +9 -0
  22. package/dist/exporters/package-info.d.ts.map +1 -0
  23. package/dist/exporters/package-info.js +73 -0
  24. package/dist/exporters/package-info.js.map +1 -0
  25. package/dist/exporters/playwright-report-parser.d.ts +21 -0
  26. package/dist/exporters/playwright-report-parser.d.ts.map +1 -0
  27. package/dist/exporters/playwright-report-parser.js +184 -0
  28. package/dist/exporters/playwright-report-parser.js.map +1 -0
  29. package/dist/exporters/scenario-merger.d.ts +21 -0
  30. package/dist/exporters/scenario-merger.d.ts.map +1 -0
  31. package/dist/exporters/scenario-merger.js +51 -0
  32. package/dist/exporters/scenario-merger.js.map +1 -0
  33. package/dist/exporters/spec-parser.d.ts +20 -0
  34. package/dist/exporters/spec-parser.d.ts.map +1 -0
  35. package/dist/exporters/spec-parser.js +259 -0
  36. package/dist/exporters/spec-parser.js.map +1 -0
  37. package/dist/exporters/step-formatter.d.ts +32 -0
  38. package/dist/exporters/step-formatter.d.ts.map +1 -0
  39. package/dist/exporters/step-formatter.js +76 -0
  40. package/dist/exporters/step-formatter.js.map +1 -0
  41. package/dist/exporters/test-data-resolver.d.ts +20 -0
  42. package/dist/exporters/test-data-resolver.d.ts.map +1 -0
  43. package/dist/exporters/test-data-resolver.js +96 -0
  44. package/dist/exporters/test-data-resolver.js.map +1 -0
  45. package/dist/exporters/types.d.ts +104 -0
  46. package/dist/exporters/types.d.ts.map +1 -0
  47. package/dist/exporters/types.js +6 -0
  48. package/dist/exporters/types.js.map +1 -0
  49. package/dist/exporters/xlsx-exporter.d.ts +19 -0
  50. package/dist/exporters/xlsx-exporter.d.ts.map +1 -0
  51. package/dist/exporters/xlsx-exporter.js +309 -0
  52. package/dist/exporters/xlsx-exporter.js.map +1 -0
  53. package/dist/generators/gherkin-parser/index.d.ts +1 -0
  54. package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
  55. package/dist/generators/gherkin-parser/index.js +3 -0
  56. package/dist/generators/gherkin-parser/index.js.map +1 -1
  57. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +29 -1
  58. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  59. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +21 -1
  60. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  61. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js +11 -2
  62. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  63. package/dist/generators/test-generator/adapters/playwright/templates/after-all.hbs +8 -0
  64. package/dist/generators/test-generator/adapters/playwright/templates/after-each.hbs +8 -0
  65. package/dist/generators/test-generator/adapters/playwright/templates/before-all.hbs +8 -0
  66. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  67. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +24 -0
  68. package/dist/generators/test-generator/code-generator.d.ts +2 -0
  69. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  70. package/dist/generators/test-generator/code-generator.js +109 -12
  71. package/dist/generators/test-generator/code-generator.js.map +1 -1
  72. package/dist/generators/test-generator/step-mapper.d.ts +1 -0
  73. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  74. package/dist/generators/test-generator/step-mapper.js +1 -1
  75. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  76. package/dist/generators/test-generator/template-engine.d.ts +29 -1
  77. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  78. package/dist/generators/test-generator/template-engine.js +11 -2
  79. package/dist/generators/test-generator/template-engine.js.map +1 -1
  80. package/dist/generators/test-generator/utils/data-resolver.d.ts +11 -2
  81. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  82. package/dist/generators/test-generator/utils/data-resolver.js +36 -25
  83. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  84. package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts +7 -0
  85. package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts.map +1 -0
  86. package/dist/generators/test-generator/utils/runtime-data-transformer.js +42 -0
  87. package/dist/generators/test-generator/utils/runtime-data-transformer.js.map +1 -0
  88. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  89. package/dist/generators/test-generator/utils/selector-resolver.js +26 -0
  90. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  91. package/dist/generators/types.d.ts +1 -0
  92. package/dist/generators/types.d.ts.map +1 -1
  93. package/dist/generators/types.js.map +1 -1
  94. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  95. package/dist/orchestrator/ai-rules-updater.js +12 -0
  96. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  97. package/dist/orchestrator/project-initializer.d.ts +21 -1
  98. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  99. package/dist/orchestrator/project-initializer.js +158 -74
  100. package/dist/orchestrator/project-initializer.js.map +1 -1
  101. package/dist/orchestrator/screen-manager.d.ts.map +1 -1
  102. package/dist/orchestrator/screen-manager.js +2 -0
  103. package/dist/orchestrator/screen-manager.js.map +1 -1
  104. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +15 -17
  105. package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +7 -5
  106. package/dist/orchestrator/templates/ai-instructions/claude-cmd-delivery.md +71 -0
  107. package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +28 -1
  108. package/dist/orchestrator/templates/ai-instructions/claude-config.md +23 -4
  109. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +142 -0
  110. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +100 -0
  111. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +73 -0
  112. package/dist/orchestrator/templates/ai-instructions/claude-skill-delivery.md +103 -0
  113. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +68 -13
  114. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +22 -0
  115. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +54 -3
  116. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +13 -15
  117. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +6 -4
  118. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +71 -0
  119. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +38 -14
  120. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +23 -4
  121. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +142 -0
  122. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +100 -0
  123. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +73 -0
  124. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +103 -0
  125. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +88 -13
  126. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +22 -0
  127. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +54 -3
  128. package/dist/orchestrator/templates/playwright.config.d.ts.map +1 -1
  129. package/dist/orchestrator/templates/playwright.config.js +6 -1
  130. package/dist/orchestrator/templates/playwright.config.js.map +1 -1
  131. package/dist/orchestrator/templates/playwright.config.ts +6 -1
  132. package/dist/orchestrator/templates/specs-base.d.ts +12 -1
  133. package/dist/orchestrator/templates/specs-base.d.ts.map +1 -1
  134. package/dist/orchestrator/templates/specs-base.js +47 -5
  135. package/dist/orchestrator/templates/specs-base.js.map +1 -1
  136. package/dist/orchestrator/templates/specs-base.ts +65 -7
  137. package/dist/orchestrator/templates/specs-test-data.d.ts +14 -0
  138. package/dist/orchestrator/templates/specs-test-data.d.ts.map +1 -0
  139. package/dist/orchestrator/templates/specs-test-data.js +100 -0
  140. package/dist/orchestrator/templates/specs-test-data.js.map +1 -0
  141. package/dist/orchestrator/templates/specs-test-data.ts +66 -0
  142. package/package.json +2 -1
  143. package/src/cli/commands/delivery.ts +348 -0
  144. package/src/cli/commands/generate.ts +2 -0
  145. package/src/cli/commands/update.ts +84 -2
  146. package/src/cli/index.ts +4 -2
  147. package/src/exporters/csv-exporter.ts +304 -0
  148. package/src/exporters/feature-parser.ts +168 -0
  149. package/src/exporters/package-info.ts +35 -0
  150. package/src/exporters/playwright-report-parser.ts +168 -0
  151. package/src/exporters/scenario-merger.ts +63 -0
  152. package/src/exporters/spec-parser.ts +247 -0
  153. package/src/exporters/step-formatter.ts +80 -0
  154. package/src/exporters/test-data-resolver.ts +59 -0
  155. package/src/exporters/types.ts +112 -0
  156. package/src/exporters/xlsx-exporter.ts +301 -0
  157. package/src/generators/gherkin-parser/index.ts +4 -0
  158. package/src/generators/test-generator/adapters/adapter-interface.ts +12 -1
  159. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +14 -2
  160. package/src/generators/test-generator/adapters/playwright/templates/after-all.hbs +8 -0
  161. package/src/generators/test-generator/adapters/playwright/templates/after-each.hbs +8 -0
  162. package/src/generators/test-generator/adapters/playwright/templates/before-all.hbs +8 -0
  163. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  164. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +24 -0
  165. package/src/generators/test-generator/code-generator.ts +122 -13
  166. package/src/generators/test-generator/step-mapper.ts +2 -2
  167. package/src/generators/test-generator/template-engine.ts +28 -2
  168. package/src/generators/test-generator/utils/data-resolver.ts +45 -27
  169. package/src/generators/test-generator/utils/runtime-data-transformer.ts +51 -0
  170. package/src/generators/test-generator/utils/selector-resolver.ts +26 -0
  171. package/src/generators/types.ts +1 -0
  172. package/src/orchestrator/ai-rules-updater.ts +12 -0
  173. package/src/orchestrator/project-initializer.ts +187 -80
  174. package/src/orchestrator/screen-manager.ts +2 -0
  175. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +15 -17
  176. package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +7 -5
  177. package/src/orchestrator/templates/ai-instructions/claude-cmd-delivery.md +71 -0
  178. package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +28 -1
  179. package/src/orchestrator/templates/ai-instructions/claude-config.md +23 -4
  180. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +142 -0
  181. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +100 -0
  182. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +73 -0
  183. package/src/orchestrator/templates/ai-instructions/claude-skill-delivery.md +103 -0
  184. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +68 -13
  185. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +22 -0
  186. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +54 -3
  187. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +13 -15
  188. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +6 -4
  189. package/src/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +71 -0
  190. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +38 -14
  191. package/src/orchestrator/templates/ai-instructions/copilot-config.md +23 -4
  192. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +142 -0
  193. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +100 -0
  194. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +73 -0
  195. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +103 -0
  196. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +88 -13
  197. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +22 -0
  198. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +54 -3
  199. package/src/orchestrator/templates/playwright.config.ts +6 -1
  200. package/src/orchestrator/templates/specs-base.ts +65 -7
  201. package/src/orchestrator/templates/specs-test-data.ts +66 -0
@@ -6,6 +6,7 @@
6
6
  import * as fs from 'fs';
7
7
  import * as path from 'path';
8
8
  import { execSync } from 'child_process';
9
+ import { AI_RULES_FILE_MAPPING } from './ai-rules-updater';
9
10
 
10
11
  export class ProjectInitializer {
11
12
  private baseCwd: string;
@@ -47,8 +48,9 @@ export class ProjectInitializer {
47
48
  // Create tsconfig.json if doesn't exist
48
49
  this.createTsConfig();
49
50
 
50
- // Create specs/base.ts for shared context
51
+ // Create specs/base.ts for shared context and specs/test-data.ts for runtime data
51
52
  this.createSpecsBase();
53
+ this.createSpecsTestData();
52
54
 
53
55
  // Create/update .gitignore
54
56
  this.updateGitignore();
@@ -62,6 +64,9 @@ export class ProjectInitializer {
62
64
  // Create VS Code settings for Copilot auto-attach
63
65
  this.createVSCodeSettings();
64
66
 
67
+ // Create root .mcp.json for Claude Code
68
+ this.createClaudeMCPConfig();
69
+
65
70
  // Display summary
66
71
  this.displaySummary(normalizedProjectName);
67
72
  }
@@ -190,46 +195,112 @@ export class ProjectInitializer {
190
195
  private createVSCodeSettings(): void {
191
196
  const settingsPath = path.join(this.cwd, '.vscode', 'settings.json');
192
197
 
193
- if (fs.existsSync(settingsPath)) {
194
- this.skippedItems.push('.vscode/settings.json');
195
- return;
196
- }
198
+ if (!fs.existsSync(settingsPath)) {
199
+ const settingsDir = path.dirname(settingsPath);
200
+ if (!fs.existsSync(settingsDir)) {
201
+ fs.mkdirSync(settingsDir, { recursive: true });
202
+ }
197
203
 
198
- const settingsDir = path.dirname(settingsPath);
199
- if (!fs.existsSync(settingsDir)) {
200
- fs.mkdirSync(settingsDir, { recursive: true });
204
+ const settings = {
205
+ 'github.copilot.chat.agent.runTasks': true,
206
+ 'chat.tools.terminal.autoApprove': {
207
+ sungen: true,
208
+ 'npx playwright': true,
209
+ },
210
+ };
211
+
212
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
213
+ this.createdItems.push('.vscode/settings.json');
214
+ } else {
215
+ this.skippedItems.push('.vscode/settings.json');
201
216
  }
202
217
 
203
- const settings = {
204
- 'github.copilot.chat.agent.runTasks': true,
205
- 'chat.tools.terminal.autoApprove': {
206
- sungen: true,
207
- 'npx playwright': true,
218
+ // .vscode/mcp.json (Copilot format) — safe-merge Playwright + Figma entries
219
+ this.mergeMCPConfig(
220
+ path.join(this.cwd, '.vscode', 'mcp.json'),
221
+ 'servers',
222
+ {
223
+ playwright: {
224
+ command: 'npx',
225
+ args: ['@playwright/mcp@latest'],
226
+ },
227
+ figma: {
228
+ type: 'http',
229
+ url: 'https://mcp.figma.com/mcp',
230
+ },
208
231
  },
209
- };
210
-
211
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
212
- this.createdItems.push('.vscode/settings.json');
213
-
214
- // Create MCP config for Playwright browser tools
215
- const mcpPath = path.join(this.cwd, '.vscode', 'mcp.json');
216
-
217
- if (fs.existsSync(mcpPath)) {
218
- this.skippedItems.push('.vscode/mcp.json');
219
- return;
220
- }
232
+ '.vscode/mcp.json',
233
+ );
234
+ }
221
235
 
222
- const mcpConfig = {
223
- servers: {
236
+ /**
237
+ * Create root .mcp.json for Claude Code — safe-merge Playwright + Figma entries
238
+ */
239
+ private createClaudeMCPConfig(): void {
240
+ this.mergeMCPConfig(
241
+ path.join(this.cwd, '.mcp.json'),
242
+ 'mcpServers',
243
+ {
224
244
  playwright: {
245
+ type: 'stdio',
225
246
  command: 'npx',
226
247
  args: ['@playwright/mcp@latest'],
227
248
  },
249
+ figma: {
250
+ type: 'http',
251
+ url: 'https://mcp.figma.com/mcp',
252
+ },
228
253
  },
229
- };
254
+ '.mcp.json',
255
+ );
256
+ }
257
+
258
+ /**
259
+ * Add MCP server entries without overwriting user config.
260
+ * Preserves existing entries with the same key — user edits always win.
261
+ */
262
+ private mergeMCPConfig(
263
+ filePath: string,
264
+ rootKey: string,
265
+ newServers: Record<string, unknown>,
266
+ displayName: string,
267
+ ): void {
268
+ const dir = path.dirname(filePath);
269
+ if (!fs.existsSync(dir)) {
270
+ fs.mkdirSync(dir, { recursive: true });
271
+ }
272
+
273
+ let config: Record<string, Record<string, unknown>> = {};
274
+ const existed = fs.existsSync(filePath);
230
275
 
231
- fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + '\n', 'utf-8');
232
- this.createdItems.push('.vscode/mcp.json');
276
+ if (existed) {
277
+ try {
278
+ config = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
279
+ } catch {
280
+ this.skippedItems.push(`${displayName} (invalid JSON, skipped)`);
281
+ return;
282
+ }
283
+ }
284
+
285
+ if (!config[rootKey] || typeof config[rootKey] !== 'object') {
286
+ config[rootKey] = {};
287
+ }
288
+
289
+ let added = 0;
290
+ for (const [name, entry] of Object.entries(newServers)) {
291
+ if (!config[rootKey][name]) {
292
+ config[rootKey][name] = entry;
293
+ added++;
294
+ }
295
+ }
296
+
297
+ if (added === 0) {
298
+ this.skippedItems.push(displayName);
299
+ return;
300
+ }
301
+
302
+ fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
303
+ this.createdItems.push(existed ? `${displayName} (updated)` : displayName);
233
304
  }
234
305
 
235
306
  /**
@@ -264,51 +335,14 @@ export class ProjectInitializer {
264
335
  }
265
336
 
266
337
  /**
267
- * Create AI rules files for GitHub Copilot and Claude Code
338
+ * Create AI rules files for GitHub Copilot and Claude Code.
339
+ * Shares `AI_RULES_FILE_MAPPING` with `sungen update` so a newly added
340
+ * skill / command template only needs to be registered once.
268
341
  */
269
342
  private createAIRules(): void {
270
343
  const aiTemplateDir = path.join(__dirname, 'templates', 'ai-instructions');
271
344
 
272
- // File mapping: [templateFile, outputPath]
273
- const fileMapping: [string, string][] = [
274
- // Config
275
- ['claude-config.md', 'CLAUDE.md'],
276
- ['copilot-config.md', '.github/copilot-instructions.md'],
277
-
278
- // Commands — Claude Code
279
- ['claude-cmd-add-screen.md', '.claude/commands/sungen/add-screen.md'],
280
- ['claude-cmd-create-test.md', '.claude/commands/sungen/create-test.md'],
281
- ['claude-cmd-run-test.md', '.claude/commands/sungen/run-test.md'],
282
- ['claude-cmd-review.md', '.claude/commands/sungen/review.md'],
283
-
284
- // Commands — GitHub Copilot
285
- ['copilot-cmd-add-screen.md', '.github/prompts/sungen-add-screen.prompt.md'],
286
- ['copilot-cmd-create-test.md', '.github/prompts/sungen-create-test.prompt.md'],
287
- ['copilot-cmd-run-test.md', '.github/prompts/sungen-run-test.prompt.md'],
288
- ['copilot-cmd-review.md', '.github/prompts/sungen-review.prompt.md'],
289
-
290
- // Skills — Claude Code
291
- ['claude-skill-gherkin-syntax.md', '.claude/skills/sungen-gherkin-syntax/SKILL.md'],
292
- ['claude-skill-selector-keys.md', '.claude/skills/sungen-selector-keys/SKILL.md'],
293
- ['claude-skill-error-mapping.md', '.claude/skills/sungen-error-mapping/SKILL.md'],
294
- ['claude-skill-tc-generation.md', '.claude/skills/sungen-tc-generation/SKILL.md'],
295
- ['claude-skill-test-design-techniques.md', '.claude/skills/sungen-test-design-techniques/SKILL.md'],
296
- ['claude-skill-selector-fix.md', '.claude/skills/sungen-selector-fix/SKILL.md'],
297
- ['claude-skill-tc-review.md', '.claude/skills/sungen-tc-review/SKILL.md'],
298
- ['claude-skill-viewpoint.md', '.claude/skills/sungen-viewpoint/SKILL.md'],
299
-
300
- // Skills — GitHub Copilot (separate copies with Copilot-friendly descriptions)
301
- ['github-skill-sungen-gherkin-syntax.md', '.github/skills/sungen-gherkin-syntax/SKILL.md'],
302
- ['github-skill-sungen-selector-keys.md', '.github/skills/sungen-selector-keys/SKILL.md'],
303
- ['github-skill-sungen-error-mapping.md', '.github/skills/sungen-error-mapping/SKILL.md'],
304
- ['github-skill-sungen-tc-generation.md', '.github/skills/sungen-tc-generation/SKILL.md'],
305
- ['github-skill-sungen-test-design-techniques.md', '.github/skills/sungen-test-design-techniques/SKILL.md'],
306
- ['github-skill-sungen-selector-fix.md', '.github/skills/sungen-selector-fix/SKILL.md'],
307
- ['github-skill-sungen-tc-review.md', '.github/skills/sungen-tc-review/SKILL.md'],
308
- ['github-skill-sungen-viewpoint.md', '.github/skills/sungen-viewpoint/SKILL.md'],
309
- ];
310
-
311
- for (const [templateFile, outputRelPath] of fileMapping) {
345
+ for (const [templateFile, outputRelPath] of AI_RULES_FILE_MAPPING) {
312
346
  const outputPath = path.join(this.cwd, outputRelPath);
313
347
 
314
348
  if (fs.existsSync(outputPath)) {
@@ -350,6 +384,27 @@ export class ProjectInitializer {
350
384
  this.createdItems.push('specs/generated/base.ts');
351
385
  }
352
386
 
387
+ /**
388
+ * Create specs/test-data.ts for runtime YAML loading
389
+ */
390
+ private createSpecsTestData(): void {
391
+ const testDataPath = path.join(this.cwd, 'specs', 'generated', 'test-data.ts');
392
+
393
+ if (fs.existsSync(testDataPath)) {
394
+ this.skippedItems.push('specs/generated/test-data.ts');
395
+ return;
396
+ }
397
+
398
+ const baseDir = path.dirname(testDataPath);
399
+ if (!fs.existsSync(baseDir)) {
400
+ fs.mkdirSync(baseDir, { recursive: true });
401
+ }
402
+
403
+ const content = this.readTemplate('specs-test-data.ts');
404
+ fs.writeFileSync(testDataPath, content, 'utf-8');
405
+ this.createdItems.push('specs/generated/test-data.ts');
406
+ }
407
+
353
408
  /**
354
409
  * Read a template file from the templates directory
355
410
  */
@@ -372,26 +427,78 @@ export class ProjectInitializer {
372
427
  this.createdItems.push('package.json');
373
428
  }
374
429
 
375
- // Check if @playwright/test is already installed
430
+ // Ensure standard scripts exist in package.json
431
+ this.ensurePackageScripts(packageJsonPath);
432
+
433
+ // Check which dependencies are missing
434
+ const requiredDeps = ['@playwright/test', '@types/node', 'yaml'];
435
+ let missingDeps: string[] = requiredDeps;
376
436
  try {
377
437
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
378
438
  const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
379
- if (deps['@playwright/test']) {
380
- console.log('✓ @playwright/test already installed\n');
381
- return;
382
- }
439
+ missingDeps = requiredDeps.filter(d => !deps[d]);
383
440
  } catch {
384
- // package.json just created, proceed with install
441
+ // package.json just created, install all
442
+ }
443
+
444
+ if (missingDeps.length === 0) {
445
+ console.log('✓ All dependencies already installed\n');
446
+ return;
385
447
  }
386
448
 
387
- // Install Playwright and TypeScript types
388
- console.log('📦 Installing @playwright/test and @types/node...\n');
389
- execSync('npm install -D @playwright/test @types/node', execOpts);
449
+ console.log(`📦 Installing ${missingDeps.join(', ')}...\n`);
450
+ execSync(`npm install -D ${missingDeps.join(' ')}`, execOpts);
390
451
 
391
452
  console.log('\n🎭 Installing Playwright browsers...\n');
392
453
  execSync('npx playwright install', execOpts);
393
454
  }
394
455
 
456
+ /**
457
+ * Ensure package.json has standard Playwright + Sungen scripts.
458
+ * Only adds missing scripts — never overwrites user customizations.
459
+ */
460
+ private ensurePackageScripts(packageJsonPath: string): void {
461
+ let packageJson: Record<string, any>;
462
+ try {
463
+ packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
464
+ } catch {
465
+ return;
466
+ }
467
+
468
+ if (!packageJson.scripts || typeof packageJson.scripts !== 'object') {
469
+ packageJson.scripts = {};
470
+ }
471
+
472
+ const standardScripts: Record<string, string> = {
473
+ 'test': 'playwright test specs/generated/',
474
+ 'test:headed': 'playwright test specs/generated/ --headed',
475
+ 'test:debug': 'playwright test specs/generated/ --debug',
476
+ 'test:ui': 'playwright test specs/generated/ --ui',
477
+ 'report': 'playwright show-report',
478
+ 'generate': 'sungen generate --all',
479
+ 'install:browsers': 'npx playwright install chromium',
480
+ };
481
+
482
+ let added = 0;
483
+ for (const [name, command] of Object.entries(standardScripts)) {
484
+ // Skip if user already has this script (don't overwrite)
485
+ // Exception: overwrite the npm init default test script
486
+ const existing = packageJson.scripts[name];
487
+ if (existing && existing !== 'echo "Error: no test specified" && exit 1') {
488
+ continue;
489
+ }
490
+ packageJson.scripts[name] = command;
491
+ added++;
492
+ }
493
+
494
+ if (added > 0) {
495
+ // Also mark as private (test projects should not be published)
496
+ packageJson.private = true;
497
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf-8');
498
+ console.log(`📝 Added ${added} standard script(s) to package.json\n`);
499
+ }
500
+ }
501
+
395
502
  /**
396
503
  * Get Playwright configuration template
397
504
  */
@@ -296,6 +296,8 @@ export class ScreenManager {
296
296
 
297
297
  ## Overview
298
298
  - **URL Path:** ${pagePath}
299
+ - **Live URL:** <!-- optional, full URL for live page capture (falls back to baseURL + URL Path) -->
300
+ - **Figma URL:** <!-- optional, Figma frame URL for design-driven capture (requires Figma MCP) -->
299
301
  - **Auth Required:** no
300
302
  - **Platform:** web
301
303
 
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: add-screen
3
- description: 'Add a new Sungen screen — scaffolds directories, helps fill spec.md, and can auto-capture a live-page screenshot via Playwright MCP'
3
+ description: 'Add a new Sungen screen — scaffolds directories, helps fill spec.md, and can capture visuals from Figma (pre-launch) or live page via the capture skills'
4
4
  argument-hint: [screen-name] [url-path]
5
- allowed-tools: Read, Grep, Bash, Glob, Edit, Write, AskUserQuestion, mcp__playwright__browser_navigate, mcp__playwright__browser_take_screenshot, mcp__playwright__browser_snapshot
5
+ allowed-tools: Read, Grep, Bash, Glob, Edit, Write, AskUserQuestion, mcp__playwright__browser_navigate, mcp__playwright__browser_take_screenshot, mcp__playwright__browser_snapshot, mcp__figma__get_design_context, mcp__figma__get_variable_defs, mcp__figma__get_screenshot
6
6
  ---
7
7
 
8
8
  You are adding a new Sungen screen for test generation.
@@ -25,27 +25,25 @@ Run:
25
25
  sungen add --screen <screen> --path <path>
26
26
  ```
27
27
 
28
- ### 2. Prepare requirements
28
+ ### 2. Fill spec.md
29
29
 
30
- Use `AskUserQuestion` to let the user choose how to prepare requirements this is the foundation for high-quality test generation:
30
+ Use `AskUserQuestion`: *"Fill `spec.md` now?"* offer **Yes, fill now (Recommended)** / **Skip, fill later**.
31
31
 
32
- - **Fill `spec.md` + capture live-page screenshot** (Recommended)best test quality
33
- - **Fill `spec.md` only** — app not live yet, or no need for visuals
34
- - **Capture live-page screenshot only** — spec will come later
35
- - **Skip requirements prep** — proceed to `/sungen:create-test` immediately
32
+ If yes → open `qa/screens/<screen>/requirements/spec.md` and help the user fill sections, fields, validation rules, business rules, and states. Especially prompt for the optional **Figma URL** and **Live URL** fields in Overview those unlock auto-capture without re-asking next run.
36
33
 
37
- **If "Fill `spec.md`" is chosen**: open `qa/screens/<screen>/requirements/spec.md` and help the user fill sections, fields, validation rules, business rules, and states.
34
+ ### 3. Capture visual source
38
35
 
39
- **If "Capture live-page screenshot" is chosen**:
40
- 1. Read `baseURL` from `playwright.config.ts` (fall back to `APP_BASE_URL` env, then ask the user).
41
- 2. `browser_navigate` to `<baseURL><path>`.
42
- 3. If redirected to login → ask the user to log in manually in the MCP browser, wait for confirmation, then re-navigate. (No auth persistence needed here — that's handled by Phase 0.5 in `sungen-selector-fix` when tests run.)
43
- 4. `browser_take_screenshot` with `filename: "qa/screens/<screen>/requirements/ui/<screen>.png"`.
44
- 5. If the screen has multiple important states (empty, loaded, error, modal open), offer additional captures named `<screen>-<state>.png`.
36
+ Use `AskUserQuestion`: *"Capture a visual reference for this screen?"* always offer all three so pre-launch projects work:
45
37
 
46
- If the user has additional UI designs (Figma exports, mockups), suggest copying them to `requirements/ui/`.
38
+ - **Figma design** (Recommended for pre-launch) invoke `sungen-capture-figma` skill
39
+ - **Live page scan** (dev/staging is up) — invoke `sungen-capture-live` skill
40
+ - **Skip** — user will drop images manually into `requirements/ui/` later, or rely on `/sungen:create-test` to prompt again
47
41
 
48
- ### 3. Next steps
42
+ Each capture skill writes outputs into `qa/screens/<screen>/requirements/ui/` and reports back a summary. Do not inline capture logic here — always delegate to the skill so behavior stays consistent with `/sungen:create-test`.
43
+
44
+ If the user has additional UI designs (mockups, hand-drawn sketches), suggest copying them to `requirements/ui/` — `sungen-capture-local` will pick them up during `/sungen:create-test`.
45
+
46
+ ### 4. Next steps
49
47
 
50
48
  Tell the user what was created, then use `AskUserQuestion` to offer next steps:
51
49
 
@@ -2,7 +2,7 @@
2
2
  name: create-test
3
3
  description: 'Create or update test cases for a Sungen screen — generates feature + test-data files (20+ scenarios per viewpoint)'
4
4
  argument-hint: [screen-name]
5
- allowed-tools: Read, Grep, Bash, Glob, AskUserQuestion
5
+ allowed-tools: Read, Grep, Bash, Glob, Write, AskUserQuestion, mcp__playwright__browser_navigate, mcp__playwright__browser_snapshot, mcp__playwright__browser_take_screenshot, mcp__figma__get_design_context, mcp__figma__get_variable_defs, mcp__figma__get_screenshot
6
6
  ---
7
7
 
8
8
  ## Role
@@ -25,10 +25,12 @@ Parse **screen** from `$ARGUMENTS`. If missing, ask the user.
25
25
  - **Continue without it** — generate tests from spec and other sources only
26
26
  - Summarize what you found in requirements and present to the user.
27
27
  4. **Screen input** (supplements requirements, or is primary source if no requirements):
28
- - Use `AskUserQuestion` to ask: **Figma design** (provide Figma URL recommended), **UI images** (screenshots/mockups in `requirements/ui/`), or **Live page scan** (optional, via Playwright MCP)?
29
- - Recommend Figma or UI images first. Live page scan is optional useful to verify specs vs actual UI or capture real data.
30
- - If live page scan: `browser_navigate` → ONE `browser_snapshot`. If auth redirect → ask user to log in manually. Never use `browser_run_code` or `browser_evaluate` to inject cookies.
31
- - If exploring, verify and supplement requirementsflag any discrepancies found.
28
+ - Use `AskUserQuestion` to ask the user to pick a visual source always offer all three options so pre-launch projects work:
29
+ - **Figma design** (Recommended for pre-launch)invoke `sungen-capture-figma` skill
30
+ - **UI images** (existing screenshots/mockups in `requirements/ui/`) invoke `sungen-capture-local` skill
31
+ - **Live page scan** (dev/staging is up)invoke `sungen-capture-live` skill
32
+ - Each capture skill writes outputs into `qa/screens/<screen>/requirements/ui/` and reports back a summary. Do not inline capture logic here — always delegate to the skill so behavior stays consistent across commands.
33
+ - After the capture skill returns, cross-check its output against `spec.md` and flag any discrepancies before moving on.
32
34
  5. Follow the `sungen-tc-generation` skill for section identification, viewpoint generation, and output format. When requirements exist, use the "Requirements-Driven Generation" strategy.
33
35
  6. Generate or update `.feature` + `test-data.yaml` following `sungen-gherkin-syntax` and `sungen-tc-generation` skills.
34
36
  7. Show summary, then use `AskUserQuestion` to offer next steps:
@@ -0,0 +1,71 @@
1
+ ---
2
+ name: delivery
3
+ description: 'Export Gherkin scenarios + Playwright results to CSV test case file for QA delivery.'
4
+ argument-hint: "[screen-name...] (omit for all screens)"
5
+ allowed-tools: Bash, Read, AskUserQuestion
6
+ ---
7
+
8
+ ## Role
9
+
10
+ You are a **QA Test Delivery Engineer**. Your job is to invoke the deterministic `sungen delivery` CLI that performs all parsing and CSV export. Your role is minimal — just run the CLI and help the user if pre-flight checks fail.
11
+
12
+ ## Parameters
13
+
14
+ Parse **screens** from `$ARGUMENTS`:
15
+ - If empty → CLI will process **all** screens in `qa/screens/`
16
+ - If provided → pass them through to the CLI
17
+
18
+ ## Steps
19
+
20
+ ### 1. Invoke the CLI
21
+
22
+ Run via Bash (single command, no extra parsing):
23
+
24
+ ```bash
25
+ npx sungen delivery <screens>
26
+ ```
27
+
28
+ - If no screen args → just run `npx sungen delivery`
29
+ - If screen args → pass them as positional arguments
30
+
31
+ The CLI handles:
32
+ - Scope detection (all screens vs specific)
33
+ - Pre-flight source checks with colorful output
34
+ - Parsing `.feature`, `.spec.ts`, `test-data.yaml`, `test-results/results.json`
35
+ - Generating CSV at `qa/deliverables/<screen>-testcases.csv`
36
+ - Printing summary table
37
+
38
+ ### 2. Handle pre-flight failures (if CLI exits non-zero)
39
+
40
+ If the CLI exits with blocking issues, it will have already printed a clear table showing exactly what's missing per screen.
41
+
42
+ Use `AskUserQuestion` to offer next steps:
43
+
44
+ **Options:**
45
+ - **Fix missing sources** (Recommended) — Print the suggested commands from CLI output and stop. User will run those commands manually, then re-invoke `/sungen:delivery`.
46
+ - **Continue with available screens** — Re-run as `npx sungen delivery <screens> --continue-on-missing` to skip screens with blocking issues.
47
+ - **Cancel** — Exit.
48
+
49
+ ### 3. Show summary + offer next steps (on success)
50
+
51
+ Forward the CLI's summary table to the user verbatim. Then use `AskUserQuestion`:
52
+
53
+ - **Open a specific CSV** — Help user inspect one of the exported files with Read tool.
54
+ - **Run tests to refresh results** — Suggest `/sungen:run-test <screen>` to update `test-results/results.json`, then re-run delivery.
55
+ - **Export another screen** — User can run `/sungen:delivery <other-screen>`.
56
+ - **Done** — Exit.
57
+
58
+ ## Important notes
59
+
60
+ - **Do NOT parse files yourself** — the CLI is the source of truth for parsing logic. Your job is orchestration + user interaction.
61
+ - **Do NOT modify feature/spec.ts/test-data files** — the delivery is read-only.
62
+ - **The CLI already respects `@manual` tags, skips `@steps:` base scenarios, groups by Category 2, and generates UTF-8 BOM CSV for Excel compatibility with Vietnamese.**
63
+ - **Pre-flight check is built into the CLI** — use `--skip-preflight` only in CI/automated pipelines where checks are done externally.
64
+
65
+ ## CLI Reference
66
+
67
+ ```
68
+ sungen delivery [screens...]
69
+ [--skip-preflight] Skip pre-flight checks (not recommended)
70
+ [--continue-on-missing] Skip screens with blocking misses
71
+ ```
@@ -18,7 +18,7 @@ Parse **screen** from `$ARGUMENTS`. If missing, ask the user.
18
18
  1. Verify `qa/screens/<screen>/` has `.feature` + `test-data.yaml`.
19
19
  2. **Phase 0 — Selector Pre-gen**: if `selectors.yaml` is missing/empty or doesn't cover the feature file's `[Reference]`s, run Phase 0 from `sungen-selector-fix` — confirm with user, `browser_navigate` → one `browser_snapshot` → merge YAML entries.
20
20
  3. **Phase 0.5 — Auth Persistence**: if the feature has `@auth:<role>` tags and `specs/.auth/<role>.json` is missing/expired, run Phase 0.5 from `sungen-selector-fix` — user logs in manually in MCP browser → `browser_storage_state` → `specs/.auth/<role>.json`. Offer `sungen makeauth <role>` as CLI fallback only if `browser_storage_state` isn't available in this MCP version.
21
- 4. Compile: `sungen generate --screen <screen>`.
21
+ 4. Compile: `sungen generate --screen <screen>` (default: runtime data loading from YAML). Use `--inline-data` only if user requests compile-time hardcoded values.
22
22
 
23
23
  ## Run & Fix (phased — per `sungen-selector-fix` skill)
24
24
 
@@ -27,6 +27,33 @@ Parse **screen** from `$ARGUMENTS`. If missing, ask the user.
27
27
  7. **Phase 3 — Full Run**: Run all tests. Fix only **new** failures (elements unique to `@normal`/`@low`). Max 1 attempt. Don't loop on low-priority failures.
28
28
  8. **Phase 4 — Regression**: One final full run. Report results. No more fix loops.
29
29
 
30
+ ## Playwright command guidelines
31
+
32
+ **Per-screen JSON results** — each run must write its JSON report to a dedicated path co-located with the `.spec.ts`, so `sungen delivery` can read the correct results per screen:
33
+
34
+ ```bash
35
+ # ✅ Correct — per-screen output file via env var
36
+ PLAYWRIGHT_JSON_OUTPUT_NAME=specs/generated/<screen>/<screen>-test-result.json \
37
+ npx playwright test specs/generated/<screen>/<screen>.spec.ts
38
+ ```
39
+
40
+ Output: `specs/generated/<screen>/<screen>-test-result.json`
41
+
42
+ **DO NOT** pass `--reporter=...` flag — it overrides the reporters from `playwright.config.ts` and disables the JSON reporter that `sungen delivery` depends on.
43
+
44
+ ```bash
45
+ # ❌ Wrong — --reporter flag disables the config's JSON reporter
46
+ npx playwright test specs/generated/<screen>/<screen>.spec.ts --reporter=list
47
+
48
+ # ❌ Wrong — no env var → writes to default test-results/results.json
49
+ # (overwritten on every screen run, loses per-screen tracking)
50
+ npx playwright test specs/generated/<screen>/<screen>.spec.ts
51
+ ```
52
+
53
+ If you want to filter scenarios, use `-g "<pattern>"` instead of a reporter override.
54
+
55
+ `sungen delivery` reads the per-screen file first, falls back to the global `test-results/results.json` if missing.
56
+
30
57
  ## Next steps
31
58
 
32
59
  After showing results, use `AskUserQuestion` to offer next steps:
@@ -15,8 +15,12 @@ You generate 3 files for sungen — a Gherkin compiler that produces Playwright
15
15
  | `sungen-viewpoint` | 10 UI patterns x 4 viewpoints — coverage checklists |
16
16
  | `sungen-selector-keys` | YAML key generation from `[Reference]` names, suffixes, lookup priority |
17
17
  | `sungen-selector-fix` | Selector generation from live page, auto-fix strategy |
18
+ | `sungen-delivery` | Export Gherkin + Playwright results → CSV test case deliverable |
19
+ | `sungen-capture-figma` | Fetch design context + PNG from a Figma frame URL via Figma Dev Mode MCP |
20
+ | `sungen-capture-local` | Load existing UI assets (screenshots, mockups, Figma exports) from `requirements/ui/` |
21
+ | `sungen-capture-live` | Capture a live running page via Playwright MCP (snapshot + screenshot) |
18
22
 
19
- ## Workflow (4 AI commands)
23
+ ## Workflow (5 AI commands)
20
24
 
21
25
  | Command | What it does |
22
26
  |---|---|
@@ -24,8 +28,9 @@ You generate 3 files for sungen — a Gherkin compiler that produces Playwright
24
28
  | `/sungen:create-test <name>` | Generate `.feature` + `test-data.yaml` (no selectors) |
25
29
  | `/sungen:review <name>` | Score syntax, coverage, viewpoint quality (60% threshold) |
26
30
  | `/sungen:run-test <name>` | Generate `selectors.yaml` from live page, compile, run, auto-fix |
31
+ | `/sungen:delivery [name...]` | Export test cases → CSV for QA delivery (all screens if no arg) |
27
32
 
28
- **Order:** add-screen → create-test → review → run-test.
33
+ **Order:** add-screen → create-test → review → run-test → delivery.
29
34
 
30
35
  After each command completes, use `AskUserQuestion` to present the next actions as selectable options. Never just print text — always give clickable choices so the user can continue the workflow seamlessly.
31
36
 
@@ -35,17 +40,31 @@ After each command completes, use `AskUserQuestion` to present the next actions
35
40
  qa/screens/<screen-name>/
36
41
  ├── features/<screen>.feature # Gherkin scenarios
37
42
  ├── selectors/<screen>.yaml # Element locators (generated during run-test)
38
- ├── test-data/<screen>.yaml # Test data variables
43
+ ├── test-data/<screen>.yaml # Test data variables (loaded at runtime)
44
+ ├── test-data/<screen>.staging.yaml # Environment override (optional)
45
+ ├── test-data/<screen>.production.yaml # Environment override (optional)
39
46
  └── requirements/
40
47
  ├── spec.md # Screen specification (primary source)
41
48
  └── ui/ # Screenshots, mockups
49
+
50
+ qa/deliverables/<screen>-testcases.csv # Exported test cases (from /sungen:delivery)
51
+ qa/deliverables/<screen>-testcases.xlsx # Styled workbook for client hand-off
42
52
  ```
43
53
 
54
+ ## Test Data
55
+
56
+ `{{variable}}` references in `.feature` map to keys in `test-data/<screen>.yaml`. Data is loaded **at runtime** — the same generated `.spec.ts` works across environments without recompiling.
57
+
58
+ **Environment overrides**: `SUNGEN_ENV=staging npx playwright test` merges `<screen>.staging.yaml` on top of `<screen>.yaml`. Create `<screen>.<env>.yaml` for environment-specific values (different credentials, URLs, test users).
59
+
44
60
  ## CLI Commands
45
61
 
46
62
  ```bash
47
63
  sungen add --screen <name> --path <url-path> # Scaffold screen directories
48
64
  sungen add --screen <name> --path <path> --feature <name> # Scaffold with sub-feature
49
- sungen generate --screen <name> # Compile .feature → .spec.ts
65
+ sungen generate --screen <name> # Compile .feature → .spec.ts (runtime data)
66
+ sungen generate --screen <name> --inline-data # Compile with hardcoded data (legacy)
50
67
  sungen generate --all # Compile all screens
68
+ sungen delivery # Export all screens → CSV + XLSX
69
+ sungen delivery <screen> # Export a single screen
51
70
  ```