@sun-asterisk/sungen 3.0.0-beta.75 → 3.0.0-beta.78

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 (76) hide show
  1. package/dist/cli/commands/audit.d.ts.map +1 -1
  2. package/dist/cli/commands/audit.js +10 -0
  3. package/dist/cli/commands/audit.js.map +1 -1
  4. package/dist/cli/commands/delivery.d.ts.map +1 -1
  5. package/dist/cli/commands/delivery.js +30 -14
  6. package/dist/cli/commands/delivery.js.map +1 -1
  7. package/dist/cli/commands/ingest.d.ts +3 -0
  8. package/dist/cli/commands/ingest.d.ts.map +1 -0
  9. package/dist/cli/commands/ingest.js +179 -0
  10. package/dist/cli/commands/ingest.js.map +1 -0
  11. package/dist/cli/index.js +2 -0
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/dashboard/templates/index.html +54 -54
  14. package/dist/harness/audit.d.ts +2 -0
  15. package/dist/harness/audit.d.ts.map +1 -1
  16. package/dist/harness/audit.js +15 -4
  17. package/dist/harness/audit.js.map +1 -1
  18. package/dist/harness/capability-plan.d.ts +6 -0
  19. package/dist/harness/capability-plan.d.ts.map +1 -1
  20. package/dist/harness/capability-plan.js +13 -0
  21. package/dist/harness/capability-plan.js.map +1 -1
  22. package/dist/harness/spec-coverage.d.ts +37 -0
  23. package/dist/harness/spec-coverage.d.ts.map +1 -0
  24. package/dist/harness/spec-coverage.js +159 -0
  25. package/dist/harness/spec-coverage.js.map +1 -0
  26. package/dist/ingest/baseline-audit.d.ts +38 -0
  27. package/dist/ingest/baseline-audit.d.ts.map +1 -0
  28. package/dist/ingest/baseline-audit.js +85 -0
  29. package/dist/ingest/baseline-audit.js.map +1 -0
  30. package/dist/ingest/gsheet-fetch.d.ts +9 -0
  31. package/dist/ingest/gsheet-fetch.d.ts.map +1 -0
  32. package/dist/ingest/gsheet-fetch.js +180 -0
  33. package/dist/ingest/gsheet-fetch.js.map +1 -0
  34. package/dist/ingest/index.d.ts +6 -0
  35. package/dist/ingest/index.d.ts.map +1 -0
  36. package/dist/ingest/index.js +22 -0
  37. package/dist/ingest/index.js.map +1 -0
  38. package/dist/ingest/legacy-parser.d.ts +39 -0
  39. package/dist/ingest/legacy-parser.d.ts.map +1 -0
  40. package/dist/ingest/legacy-parser.js +218 -0
  41. package/dist/ingest/legacy-parser.js.map +1 -0
  42. package/dist/ingest/reconcile.d.ts +30 -0
  43. package/dist/ingest/reconcile.d.ts.map +1 -0
  44. package/dist/ingest/reconcile.js +65 -0
  45. package/dist/ingest/reconcile.js.map +1 -0
  46. package/dist/ingest/to-gherkin.d.ts +33 -0
  47. package/dist/ingest/to-gherkin.d.ts.map +1 -0
  48. package/dist/ingest/to-gherkin.js +93 -0
  49. package/dist/ingest/to-gherkin.js.map +1 -0
  50. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  51. package/dist/orchestrator/ai-rules-updater.js +2 -0
  52. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  53. package/dist/orchestrator/templates/ai-instructions/claude-skill-delivery.md +10 -0
  54. package/dist/orchestrator/templates/ai-instructions/claude-skill-ingest-legacy.md +79 -0
  55. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +10 -0
  56. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-ingest-legacy.md +79 -0
  57. package/package.json +3 -3
  58. package/src/cli/commands/audit.ts +7 -0
  59. package/src/cli/commands/delivery.ts +31 -15
  60. package/src/cli/commands/ingest.ts +141 -0
  61. package/src/cli/index.ts +2 -0
  62. package/src/dashboard/templates/index.html +54 -54
  63. package/src/harness/audit.ts +17 -4
  64. package/src/harness/capability-plan.ts +11 -0
  65. package/src/harness/spec-coverage.ts +139 -0
  66. package/src/ingest/baseline-audit.ts +100 -0
  67. package/src/ingest/gsheet-fetch.ts +152 -0
  68. package/src/ingest/index.ts +5 -0
  69. package/src/ingest/legacy-parser.ts +184 -0
  70. package/src/ingest/reconcile.ts +80 -0
  71. package/src/ingest/to-gherkin.ts +108 -0
  72. package/src/orchestrator/ai-rules-updater.ts +2 -0
  73. package/src/orchestrator/templates/ai-instructions/claude-skill-delivery.md +10 -0
  74. package/src/orchestrator/templates/ai-instructions/claude-skill-ingest-legacy.md +79 -0
  75. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +10 -0
  76. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-ingest-legacy.md +79 -0
@@ -0,0 +1,141 @@
1
+ import { Command } from 'commander';
2
+ import * as path from 'path';
3
+ import * as fs from 'fs';
4
+ import { parseLegacyFile, listSheets, baselineAudit, BaselineReport, LegacyInventory, inventoryToGherkin, reconcileViewpoints, renderViewpointOverview, fetchGoogleSheet } from '../../ingest';
5
+
6
+ function renderInventoryMd(inv: LegacyInventory, r: BaselineReport): string {
7
+ const lines: string[] = [];
8
+ lines.push(`# Legacy Testcase Inventory — ${inv.source.file}`, '');
9
+ lines.push(`Total testcases: **${r.total}**`, '');
10
+ lines.push('## Sheets', '', '| Sheet | Type | Rows |', '|---|---|---|');
11
+ for (const s of r.sheets) lines.push(`| ${s.name} | ${s.type} | ${s.rows} |`);
12
+ lines.push('', '## By category', '');
13
+ for (const [k, v] of Object.entries(r.byCategory).sort((a, b) => b[1] - a[1])) lines.push(`- ${k}: ${v}`);
14
+ lines.push('', `Depth: **${(r.depthRatio * 100).toFixed(0)}%** (${r.deepCount}/${r.total} assert a concrete expected value)`, '');
15
+ return lines.join('\n');
16
+ }
17
+
18
+ function render(inv: LegacyInventory, r: BaselineReport): void {
19
+ const L = console.log;
20
+ const pct = (x: number) => (r.total ? Math.round((x / r.total) * 100) : 0);
21
+ L('');
22
+ L(`━━━ Legacy Ingest — ${inv.source.file} ━━━`);
23
+ L('');
24
+ L(` Sheets:`);
25
+ for (const s of r.sheets) L(` • ${s.name} — ${s.type} (${s.rows})`);
26
+ L('');
27
+ L(` TOTAL testcases: ${r.total}`);
28
+ L('');
29
+ L(' ── QA baseline ──');
30
+ L(` By category: ${Object.entries(r.byCategory).sort((a, b) => b[1] - a[1]).slice(0, 12).map(([k, v]) => `${k}=${v}`).join(' ')}`);
31
+ L(` By priority: ${Object.entries(r.byPriority).map(([k, v]) => `${k}=${v}`).join(' ')}`);
32
+ L(` By result: ${Object.entries(r.byResult).map(([k, v]) => `${k}=${v}`).join(' ')}`);
33
+ L(` Depth: ${r.deepCount}/${r.total} (${(r.depthRatio * 100).toFixed(0)}%) assert a concrete expected value → convert to DEEP Gherkin`);
34
+ L(` Duplicates: ${r.duplicateClusters} same-shape cluster(s), ${r.exactDuplicates} likely exact`);
35
+ L('');
36
+ L(' ── Capability Plan (which driver, if any) ──');
37
+ L(` UI automatable : ${r.reasons.ui} (${pct(r.reasons.ui)}%)`);
38
+ L(` cross-screen → flow : ${r.reasons.crossScreen} (${pct(r.reasons.crossScreen)}%)`);
39
+ L(` capability-manual : ${r.reasons.capabilityManual} (${pct(r.reasons.capabilityManual)}%)`);
40
+ L(` keep-manual : ${r.reasons.keepManual} (${pct(r.reasons.keepManual)}%)`);
41
+ if (r.reasons.driverCandidates.length)
42
+ L(` driver candidates : ${r.reasons.driverCandidates.map((d) => `${d.driver}×${d.count}`).join(' ')}`);
43
+ else
44
+ L(` driver candidates : none (no capability-manual at scale → no driver justified)`);
45
+ L('');
46
+ }
47
+
48
+ export function registerIngestCommand(program: Command): void {
49
+ program
50
+ .command('ingest')
51
+ .description('Ingest a legacy manual testcase workbook (CSV/XLSX/JSON-bundle) → normalized inventory + QA baseline audit')
52
+ .option('--legacy <file...>', 'Path(s) to the legacy testcase file(s): .csv, .xlsx, or a .json sheet-bundle')
53
+ .option('--gsheet <urlOrId>', 'Fetch a Google Sheet (all tabs) under YOUR Google identity (ADC, read-only) → bundle. Needs: npm i googleapis + gcloud auth application-default login')
54
+ .option('-s, --screen <name>', 'Screen name (output goes under qa/screens/<name>/requirements/legacy/)')
55
+ .option('--out <dir>', 'Output directory (overrides the default screen path)')
56
+ .option('--sheets <names>', 'Comma-separated tab names to ingest (default: all). Workbooks carry many tabs.')
57
+ .option('--list-sheets', 'List the tabs + detected type (testcase/viewpoint-matrix/ui-checklist) and exit')
58
+ .option('--emit-gherkin', 'Also emit a traceable Gherkin DRAFT (.legacy-draft.feature) + trace map (P-B)')
59
+ .option('--json', 'Print the raw inventory + baseline JSON')
60
+ .action(async (options) => {
61
+ try {
62
+ if (!options.legacy && !options.gsheet) throw new Error('Provide --legacy <file...> or --gsheet <url|id>');
63
+
64
+ const outDir = options.out
65
+ ? path.resolve(process.cwd(), options.out)
66
+ : options.screen
67
+ ? path.join(process.cwd(), 'qa', 'screens', options.screen, 'requirements', 'legacy')
68
+ : path.join(process.cwd(), '.sungen', 'legacy');
69
+ fs.mkdirSync(outDir, { recursive: true });
70
+
71
+ let files: string[];
72
+ if (options.gsheet) {
73
+ // Fetch as the user (not AI) — bypasses the AI-context DLP legitimately.
74
+ const bundle = await fetchGoogleSheet(String(options.gsheet));
75
+ const bundlePath = path.join(outDir, 'bundle.json');
76
+ fs.writeFileSync(bundlePath, JSON.stringify(bundle, null, 0));
77
+ console.log(` Fetched Google Sheet "${bundle.source}" → ${bundle.sheets.length} tab(s) → ${path.relative(process.cwd(), bundlePath)}`);
78
+ files = [bundlePath];
79
+ } else {
80
+ files = (Array.isArray(options.legacy) ? options.legacy : [options.legacy])
81
+ .map((f: string) => path.resolve(process.cwd(), f));
82
+ for (const f of files) if (!fs.existsSync(f)) throw new Error(`File not found: ${f}`);
83
+ }
84
+
85
+ if (options.listSheets) {
86
+ const sheets = await listSheets(files);
87
+ console.log('');
88
+ console.log(' Tabs found:');
89
+ for (const s of sheets) console.log(` • ${s.name} — ${s.type} (${s.rows} rows)`);
90
+ console.log('');
91
+ console.log(` Ingest the testcase tab(s) with: --sheets "${sheets.filter((s) => s.type === 'testcase').map((s) => s.name).join(',')}"`);
92
+ console.log('');
93
+ return;
94
+ }
95
+
96
+ const onlySheets: string[] | undefined = options.sheets ? String(options.sheets).split(',') : undefined;
97
+ const inv = await parseLegacyFile(files, onlySheets);
98
+ const report = baselineAudit(inv);
99
+ fs.writeFileSync(path.join(outDir, 'inventory.json'), JSON.stringify({ inventory: inv, baseline: report }, null, 2));
100
+ fs.writeFileSync(path.join(outDir, 'inventory.md'), renderInventoryMd(inv, report));
101
+
102
+ let convert;
103
+ let recon;
104
+ if (options.emitGherkin) {
105
+ const featureName = options.screen || (files.length === 1 ? path.basename(files[0]).replace(/\.[^.]+$/, '') : 'legacy');
106
+ convert = inventoryToGherkin(inv, featureName);
107
+ fs.writeFileSync(path.join(outDir, `${featureName}.legacy-draft.feature`), convert.feature);
108
+ fs.writeFileSync(path.join(outDir, 'legacy-trace.json'), JSON.stringify(convert.trace, null, 2));
109
+ // Viewpoint reconciliation: legacy coverage vs catalog → blind-spots (P-C)
110
+ recon = reconcileViewpoints(inv);
111
+ fs.writeFileSync(path.join(outDir, 'test-viewpoint.draft.md'), renderViewpointOverview(featureName, recon));
112
+ }
113
+
114
+ if (options.json) console.log(JSON.stringify({ inventory: inv, baseline: report, convert, reconciliation: recon }, null, 2));
115
+ else {
116
+ render(inv, report);
117
+ if (convert) {
118
+ const g = convert.gap;
119
+ console.log(' ── Gherkin draft (P-B) ──');
120
+ console.log(` ${g.total} scenario(s) drafted → ui=${g.ui} · cross-screen=${g.crossScreen} · @manual(capability)=${g.manualCapability} · @manual(keep)=${g.manualKeep}`);
121
+ if (g.noExpected) console.log(` ⚠ ${g.noExpected} testcase(s) without an Expected → needs review`);
122
+ console.log(` Draft: ${path.relative(process.cwd(), outDir)}/*.legacy-draft.feature (refine via /sungen:create-test)`);
123
+ }
124
+ if (recon) {
125
+ console.log(' ── Viewpoint reconciliation (legacy vs catalog) ──');
126
+ console.log(` page-type: ${recon.pageType ?? 'unknown'} · legacy covers ${recon.themesCovered}/${recon.themesTotal} catalog themes (${(recon.coverageRatio * 100).toFixed(0)}%)`);
127
+ if (recon.blindSpots.length)
128
+ console.log(` ⚠ BLIND-SPOTS (catalog expects, legacy lacks): ${recon.blindSpots.map((b) => `${b.theme}[${b.status}]`).join(', ')}`);
129
+ else
130
+ console.log(` ✓ no catalog blind-spots — legacy covers the expected themes`);
131
+ console.log(` Draft viewpoint: ${path.relative(process.cwd(), outDir)}/test-viewpoint.draft.md → seed /sungen:create-test`);
132
+ }
133
+ console.log(` Inventory: ${path.relative(process.cwd(), outDir)}/inventory.json`);
134
+ console.log('');
135
+ }
136
+ } catch (error) {
137
+ console.error('Error:', error instanceof Error ? error.message : error);
138
+ process.exit(1);
139
+ }
140
+ });
141
+ }
package/src/cli/index.ts CHANGED
@@ -15,6 +15,7 @@ import { registerFigmaCommand } from './commands/figma';
15
15
  import { registerAddFlowCommand } from './commands/add-flow';
16
16
  import { registerDashboardCommand } from './commands/dashboard';
17
17
  import { registerAuditCommand } from './commands/audit';
18
+ import { registerIngestCommand } from './commands/ingest';
18
19
  import { registerManifestCommand } from './commands/manifest';
19
20
  import { registerLedgerCommand } from './commands/ledger';
20
21
  import { registerFeedbackCommand } from './commands/feedback';
@@ -60,6 +61,7 @@ async function main() {
60
61
  registerBlindspotCommand(program);
61
62
  registerCapabilityCommand(program);
62
63
  registerFlowCheckCommand(program);
64
+ registerIngestCommand(program);
63
65
 
64
66
  await program.parseAsync(process.argv);
65
67
  }