@sun-asterisk/sungen 2.4.5 → 2.4.6

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 (129) 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/update.d.ts.map +1 -1
  6. package/dist/cli/commands/update.js +64 -1
  7. package/dist/cli/commands/update.js.map +1 -1
  8. package/dist/cli/index.js +4 -2
  9. package/dist/cli/index.js.map +1 -1
  10. package/dist/exporters/csv-exporter.d.ts +32 -0
  11. package/dist/exporters/csv-exporter.d.ts.map +1 -0
  12. package/dist/exporters/csv-exporter.js +311 -0
  13. package/dist/exporters/csv-exporter.js.map +1 -0
  14. package/dist/exporters/feature-parser.d.ts +48 -0
  15. package/dist/exporters/feature-parser.d.ts.map +1 -0
  16. package/dist/exporters/feature-parser.js +178 -0
  17. package/dist/exporters/feature-parser.js.map +1 -0
  18. package/dist/exporters/package-info.d.ts +9 -0
  19. package/dist/exporters/package-info.d.ts.map +1 -0
  20. package/dist/exporters/package-info.js +73 -0
  21. package/dist/exporters/package-info.js.map +1 -0
  22. package/dist/exporters/playwright-report-parser.d.ts +21 -0
  23. package/dist/exporters/playwright-report-parser.d.ts.map +1 -0
  24. package/dist/exporters/playwright-report-parser.js +184 -0
  25. package/dist/exporters/playwright-report-parser.js.map +1 -0
  26. package/dist/exporters/scenario-merger.d.ts +21 -0
  27. package/dist/exporters/scenario-merger.d.ts.map +1 -0
  28. package/dist/exporters/scenario-merger.js +51 -0
  29. package/dist/exporters/scenario-merger.js.map +1 -0
  30. package/dist/exporters/spec-parser.d.ts +20 -0
  31. package/dist/exporters/spec-parser.d.ts.map +1 -0
  32. package/dist/exporters/spec-parser.js +259 -0
  33. package/dist/exporters/spec-parser.js.map +1 -0
  34. package/dist/exporters/step-formatter.d.ts +32 -0
  35. package/dist/exporters/step-formatter.d.ts.map +1 -0
  36. package/dist/exporters/step-formatter.js +76 -0
  37. package/dist/exporters/step-formatter.js.map +1 -0
  38. package/dist/exporters/test-data-resolver.d.ts +20 -0
  39. package/dist/exporters/test-data-resolver.d.ts.map +1 -0
  40. package/dist/exporters/test-data-resolver.js +96 -0
  41. package/dist/exporters/test-data-resolver.js.map +1 -0
  42. package/dist/exporters/types.d.ts +104 -0
  43. package/dist/exporters/types.d.ts.map +1 -0
  44. package/dist/exporters/types.js +6 -0
  45. package/dist/exporters/types.js.map +1 -0
  46. package/dist/exporters/xlsx-exporter.d.ts +19 -0
  47. package/dist/exporters/xlsx-exporter.d.ts.map +1 -0
  48. package/dist/exporters/xlsx-exporter.js +309 -0
  49. package/dist/exporters/xlsx-exporter.js.map +1 -0
  50. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  51. package/dist/generators/test-generator/utils/selector-resolver.js +26 -0
  52. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  53. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  54. package/dist/orchestrator/ai-rules-updater.js +12 -0
  55. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  56. package/dist/orchestrator/project-initializer.d.ts +12 -1
  57. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  58. package/dist/orchestrator/project-initializer.js +84 -64
  59. package/dist/orchestrator/project-initializer.js.map +1 -1
  60. package/dist/orchestrator/screen-manager.d.ts.map +1 -1
  61. package/dist/orchestrator/screen-manager.js +2 -0
  62. package/dist/orchestrator/screen-manager.js.map +1 -1
  63. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +15 -17
  64. package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +7 -5
  65. package/dist/orchestrator/templates/ai-instructions/claude-cmd-delivery.md +71 -0
  66. package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +27 -0
  67. package/dist/orchestrator/templates/ai-instructions/claude-config.md +12 -2
  68. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +142 -0
  69. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +100 -0
  70. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +73 -0
  71. package/dist/orchestrator/templates/ai-instructions/claude-skill-delivery.md +103 -0
  72. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +2 -0
  73. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +22 -0
  74. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +13 -15
  75. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +6 -4
  76. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +71 -0
  77. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +38 -14
  78. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +12 -2
  79. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +142 -0
  80. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +100 -0
  81. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +73 -0
  82. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +103 -0
  83. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +2 -0
  84. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +22 -0
  85. package/dist/orchestrator/templates/playwright.config.d.ts.map +1 -1
  86. package/dist/orchestrator/templates/playwright.config.js +6 -1
  87. package/dist/orchestrator/templates/playwright.config.js.map +1 -1
  88. package/dist/orchestrator/templates/playwright.config.ts +6 -1
  89. package/package.json +2 -1
  90. package/src/cli/commands/delivery.ts +348 -0
  91. package/src/cli/commands/update.ts +84 -2
  92. package/src/cli/index.ts +4 -2
  93. package/src/exporters/csv-exporter.ts +304 -0
  94. package/src/exporters/feature-parser.ts +168 -0
  95. package/src/exporters/package-info.ts +35 -0
  96. package/src/exporters/playwright-report-parser.ts +168 -0
  97. package/src/exporters/scenario-merger.ts +63 -0
  98. package/src/exporters/spec-parser.ts +247 -0
  99. package/src/exporters/step-formatter.ts +80 -0
  100. package/src/exporters/test-data-resolver.ts +59 -0
  101. package/src/exporters/types.ts +112 -0
  102. package/src/exporters/xlsx-exporter.ts +301 -0
  103. package/src/generators/test-generator/utils/selector-resolver.ts +26 -0
  104. package/src/orchestrator/ai-rules-updater.ts +12 -0
  105. package/src/orchestrator/project-initializer.ts +103 -70
  106. package/src/orchestrator/screen-manager.ts +2 -0
  107. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +15 -17
  108. package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +7 -5
  109. package/src/orchestrator/templates/ai-instructions/claude-cmd-delivery.md +71 -0
  110. package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +27 -0
  111. package/src/orchestrator/templates/ai-instructions/claude-config.md +12 -2
  112. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +142 -0
  113. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +100 -0
  114. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +73 -0
  115. package/src/orchestrator/templates/ai-instructions/claude-skill-delivery.md +103 -0
  116. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +2 -0
  117. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +22 -0
  118. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +13 -15
  119. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +6 -4
  120. package/src/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +71 -0
  121. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +38 -14
  122. package/src/orchestrator/templates/ai-instructions/copilot-config.md +12 -2
  123. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +142 -0
  124. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +100 -0
  125. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +73 -0
  126. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +103 -0
  127. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +2 -0
  128. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +22 -0
  129. package/src/orchestrator/templates/playwright.config.ts +6 -1
@@ -0,0 +1,301 @@
1
+ /**
2
+ * Render the same rows/summary the CSV exporter produces into a styled .xlsx
3
+ * workbook. Keeps the BM-2-901-13 layout but adds:
4
+ * - bold header row with filled background + white font
5
+ * - borders on the data region
6
+ * - text wrapping in Steps / Expected / Pre-condition / Test Data
7
+ * - frozen header so the first data rows stay aligned with the title row
8
+ * - sensible column widths
9
+ * - category-separator rows rendered as a filled bar spanning the table
10
+ */
11
+
12
+ import * as fs from 'fs';
13
+ import * as path from 'path';
14
+ import ExcelJS from 'exceljs';
15
+ import { ScreenSummary, TestCaseRow } from './types';
16
+ import { getPackageVersion } from './package-info';
17
+
18
+ const COL_COUNT = 16;
19
+ const HEADER_FILL = 'FF1F4E78'; // deep blue band
20
+ const HEADER_FONT = 'FFFFFFFF';
21
+ const META_FILL = 'FFDCE6F1'; // soft blue for meta rows
22
+ const CATEGORY_FILL = 'FFFFF2CC'; // soft yellow for group dividers
23
+ const BORDER_COLOR = 'FFBFBFBF';
24
+
25
+ type AnyCell = ExcelJS.Cell;
26
+ type AnyRow = ExcelJS.Row;
27
+
28
+ function applyBorder(cell: AnyCell): void {
29
+ cell.border = {
30
+ top: { style: 'thin', color: { argb: BORDER_COLOR } },
31
+ left: { style: 'thin', color: { argb: BORDER_COLOR } },
32
+ bottom: { style: 'thin', color: { argb: BORDER_COLOR } },
33
+ right: { style: 'thin', color: { argb: BORDER_COLOR } },
34
+ };
35
+ }
36
+
37
+ export function renderXlsx(
38
+ summary: ScreenSummary,
39
+ rows: TestCaseRow[],
40
+ specLink: string
41
+ ): ExcelJS.Workbook {
42
+ const wb = new ExcelJS.Workbook();
43
+ wb.creator = 'sungen delivery';
44
+ wb.created = new Date();
45
+ const ws = wb.addWorksheet('Testcases', {
46
+ views: [{ state: 'frozen', ySplit: 0, xSplit: 0 }],
47
+ });
48
+
49
+ const issueDate = (() => {
50
+ const d = new Date();
51
+ return `${String(d.getDate()).padStart(2, '0')}/${String(d.getMonth() + 1).padStart(2, '0')}/${d.getFullYear()}`;
52
+ })();
53
+
54
+ const total = summary.total || 1;
55
+ const pct = (n: number) => `${Math.round((n / total) * 100)}%`;
56
+
57
+ // -- Column widths (matches CSV column order) --
58
+ ws.columns = [
59
+ { width: 18 }, // TC ID
60
+ { width: 42 }, // Category 1
61
+ { width: 14 }, // Category 2
62
+ { width: 26 }, // Category 3
63
+ { width: 14 }, // Category 4
64
+ { width: 36 }, // Pre-condition
65
+ { width: 30 }, // Test Data
66
+ { width: 46 }, // Steps
67
+ { width: 52 }, // Expected results
68
+ { width: 10 }, // Priority
69
+ { width: 14 }, // Testcase type
70
+ { width: 14 }, // Test Result
71
+ { width: 14 }, // Executed Date
72
+ { width: 16 }, // Test Executor
73
+ { width: 38 }, // Test Environment
74
+ { width: 40 }, // Note
75
+ ];
76
+
77
+ // -- Top metadata band (rows 1-4) --
78
+ const titleLabel = `${summary.screen.toUpperCase()} TESTCASE`;
79
+ const titleRow = ws.addRow(['', '', '', titleLabel, '', '', 'No: BM-2-901-13']);
80
+ titleRow.getCell(4).font = { bold: true, size: 16 };
81
+ titleRow.getCell(4).alignment = { horizontal: 'center', vertical: 'middle' };
82
+ ws.mergeCells(titleRow.number, 4, titleRow.number, 6);
83
+ titleRow.getCell(7).font = { bold: true };
84
+ titleRow.height = 22;
85
+
86
+ const versionRow = ws.addRow(['', '', '', '', '', '', `Version: ${getPackageVersion()}`]);
87
+ versionRow.getCell(7).font = { bold: true };
88
+ const dateRow = ws.addRow(['', '', '', '', '', '', `Issue Date: ${issueDate}`]);
89
+ dateRow.getCell(7).font = { bold: true };
90
+ const companyRow = ws.addRow([
91
+ 'SUN ASTERISK VIETNAM CO., LTD',
92
+ '',
93
+ '',
94
+ '',
95
+ '',
96
+ '',
97
+ 'ISO/IEC 27001:2022 & ISO 9001:2015',
98
+ ]);
99
+ companyRow.getCell(1).font = { bold: true };
100
+ companyRow.getCell(7).font = { bold: true };
101
+
102
+ ws.addRow([]); // spacer
103
+
104
+ // -- Summary band --
105
+ const summaryHeader = ws.addRow([
106
+ '',
107
+ '',
108
+ 'Total TCs',
109
+ 'Passed',
110
+ 'Failed',
111
+ 'Pending',
112
+ 'N/A',
113
+ 'Remaining',
114
+ ]);
115
+ for (let i = 3; i <= 8; i++) {
116
+ const c = summaryHeader.getCell(i);
117
+ c.font = { bold: true, color: { argb: HEADER_FONT } };
118
+ c.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: HEADER_FILL } };
119
+ c.alignment = { horizontal: 'center', vertical: 'middle' };
120
+ applyBorder(c);
121
+ }
122
+
123
+ const summaryCounts = ws.addRow([
124
+ '',
125
+ '',
126
+ summary.total,
127
+ summary.passed,
128
+ summary.failed,
129
+ summary.pending,
130
+ summary.na,
131
+ summary.pending + summary.na,
132
+ ]);
133
+ for (let i = 3; i <= 8; i++) {
134
+ const c = summaryCounts.getCell(i);
135
+ c.font = { bold: true };
136
+ c.alignment = { horizontal: 'center' };
137
+ c.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: META_FILL } };
138
+ applyBorder(c);
139
+ }
140
+
141
+ const summaryPct = ws.addRow([
142
+ '',
143
+ '',
144
+ '',
145
+ pct(summary.passed),
146
+ pct(summary.failed),
147
+ pct(summary.pending),
148
+ pct(summary.na),
149
+ pct(summary.pending + summary.na),
150
+ ]);
151
+ for (let i = 4; i <= 8; i++) {
152
+ const c = summaryPct.getCell(i);
153
+ c.alignment = { horizontal: 'center' };
154
+ c.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: META_FILL } };
155
+ applyBorder(c);
156
+ }
157
+
158
+ ws.addRow([]); // spacer
159
+
160
+ const specRow = ws.addRow(['Spec/Design link:', specLink]);
161
+ specRow.getCell(1).font = { bold: true };
162
+ specRow.getCell(2).font = { color: { argb: 'FF1F4E78' }, underline: true };
163
+
164
+ ws.addRow([]); // spacer
165
+
166
+ const legendRow = ws.addRow(['*: Mandatory']);
167
+ legendRow.getCell(1).font = { italic: true, color: { argb: 'FF808080' } };
168
+
169
+ // -- Column header row (bold, filled, wrapped) --
170
+ const headerRow = ws.addRow([
171
+ 'TC ID*',
172
+ '{Category 1}',
173
+ '{Category 2}',
174
+ '{Category 3}',
175
+ '{Category 4}',
176
+ 'Pre-condition',
177
+ 'Test Data',
178
+ 'Steps*',
179
+ 'Expected results*',
180
+ 'Priority',
181
+ 'Testcase type',
182
+ 'Test Result*',
183
+ 'Executed Date*',
184
+ 'Test Executor*',
185
+ 'Test Environment',
186
+ 'Note\n(Test evidence, DefectID, Actual result)',
187
+ ]);
188
+ headerRow.height = 38;
189
+ headerRow.eachCell((cell) => {
190
+ cell.font = { bold: true, color: { argb: HEADER_FONT } };
191
+ cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: HEADER_FILL } };
192
+ cell.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true };
193
+ applyBorder(cell);
194
+ });
195
+
196
+ // Freeze everything above and below the header row — reveal header when scrolling.
197
+ ws.views = [{ state: 'frozen', ySplit: headerRow.number }];
198
+
199
+ // -- Data rows grouped by category --
200
+ const order = ['Accessing', 'GUI', 'Function'];
201
+ const grouped = new Map<string, TestCaseRow[]>();
202
+ for (const r of rows) {
203
+ const g = grouped.get(r.category2) || [];
204
+ g.push(r);
205
+ grouped.set(r.category2, g);
206
+ }
207
+ const emittedGroups = new Set<string>();
208
+
209
+ function emitGroup(groupName: string, groupRows: TestCaseRow[]): void {
210
+ const divider = ws.addRow(['', groupName]);
211
+ divider.getCell(2).font = { bold: true };
212
+ divider.eachCell({ includeEmpty: false }, (cell) => {
213
+ cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: CATEGORY_FILL } };
214
+ });
215
+ // Fill the full width so the band spans the whole table
216
+ for (let i = 1; i <= COL_COUNT; i++) {
217
+ const c = divider.getCell(i);
218
+ c.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: CATEGORY_FILL } };
219
+ applyBorder(c);
220
+ }
221
+
222
+ for (const r of groupRows) {
223
+ const row = ws.addRow([
224
+ r.tcId,
225
+ r.category1,
226
+ '',
227
+ '',
228
+ '',
229
+ r.precondition,
230
+ r.testData,
231
+ r.steps,
232
+ r.expectedResults,
233
+ r.priority,
234
+ r.testcaseType,
235
+ r.testResult,
236
+ r.executedDate,
237
+ r.testExecutor,
238
+ r.testEnvironment,
239
+ r.note,
240
+ ]);
241
+ row.alignment = { vertical: 'top', wrapText: true };
242
+ row.eachCell({ includeEmpty: true }, (cell, colNumber) => {
243
+ applyBorder(cell);
244
+ if (colNumber === 1) {
245
+ cell.font = { bold: true };
246
+ }
247
+ if (colNumber === 12) {
248
+ // Test Result — colour-code
249
+ const val = String(cell.value || '');
250
+ if (val === 'Passed') {
251
+ cell.font = { color: { argb: 'FF107C41' }, bold: true };
252
+ } else if (val === 'Failed') {
253
+ cell.font = { color: { argb: 'FFC00000' }, bold: true };
254
+ } else if (val === 'Pending') {
255
+ cell.font = { color: { argb: 'FF996D00' }, bold: true };
256
+ } else if (val === 'N/A') {
257
+ cell.font = { color: { argb: 'FF808080' } };
258
+ }
259
+ }
260
+ });
261
+ }
262
+ }
263
+
264
+ for (const groupName of order) {
265
+ const groupRows = grouped.get(groupName);
266
+ if (!groupRows || groupRows.length === 0) continue;
267
+ emittedGroups.add(groupName);
268
+ emitGroup(groupName, groupRows);
269
+ }
270
+ for (const [groupName, groupRows] of grouped.entries()) {
271
+ if (emittedGroups.has(groupName)) continue;
272
+ emitGroup(groupName, groupRows);
273
+ }
274
+
275
+ // Auto-filter on the header row so QA can filter by result / priority / category
276
+ ws.autoFilter = {
277
+ from: { row: headerRow.number, column: 1 },
278
+ to: { row: ws.rowCount, column: COL_COUNT },
279
+ };
280
+
281
+ return wb;
282
+ }
283
+
284
+ /**
285
+ * Write the workbook to `qa/deliverables/<screen>-testcases.xlsx`.
286
+ * Directory is created on demand. Returns the absolute output path.
287
+ */
288
+ export async function writeXlsx(
289
+ cwd: string,
290
+ screen: string,
291
+ wb: ExcelJS.Workbook
292
+ ): Promise<string> {
293
+ const outDir = path.join(cwd, 'qa', 'deliverables');
294
+ if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
295
+ const outPath = path.join(outDir, `${screen}-testcases.xlsx`);
296
+ await wb.xlsx.writeFile(outPath);
297
+ return outPath;
298
+ }
299
+
300
+ void applyBorder;
301
+ void ({} as AnyRow);
@@ -218,6 +218,14 @@ export class SelectorResolver {
218
218
  'tab': () => ({ strategy: 'role', role: 'tab', name: label, value: 'tab' }),
219
219
  'dialog': () => ({ strategy: 'role', role: 'dialog', name: label, value: 'dialog' }),
220
220
  'alertdialog': () => ({ strategy: 'role', role: 'alertdialog', name: label, value: 'alertdialog' }),
221
+ 'combobox': () => ({ strategy: 'role', role: 'combobox', name: label, value: 'combobox' }),
222
+ 'menuitem': () => ({ strategy: 'role', role: 'menuitem', name: label, value: 'menuitem' }),
223
+ 'progressbar': () => ({ strategy: 'role', role: 'progressbar', name: label, value: 'progressbar' }),
224
+ 'region': () => ({ strategy: 'role', role: 'region', name: label, value: 'region' }),
225
+ 'article': () => ({ strategy: 'role', role: 'article', name: label, value: 'article' }),
226
+ 'cell': () => ({ strategy: 'role', role: 'cell', name: label, value: 'cell' }),
227
+ 'status': () => ({ strategy: 'role', role: 'status', name: label, value: 'status' }),
228
+ 'navigation': () => ({ strategy: 'role', role: 'navigation', name: label, value: 'navigation' }),
221
229
  };
222
230
 
223
231
  const factory = strategyMap[normalized];
@@ -263,6 +271,24 @@ export class SelectorResolver {
263
271
  // Modal/drawer alias → dialog role
264
272
  'modal': 'dialog',
265
273
  'drawer': 'dialog',
274
+ // Dropdown/select alias -> combobox role
275
+ 'dropdown': 'combobox',
276
+ 'select': 'combobox',
277
+ // Data aliases
278
+ 'item': 'listitem',
279
+ 'card': 'article',
280
+ 'cell': 'cell',
281
+ 'section': 'region',
282
+ // Feedback aliases
283
+ 'badge': 'text',
284
+ 'tooltip': 'text',
285
+ 'tag': 'text',
286
+ // Trigger aliases
287
+ 'menuitem': 'menuitem',
288
+ // System aliases
289
+ 'progressbar': 'progressbar',
290
+ 'spinner': 'status',
291
+ 'breadcrumb': 'navigation',
266
292
  };
267
293
  return aliasMap[elementType] || elementType;
268
294
  }
@@ -18,11 +18,15 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
18
18
  ['claude-cmd-add-screen.md', '.claude/commands/sungen/add-screen.md'],
19
19
  ['claude-cmd-create-test.md', '.claude/commands/sungen/create-test.md'],
20
20
  ['claude-cmd-run-test.md', '.claude/commands/sungen/run-test.md'],
21
+ ['claude-cmd-review.md', '.claude/commands/sungen/review.md'],
22
+ ['claude-cmd-delivery.md', '.claude/commands/sungen/delivery.md'],
21
23
 
22
24
  // Commands — GitHub Copilot
23
25
  ['copilot-cmd-add-screen.md', '.github/prompts/sungen-add-screen.prompt.md'],
24
26
  ['copilot-cmd-create-test.md', '.github/prompts/sungen-create-test.prompt.md'],
25
27
  ['copilot-cmd-run-test.md', '.github/prompts/sungen-run-test.prompt.md'],
28
+ ['copilot-cmd-review.md', '.github/prompts/sungen-review.prompt.md'],
29
+ ['copilot-cmd-delivery.md', '.github/prompts/sungen-delivery.prompt.md'],
26
30
 
27
31
  // Skills — Claude Code
28
32
  ['claude-skill-gherkin-syntax.md', '.claude/skills/sungen-gherkin-syntax/SKILL.md'],
@@ -33,6 +37,10 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
33
37
  ['claude-skill-selector-fix.md', '.claude/skills/sungen-selector-fix/SKILL.md'],
34
38
  ['claude-skill-tc-review.md', '.claude/skills/sungen-tc-review/SKILL.md'],
35
39
  ['claude-skill-viewpoint.md', '.claude/skills/sungen-viewpoint/SKILL.md'],
40
+ ['claude-skill-delivery.md', '.claude/skills/sungen-delivery/SKILL.md'],
41
+ ['claude-skill-capture-figma.md', '.claude/skills/sungen-capture-figma/SKILL.md'],
42
+ ['claude-skill-capture-local.md', '.claude/skills/sungen-capture-local/SKILL.md'],
43
+ ['claude-skill-capture-live.md', '.claude/skills/sungen-capture-live/SKILL.md'],
36
44
 
37
45
  // Skills — GitHub Copilot
38
46
  ['github-skill-sungen-gherkin-syntax.md', '.github/skills/sungen-gherkin-syntax/SKILL.md'],
@@ -43,6 +51,10 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
43
51
  ['github-skill-sungen-selector-fix.md', '.github/skills/sungen-selector-fix/SKILL.md'],
44
52
  ['github-skill-sungen-tc-review.md', '.github/skills/sungen-tc-review/SKILL.md'],
45
53
  ['github-skill-sungen-viewpoint.md', '.github/skills/sungen-viewpoint/SKILL.md'],
54
+ ['github-skill-sungen-delivery.md', '.github/skills/sungen-delivery/SKILL.md'],
55
+ ['github-skill-sungen-capture-figma.md', '.github/skills/sungen-capture-figma/SKILL.md'],
56
+ ['github-skill-sungen-capture-local.md', '.github/skills/sungen-capture-local/SKILL.md'],
57
+ ['github-skill-sungen-capture-live.md', '.github/skills/sungen-capture-live/SKILL.md'],
46
58
  ];
47
59
 
48
60
  export class AIRulesUpdater {
@@ -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;
@@ -62,6 +63,9 @@ export class ProjectInitializer {
62
63
  // Create VS Code settings for Copilot auto-attach
63
64
  this.createVSCodeSettings();
64
65
 
66
+ // Create root .mcp.json for Claude Code
67
+ this.createClaudeMCPConfig();
68
+
65
69
  // Display summary
66
70
  this.displaySummary(normalizedProjectName);
67
71
  }
@@ -190,46 +194,112 @@ export class ProjectInitializer {
190
194
  private createVSCodeSettings(): void {
191
195
  const settingsPath = path.join(this.cwd, '.vscode', 'settings.json');
192
196
 
193
- if (fs.existsSync(settingsPath)) {
194
- this.skippedItems.push('.vscode/settings.json');
195
- return;
196
- }
197
+ if (!fs.existsSync(settingsPath)) {
198
+ const settingsDir = path.dirname(settingsPath);
199
+ if (!fs.existsSync(settingsDir)) {
200
+ fs.mkdirSync(settingsDir, { recursive: true });
201
+ }
197
202
 
198
- const settingsDir = path.dirname(settingsPath);
199
- if (!fs.existsSync(settingsDir)) {
200
- fs.mkdirSync(settingsDir, { recursive: true });
203
+ const settings = {
204
+ 'github.copilot.chat.agent.runTasks': true,
205
+ 'chat.tools.terminal.autoApprove': {
206
+ sungen: true,
207
+ 'npx playwright': true,
208
+ },
209
+ };
210
+
211
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
212
+ this.createdItems.push('.vscode/settings.json');
213
+ } else {
214
+ this.skippedItems.push('.vscode/settings.json');
201
215
  }
202
216
 
203
- const settings = {
204
- 'github.copilot.chat.agent.runTasks': true,
205
- 'chat.tools.terminal.autoApprove': {
206
- sungen: true,
207
- 'npx playwright': true,
217
+ // .vscode/mcp.json (Copilot format) — safe-merge Playwright + Figma entries
218
+ this.mergeMCPConfig(
219
+ path.join(this.cwd, '.vscode', 'mcp.json'),
220
+ 'servers',
221
+ {
222
+ playwright: {
223
+ command: 'npx',
224
+ args: ['@playwright/mcp@latest'],
225
+ },
226
+ figma: {
227
+ type: 'http',
228
+ url: 'https://mcp.figma.com/mcp',
229
+ },
208
230
  },
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
- }
231
+ '.vscode/mcp.json',
232
+ );
233
+ }
221
234
 
222
- const mcpConfig = {
223
- servers: {
235
+ /**
236
+ * Create root .mcp.json for Claude Code — safe-merge Playwright + Figma entries
237
+ */
238
+ private createClaudeMCPConfig(): void {
239
+ this.mergeMCPConfig(
240
+ path.join(this.cwd, '.mcp.json'),
241
+ 'mcpServers',
242
+ {
224
243
  playwright: {
244
+ type: 'stdio',
225
245
  command: 'npx',
226
246
  args: ['@playwright/mcp@latest'],
227
247
  },
248
+ figma: {
249
+ type: 'http',
250
+ url: 'https://mcp.figma.com/mcp',
251
+ },
228
252
  },
229
- };
253
+ '.mcp.json',
254
+ );
255
+ }
256
+
257
+ /**
258
+ * Add MCP server entries without overwriting user config.
259
+ * Preserves existing entries with the same key — user edits always win.
260
+ */
261
+ private mergeMCPConfig(
262
+ filePath: string,
263
+ rootKey: string,
264
+ newServers: Record<string, unknown>,
265
+ displayName: string,
266
+ ): void {
267
+ const dir = path.dirname(filePath);
268
+ if (!fs.existsSync(dir)) {
269
+ fs.mkdirSync(dir, { recursive: true });
270
+ }
271
+
272
+ let config: Record<string, Record<string, unknown>> = {};
273
+ const existed = fs.existsSync(filePath);
274
+
275
+ if (existed) {
276
+ try {
277
+ config = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
278
+ } catch {
279
+ this.skippedItems.push(`${displayName} (invalid JSON, skipped)`);
280
+ return;
281
+ }
282
+ }
230
283
 
231
- fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + '\n', 'utf-8');
232
- this.createdItems.push('.vscode/mcp.json');
284
+ if (!config[rootKey] || typeof config[rootKey] !== 'object') {
285
+ config[rootKey] = {};
286
+ }
287
+
288
+ let added = 0;
289
+ for (const [name, entry] of Object.entries(newServers)) {
290
+ if (!config[rootKey][name]) {
291
+ config[rootKey][name] = entry;
292
+ added++;
293
+ }
294
+ }
295
+
296
+ if (added === 0) {
297
+ this.skippedItems.push(displayName);
298
+ return;
299
+ }
300
+
301
+ fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
302
+ this.createdItems.push(existed ? `${displayName} (updated)` : displayName);
233
303
  }
234
304
 
235
305
  /**
@@ -264,51 +334,14 @@ export class ProjectInitializer {
264
334
  }
265
335
 
266
336
  /**
267
- * Create AI rules files for GitHub Copilot and Claude Code
337
+ * Create AI rules files for GitHub Copilot and Claude Code.
338
+ * Shares `AI_RULES_FILE_MAPPING` with `sungen update` so a newly added
339
+ * skill / command template only needs to be registered once.
268
340
  */
269
341
  private createAIRules(): void {
270
342
  const aiTemplateDir = path.join(__dirname, 'templates', 'ai-instructions');
271
343
 
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) {
344
+ for (const [templateFile, outputRelPath] of AI_RULES_FILE_MAPPING) {
312
345
  const outputPath = path.join(this.cwd, outputRelPath);
313
346
 
314
347
  if (fs.existsSync(outputPath)) {
@@ -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