@sun-asterisk/sungen 2.5.2 → 2.6.1

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 (187) hide show
  1. package/dist/cli/commands/add-flow.d.ts +3 -0
  2. package/dist/cli/commands/add-flow.d.ts.map +1 -0
  3. package/dist/cli/commands/add-flow.js +27 -0
  4. package/dist/cli/commands/add-flow.js.map +1 -0
  5. package/dist/cli/commands/delivery.d.ts.map +1 -1
  6. package/dist/cli/commands/delivery.js +95 -60
  7. package/dist/cli/commands/delivery.js.map +1 -1
  8. package/dist/cli/commands/generate.d.ts.map +1 -1
  9. package/dist/cli/commands/generate.js +38 -6
  10. package/dist/cli/commands/generate.js.map +1 -1
  11. package/dist/cli/index.js +3 -1
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +16 -0
  14. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  15. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +3 -0
  16. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  17. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  18. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -2
  19. package/dist/generators/test-generator/adapters/playwright/templates/scenario.hbs +20 -1
  20. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/alert-accept-action.hbs +1 -1
  21. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/alert-dismiss-action.hbs +1 -1
  22. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/alert-fill-action.hbs +1 -1
  23. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/alert-text-assertion.hbs +2 -2
  24. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +1 -0
  25. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs +2 -1
  26. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +1 -2
  27. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-timeout.hbs +1 -1
  28. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +84 -4
  29. package/dist/generators/test-generator/code-generator.d.ts +2 -0
  30. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  31. package/dist/generators/test-generator/code-generator.js +105 -17
  32. package/dist/generators/test-generator/code-generator.js.map +1 -1
  33. package/dist/generators/test-generator/patterns/interaction-patterns.d.ts.map +1 -1
  34. package/dist/generators/test-generator/patterns/interaction-patterns.js +22 -3
  35. package/dist/generators/test-generator/patterns/interaction-patterns.js.map +1 -1
  36. package/dist/generators/test-generator/patterns/navigation-patterns.d.ts.map +1 -1
  37. package/dist/generators/test-generator/patterns/navigation-patterns.js +8 -3
  38. package/dist/generators/test-generator/patterns/navigation-patterns.js.map +1 -1
  39. package/dist/generators/test-generator/step-mapper.d.ts +4 -0
  40. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  41. package/dist/generators/test-generator/step-mapper.js +7 -0
  42. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  43. package/dist/generators/test-generator/template-engine.d.ts +14 -0
  44. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  45. package/dist/generators/test-generator/template-engine.js +1 -1
  46. package/dist/generators/test-generator/template-engine.js.map +1 -1
  47. package/dist/generators/test-generator/utils/data-resolver.d.ts +3 -20
  48. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  49. package/dist/generators/test-generator/utils/data-resolver.js +23 -66
  50. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  51. package/dist/generators/test-generator/utils/selector-resolver.d.ts +2 -6
  52. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  53. package/dist/generators/test-generator/utils/selector-resolver.js +18 -80
  54. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  55. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  56. package/dist/orchestrator/ai-rules-updater.js +4 -0
  57. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  58. package/dist/orchestrator/flow-manager.d.ts +22 -0
  59. package/dist/orchestrator/flow-manager.d.ts.map +1 -0
  60. package/dist/orchestrator/flow-manager.js +251 -0
  61. package/dist/orchestrator/flow-manager.js.map +1 -0
  62. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  63. package/dist/orchestrator/project-initializer.js +1 -0
  64. package/dist/orchestrator/project-initializer.js.map +1 -1
  65. package/dist/orchestrator/screen-manager.d.ts.map +1 -1
  66. package/dist/orchestrator/screen-manager.js +3 -1
  67. package/dist/orchestrator/screen-manager.js.map +1 -1
  68. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-flow.md +88 -0
  69. package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +11 -8
  70. package/dist/orchestrator/templates/ai-instructions/claude-cmd-review.md +8 -6
  71. package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +15 -11
  72. package/dist/orchestrator/templates/ai-instructions/claude-config.md +41 -10
  73. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +12 -0
  74. package/dist/orchestrator/templates/ai-instructions/claude-skill-delivery.md +19 -18
  75. package/dist/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +12 -0
  76. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +122 -10
  77. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +31 -3
  78. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +45 -0
  79. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +92 -0
  80. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +30 -0
  81. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +86 -0
  82. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +13 -10
  83. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +16 -15
  84. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-review.md +9 -7
  85. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +21 -17
  86. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +40 -9
  87. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +12 -0
  88. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +19 -18
  89. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +12 -0
  90. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +122 -10
  91. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +31 -3
  92. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +45 -0
  93. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +93 -0
  94. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +30 -0
  95. package/dist/orchestrator/templates/playwright.config.d.ts.map +1 -1
  96. package/dist/orchestrator/templates/playwright.config.js +3 -1
  97. package/dist/orchestrator/templates/playwright.config.js.map +1 -1
  98. package/dist/orchestrator/templates/playwright.config.ts +4 -1
  99. package/dist/orchestrator/templates/specs-base.d.ts +3 -4
  100. package/dist/orchestrator/templates/specs-base.d.ts.map +1 -1
  101. package/dist/orchestrator/templates/specs-base.js +60 -91
  102. package/dist/orchestrator/templates/specs-base.js.map +1 -1
  103. package/dist/orchestrator/templates/specs-base.ts +61 -101
  104. package/dist/orchestrator/templates/specs-test-data.d.ts +3 -1
  105. package/dist/orchestrator/templates/specs-test-data.d.ts.map +1 -1
  106. package/dist/orchestrator/templates/specs-test-data.js +53 -2
  107. package/dist/orchestrator/templates/specs-test-data.js.map +1 -1
  108. package/dist/orchestrator/templates/specs-test-data.ts +56 -2
  109. package/package.json +1 -1
  110. package/src/cli/commands/add-flow.ts +25 -0
  111. package/src/cli/commands/delivery.ts +109 -58
  112. package/src/cli/commands/generate.ts +43 -6
  113. package/src/cli/index.ts +3 -1
  114. package/src/generators/test-generator/adapters/adapter-interface.ts +6 -1
  115. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +1 -1
  116. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -2
  117. package/src/generators/test-generator/adapters/playwright/templates/scenario.hbs +20 -1
  118. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/alert-accept-action.hbs +1 -1
  119. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/alert-dismiss-action.hbs +1 -1
  120. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/alert-fill-action.hbs +1 -1
  121. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/alert-text-assertion.hbs +2 -2
  122. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +1 -0
  123. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs +2 -1
  124. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +1 -2
  125. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-timeout.hbs +1 -1
  126. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +84 -4
  127. package/src/generators/test-generator/code-generator.ts +119 -20
  128. package/src/generators/test-generator/patterns/interaction-patterns.ts +25 -3
  129. package/src/generators/test-generator/patterns/navigation-patterns.ts +8 -3
  130. package/src/generators/test-generator/step-mapper.ts +8 -0
  131. package/src/generators/test-generator/template-engine.ts +5 -2
  132. package/src/generators/test-generator/utils/data-resolver.ts +25 -77
  133. package/src/generators/test-generator/utils/selector-resolver.ts +23 -109
  134. package/src/orchestrator/ai-rules-updater.ts +5 -0
  135. package/src/orchestrator/flow-manager.ts +243 -0
  136. package/src/orchestrator/project-initializer.ts +1 -0
  137. package/src/orchestrator/screen-manager.ts +3 -1
  138. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-flow.md +88 -0
  139. package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +11 -8
  140. package/src/orchestrator/templates/ai-instructions/claude-cmd-review.md +8 -6
  141. package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +15 -11
  142. package/src/orchestrator/templates/ai-instructions/claude-config.md +41 -10
  143. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-live.md +12 -0
  144. package/src/orchestrator/templates/ai-instructions/claude-skill-delivery.md +19 -18
  145. package/src/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +12 -0
  146. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +122 -10
  147. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +31 -3
  148. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +45 -0
  149. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +92 -0
  150. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +30 -0
  151. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +86 -0
  152. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +13 -10
  153. package/src/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +16 -15
  154. package/src/orchestrator/templates/ai-instructions/copilot-cmd-review.md +9 -7
  155. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +21 -17
  156. package/src/orchestrator/templates/ai-instructions/copilot-config.md +40 -9
  157. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-live.md +12 -0
  158. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +19 -18
  159. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +12 -0
  160. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +122 -10
  161. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +31 -3
  162. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +45 -0
  163. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +93 -0
  164. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +30 -0
  165. package/src/orchestrator/templates/playwright.config.ts +4 -1
  166. package/src/orchestrator/templates/specs-base.ts +61 -101
  167. package/src/orchestrator/templates/specs-test-data.ts +56 -2
  168. package/dist/utils/feature-finder.d.ts +0 -9
  169. package/dist/utils/feature-finder.d.ts.map +0 -1
  170. package/dist/utils/feature-finder.js +0 -67
  171. package/dist/utils/feature-finder.js.map +0 -1
  172. package/dist/utils/screen-paths.d.ts +0 -10
  173. package/dist/utils/screen-paths.d.ts.map +0 -1
  174. package/dist/utils/screen-paths.js +0 -73
  175. package/dist/utils/screen-paths.js.map +0 -1
  176. package/dist/utils/selector-loader.d.ts +0 -6
  177. package/dist/utils/selector-loader.d.ts.map +0 -1
  178. package/dist/utils/selector-loader.js +0 -20
  179. package/dist/utils/selector-loader.js.map +0 -1
  180. package/dist/utils/test-data-loader.d.ts +0 -6
  181. package/dist/utils/test-data-loader.d.ts.map +0 -1
  182. package/dist/utils/test-data-loader.js +0 -20
  183. package/dist/utils/test-data-loader.js.map +0 -1
  184. package/src/utils/feature-finder.ts +0 -33
  185. package/src/utils/screen-paths.ts +0 -37
  186. package/src/utils/selector-loader.ts +0 -23
  187. package/src/utils/test-data-loader.ts +0 -23
@@ -1,6 +1,4 @@
1
- import * as fs from 'fs';
2
1
  import * as path from 'path';
3
- import yaml from 'yaml';
4
2
  import { readYamlIfExists } from '../../../utils/yaml-io';
5
3
 
6
4
  /**
@@ -9,17 +7,13 @@ import { readYamlIfExists } from '../../../utils/yaml-io';
9
7
  * Two modes:
10
8
  * - Compile-time (default): resolves YAML values and bakes them into generated code
11
9
  * - Runtime (runtimeMode=true): returns markers that post-processor converts to testData.get() calls
12
- *
13
- * Supports override files with priority:
14
- * 1. .override.yaml (highest - user customizations)
15
- * 2. -override.yaml (legacy - backward compat)
16
- * 3. .yaml (lowest - auto-generated base)
17
10
  */
18
11
  export class DataResolver {
19
12
  private dataCache = new Map<string, any>();
20
13
  private testDataDir: string;
21
14
  private screenName?: string;
22
15
  private runtimeMode: boolean;
16
+ private flowMode: boolean = false;
23
17
 
24
18
  constructor(testDataDir?: string, screenName?: string, runtimeMode: boolean = false) {
25
19
  this.testDataDir = testDataDir || path.join(process.cwd(), 'qa', 'test-data');
@@ -27,13 +21,14 @@ export class DataResolver {
27
21
  this.runtimeMode = runtimeMode;
28
22
  }
29
23
 
30
- /**
31
- * Set screen context for data resolution
32
- */
33
24
  setScreenContext(screenName: string): void {
34
25
  this.screenName = screenName;
35
26
  }
36
27
 
28
+ setFlowMode(enabled: boolean): void {
29
+ this.flowMode = enabled;
30
+ }
31
+
37
32
  /**
38
33
  * Resolve data reference to actual value
39
34
  * @param dataRef - Format: email.valid (path.to.value without filename)
@@ -107,89 +102,42 @@ export class DataResolver {
107
102
 
108
103
  /**
109
104
  * Load data file from disk (with caching)
110
- * Searches new screen-based directory structure only
105
+ * Merges base + env-specific: {name}.yaml + {name}.{SUNGEN_ENV}.yaml
111
106
  */
112
107
  private loadDataFile(fileName: string): any {
113
108
  if (this.dataCache.has(fileName)) {
114
109
  return this.dataCache.get(fileName)!;
115
110
  }
116
111
 
117
- // Merge base + override: base provides defaults, override wins on conflict
118
- const possiblePaths = this.getPossibleDataPaths(fileName);
119
- let merged: any = {};
120
- let found = false;
121
-
122
- // Load in reverse priority order (base first, then overrides on top)
123
- for (const p of [...possiblePaths].reverse()) {
124
- const data = readYamlIfExists(p);
125
- if (data && typeof data === 'object' && Object.keys(data).length > 0) {
126
- merged = { ...merged, ...data };
127
- found = true;
128
- }
112
+ if (!this.screenName) {
113
+ throw new Error(`Data resolution requires screen/flow context for "${fileName}"`);
129
114
  }
130
115
 
131
- if (!found) {
116
+ const subDir = this.flowMode ? 'flows' : 'screens';
117
+ const baseDir = path.join(process.cwd(), 'qa', subDir, this.screenName, 'test-data');
118
+ const basePath = path.join(baseDir, `${fileName}.yaml`);
119
+ const data = readYamlIfExists(basePath);
120
+
121
+ if (!data || typeof data !== 'object' || Object.keys(data).length === 0) {
132
122
  throw new Error(
133
- `Data file not found. Tried:\n${possiblePaths.join('\n')}`
123
+ `Data file not found or empty: qa/${subDir}/${this.screenName}/test-data/${fileName}.yaml`
134
124
  );
135
125
  }
136
126
 
137
- this.dataCache.set(fileName, merged);
138
- return merged;
139
- }
127
+ let merged = { ...data };
140
128
 
141
- /**
142
- * Find data file path (checks new screen-based structure)
143
- * Priority: .override.yaml > -override.yaml > base .yaml
144
- * Skips files that exist but contain no data (only comments)
145
- */
146
- private findDataFilePath(fileName: string): string | null {
147
- const possiblePaths = this.getPossibleDataPaths(fileName);
148
-
149
- for (const p of possiblePaths) {
150
- const data = readYamlIfExists(p);
151
- if (data !== null) {
152
- // Skip if file is empty or only contains null/undefined
153
- if (data === undefined || (typeof data === 'object' && Object.keys(data).length === 0)) {
154
- continue;
155
- }
156
-
157
- // Emit deprecation warning for old -override.yaml pattern
158
- if (p.includes('-override.yaml')) {
159
- console.warn(`⚠️ Deprecated: ${path.basename(p)}`);
160
- console.warn(` Rename to: ${fileName}.override.yaml for better compatibility`);
161
- }
162
- return p;
129
+ // Merge env-specific data: {name}.{SUNGEN_ENV}.yaml
130
+ const env = process.env.SUNGEN_ENV;
131
+ if (env) {
132
+ const envPath = path.join(baseDir, `${fileName}.${env}.yaml`);
133
+ const envData = readYamlIfExists(envPath);
134
+ if (envData && typeof envData === 'object') {
135
+ merged = { ...merged, ...envData };
163
136
  }
164
137
  }
165
-
166
- return null;
167
- }
168
138
 
169
- /**
170
- * Get list of possible data file paths (priority order)
171
- * Priority: .override.yaml > -override.yaml > base .yaml
172
- */
173
- private getPossibleDataPaths(fileName: string): string[] {
174
- const paths: string[] = [];
175
-
176
- // New structure: qa/screens/{screenName}/test-data/
177
- if (this.screenName) {
178
- const cwd = process.cwd();
179
- const basePath = path.join(cwd, 'qa', 'screens', this.screenName, 'test-data');
180
-
181
- // Priority 1: New .override.yaml pattern (highest)
182
- paths.push(path.join(basePath, `${fileName}.override.yaml`));
183
-
184
- // Priority 2: Old -override.yaml pattern (backward compat)
185
- paths.push(path.join(basePath, `${fileName}-override.yaml`));
186
-
187
- // Priority 3: Base file (lowest)
188
- paths.push(path.join(basePath, `${fileName}.yaml`));
189
- paths.push(path.join(basePath, `${fileName}.yml`));
190
- }
191
-
192
- return paths;
139
+ this.dataCache.set(fileName, merged);
140
+ return merged;
193
141
  }
194
142
 
195
143
  /**
@@ -82,12 +82,17 @@ export class SelectorResolver {
82
82
  private selectorsDir: string;
83
83
  private featureName?: string;
84
84
  private screenName?: string;
85
+ private flowMode: boolean = false;
85
86
 
86
87
  constructor(selectorsDir?: string, screenName?: string) {
87
88
  this.selectorsDir = selectorsDir || path.join(process.cwd(), 'qa', 'selectors', 'screens');
88
89
  this.screenName = screenName;
89
90
  }
90
91
 
92
+ setFlowMode(enabled: boolean): void {
93
+ this.flowMode = enabled;
94
+ }
95
+
91
96
  /**
92
97
  * Set screen context for base selector file lookup
93
98
  */
@@ -160,9 +165,15 @@ export class SelectorResolver {
160
165
  // Check if it's legacy format (contains : or has dot with screen prefix)
161
166
  // Legacy: "login:email_field" or "login.email_field"
162
167
  // Natural: "Email Address", "Password", "Submit Button"
168
+ // Flow namespaced: "Login:Submit" → key "login:submit" in flow's own YAML
163
169
  const hasColonSeparator = selectorRef.includes(':');
164
170
  const hasDotSeparator = selectorRef.includes('.');
165
-
171
+
172
+ // Flow mode: colon is a namespace separator, resolve as natural language key
173
+ if (hasColonSeparator && this.flowMode) {
174
+ return this.resolveNaturalLanguage(selectorRef, contextName!, elementType, nth);
175
+ }
176
+
166
177
  // If it has colon, it's definitely legacy
167
178
  if (hasColonSeparator) {
168
179
  return this.resolveLegacyFormat(selectorRef);
@@ -601,7 +612,7 @@ export class SelectorResolver {
601
612
 
602
613
  const selectors: SelectorFile = {};
603
614
 
604
- // 1. Try load base file (screen-level shared selectors) - OPTIONAL
615
+ // 1. Try load base file (screen/flow-level shared selectors) - OPTIONAL
605
616
  if (this.screenName) {
606
617
  const basePath = this.findBaseSelectorPath(this.screenName);
607
618
  if (basePath) {
@@ -610,21 +621,11 @@ export class SelectorResolver {
610
621
  Object.assign(selectors, SelectorResolver.normalizeKeys(baseSelectors));
611
622
  }
612
623
  }
613
-
614
- // Load base override file
615
- const baseOverridePath = this.findOverrideSelectorPath(this.screenName);
616
- if (baseOverridePath) {
617
- const overrideSelectors = readYamlIfExists<SelectorFile>(baseOverridePath);
618
- if (overrideSelectors) {
619
- Object.assign(selectors, SelectorResolver.normalizeKeys(overrideSelectors));
620
- }
621
- }
622
624
  }
623
625
 
624
626
  // 2. Load feature-specific selectors - REQUIRED
625
627
  const featurePath = this.findBaseSelectorPath(featureName);
626
628
  if (!featurePath) {
627
- // If no base file, that's OK if we have base from screen
628
629
  if (Object.keys(selectors).length === 0) {
629
630
  throw new Error(
630
631
  `Selector file not found for feature "${featureName}". ` +
@@ -638,15 +639,6 @@ export class SelectorResolver {
638
639
  }
639
640
  }
640
641
 
641
- // 3. Load feature override file
642
- const featureOverridePath = this.findOverrideSelectorPath(featureName);
643
- if (featureOverridePath) {
644
- const overrideSelectors = readYamlIfExists<SelectorFile>(featureOverridePath);
645
- if (overrideSelectors) {
646
- Object.assign(selectors, SelectorResolver.normalizeKeys(overrideSelectors));
647
- }
648
- }
649
-
650
642
  this.selectorCache.set(cacheKey, selectors);
651
643
  return selectors;
652
644
  }
@@ -657,72 +649,25 @@ export class SelectorResolver {
657
649
  private findBaseSelectorPath(name: string): string | null {
658
650
  const possiblePaths: string[] = [];
659
651
 
660
- // New structure: qa/screens/{screenName}/selectors/
652
+ // New structure: qa/screens/{screenName}/selectors/ or qa/flows/{flowName}/selectors/
661
653
  const qaDir = path.dirname(path.dirname(this.selectorsDir));
662
-
663
- if (this.screenName) {
664
- // Priority: Base .yaml only
665
- possiblePaths.push(
666
- path.join(qaDir, 'screens', this.screenName, 'selectors', `${name}.yaml`)
667
- );
668
- } else {
669
- possiblePaths.push(
670
- path.join(qaDir, 'screens', name, 'selectors', `${name}.yaml`)
671
- );
672
- }
673
-
674
- // Old structure
675
- possiblePaths.push(path.join(this.selectorsDir, `${name}.yaml`));
676
-
677
- for (const p of possiblePaths) {
678
- if (fs.existsSync(p)) {
679
- return p;
680
- }
681
- }
682
-
683
- return null;
684
- }
685
-
686
- /**
687
- * Find OVERRIDE selector file path
688
- * Priority: .override.yaml > -override.yaml
689
- */
690
- private findOverrideSelectorPath(name: string): string | null {
691
- const possiblePaths: string[] = [];
654
+ const subDir = this.flowMode ? 'flows' : 'screens';
692
655
 
693
- // New structure: qa/screens/{screenName}/selectors/
694
- const qaDir = path.dirname(path.dirname(this.selectorsDir));
695
-
696
656
  if (this.screenName) {
697
- // Priority 1: New .override.yaml
698
657
  possiblePaths.push(
699
- path.join(qaDir, 'screens', this.screenName, 'selectors', `${name}.override.yaml`)
700
- );
701
-
702
- // Priority 2: Old -override.yaml
703
- possiblePaths.push(
704
- path.join(qaDir, 'screens', this.screenName, 'selectors', `${name}-override.yaml`)
658
+ path.join(qaDir, subDir, this.screenName, 'selectors', `${name}.yaml`)
705
659
  );
706
660
  } else {
707
661
  possiblePaths.push(
708
- path.join(qaDir, 'screens', name, 'selectors', `${name}.override.yaml`)
709
- );
710
- possiblePaths.push(
711
- path.join(qaDir, 'screens', name, 'selectors', `${name}-override.yaml`)
662
+ path.join(qaDir, subDir, name, 'selectors', `${name}.yaml`)
712
663
  );
713
664
  }
714
665
 
715
666
  // Old structure
716
- possiblePaths.push(path.join(this.selectorsDir, `${name}.override.yaml`));
717
- possiblePaths.push(path.join(this.selectorsDir, `${name}-override.yaml`));
667
+ possiblePaths.push(path.join(this.selectorsDir, `${name}.yaml`));
718
668
 
719
669
  for (const p of possiblePaths) {
720
670
  if (fs.existsSync(p)) {
721
- // Emit deprecation warning for old -override.yaml pattern
722
- if (p.includes('-override.yaml')) {
723
- console.warn(`⚠️ Deprecated: ${path.basename(p)}`);
724
- console.warn(` Rename to: ${name}.override.yaml for better compatibility`);
725
- }
726
671
  return p;
727
672
  }
728
673
  }
@@ -732,59 +677,28 @@ export class SelectorResolver {
732
677
 
733
678
  /**
734
679
  * Find selector file path (checks both old and new structures)
735
- * Priority: .override.yaml > -override.yaml > base .yaml
736
680
  */
737
681
  private findSelectorPath(name: string): string | null {
738
- // Build list of possible paths (priority order)
739
682
  const possiblePaths: string[] = [];
740
683
 
741
- // New structure: qa/screens/<screenName>/selectors/
742
- const qaDir = path.dirname(path.dirname(this.selectorsDir)); // go up from qa/selectors/screens to qa/
743
-
684
+ const qaDir = path.dirname(path.dirname(this.selectorsDir));
685
+ const subDir = this.flowMode ? 'flows' : 'screens';
686
+
744
687
  if (this.screenName) {
745
- // Priority 1: New .override.yaml pattern (highest)
746
688
  possiblePaths.push(
747
- path.join(qaDir, 'screens', this.screenName, 'selectors', `${name}.override.yaml`)
748
- );
749
-
750
- // Priority 2: Old -override.yaml pattern (backward compat)
751
- possiblePaths.push(
752
- path.join(qaDir, 'screens', this.screenName, 'selectors', `${name}-override.yaml`)
753
- );
754
-
755
- // Priority 3: Base file (lowest)
756
- possiblePaths.push(
757
- path.join(qaDir, 'screens', this.screenName, 'selectors', `${name}.yaml`)
689
+ path.join(qaDir, subDir, this.screenName, 'selectors', `${name}.yaml`)
758
690
  );
759
691
  } else {
760
- // Fallback: assume name is both screen and feature
761
- possiblePaths.push(
762
- path.join(qaDir, 'screens', name, 'selectors', `${name}.override.yaml`)
763
- );
764
692
  possiblePaths.push(
765
- path.join(qaDir, 'screens', name, 'selectors', `${name}-override.yaml`)
766
- );
767
- possiblePaths.push(
768
- path.join(qaDir, 'screens', name, 'selectors', `${name}.yaml`)
693
+ path.join(qaDir, subDir, name, 'selectors', `${name}.yaml`)
769
694
  );
770
695
  }
771
696
 
772
697
  // Old structure: qa/selectors/screens/
773
- // Priority 1: New .override.yaml
774
- possiblePaths.push(path.join(this.selectorsDir, `${name}.override.yaml`));
775
- // Priority 2: Old -override.yaml
776
- possiblePaths.push(path.join(this.selectorsDir, `${name}-override.yaml`));
777
- // Priority 3: Base
778
698
  possiblePaths.push(path.join(this.selectorsDir, `${name}.yaml`));
779
699
 
780
- // Find first existing file and emit deprecation warning for old pattern
781
700
  for (const p of possiblePaths) {
782
701
  if (fs.existsSync(p)) {
783
- // Emit deprecation warning for old -override.yaml pattern
784
- if (p.includes('-override.yaml')) {
785
- console.warn(`⚠️ Deprecated: ${path.basename(p)}`);
786
- console.warn(` Rename to: ${name}.override.yaml for better compatibility`);
787
- }
788
702
  return p;
789
703
  }
790
704
  }
@@ -16,6 +16,7 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
16
16
 
17
17
  // Commands — Claude Code
18
18
  ['claude-cmd-add-screen.md', '.claude/commands/sungen/add-screen.md'],
19
+ ['claude-cmd-add-flow.md', '.claude/commands/sungen/add-flow.md'],
19
20
  ['claude-cmd-create-test.md', '.claude/commands/sungen/create-test.md'],
20
21
  ['claude-cmd-run-test.md', '.claude/commands/sungen/run-test.md'],
21
22
  ['claude-cmd-review.md', '.claude/commands/sungen/review.md'],
@@ -23,6 +24,7 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
23
24
 
24
25
  // Commands — GitHub Copilot
25
26
  ['copilot-cmd-add-screen.md', '.github/prompts/sungen-add-screen.prompt.md'],
27
+ ['copilot-cmd-add-flow.md', '.github/prompts/sungen-add-flow.prompt.md'],
26
28
  ['copilot-cmd-create-test.md', '.github/prompts/sungen-create-test.prompt.md'],
27
29
  ['copilot-cmd-run-test.md', '.github/prompts/sungen-run-test.prompt.md'],
28
30
  ['copilot-cmd-review.md', '.github/prompts/sungen-review.prompt.md'],
@@ -43,6 +45,9 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
43
45
  ['claude-skill-capture-live.md', '.claude/skills/sungen-capture-live/SKILL.md'],
44
46
  ['claude-skill-figma-source.md', '.claude/skills/sungen-figma-source/SKILL.md'],
45
47
 
48
+ // Skills — Copilot (prompt-based)
49
+ ['copilot-skill-figma-source.md', '.github/prompts/sungen-figma-source.prompt.md'],
50
+
46
51
  // Skills — GitHub Copilot
47
52
  ['github-skill-sungen-gherkin-syntax.md', '.github/skills/sungen-gherkin-syntax/SKILL.md'],
48
53
  ['github-skill-sungen-selector-keys.md', '.github/skills/sungen-selector-keys/SKILL.md'],
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Flow Manager
3
+ * Manages flow definition files in qa/flows directory
4
+ * Flows are independent E2E test journeys spanning multiple screens
5
+ */
6
+
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+
10
+ export interface FlowOptions {
11
+ name: string;
12
+ path?: string;
13
+ description?: string;
14
+ }
15
+
16
+ export class FlowManager {
17
+ private cwd: string;
18
+ private flowsDir: string;
19
+
20
+ constructor() {
21
+ this.cwd = process.cwd();
22
+ this.flowsDir = path.join(this.cwd, 'qa', 'flows');
23
+ }
24
+
25
+ async addFlow(options: FlowOptions): Promise<void> {
26
+ this.validateFlowName(options.name);
27
+
28
+ const flowName = this.normalizeFlowName(options.name);
29
+ const flowDir = path.join(this.flowsDir, flowName);
30
+
31
+ const featuresDir = path.join(flowDir, 'features');
32
+ const selectorsDir = path.join(flowDir, 'selectors');
33
+ const testDataDir = path.join(flowDir, 'test-data');
34
+ const requirementsDir = path.join(flowDir, 'requirements');
35
+ const requirementsUiDir = path.join(requirementsDir, 'ui');
36
+
37
+ const featurePath = path.join(featuresDir, `${flowName}.feature`);
38
+ const selectorPath = path.join(selectorsDir, `${flowName}.yaml`);
39
+ const testDataPath = path.join(testDataDir, `${flowName}.yaml`);
40
+
41
+ if (fs.existsSync(flowDir)) {
42
+ console.error(`Error: Flow "${options.name}" already exists at ${flowDir}`);
43
+ process.exit(1);
44
+ }
45
+
46
+ console.log(`Creating flow: ${options.name}\n`);
47
+
48
+ try {
49
+ if (!fs.existsSync(this.flowsDir)) {
50
+ fs.mkdirSync(this.flowsDir, { recursive: true });
51
+ }
52
+ fs.mkdirSync(featuresDir, { recursive: true });
53
+ fs.mkdirSync(selectorsDir, { recursive: true });
54
+ fs.mkdirSync(testDataDir, { recursive: true });
55
+ fs.mkdirSync(requirementsUiDir, { recursive: true });
56
+ } catch (error) {
57
+ console.error(`Error: Failed to create directories`);
58
+ console.error(` ${error instanceof Error ? error.message : String(error)}`);
59
+ process.exit(1);
60
+ }
61
+
62
+ fs.writeFileSync(featurePath, this.generateFeatureTemplate(options, flowName), 'utf-8');
63
+
64
+ const startPath = options.path || '/login';
65
+ fs.writeFileSync(selectorPath, [
66
+ `# ${options.name} Flow Selectors`,
67
+ `# Namespace keys by screen to avoid duplicates: "login:submit", "awards:submit"`,
68
+ ``,
69
+ `# --- Login screen ---`,
70
+ `login:`,
71
+ ` type: 'page'`,
72
+ ` value: '${startPath}'`,
73
+ ``,
74
+ `# "login:email":`,
75
+ `# type: 'testid'`,
76
+ `# value: 'email-input'`,
77
+ ``,
78
+ `# "login:submit":`,
79
+ `# type: 'role'`,
80
+ `# value: 'button'`,
81
+ `# name: 'Login'`,
82
+ ``,
83
+ ].join('\n'), 'utf-8');
84
+
85
+ fs.writeFileSync(testDataPath, [
86
+ `# ${options.name} Flow Test Data`,
87
+ `# Namespace by phase: login.email, submission.nominee`,
88
+ `# Reference in features using {{variable}} syntax`,
89
+ ``,
90
+ ].join('\n'), 'utf-8');
91
+
92
+ const specPath = path.join(requirementsDir, 'spec.md');
93
+ fs.writeFileSync(specPath, this.generateSpecTemplate(options, flowName), 'utf-8');
94
+
95
+ const viewpointPath = path.join(requirementsDir, 'test-viewpoint.md');
96
+ fs.writeFileSync(viewpointPath, this.generateViewpointTemplate(options), 'utf-8');
97
+
98
+ console.log(`Created files:`);
99
+ console.log(` ${path.relative(this.cwd, featurePath)}`);
100
+ console.log(` ${path.relative(this.cwd, selectorPath)}`);
101
+ console.log(` ${path.relative(this.cwd, testDataPath)}`);
102
+ console.log(` ${path.relative(this.cwd, specPath)}`);
103
+ console.log(` ${path.relative(this.cwd, viewpointPath)}`);
104
+ console.log(` ${path.relative(this.cwd, requirementsUiDir)}/`);
105
+ console.log('');
106
+
107
+ console.log('Next steps:');
108
+ console.log(` 1. Fill requirements/spec.md with flow specification (screens, transitions, business rules)`);
109
+ console.log(` Optionally add UI designs to requirements/ui/`);
110
+ console.log(` 2. Generate test cases: /sungen:create-test ${flowName}`);
111
+ console.log(` 3. Compile: sungen generate --flow ${flowName}`);
112
+ console.log(` 4. Run: npx playwright test\n`);
113
+ }
114
+
115
+ private validateFlowName(name: string): void {
116
+ if (!name || name.trim().length === 0) {
117
+ console.error('Error: Flow name cannot be empty');
118
+ process.exit(1);
119
+ }
120
+ if (!/[a-zA-Z0-9]/.test(name)) {
121
+ console.error('Error: Flow name must contain at least one alphanumeric character');
122
+ process.exit(1);
123
+ }
124
+ }
125
+
126
+ private normalizeFlowName(name: string): string {
127
+ return name
128
+ .toLowerCase()
129
+ .replace(/[^a-z0-9]+/g, '-')
130
+ .replace(/^-+|-+$/g, '')
131
+ .replace(/-+/g, '-');
132
+ }
133
+
134
+ private generateSpecTemplate(options: FlowOptions, flowName: string): string {
135
+ const startPath = options.path || '/login';
136
+ return `# ${options.name} Flow Specification
137
+
138
+ ## Overview
139
+ - **Start URL:** ${startPath}
140
+ - **Auth Required:** no
141
+ - **Platform:** web
142
+
143
+ ## Screens in Flow
144
+ <!-- List all screens this flow visits, in order -->
145
+
146
+ | # | Screen | URL Path | Description |
147
+ |---|--------|----------|-------------|
148
+ | 1 | [Screen Name] | ${startPath} | [What user does here] |
149
+
150
+ ## Flow Steps
151
+
152
+ ### Step 1: [Screen Name]
153
+ - **Action:** [What the user does]
154
+ - **Expected:** [What should happen]
155
+ - **Transition:** Navigates to Step 2
156
+
157
+ ## Business Rules
158
+ <!-- Rules that affect the flow: permissions, conditions, limits -->
159
+ - [Rule 1]
160
+
161
+ ## Notes
162
+ <!-- Edge cases, known issues, environment-specific behavior -->
163
+ - [Note 1]
164
+ `;
165
+ }
166
+
167
+ private generateViewpointTemplate(options: FlowOptions): string {
168
+ return [
169
+ `# ${options.name} Flow — Test Viewpoints`,
170
+ '',
171
+ '## Edge Cases',
172
+ '',
173
+ '<!-- Sample — replace with actual edge cases for this flow:',
174
+ '- Network drops between screen transitions — should resume or show error?',
175
+ '- Session expires mid-flow — should redirect to login and preserve progress?',
176
+ '- Browser back button at step 3 — should return to step 2 with data intact?',
177
+ '- Complete flow twice rapidly — should prevent duplicate submissions',
178
+ '-->',
179
+ '',
180
+ '## Known Issues',
181
+ '',
182
+ '<!-- Sample — replace with actual known issues:',
183
+ '- [BUG-001] Form data lost if user navigates away and returns',
184
+ '- [BUG-002] Confirmation page shows stale data on slow connections',
185
+ '-->',
186
+ '',
187
+ '## Design Decisions',
188
+ '',
189
+ '<!-- Sample — replace with actual design decisions:',
190
+ '- Flow requires completing all steps in order (no skip)',
191
+ '- Draft is auto-saved at each step transition',
192
+ '- Cancel at any step discards the entire flow',
193
+ '-->',
194
+ '',
195
+ '## Cross-Screen Concerns',
196
+ '',
197
+ '<!-- Sample — replace with actual cross-screen concerns:',
198
+ '- Auth role must remain consistent across all screens',
199
+ '- Data entered in step 1 must be visible in confirmation (step 3)',
200
+ '- Error in step 2 should allow returning to step 1 without re-entering data',
201
+ '-->',
202
+ '',
203
+ '## Priority Viewpoints',
204
+ '',
205
+ '<!-- Rate importance for this flow (High / Medium / Low / Skip):',
206
+ '',
207
+ '| VP | Priority | Reason |',
208
+ '|---|----------|--------|',
209
+ '| VP-UI | Medium | Standard forms across screens |',
210
+ '| VP-VAL | High | Cross-screen validation rules |',
211
+ '| VP-LOGIC | High | Multi-step business logic, state transitions |',
212
+ '| VP-SEC | Medium | Auth must persist, role-based access at each screen |',
213
+ '-->',
214
+ '',
215
+ ].join('\n');
216
+ }
217
+
218
+ private generateFeatureTemplate(options: FlowOptions, flowName: string): string {
219
+ const description = options.description || `complete the ${options.name} flow`;
220
+
221
+ return `@flow
222
+ Feature: ${options.name} Flow
223
+
224
+ As a user
225
+ I want to ${description}
226
+ So that I can accomplish my end-to-end goal
227
+
228
+ Background:
229
+ Given User is on [Login] page
230
+
231
+ @high @auth:user
232
+ Scenario: User login successfully
233
+ When User fill [Login:Email] field with {{login.email}}
234
+ And User fill [Login:Password] field with {{login.password}}
235
+ And User click [Login:Submit] button
236
+ Then User see [Dashboard] page
237
+
238
+ Scenario: Sample next step for ${options.name}
239
+ When User click [Dashboard:Element] button
240
+ Then User see "expected result" text
241
+ `;
242
+ }
243
+ }
@@ -90,6 +90,7 @@ export class ProjectInitializer {
90
90
  const dirs = [
91
91
  'qa',
92
92
  'qa/screens',
93
+ 'qa/flows',
93
94
  'specs',
94
95
  'specs/generated',
95
96
  'specs/storage',
@@ -358,9 +358,11 @@ export class ScreenManager {
358
358
  So that I can accomplish my tasks
359
359
  Path: ${featurePath}
360
360
 
361
+ Background:
362
+ Given User is on [${screenName}] page
363
+
361
364
  @high
362
365
  Scenario: Sample scenario for ${options.name}
363
- Given User is on [${screenName}] page
364
366
  When User click [element] button
365
367
  Then User see [result] text with {{success}}
366
368
  `;