@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.
- package/dist/cli/commands/delivery.d.ts +7 -0
- package/dist/cli/commands/delivery.d.ts.map +1 -0
- package/dist/cli/commands/delivery.js +348 -0
- package/dist/cli/commands/delivery.js.map +1 -0
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +2 -0
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +64 -1
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/index.js +4 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/exporters/csv-exporter.d.ts +32 -0
- package/dist/exporters/csv-exporter.d.ts.map +1 -0
- package/dist/exporters/csv-exporter.js +311 -0
- package/dist/exporters/csv-exporter.js.map +1 -0
- package/dist/exporters/feature-parser.d.ts +48 -0
- package/dist/exporters/feature-parser.d.ts.map +1 -0
- package/dist/exporters/feature-parser.js +178 -0
- package/dist/exporters/feature-parser.js.map +1 -0
- package/dist/exporters/package-info.d.ts +9 -0
- package/dist/exporters/package-info.d.ts.map +1 -0
- package/dist/exporters/package-info.js +73 -0
- package/dist/exporters/package-info.js.map +1 -0
- package/dist/exporters/playwright-report-parser.d.ts +21 -0
- package/dist/exporters/playwright-report-parser.d.ts.map +1 -0
- package/dist/exporters/playwright-report-parser.js +184 -0
- package/dist/exporters/playwright-report-parser.js.map +1 -0
- package/dist/exporters/scenario-merger.d.ts +21 -0
- package/dist/exporters/scenario-merger.d.ts.map +1 -0
- package/dist/exporters/scenario-merger.js +51 -0
- package/dist/exporters/scenario-merger.js.map +1 -0
- package/dist/exporters/spec-parser.d.ts +20 -0
- package/dist/exporters/spec-parser.d.ts.map +1 -0
- package/dist/exporters/spec-parser.js +259 -0
- package/dist/exporters/spec-parser.js.map +1 -0
- package/dist/exporters/step-formatter.d.ts +32 -0
- package/dist/exporters/step-formatter.d.ts.map +1 -0
- package/dist/exporters/step-formatter.js +76 -0
- package/dist/exporters/step-formatter.js.map +1 -0
- package/dist/exporters/test-data-resolver.d.ts +20 -0
- package/dist/exporters/test-data-resolver.d.ts.map +1 -0
- package/dist/exporters/test-data-resolver.js +96 -0
- package/dist/exporters/test-data-resolver.js.map +1 -0
- package/dist/exporters/types.d.ts +104 -0
- package/dist/exporters/types.d.ts.map +1 -0
- package/dist/exporters/types.js +6 -0
- package/dist/exporters/types.js.map +1 -0
- package/dist/exporters/xlsx-exporter.d.ts +19 -0
- package/dist/exporters/xlsx-exporter.d.ts.map +1 -0
- package/dist/exporters/xlsx-exporter.js +309 -0
- package/dist/exporters/xlsx-exporter.js.map +1 -0
- package/dist/generators/gherkin-parser/index.d.ts +1 -0
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +3 -0
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts +29 -1
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +21 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js +11 -2
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/after-all.hbs +8 -0
- package/dist/generators/test-generator/adapters/playwright/templates/after-each.hbs +8 -0
- package/dist/generators/test-generator/adapters/playwright/templates/before-all.hbs +8 -0
- package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +24 -0
- package/dist/generators/test-generator/code-generator.d.ts +2 -0
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +109 -12
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/step-mapper.d.ts +1 -0
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +1 -1
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts +29 -1
- package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
- package/dist/generators/test-generator/template-engine.js +11 -2
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.d.ts +11 -2
- package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.js +36 -25
- package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts +7 -0
- package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/runtime-data-transformer.js +42 -0
- package/dist/generators/test-generator/utils/runtime-data-transformer.js.map +1 -0
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +26 -0
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/generators/types.d.ts +1 -0
- package/dist/generators/types.d.ts.map +1 -1
- package/dist/generators/types.js.map +1 -1
- package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
- package/dist/orchestrator/ai-rules-updater.js +12 -0
- package/dist/orchestrator/ai-rules-updater.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts +21 -1
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +158 -74
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/screen-manager.d.ts.map +1 -1
- package/dist/orchestrator/screen-manager.js +2 -0
- package/dist/orchestrator/screen-manager.js.map +1 -1
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +15 -17
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +7 -5
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-delivery.md +71 -0
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +28 -1
- package/dist/orchestrator/templates/ai-instructions/claude-config.md +23 -4
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +142 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +100 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +73 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-delivery.md +103 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +68 -13
- package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +22 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +54 -3
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +13 -15
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +6 -4
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +71 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +38 -14
- package/dist/orchestrator/templates/ai-instructions/copilot-config.md +23 -4
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +142 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +100 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +73 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +103 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +88 -13
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +22 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +54 -3
- package/dist/orchestrator/templates/playwright.config.d.ts.map +1 -1
- package/dist/orchestrator/templates/playwright.config.js +6 -1
- package/dist/orchestrator/templates/playwright.config.js.map +1 -1
- package/dist/orchestrator/templates/playwright.config.ts +6 -1
- package/dist/orchestrator/templates/specs-base.d.ts +12 -1
- package/dist/orchestrator/templates/specs-base.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-base.js +47 -5
- package/dist/orchestrator/templates/specs-base.js.map +1 -1
- package/dist/orchestrator/templates/specs-base.ts +65 -7
- package/dist/orchestrator/templates/specs-test-data.d.ts +14 -0
- package/dist/orchestrator/templates/specs-test-data.d.ts.map +1 -0
- package/dist/orchestrator/templates/specs-test-data.js +100 -0
- package/dist/orchestrator/templates/specs-test-data.js.map +1 -0
- package/dist/orchestrator/templates/specs-test-data.ts +66 -0
- package/package.json +2 -1
- package/src/cli/commands/delivery.ts +348 -0
- package/src/cli/commands/generate.ts +2 -0
- package/src/cli/commands/update.ts +84 -2
- package/src/cli/index.ts +4 -2
- package/src/exporters/csv-exporter.ts +304 -0
- package/src/exporters/feature-parser.ts +168 -0
- package/src/exporters/package-info.ts +35 -0
- package/src/exporters/playwright-report-parser.ts +168 -0
- package/src/exporters/scenario-merger.ts +63 -0
- package/src/exporters/spec-parser.ts +247 -0
- package/src/exporters/step-formatter.ts +80 -0
- package/src/exporters/test-data-resolver.ts +59 -0
- package/src/exporters/types.ts +112 -0
- package/src/exporters/xlsx-exporter.ts +301 -0
- package/src/generators/gherkin-parser/index.ts +4 -0
- package/src/generators/test-generator/adapters/adapter-interface.ts +12 -1
- package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +14 -2
- package/src/generators/test-generator/adapters/playwright/templates/after-all.hbs +8 -0
- package/src/generators/test-generator/adapters/playwright/templates/after-each.hbs +8 -0
- package/src/generators/test-generator/adapters/playwright/templates/before-all.hbs +8 -0
- package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +24 -0
- package/src/generators/test-generator/code-generator.ts +122 -13
- package/src/generators/test-generator/step-mapper.ts +2 -2
- package/src/generators/test-generator/template-engine.ts +28 -2
- package/src/generators/test-generator/utils/data-resolver.ts +45 -27
- package/src/generators/test-generator/utils/runtime-data-transformer.ts +51 -0
- package/src/generators/test-generator/utils/selector-resolver.ts +26 -0
- package/src/generators/types.ts +1 -0
- package/src/orchestrator/ai-rules-updater.ts +12 -0
- package/src/orchestrator/project-initializer.ts +187 -80
- package/src/orchestrator/screen-manager.ts +2 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +15 -17
- package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +7 -5
- package/src/orchestrator/templates/ai-instructions/claude-cmd-delivery.md +71 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +28 -1
- package/src/orchestrator/templates/ai-instructions/claude-config.md +23 -4
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-figma.md +142 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +100 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-local.md +73 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-delivery.md +103 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +68 -13
- package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +22 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +54 -3
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +13 -15
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +6 -4
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +71 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +38 -14
- package/src/orchestrator/templates/ai-instructions/copilot-config.md +23 -4
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-figma.md +142 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +100 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-local.md +73 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +103 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +88 -13
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +22 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +54 -3
- package/src/orchestrator/templates/playwright.config.ts +6 -1
- package/src/orchestrator/templates/specs-base.ts +65 -7
- package/src/orchestrator/templates/specs-test-data.ts +66 -0
|
@@ -3,6 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import { ParsedFeature, ParsedScenario, ParsedStep } from '../gherkin-parser';
|
|
4
4
|
import { StepMapper } from './step-mapper';
|
|
5
5
|
import { TestGeneratorAdapter, adapterRegistry } from './adapters';
|
|
6
|
+
import { transformToRuntimeData } from './utils/runtime-data-transformer';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Filter base scenario steps for @extend: only keep Given→When steps.
|
|
@@ -27,6 +28,33 @@ function filterBaseStepsForExtend(steps: ParsedStep[]): ParsedStep[] {
|
|
|
27
28
|
return result;
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Extract @cleanup:* tags into autoCleanup config string for test.use()
|
|
33
|
+
* @cleanup:overlay @cleanup:forms → 'overlay: true, forms: true'
|
|
34
|
+
*/
|
|
35
|
+
function extractCleanupConfig(tags: string[]): string | undefined {
|
|
36
|
+
const validKeys = ['overlay', 'forms', 'scroll', 'storage'];
|
|
37
|
+
const entries = tags
|
|
38
|
+
.filter(t => t.startsWith('@cleanup:'))
|
|
39
|
+
.map(t => t.replace('@cleanup:', ''))
|
|
40
|
+
.filter(key => {
|
|
41
|
+
if (!validKeys.includes(key)) {
|
|
42
|
+
console.warn(`⚠ Unknown @cleanup:${key} — valid options: ${validKeys.join(', ')}`);
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
});
|
|
47
|
+
if (entries.length === 0) return undefined;
|
|
48
|
+
return entries.map(key => `${key}: true`).join(', ');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check for @screenshot:on-failure tag
|
|
53
|
+
*/
|
|
54
|
+
function hasScreenshotOnFailure(tags: string[]): boolean {
|
|
55
|
+
return tags.includes('@screenshot:on-failure');
|
|
56
|
+
}
|
|
57
|
+
|
|
30
58
|
/**
|
|
31
59
|
* Extract auth role from tags
|
|
32
60
|
* @auth:admin → 'admin'
|
|
@@ -106,7 +134,7 @@ export class CodeGenerator {
|
|
|
106
134
|
// Steps registry built per feature during generateTestCode(); used by countSteps()
|
|
107
135
|
private stepsRegistry = new Map<string, ParsedScenario>();
|
|
108
136
|
|
|
109
|
-
constructor(options: { useAI?: boolean; verbose?: boolean; framework?: string; baseURL?: string; screenName?: string } = {}) {
|
|
137
|
+
constructor(options: { useAI?: boolean; verbose?: boolean; framework?: string; baseURL?: string; screenName?: string; runtimeData?: boolean } = {}) {
|
|
110
138
|
this.options = options;
|
|
111
139
|
this.screenName = options.screenName;
|
|
112
140
|
this.stepMapper = new StepMapper(options);
|
|
@@ -155,14 +183,19 @@ export class CodeGenerator {
|
|
|
155
183
|
: path.join(outputDir, fileName);
|
|
156
184
|
|
|
157
185
|
// Generate imports using adapter
|
|
158
|
-
const imports = this.adapter.renderImports();
|
|
186
|
+
const imports = this.adapter.renderImports({ runtimeData: this.options.runtimeData });
|
|
159
187
|
|
|
160
188
|
// Generate test code (async now to support AI mapping)
|
|
161
189
|
const testCode = await this.generateTestCode(feature);
|
|
162
190
|
|
|
163
191
|
// Combine and collapse any runs of 3+ newlines down to 2 (one blank line max)
|
|
164
192
|
const raw = `${imports}\n\n${testCode}`;
|
|
165
|
-
|
|
193
|
+
let code = raw.replace(/\n{3,}/g, '\n\n');
|
|
194
|
+
|
|
195
|
+
// Runtime data: replace __SUNGEN_TD_ markers with testData.get() calls
|
|
196
|
+
if (this.options.runtimeData) {
|
|
197
|
+
code = transformToRuntimeData(code);
|
|
198
|
+
}
|
|
166
199
|
|
|
167
200
|
return {
|
|
168
201
|
featureName: feature.name,
|
|
@@ -180,6 +213,7 @@ export class CodeGenerator {
|
|
|
180
213
|
private countSteps(feature: ParsedFeature): number {
|
|
181
214
|
let total = 0;
|
|
182
215
|
for (const scenario of feature.scenarios) {
|
|
216
|
+
if (scenario.stepsName || scenario.hookType) continue;
|
|
183
217
|
if (scenario.extendsName) {
|
|
184
218
|
const base = this.stepsRegistry.get(scenario.extendsName);
|
|
185
219
|
total += (base ? base.steps.length : 0) + scenario.steps.length;
|
|
@@ -206,17 +240,30 @@ export class CodeGenerator {
|
|
|
206
240
|
* Ensure specs/base.ts exists in the output directory
|
|
207
241
|
*/
|
|
208
242
|
ensureBaseFile(outputDir: string): void {
|
|
243
|
+
const templatesRoot = path.join(__dirname, '..', '..', '..', 'orchestrator', 'templates');
|
|
244
|
+
|
|
209
245
|
const basePath = path.join(outputDir, 'base.ts');
|
|
210
|
-
if (fs.existsSync(basePath))
|
|
246
|
+
if (!fs.existsSync(basePath)) {
|
|
247
|
+
const templatePath = path.join(templatesRoot, 'specs-base.ts');
|
|
248
|
+
if (fs.existsSync(templatePath)) {
|
|
249
|
+
const baseDir = path.dirname(basePath);
|
|
250
|
+
if (!fs.existsSync(baseDir)) {
|
|
251
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
252
|
+
}
|
|
253
|
+
fs.copyFileSync(templatePath, basePath);
|
|
254
|
+
console.log('✓ Created: specs/base.ts');
|
|
255
|
+
}
|
|
256
|
+
}
|
|
211
257
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
fs.
|
|
258
|
+
if (this.options.runtimeData) {
|
|
259
|
+
const testDataPath = path.join(outputDir, 'test-data.ts');
|
|
260
|
+
if (!fs.existsSync(testDataPath)) {
|
|
261
|
+
const templatePath = path.join(templatesRoot, 'specs-test-data.ts');
|
|
262
|
+
if (fs.existsSync(templatePath)) {
|
|
263
|
+
fs.copyFileSync(templatePath, testDataPath);
|
|
264
|
+
console.log('✓ Created: specs/test-data.ts');
|
|
265
|
+
}
|
|
217
266
|
}
|
|
218
|
-
fs.copyFileSync(templatePath, basePath);
|
|
219
|
-
console.log('✓ Created: specs/base.ts');
|
|
220
267
|
}
|
|
221
268
|
}
|
|
222
269
|
|
|
@@ -233,12 +280,14 @@ export class CodeGenerator {
|
|
|
233
280
|
|
|
234
281
|
// Derive screen name from source file path when not explicitly set
|
|
235
282
|
// qa/screens/{screenName}/features/{featureName}.feature -> screenName
|
|
283
|
+
let effectiveScreenName = this.screenName;
|
|
236
284
|
if (!this.screenName && feature.sourceFile) {
|
|
237
285
|
const sourceDir = path.dirname(feature.sourceFile);
|
|
238
286
|
const parts = sourceDir.split(path.sep);
|
|
239
287
|
const screensIndex = parts.indexOf('screens');
|
|
240
288
|
if (screensIndex >= 0 && screensIndex < parts.length - 2) {
|
|
241
|
-
|
|
289
|
+
effectiveScreenName = parts[screensIndex + 1];
|
|
290
|
+
this.stepMapper.setScreenContext(effectiveScreenName);
|
|
242
291
|
}
|
|
243
292
|
}
|
|
244
293
|
|
|
@@ -261,13 +310,35 @@ export class CodeGenerator {
|
|
|
261
310
|
this.stepsRegistry.set(scenario.stepsName, scenario);
|
|
262
311
|
}
|
|
263
312
|
}
|
|
264
|
-
|
|
313
|
+
|
|
314
|
+
// Pre-pass: extract hook scenarios (@beforeAll, @afterEach, @afterAll)
|
|
315
|
+
const hookScenarios = new Map<string, ParsedScenario>();
|
|
316
|
+
for (const scenario of feature.scenarios) {
|
|
317
|
+
if (scenario.hookType) {
|
|
318
|
+
if (hookScenarios.has(scenario.hookType)) {
|
|
319
|
+
console.warn(`⚠ Duplicate @${scenario.hookType} hook — last definition wins`);
|
|
320
|
+
}
|
|
321
|
+
hookScenarios.set(scenario.hookType, scenario);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
265
325
|
// Generate background if exists
|
|
266
326
|
let background: string | undefined;
|
|
267
327
|
if (feature.background) {
|
|
268
328
|
background = await this.generateBeforeEach(feature.background);
|
|
269
329
|
}
|
|
270
330
|
|
|
331
|
+
// Generate hook blocks
|
|
332
|
+
const beforeAll = hookScenarios.has('beforeAll')
|
|
333
|
+
? await this.generateHook(hookScenarios.get('beforeAll')!, 'beforeAll')
|
|
334
|
+
: undefined;
|
|
335
|
+
const afterEach = hookScenarios.has('afterEach')
|
|
336
|
+
? await this.generateHook(hookScenarios.get('afterEach')!, 'afterEach')
|
|
337
|
+
: undefined;
|
|
338
|
+
const afterAll = hookScenarios.has('afterAll')
|
|
339
|
+
? await this.generateHook(hookScenarios.get('afterAll')!, 'afterAll')
|
|
340
|
+
: undefined;
|
|
341
|
+
|
|
271
342
|
// Generate all scenarios with feature tags for inheritance
|
|
272
343
|
// Skip scenarios tagged with @manual
|
|
273
344
|
// Track auth role per scenario for grouping
|
|
@@ -280,6 +351,11 @@ export class CodeGenerator {
|
|
|
280
351
|
continue;
|
|
281
352
|
}
|
|
282
353
|
|
|
354
|
+
// Skip hook scenarios — already generated as hook blocks above
|
|
355
|
+
if (scenario.hookType) {
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
|
|
283
359
|
// Resolve auth tags for @extend scenarios (same logic as generateScenario)
|
|
284
360
|
let authFeatureTags = feature.tags || [];
|
|
285
361
|
if (scenario.extendsName) {
|
|
@@ -327,12 +403,24 @@ export class CodeGenerator {
|
|
|
327
403
|
? authGroups[0].authRole
|
|
328
404
|
: undefined;
|
|
329
405
|
|
|
406
|
+
// Extract @cleanup:* tags for autoCleanup fixture config
|
|
407
|
+
const cleanupConfig = extractCleanupConfig(feature.tags || []);
|
|
408
|
+
const screenshotOnFailure = hasScreenshotOnFailure(feature.tags || []);
|
|
409
|
+
|
|
330
410
|
// Use adapter to render the complete test file structure
|
|
331
411
|
return this.adapter.renderTestFile({
|
|
332
412
|
imports: '', // Not used in template as it's rendered separately
|
|
333
413
|
featureName: feature.name,
|
|
334
414
|
featureDescription: feature.description,
|
|
335
415
|
background,
|
|
416
|
+
beforeAll,
|
|
417
|
+
afterEach,
|
|
418
|
+
afterAll,
|
|
419
|
+
cleanupConfig,
|
|
420
|
+
screenshotOnFailure,
|
|
421
|
+
runtimeData: this.options.runtimeData,
|
|
422
|
+
screenName: effectiveScreenName,
|
|
423
|
+
featureFileName: featureName,
|
|
336
424
|
scenarios: needsGrouping ? [] : scenarios,
|
|
337
425
|
authGroups: needsGrouping ? authGroups : undefined,
|
|
338
426
|
singleAuthRole,
|
|
@@ -356,6 +444,27 @@ export class CodeGenerator {
|
|
|
356
444
|
});
|
|
357
445
|
}
|
|
358
446
|
|
|
447
|
+
private async generateHook(
|
|
448
|
+
scenario: ParsedScenario,
|
|
449
|
+
hookType: 'beforeAll' | 'afterEach' | 'afterAll'
|
|
450
|
+
): Promise<string> {
|
|
451
|
+
const steps: Array<{ comment?: string; code: string }> = [];
|
|
452
|
+
for (const step of scenario.steps) {
|
|
453
|
+
const mapped = await Promise.resolve(this.stepMapper.mapStep(step));
|
|
454
|
+
steps.push({
|
|
455
|
+
comment: mapped.comment,
|
|
456
|
+
code: this.indentCode(mapped.code, 4),
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const renderMap = {
|
|
461
|
+
beforeAll: () => this.adapter.renderBeforeAll({ steps }),
|
|
462
|
+
afterEach: () => this.adapter.renderAfterEach({ steps }),
|
|
463
|
+
afterAll: () => this.adapter.renderAfterAll({ steps }),
|
|
464
|
+
};
|
|
465
|
+
return renderMap[hookType]();
|
|
466
|
+
}
|
|
467
|
+
|
|
359
468
|
private async generateScenario(
|
|
360
469
|
scenario: ParsedScenario,
|
|
361
470
|
hasBackground: boolean,
|
|
@@ -35,7 +35,7 @@ export class StepMapper {
|
|
|
35
35
|
private inRowScope: boolean = false;
|
|
36
36
|
private rowScopeTable: string = '';
|
|
37
37
|
|
|
38
|
-
constructor(options: { verbose?: boolean; baseURL?: string; featureName?: string; screenName?: string; featurePath?: string } = {}) {
|
|
38
|
+
constructor(options: { verbose?: boolean; baseURL?: string; featureName?: string; screenName?: string; featurePath?: string; runtimeData?: boolean } = {}) {
|
|
39
39
|
this.verbose = options.verbose ?? false;
|
|
40
40
|
this.baseURL = options.baseURL || null; // null means path-only navigation
|
|
41
41
|
this.featureName = options.featureName;
|
|
@@ -46,7 +46,7 @@ export class StepMapper {
|
|
|
46
46
|
this.templateEngine = new TemplateEngine(playwrightTemplatesDir);
|
|
47
47
|
this.patternRegistry = new PatternRegistry();
|
|
48
48
|
this.selectorResolver = new SelectorResolver(undefined, options.screenName);
|
|
49
|
-
this.dataResolver = new DataResolver(undefined, options.screenName);
|
|
49
|
+
this.dataResolver = new DataResolver(undefined, options.screenName, options.runtimeData);
|
|
50
50
|
|
|
51
51
|
if (this.verbose) {
|
|
52
52
|
console.log(` [StepMapper] ${this.patternRegistry.getPatternCount()} patterns loaded`);
|
|
@@ -229,8 +229,8 @@ export class TemplateEngine {
|
|
|
229
229
|
this.baseContext = {};
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
renderImports(): string {
|
|
233
|
-
return this.render('imports', {});
|
|
232
|
+
renderImports(options?: { runtimeData?: boolean }): string {
|
|
233
|
+
return this.render('imports', { runtimeData: options?.runtimeData });
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
renderTestFile(data: {
|
|
@@ -238,6 +238,14 @@ export class TemplateEngine {
|
|
|
238
238
|
featureName: string;
|
|
239
239
|
featureDescription?: string;
|
|
240
240
|
background?: string;
|
|
241
|
+
beforeAll?: string;
|
|
242
|
+
afterEach?: string;
|
|
243
|
+
afterAll?: string;
|
|
244
|
+
cleanupConfig?: string;
|
|
245
|
+
screenshotOnFailure?: boolean;
|
|
246
|
+
runtimeData?: boolean;
|
|
247
|
+
screenName?: string;
|
|
248
|
+
featureFileName?: string;
|
|
241
249
|
scenarios: string[];
|
|
242
250
|
authGroups?: Array<{ authRole?: string; scenarios: string[] }>;
|
|
243
251
|
singleAuthRole?: string;
|
|
@@ -251,6 +259,24 @@ export class TemplateEngine {
|
|
|
251
259
|
return this.render('before-each', data);
|
|
252
260
|
}
|
|
253
261
|
|
|
262
|
+
renderBeforeAll(data: {
|
|
263
|
+
steps: Array<{ comment?: string; code: string }>;
|
|
264
|
+
}): string {
|
|
265
|
+
return this.render('before-all', data);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
renderAfterEach(data: {
|
|
269
|
+
steps: Array<{ comment?: string; code: string }>;
|
|
270
|
+
}): string {
|
|
271
|
+
return this.render('after-each', data);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
renderAfterAll(data: {
|
|
275
|
+
steps: Array<{ comment?: string; code: string }>;
|
|
276
|
+
}): string {
|
|
277
|
+
return this.render('after-all', data);
|
|
278
|
+
}
|
|
279
|
+
|
|
254
280
|
renderScenario(data: {
|
|
255
281
|
scenarioName: string;
|
|
256
282
|
steps: Array<{ comment?: string; code: string }>;
|
|
@@ -5,8 +5,11 @@ import { readYamlIfExists } from '../../../utils/yaml-io';
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* DataResolver - Resolves data references to actual values
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
*
|
|
9
|
+
* Two modes:
|
|
10
|
+
* - Compile-time (default): resolves YAML values and bakes them into generated code
|
|
11
|
+
* - Runtime (runtimeMode=true): returns markers that post-processor converts to testData.get() calls
|
|
12
|
+
*
|
|
10
13
|
* Supports override files with priority:
|
|
11
14
|
* 1. .override.yaml (highest - user customizations)
|
|
12
15
|
* 2. -override.yaml (legacy - backward compat)
|
|
@@ -16,10 +19,12 @@ export class DataResolver {
|
|
|
16
19
|
private dataCache = new Map<string, any>();
|
|
17
20
|
private testDataDir: string;
|
|
18
21
|
private screenName?: string;
|
|
22
|
+
private runtimeMode: boolean;
|
|
19
23
|
|
|
20
|
-
constructor(testDataDir?: string, screenName?: string) {
|
|
24
|
+
constructor(testDataDir?: string, screenName?: string, runtimeMode: boolean = false) {
|
|
21
25
|
this.testDataDir = testDataDir || path.join(process.cwd(), 'qa', 'test-data');
|
|
22
26
|
this.screenName = screenName;
|
|
27
|
+
this.runtimeMode = runtimeMode;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
/**
|
|
@@ -36,34 +41,18 @@ export class DataResolver {
|
|
|
36
41
|
* @returns The resolved value
|
|
37
42
|
*/
|
|
38
43
|
resolveData(dataRef: string, featureName?: string): string {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (parts.length < 1) {
|
|
44
|
-
throw new Error(`Invalid data reference: ${dataRef}. Expected format: path.to.value`);
|
|
44
|
+
if (this.runtimeMode) {
|
|
45
|
+
this.validateDataRef(dataRef, featureName);
|
|
46
|
+
return DataResolver.encodeMarker(dataRef);
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
let fileName: string;
|
|
50
|
-
let valuePath: string[];
|
|
51
|
-
|
|
52
|
-
if (featureName) {
|
|
53
|
-
fileName = featureName;
|
|
54
|
-
valuePath = parts;
|
|
55
|
-
} else {
|
|
56
|
-
if (parts.length < 2) {
|
|
57
|
-
throw new Error(`Invalid data reference: ${dataRef}. Expected format: file.path.to.value or provide featureName`);
|
|
58
|
-
}
|
|
59
|
-
fileName = parts[0];
|
|
60
|
-
valuePath = parts.slice(1);
|
|
61
|
-
}
|
|
49
|
+
return this.resolveValue(dataRef, featureName);
|
|
50
|
+
}
|
|
62
51
|
|
|
63
|
-
|
|
52
|
+
private resolveValue(dataRef: string, featureName?: string): string {
|
|
53
|
+
const { fileName, valuePath } = this.parseDataRef(dataRef, featureName);
|
|
64
54
|
const data = this.loadDataFile(fileName);
|
|
65
55
|
|
|
66
|
-
// Navigate to the value
|
|
67
56
|
let current = data;
|
|
68
57
|
for (const key of valuePath) {
|
|
69
58
|
if (current && typeof current === 'object' && key in current) {
|
|
@@ -75,7 +64,6 @@ export class DataResolver {
|
|
|
75
64
|
}
|
|
76
65
|
}
|
|
77
66
|
|
|
78
|
-
// Return as string
|
|
79
67
|
if (typeof current === 'string') {
|
|
80
68
|
return current;
|
|
81
69
|
} else if (typeof current === 'number' || typeof current === 'boolean') {
|
|
@@ -87,6 +75,36 @@ export class DataResolver {
|
|
|
87
75
|
}
|
|
88
76
|
}
|
|
89
77
|
|
|
78
|
+
private validateDataRef(dataRef: string, featureName?: string): void {
|
|
79
|
+
// Same navigation as resolveValue — validates the key path exists at compile time
|
|
80
|
+
this.resolveValue(dataRef, featureName);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private parseDataRef(dataRef: string, featureName?: string): { fileName: string; valuePath: string[] } {
|
|
84
|
+
const parts = dataRef.split('.');
|
|
85
|
+
|
|
86
|
+
if (parts.length < 1) {
|
|
87
|
+
throw new Error(`Invalid data reference: ${dataRef}. Expected format: path.to.value`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (featureName) {
|
|
91
|
+
return { fileName: featureName, valuePath: parts };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (parts.length < 2) {
|
|
95
|
+
throw new Error(`Invalid data reference: ${dataRef}. Expected format: file.path.to.value or provide featureName`);
|
|
96
|
+
}
|
|
97
|
+
return { fileName: parts[0], valuePath: parts.slice(1) };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
static encodeMarker(ref: string): string {
|
|
101
|
+
return `__SUNGEN_TD_${ref.replace(/\./g, '_D_')}__`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
static decodeMarker(encoded: string): string {
|
|
105
|
+
return encoded.replace(/^__SUNGEN_TD_/, '').replace(/__$/, '').replace(/_D_/g, '.');
|
|
106
|
+
}
|
|
107
|
+
|
|
90
108
|
/**
|
|
91
109
|
* Load data file from disk (with caching)
|
|
92
110
|
* Searches new screen-based directory structure only
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const MARKER_PATTERN = /__SUNGEN_TD_([A-Za-z0-9_]+)__/;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Replace __SUNGEN_TD_ markers with testData.get() calls in generated code.
|
|
5
|
+
* Three passes: comments, string literals, then regex literals.
|
|
6
|
+
*/
|
|
7
|
+
export function transformToRuntimeData(code: string): string {
|
|
8
|
+
// Pass 0: Comments — replace markers in // comments with decoded key name
|
|
9
|
+
// Prevents Pass 2 from misinterpreting // comment markers as regex delimiters
|
|
10
|
+
code = code.replace(
|
|
11
|
+
/\/\/(.*)__SUNGEN_TD_([A-Za-z0-9_]+)__(.*)/g,
|
|
12
|
+
(_, before, enc, after) => `//${before}${decodeKey(enc)}${after}`
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
// Pass 1: String literal context — handles both whole-string and embedded markers
|
|
16
|
+
// 'marker' → testData.get('key')
|
|
17
|
+
// 'prefix__marker__suffix' → `prefix${testData.get('key')}suffix`
|
|
18
|
+
code = code.replace(
|
|
19
|
+
/(['"])((?:(?!\1).)*?)__SUNGEN_TD_([A-Za-z0-9_]+)__((?:(?!\1).)*?)\1/g,
|
|
20
|
+
(_, _quote, prefix, enc, suffix) => {
|
|
21
|
+
const key = decodeKey(enc);
|
|
22
|
+
if (!prefix && !suffix) {
|
|
23
|
+
return `testData.get('${key}')`;
|
|
24
|
+
}
|
|
25
|
+
return `\`${prefix}\${testData.get('${key}')}${suffix}\``;
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// Pass 2: Regex literal context — /prefix__marker__suffix/ → new RegExp(`...`)
|
|
30
|
+
// Only matches within a single line (no newlines in prefix/suffix)
|
|
31
|
+
code = code.replace(
|
|
32
|
+
/\/((?:[^/\\\n]|\\.)*?)__SUNGEN_TD_([A-Za-z0-9_]+)__((?:[^/\\\n]|\\.)*?)\/([gimsuy]*)/g,
|
|
33
|
+
(_, prefix, enc, suffix, flags) => {
|
|
34
|
+
const key = decodeKey(enc);
|
|
35
|
+
const ref = `testData.get('${key}')`;
|
|
36
|
+
const flagStr = flags ? `, '${flags}'` : '';
|
|
37
|
+
if (!prefix && !suffix) return `new RegExp(${ref}${flagStr})`;
|
|
38
|
+
return `new RegExp(\`${prefix}\${${ref}}${suffix}\`${flagStr})`;
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
return code;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function hasRuntimeDataMarkers(code: string): boolean {
|
|
46
|
+
return MARKER_PATTERN.test(code);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function decodeKey(encoded: string): string {
|
|
50
|
+
return encoded.replace(/_D_/g, '.');
|
|
51
|
+
}
|
|
@@ -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
|
}
|
package/src/generators/types.ts
CHANGED
|
@@ -170,6 +170,7 @@ export interface GenerateOptions {
|
|
|
170
170
|
all?: boolean; // Generate all screens
|
|
171
171
|
force?: boolean; // Force re-generation
|
|
172
172
|
skipCache?: boolean; // Skip cache lookup
|
|
173
|
+
runtimeData?: boolean; // Runtime data loading (default: true). False = compile-time hardcoding.
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
export interface ValidateOptions {
|
|
@@ -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 {
|