@sun-asterisk/sungen 2.0.2 → 2.1.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 (79) hide show
  1. package/README.md +2 -2
  2. package/dist/cli/commands/init.d.ts.map +1 -1
  3. package/dist/cli/commands/init.js +3 -2
  4. package/dist/cli/commands/init.js.map +1 -1
  5. package/dist/cli/index.js +1 -1
  6. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
  7. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
  8. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +1 -1
  9. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
  10. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +1 -1
  11. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +3 -3
  12. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
  13. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
  14. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs +1 -1
  15. package/dist/generators/test-generator/patterns/assertion-patterns.d.ts.map +1 -1
  16. package/dist/generators/test-generator/patterns/assertion-patterns.js +12 -13
  17. package/dist/generators/test-generator/patterns/assertion-patterns.js.map +1 -1
  18. package/dist/generators/test-generator/patterns/interaction-patterns.js +4 -4
  19. package/dist/generators/test-generator/patterns/interaction-patterns.js.map +1 -1
  20. package/dist/generators/test-generator/utils/selector-resolver.js +14 -14
  21. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  22. package/dist/orchestrator/project-initializer.d.ts +3 -6
  23. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  24. package/dist/orchestrator/project-initializer.js +62 -58
  25. package/dist/orchestrator/project-initializer.js.map +1 -1
  26. package/dist/orchestrator/screen-manager.d.ts.map +1 -1
  27. package/dist/orchestrator/screen-manager.js +0 -1
  28. package/dist/orchestrator/screen-manager.js.map +1 -1
  29. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +42 -0
  30. package/dist/orchestrator/templates/ai-instructions/claude-cmd-make-tc.md +60 -0
  31. package/dist/orchestrator/templates/ai-instructions/claude-cmd-make-test.md +59 -0
  32. package/dist/orchestrator/templates/ai-instructions/claude-config.md +90 -0
  33. package/dist/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +27 -0
  34. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +123 -0
  35. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +94 -0
  36. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +41 -0
  37. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-make-tc.md +59 -0
  38. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-make-test.md +58 -0
  39. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +90 -0
  40. package/dist/orchestrator/templates/ai-instructions/copilot-skill-error-mapping.md +27 -0
  41. package/dist/orchestrator/templates/ai-instructions/copilot-skill-gherkin-syntax.md +123 -0
  42. package/dist/orchestrator/templates/ai-instructions/copilot-skill-selector-keys.md +94 -0
  43. package/dist/orchestrator/templates/readme.md +43 -39
  44. package/docs/gherkin standards/gherkin-core-standard.md +141 -90
  45. package/docs/gherkin standards/gherkin-core-standard.vi.md +264 -54
  46. package/package.json +2 -2
  47. package/src/cli/commands/init.ts +3 -2
  48. package/src/cli/index.ts +1 -1
  49. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
  50. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
  51. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +1 -1
  52. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
  53. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +1 -1
  54. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +3 -3
  55. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
  56. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
  57. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs +1 -1
  58. package/src/generators/test-generator/patterns/assertion-patterns.ts +12 -13
  59. package/src/generators/test-generator/patterns/interaction-patterns.ts +4 -4
  60. package/src/generators/test-generator/utils/selector-resolver.ts +15 -15
  61. package/src/orchestrator/project-initializer.ts +74 -58
  62. package/src/orchestrator/screen-manager.ts +0 -1
  63. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +42 -0
  64. package/src/orchestrator/templates/ai-instructions/claude-cmd-make-tc.md +60 -0
  65. package/src/orchestrator/templates/ai-instructions/claude-cmd-make-test.md +59 -0
  66. package/src/orchestrator/templates/ai-instructions/claude-config.md +90 -0
  67. package/src/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +27 -0
  68. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +123 -0
  69. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +94 -0
  70. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +41 -0
  71. package/src/orchestrator/templates/ai-instructions/copilot-cmd-make-tc.md +59 -0
  72. package/src/orchestrator/templates/ai-instructions/copilot-cmd-make-test.md +58 -0
  73. package/src/orchestrator/templates/ai-instructions/copilot-config.md +90 -0
  74. package/src/orchestrator/templates/ai-instructions/copilot-skill-error-mapping.md +27 -0
  75. package/src/orchestrator/templates/ai-instructions/copilot-skill-gherkin-syntax.md +123 -0
  76. package/src/orchestrator/templates/ai-instructions/copilot-skill-selector-keys.md +94 -0
  77. package/src/orchestrator/templates/readme.md +43 -39
  78. package/dist/orchestrator/templates/ai-rules.md +0 -189
  79. package/src/orchestrator/templates/ai-rules.md +0 -189
@@ -109,7 +109,7 @@ export const assertionPatterns: StepPattern[] = [
109
109
 
110
110
  if (resolved.strategy === 'locator') {
111
111
  const code = context.templateEngine.renderStep('hidden-with-filter-assertion', {
112
- ...resolved, dataValue, nth: resolved.nth || 0,
112
+ ...resolved, dataValue, nth: resolved.nth,
113
113
  });
114
114
  return { code, comment: `Assert ${step.selectorRef} hidden with ${step.dataRef}` };
115
115
  }
@@ -127,7 +127,7 @@ export const assertionPatterns: StepPattern[] = [
127
127
  role: resolved.role,
128
128
  name: resolved.name && resolved.name.trim() ? resolved.name : undefined,
129
129
  dataValue,
130
- nth: resolved.nth || 0,
130
+ nth: resolved.nth,
131
131
  });
132
132
  return { code, comment: `Assert ${step.selectorRef} hidden with ${step.dataRef}` };
133
133
  }
@@ -135,7 +135,7 @@ export const assertionPatterns: StepPattern[] = [
135
135
  const selectorValue = resolved.value || '';
136
136
  if (selectorValue && selectorValue.trim()) {
137
137
  const code = context.templateEngine.renderStep('hidden-with-filter-assertion', {
138
- ...resolved, dataValue, nth: resolved.nth || 0,
138
+ ...resolved, dataValue, nth: resolved.nth,
139
139
  });
140
140
  return { code, comment: `Assert ${step.selectorRef} hidden with ${step.dataRef}` };
141
141
  }
@@ -144,7 +144,7 @@ export const assertionPatterns: StepPattern[] = [
144
144
  selectorRef: step.selectorRef,
145
145
  selectorValue: resolved.value || '',
146
146
  dataValue,
147
- nth: resolved.nth || 0,
147
+ nth: resolved.nth,
148
148
  });
149
149
  return { code, comment: `Assert ${step.selectorRef} hidden with ${step.dataRef}` };
150
150
  },
@@ -181,7 +181,7 @@ export const assertionPatterns: StepPattern[] = [
181
181
 
182
182
  if (resolved.strategy === 'locator') {
183
183
  const code = context.templateEngine.renderStep('disabled-with-filter-assertion', {
184
- ...resolved, dataValue, nth: resolved.nth || 0,
184
+ ...resolved, dataValue, nth: resolved.nth,
185
185
  });
186
186
  return { code, comment: `Assert ${step.selectorRef} disabled with ${step.dataRef}` };
187
187
  }
@@ -191,7 +191,7 @@ export const assertionPatterns: StepPattern[] = [
191
191
  role: resolved.role,
192
192
  name: resolved.name && resolved.name.trim() ? resolved.name : undefined,
193
193
  dataValue,
194
- nth: resolved.nth || 0,
194
+ nth: resolved.nth,
195
195
  });
196
196
  return { code, comment: `Assert ${step.selectorRef} disabled with ${step.dataRef}` };
197
197
  }
@@ -199,7 +199,7 @@ export const assertionPatterns: StepPattern[] = [
199
199
  const selectorValue = resolved.value || '';
200
200
  if (selectorValue && selectorValue.trim()) {
201
201
  const code = context.templateEngine.renderStep('disabled-with-filter-assertion', {
202
- ...resolved, dataValue, nth: resolved.nth || 0,
202
+ ...resolved, dataValue, nth: resolved.nth,
203
203
  });
204
204
  return { code, comment: `Assert ${step.selectorRef} disabled with ${step.dataRef}` };
205
205
  }
@@ -208,7 +208,7 @@ export const assertionPatterns: StepPattern[] = [
208
208
  selectorRef: step.selectorRef,
209
209
  selectorValue: resolved.value || '',
210
210
  dataValue,
211
- nth: resolved.nth || 0,
211
+ nth: resolved.nth,
212
212
  });
213
213
  return { code, comment: `Assert ${step.selectorRef} disabled with ${step.dataRef}` };
214
214
  },
@@ -284,7 +284,7 @@ export const assertionPatterns: StepPattern[] = [
284
284
  const code = context.templateEngine.renderStep('visible-with-locator-variable-assertion', {
285
285
  ...resolved,
286
286
  dataValue,
287
- nth: resolved.nth || 0,
287
+ nth: resolved.nth,
288
288
  });
289
289
  return {
290
290
  code,
@@ -313,7 +313,7 @@ export const assertionPatterns: StepPattern[] = [
313
313
  name: isImgRole ? (hasName ? resolved.name : dataValue) : (hasName ? resolved.name : undefined),
314
314
  dataValue: isImgRole ? undefined : dataValue,
315
315
  exact: resolved.exact || false,
316
- nth: resolved.nth || 0,
316
+ nth: resolved.nth,
317
317
  });
318
318
  return {
319
319
  code,
@@ -331,7 +331,7 @@ export const assertionPatterns: StepPattern[] = [
331
331
  value: selectorValue,
332
332
  dataValue,
333
333
  dataRef: step.dataRef,
334
- nth: resolved.nth || 0,
334
+ nth: resolved.nth,
335
335
  exact: resolved.exact || false,
336
336
  });
337
337
  return {
@@ -344,7 +344,7 @@ export const assertionPatterns: StepPattern[] = [
344
344
  selectorRef: step.selectorRef,
345
345
  selectorValue: resolved.value || '',
346
346
  dataValue,
347
- nth: resolved.nth || 0,
347
+ nth: resolved.nth,
348
348
  });
349
349
  return {
350
350
  code,
@@ -523,7 +523,6 @@ export const assertionPatterns: StepPattern[] = [
523
523
  data: {
524
524
  strategy: 'text',
525
525
  value: dataValue,
526
- nth: 0,
527
526
  },
528
527
  comment: `Assert ${step.dataRef} is visible`,
529
528
  };
@@ -39,16 +39,16 @@ export const interactionPatterns: StepPattern[] = [
39
39
  },
40
40
  resolver: (step, context) => {
41
41
  let selectorValue = '';
42
- let nth = 0;
42
+ let nth: number | undefined;
43
43
  try {
44
44
  const resolved = context.selectorResolver.resolveSelector(
45
45
  step.selectorRef!, context.featureName, step.elementType, step.nth
46
46
  );
47
47
  selectorValue = resolved.value || '';
48
- nth = resolved.nth || 0;
48
+ nth = resolved.nth;
49
49
  } catch (error) {
50
50
  selectorValue = '';
51
- nth = 0;
51
+ nth = undefined;
52
52
  }
53
53
 
54
54
  let dataValue: string;
@@ -174,7 +174,7 @@ export const interactionPatterns: StepPattern[] = [
174
174
 
175
175
  return {
176
176
  templateName: 'click-action',
177
- data: { strategy: 'text', value, nth: 0 },
177
+ data: { strategy: 'text', value },
178
178
  comment: `Click ${step.dataRef}`,
179
179
  };
180
180
  },
@@ -196,7 +196,7 @@ interface SelectorEntry {
196
196
  type?: SelectorType; // Locator strategy
197
197
  value?: string; // Natural language value for the locator (e.g., 'button', 'link' for role)
198
198
  name?: string; // Accessible name for role-based selectors (e.g., 'Login', 'Submit')
199
- nth?: number; // Element index (0 = no index, 1+ = append .nth())
199
+ nth?: number; // Element index (undefined = no .nth(), 0+ = .nth(N))
200
200
  exact?: boolean; // Whether to use exact matching (default: false)
201
201
  inputMethod?: string; // 'pressSequentially' for contenteditable/rich-text elements
202
202
 
@@ -375,19 +375,19 @@ export class SelectorResolver {
375
375
  const normalized = SelectorResolver.normalizeElementType(elementType);
376
376
 
377
377
  const strategyMap: Record<string, () => ResolvedSelector> = {
378
- 'img': () => ({ strategy: 'role', role: 'img', name: label, value: 'img', nth: 0 }),
379
- 'button': () => ({ strategy: 'role', role: 'button', name: label, value: 'button', nth: 0 }),
380
- 'link': () => ({ strategy: 'role', role: 'link', name: label, value: 'link', nth: 0 }),
381
- 'text': () => ({ strategy: 'text', value: label, nth: 0 }),
382
- 'field': () => ({ strategy: 'placeholder', value: label, nth: 0 }),
383
- 'checkbox': () => ({ strategy: 'role', role: 'checkbox', name: label, value: 'checkbox', nth: 0 }),
384
- 'radio': () => ({ strategy: 'role', role: 'radio', name: label, value: 'radio', nth: 0 }),
385
- 'row': () => ({ strategy: 'role', role: 'row', name: label, value: 'row', nth: 0 }),
386
- 'table': () => ({ strategy: 'role', role: 'table', name: label, value: 'table', nth: 0 }),
387
- 'columnheader': () => ({ strategy: 'role', role: 'columnheader', name: label, value: 'columnheader', nth: 0 }),
388
- 'heading': () => ({ strategy: 'role', role: 'heading', name: label, value: 'heading', nth: 0 }),
389
- 'list': () => ({ strategy: 'role', role: 'list', name: label, value: 'list', nth: 0 }),
390
- 'listitem': () => ({ strategy: 'role', role: 'listitem', value: 'listitem', nth: 0 }),
378
+ 'img': () => ({ strategy: 'role', role: 'img', name: label, value: 'img' }),
379
+ 'button': () => ({ strategy: 'role', role: 'button', name: label, value: 'button' }),
380
+ 'link': () => ({ strategy: 'role', role: 'link', name: label, value: 'link' }),
381
+ 'text': () => ({ strategy: 'text', value: label }),
382
+ 'field': () => ({ strategy: 'placeholder', value: label }),
383
+ 'checkbox': () => ({ strategy: 'role', role: 'checkbox', name: label, value: 'checkbox' }),
384
+ 'radio': () => ({ strategy: 'role', role: 'radio', name: label, value: 'radio' }),
385
+ 'row': () => ({ strategy: 'role', role: 'row', name: label, value: 'row' }),
386
+ 'table': () => ({ strategy: 'role', role: 'table', name: label, value: 'table' }),
387
+ 'columnheader': () => ({ strategy: 'role', role: 'columnheader', name: label, value: 'columnheader' }),
388
+ 'heading': () => ({ strategy: 'role', role: 'heading', name: label, value: 'heading' }),
389
+ 'list': () => ({ strategy: 'role', role: 'list', name: label, value: 'list' }),
390
+ 'listitem': () => ({ strategy: 'role', role: 'listitem', value: 'listitem' }),
391
391
  };
392
392
 
393
393
  const factory = strategyMap[normalized];
@@ -549,7 +549,7 @@ export class SelectorResolver {
549
549
  const value = entry.value !== undefined && entry.value !== null ? entry.value : originalLabel;
550
550
  // Check if name exists in entry (even if empty string), use it; otherwise use originalLabel
551
551
  const name = entry.name !== undefined && entry.name !== null ? entry.name : originalLabel;
552
- const nth = entry.nth || 0;
552
+ const nth = entry.nth !== undefined && entry.nth !== null ? entry.nth : undefined;
553
553
  const exact = entry.exact === true || entry.match === 'exact';
554
554
  const inputMethod = entry.inputMethod;
555
555
 
@@ -5,6 +5,7 @@
5
5
 
6
6
  import * as fs from 'fs';
7
7
  import * as path from 'path';
8
+ import { execSync } from 'child_process';
8
9
 
9
10
  export class ProjectInitializer {
10
11
  private baseCwd: string;
@@ -12,7 +13,7 @@ export class ProjectInitializer {
12
13
  private createdItems: string[] = [];
13
14
  private skippedItems: string[] = [];
14
15
 
15
- constructor() {
16
+ constructor(private baseUrl: string) {
16
17
  this.baseCwd = process.cwd();
17
18
  this.cwd = this.baseCwd;
18
19
  }
@@ -37,6 +38,9 @@ export class ProjectInitializer {
37
38
  // Create directories
38
39
  this.createDirectories();
39
40
 
41
+ // Ensure package.json and install Playwright
42
+ await this.setupDependencies();
43
+
40
44
  // Create playwright config if doesn't exist
41
45
  this.createPlaywrightConfig();
42
46
 
@@ -99,7 +103,8 @@ export class ProjectInitializer {
99
103
  return;
100
104
  }
101
105
 
102
- const configContent = this.getPlaywrightConfigTemplate();
106
+ const configContent = this.getPlaywrightConfigTemplate()
107
+ .replace('https://example.com', this.baseUrl);
103
108
  fs.writeFileSync(configPath, configContent, 'utf-8');
104
109
  this.createdItems.push('playwright.config.ts');
105
110
  }
@@ -174,9 +179,6 @@ export class ProjectInitializer {
174
179
  });
175
180
  }
176
181
 
177
- // Check for Playwright installation
178
- this.checkDependencies();
179
-
180
182
  console.log('\nNext steps:');
181
183
  let stepIndex = 1;
182
184
  if (projectName) {
@@ -192,48 +194,54 @@ export class ProjectInitializer {
192
194
  * Create AI rules files for GitHub Copilot and Claude Code
193
195
  */
194
196
  private createAIRules(): void {
195
- const rulesContent = this.getAIRulesContent();
196
-
197
- // GitHub Copilot: .github/copilot-instructions.md
198
- const githubDir = path.join(this.cwd, '.github');
199
- const copilotPath = path.join(githubDir, 'copilot-instructions.md');
200
- if (!fs.existsSync(copilotPath)) {
201
- if (!fs.existsSync(githubDir)) {
202
- fs.mkdirSync(githubDir, { recursive: true });
203
- }
204
- fs.writeFileSync(copilotPath, rulesContent, 'utf-8');
205
- this.createdItems.push('.github/copilot-instructions.md');
206
- } else {
207
- this.skippedItems.push('.github/copilot-instructions.md');
208
- }
197
+ const aiTemplateDir = path.join(__dirname, 'templates', 'ai-instructions');
198
+
199
+ // File mapping: [templateFile, outputPath]
200
+ const fileMapping: [string, string][] = [
201
+ // Config
202
+ ['claude-config.md', 'CLAUDE.md'],
203
+ ['copilot-config.md', '.github/copilot-instructions.md'],
204
+
205
+ // Commands — Claude Code
206
+ ['claude-cmd-add-screen.md', '.claude/commands/sungen/add-screen.md'],
207
+ ['claude-cmd-make-tc.md', '.claude/commands/sungen/make-tc.md'],
208
+ ['claude-cmd-make-test.md', '.claude/commands/sungen/make-test.md'],
209
+
210
+ // Commands — GitHub Copilot
211
+ ['copilot-cmd-add-screen.md', '.github/prompts/sungen-add-screen.prompt.md'],
212
+ ['copilot-cmd-make-tc.md', '.github/prompts/sungen-make-tc.prompt.md'],
213
+ ['copilot-cmd-make-test.md', '.github/prompts/sungen-make-test.prompt.md'],
214
+
215
+ // Skills — Claude Code
216
+ ['claude-skill-gherkin-syntax.md', '.claude/skills/sungen-gherkin-syntax/SKILL.md'],
217
+ ['claude-skill-selector-keys.md', '.claude/skills/sungen-selector-keys/SKILL.md'],
218
+ ['claude-skill-error-mapping.md', '.claude/skills/sungen-error-mapping/SKILL.md'],
219
+
220
+ // Skills — GitHub Copilot
221
+ ['copilot-skill-gherkin-syntax.md', '.github/prompts/sungen-gherkin-syntax.prompt.md'],
222
+ ['copilot-skill-selector-keys.md', '.github/prompts/sungen-selector-keys.prompt.md'],
223
+ ['copilot-skill-error-mapping.md', '.github/prompts/sungen-error-mapping.prompt.md'],
224
+ ];
209
225
 
210
- // Claude Code: CLAUDE.md
211
- const claudePath = path.join(this.cwd, 'CLAUDE.md');
212
- if (!fs.existsSync(claudePath)) {
213
- fs.writeFileSync(claudePath, rulesContent, 'utf-8');
214
- this.createdItems.push('CLAUDE.md');
215
- } else {
216
- this.skippedItems.push('CLAUDE.md');
217
- }
226
+ for (const [templateFile, outputRelPath] of fileMapping) {
227
+ const outputPath = path.join(this.cwd, outputRelPath);
218
228
 
219
- // Gherkin Dictionary: docs/gherkin-dictionary.md
220
- const docsDir = path.join(this.cwd, 'docs');
221
- const dictPath = path.join(docsDir, 'gherkin-dictionary.md');
222
- if (!fs.existsSync(dictPath)) {
223
- // Copy from sungen package if available, otherwise generate stub
224
- const packageDictPath = path.join(__dirname, '..', '..', 'docs', 'gherkin-dictionary.md');
225
- if (!fs.existsSync(docsDir)) {
226
- fs.mkdirSync(docsDir, { recursive: true });
229
+ if (fs.existsSync(outputPath)) {
230
+ this.skippedItems.push(outputRelPath);
231
+ continue;
227
232
  }
228
- if (fs.existsSync(packageDictPath)) {
229
- fs.copyFileSync(packageDictPath, dictPath);
230
- } else {
231
- fs.writeFileSync(dictPath, '# Sungen Gherkin Dictionary\n\nSee https://github.com/sun-asterisk/sungen for the full dictionary.\n', 'utf-8');
233
+
234
+ const outputDir = path.dirname(outputPath);
235
+ if (!fs.existsSync(outputDir)) {
236
+ fs.mkdirSync(outputDir, { recursive: true });
232
237
  }
233
- this.createdItems.push('docs/gherkin-dictionary.md');
234
- } else {
235
- this.skippedItems.push('docs/gherkin-dictionary.md');
238
+
239
+ const templatePath = path.join(aiTemplateDir, templateFile);
240
+ const content = fs.readFileSync(templatePath, 'utf-8');
241
+ fs.writeFileSync(outputPath, content, 'utf-8');
242
+ this.createdItems.push(outputRelPath);
236
243
  }
244
+
237
245
  }
238
246
 
239
247
  /**
@@ -244,30 +252,38 @@ export class ProjectInitializer {
244
252
  return fs.readFileSync(templatePath, 'utf-8');
245
253
  }
246
254
 
247
- /**
248
- * Get AI rules content (shared between Copilot and Claude)
249
- */
250
- private getAIRulesContent(): string {
251
- return this.readTemplate('ai-rules.md');
252
- }
253
-
254
255
  /**
255
256
  * Check for required dependencies
256
257
  */
257
- private checkDependencies(): void {
258
+ private async setupDependencies(): Promise<void> {
259
+ const packageJsonPath = path.join(this.cwd, 'package.json');
260
+ const execOpts = { cwd: this.cwd, stdio: 'inherit' as const };
261
+
262
+ // Initialize package.json if it doesn't exist
263
+ if (!fs.existsSync(packageJsonPath)) {
264
+ console.log('📦 No package.json found. Initializing...\n');
265
+ execSync('npm init -y', execOpts);
266
+ this.createdItems.push('package.json');
267
+ }
268
+
269
+ // Check if @playwright/test is already installed
258
270
  try {
259
- const packageJsonPath = path.join(this.cwd, 'package.json');
260
271
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
261
272
  const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
262
-
263
- if (!deps['@playwright/test']) {
264
- console.log('\n⚠️ Playwright not detected. Install with:');
265
- console.log(' npm install -D @playwright/test');
266
- console.log(' npx playwright install');
273
+ if (deps['@playwright/test']) {
274
+ console.log('@playwright/test already installed\n');
275
+ return;
267
276
  }
268
- } catch (error) {
269
- // Ignore if can't read package.json
277
+ } catch {
278
+ // package.json just created, proceed with install
270
279
  }
280
+
281
+ // Install Playwright
282
+ console.log('📦 Installing @playwright/test...\n');
283
+ execSync('npm install -D @playwright/test', execOpts);
284
+
285
+ console.log('\n🎭 Installing Playwright browsers...\n');
286
+ execSync('npx playwright install', execOpts);
271
287
  }
272
288
 
273
289
  /**
@@ -97,7 +97,6 @@ export class ScreenManager {
97
97
  `${screenName}:`,
98
98
  ` type: 'page'`,
99
99
  ` value: '${pagePath}'`,
100
- ` nth: 0`,
101
100
  ``,
102
101
  ].join('\n'), 'utf-8');
103
102
  }
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: add-screen
3
+ description: 'Add a new Sungen screen — scaffolds directories and delegates to make-tc for test case creation'
4
+ argument-hint: [screen-name] [url-path]
5
+ allowed-tools: Read, Grep, Bash, Glob
6
+ ---
7
+
8
+ You are adding a new Sungen screen for test generation.
9
+
10
+ ## Parameters
11
+
12
+ Parse from `$ARGUMENTS`:
13
+ - **screen** — screen name (e.g., `login`, `dashboard`, `settings`)
14
+ - **path** — URL path (e.g., `/login`, `/dashboard`, `/settings`)
15
+
16
+ If **screen** is missing, ask: "What is the screen name? (e.g., `login`, `dashboard`)"
17
+ If **path** is missing, ask: "What is the URL path? (e.g., `/login`, `/dashboard`)"
18
+
19
+ ## Steps
20
+
21
+ ### 1. Scaffold the screen
22
+
23
+ Run:
24
+ ```bash
25
+ sungen add --screen <screen> --path <path>
26
+ ```
27
+
28
+ ### 2. Create test cases
29
+
30
+ Ask the user: "Would you like to create test cases now?"
31
+
32
+ If yes, delegate to `/sungen:make-tc <screen>` to handle:
33
+ - Exploring the live page or analyzing static designs
34
+ - Gathering test viewpoints (UI/UX, Validation, Logic, Security)
35
+ - Generating the 3 files (feature, selectors, test-data)
36
+
37
+ ### 3. Confirm
38
+
39
+ Tell the user what was created and next steps:
40
+ - If the page requires authentication before exploring via browser, read `baseURL` from `playwright.config.ts` and tell the user to manually run: `sungen makeauth <role> --url <baseURL>` (e.g., `sungen makeauth admin --url http://localhost:3000`). **Do NOT execute this command yourself.**
41
+ - Edit the generated files as needed
42
+ - Run `sungen generate --screen <screen>` to compile to Playwright `.spec.ts`
@@ -0,0 +1,60 @@
1
+ ---
2
+ name: make-tc
3
+ description: 'Create test cases for a Sungen screen — gathers viewpoints, explores live page or static designs, generates feature/selectors/test-data files'
4
+ argument-hint: [screen-name]
5
+ allowed-tools: Read, Grep, Bash, Glob
6
+ ---
7
+
8
+ ## Role
9
+
10
+ You are a **Senior QA Engineer** specialized in test case design. You structure test cases by viewpoint categories and translate UI into Gherkin test cases following the `sungen-gherkin-syntax` and `sungen-selector-keys` skills.
11
+
12
+ ## Parameters
13
+
14
+ Parse from `$ARGUMENTS`:
15
+ - **screen** — screen name (e.g., `login`, `dashboard`)
16
+
17
+ If missing, ask the user.
18
+
19
+ ## Steps
20
+
21
+ ### 1. Verify screen exists
22
+
23
+ Check `qa/screens/<screen>/` exists. If not, tell user to run `/sungen:add-screen` first.
24
+
25
+ ### 2. Determine source mode
26
+
27
+ Ask: "Do you have a **live page** or **static designs** (screenshots, Figma, images)?"
28
+
29
+ **Live page →** explore via browser (see CLAUDE.md for Playwright MCP rules). If auth needed, check `specs/.auth/<role>.json` exists — if not, read `baseURL` from `playwright.config.ts` and tell the user to manually run: `sungen makeauth <role> --url <baseURL>` (e.g., `sungen makeauth admin --url http://localhost:3000`). **Do NOT execute `sungen makeauth` yourself.** Wait for the user to confirm auth is ready before proceeding.
30
+
31
+ **Static designs →** ask user to provide screenshot paths, Figma exports, or UI descriptions. Note: selectors will be best-guess until live page is available.
32
+
33
+ Present discovered elements to user, then proceed.
34
+
35
+ ### 3. Gather test viewpoints
36
+
37
+ | VP Category | Description |
38
+ |---|---|
39
+ | **UI/UX** | Default screen appearance, static elements, default states |
40
+ | **Validation** | Field/form validation, error messages |
41
+ | **Logic** | Business logic, happy paths, redirects |
42
+ | **Security** | Auth guards, permission checks |
43
+
44
+ For each viewpoint, gather: **UI Target**, **Test Viewpoint**, **Expected Result**.
45
+
46
+ User can provide all at once as a table:
47
+ ```
48
+ | VP Category | UI Target | Test Viewpoint | Expected Result |
49
+ ```
50
+
51
+ ### 4. Generate files
52
+
53
+ Generate the 3 files following `sungen-gherkin-syntax` and `sungen-selector-keys` skill rules:
54
+ - `qa/screens/<screen>/features/<screen>.feature` — one Scenario per viewpoint
55
+ - `qa/screens/<screen>/selectors/<screen>.yaml`
56
+ - `qa/screens/<screen>/test-data/<screen>.yaml`
57
+
58
+ ### 5. Confirm
59
+
60
+ Show what was generated. Next: `sungen generate --screen <screen>`
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: make-test
3
+ description: 'Compile and run Playwright tests for a Sungen screen — auto-fixes selectors on failure, falls back to AI .spec.ts fix after 5 attempts'
4
+ argument-hint: [screen-name]
5
+ allowed-tools: Read, Grep, Bash, Glob, Edit
6
+ ---
7
+
8
+ ## Role
9
+
10
+ You are a **Senior Developer** specialized in Playwright test debugging. You diagnose test failures, fix selectors/test-data, and patch `.spec.ts` when needed. Use the `sungen-error-mapping` and `sungen-selector-keys` skills for error diagnosis and key fixes.
11
+
12
+ ## Parameters
13
+
14
+ Parse from `$ARGUMENTS`:
15
+ - **screen** — screen name (e.g., `login`, `dashboard`)
16
+
17
+ If missing, ask the user.
18
+
19
+ ## Steps
20
+
21
+ ### 1. Verify screen exists
22
+
23
+ Check `qa/screens/<screen>/` has all 3 source files. If not, tell user to run `/sungen:add-screen` and `/sungen:make-tc` first.
24
+
25
+ ### 2. Compile
26
+
27
+ ```bash
28
+ sungen generate --screen <screen>
29
+ ```
30
+
31
+ If fails, fix source files per `sungen-error-mapping` skill and retry.
32
+
33
+ ### 3. Run tests
34
+
35
+ ```bash
36
+ npx playwright test specs/screens/<screen>/<screen>.spec.ts
37
+ ```
38
+
39
+ If pass → Step 5. If fail → Step 4.
40
+
41
+ ### 4. Fix and retry (max 5 attempts)
42
+
43
+ Per attempt:
44
+ 1. Read Playwright error output
45
+ 2. Map error to fix using `sungen-error-mapping` skill
46
+ 3. Fix `selectors.yaml` or `test-data.yaml`
47
+ 4. Recompile: `sungen generate --screen <screen>`
48
+ 5. Retest
49
+
50
+ ### 5. Fallback — AI fix .spec.ts
51
+
52
+ After 5 failed attempts, ask user:
53
+ > Tests still failing. Would you like me to directly fix the `.spec.ts` file?
54
+
55
+ If yes: read `.spec.ts`, fix locators/assertions, mark with `// AI-fixed: <reason>`
56
+
57
+ ### 6. Confirm
58
+
59
+ Show: pass/fail, attempt count, files changed.
@@ -0,0 +1,90 @@
1
+ # Sungen AI Rules
2
+
3
+ You generate 3 files for sungen — a Gherkin compiler that produces Playwright tests.
4
+ **You do NOT write Playwright code.** You only write `.feature`, `selectors.yaml`, and `test-data.yaml`.
5
+
6
+ For Gherkin syntax, selector types, and YAML rules, the `sungen-gherkin-syntax` skill is auto-loaded when needed.
7
+ For error diagnosis, the `sungen-error-mapping` skill is auto-loaded when needed.
8
+
9
+ ## Complete Example
10
+
11
+ Given a login page, here are the 3 files you generate:
12
+
13
+ **qa/screens/login/features/login.feature**
14
+ ```gherkin
15
+ @auth:admin
16
+ Feature: Login Screen
17
+
18
+ Scenario: Successful login
19
+ Given User is on [login] page
20
+ When User fill [Email] field with {{email}}
21
+ And User fill [Password] field with {{password}}
22
+ And User click [Submit] button
23
+ Then User see [Welcome] heading with {{welcome_text}}
24
+ And User see [Dashboard] link is visible
25
+ ```
26
+
27
+ **qa/screens/login/selectors/login.yaml**
28
+ ```yaml
29
+ login:
30
+ type: 'page'
31
+ value: '/login'
32
+
33
+ email:
34
+ type: 'placeholder'
35
+ value: 'Enter your email'
36
+
37
+ password:
38
+ type: 'placeholder'
39
+ value: 'Enter your password'
40
+
41
+ submit:
42
+ type: 'role'
43
+ value: 'button'
44
+ name: 'Submit'
45
+
46
+ welcome:
47
+ type: 'role'
48
+ value: 'heading'
49
+ name: 'Welcome'
50
+
51
+ dashboard:
52
+ type: 'role'
53
+ value: 'link'
54
+ name: 'Dashboard'
55
+ ```
56
+
57
+ **qa/screens/login/test-data/login.yaml**
58
+ ```yaml
59
+ email: 'admin@example.com'
60
+ password: 'password123'
61
+ welcome_text: 'Welcome back'
62
+ ```
63
+
64
+ ## Using Playwright MCP to explore pages
65
+
66
+ When exploring a page to generate test files:
67
+ 1. Read `playwright.config.ts` for `baseURL`
68
+ 2. Use `browser_navigate` to open `baseURL + /path`
69
+ 3. Use `browser_snapshot` to see all elements
70
+ 4. Generate the 3 files from the snapshot
71
+
72
+ **NEVER use `browser_run_code`.** It fails with `require is not defined`.
73
+ Only use: `browser_navigate`, `browser_snapshot`, `browser_click`, `browser_fill_form`, `browser_evaluate`.
74
+
75
+ To browse authenticated pages via MCP:
76
+ 1. Check if `specs/.auth/<role>.json` exists
77
+ 2. If not, read `baseURL` from `playwright.config.ts` and tell the user to manually run: `sungen makeauth <role> --url <baseURL>`. **Do NOT execute `sungen makeauth` yourself.** Wait for user confirmation.
78
+ 3. Read `specs/.auth/<role>.json`
79
+ 4. `browser_navigate` to the page
80
+ 5. `browser_evaluate` to inject localStorage and cookies from the JSON
81
+ 6. `browser_navigate` again to reload with auth
82
+
83
+ ## Commands
84
+
85
+ ```bash
86
+ sungen add --screen <name> --path <url-path> # Create screen
87
+ sungen generate --screen <name> # Compile to .spec.ts
88
+ sungen generate --all # Compile all
89
+ sungen makeauth <role> # Capture auth state
90
+ ```