@sun-asterisk/sungen 2.6.12 → 2.6.15

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 (107) hide show
  1. package/dist/cli/commands/delivery.d.ts.map +1 -1
  2. package/dist/cli/commands/delivery.js +215 -65
  3. package/dist/cli/commands/delivery.js.map +1 -1
  4. package/dist/cli/index.js +1 -1
  5. package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
  6. package/dist/dashboard/snapshot-builder.js +173 -32
  7. package/dist/dashboard/snapshot-builder.js.map +1 -1
  8. package/dist/dashboard/templates/index.html +84 -84
  9. package/dist/dashboard/types.d.ts +35 -0
  10. package/dist/dashboard/types.d.ts.map +1 -1
  11. package/dist/exporters/csv-exporter.d.ts +24 -3
  12. package/dist/exporters/csv-exporter.d.ts.map +1 -1
  13. package/dist/exporters/csv-exporter.js +28 -7
  14. package/dist/exporters/csv-exporter.js.map +1 -1
  15. package/dist/exporters/json-exporter.d.ts +15 -0
  16. package/dist/exporters/json-exporter.d.ts.map +1 -1
  17. package/dist/exporters/json-exporter.js +7 -2
  18. package/dist/exporters/json-exporter.js.map +1 -1
  19. package/dist/exporters/playwright-report-parser.d.ts +7 -0
  20. package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
  21. package/dist/exporters/playwright-report-parser.js +20 -0
  22. package/dist/exporters/playwright-report-parser.js.map +1 -1
  23. package/dist/exporters/selector-key-resolver.d.ts +55 -0
  24. package/dist/exporters/selector-key-resolver.d.ts.map +1 -0
  25. package/dist/exporters/selector-key-resolver.js +208 -0
  26. package/dist/exporters/selector-key-resolver.js.map +1 -0
  27. package/dist/exporters/test-data-resolver.d.ts +15 -2
  28. package/dist/exporters/test-data-resolver.d.ts.map +1 -1
  29. package/dist/exporters/test-data-resolver.js +61 -8
  30. package/dist/exporters/test-data-resolver.js.map +1 -1
  31. package/dist/exporters/types.d.ts +1 -0
  32. package/dist/exporters/types.d.ts.map +1 -1
  33. package/dist/exporters/xlsx-exporter.d.ts +28 -3
  34. package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
  35. package/dist/exporters/xlsx-exporter.js +34 -6
  36. package/dist/exporters/xlsx-exporter.js.map +1 -1
  37. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  38. package/dist/generators/test-generator/code-generator.js +13 -0
  39. package/dist/generators/test-generator/code-generator.js.map +1 -1
  40. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  41. package/dist/orchestrator/ai-rules-updater.js +4 -0
  42. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  43. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +48 -14
  44. package/dist/orchestrator/templates/ai-instructions/claude-cmd-dashboard.md +4 -1
  45. package/dist/orchestrator/templates/ai-instructions/claude-cmd-delivery.md +22 -11
  46. package/dist/orchestrator/templates/ai-instructions/claude-cmd-locale.md +71 -0
  47. package/dist/orchestrator/templates/ai-instructions/claude-cmd-review.md +23 -8
  48. package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +45 -6
  49. package/dist/orchestrator/templates/ai-instructions/claude-config.md +4 -1
  50. package/dist/orchestrator/templates/ai-instructions/claude-skill-locale.md +316 -0
  51. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +50 -13
  52. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-dashboard.md +4 -1
  53. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +20 -9
  54. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-locale.md +70 -0
  55. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-review.md +23 -8
  56. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +44 -6
  57. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +4 -1
  58. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-locale.md +291 -0
  59. package/dist/orchestrator/templates/playwright.config.ts +25 -8
  60. package/dist/orchestrator/templates/specs-base.ts +9 -0
  61. package/dist/orchestrator/templates/specs-locale-fixture.ts +105 -0
  62. package/package.json +1 -1
  63. package/src/cli/commands/delivery.ts +256 -65
  64. package/src/cli/index.ts +1 -1
  65. package/src/dashboard/snapshot-builder.ts +207 -32
  66. package/src/dashboard/templates/index.html +84 -84
  67. package/src/dashboard/types.ts +40 -3
  68. package/src/exporters/csv-exporter.ts +36 -7
  69. package/src/exporters/json-exporter.ts +22 -2
  70. package/src/exporters/playwright-report-parser.ts +20 -0
  71. package/src/exporters/selector-key-resolver.ts +190 -0
  72. package/src/exporters/test-data-resolver.ts +65 -7
  73. package/src/exporters/types.ts +1 -0
  74. package/src/exporters/xlsx-exporter.ts +61 -7
  75. package/src/generators/test-generator/code-generator.ts +14 -0
  76. package/src/orchestrator/ai-rules-updater.ts +4 -0
  77. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +48 -14
  78. package/src/orchestrator/templates/ai-instructions/claude-cmd-dashboard.md +4 -1
  79. package/src/orchestrator/templates/ai-instructions/claude-cmd-delivery.md +22 -11
  80. package/src/orchestrator/templates/ai-instructions/claude-cmd-locale.md +71 -0
  81. package/src/orchestrator/templates/ai-instructions/claude-cmd-review.md +23 -8
  82. package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +45 -6
  83. package/src/orchestrator/templates/ai-instructions/claude-config.md +4 -1
  84. package/src/orchestrator/templates/ai-instructions/claude-skill-locale.md +316 -0
  85. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +50 -13
  86. package/src/orchestrator/templates/ai-instructions/copilot-cmd-dashboard.md +4 -1
  87. package/src/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +20 -9
  88. package/src/orchestrator/templates/ai-instructions/copilot-cmd-locale.md +70 -0
  89. package/src/orchestrator/templates/ai-instructions/copilot-cmd-review.md +23 -8
  90. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +44 -6
  91. package/src/orchestrator/templates/ai-instructions/copilot-config.md +4 -1
  92. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-locale.md +291 -0
  93. package/src/orchestrator/templates/playwright.config.ts +25 -8
  94. package/src/orchestrator/templates/specs-base.ts +9 -0
  95. package/src/orchestrator/templates/specs-locale-fixture.ts +105 -0
  96. package/dist/orchestrator/templates/playwright.config.d.ts +0 -10
  97. package/dist/orchestrator/templates/playwright.config.d.ts.map +0 -1
  98. package/dist/orchestrator/templates/playwright.config.js +0 -104
  99. package/dist/orchestrator/templates/playwright.config.js.map +0 -1
  100. package/dist/orchestrator/templates/specs-base.d.ts +0 -14
  101. package/dist/orchestrator/templates/specs-base.d.ts.map +0 -1
  102. package/dist/orchestrator/templates/specs-base.js +0 -77
  103. package/dist/orchestrator/templates/specs-base.js.map +0 -1
  104. package/dist/orchestrator/templates/specs-test-data.d.ts +0 -16
  105. package/dist/orchestrator/templates/specs-test-data.d.ts.map +0 -1
  106. package/dist/orchestrator/templates/specs-test-data.js +0 -151
  107. package/dist/orchestrator/templates/specs-test-data.js.map +0 -1
@@ -18,6 +18,7 @@ import { parse as parseYaml } from 'yaml';
18
18
  import { parseFeatureMetadata } from '../exporters/feature-parser';
19
19
  import { parseSpecFile } from '../exporters/spec-parser';
20
20
  import { loadTestData } from '../exporters/test-data-resolver';
21
+ import { loadSelectorKeyMap } from '../exporters/selector-key-resolver';
21
22
  import { loadPlaywrightReport } from '../exporters/playwright-report-parser';
22
23
  import { mergeFeatureAndSpec } from '../exporters/scenario-merger';
23
24
  import { buildScreenSnapshot } from '../exporters/json-exporter';
@@ -26,11 +27,20 @@ import { EnvironmentInfo } from '../exporters/types';
26
27
  import {
27
28
  AggregateSummary,
28
29
  DashboardSnapshot,
30
+ FeatureSnapshot,
31
+ LocaleSnapshot,
29
32
  ScenarioSnapshot,
30
33
  ScreenSnapshot,
34
+ ScreenSummaryStats,
31
35
  SNAPSHOT_VERSION,
32
36
  } from './types';
33
37
 
38
+ /**
39
+ * Locale code displayed when a screen has no overlay variants — the base
40
+ * test-data file alone. Matches the delivery CLI's `DEFAULT_BASE_LOCALE`.
41
+ */
42
+ const DEFAULT_BASE_LOCALE = 'vi';
43
+
34
44
  export interface DashboardTarget {
35
45
  name: string;
36
46
  isFlow: boolean;
@@ -80,12 +90,62 @@ function buildOneScreen(
80
90
  env: EnvironmentInfo
81
91
  ): ScreenSnapshot | null {
82
92
  const base = qaDir(cwd, target);
83
- const genBase = generatedDir(cwd, target);
84
- const featureFile = path.join(base, 'features', `${target.name}.feature`);
85
- const testDataFile = path.join(base, 'test-data', `${target.name}.yaml`);
86
- const specFile = path.join(genBase, `${target.name}.spec.ts`);
93
+ const featuresDir = path.join(base, 'features');
94
+ if (!fs.existsSync(featuresDir)) return null;
95
+
96
+ // Enumerate all .feature files inside the screen/flow — matches delivery's
97
+ // discovery so dashboard scenarios share TC IDs with the exported CSVs.
98
+ const featureBaseNames = fs.readdirSync(featuresDir)
99
+ .filter((f) => f.endsWith('.feature'))
100
+ .map((f) => f.slice(0, -'.feature'.length))
101
+ .sort();
102
+ if (featureBaseNames.length === 0) return null;
103
+
87
104
  const specMdFile = path.join(base, 'requirements', 'spec.md');
88
- const resultsFile = resolveResultsPath(cwd, target);
105
+ const specLink = fs.existsSync(specMdFile) ? path.relative(cwd, specMdFile) : undefined;
106
+
107
+ const features: FeatureSnapshot[] = [];
108
+ for (const featureBaseName of featureBaseNames) {
109
+ const fs2 = buildOneFeature(cwd, target, featureBaseName, env, specLink);
110
+ if (fs2) features.push(fs2);
111
+ }
112
+ if (features.length === 0) return null;
113
+
114
+ // Flat scenarios across all features (each scenario already carries its
115
+ // own `featureBaseName`, so consumers that want to group can do so cheaply).
116
+ const scenarios = features.flatMap((f) => f.scenarios);
117
+ const label = target.isFlow ? `flow/${target.name}` : target.name;
118
+ const primary = features[0];
119
+
120
+ return {
121
+ name: label,
122
+ isFlow: target.isFlow,
123
+ featureName: primary.featureName,
124
+ featurePath: primary.featurePath,
125
+ specLink,
126
+ summary: rollupScreenSummary(features),
127
+ scenarios,
128
+ features,
129
+ };
130
+ }
131
+
132
+ /**
133
+ * Build a FeatureSnapshot for a single `.feature` file inside a screen/flow.
134
+ * TC IDs are generated using `featureBaseName` as the prefix so they match
135
+ * the delivery CSV (e.g. `HOME-MODAL-UI-001`, not `HOME-UI-001`).
136
+ */
137
+ function buildOneFeature(
138
+ cwd: string,
139
+ target: DashboardTarget,
140
+ featureBaseName: string,
141
+ env: EnvironmentInfo,
142
+ specLink: string | undefined,
143
+ ): FeatureSnapshot | null {
144
+ const base = qaDir(cwd, target);
145
+ const genBase = generatedDir(cwd, target);
146
+ const featureFile = path.join(base, 'features', `${featureBaseName}.feature`);
147
+ const testDataFile = path.join(base, 'test-data', `${featureBaseName}.yaml`);
148
+ const specFile = path.join(genBase, `${featureBaseName}.spec.ts`);
89
149
 
90
150
  if (!fs.existsSync(featureFile)) return null;
91
151
 
@@ -93,33 +153,157 @@ function buildOneScreen(
93
153
  const spec = fs.existsSync(specFile)
94
154
  ? parseSpecFile(specFile)
95
155
  : { tests: [] };
96
- const testData = fs.existsSync(testDataFile) ? loadTestData(testDataFile) : {};
97
- const results = resultsFile ? loadPlaywrightReport(resultsFile) : null;
98
-
99
156
  const merged = mergeFeatureAndSpec(feature, spec);
100
- const label = target.isFlow ? `flow/${target.name}` : target.name;
101
- const specLink = fs.existsSync(specMdFile) ? path.relative(cwd, specMdFile) : undefined;
102
157
 
103
158
  // Screens declare their URL inline in the .feature ("Path: /awards"). Flows
104
- // don't have one there, so fall back to the first `type: 'page'` entry in
105
- // the flow's selectors YAML — that's where the entry point is recorded.
159
+ // don't, so fall back to the first `type: 'page'` entry in selectors YAML.
106
160
  let featurePath = feature.featurePath;
161
+ const selectorsFile = path.join(base, 'selectors', `${featureBaseName}.yaml`);
107
162
  if (!featurePath && target.isFlow) {
108
- const selectorsFile = path.join(base, 'selectors', `${target.name}.yaml`);
109
163
  featurePath = readPagePathFromSelectors(selectorsFile);
110
164
  }
165
+ const selectorKeyMap = loadSelectorKeyMap(selectorsFile);
166
+
167
+ // Discover locale variants by scanning <feature>-test-result*.json files.
168
+ // Always includes base ('') as the first entry, even if its results file
169
+ // is missing — UI still needs to render the feature.
170
+ const variants = discoverLocaleVariants(genBase, featureBaseName);
171
+
172
+ // Build per-locale ScreenSnapshot, then collect into FeatureSnapshot.locales[].
173
+ // Base variant is also used to populate the top-level (`scenarios`, `summary`)
174
+ // for back-compat with consumers that don't read the locales array.
175
+ const locales: LocaleSnapshot[] = [];
176
+ let baseBuilt: { scenarios: ScenarioSnapshot[]; summary: ScreenSummaryStats; featureName: string; featurePath?: string; specLink?: string } | null = null;
177
+
178
+ for (const variant of variants) {
179
+ const testData = fs.existsSync(testDataFile)
180
+ ? loadTestData(testDataFile, variant.locale || null)
181
+ : {};
182
+ const results = variant.resultsPath ? loadPlaywrightReport(variant.resultsPath) : null;
183
+
184
+ const built = buildScreenSnapshot({
185
+ screen: featureBaseName,
186
+ isFlow: target.isFlow,
187
+ featureName: feature.featureName,
188
+ featurePath,
189
+ specLink,
190
+ merged,
191
+ testData,
192
+ results,
193
+ env,
194
+ featureBaseName,
195
+ selectorKeyMap,
196
+ });
197
+
198
+ locales.push({
199
+ locale: variant.locale,
200
+ displayCode: variant.displayCode,
201
+ summary: built.summary,
202
+ scenarios: built.scenarios,
203
+ });
204
+
205
+ if (variant.locale === '') {
206
+ baseBuilt = built;
207
+ }
208
+ }
111
209
 
112
- return buildScreenSnapshot({
113
- screen: label,
114
- isFlow: target.isFlow,
115
- featureName: feature.featureName,
116
- featurePath,
117
- specLink,
118
- merged,
119
- testData,
120
- results,
121
- env,
210
+ // Guarantee a base entry — every feature in the dashboard must have
211
+ // something to fall back to even if base results file is missing.
212
+ if (!baseBuilt && locales.length > 0) {
213
+ baseBuilt = {
214
+ scenarios: locales[0].scenarios,
215
+ summary: locales[0].summary,
216
+ featureName: feature.featureName,
217
+ featurePath,
218
+ specLink,
219
+ };
220
+ }
221
+ if (!baseBuilt) return null;
222
+
223
+ return {
224
+ featureBaseName,
225
+ featureName: baseBuilt.featureName,
226
+ featurePath: baseBuilt.featurePath,
227
+ specLink: baseBuilt.specLink,
228
+ summary: baseBuilt.summary,
229
+ scenarios: baseBuilt.scenarios,
230
+ // Only expose `locales` array when 2+ variants exist — single-locale
231
+ // features stay slimmer and UI can skip the locale tab UI entirely.
232
+ locales: locales.length >= 2 ? locales : undefined,
233
+ };
234
+ }
235
+
236
+ /**
237
+ * A single locale variant of a feature.
238
+ * `locale === ''` denotes the base (file `<name>-test-result.json`,
239
+ * test-data without overlay merge). Variants come from
240
+ * `<name>-test-result.<locale>.json` filenames.
241
+ */
242
+ interface FeatureLocaleVariant {
243
+ locale: string;
244
+ displayCode: string;
245
+ resultsPath: string | null;
246
+ }
247
+
248
+ /**
249
+ * Scan the generated directory for `<basename>-test-result*.json` files
250
+ * and infer locale codes. The base entry is always returned first; locale
251
+ * variants follow alphabetically so sheet/tab order stays deterministic.
252
+ *
253
+ * Mirrors `discoverLocaleVariants` in `src/cli/commands/delivery.ts` so the
254
+ * dashboard and the CSV/XLSX delivery agree on what locales exist for a
255
+ * given feature.
256
+ */
257
+ function discoverLocaleVariants(genDir: string, featureBaseName: string): FeatureLocaleVariant[] {
258
+ const prefix = `${featureBaseName}-test-result`;
259
+ const variants: FeatureLocaleVariant[] = [];
260
+
261
+ const basePath = path.join(genDir, `${prefix}.json`);
262
+ variants.push({
263
+ locale: '',
264
+ displayCode: DEFAULT_BASE_LOCALE.toUpperCase(),
265
+ resultsPath: fs.existsSync(basePath) ? basePath : null,
122
266
  });
267
+
268
+ if (fs.existsSync(genDir)) {
269
+ const localeFiles = fs.readdirSync(genDir)
270
+ .filter((f) => f.startsWith(`${prefix}.`) && f.endsWith('.json') && f !== `${prefix}.json`)
271
+ .sort();
272
+ for (const f of localeFiles) {
273
+ const locale = f.slice(prefix.length + 1, -'.json'.length);
274
+ if (!locale) continue;
275
+ variants.push({
276
+ locale,
277
+ displayCode: locale.toUpperCase(),
278
+ resultsPath: path.join(genDir, f),
279
+ });
280
+ }
281
+ }
282
+
283
+ return variants;
284
+ }
285
+
286
+ /**
287
+ * Combine per-feature summaries into a single screen-level rollup.
288
+ * passRate is recomputed across the combined passed+failed totals so a
289
+ * screen with one all-passing feature and one all-failing feature shows
290
+ * 50% rather than the arithmetic mean of two rates.
291
+ */
292
+ function rollupScreenSummary(features: FeatureSnapshot[]): ScreenSummaryStats {
293
+ const out: ScreenSummaryStats = {
294
+ total: 0, passed: 0, failed: 0, pending: 0, na: 0, notCompiled: 0, passRate: 0,
295
+ };
296
+ for (const f of features) {
297
+ out.total += f.summary.total;
298
+ out.passed += f.summary.passed;
299
+ out.failed += f.summary.failed;
300
+ out.pending += f.summary.pending;
301
+ out.na += f.summary.na;
302
+ out.notCompiled += f.summary.notCompiled;
303
+ }
304
+ const executed = out.passed + out.failed;
305
+ out.passRate = executed > 0 ? out.passed / executed : 0;
306
+ return out;
123
307
  }
124
308
 
125
309
  // ----------------------------------------------------------------------------
@@ -178,15 +362,6 @@ function generatedDir(cwd: string, target: DashboardTarget): string {
178
362
  : path.join(cwd, 'specs', 'generated', target.name);
179
363
  }
180
364
 
181
- function resolveResultsPath(cwd: string, target: DashboardTarget): string | null {
182
- const genDir = generatedDir(cwd, target);
183
- const perTarget = path.join(genDir, `${target.name}-test-result.json`);
184
- if (fs.existsSync(perTarget)) return perTarget;
185
- const global = path.join(cwd, 'test-results', 'results.json');
186
- if (fs.existsSync(global)) return global;
187
- return null;
188
- }
189
-
190
365
  // ----------------------------------------------------------------------------
191
366
  // Discovery (also used by CLI)
192
367
  // ----------------------------------------------------------------------------