@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,80 @@
1
+ /**
2
+ * Viewpoint Reconciliation (P-C) — the link between a legacy suite and the Harness.
3
+ *
4
+ * A 1:1 convert reproduces the legacy suite's blind spots. Reconciliation runs the
5
+ * harness viewpoint-gate over the legacy-derived scenarios to answer: which catalog-
6
+ * expected viewpoints does the legacy suite ALREADY cover, and which are MISSING
7
+ * (blind spots the old QA never wrote)? It emits a draft viewpoint overview that
8
+ * seeds `/sungen:create-test` + the harness coverage-gate, so the generated suite is
9
+ * raised to catalog quality instead of merely mirroring the legacy.
10
+ */
11
+ import { LegacyInventory } from './legacy-parser';
12
+ import { legacyToScenarioInfo } from './baseline-audit';
13
+ import { loadCatalog, viewpointGate } from '../harness/sensors';
14
+
15
+ export interface Reconciliation {
16
+ pageType: string | null;
17
+ coverageRatio: number;
18
+ themesCovered: number;
19
+ themesTotal: number;
20
+ blindSpots: { theme: string; status: 'missing' | 'shallow' }[]; // catalog themes the legacy suite lacks/under-covers
21
+ universalGaps: string[];
22
+ legacyViewpoints: { vpGroup: string; count: number }[]; // what the legacy suite implies
23
+ }
24
+
25
+ function vpGroupOf(category: string): string {
26
+ const c = (category || '').toLowerCase();
27
+ if (/search|filter|điều kiện|条件/.test(c)) return 'SEARCH';
28
+ if (/input|validation|nhập|入力/.test(c)) return 'VAL';
29
+ if (/display|hiển thị|表示|data/.test(c)) return 'DISPLAY';
30
+ if (/layout|responsive|tab order|ui|giao diện/.test(c)) return 'UI';
31
+ if (/flow|navigation|遷移|chuyển|event/.test(c)) return 'NAV';
32
+ if (/paging|sort|phân trang/.test(c)) return 'LIST';
33
+ return (c.replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').toUpperCase().slice(0, 8)) || 'GEN';
34
+ }
35
+
36
+ export function reconcileViewpoints(inv: LegacyInventory): Reconciliation {
37
+ const scenarios = inv.testcases.map(legacyToScenarioInfo);
38
+ const catalog = loadCatalog();
39
+ const gate = viewpointGate(scenarios, [], catalog);
40
+
41
+ const groups: Record<string, number> = {};
42
+ for (const tc of inv.testcases) {
43
+ const g = vpGroupOf(tc.category);
44
+ groups[g] = (groups[g] || 0) + 1;
45
+ }
46
+
47
+ return {
48
+ pageType: gate.pageType,
49
+ coverageRatio: gate.coverageRatio,
50
+ themesCovered: gate.themesCovered,
51
+ themesTotal: gate.themesTotal,
52
+ blindSpots: gate.gaps.map((g) => ({ theme: g.theme, status: g.status })),
53
+ universalGaps: gate.universalGaps,
54
+ legacyViewpoints: Object.entries(groups).map(([vpGroup, count]) => ({ vpGroup, count }))
55
+ .sort((a, b) => b.count - a.count),
56
+ };
57
+ }
58
+
59
+ /** A draft test-viewpoint.md: legacy-derived viewpoints + catalog blind-spots to fill. */
60
+ export function renderViewpointOverview(featureName: string, r: Reconciliation): string {
61
+ const L: string[] = [];
62
+ L.push(`# Test Viewpoint — ${featureName} (drafted from legacy + reconciled with the catalog)`, '');
63
+ L.push('> Auto-drafted by `sungen ingest`. Rows from the legacy suite are listed first;');
64
+ L.push('> rows marked **⚠ BLIND-SPOT** are catalog-expected viewpoints the legacy suite does');
65
+ L.push('> NOT cover — add scenarios for them via `/sungen:create-test`. Refine before use.', '');
66
+ L.push(`Detected page-type: \`${r.pageType ?? 'unknown'}\` · legacy covers ${r.themesCovered}/${r.themesTotal} catalog themes (${(r.coverageRatio * 100).toFixed(0)}%)`, '');
67
+ L.push('## Priority Viewpoints', '', '| VP | Priority | Reason |', '|---|---|---|');
68
+ let n = 1;
69
+ for (const v of r.legacyViewpoints) {
70
+ L.push(`| VP-${v.vpGroup}-${String(n).padStart(3, '0')} | Medium | legacy suite: ${v.count} testcase(s) |`);
71
+ n++;
72
+ }
73
+ for (const b of r.blindSpots) {
74
+ L.push(`| VP-GAP-${String(n).padStart(3, '0')} | High | ⚠ BLIND-SPOT — catalog expects "${b.theme}" (${b.status}); legacy has none |`);
75
+ n++;
76
+ }
77
+ if (r.universalGaps.length)
78
+ L.push('', `> Universal reminders not covered: ${r.universalGaps.join(', ')}`);
79
+ return L.join('\n') + '\n';
80
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Legacy inventory → Gherkin DRAFT + traceability + gap report (P-B).
3
+ *
4
+ * Deterministic SCAFFOLDING only: it preserves every legacy testcase as a scenario
5
+ * with its source (TC id + verbatim Steps/Expected as comments), the right priority +
6
+ * @manual tags (from the capability classifier), and a Given seeded from the
7
+ * precondition. The free-text → real `[Reference] type` step mapping is a SEMANTIC step
8
+ * done by the AI layer (`/sungen:create-test`) on top of this draft — the draft makes
9
+ * that refinement traceable and correctly scoped, it is not final runnable Gherkin.
10
+ */
11
+ import { LegacyInventory, LegacyTestcase } from './legacy-parser';
12
+ import { classifyReason, MANUAL_REASONS } from '../harness/capability-plan';
13
+
14
+ export interface TraceEntry {
15
+ tcId: string;
16
+ scenario: string;
17
+ vpGroup: string;
18
+ mode: 'ui' | 'cross-screen' | 'manual-capability' | 'manual-keep';
19
+ reasonCode: string; // '' for ui
20
+ drivers: string[];
21
+ }
22
+
23
+ export interface ConvertResult {
24
+ feature: string; // the draft .feature text
25
+ trace: TraceEntry[];
26
+ gap: {
27
+ total: number; ui: number; crossScreen: number;
28
+ manualCapability: number; manualKeep: number; noExpected: number;
29
+ };
30
+ }
31
+
32
+ // Map a legacy category phrase → a VP group code (best-effort; falls back to a slug).
33
+ function vpGroupOf(category: string): string {
34
+ const c = (category || '').toLowerCase();
35
+ if (/search|filter|điều kiện|条件/.test(c)) return 'SEARCH';
36
+ if (/input|validation|nhập|入力/.test(c)) return 'VAL';
37
+ if (/display|hiển thị|表示|data/.test(c)) return 'DISPLAY';
38
+ if (/layout|responsive|tab order|ui|giao diện/.test(c)) return 'UI';
39
+ if (/flow|navigation|遷移|chuyển|event/.test(c)) return 'NAV';
40
+ if (/paging|sort|phân trang/.test(c)) return 'LIST';
41
+ const slug = c.replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').toUpperCase().slice(0, 8);
42
+ return slug || 'GEN';
43
+ }
44
+
45
+ function oneLine(s: string | undefined, max = 60): string {
46
+ return (s || '').replace(/\s+/g, ' ').trim().slice(0, max);
47
+ }
48
+
49
+ function classify(tc: LegacyTestcase): { mode: TraceEntry['mode']; code: string; drivers: string[] } {
50
+ const code = classifyReason(`${tc.precondition || ''} ${tc.steps} ${tc.expected} ${tc.testData || ''}`);
51
+ if (!code) return { mode: 'ui', code: '', drivers: [] };
52
+ if (code === 'XS') return { mode: 'cross-screen', code, drivers: [] };
53
+ const def = MANUAL_REASONS[code];
54
+ if (def?.cls === 'keep') return { mode: 'manual-keep', code, drivers: [] };
55
+ return { mode: 'manual-capability', code, drivers: def?.drivers || [] };
56
+ }
57
+
58
+ export function inventoryToGherkin(inv: LegacyInventory, featureName: string): ConvertResult {
59
+ const tcs = inv.testcases;
60
+ const trace: TraceEntry[] = [];
61
+ const gap = { total: tcs.length, ui: 0, crossScreen: 0, manualCapability: 0, manualKeep: 0, noExpected: 0 };
62
+ const seq: Record<string, number> = {};
63
+
64
+ const lines: string[] = [];
65
+ lines.push('@legacy');
66
+ lines.push(`Feature: ${featureName} (drafted from legacy testcases: ${inv.source.file})`);
67
+ lines.push('');
68
+ lines.push(' # Auto-drafted by `sungen ingest --emit-gherkin` from a legacy manual testcase suite.');
69
+ lines.push(' # Each scenario carries its source TC id (@legacy:<id>) + the original');
70
+ lines.push(' # precondition/steps/expected as comments. REFINE the When/Then into real');
71
+ lines.push(' # [Reference] type steps with /sungen:create-test — the free-text → step');
72
+ lines.push(' # mapping is an AI step; this draft only fixes structure + traceability.');
73
+ lines.push('');
74
+
75
+ for (const tc of tcs) {
76
+ const vp = vpGroupOf(tc.category);
77
+ seq[vp] = (seq[vp] || 0) + 1;
78
+ const code = `VP-${vp}-${String(seq[vp]).padStart(3, '0')}`;
79
+ const desc = oneLine(tc.subCategory || tc.steps) || tc.id;
80
+ const title = `${code} ${desc}`;
81
+ const { mode, code: reason, drivers } = classify(tc);
82
+ if (!tc.expected) gap.noExpected++;
83
+ if (mode === 'ui') gap.ui++;
84
+ else if (mode === 'cross-screen') gap.crossScreen++;
85
+ else if (mode === 'manual-capability') gap.manualCapability++;
86
+ else gap.manualKeep++;
87
+
88
+ const tags = [`@${tc.priority === 'unknown' ? 'normal' : tc.priority}`];
89
+ if (mode !== 'ui') tags.push('@manual');
90
+ tags.push(`@legacy:${tc.id}`);
91
+
92
+ lines.push(` ${tags.join(' ')}`);
93
+ lines.push(` Scenario: ${title}`);
94
+ if (mode === 'manual-capability') lines.push(` # @manual:${reason} — needs ${drivers.join('/') || 'capability'} (driver candidate)`);
95
+ else if (mode === 'manual-keep') lines.push(` # @manual:${reason} — judgment floor, keep manual`);
96
+ else if (mode === 'cross-screen') lines.push(` # cross-screen → model as a flow (/sungen:add-flow)`);
97
+ if (tc.precondition) lines.push(` # precondition: ${oneLine(tc.precondition, 120)}`);
98
+ lines.push(` # steps: ${oneLine(tc.steps, 160)}`);
99
+ lines.push(` # expected: ${oneLine(tc.expected, 160)}`);
100
+ if (tc.precondition) lines.push(` Given User is on [${featureName}] page`);
101
+ lines.push(' # TODO(create-test): map the steps above to real When/Then [Reference] steps');
102
+ lines.push('');
103
+
104
+ trace.push({ tcId: tc.id, scenario: title, vpGroup: vp, mode, reasonCode: reason, drivers });
105
+ }
106
+
107
+ return { feature: lines.join('\n'), trace, gap };
108
+ }
@@ -47,6 +47,7 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
47
47
  ['claude-skill-selector-fix.md', '.claude/skills/sungen-selector-fix/SKILL.md'],
48
48
  ['claude-skill-tc-review.md', '.claude/skills/sungen-tc-review/SKILL.md'],
49
49
  ['claude-skill-harness-audit.md', '.claude/skills/sungen-harness-audit/SKILL.md'],
50
+ ['claude-skill-ingest-legacy.md', '.claude/skills/sungen-ingest-legacy/SKILL.md'],
50
51
  ['claude-skill-viewpoint.md', '.claude/skills/sungen-viewpoint/SKILL.md'],
51
52
  ['claude-skill-viewpoint-group-a-data-entry.md', '.claude/skills/sungen-viewpoint/group-a-data-entry.md'],
52
53
  ['claude-skill-viewpoint-group-b-data-ops.md', '.claude/skills/sungen-viewpoint/group-b-data-ops.md'],
@@ -78,6 +79,7 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
78
79
  ['github-skill-sungen-selector-fix.md', '.github/skills/sungen-selector-fix/SKILL.md'],
79
80
  ['github-skill-sungen-tc-review.md', '.github/skills/sungen-tc-review/SKILL.md'],
80
81
  ['github-skill-sungen-harness-audit.md', '.github/skills/sungen-harness-audit/SKILL.md'],
82
+ ['github-skill-sungen-ingest-legacy.md', '.github/skills/sungen-ingest-legacy/SKILL.md'],
81
83
  ['github-skill-sungen-viewpoint.md', '.github/skills/sungen-viewpoint/SKILL.md'],
82
84
  ['github-skill-sungen-viewpoint-group-a-data-entry.md', '.github/skills/sungen-viewpoint/group-a-data-entry.md'],
83
85
  ['github-skill-sungen-viewpoint-group-b-data-ops.md', '.github/skills/sungen-viewpoint/group-b-data-ops.md'],
@@ -78,6 +78,16 @@ The CLI reads the **per-target result file first** (co-located with `.spec.ts`),
78
78
 
79
79
  ---
80
80
 
81
+ ## XLSX sheets — Auto / Manual split
82
+
83
+ The `.xlsx` is split into two sheets so QA manages the sets separately:
84
+ - **`Auto`** — automatable test cases (`Auto` + `Not compiled`).
85
+ - **`Manual`** — `@manual` test cases (always present, header-only when there are none).
86
+
87
+ Multi-locale (no `SUNGEN_ENV`): one **`<LOCALE> Auto`** sheet per locale + a single shared **`Manual`** sheet (manual TCs are locale-invariant). The **CSV stays one file with every row** — the `Testcase type` column distinguishes Auto vs Manual.
88
+
89
+ ---
90
+
81
91
  ## Excluded from CSV
82
92
 
83
93
  - `@steps:<name>` **base** scenarios — these are setup-only, inlined into `@extend:...` scenarios at compile time
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: sungen-ingest-legacy
3
+ description: 'Import a legacy manual testcase suite from Google Sheets (multi-tab) or a local file into Sungen — fetch via MCP, then sungen ingest. Use when the user wants to convert/evaluate an existing manual testcase spreadsheet.'
4
+ user-invocable: true
5
+ ---
6
+
7
+ # sungen-ingest-legacy
8
+
9
+ Bring an existing **manual testcase workbook** into Sungen for evaluation + conversion. The
10
+ fetch (Google login + pick file) is done here via MCP; the parsing/audit is deterministic
11
+ (`sungen ingest`). **Security:** the workbook is the user's project data — read it on
12
+ consent, keep the output in their project, never upload or commit the content.
13
+
14
+ ## Flow
15
+
16
+ 1. **Locate the source.**
17
+ - **Google Sheets (recommended):** use the Google Drive MCP. Authenticate if needed, then
18
+ `search_files` / list to find the workbook; confirm the file with the user.
19
+ - **Local:** if the user points to a `.xlsx`/`.csv`, skip to step 4.
20
+
21
+ 2. **List the tabs.** Read the workbook's sheet/tab names (Drive MCP file metadata or a values
22
+ read). A legacy workbook usually has **many** tabs — some are testcases, some are
23
+ viewpoint/UI matrices.
24
+
25
+ 3. **Assemble a JSON sheet-bundle.** For each tab, read its cell values (a 2-D array of
26
+ strings) and write a local bundle in the user project (e.g.
27
+ `qa/screens/<screen>/requirements/legacy/bundle.json`):
28
+ ```json
29
+ { "source": "<workbook name>", "sheets": [ { "name": "<tab>", "rows": [["TC ID","Page",…],["TC-01",…]] } ] }
30
+ ```
31
+ Record only the **source link** in `requirements/legacy/source.yaml` — never the content.
32
+
33
+ 4. **Classify the tabs.** Run:
34
+ ```bash
35
+ sungen ingest --legacy <bundle.json|file.xlsx|file.csv> --list-sheets
36
+ ```
37
+ It prints each tab + detected type (`testcase` / `viewpoint-matrix` / `ui-checklist`).
38
+
39
+ 5. **Confirm which tabs to ingest.** Use `AskUserQuestion` to let the user pick the
40
+ **testcase** tabs (matrix/UI tabs feed the viewpoint layer later, not the inventory).
41
+
42
+ 6. **Ingest + reconcile.**
43
+ ```bash
44
+ sungen ingest --legacy <source> --screen <screen> --sheets "<Tab A>,<Tab B>" --emit-gherkin
45
+ ```
46
+ Produces: `inventory.json` (+ baseline audit), `*.legacy-draft.feature` + `legacy-trace.json`
47
+ (parity: `@legacy:<id>` per scenario), and `test-viewpoint.draft.md` with **blind-spots**
48
+ (catalog-expected viewpoints the legacy suite lacks).
49
+
50
+ 7. **Hand off to quality.** Tell the user the next step is `/sungen:create-test <screen>` —
51
+ it discovers + refines the draft into real `[Reference]` steps and fills the blind-spots;
52
+ then `sungen audit <screen>` gates quality. A 1:1 convert is NOT the deliverable; the
53
+ harness raises the legacy floor to catalog quality.
54
+
55
+ ## Governance block (important)
56
+
57
+ Many orgs mark confidential files as **"ineligible for generative AI contexts"** — the
58
+ Google Drive MCP will then **refuse** to read the file (metadata + download both error).
59
+ This is the org's DLP policy, not a bug, and it is the *expected* outcome for a
60
+ confidential testcase suite. When you hit it, **do not retry** — fall back:
61
+
62
+ > "This sheet is restricted by your org's data policy, so I can't read it through the
63
+ > AI connector. Two ways to proceed, both running as **you**, not AI:
64
+ > (1) `sungen ingest --gsheet <url>` — fetches under your own Google identity
65
+ > (read-only; Viewer/Commenter is enough). It offers to install `googleapis`
66
+ > and to open the Google login in your browser (pick your account), then
67
+ > retries automatically. Needs the gcloud SDK for the browser login.
68
+ > (2) Export it manually (**File → Download → Microsoft Excel `.xlsx`**) and I'll run
69
+ > `sungen ingest --legacy <file>.xlsx`."
70
+
71
+ The local-file path is deterministic and **never sends the content through AI** — the
72
+ correct, governance-compliant channel for confidential data. The MCP auto-pick is only
73
+ for files the org does *not* restrict.
74
+
75
+ ## Notes
76
+ - Multiple local CSVs (one per tab) also work: `--legacy tab1.csv tab2.csv …`.
77
+ - Re-run only re-fetches when the user asks; otherwise reuse the saved bundle.
78
+ - Do not invent testcases. Only ingest what the workbook contains; the *augmentation*
79
+ (blind-spots) happens in `/sungen:create-test`, flagged for human review.
@@ -78,6 +78,16 @@ The CLI reads the **per-target result file first** (co-located with `.spec.ts`),
78
78
 
79
79
  ---
80
80
 
81
+ ## XLSX sheets — Auto / Manual split
82
+
83
+ The `.xlsx` is split into two sheets so QA manages the sets separately:
84
+ - **`Auto`** — automatable test cases (`Auto` + `Not compiled`).
85
+ - **`Manual`** — `@manual` test cases (always present, header-only when there are none).
86
+
87
+ Multi-locale (no `SUNGEN_ENV`): one **`<LOCALE> Auto`** sheet per locale + a single shared **`Manual`** sheet (manual TCs are locale-invariant). The **CSV stays one file with every row** — the `Testcase type` column distinguishes Auto vs Manual.
88
+
89
+ ---
90
+
81
91
  ## Excluded from CSV
82
92
 
83
93
  - `@steps:<name>` **base** scenarios — these are setup-only, inlined into `@extend:...` scenarios at compile time
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: sungen-ingest-legacy
3
+ description: 'Import a legacy manual testcase suite from Google Sheets (multi-tab) or a local file into Sungen — fetch via MCP, then sungen ingest. Use when the user wants to convert/evaluate an existing manual testcase spreadsheet.'
4
+ user-invocable: true
5
+ ---
6
+
7
+ # sungen-ingest-legacy
8
+
9
+ Bring an existing **manual testcase workbook** into Sungen for evaluation + conversion. The
10
+ fetch (Google login + pick file) is done here via MCP; the parsing/audit is deterministic
11
+ (`sungen ingest`). **Security:** the workbook is the user's project data — read it on
12
+ consent, keep the output in their project, never upload or commit the content.
13
+
14
+ ## Flow
15
+
16
+ 1. **Locate the source.**
17
+ - **Google Sheets (recommended):** use the Google Drive MCP. Authenticate if needed, then
18
+ `search_files` / list to find the workbook; confirm the file with the user.
19
+ - **Local:** if the user points to a `.xlsx`/`.csv`, skip to step 4.
20
+
21
+ 2. **List the tabs.** Read the workbook's sheet/tab names (Drive MCP file metadata or a values
22
+ read). A legacy workbook usually has **many** tabs — some are testcases, some are
23
+ viewpoint/UI matrices.
24
+
25
+ 3. **Assemble a JSON sheet-bundle.** For each tab, read its cell values (a 2-D array of
26
+ strings) and write a local bundle in the user project (e.g.
27
+ `qa/screens/<screen>/requirements/legacy/bundle.json`):
28
+ ```json
29
+ { "source": "<workbook name>", "sheets": [ { "name": "<tab>", "rows": [["TC ID","Page",…],["TC-01",…]] } ] }
30
+ ```
31
+ Record only the **source link** in `requirements/legacy/source.yaml` — never the content.
32
+
33
+ 4. **Classify the tabs.** Run:
34
+ ```bash
35
+ sungen ingest --legacy <bundle.json|file.xlsx|file.csv> --list-sheets
36
+ ```
37
+ It prints each tab + detected type (`testcase` / `viewpoint-matrix` / `ui-checklist`).
38
+
39
+ 5. **Confirm which tabs to ingest.** Use `AskUserQuestion` to let the user pick the
40
+ **testcase** tabs (matrix/UI tabs feed the viewpoint layer later, not the inventory).
41
+
42
+ 6. **Ingest + reconcile.**
43
+ ```bash
44
+ sungen ingest --legacy <source> --screen <screen> --sheets "<Tab A>,<Tab B>" --emit-gherkin
45
+ ```
46
+ Produces: `inventory.json` (+ baseline audit), `*.legacy-draft.feature` + `legacy-trace.json`
47
+ (parity: `@legacy:<id>` per scenario), and `test-viewpoint.draft.md` with **blind-spots**
48
+ (catalog-expected viewpoints the legacy suite lacks).
49
+
50
+ 7. **Hand off to quality.** Tell the user the next step is `/sungen:create-test <screen>` —
51
+ it discovers + refines the draft into real `[Reference]` steps and fills the blind-spots;
52
+ then `sungen audit <screen>` gates quality. A 1:1 convert is NOT the deliverable; the
53
+ harness raises the legacy floor to catalog quality.
54
+
55
+ ## Governance block (important)
56
+
57
+ Many orgs mark confidential files as **"ineligible for generative AI contexts"** — the
58
+ Google Drive MCP will then **refuse** to read the file (metadata + download both error).
59
+ This is the org's DLP policy, not a bug, and it is the *expected* outcome for a
60
+ confidential testcase suite. When you hit it, **do not retry** — fall back:
61
+
62
+ > "This sheet is restricted by your org's data policy, so I can't read it through the
63
+ > AI connector. Two ways to proceed, both running as **you**, not AI:
64
+ > (1) `sungen ingest --gsheet <url>` — fetches under your own Google identity
65
+ > (read-only; Viewer/Commenter is enough). It offers to install `googleapis`
66
+ > and to open the Google login in your browser (pick your account), then
67
+ > retries automatically. Needs the gcloud SDK for the browser login.
68
+ > (2) Export it manually (**File → Download → Microsoft Excel `.xlsx`**) and I'll run
69
+ > `sungen ingest --legacy <file>.xlsx`."
70
+
71
+ The local-file path is deterministic and **never sends the content through AI** — the
72
+ correct, governance-compliant channel for confidential data. The MCP auto-pick is only
73
+ for files the org does *not* restrict.
74
+
75
+ ## Notes
76
+ - Multiple local CSVs (one per tab) also work: `--legacy tab1.csv tab2.csv …`.
77
+ - Re-run only re-fetches when the user asks; otherwise reuse the saved bundle.
78
+ - Do not invent testcases. Only ingest what the workbook contains; the *augmentation*
79
+ (blind-spots) happens in `/sungen:create-test`, flagged for human review.