@sun-asterisk/sungen 2.6.7 → 2.6.10

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 (96) hide show
  1. package/dist/cli/commands/dashboard.d.ts +2 -1
  2. package/dist/cli/commands/dashboard.d.ts.map +1 -1
  3. package/dist/cli/commands/dashboard.js +9 -9
  4. package/dist/cli/commands/dashboard.js.map +1 -1
  5. package/dist/cli/commands/delivery.d.ts.map +1 -1
  6. package/dist/cli/commands/delivery.js +33 -0
  7. package/dist/cli/commands/delivery.js.map +1 -1
  8. package/dist/cli/index.js +1 -1
  9. package/dist/cli/index.js.map +1 -1
  10. package/dist/dashboard/history-store.d.ts +13 -9
  11. package/dist/dashboard/history-store.d.ts.map +1 -1
  12. package/dist/dashboard/history-store.js +19 -28
  13. package/dist/dashboard/history-store.js.map +1 -1
  14. package/dist/dashboard/html-renderer.d.ts +1 -1
  15. package/dist/dashboard/html-renderer.d.ts.map +1 -1
  16. package/dist/dashboard/html-renderer.js +2 -2
  17. package/dist/dashboard/html-renderer.js.map +1 -1
  18. package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
  19. package/dist/dashboard/snapshot-builder.js +38 -2
  20. package/dist/dashboard/snapshot-builder.js.map +1 -1
  21. package/dist/dashboard/templates/index.html +142 -221
  22. package/dist/exporters/csv-exporter.d.ts +4 -0
  23. package/dist/exporters/csv-exporter.d.ts.map +1 -1
  24. package/dist/exporters/csv-exporter.js +35 -26
  25. package/dist/exporters/csv-exporter.js.map +1 -1
  26. package/dist/exporters/feature-parser.d.ts.map +1 -1
  27. package/dist/exporters/feature-parser.js +16 -4
  28. package/dist/exporters/feature-parser.js.map +1 -1
  29. package/dist/exporters/json-exporter.d.ts.map +1 -1
  30. package/dist/exporters/json-exporter.js +28 -20
  31. package/dist/exporters/json-exporter.js.map +1 -1
  32. package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
  33. package/dist/exporters/playwright-report-parser.js +22 -5
  34. package/dist/exporters/playwright-report-parser.js.map +1 -1
  35. package/dist/exporters/scenario-merger.d.ts +23 -1
  36. package/dist/exporters/scenario-merger.d.ts.map +1 -1
  37. package/dist/exporters/scenario-merger.js +39 -0
  38. package/dist/exporters/scenario-merger.js.map +1 -1
  39. package/dist/exporters/step-formatter.d.ts +31 -3
  40. package/dist/exporters/step-formatter.d.ts.map +1 -1
  41. package/dist/exporters/step-formatter.js +52 -19
  42. package/dist/exporters/step-formatter.js.map +1 -1
  43. package/dist/exporters/sun-logo.d.ts +10 -0
  44. package/dist/exporters/sun-logo.d.ts.map +1 -0
  45. package/dist/exporters/sun-logo.js +13 -0
  46. package/dist/exporters/sun-logo.js.map +1 -0
  47. package/dist/exporters/test-data-resolver.d.ts +13 -5
  48. package/dist/exporters/test-data-resolver.d.ts.map +1 -1
  49. package/dist/exporters/test-data-resolver.js +36 -14
  50. package/dist/exporters/test-data-resolver.js.map +1 -1
  51. package/dist/exporters/types.d.ts +16 -0
  52. package/dist/exporters/types.d.ts.map +1 -1
  53. package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
  54. package/dist/exporters/xlsx-exporter.js +169 -99
  55. package/dist/exporters/xlsx-exporter.js.map +1 -1
  56. package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +1 -1
  57. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +154 -67
  58. package/dist/orchestrator/templates/ai-instructions/claude-skill-test-design-techniques.md +39 -0
  59. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +1 -1
  60. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +134 -61
  61. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-test-design-techniques.md +39 -0
  62. package/dist/orchestrator/templates/playwright.config.d.ts.map +1 -1
  63. package/dist/orchestrator/templates/playwright.config.js +2 -0
  64. package/dist/orchestrator/templates/playwright.config.js.map +1 -1
  65. package/dist/orchestrator/templates/playwright.config.ts +2 -0
  66. package/dist/orchestrator/templates/specs-base.d.ts.map +1 -1
  67. package/dist/orchestrator/templates/specs-base.js +1 -5
  68. package/dist/orchestrator/templates/specs-base.js.map +1 -1
  69. package/dist/orchestrator/templates/specs-base.ts +1 -5
  70. package/package.json +1 -1
  71. package/src/cli/commands/dashboard.ts +9 -9
  72. package/src/cli/commands/delivery.ts +30 -0
  73. package/src/cli/index.ts +1 -1
  74. package/src/dashboard/history-store.ts +22 -28
  75. package/src/dashboard/html-renderer.ts +6 -2
  76. package/src/dashboard/snapshot-builder.ts +36 -2
  77. package/src/dashboard/templates/index.html +142 -221
  78. package/src/dashboard/types.ts +1 -1
  79. package/src/exporters/csv-exporter.ts +44 -27
  80. package/src/exporters/feature-parser.ts +27 -8
  81. package/src/exporters/json-exporter.ts +31 -21
  82. package/src/exporters/playwright-report-parser.ts +23 -5
  83. package/src/exporters/scenario-merger.ts +65 -1
  84. package/src/exporters/step-formatter.ts +48 -23
  85. package/src/exporters/sun-logo.ts +10 -0
  86. package/src/exporters/test-data-resolver.ts +37 -13
  87. package/src/exporters/types.ts +18 -1
  88. package/src/exporters/xlsx-exporter.ts +176 -101
  89. package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +1 -1
  90. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +154 -67
  91. package/src/orchestrator/templates/ai-instructions/claude-skill-test-design-techniques.md +39 -0
  92. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +1 -1
  93. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +134 -61
  94. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-test-design-techniques.md +39 -0
  95. package/src/orchestrator/templates/playwright.config.ts +2 -0
  96. package/src/orchestrator/templates/specs-base.ts +1 -5
@@ -8,7 +8,11 @@ import { parse as parseYaml } from 'yaml';
8
8
 
9
9
  /**
10
10
  * Load test-data.yaml into a flat key→value map.
11
- * Returns empty map if file missing.
11
+ * Nested objects are flattened with dot notation so flow files like
12
+ * kudos:
13
+ * recipient: "An"
14
+ * become { "kudos.recipient": "An" }, matching {{kudos.recipient}} refs.
15
+ * Arrays are JSON-stringified. Returns empty map if file missing.
12
16
  */
13
17
  export function loadTestData(testDataFilePath: string): Record<string, string> {
14
18
  if (!fs.existsSync(testDataFilePath)) return {};
@@ -16,38 +20,58 @@ export function loadTestData(testDataFilePath: string): Record<string, string> {
16
20
  const parsed = parseYaml(content);
17
21
  if (!parsed || typeof parsed !== 'object') return {};
18
22
 
19
- // Flatten shallow object to string values
20
23
  const result: Record<string, string> = {};
21
- for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {
24
+ flatten(parsed as Record<string, unknown>, '', result);
25
+ return result;
26
+ }
27
+
28
+ function flatten(obj: Record<string, unknown>, prefix: string, out: Record<string, string>): void {
29
+ for (const [key, value] of Object.entries(obj)) {
22
30
  if (value === null || value === undefined) continue;
23
- if (typeof value === 'object') continue; // skip nested structures
24
- result[key] = String(value);
31
+ const path = prefix ? `${prefix}.${key}` : key;
32
+ if (Array.isArray(value)) {
33
+ out[path] = JSON.stringify(value);
34
+ } else if (typeof value === 'object') {
35
+ flatten(value as Record<string, unknown>, path, out);
36
+ } else {
37
+ out[path] = String(value);
38
+ }
25
39
  }
26
- return result;
27
40
  }
28
41
 
29
42
  /**
30
- * Format a list of variable references into a "key: value; key2: value2" string
31
- * suitable for the CSV Test Data column.
43
+ * Format a list of variable references into a "key: value" string.
32
44
  *
33
- * Long values (>80 chars) are truncated with "…".
45
+ * - `separator` controls how pairs are joined. Default `'; '` keeps CSV/XLSX
46
+ * cells on one line. Pass `'\n'` for surfaces that render multi-line text
47
+ * (e.g. the dashboard modal uses whitespace-pre-wrap).
48
+ * - Test data is never truncated by default — values represent real test
49
+ * inputs and clients need to see them in full. Pass a finite `maxLen` only
50
+ * if the downstream surface requires a cap (e.g. terminal preview).
34
51
  */
35
52
  export function formatTestData(
36
53
  referencedVars: string[],
37
- testData: Record<string, string>
54
+ testData: Record<string, string>,
55
+ maxLen: number = Infinity,
56
+ separator: string = '; ',
38
57
  ): string {
39
58
  if (referencedVars.length === 0) return '';
59
+ // When joining with newlines (dashboard multi-line view), prefix each line
60
+ // with a bullet point so the list scans easily. CSV-style "; " join skips
61
+ // the bullet — single-cell text shouldn't have list markers.
62
+ const useBullet = separator.includes('\n');
40
63
  const pairs: string[] = [];
41
64
  for (const key of referencedVars) {
42
65
  const value = testData[key];
43
66
  if (value === undefined) continue;
44
- pairs.push(`${key}: ${truncate(value, 80)}`);
67
+ const line = `${key}: ${truncate(value, maxLen)}`;
68
+ pairs.push(useBullet ? `• ${line}` : line);
45
69
  }
46
- return pairs.join('; ');
70
+ return pairs.join(separator);
47
71
  }
48
72
 
49
73
  function truncate(s: string, max: number): string {
50
- if (s.length <= max) return s;
74
+ if (!isFinite(max) || s.length <= max) return s;
51
75
  return s.substring(0, max - 1) + '…';
52
76
  }
53
77
 
@@ -15,7 +15,7 @@ export interface TestCaseRow {
15
15
  testData: string; // semicolon-separated key: value
16
16
  steps: string; // numbered steps
17
17
  expectedResults: string; // numbered expected results
18
- priority: string; // Critical | High | Normal | Low
18
+ priority: string; // High | Normal | Low
19
19
  testcaseType: string; // Auto | Manual | Not compiled
20
20
  testResult: string; // Passed | Failed | Pending | N/A
21
21
  executedDate: string; // dd/mm/yyyy
@@ -59,9 +59,20 @@ export interface FeatureMetadata {
59
59
  featureName: string; // e.g., "Create Kudo Modal"
60
60
  featurePath?: string; // e.g., "/kudos"
61
61
  featureTags: string[]; // Feature-level tags
62
+ /** Background block classified by bucket. Shared across all runnable
63
+ * scenarios — the merger is responsible for prepending these. */
64
+ backgroundGivenSteps: string[];
65
+ backgroundWhenSteps: string[];
66
+ backgroundThenSteps: string[];
67
+ backgroundOrderedSteps: OrderedStep[];
62
68
  scenarios: ScenarioMetadata[];
63
69
  }
64
70
 
71
+ export interface OrderedStep {
72
+ text: string;
73
+ bucket: 'given' | 'when' | 'then';
74
+ }
75
+
65
76
  export interface ScenarioMetadata {
66
77
  name: string; // Full scenario name (with VP prefix)
67
78
  tags: string[]; // Scenario-level tags (merged with feature tags)
@@ -71,6 +82,12 @@ export interface ScenarioMetadata {
71
82
  rawGivenSteps: string[]; // Raw Given/And-after-Given step text
72
83
  rawWhenSteps: string[]; // Raw When/And-after-When step text
73
84
  rawThenSteps: string[]; // Raw Then/And-after-Then step text
85
+ /**
86
+ * Steps in original chronological order with each step labelled by its
87
+ * effective bucket (And/But inherits from the prior explicit keyword).
88
+ * Use this when ordering matters — e.g. mid-scenario `Given` after `When`.
89
+ */
90
+ orderedSteps: OrderedStep[];
74
91
  }
75
92
 
76
93
  /**
@@ -14,12 +14,13 @@ import * as path from 'path';
14
14
  import ExcelJS from 'exceljs';
15
15
  import { ScreenSummary, TestCaseRow } from './types';
16
16
  import { getPackageVersion } from './package-info';
17
+ import { SUN_LOGO_PNG_BASE64 } from './sun-logo';
17
18
 
18
19
  const COL_COUNT = 16;
19
- const HEADER_FILL = 'FF1F4E78'; // deep blue band
20
- const HEADER_FONT = 'FFFFFFFF';
20
+ const HEADER_FILL = 'FFD9D2E9'; // lavender matches the summary-header band on row 6
21
+ const HEADER_FONT = 'FF000000'; // black text reads better on the light lavender
21
22
  const META_FILL = 'FFDCE6F1'; // soft blue for meta rows
22
- const CATEGORY_FILL = 'FFFFF2CC'; // soft yellow for group dividers
23
+ const CATEGORY_FILL = 'FFD9D2E9'; // same lavender for group dividers (Accessing / GUI / Function)
23
24
  const BORDER_COLOR = 'FFBFBFBF';
24
25
 
25
26
  type AnyCell = ExcelJS.Cell;
@@ -51,111 +52,185 @@ export function renderXlsx(
51
52
  return `${String(d.getDate()).padStart(2, '0')}/${String(d.getMonth() + 1).padStart(2, '0')}/${d.getFullYear()}`;
52
53
  })();
53
54
 
54
- const total = summary.total || 1;
55
- const pct = (n: number) => `${Math.round((n / total) * 100)}%`;
56
-
57
- // -- Column widths (matches CSV column order) --
55
+ // -- Column widths matching template_report.xlsx (Sample sheet) --
58
56
  ws.columns = [
59
- { width: 18 }, // TC ID
60
- { width: 42 }, // Category 1
61
- { width: 14 }, // Category 2
62
- { width: 26 }, // Category 3
63
- { width: 14 }, // Category 4
64
- { width: 36 }, // Pre-condition
65
- { width: 30 }, // Test Data
66
- { width: 46 }, // Steps
67
- { width: 52 }, // Expected results
68
- { width: 10 }, // Priority
69
- { width: 14 }, // Testcase type
70
- { width: 14 }, // Test Result
71
- { width: 14 }, // Executed Date
72
- { width: 16 }, // Test Executor
73
- { width: 38 }, // Test Environment
74
- { width: 40 }, // Note
57
+ { width: 6.38 }, // A — TC ID
58
+ { width: 12.5 }, // B — Screen/Function
59
+ { width: 15.38 }, // C — Big item
60
+ { width: 16.25 }, // D — Medium item
61
+ { width: 17 }, // E — Test Object
62
+ { width: 25.5 }, // F — Pre-condition
63
+ { width: 24.88 }, // G — Test Data
64
+ { width: 24.88 }, // H — Steps
65
+ { width: 41 }, // I — Expected results
66
+ { width: 12 }, // J — Priority
67
+ { width: 13.5 }, // K — Testcase type
68
+ { width: 15.88 }, // L — Test Result
69
+ { width: 13.5 }, // M — Executed Date
70
+ { width: 16.5 }, // N — Test Executor
71
+ { width: 23 }, // O — Test Environment
72
+ { width: 29 }, // P — Note
75
73
  ];
76
74
 
77
- // -- Top metadata band (rows 1-4) --
75
+ // -- Top metadata band (rows 1-4) — 1:1 with template_report.xlsx --
76
+ const TIMES = 'Times New Roman';
77
+ const ARIAL_FONT = 'Arial';
78
+ const BLACK = { argb: 'FF000000' };
79
+ const WHITE = { argb: 'FFFFFFFF' };
80
+ const LAVENDER = { argb: 'FFD9D2E9' };
81
+ const thinBlack = { style: 'thin' as const, color: BLACK };
82
+ const allBordersBlack = { top: thinBlack, left: thinBlack, bottom: thinBlack, right: thinBlack };
83
+
84
+ // Ensure rows 1..8 exist before merging.
85
+ for (let r = 1; r <= 8; r++) ws.getRow(r);
86
+
87
+ ws.mergeCells('A1:C3');
88
+ ws.mergeCells('D1:F3');
89
+ ws.mergeCells('G1:H1');
90
+ ws.mergeCells('G2:H2');
91
+ ws.mergeCells('G3:H3');
92
+ ws.mergeCells('A4:F4');
93
+ ws.mergeCells('G4:H4');
94
+
95
+ // A1 (logo band, merged A1:C3) — give it bordering + embed the Sun* logo.
96
+ const a1 = ws.getCell('A1');
97
+ a1.alignment = { horizontal: 'center', vertical: 'middle' };
98
+ a1.border = allBordersBlack;
99
+
100
+ // Embed Sun* logo using a oneCellAnchor + explicit pixel `ext`, exactly
101
+ // like the template. tl is offset into the merged band so the logo lands
102
+ // centred in A1:C3 instead of pinned to the top-left corner.
103
+ try {
104
+ const imageId = wb.addImage({
105
+ buffer: Buffer.from(SUN_LOGO_PNG_BASE64, 'base64') as unknown as ExcelJS.Buffer,
106
+ extension: 'png',
107
+ });
108
+ // A1:C3 ≈ 240 × 60 px. For a 90×50 logo centred horizontally / vertically:
109
+ // left margin = (240 − 90)/2 ≈ 75 px → 9525 EMU/px → 714375 EMU
110
+ // top margin = (60 − 50)/2 ≈ 5 px → 47625 EMU
111
+ // ExcelJS's fractional `col`/`row` clamps to a small EMU range, so we
112
+ // bypass it and write `native*` fields directly (the underlying XML uses
113
+ // the same field names).
114
+ ws.addImage(imageId, {
115
+ tl: { nativeCol: 0, nativeColOff: 714375, nativeRow: 0, nativeRowOff: 47625 } as unknown as ExcelJS.Anchor,
116
+ ext: { width: 90, height: 50 },
117
+ editAs: 'oneCell',
118
+ } as unknown as ExcelJS.ImageRange);
119
+ } catch { /* logo is decorative — never block export */ }
120
+
121
+ // D1 (TESTCASE title)
78
122
  const titleLabel = `${summary.screen.toUpperCase()} TESTCASE`;
79
- const titleRow = ws.addRow(['', '', '', titleLabel, '', '', 'No: BM-2-901-13']);
80
- titleRow.getCell(4).font = { bold: true, size: 16 };
81
- titleRow.getCell(4).alignment = { horizontal: 'center', vertical: 'middle' };
82
- ws.mergeCells(titleRow.number, 4, titleRow.number, 6);
83
- titleRow.getCell(7).font = { bold: true };
84
- titleRow.height = 22;
85
-
86
- const versionRow = ws.addRow(['', '', '', '', '', '', `Version: ${getPackageVersion()}`]);
87
- versionRow.getCell(7).font = { bold: true };
88
- const dateRow = ws.addRow(['', '', '', '', '', '', `Issue Date: ${issueDate}`]);
89
- dateRow.getCell(7).font = { bold: true };
90
- const companyRow = ws.addRow([
91
- 'SUN ASTERISK VIETNAM CO., LTD',
92
- '',
93
- '',
94
- '',
95
- '',
96
- '',
97
- 'ISO/IEC 27001:2022 & ISO 9001:2015',
98
- ]);
99
- companyRow.getCell(1).font = { bold: true };
100
- companyRow.getCell(7).font = { bold: true };
123
+ const d1 = ws.getCell('D1');
124
+ d1.value = titleLabel;
125
+ d1.font = { bold: true, size: 18, name: TIMES };
126
+ d1.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true };
127
+ d1.border = allBordersBlack;
101
128
 
102
- ws.addRow([]); // spacer
129
+ // G1 — No: BM-2-901-13
130
+ const g1 = ws.getCell('G1');
131
+ g1.value = 'No: BM-2-901-13';
132
+ g1.font = { size: 12, name: TIMES };
133
+ g1.alignment = { vertical: 'middle' };
134
+ g1.border = { top: thinBlack, left: thinBlack, right: thinBlack };
103
135
 
104
- // -- Summary band --
105
- const summaryHeader = ws.addRow([
106
- '',
107
- '',
108
- 'Total TCs',
109
- 'Passed',
110
- 'Failed',
111
- 'Pending',
112
- 'N/A',
113
- 'Remaining',
114
- ]);
115
- for (let i = 3; i <= 8; i++) {
116
- const c = summaryHeader.getCell(i);
117
- c.font = { bold: true, color: { argb: HEADER_FONT } };
118
- c.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: HEADER_FILL } };
119
- c.alignment = { horizontal: 'center', vertical: 'middle' };
120
- applyBorder(c);
136
+ // G2 Version
137
+ const g2 = ws.getCell('G2');
138
+ g2.value = `Version: ${getPackageVersion()}`;
139
+ g2.font = { size: 12, name: TIMES };
140
+ g2.alignment = { vertical: 'middle' };
141
+ g2.border = { left: thinBlack, right: thinBlack };
142
+
143
+ // G3 — Issue Date
144
+ const g3 = ws.getCell('G3');
145
+ g3.value = `Issue Date: ${issueDate}`;
146
+ g3.font = { size: 12, name: TIMES };
147
+ g3.alignment = { vertical: 'middle' };
148
+ g3.border = { left: thinBlack, right: thinBlack, bottom: thinBlack };
149
+
150
+ // A4 SUN ASTERISK VIETNAM CO., LTD
151
+ const a4 = ws.getCell('A4');
152
+ a4.value = 'SUN ASTERISK VIETNAM CO., LTD';
153
+ a4.font = { bold: true, size: 10, name: TIMES };
154
+ a4.alignment = { vertical: 'middle' };
155
+ a4.border = allBordersBlack;
156
+
157
+ // G4 — ISO/IEC ...
158
+ const g4 = ws.getCell('G4');
159
+ g4.value = 'ISO/IEC 27001:2022 & ISO 9001:2015';
160
+ g4.font = { bold: true, size: 10, name: TIMES };
161
+ g4.alignment = { horizontal: 'right', vertical: 'middle', wrapText: true };
162
+ g4.border = allBordersBlack;
163
+
164
+ // -- Row 5: spacer (height 12) --
165
+ ws.getRow(5).height = 12;
166
+
167
+ // -- Row 6: Summary headers (cols C..H), lavender fill --
168
+ const sumLabels: Record<string, string> = {
169
+ C: 'Total TCs', D: 'Passed', E: 'Failed', F: 'Pending', G: 'N/A', H: 'Remaining',
170
+ };
171
+ for (const [col, label] of Object.entries(sumLabels)) {
172
+ const c = ws.getCell(`${col}6`);
173
+ c.value = label;
174
+ c.font = { bold: true, size: 10, color: BLACK, name: ARIAL_FONT };
175
+ c.fill = { type: 'pattern', pattern: 'solid', fgColor: LAVENDER };
176
+ c.alignment = { horizontal: 'center', vertical: 'top' };
177
+ c.border = allBordersBlack;
121
178
  }
179
+ ws.getRow(6).height = 12;
122
180
 
123
- const summaryCounts = ws.addRow([
124
- '',
125
- '',
126
- summary.total,
127
- summary.passed,
128
- summary.failed,
129
- summary.pending,
130
- summary.na,
131
- summary.pending + summary.na,
132
- ]);
133
- for (let i = 3; i <= 8; i++) {
134
- const c = summaryCounts.getCell(i);
135
- c.font = { bold: true };
136
- c.alignment = { horizontal: 'center' };
137
- c.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: META_FILL } };
138
- applyBorder(c);
181
+ // -- Row 7: Counts (cols C..H), live formulas referencing the data area --
182
+ // Data table starts at row 15 (col header at row 14, divider at row 14? actually
183
+ // header at row 13 + divider at 14 + first data at 15). The formulas read col L
184
+ // (Test Result) for status counts, col A (TC ID) for total via COUNTA.
185
+ const COUNT_RANGE_END = 10000;
186
+ const r7Values: Record<string, ExcelJS.CellValue> = {
187
+ C: { formula: `COUNTA(A15:A${COUNT_RANGE_END})`, result: summary.total } as ExcelJS.CellFormulaValue,
188
+ D: { formula: `COUNTIF(L15:L${COUNT_RANGE_END},"Passed")`, result: summary.passed } as ExcelJS.CellFormulaValue,
189
+ E: { formula: `COUNTIF(L15:L${COUNT_RANGE_END},"Failed")`, result: summary.failed } as ExcelJS.CellFormulaValue,
190
+ F: { formula: `COUNTIF(L15:L${COUNT_RANGE_END},"Pending")`, result: summary.pending } as ExcelJS.CellFormulaValue,
191
+ G: { formula: `COUNTIF(L15:L${COUNT_RANGE_END},"N/A")`, result: summary.na } as ExcelJS.CellFormulaValue,
192
+ H: { formula: 'C7-D7-G7', result: summary.failed + summary.pending } as ExcelJS.CellFormulaValue,
193
+ };
194
+ for (const [col, val] of Object.entries(r7Values)) {
195
+ const c = ws.getCell(`${col}7`);
196
+ c.value = val;
197
+ c.font = { size: 10, color: BLACK, name: ARIAL_FONT };
198
+ c.fill = { type: 'pattern', pattern: 'solid', fgColor: WHITE };
199
+ c.alignment = { horizontal: 'center', vertical: 'top' };
200
+ c.border = allBordersBlack;
201
+ if (col === 'H') c.numFmt = '#,##0';
139
202
  }
203
+ ws.getRow(7).height = 12;
140
204
 
141
- const summaryPct = ws.addRow([
142
- '',
143
- '',
144
- '',
145
- pct(summary.passed),
146
- pct(summary.failed),
147
- pct(summary.pending),
148
- pct(summary.na),
149
- pct(summary.pending + summary.na),
150
- ]);
151
- for (let i = 4; i <= 8; i++) {
152
- const c = summaryPct.getCell(i);
153
- c.alignment = { horizontal: 'center' };
154
- c.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: META_FILL } };
155
- applyBorder(c);
205
+ // C8 has no value in the template but still needs a border so the band
206
+ // visually closes underneath "Total TCs".
207
+ const c8 = ws.getCell('C8');
208
+ c8.font = { size: 10, color: BLACK, name: ARIAL_FONT };
209
+ c8.fill = { type: 'pattern', pattern: 'solid', fgColor: WHITE };
210
+ c8.alignment = { horizontal: 'center', vertical: 'top' };
211
+ c8.border = allBordersBlack;
212
+
213
+ // -- Row 8: Percentages (cols D..H), formula = count / (Total − N/A) --
214
+ const r8Values: Record<string, ExcelJS.CellValue> = {
215
+ D: { formula: 'IFERROR(D7/(C7-G7),0)', result: summary.passed / Math.max(1, summary.total - summary.na) } as ExcelJS.CellFormulaValue,
216
+ E: { formula: 'IFERROR(E7/(C7-G7),0)', result: summary.failed / Math.max(1, summary.total - summary.na) } as ExcelJS.CellFormulaValue,
217
+ F: { formula: 'IFERROR(F7/(C7-G7),0)', result: summary.pending / Math.max(1, summary.total - summary.na) } as ExcelJS.CellFormulaValue,
218
+ G: { formula: 'IFERROR(G7/(C7-G7),0)', result: summary.na / Math.max(1, summary.total - summary.na) } as ExcelJS.CellFormulaValue,
219
+ H: { formula: 'IFERROR(H7/(C7-G7),0)', result: (summary.failed + summary.pending) / Math.max(1, summary.total - summary.na) } as ExcelJS.CellFormulaValue,
220
+ };
221
+ for (const [col, val] of Object.entries(r8Values)) {
222
+ const c = ws.getCell(`${col}8`);
223
+ c.value = val;
224
+ c.font = { size: 10, color: BLACK, name: ARIAL_FONT };
225
+ c.fill = { type: 'pattern', pattern: 'solid', fgColor: WHITE };
226
+ c.alignment = { horizontal: 'center', vertical: 'top' };
227
+ c.border = allBordersBlack;
228
+ c.numFmt = '0%';
156
229
  }
230
+ ws.getRow(8).height = 12;
157
231
 
158
- ws.addRow([]); // spacer
232
+ // After rows 1-8 are populated, append a blank row 9 spacer.
233
+ while (ws.rowCount < 9) ws.addRow([]);
159
234
 
160
235
  const specRow = ws.addRow(['Spec/Design link:', specLink]);
161
236
  specRow.getCell(1).font = { bold: true };
@@ -169,10 +244,10 @@ export function renderXlsx(
169
244
  // -- Column header row (bold, filled, wrapped) --
170
245
  const headerRow = ws.addRow([
171
246
  'TC ID*',
172
- '{Category 1}',
173
- '{Category 2}',
174
- '{Category 3}',
175
- '{Category 4}',
247
+ 'Screen/Function',
248
+ 'Big item',
249
+ 'Medium item',
250
+ 'Test Object',
176
251
  'Pre-condition',
177
252
  'Test Data',
178
253
  'Steps*',
@@ -222,10 +297,10 @@ export function renderXlsx(
222
297
  for (const r of groupRows) {
223
298
  const row = ws.addRow([
224
299
  r.tcId,
225
- r.category1,
226
300
  '',
227
301
  '',
228
302
  '',
303
+ r.category1,
229
304
  r.precondition,
230
305
  r.testData,
231
306
  r.steps,
@@ -51,7 +51,7 @@ Parse **name** from `$ARGUMENTS`. If missing, ask the user.
51
51
 
52
52
  Summarize what you found in requirements and present to the user.
53
53
 
54
- 4. Follow the `sungen-tc-generation` skill for section identification, viewpoint generation, and output format. **For flows**, use the "Flow Test Generation" section in the skill. When requirements exist, use the "Requirements-Driven Generation" strategy.
54
+ 4. Follow the `sungen-tc-generation` skill for section identification, viewpoint generation, and output format. **For flows**, use the "Flow Test Generation" section in the skill. When requirements exist, use the "Requirements-Driven Generation" strategy. **For Tier 1**, apply the **Lightweight Guard** — verify required fields, validation rules, business rules, security checks, and key state transitions all have TCs after generation. **For Tier 2+**, **MUST** apply the full **Mapping Contract** — walk every `spec.md` section top-to-bottom and produce the indicated TCs per Table 1; handle `test-viewpoint.md` per Table 2. Do not silently skip sections.
55
55
  5. Generate or update `.feature` + `test-data.yaml` following `sungen-gherkin-syntax` and `sungen-tc-generation` skills. **For flows**: use `[Screen:Element]` namespace format, namespace test-data by phase, add `@flow` tag.
56
56
  6. Show summary, then use `AskUserQuestion` to offer next steps based on which tier was just generated:
57
57