@sun-asterisk/sungen 2.6.1 → 2.6.3

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 (62) hide show
  1. package/dist/cli/commands/dashboard.d.ts +10 -0
  2. package/dist/cli/commands/dashboard.d.ts.map +1 -0
  3. package/dist/cli/commands/dashboard.js +171 -0
  4. package/dist/cli/commands/dashboard.js.map +1 -0
  5. package/dist/cli/index.js +4 -2
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/dashboard/history-store.d.ts +27 -0
  8. package/dist/dashboard/history-store.d.ts.map +1 -0
  9. package/dist/dashboard/history-store.js +112 -0
  10. package/dist/dashboard/history-store.js.map +1 -0
  11. package/dist/dashboard/html-renderer.d.ts +30 -0
  12. package/dist/dashboard/html-renderer.d.ts.map +1 -0
  13. package/dist/dashboard/html-renderer.js +111 -0
  14. package/dist/dashboard/html-renderer.js.map +1 -0
  15. package/dist/dashboard/snapshot-builder.d.ts +30 -0
  16. package/dist/dashboard/snapshot-builder.d.ts.map +1 -0
  17. package/dist/dashboard/snapshot-builder.js +263 -0
  18. package/dist/dashboard/snapshot-builder.js.map +1 -0
  19. package/dist/dashboard/templates/index.html +287 -0
  20. package/dist/dashboard/types.d.ts +122 -0
  21. package/dist/dashboard/types.d.ts.map +1 -0
  22. package/dist/dashboard/types.js +11 -0
  23. package/dist/dashboard/types.js.map +1 -0
  24. package/dist/exporters/json-exporter.d.ts +25 -0
  25. package/dist/exporters/json-exporter.d.ts.map +1 -0
  26. package/dist/exporters/json-exporter.js +135 -0
  27. package/dist/exporters/json-exporter.js.map +1 -0
  28. package/dist/exporters/playwright-report-parser.d.ts +2 -1
  29. package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
  30. package/dist/exporters/playwright-report-parser.js +12 -5
  31. package/dist/exporters/playwright-report-parser.js.map +1 -1
  32. package/dist/exporters/spec-parser.d.ts.map +1 -1
  33. package/dist/exporters/spec-parser.js +8 -3
  34. package/dist/exporters/spec-parser.js.map +1 -1
  35. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  36. package/dist/orchestrator/ai-rules-updater.js +4 -0
  37. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  38. package/dist/orchestrator/templates/ai-instructions/claude-cmd-dashboard.md +62 -0
  39. package/dist/orchestrator/templates/ai-instructions/claude-skill-dashboard.md +121 -0
  40. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-dashboard.md +62 -0
  41. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-dashboard.md +121 -0
  42. package/dist/orchestrator/templates/playwright.config.d.ts.map +1 -1
  43. package/dist/orchestrator/templates/playwright.config.js +17 -1
  44. package/dist/orchestrator/templates/playwright.config.js.map +1 -1
  45. package/dist/orchestrator/templates/playwright.config.ts +20 -1
  46. package/package.json +4 -3
  47. package/src/cli/commands/dashboard.ts +158 -0
  48. package/src/cli/index.ts +4 -2
  49. package/src/dashboard/history-store.ts +86 -0
  50. package/src/dashboard/html-renderer.ts +90 -0
  51. package/src/dashboard/snapshot-builder.ts +273 -0
  52. package/src/dashboard/templates/index.html +287 -0
  53. package/src/dashboard/types.ts +148 -0
  54. package/src/exporters/json-exporter.ts +162 -0
  55. package/src/exporters/playwright-report-parser.ts +12 -5
  56. package/src/exporters/spec-parser.ts +8 -3
  57. package/src/orchestrator/ai-rules-updater.ts +4 -0
  58. package/src/orchestrator/templates/ai-instructions/claude-cmd-dashboard.md +62 -0
  59. package/src/orchestrator/templates/ai-instructions/claude-skill-dashboard.md +121 -0
  60. package/src/orchestrator/templates/ai-instructions/copilot-cmd-dashboard.md +62 -0
  61. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-dashboard.md +121 -0
  62. package/src/orchestrator/templates/playwright.config.ts +20 -1
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Schemas for the sungen dashboard snapshot.
3
+ *
4
+ * A snapshot is a self-describing JSON document that powers a single render
5
+ * of qa/dashboard/index.html. Past snapshots live in qa/dashboard/history/.
6
+ */
7
+
8
+ export const SNAPSHOT_VERSION = 1 as const;
9
+
10
+ export interface DashboardSnapshot {
11
+ version: typeof SNAPSHOT_VERSION;
12
+ runId: string; // ISO-ish: 2026-04-28T10-32-15Z (filename-safe)
13
+ generatedAt: string; // full ISO timestamp
14
+ environment: SnapshotEnvironment;
15
+ summary: AggregateSummary;
16
+ screens: ScreenSnapshot[];
17
+ }
18
+
19
+ export interface SnapshotEnvironment {
20
+ baseURL: string;
21
+ projectName: string;
22
+ executor: string;
23
+ sungenVersion: string;
24
+ gitBranch?: string;
25
+ gitSha?: string;
26
+ }
27
+
28
+ export interface AggregateSummary {
29
+ total: number;
30
+ passed: number;
31
+ failed: number;
32
+ pending: number;
33
+ na: number;
34
+ notCompiled: number;
35
+ passRate: number; // 0..1, only counting executed (passed+failed)
36
+ byPriority: Record<string, number>; // Critical/High/Normal/Low
37
+ byCategory: Record<string, number>; // Accessing/GUI/Function
38
+ byType: Record<string, number>; // Auto/Manual/Not compiled
39
+ }
40
+
41
+ export interface ScreenSnapshot {
42
+ name: string; // "kudos" or "flow/checkout"
43
+ isFlow: boolean;
44
+ featureName: string;
45
+ featurePath?: string;
46
+ specLink?: string; // relative path to requirements/spec.md
47
+ summary: ScreenSummaryStats;
48
+ scenarios: ScenarioSnapshot[];
49
+ }
50
+
51
+ export interface ScreenSummaryStats {
52
+ total: number;
53
+ passed: number;
54
+ failed: number;
55
+ pending: number;
56
+ na: number;
57
+ notCompiled: number;
58
+ passRate: number;
59
+ }
60
+
61
+ export interface ScenarioSnapshot {
62
+ tcId: string; // KUDOS-UI-001
63
+ category1: string; // scenario name (VP-prefix stripped)
64
+ category2: string; // Accessing | GUI | Function
65
+ category3: string; // feature name
66
+ category4: string; // screen name
67
+ vpId?: string; // VP-UI-001 if present
68
+ priority: 'Critical' | 'High' | 'Normal' | 'Low';
69
+ type: 'Auto' | 'Manual' | 'Not compiled';
70
+ status: 'Passed' | 'Failed' | 'Pending' | 'N/A';
71
+ tags: string[];
72
+ precondition: string;
73
+ testData: string;
74
+ steps: string[]; // numbered, one entry per step
75
+ expectedResults: string[];
76
+ duration?: number; // ms
77
+ errorMessage?: string;
78
+ tracePath?: string;
79
+ executedDate?: string; // dd/mm/yyyy
80
+ testEnvironment?: string;
81
+ testExecutor?: string;
82
+ }
83
+
84
+ // ----------------------------------------------------------------------------
85
+ // Diff schemas (Phase 2 will populate these in the dashboard UI from history)
86
+ // ----------------------------------------------------------------------------
87
+
88
+ export interface SnapshotDiff {
89
+ baseRunId: string;
90
+ headRunId: string;
91
+ summary: DiffSummary;
92
+ screens: ScreenDiff[];
93
+ tests: TestDiff[];
94
+ }
95
+
96
+ export interface DiffSummary {
97
+ added: number;
98
+ removed: number;
99
+ newlyPassing: number;
100
+ newlyFailing: number;
101
+ statusChanged: number;
102
+ priorityChanged: number;
103
+ totalDelta: number; // head.total - base.total
104
+ passRateDelta: number; // head - base
105
+ }
106
+
107
+ export interface ScreenDiff {
108
+ name: string;
109
+ totalDelta: number;
110
+ passedDelta: number;
111
+ failedDelta: number;
112
+ passRateDelta: number;
113
+ isNew: boolean;
114
+ isRemoved: boolean;
115
+ }
116
+
117
+ export interface TestDiff {
118
+ tcId: string;
119
+ screen: string;
120
+ changeType:
121
+ | 'added'
122
+ | 'removed'
123
+ | 'newly_passing'
124
+ | 'newly_failing'
125
+ | 'status_changed'
126
+ | 'priority_changed'
127
+ | 'unchanged';
128
+ baseStatus?: ScenarioSnapshot['status'];
129
+ headStatus?: ScenarioSnapshot['status'];
130
+ basePriority?: ScenarioSnapshot['priority'];
131
+ headPriority?: ScenarioSnapshot['priority'];
132
+ category1: string;
133
+ }
134
+
135
+ // ----------------------------------------------------------------------------
136
+ // Wire-format embedded into the HTML template
137
+ // ----------------------------------------------------------------------------
138
+
139
+ /**
140
+ * The single payload injected into qa/dashboard/index.html at build time.
141
+ * Holds the current snapshot plus all retained history runs (max 10).
142
+ *
143
+ * The dashboard UI reads `window.__SUNGEN_DASHBOARD__` at boot.
144
+ */
145
+ export interface DashboardPayload {
146
+ current: DashboardSnapshot;
147
+ history: DashboardSnapshot[]; // oldest → newest, current excluded
148
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Convert merged Gherkin/spec/results data → structured JSON for the dashboard.
3
+ *
4
+ * Mirrors csv-exporter's buildTestCaseRows / buildSummary, but emits the
5
+ * richer ScenarioSnapshot / ScreenSnapshot shape used by the dashboard UI.
6
+ */
7
+
8
+ import { MergedScenario } from './scenario-merger';
9
+ import {
10
+ extractAuthRole,
11
+ extractPriority,
12
+ extractTestcaseType,
13
+ generateTcId,
14
+ mapVpToCategory2,
15
+ splitVpAndName,
16
+ } from './feature-parser';
17
+ import { formatPrecondition, cleanStepLine } from './step-formatter';
18
+ import { formatTestData } from './test-data-resolver';
19
+ import {
20
+ formatExecutedDate,
21
+ statusToTestResult,
22
+ } from './playwright-report-parser';
23
+ import { EnvironmentInfo, PlaywrightResult } from './types';
24
+ import {
25
+ ScenarioSnapshot,
26
+ ScreenSnapshot,
27
+ ScreenSummaryStats,
28
+ } from '../dashboard/types';
29
+
30
+ export interface BuildScreenSnapshotInput {
31
+ screen: string;
32
+ isFlow: boolean;
33
+ featureName: string;
34
+ featurePath?: string;
35
+ specLink?: string;
36
+ merged: MergedScenario[];
37
+ testData: Record<string, string>;
38
+ results: Map<string, PlaywrightResult> | null;
39
+ env: EnvironmentInfo;
40
+ }
41
+
42
+ /**
43
+ * Build a ScreenSnapshot from merged scenarios.
44
+ */
45
+ export function buildScreenSnapshot(input: BuildScreenSnapshotInput): ScreenSnapshot {
46
+ const scenarios: ScenarioSnapshot[] = [];
47
+ let fallbackIndex = 1;
48
+
49
+ for (const m of input.merged) {
50
+ const { vpId, category1 } = splitVpAndName(m.feature.name);
51
+ const tcId = generateTcId(input.screen, vpId, fallbackIndex);
52
+ if (!vpId) fallbackIndex++;
53
+
54
+ const category2 = mapVpToCategory2(vpId);
55
+ const priority = extractPriority(m.feature.tags) as ScenarioSnapshot['priority'];
56
+ const baseType = extractTestcaseType(m.feature.tags);
57
+ const authRole = extractAuthRole(m.feature.tags);
58
+
59
+ let stepsRaw: string[];
60
+ let expectedRaw: string[];
61
+ let givenForPrecondition: string[];
62
+
63
+ if (m.spec) {
64
+ givenForPrecondition = m.spec.precondition;
65
+ stepsRaw = m.spec.steps;
66
+ expectedRaw = m.spec.expectations;
67
+ } else {
68
+ givenForPrecondition = m.feature.rawGivenSteps;
69
+ stepsRaw = m.feature.rawWhenSteps;
70
+ expectedRaw = m.feature.rawThenSteps;
71
+ }
72
+
73
+ const precondition = formatPrecondition(authRole, givenForPrecondition);
74
+ const steps = stepsRaw.map(cleanStepLine).filter(Boolean);
75
+ const expectedResults = expectedRaw.map(cleanStepLine).filter(Boolean);
76
+ const testDataStr = formatTestData(m.feature.referencedVars, input.testData);
77
+
78
+ let result: PlaywrightResult | undefined;
79
+ if (input.results && m.spec) {
80
+ result = input.results.get(m.spec.testTitle);
81
+ }
82
+
83
+ let status: ScenarioSnapshot['status'];
84
+ let executedDate: string | undefined;
85
+ let errorMessage: string | undefined;
86
+ let tracePath: string | undefined;
87
+ let testEnvironment: string | undefined;
88
+ let testExecutor: string | undefined;
89
+ let type: ScenarioSnapshot['type'];
90
+
91
+ if (!m.spec) {
92
+ type = baseType === 'Manual' ? 'Manual' : 'Not compiled';
93
+ status = baseType === 'Manual' ? 'Pending' : 'N/A';
94
+ } else {
95
+ type = baseType;
96
+ if (!result) {
97
+ status = 'Pending';
98
+ } else {
99
+ status = statusToTestResult(result.status) as ScenarioSnapshot['status'];
100
+ executedDate = formatExecutedDate(result.startTime);
101
+ if (result.error) errorMessage = String(result.error).split('\n')[0].trim();
102
+ if (result.tracePath) tracePath = result.tracePath;
103
+ testEnvironment = `${input.env.baseURL} (${input.env.projectName})`;
104
+ testExecutor = input.env.executor;
105
+ }
106
+ }
107
+
108
+ scenarios.push({
109
+ tcId,
110
+ category1,
111
+ category2,
112
+ category3: input.featureName,
113
+ category4: input.screen,
114
+ vpId,
115
+ priority,
116
+ type,
117
+ status,
118
+ tags: m.feature.tags,
119
+ precondition,
120
+ testData: testDataStr,
121
+ steps,
122
+ expectedResults,
123
+ executedDate,
124
+ errorMessage,
125
+ tracePath,
126
+ testEnvironment,
127
+ testExecutor,
128
+ });
129
+ }
130
+
131
+ return {
132
+ name: input.screen,
133
+ isFlow: input.isFlow,
134
+ featureName: input.featureName,
135
+ featurePath: input.featurePath,
136
+ specLink: input.specLink,
137
+ summary: summarizeScreen(scenarios),
138
+ scenarios,
139
+ };
140
+ }
141
+
142
+ function summarizeScreen(scenarios: ScenarioSnapshot[]): ScreenSummaryStats {
143
+ const stats: ScreenSummaryStats = {
144
+ total: scenarios.length,
145
+ passed: 0,
146
+ failed: 0,
147
+ pending: 0,
148
+ na: 0,
149
+ notCompiled: 0,
150
+ passRate: 0,
151
+ };
152
+ for (const s of scenarios) {
153
+ if (s.status === 'Passed') stats.passed++;
154
+ else if (s.status === 'Failed') stats.failed++;
155
+ else if (s.status === 'Pending') stats.pending++;
156
+ else if (s.status === 'N/A') stats.na++;
157
+ if (s.type === 'Not compiled') stats.notCompiled++;
158
+ }
159
+ const executed = stats.passed + stats.failed;
160
+ stats.passRate = executed > 0 ? stats.passed / executed : 0;
161
+ return stats;
162
+ }
@@ -74,7 +74,11 @@ export function loadPlaywrightReport(reportPath: string): Map<string, Playwright
74
74
  const result = new Map<string, PlaywrightResult>();
75
75
  for (const [fullTitle, spec] of specMap.entries()) {
76
76
  const test = spec.tests?.[0];
77
- const res = test?.results?.[0];
77
+ // With retries enabled, take the LAST attempt — that's the final outcome.
78
+ // A flaky test (failed once, passed retry) shows as "passed" in the dashboard
79
+ // because the user-visible end state is "passed".
80
+ const results = test?.results || [];
81
+ const res = results[results.length - 1];
78
82
  const status = normalizeStatus(res?.status);
79
83
  const errorMsg = res?.error?.message || '';
80
84
  const trace = res?.attachments?.find((a) => a.name === 'trace' || a.contentType === 'application/zip')?.path;
@@ -141,15 +145,18 @@ export function statusToTestResult(status: PlaywrightResult['status']): string {
141
145
  }
142
146
 
143
147
  /**
144
- * Format startTime (ISO string) → "dd/mm/yyyy".
148
+ * Format startTime (ISO string) → "dd/mm/yyyy" in Vietnam time (UTC+7),
149
+ * so reports stay consistent regardless of where Playwright was executed.
145
150
  */
146
151
  export function formatExecutedDate(startTime: string | undefined): string {
147
152
  if (!startTime) return '';
148
153
  const d = new Date(startTime);
149
154
  if (isNaN(d.getTime())) return '';
150
- const dd = String(d.getDate()).padStart(2, '0');
151
- const mm = String(d.getMonth() + 1).padStart(2, '0');
152
- const yyyy = d.getFullYear();
155
+ const VN_OFFSET_MS = 7 * 60 * 60 * 1000;
156
+ const shifted = new Date(d.getTime() + VN_OFFSET_MS);
157
+ const dd = String(shifted.getUTCDate()).padStart(2, '0');
158
+ const mm = String(shifted.getUTCMonth() + 1).padStart(2, '0');
159
+ const yyyy = shifted.getUTCFullYear();
153
160
  return `${dd}/${mm}/${yyyy}`;
154
161
  }
155
162
 
@@ -15,13 +15,18 @@ function extractTestBlock(content: string, startIdx: number): {
15
15
  body: string;
16
16
  endIdx: number;
17
17
  } | null {
18
- // Match test('title', async ({ page }) => {
19
- const testRegex = /test\s*\(\s*['"]([^'"]+)['"]\s*,\s*async\s*\([^)]*\)\s*=>\s*\{/g;
18
+ // Match both legacy and tag-pass-through forms:
19
+ // test('title', async ({ page }) => {
20
+ // test('title', { tag: [...] }, async ({ page }) => {
21
+ // Backreference \1 lets the inner title contain the opposite quote type
22
+ // (e.g. test('Footer "X" link', ...) — common when scenarios cite UI labels).
23
+ const testRegex = /test\s*\(\s*(['"])((?:(?!\1).)+)\1\s*,\s*(?:\{[^}]*\}\s*,\s*)?async\s*\([^)]*\)\s*=>\s*\{/g;
20
24
  testRegex.lastIndex = startIdx;
21
25
  const match = testRegex.exec(content);
22
26
  if (!match) return null;
23
27
 
24
- const title = match[1];
28
+ // [1] = outer quote char, [2] = title
29
+ const title = match[2];
25
30
  const bodyStart = match.index + match[0].length;
26
31
 
27
32
  // Find matching closing brace (accounting for nested braces)
@@ -21,6 +21,7 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
21
21
  ['claude-cmd-run-test.md', '.claude/commands/sungen/run-test.md'],
22
22
  ['claude-cmd-review.md', '.claude/commands/sungen/review.md'],
23
23
  ['claude-cmd-delivery.md', '.claude/commands/sungen/delivery.md'],
24
+ ['claude-cmd-dashboard.md', '.claude/commands/sungen/dashboard.md'],
24
25
 
25
26
  // Commands — GitHub Copilot
26
27
  ['copilot-cmd-add-screen.md', '.github/prompts/sungen-add-screen.prompt.md'],
@@ -29,6 +30,7 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
29
30
  ['copilot-cmd-run-test.md', '.github/prompts/sungen-run-test.prompt.md'],
30
31
  ['copilot-cmd-review.md', '.github/prompts/sungen-review.prompt.md'],
31
32
  ['copilot-cmd-delivery.md', '.github/prompts/sungen-delivery.prompt.md'],
33
+ ['copilot-cmd-dashboard.md', '.github/prompts/sungen-dashboard.prompt.md'],
32
34
 
33
35
  // Skills — Claude Code
34
36
  ['claude-skill-gherkin-syntax.md', '.claude/skills/sungen-gherkin-syntax/SKILL.md'],
@@ -40,6 +42,7 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
40
42
  ['claude-skill-tc-review.md', '.claude/skills/sungen-tc-review/SKILL.md'],
41
43
  ['claude-skill-viewpoint.md', '.claude/skills/sungen-viewpoint/SKILL.md'],
42
44
  ['claude-skill-delivery.md', '.claude/skills/sungen-delivery/SKILL.md'],
45
+ ['claude-skill-dashboard.md', '.claude/skills/sungen-dashboard/SKILL.md'],
43
46
  ['claude-skill-capture-figma.md', '.claude/skills/sungen-capture-figma/SKILL.md'],
44
47
  ['claude-skill-capture-local.md', '.claude/skills/sungen-capture-local/SKILL.md'],
45
48
  ['claude-skill-capture-live.md', '.claude/skills/sungen-capture-live/SKILL.md'],
@@ -58,6 +61,7 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
58
61
  ['github-skill-sungen-tc-review.md', '.github/skills/sungen-tc-review/SKILL.md'],
59
62
  ['github-skill-sungen-viewpoint.md', '.github/skills/sungen-viewpoint/SKILL.md'],
60
63
  ['github-skill-sungen-delivery.md', '.github/skills/sungen-delivery/SKILL.md'],
64
+ ['github-skill-sungen-dashboard.md', '.github/skills/sungen-dashboard/SKILL.md'],
61
65
  ['github-skill-sungen-capture-figma.md', '.github/skills/sungen-capture-figma/SKILL.md'],
62
66
  ['github-skill-sungen-capture-local.md', '.github/skills/sungen-capture-local/SKILL.md'],
63
67
  ['github-skill-sungen-capture-live.md', '.github/skills/sungen-capture-live/SKILL.md'],
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: dashboard
3
+ description: 'Build a single-file HTML dashboard with test cases + pass/fail results, history trends, compare runs, and CSV/XLSX export.'
4
+ argument-hint: "[screen-name...] (omit for all)"
5
+ allowed-tools: Bash, Read, AskUserQuestion
6
+ ---
7
+
8
+ ## Role
9
+
10
+ You are a **QA Reporting Engineer**. Your job is to invoke the deterministic `sungen dashboard` CLI which builds a self-contained HTML report. The HTML is shareable as-is (open via `file://`, no server required).
11
+
12
+ ## Parameters
13
+
14
+ Parse **screens** from `$ARGUMENTS`:
15
+ - If empty → CLI will include **all** screens and flows.
16
+ - If provided → pass them through.
17
+
18
+ ## Steps
19
+
20
+ ### 1. Invoke the CLI
21
+
22
+ ```bash
23
+ npx sungen dashboard <screens>
24
+ ```
25
+
26
+ - No args → all screens + flows.
27
+ - With args → only those targets.
28
+ - Add `--open` to auto-open the rendered HTML in the user's browser.
29
+
30
+ The CLI handles:
31
+ - Discovery of `qa/screens/*` and `qa/flows/*`.
32
+ - Reuses the same parsers as `sungen delivery` (feature, spec.ts, test-data.yaml, results.json).
33
+ - Builds `DashboardSnapshot` JSON.
34
+ - Persists `qa/dashboard/history/<runId>.json` (max 10 retained, oldest pruned).
35
+ - Renders `qa/dashboard/index.html` (~1MB, single file, fully self-contained).
36
+ - Prints a summary table.
37
+
38
+ ### 2. Show the result + offer next steps
39
+
40
+ Forward the CLI's summary verbatim, then use `AskUserQuestion`:
41
+
42
+ - **Open the dashboard** — Suggest `npx sungen dashboard --open` to open in the default browser, or just open `qa/dashboard/index.html` manually.
43
+ - **Re-run after a test run** — Run `/sungen:run-test <screen>` to refresh `test-results/results.json`, then re-invoke `/sungen:dashboard`.
44
+ - **Build for a single screen** — Run `/sungen:dashboard <screen>` to scope the report.
45
+ - **Share the file** — The HTML at `qa/dashboard/index.html` can be emailed / Slacked / committed; recipients open it directly without any server.
46
+ - **Done** — Exit.
47
+
48
+ ## Important notes
49
+
50
+ - **Do NOT parse files yourself** — the CLI is the source of truth.
51
+ - **The HTML is stateless** — the snapshot is embedded in `<script id="__SUNGEN_DASHBOARD__" type="application/json">…</script>`. Re-running the CLI overwrites this file.
52
+ - **History is retained in `qa/dashboard/history/`** — these JSON files power the Trends and Compare views. Keep them in git so collaborators see the same trend.
53
+ - **CSV/XLSX export** in the dashboard runs entirely in the browser (SheetJS) and matches the `sungen delivery` format. Use it for quick exports without re-running the CLI.
54
+
55
+ ## CLI Reference
56
+
57
+ ```
58
+ sungen dashboard [screens...]
59
+ [--no-history] Do not persist this run under qa/dashboard/history/
60
+ [--max-history <n>] Cap retained history files (default: 10)
61
+ [--open] Open the rendered HTML in the default browser
62
+ ```
@@ -0,0 +1,121 @@
1
+ ---
2
+ name: sungen-dashboard
3
+ description: 'Build a single-file HTML dashboard summarising test cases & Playwright results. Auto-loaded by /sungen:dashboard.'
4
+ user-invocable: false
5
+ ---
6
+
7
+ ## Purpose
8
+
9
+ Generate `qa/dashboard/index.html` — a single-file, share-ready test report covering all (or selected) screens and flows. The HTML embeds the snapshot data inline, so it can be emailed, Slacked, or committed and opened by anyone via `file://` without any server.
10
+
11
+ **This skill delegates all heavy work to the `sungen dashboard` CLI.** The CLI is the source of truth for parsing logic — do NOT re-parse files in AI. Your role is only to:
12
+
13
+ 1. Invoke the CLI.
14
+ 2. Show its output verbatim.
15
+ 3. Help the user decide next actions.
16
+
17
+ ---
18
+
19
+ ## Architecture
20
+
21
+ ```
22
+ User → /sungen:dashboard [screen...]
23
+
24
+
25
+ sungen dashboard CLI (deterministic — no AI tokens)
26
+ ├─ Discovery: qa/screens/* + qa/flows/*
27
+ ├─ Reuse delivery parsers (.feature, .spec.ts, test-data.yaml, results.json)
28
+ ├─ Build DashboardSnapshot JSON
29
+ ├─ Write qa/dashboard/history/<runId>.json (max 20 retained, oldest pruned)
30
+ ├─ Inject payload into pre-built HTML template
31
+ └─ Write qa/dashboard/index.html (~1MB, fully self-contained)
32
+ ```
33
+
34
+ Source modules: `src/dashboard/*.ts` + `src/exporters/json-exporter.ts`.
35
+ UI source: `dashboard/` (built once, ships in npm package as `dist/dashboard/templates/index.html`).
36
+
37
+ ---
38
+
39
+ ## Required sources (CLI tolerates missing files)
40
+
41
+ | # | Source | Path | Required? |
42
+ |---|--------|------|-----------|
43
+ | 1 | Feature file | `qa/screens/<name>/features/<name>.feature` (or `qa/flows/...`) | Yes — screens without a feature are skipped |
44
+ | 2 | Test data | `qa/screens/<name>/test-data/<name>.yaml` | Optional — `{{vars}}` fall back to literal |
45
+ | 3 | Compiled spec | `specs/generated/<name>/<name>.spec.ts` | Optional — flagged as "Not compiled" if missing |
46
+ | 4 | Test results | `specs/generated/<name>/<name>-test-result.json` (or `test-results/results.json`) | Optional — TCs show as "Pending" if missing |
47
+
48
+ Unlike `sungen delivery`, the dashboard CLI is **forgiving** — it always renders whatever data is available. Pre-flight is implicit, not blocking.
49
+
50
+ ---
51
+
52
+ ## Output structure
53
+
54
+ ```
55
+ qa/dashboard/
56
+ ├── index.html # share-ready single-file report
57
+ └── history/
58
+ ├── 2026-04-26T...Z.json # past snapshots, oldest → newest
59
+ ├── 2026-04-27T...Z.json
60
+ └── … # max 20, older pruned automatically
61
+ ```
62
+
63
+ Both should be committed to git so collaborators see the same trend lines.
64
+
65
+ ---
66
+
67
+ ## Dashboard views
68
+
69
+ | View | What it shows |
70
+ |------|---------------|
71
+ | **Overview** | Stat cards (total/passed/failed/pending), pass-rate donut, priority bar chart, trend mini-chart, per-screen progress bars |
72
+ | **Suites** | Tree: screen → scenarios; filter by search/status/priority; click → detail modal with steps, expected, error, trace |
73
+ | **Trends** | Pass-rate line chart + stacked status bars across all retained runs (requires ≥2 runs) |
74
+ | **Compare** | Pick base/head run; per-screen Δ table; lists of newly passing/failing/added/removed/changed tests |
75
+ | **Export** | Browser-side CSV / XLSX download. One-button generation, no server. Same column layout as `sungen delivery` |
76
+
77
+ Trends + Compare are disabled in the sidebar until at least 2 runs exist in history.
78
+
79
+ ---
80
+
81
+ ## CLI command reference
82
+
83
+ ```bash
84
+ # All screens + flows
85
+ sungen dashboard
86
+
87
+ # Specific targets
88
+ sungen dashboard kudos awards flow/checkout
89
+
90
+ # Skip history persistence (one-off / CI ephemeral)
91
+ sungen dashboard --no-history
92
+
93
+ # Cap retained history (default: 20)
94
+ sungen dashboard --max-history 50
95
+
96
+ # Auto-open in default browser
97
+ sungen dashboard --open
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Skill responsibilities (when invoked from /sungen:dashboard)
103
+
104
+ 1. **Run the CLI** with whatever arguments came from `$ARGUMENTS`.
105
+ 2. **Show the CLI output verbatim** — do not summarize, paraphrase, or omit warnings.
106
+ 3. **Offer next-step options via `AskUserQuestion`** based on what just happened:
107
+ - On success → open dashboard, share file, run more tests, build for another screen, done.
108
+ - On error → diagnose from the error message; common causes are missing `qa/` directories or no targets.
109
+ 4. **Do NOT** parse `.feature`, `.spec.ts`, or results files yourself — that is the CLI's job.
110
+ 5. **Do NOT** touch files under `qa/dashboard/` directly — only the CLI writes there.
111
+
112
+ ---
113
+
114
+ ## Sharing the dashboard
115
+
116
+ The HTML is self-contained:
117
+ - No CDN or external font — system font stack only.
118
+ - All JS, CSS, charts, and the SheetJS library are inlined.
119
+ - The snapshot payload sits in `<script id="__SUNGEN_DASHBOARD__" type="application/json">…</script>`.
120
+
121
+ Acceptable sharing: email, Slack upload, S3 bucket, committed in git. The recipient opens it directly, no install required. ~1MB on disk, ~300KB gzipped over the wire.
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: sungen-dashboard
3
+ description: 'Build a single-file HTML dashboard with test cases + pass/fail results, history trends, compare runs, and CSV/XLSX export.'
4
+ argument-hint: "[screen-name...] (omit for all)"
5
+ tools: [read, execute, edit, vscode/askQuestions]
6
+ ---
7
+
8
+ ## Role
9
+
10
+ You are a **QA Reporting Engineer**. Your job is to invoke the deterministic `sungen dashboard` CLI which builds a self-contained HTML report. The HTML is shareable as-is (open via `file://`, no server required).
11
+
12
+ ## Parameters
13
+
14
+ Parse **screens** from `$ARGUMENTS`:
15
+ - If empty → CLI will include **all** screens and flows.
16
+ - If provided → pass them through.
17
+
18
+ ## Steps
19
+
20
+ ### 1. Invoke the CLI
21
+
22
+ ```bash
23
+ npx sungen dashboard <screens>
24
+ ```
25
+
26
+ - No args → all screens + flows.
27
+ - With args → only those targets.
28
+ - Add `--open` to auto-open the rendered HTML in the user's browser.
29
+
30
+ The CLI handles:
31
+ - Discovery of `qa/screens/*` and `qa/flows/*`.
32
+ - Reuses the same parsers as `sungen delivery` (feature, spec.ts, test-data.yaml, results.json).
33
+ - Builds `DashboardSnapshot` JSON.
34
+ - Persists `qa/dashboard/history/<runId>.json` (max 10 retained, oldest pruned).
35
+ - Renders `qa/dashboard/index.html` (~1MB, single file, fully self-contained).
36
+ - Prints a summary table.
37
+
38
+ ### 2. Show the result + offer next steps
39
+
40
+ Forward the CLI's summary verbatim, then ask the user what they want next:
41
+
42
+ - **Open the dashboard** — Suggest `npx sungen dashboard --open` or open `qa/dashboard/index.html` manually.
43
+ - **Re-run after a test run** — Run `/sungen:run-test <screen>` to refresh `test-results/results.json`, then re-invoke `/sungen:dashboard`.
44
+ - **Build for a single screen** — Run `/sungen:dashboard <screen>` to scope the report.
45
+ - **Share the file** — The HTML at `qa/dashboard/index.html` can be emailed / Slacked / committed; recipients open it directly without any server.
46
+ - **Done** — Exit.
47
+
48
+ ## Important notes
49
+
50
+ - **Do NOT parse files yourself** — the CLI is the source of truth.
51
+ - **The HTML is stateless** — the snapshot is embedded in `<script id="__SUNGEN_DASHBOARD__" type="application/json">…</script>`. Re-running the CLI overwrites this file.
52
+ - **History is retained in `qa/dashboard/history/`** — these JSON files power the Trends and Compare views. Keep them in git so collaborators see the same trend.
53
+ - **CSV/XLSX export** in the dashboard runs entirely in the browser (SheetJS) and matches the `sungen delivery` format. Use it for quick exports without re-running the CLI.
54
+
55
+ ## CLI Reference
56
+
57
+ ```
58
+ sungen dashboard [screens...]
59
+ [--no-history] Do not persist this run under qa/dashboard/history/
60
+ [--max-history <n>] Cap retained history files (default: 10)
61
+ [--open] Open the rendered HTML in the default browser
62
+ ```