@sun-asterisk/sungen 3.2.15 → 3.2.16-beta.1

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 (84) hide show
  1. package/dist/cli/commands/delivery.d.ts.map +1 -1
  2. package/dist/cli/commands/delivery.js +138 -1
  3. package/dist/cli/commands/delivery.js.map +1 -1
  4. package/dist/dashboard/templates/index.html +1 -1
  5. package/dist/exporters/feature-parser.js +1 -1
  6. package/dist/exporters/feature-parser.js.map +1 -1
  7. package/dist/exporters/matrix/build.d.ts +36 -0
  8. package/dist/exporters/matrix/build.d.ts.map +1 -0
  9. package/dist/exporters/matrix/build.js +267 -0
  10. package/dist/exporters/matrix/build.js.map +1 -0
  11. package/dist/exporters/matrix/export.d.ts +47 -0
  12. package/dist/exporters/matrix/export.d.ts.map +1 -0
  13. package/dist/exporters/matrix/export.js +136 -0
  14. package/dist/exporters/matrix/export.js.map +1 -0
  15. package/dist/exporters/matrix/fingerprint.d.ts +20 -0
  16. package/dist/exporters/matrix/fingerprint.d.ts.map +1 -0
  17. package/dist/exporters/matrix/fingerprint.js +83 -0
  18. package/dist/exporters/matrix/fingerprint.js.map +1 -0
  19. package/dist/exporters/matrix/gates.d.ts +21 -0
  20. package/dist/exporters/matrix/gates.d.ts.map +1 -0
  21. package/dist/exporters/matrix/gates.js +258 -0
  22. package/dist/exporters/matrix/gates.js.map +1 -0
  23. package/dist/exporters/matrix/map-loader.d.ts +22 -0
  24. package/dist/exporters/matrix/map-loader.d.ts.map +1 -0
  25. package/dist/exporters/matrix/map-loader.js +173 -0
  26. package/dist/exporters/matrix/map-loader.js.map +1 -0
  27. package/dist/exporters/matrix/render-csv.d.ts +8 -0
  28. package/dist/exporters/matrix/render-csv.d.ts.map +1 -0
  29. package/dist/exporters/matrix/render-csv.js +69 -0
  30. package/dist/exporters/matrix/render-csv.js.map +1 -0
  31. package/dist/exporters/matrix/render-xlsx.d.ts +17 -0
  32. package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -0
  33. package/dist/exporters/matrix/render-xlsx.js +298 -0
  34. package/dist/exporters/matrix/render-xlsx.js.map +1 -0
  35. package/dist/exporters/matrix/types.d.ts +153 -0
  36. package/dist/exporters/matrix/types.d.ts.map +1 -0
  37. package/dist/exporters/matrix/types.js +15 -0
  38. package/dist/exporters/matrix/types.js.map +1 -0
  39. package/dist/exporters/scenario-merger.js +1 -1
  40. package/dist/exporters/scenario-merger.js.map +1 -1
  41. package/dist/exporters/spec-parser.js +1 -1
  42. package/dist/exporters/spec-parser.js.map +1 -1
  43. package/dist/exporters/xlsx-report-builder.d.ts +1 -1
  44. package/dist/exporters/xlsx-report-builder.d.ts.map +1 -1
  45. package/dist/exporters/xlsx-report-builder.js +3 -3
  46. package/dist/exporters/xlsx-report-builder.js.map +1 -1
  47. package/dist/harness/audit.d.ts.map +1 -1
  48. package/dist/harness/audit.js +6 -1
  49. package/dist/harness/audit.js.map +1 -1
  50. package/dist/harness/capability.d.ts +25 -0
  51. package/dist/harness/capability.d.ts.map +1 -1
  52. package/dist/harness/capability.js +44 -2
  53. package/dist/harness/capability.js.map +1 -1
  54. package/dist/orchestrator/templates/ai-src/commands/create-test.md +1 -1
  55. package/dist/orchestrator/templates/ai-src/commands/delivery.md +118 -120
  56. package/dist/orchestrator/templates/ai-src/config/claude.md +1 -1
  57. package/dist/orchestrator/templates/ai-src/config/copilot.md +1 -1
  58. package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +36 -6
  59. package/dist/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +1 -1
  60. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +1 -0
  61. package/package.json +4 -4
  62. package/src/cli/commands/delivery.ts +168 -2
  63. package/src/dashboard/templates/index.html +1 -1
  64. package/src/exporters/feature-parser.ts +1 -1
  65. package/src/exporters/matrix/build.ts +294 -0
  66. package/src/exporters/matrix/export.ts +124 -0
  67. package/src/exporters/matrix/fingerprint.ts +49 -0
  68. package/src/exporters/matrix/gates.ts +284 -0
  69. package/src/exporters/matrix/map-loader.ts +142 -0
  70. package/src/exporters/matrix/render-csv.ts +71 -0
  71. package/src/exporters/matrix/render-xlsx.ts +289 -0
  72. package/src/exporters/matrix/types.ts +176 -0
  73. package/src/exporters/scenario-merger.ts +1 -1
  74. package/src/exporters/spec-parser.ts +1 -1
  75. package/src/exporters/xlsx-report-builder.ts +3 -2
  76. package/src/harness/audit.ts +7 -2
  77. package/src/harness/capability.ts +53 -3
  78. package/src/orchestrator/templates/ai-src/commands/create-test.md +1 -1
  79. package/src/orchestrator/templates/ai-src/commands/delivery.md +118 -120
  80. package/src/orchestrator/templates/ai-src/config/claude.md +1 -1
  81. package/src/orchestrator/templates/ai-src/config/copilot.md +1 -1
  82. package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +36 -6
  83. package/src/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +1 -1
  84. package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +1 -0
@@ -1,146 +1,144 @@
1
1
  ---
2
2
  name: delivery
3
- description: "Export Gherkin scenarios + Playwright results to CSV test case file for QA delivery."
4
- argument-hint: "[screen-name...] [--env <locale>] (omit screens for all; --env for locale-specific export)"
3
+ description: "Export the Test Case & Coverage Matrix (review/manual/customer deliverable) from Gherkin + Playwright results. --legacy exports the classic per-scenario CSV/XLSX."
4
+ argument-hint: "[name...] [--env <locale>] [--legacy] (omit names for all; --env for locale-specific export)"
5
5
  order: 50
6
- claude-tools: "Bash, Read, AskUserQuestion"
6
+ claude-tools: "Bash, Read, Write, AskUserQuestion"
7
7
  copilot-tools: "[read, execute, edit, vscode/askQuestions]"
8
- codex-trigger: "Run when the user asks to EXPORT or deliver test cases to CSV/XLSX for QA hand-off. Final step. Do NOT use for creating or running tests."
8
+ codex-trigger: "Run when the user asks to EXPORT or deliver test cases for QA hand-off. Final step. Do NOT use for creating or running tests."
9
9
  ---
10
10
  ## Role
11
11
 
12
- You are a **QA Test Delivery Engineer**. Your job is to invoke the deterministic `sungen delivery` CLI that performs all parsing and CSV export. Your role is minimal — just run the CLI and help the user if pre-flight checks fail.
12
+ You are a **QA Test Delivery Engineer**. The deterministic `sungen delivery` CLI does all parsing,
13
+ gating, and rendering. Your job is (1) invoking it, (2) **proposing the Delivery Map** when a unit
14
+ does not have one yet — the one semantic step the CLI cannot do — and (3) walking the user through
15
+ review → approve → official render.
16
+
17
+ ## The two delivery modes
18
+
19
+ - **Matrix (default)** — the Test Case & Coverage Matrix: one row per *delivery item* (one target +
20
+ one test intent + one oracle family), with each Gherkin scenario / `@cases` row preserved as a
21
+ collapsible *coverage variant* sub-row. No numbered step dumps. This is the reviewable /
22
+ manually-executable / customer-facing standard.
23
+ - **Legacy (`--legacy`, alias `--full`)** — the classic per-scenario-steps workbook (BM-2-901-13).
24
+ Use it when the user asks for the old format or a project is mid-engagement on it.
13
25
 
14
26
  ## Parameters
15
27
 
16
28
  Parse from `$ARGUMENTS`:
17
- {{#cap parallel-subagents}}
18
- - **screens**zero or more screen/flow names. Empty → CLI processes all targets in `qa/screens/` + `qa/flows/`.
19
- {{/cap}}
20
- {{^cap parallel-subagents}}
21
- - **names** — zero or more screen/flow names. Empty → CLI processes all targets in `qa/screens/` + `qa/flows/`.
22
- {{/cap}}
23
- - **`--env <locale>`** — optional. Sets `SUNGEN_ENV=<locale>` for the run so the CLI merges `<name>.<locale>.yaml` over the base test-data and writes `<name>-testcases.<locale>.csv` / `.xlsx`. Accept `--locale <locale>` as an alias.
24
-
25
- If `--env` is passed but no value follows, ask the user which locale to use.
29
+ - **names** — zero or more screen/flow/api names. Empty → all targets.
30
+ - **`--env <locale>`** sets `SUNGEN_ENV=<locale>` for the run (accept `--locale` as alias).
31
+ - **`--legacy` / `--full`** — pass through to the CLI and skip the map flow entirely.
26
32
 
27
33
  ## Steps
28
34
 
29
- ### 1. Invoke the CLI
30
-
31
- {{#cap parallel-subagents}}
32
- Run via Bash (single command, no extra parsing). Prefer the local `./bin/sungen.js` when it exists — the sungen monorepo ships local-only features the global npm package doesn't have yet (multi-sheet locale aggregation, `.<env>` filename suffix, locale-aware step rendering). Fall back to `npx sungen` in downstream projects.
33
- {{/cap}}
34
- {{^cap parallel-subagents}}
35
- Run via Bash (single command, no extra parsing):
36
- {{/cap}}
35
+ ### 1. Run the CLI once
37
36
 
38
37
  ```bash
39
- # No env — local-first dispatcher:
40
- {{#cap parallel-subagents}}
41
- [ -x ./bin/sungen.js ] && ./bin/sungen.js delivery <screens> || npx sungen delivery <screens>
42
- {{/cap}}
43
- {{^cap parallel-subagents}}
44
38
  [ -x ./bin/sungen.js ] && ./bin/sungen.js delivery <names> || npx sungen delivery <names>
45
- {{/cap}}
46
-
47
- # Locale-specific:
48
- {{#cap parallel-subagents}}
49
- [ -x ./bin/sungen.js ] && SUNGEN_ENV=<locale> ./bin/sungen.js delivery <screens> || SUNGEN_ENV=<locale> npx sungen delivery <screens>
50
- {{/cap}}
51
- {{^cap parallel-subagents}}
52
- [ -x ./bin/sungen.js ] && SUNGEN_ENV=<locale> ./bin/sungen.js delivery <names> || SUNGEN_ENV=<locale> npx sungen delivery <names>
53
- {{/cap}}
54
39
  ```
40
+ (prepend `SUNGEN_ENV=<locale>` when `--env` was given; append `--legacy` when requested — then skip
41
+ to step 5.)
42
+
43
+ Three outcomes per target:
44
+ - **Rendered** → done, go to step 5.
45
+ - **"no delivery map"** → go to step 2 (propose it).
46
+ - **Gate findings / "not approved"** → go to step 4 (review & approve).
47
+
48
+ ### 2. Propose the Delivery Map (only when missing or the user asks to regroup)
49
+
50
+ Read the unit's `.feature` (and `requirements/spec.md` for target naming). Write
51
+ `qa/<screens|flows|api>/<unit>/delivery/<unit>.map.yaml`:
52
+
53
+ ```yaml
54
+ version: 1
55
+ unit: <unit>
56
+ groups:
57
+ - id: DI-<TARGET>-<THEME> # stable kebab id, never renumbered
58
+ target: login.email # ONE target: field/component dot-path, flow phrase, or METHOD /path
59
+ intent: <one behavior/rule this item verifies>
60
+ oracle: <the shared observable Pass/Fail statement>
61
+ category: normal | abnormal | security | nfr
62
+ review: proposed # ALWAYS proposed — only QA approval flips it
63
+ variants: [VP-VAL-001-B, VP-VAL-001-S] # VP-ids; bare id on a @cases scenario = all its rows
64
+ dispositions: # scenarios intentionally NOT delivered as test cases
65
+ VP-DATA-000: { as: excluded, reason: data-setup checklist }
66
+ # as: excluded | blocked | covered_elsewhere | accepted_risk
67
+ ```
68
+
69
+ **Grouping rules (the aggregation signature):**
70
+ - One group = **one target + one intent + one oracle family**. When unsure, keep items separate —
71
+ the gates and QA decide, never guess-merge.
72
+ - MAY share a group (become coverage dimensions): equivalence partitions, boundary values,
73
+ different data (`@cases` rows), a different trigger with the same oracle (blur vs submit),
74
+ locales.
75
+ - MUST split: different target, intent, oracle family, category, execution mode (`@manual` vs
76
+ auto), test layer (`@api`/`@query`), or priority tag; sequence-sensitive flows (re-Given/When
77
+ after a Then) stay solo.
78
+ - Every scenario must land in exactly one group **or** one disposition (Gate B enforces 100%
79
+ disposition). Data-setup blocks (`@manual:data-setup`) → `excluded`; SPEC-GAP placeholders →
80
+ `blocked`.
81
+
82
+ Then validate and fix any ERROR findings:
83
+
84
+ ```bash
85
+ [ -x ./bin/sungen.js ] && ./bin/sungen.js delivery <unit> --check || npx sungen delivery <unit> --check
86
+ ```
87
+
88
+ ### 3. Present the grouping for review
89
+
90
+ Show the user a compact summary: item count vs variant count, the groups (id · target · intent ·
91
+ variant count), dispositions, and every remaining `review`-level finding (oracle-shape /
92
+ precondition heuristics — these are exactly what QA must confirm). Then `AskUserQuestion`:
93
+
94
+ - **Approve all groups** (Recommended when the findings look right)
95
+ - **Adjust the grouping** — user names the groups to change; edit the map, re-run `--check`, re-present.
96
+ - **Preview draft first** — `sungen delivery <unit> --preview` (DRAFT watermark), then re-ask.
97
+ - **Cancel**
98
+
99
+ ### 4. Approve + official render
100
+
101
+ ```bash
102
+ [ -x ./bin/sungen.js ] && ./bin/sungen.js delivery <unit> --approve || npx sungen delivery <unit> --approve
103
+ [ -x ./bin/sungen.js ] && ./bin/sungen.js delivery <unit> || npx sungen delivery <unit>
104
+ ```
105
+
106
+ `--approve` stamps scenario fingerprints into the map — if the `.feature` changes later, Gate E
107
+ flips the affected groups back to review (drift). Approved + unchanged groups render without
108
+ re-asking.
109
+
110
+ ### 5. Show summary + offer next steps
55
111
 
56
- {{#cap parallel-subagents}}
57
- - If no screen args omit `<screens>` (CLI processes all targets).
58
- {{/cap}}
59
- {{^cap parallel-subagents}}
60
- - If no name args omit `<names>` (CLI processes all targets).
61
- {{/cap}}
62
- - If `--env <locale>` was provided → prepend `SUNGEN_ENV=<locale>` to the command. Do NOT pass `--env` to the CLI itself — it's not a CLI flag, only a slash-command convenience.
63
-
64
- The CLI handles:
65
- {{#cap parallel-subagents}}
66
- - Scope detection (all screens + flows vs specific)
67
- {{/cap}}
68
- {{^cap parallel-subagents}}
69
- - Scope detection (all screens + flows vs specific names)
70
- - Auto-detect: `qa/flows/<name>/` → flow, `qa/screens/<name>/` → screen
71
- {{/cap}}
72
- - Pre-flight source checks with colorful output
73
- - Parsing `.feature`, `.spec.ts`, `test-data.yaml` (+ `<name>.<env>.yaml` overlay when `SUNGEN_ENV` is set), and per-target `<name>-test-result[.<env>].json`
74
- - Generating CSV/XLSX at `qa/deliverables/<name>-testcases[.<env>].csv` / `.xlsx`
75
- - Printing summary table
76
-
77
- ### 2. Handle pre-flight failures (if CLI exits non-zero)
78
-
79
- {{#cap parallel-subagents}}
80
- If the CLI exits with blocking issues, it will have already printed a clear table showing exactly what's missing per screen.
81
- {{/cap}}
82
- {{^cap parallel-subagents}}
83
- If the CLI exits with blocking issues, it will have already printed a clear table showing exactly what's missing per target.
84
- {{/cap}}
85
-
86
- Use `AskUserQuestion` to offer next steps:
87
-
88
- **Options:**
89
- - **Fix missing sources** (Recommended) — Print the suggested commands from CLI output and stop. User will run those commands manually, then re-invoke `/sungen:delivery`.
90
- {{#cap parallel-subagents}}
91
- - **Continue with available screens** — Re-run as `npx sungen delivery <screens> --continue-on-missing` to skip screens with blocking issues.
92
- {{/cap}}
93
- {{^cap parallel-subagents}}
94
- - **Continue with available targets** — Re-run as `npx sungen delivery <names> --continue-on-missing` to skip targets with blocking issues.
95
- {{/cap}}
96
- - **Cancel** — Exit.
97
-
98
- ### 3. Show summary + offer next steps (on success)
99
-
100
- Forward the CLI's summary table to the user verbatim. Then use `AskUserQuestion`:
101
-
102
- - **Open a specific CSV** — Help user inspect one of the exported files with Read tool.
103
- {{#cap parallel-subagents}}
104
- - **Run tests to refresh results** — Suggest `/sungen:run-test <screen>` to update `test-results/results.json`, then re-run delivery.
105
- - **Export another screen** — User can run `/sungen:delivery <other-screen>`.
106
- {{/cap}}
107
- {{^cap parallel-subagents}}
108
- - **Run tests to refresh results** — Suggest `/sungen-run-test <name>` to update test results, then re-run delivery.
109
- - **Export another target** — User can run `/sungen-delivery <other-name>`.
110
- {{/cap}}
111
- - **Done** — Exit.
112
+ Forward the CLI summary verbatim (Items = review rows; **Variants = execution units — progress is
113
+ counted in variants, never items**). Then `AskUserQuestion`:
114
+
115
+ - **Open the workbook** — inspect `qa/deliverables/<unit>-testcases.xlsx` (Testcases sheet:
116
+ collapse outline level 1 for the customer view; Coverage sheet: target × category grid + gaps).
117
+ - **Run tests to refresh results** — `/sungen:run-test <unit>`, then re-run delivery.
118
+ - **Export the legacy workbook too** `sungen delivery <unit> --legacy`.
119
+ - **Done**
112
120
 
113
121
  ## Important notes
114
122
 
115
- - **Do NOT parse files yourself** — the CLI is the source of truth for parsing logic. Your job is orchestration + user interaction.
116
- - **Do NOT modify feature/spec.ts/test-data files** the delivery is read-only.
117
- - **The CLI already respects `@manual` tags, skips `@steps:` base scenarios, groups by Category 2, and generates UTF-8 BOM CSV for Excel compatibility with Vietnamese.**
118
- - **Pre-flight check is built into the CLI** — use `--skip-preflight` only in CI/automated pipelines where checks are done externally.
123
+ - **Do NOT parse/duplicate what the CLI computes** gates recompute mode/layer/priority/oracle
124
+ shapes from the `.feature`; the map only carries the semantic fields (target/intent/oracle/
125
+ category/grouping).
126
+ - **Never write `review: approved` yourself** — only `--approve` after the user agreed. Proposing
127
+ with `review: approved` defeats the QA review this format exists for.
128
+ - **Do NOT modify feature/test-data files during delivery** — read-only except the map file.
129
+ - The map is committed and stable: adding a group never renumbers others; deleting scenarios →
130
+ update the map (Gate B lists exactly what's unmapped/stale).
119
131
 
120
- ## CLI Reference
132
+ ## CLI reference
121
133
 
122
134
  ```
123
- {{#cap parallel-subagents}}
124
- sungen delivery [screens...]
125
- {{/cap}}
126
- {{^cap parallel-subagents}}
127
- sungen delivery [names...]
128
- {{/cap}}
129
- [--skip-preflight] Skip pre-flight checks (not recommended)
130
- {{#cap parallel-subagents}}
131
- [--continue-on-missing] Skip screens with blocking misses
132
- {{/cap}}
133
- {{^cap parallel-subagents}}
134
- [--continue-on-missing] Skip targets with blocking misses
135
- {{/cap}}
136
-
137
- # Locale-aware export (env var, not a CLI flag):
138
- {{#cap parallel-subagents}}
139
- SUNGEN_ENV=<locale> sungen delivery [screens...]
140
- {{/cap}}
141
- {{^cap parallel-subagents}}
142
- SUNGEN_ENV=<locale> sungen delivery [names...]
143
- {{/cap}}
144
- → reads <name>.<locale>.yaml overlay, picks <name>-test-result.<locale>.json,
145
- writes <name>-testcases.<locale>.csv / .xlsx
135
+ sungen delivery [names...] # matrix (default; needs the map)
136
+ --check # gates only — validate the map, write nothing
137
+ --approve [DI-a,DI-b] # flip proposed→approved (+ stamp fingerprints); all groups when bare
138
+ --preview # render despite review findings (DRAFT watermark)
139
+ --legacy | --full # classic per-scenario workbook (no map needed)
140
+ --skip-preflight | --continue-on-missing | --env <env> # as before
146
141
  ```
142
+
143
+ Outputs: `qa/deliverables/<unit>-testcases.xlsx` (Testcases + Coverage sheets) + `.csv`
144
+ (flat, `Level` column `item|variant`). Legacy mode writes the classic files instead.
@@ -30,7 +30,7 @@ You generate 3 files for sungen — a Gherkin compiler that produces Playwright
30
30
  | `/sungen:create-data-test <name>` | Generate standardized test-data (valid/boundary/invalid + CHK trace) from the Data Factory catalog; no name = all units (asks to confirm) |
31
31
  | `/sungen:review <name>` | Score syntax, coverage, viewpoint quality (auto-detects screen or flow) |
32
32
  | `/sungen:run-test <name>` | Generate `selectors.yaml`, compile, run, auto-fix (auto-detects screen or flow) |
33
- | `/sungen:delivery [name...]` | Export test cases CSV for QA delivery (all screens if no arg) |
33
+ | `/sungen:delivery [name...]` | Export the Test Case & Coverage Matrix (review/manual/customer deliverable; `--legacy` = classic CSV; all screens if no arg) |
34
34
  | `/sungen:locale <name> <locale>` | Bootstrap i18n for a screen — audit selectors, detect locale switch, generate overlay (run before `/sungen:run-test --env <locale>`) |
35
35
 
36
36
  **Screen path:** add-screen → create-test → review → run-test → delivery.
@@ -30,7 +30,7 @@ You generate 3 files for sungen — a Gherkin compiler that produces Playwright
30
30
  | `/sungen-create-data-test <name>` | Generate standardized test-data (valid/boundary/invalid + CHK trace) from the Data Factory catalog; no name = all units (asks to confirm) |
31
31
  | `/sungen-review <name>` | Score syntax, coverage, viewpoint quality (auto-detects screen or flow) |
32
32
  | `/sungen-run-test <name>` | Generate `selectors.yaml`, compile, run, auto-fix (auto-detects screen or flow) |
33
- | `/sungen-delivery [name...]` | Export test cases CSV for QA delivery (all screens if no arg) |
33
+ | `/sungen-delivery [name...]` | Export the Test Case & Coverage Matrix (review/manual/customer deliverable; `--legacy` = classic CSV; all screens if no arg) |
34
34
  | `/sungen-locale <name> <locale>` | Bootstrap i18n for a screen — audit selectors, detect locale switch, generate overlay (run before `/sungen-run-test --env <locale>`) |
35
35
 
36
36
  **Screen path:** add-screen → create-test → review → run-test → delivery.
@@ -1,21 +1,51 @@
1
1
  ---
2
2
  name: sungen-delivery
3
- description: 'Export Gherkin scenarios + Playwright results CSV test case deliverable. Auto-loaded by delivery command.'
3
+ description: 'Export the Test Case & Coverage Matrix (or the legacy per-scenario CSV) from Gherkin + Playwright results. Auto-loaded by delivery command.'
4
4
  user-invocable: false
5
5
  ---
6
6
 
7
7
  ## Purpose
8
8
 
9
- Export test cases from Sungen screens and flows to a standardized CSV file (format BM-2-901-13) for QA delivery.
9
+ Export test cases from Sungen units to the QA deliverable. Two modes:
10
10
 
11
- **This skill delegates all heavy work to the `sungen delivery` CLI.** The CLI is the single source of truth for parsing logic — do NOT re-parse files in AI. Your role is only to:
11
+ - **Matrix (default)** the **Test Case & Coverage Matrix**: one row per *delivery item* (one
12
+ target + one test intent + one oracle family); scenarios / `@cases` rows stay as collapsible
13
+ *coverage variant* sub-rows. Built for review, manual execution, customer hand-off, and
14
+ reporting — no numbered step dumps.
15
+ - **Legacy (`--legacy`/`--full`)** — the classic per-scenario BM-2-901-13 workbook (documented
16
+ in the second half of this skill; unchanged).
12
17
 
13
- 1. Invoke the CLI
14
- 2. Show its output verbatim
15
- 3. Help the user react to pre-flight failures
18
+ **This skill delegates all heavy work to the `sungen delivery` CLI.** The CLI is the single source
19
+ of truth for parsing + gating. The AI's only semantic contribution is **proposing the Delivery
20
+ Map** (grouping) — everything else is: invoke the CLI, show output verbatim, help on failures.
16
21
 
17
22
  ---
18
23
 
24
+ ## Matrix mode — the Delivery Map
25
+
26
+ `qa/<screens|flows|api>/<unit>/delivery/<unit>.map.yaml` — committed, AI-proposed
27
+ (`review: proposed`), gate-validated, QA-approved (`sungen delivery --approve` stamps scenario
28
+ fingerprints). Schema + grouping rules live in the delivery command instructions; the normative
29
+ spec is `docs/spec/delivery-coverage-matrix-spec.md`.
30
+
31
+ **Gates** (CLI `--check`): A source (VP-ids unique, oracle present, Background setup-only) ·
32
+ B mapping (every scenario in exactly one group XOR one disposition) · C aggregation (mode/layer/
33
+ priority recomputed and equal within a group — heuristic oracle-shape/precondition mismatches are
34
+ review-level, silenced once approved and unchanged) · D executability (precondition · condition+
35
+ data · trigger · oracle all renderable; every `{{var}}` resolves) · E drift (fingerprint mismatch
36
+ → back to review) · G review state (proposed groups block the official render; `--preview` renders
37
+ a DRAFT watermark).
38
+
39
+ **Workbook**: `Testcases` sheet (parent rows + outline-level-1 variant sub-rows — collapse for the
40
+ customer view, expand to execute; result roll-up failed→blocked→not_run→partial→passed, counts like
41
+ `7/8 Passed · 1 Failed`) + `Coverage` sheet (target × category grid with explicit `—` gaps,
42
+ dispositions, generation manifest). CSV is flat with a `Level` column (`item`/`variant`).
43
+ `delivery_item_count` ≠ progress — variants are the execution metric.
44
+
45
+ ---
46
+
47
+ ## Legacy mode (--legacy / --full)
48
+
19
49
  ## Architecture
20
50
 
21
51
  ```
@@ -66,7 +66,7 @@ Use these when repairing GATE/DEPTH findings for the hard viewpoints (cart/detai
66
66
  2. **Stop when** `gateStatus == PASS` AND `findings` empty — or budget exhausted.
67
67
  3. **Never fake a pass.** A shallow `see [Cart] page` does not satisfy `cart-correctness`. If a gap is genuinely cross-screen or needs capabilities the DSL lacks (e.g. capture an element value to compare elsewhere), **report it as a residual gap / flow item** instead of forcing a green gate.
68
68
  4. **EP/data families are OK.** A `duplicates` cluster with `sameDataLikely=false` is an intentional equivalence-partition family (e.g. many invalid-email cases) — keep it; only collapse `sameDataLikely=true` exact duplicates.
69
- 5. **Advisory findings — surface, don't gate.** `MANUAL-REASON-MISMATCH` → fix the scenario's `@manual:Mx` code (so the planner recommends the right driver) during repair. `CAPABILITY-SUGGESTION` → **present it to the user as a next-step option** (e.g. "N @manual could be automated — `sungen capability add api db`?"), **recommend-only — never auto-install**. Neither fails the gate.
69
+ 5. **Advisory findings — surface, don't gate.** `MANUAL-REASON-MISMATCH` → fix the scenario's `@manual:Mx` code (so the planner recommends the right driver) during repair. `CAPABILITY-SUGGESTION` → **present it to the user as a next-step option** (e.g. "N @manual could be automated — `sungen capability add api db`?"), **recommend-only — never auto-install**. `VERIFICATION-OUT-OF-SCOPE (api|db)` → the unit uses `@api`/`@query`/`@requires:api|db` verification the project's recorded **test type** never opted into (E2E/UI-only per `capabilities.yaml`, and the viewpoint doesn't ask for it). During repair: **rewrite the oracle to be UI-observable** (or downgrade the deep check to an `(optional deep check: …)` note); only keep the API/DB verification if the project genuinely tests that layer — then record it (`verification: [ui, api]` / `sungen capability add`). Keep API/DB-in-E2E to the ≤20% band. Neither of these fails the gate.
70
70
 
71
71
  ## Discovery / fallback tree (when input is limited)
72
72
 
@@ -328,6 +328,7 @@ Security: [S1 – admin only]
328
328
  Gate (`SG-W008`, both directions): a `@manual` with Gherkin steps but no `Then` fires — comments (even labeled `Oracle:` lines) can NOT substitute for the missing `Then`. A `@manual` with NO steps ALSO fires when its comments lack ≥1 numbered action line + ≥1 labeled `Observable:`/`Oracle:`/`Expected:` line — prose paragraphs and rationale-only comments export EMPTY Steps/Expected cells.
329
329
  In shape A the observable/oracle lines MUST carry their **label** (`Observable:` / `Oracle:` / `Expected:`) — the delivery parser extracts Expected results **by label**; unlabeled prose all lands in the Steps column and the Expected cell exports EMPTY. And the numbered action steps MUST precede them — without the actions the tester has an oracle but no procedure.
330
330
  **Oracle layer must match the unit** — a screen-unit manual defaults to a **UI oracle**: phrase the expected as what the tester SEES (on-screen message, list/table state after reload). If the business effect IS UI-observable, do NOT cite API responses or DB state as the oracle — "server rejects the request" is verified by *the error message on screen + no new row after reload*, not by reading the API response. Only when the effect has NO UI observation (mail content, stored hash, request count, response-body leak) may the oracle use another layer — then: (a) put the **layer label inside the line**: `Oracle: UI — …` / `Oracle: API — …` / `Oracle: DB — …` so QA knows the tooling needed from the deliverable alone; (b) tag `@manual:M2` so the capability planner recommends the api/db driver; (c) prefer the automation-ready `@requires:api`/`@query` form when the steps are expressible. A deep engineering check may ride along as `(optional deep check: …)` appended to the UI oracle — it must never REPLACE a UI-observable oracle.
331
+ **Respect the project's VERIFICATION SCOPE (the recorded test type).** Read `qa/capabilities.yaml` — the effective scope is `verification:` if set, else derived from `enabled` (`ui` always; `api`/`db` only if that driver is on). If the project is **E2E/UI-only** (no `api`/`db`) **and the test-viewpoint does not ask for API/DB verification**, do **NOT** author `@api`/`@query`/`@requires:api|db` verification — keep the oracle **UI-observable**, or leave the deep check as an `(optional deep check: …)` note. Author API/DB verification only when the scope (or an explicit viewpoint) calls for it, and even then keep the API/DB-in-E2E combination to a **≤20% band** of scenarios (the effects with no UI observation) — never the default. `sungen audit` reports `VERIFICATION-OUT-OF-SCOPE` when a unit uses API/DB verify the project never opted into.
331
332
  Use this comment shape:
332
333
  ```gherkin
333
334
  @high @manual
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sun-asterisk/sungen",
3
- "version": "3.2.15",
3
+ "version": "3.2.16-beta.1",
4
4
  "description": "Deterministic E2E Test Compiler - Gherkin + Selectors → Playwright tests",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -12,7 +12,7 @@
12
12
  "copy-templates": "mkdir -p dist/generators/test-generator/adapters/playwright/templates/steps && mkdir -p dist/generators/test-generator/templates && mkdir -p dist/orchestrator/templates && mkdir -p dist/dashboard/templates && cp -r src/generators/test-generator/adapters/playwright/templates/*.hbs dist/generators/test-generator/adapters/playwright/templates/ 2>/dev/null || true && cp -r src/generators/test-generator/adapters/playwright/templates/steps dist/generators/test-generator/adapters/playwright/templates/ && mkdir -p dist/generators/test-generator/adapters/appium/templates/steps && cp -r src/generators/test-generator/adapters/appium/templates/*.hbs dist/generators/test-generator/adapters/appium/templates/ 2>/dev/null || true && cp -r src/generators/test-generator/adapters/appium/templates/steps dist/generators/test-generator/adapters/appium/templates/ && cp src/generators/test-generator/templates/*.hbs dist/generators/test-generator/templates/ 2>/dev/null || true && cp -r src/orchestrator/templates/* dist/orchestrator/templates/ && cp src/dashboard/templates/index.html dist/dashboard/templates/index.html && mkdir -p dist/harness/catalog && cp src/harness/catalog/*.yaml dist/harness/catalog/",
13
13
  "build:dashboard": "cd ../../dashboard && npm install --silent && npm run build && cd - && cp ../../dashboard/dist/index.html src/dashboard/templates/index.html",
14
14
  "dev": "tsx src/cli/index.ts",
15
- "test": "tsx tests/golden/run.ts && tsx tests/audit/run.ts && tsx tests/ingest/run.ts && tsx tests/eval/run.ts && tsx tests/exporter/run.ts && tsx tests/exporter/feature-parser-category.run.ts && tsx tests/exporter/api-detail-sheet.run.ts && tsx tests/exporter/overview-sheet.run.ts && tsx tests/exporter/delivery-divider-tcid.run.ts && tsx tests/exporter/note-expected-actual.run.ts && tsx tests/exporter/test-data-vars.run.ts && tsx tests/exporter/mobile-app-id.run.ts && tsx tests/exporter/api-testcase-cells.run.ts && tsx tests/exporter/delivery-cases-result.run.ts && tsx tests/exporter/precondition-auth-role.run.ts && tsx tests/exporter/report-builder-parity.run.ts && tsx tests/exporter/delivery-preflight-testdata.run.ts && tsx tests/exporter/delivery-preflight-selectors.run.ts && tsx tests/exporter/delivery-prerun-overview.run.ts && tsx tests/exporter/delivery-locale-env-discovery.run.ts && tsx tests/exporter/manual-testcase-testdata.run.ts && tsx tests/dashboard/api-flows-discovery.run.ts && tsx tests/dashboard/status-results-fallback.run.ts && tsx tests/codegen/verb-ref-masking.run.ts && tsx tests/codegen/table-aria-hidden-locators.run.ts && tsx tests/codegen/steps-base-not-compiled.run.ts && tsx tests/codegen/manual-oracle-gate.run.ts && tsx tests/codegen/locator-value-qualified-assertions.run.ts && tsx tests/codegen/table-match-data-column-mapping.run.ts && tsx tests/codegen/param-binding-native-type.run.ts && tsx tests/codegen/table-row-scope-redeclaration.run.ts && tsx tests/codegen/table-row-label-resolution.run.ts && tsx tests/api-runtime/base-path-url-join.run.ts && tsx tests/api-runtime/file-upload-multipart.run.ts && tsx tests/api-runtime/schema-assertion.run.ts && tsx tests/runtime/test-data-cross-ref.run.ts && tsx tests/runtime/test-data-array-projection.run.ts && tsx tests/runtime/env-secret-interpolation.run.ts && tsx tests/capabilities/run.ts && tsx tests/openapi/run.ts && tsx tests/api-field-coverage/run.ts && tsx tests/packaging/run.ts && tsx tests/generate-hint/run.ts && tsx tests/template-assertion/run.ts && tsx tests/journey/run.ts && tsx tests/harness/serial-cascade.run.ts && tsx tests/ai-skills/cross-assistant-orphan.run.ts && tsx tests/init/codex-mcp-config.run.ts && tsx src/orchestrator/ai-skills/golden-skills.test.ts && tsx tests/db-runtime/sql-placeholder-rewrite.run.ts && tsx tests/db-runtime/fallback-flow-codified.run.ts && tsx tests/db-runtime/cosmos-engine.run.ts && tsx tests/db-runtime/mongodb-engine.run.ts && tsx tests/db-runtime/dynamodb-engine.run.ts && tsx tests/db-runtime/mysql-integration.opt-in.run.ts && tsx tests/db-runtime/mongodb-integration.opt-in.run.ts && tsx tests/db-runtime/dynamodb-integration.opt-in.run.ts",
15
+ "test": "tsx tests/golden/run.ts && tsx tests/audit/run.ts && tsx tests/ingest/run.ts && tsx tests/eval/run.ts && tsx tests/exporter/run.ts && tsx tests/exporter/delivery-matrix.run.ts && tsx tests/exporter/feature-parser-category.run.ts && tsx tests/exporter/api-detail-sheet.run.ts && tsx tests/exporter/overview-sheet.run.ts && tsx tests/exporter/delivery-divider-tcid.run.ts && tsx tests/exporter/note-expected-actual.run.ts && tsx tests/exporter/test-data-vars.run.ts && tsx tests/exporter/mobile-app-id.run.ts && tsx tests/exporter/api-testcase-cells.run.ts && tsx tests/exporter/delivery-cases-result.run.ts && tsx tests/exporter/precondition-auth-role.run.ts && tsx tests/exporter/report-builder-parity.run.ts && tsx tests/exporter/delivery-preflight-testdata.run.ts && tsx tests/exporter/delivery-preflight-selectors.run.ts && tsx tests/exporter/delivery-prerun-overview.run.ts && tsx tests/exporter/delivery-locale-env-discovery.run.ts && tsx tests/exporter/manual-testcase-testdata.run.ts && tsx tests/dashboard/api-flows-discovery.run.ts && tsx tests/dashboard/status-results-fallback.run.ts && tsx tests/codegen/verb-ref-masking.run.ts && tsx tests/codegen/table-aria-hidden-locators.run.ts && tsx tests/codegen/steps-base-not-compiled.run.ts && tsx tests/codegen/manual-oracle-gate.run.ts && tsx tests/codegen/locator-value-qualified-assertions.run.ts && tsx tests/codegen/table-match-data-column-mapping.run.ts && tsx tests/codegen/param-binding-native-type.run.ts && tsx tests/codegen/table-row-scope-redeclaration.run.ts && tsx tests/codegen/table-row-label-resolution.run.ts && tsx tests/api-runtime/base-path-url-join.run.ts && tsx tests/api-runtime/file-upload-multipart.run.ts && tsx tests/api-runtime/schema-assertion.run.ts && tsx tests/runtime/test-data-cross-ref.run.ts && tsx tests/runtime/test-data-array-projection.run.ts && tsx tests/runtime/env-secret-interpolation.run.ts && tsx tests/capabilities/run.ts && tsx tests/openapi/run.ts && tsx tests/api-field-coverage/run.ts && tsx tests/packaging/run.ts && tsx tests/generate-hint/run.ts && tsx tests/template-assertion/run.ts && tsx tests/journey/run.ts && tsx tests/harness/serial-cascade.run.ts && tsx tests/ai-skills/cross-assistant-orphan.run.ts && tsx tests/init/codex-mcp-config.run.ts && tsx src/orchestrator/ai-skills/golden-skills.test.ts && tsx tests/db-runtime/sql-placeholder-rewrite.run.ts && tsx tests/db-runtime/fallback-flow-codified.run.ts && tsx tests/db-runtime/cosmos-engine.run.ts && tsx tests/db-runtime/mongodb-engine.run.ts && tsx tests/db-runtime/dynamodb-engine.run.ts && tsx tests/db-runtime/mysql-integration.opt-in.run.ts && tsx tests/db-runtime/mongodb-integration.opt-in.run.ts && tsx tests/db-runtime/dynamodb-integration.opt-in.run.ts",
16
16
  "test:update": "tsx tests/golden/run.ts --update && tsx tests/audit/run.ts --update && tsx tests/ingest/run.ts --update",
17
17
  "prepublishOnly": "npm run build:dashboard && npm run build"
18
18
  },
@@ -39,8 +39,8 @@
39
39
  "@babel/types": "^7.28.5",
40
40
  "@cucumber/gherkin": "^37.0.0",
41
41
  "@cucumber/messages": "^31.0.0",
42
- "@sungen/driver-data-factory": "3.2.15",
43
- "@sungen/driver-ui": "3.2.15",
42
+ "@sungen/driver-data-factory": "3.2.16-beta.1",
43
+ "@sungen/driver-ui": "3.2.16-beta.1",
44
44
  "chalk": "^5.6.2",
45
45
  "commander": "^14.0.2",
46
46
  "dotenv": "^17.2.3",
@@ -25,6 +25,16 @@ import { renderXlsxMultiSheet, writeXlsx, buildApiDetailRows, addApiDetailSheet
25
25
  import { loadApiCatalog } from '../../exporters/api-catalog-loader';
26
26
  import { resolvePlatformAppId } from '../../exporters/mobile-app-id';
27
27
  import { EnvironmentInfo, PreflightCheck, ScreenSummary, TestCaseRow, ApiCatalogEntry } from '../../exporters/types';
28
+ import { mapFilePath } from '../../exporters/matrix/map-loader';
29
+ import {
30
+ MatrixTargetPaths,
31
+ loadMatrixModel,
32
+ approveMatrix,
33
+ writeMatrixDeliverables,
34
+ hasErrors,
35
+ hasReview,
36
+ } from '../../exporters/matrix/export';
37
+ import { MatrixFinding } from '../../exporters/matrix/types';
28
38
 
29
39
  const COLOR = {
30
40
  reset: '\x1b[0m',
@@ -597,6 +607,114 @@ async function exportTarget(
597
607
  }
598
608
  }
599
609
 
610
+ // ----------------------------------------------------------------------------
611
+ // Matrix export (Delivery v2 — the default; --legacy keeps the classic workbook)
612
+ // ----------------------------------------------------------------------------
613
+
614
+ function matrixPathsFor(cwd: string, target: DeliveryTarget): MatrixTargetPaths {
615
+ const base = qaDir(cwd, target);
616
+ const genBase = generatedDir(cwd, target);
617
+ return {
618
+ cwd,
619
+ unit: target.featureBaseName,
620
+ label: target.isFlow ? `flow/${target.featureBaseName}` : target.featureBaseName,
621
+ featureFile: path.join(base, 'features', `${target.featureBaseName}.feature`),
622
+ testDataFile: resolveTestDataPathForTarget(cwd, target),
623
+ specFile: path.join(genBase, `${target.featureBaseName}.spec.ts`),
624
+ resultsPath: resolveResultsPath(cwd, target),
625
+ mapFile: mapFilePath(base, target.featureBaseName),
626
+ };
627
+ }
628
+
629
+ function printFindings(findings: MatrixFinding[]): void {
630
+ const icon = { error: `${COLOR.red}✗`, review: `${COLOR.yellow}⚠`, warning: `${COLOR.gray}·` } as const;
631
+ for (const f of findings) {
632
+ log(` ${icon[f.severity]} [Gate ${f.gate}]${COLOR.reset} ${f.ref ? `${f.ref}: ` : ''}${f.message}`);
633
+ }
634
+ }
635
+
636
+ interface MatrixRunSummary {
637
+ label: string;
638
+ items: number;
639
+ variants: number;
640
+ passed: number;
641
+ failed: number;
642
+ file: string;
643
+ }
644
+
645
+ /**
646
+ * Matrix-mode export of one target. Returns null when blocked (missing map,
647
+ * gate errors, or unapproved groups without --preview) — the caller aggregates
648
+ * a non-zero exit.
649
+ */
650
+ async function exportMatrixTarget(
651
+ cwd: string,
652
+ target: DeliveryTarget,
653
+ opts: { check?: boolean; preview?: boolean },
654
+ ): Promise<MatrixRunSummary | null> {
655
+ const paths = matrixPathsFor(cwd, target);
656
+ const { model, mapMissing, mapErrors } = loadMatrixModel(paths);
657
+
658
+ if (mapMissing) {
659
+ log(`${COLOR.yellow}${paths.label}: no delivery map${COLOR.reset} (${path.relative(cwd, paths.mapFile)})`);
660
+ log(` The matrix needs the reviewed grouping (target / intent / oracle per delivery item).`);
661
+ log(` ${COLOR.cyan}→ run /sungen:delivery ${target.screen} so the map is proposed for your review${COLOR.reset}`);
662
+ log(` ${COLOR.cyan}→ or export the classic per-scenario workbook: sungen delivery ${target.screen} --legacy${COLOR.reset}`);
663
+ return null;
664
+ }
665
+ if (mapErrors.length > 0 || !model) {
666
+ log(`${COLOR.red}${paths.label}: delivery map is invalid${COLOR.reset}`);
667
+ for (const e of mapErrors) log(` - ${e}`);
668
+ return null;
669
+ }
670
+
671
+ log(`${COLOR.bold}${paths.label}${COLOR.reset} — ${model.manifest.itemCount} delivery item(s), ${model.manifest.variantCount} coverage variant(s), state: ${model.manifest.approvalState}`);
672
+ printFindings(model.findings);
673
+
674
+ if (opts.check) {
675
+ // Gates-only run: the summary IS the output; exit code reflects errors.
676
+ return hasErrors(model.findings)
677
+ ? null
678
+ : { label: paths.label, items: model.manifest.itemCount, variants: model.manifest.variantCount, passed: 0, failed: 0, file: '(check only)' };
679
+ }
680
+ if (hasErrors(model.findings)) {
681
+ log(` ${COLOR.red}blocked:${COLOR.reset} fix the Gate errors above, then re-run.`);
682
+ return null;
683
+ }
684
+ if (hasReview(model.findings) && !opts.preview) {
685
+ log(` ${COLOR.yellow}not approved for official delivery.${COLOR.reset}`);
686
+ log(` ${COLOR.cyan}→ approve the reviewed grouping: sungen delivery ${target.screen} --approve${COLOR.reset}`);
687
+ log(` ${COLOR.cyan}→ or render a watermarked draft: sungen delivery ${target.screen} --preview${COLOR.reset}`);
688
+ return null;
689
+ }
690
+
691
+ const { csvPath, xlsxPath } = await writeMatrixDeliverables(paths, model);
692
+ const passed = model.items.reduce((a, i) => a + i.resultCounts.passed, 0);
693
+ const failed = model.items.reduce((a, i) => a + i.resultCounts.failed, 0);
694
+ log(` ${COLOR.green}→ ${path.relative(cwd, xlsxPath)}${COLOR.reset} ${COLOR.gray}(+ ${path.relative(cwd, csvPath)})${COLOR.reset}`);
695
+ return {
696
+ label: paths.label,
697
+ items: model.manifest.itemCount,
698
+ variants: model.manifest.variantCount,
699
+ passed,
700
+ failed,
701
+ file: path.relative(cwd, xlsxPath),
702
+ };
703
+ }
704
+
705
+ function printMatrixSummaryTable(summaries: MatrixRunSummary[]): void {
706
+ log(`\n${COLOR.bold}Delivery matrix export complete${COLOR.reset}\n`);
707
+ const colWidth = Math.max(20, ...summaries.map((s) => s.label.length)) + 1;
708
+ log(` ${'Feature'.padEnd(colWidth)}Items Variants Passed Failed File`);
709
+ log(' ' + '-'.repeat(colWidth + 50));
710
+ for (const s of summaries) {
711
+ log(
712
+ ` ${s.label.padEnd(colWidth)}${String(s.items).padStart(5)} ${String(s.variants).padStart(8)} ${String(s.passed).padStart(6)} ${String(s.failed).padStart(6)} ${s.file}`
713
+ );
714
+ }
715
+ log(`\n${COLOR.gray}Items = review rows (one target + intent + oracle family). Variants = execution units — use variant counts for progress.${COLOR.reset}\n`);
716
+ }
717
+
600
718
  // ----------------------------------------------------------------------------
601
719
  // Reporter
602
720
  // ----------------------------------------------------------------------------
@@ -667,12 +785,20 @@ function printSummaryTable(summaries: ScreenSummary[]): void {
667
785
  export function registerDeliveryCommand(program: Command): void {
668
786
  program
669
787
  .command('delivery')
670
- .description('Export Gherkin + Playwright results CSV test case deliverable')
788
+ .description('Export the Test Case & Coverage Matrix (Gherkin + results). --legacy keeps the classic per-scenario workbook.')
671
789
  .argument('[names...]', 'Specific screen or flow names. Omit to process all.')
672
790
  .option('--skip-preflight', 'Skip pre-flight checks (not recommended)')
673
791
  .option('--continue-on-missing', 'Skip targets with blocking misses instead of aborting')
674
792
  .option('--env <env>', 'Environment/locale of the run to deliver (sets SUNGEN_ENV; e.g. dev, staging, ja, dev-ja)')
675
- .action(async (names: string[], options: { skipPreflight?: boolean; continueOnMissing?: boolean; env?: string }) => {
793
+ .option('--legacy', 'Classic per-scenario-steps workbook (BM-2-901-13 layout)')
794
+ .option('--full', 'Alias of --legacy')
795
+ .option('--check', 'Matrix gates only — validate the delivery map, write nothing')
796
+ .option('--approve [ids]', 'Approve proposed groups (all, or a comma-separated id list) + stamp fingerprints')
797
+ .option('--preview', 'Render the matrix despite review-required findings (DRAFT watermark)')
798
+ .action(async (names: string[], options: {
799
+ skipPreflight?: boolean; continueOnMissing?: boolean; env?: string;
800
+ legacy?: boolean; full?: boolean; check?: boolean; approve?: boolean | string; preview?: boolean;
801
+ }) => {
676
802
  try {
677
803
  // Same effect as running with SUNGEN_ENV=<env> — result-file resolution,
678
804
  // locale pairing, and test-data overlays all key off this one string.
@@ -728,6 +854,46 @@ export function registerDeliveryCommand(program: Command): void {
728
854
  }
729
855
 
730
856
  // 3. Export
857
+ const legacy = options.legacy || options.full;
858
+ if (!legacy) {
859
+ // Matrix mode (the default). --approve is its own sub-action.
860
+ if (options.approve !== undefined) {
861
+ const ids = typeof options.approve === 'string'
862
+ ? options.approve.split(',').map((s) => s.trim()).filter(Boolean)
863
+ : undefined;
864
+ let blocked = false;
865
+ for (const target of toExport) {
866
+ const paths = matrixPathsFor(cwd, target);
867
+ try {
868
+ const { findings, approved } = approveMatrix(paths, ids);
869
+ if (approved.length === 0) {
870
+ log(`${COLOR.red}${paths.label}: approval blocked by Gate errors${COLOR.reset}`);
871
+ printFindings(findings);
872
+ blocked = true;
873
+ } else {
874
+ log(`${COLOR.green}${paths.label}: approved ${approved.length} group(s)${COLOR.reset} — fingerprints stamped in ${path.relative(cwd, paths.mapFile)}`);
875
+ }
876
+ } catch (e) {
877
+ log(`${COLOR.red}${paths.label}:${COLOR.reset} ${e instanceof Error ? e.message : e}`);
878
+ blocked = true;
879
+ }
880
+ }
881
+ if (blocked) process.exit(1);
882
+ return;
883
+ }
884
+
885
+ const matrixSummaries: MatrixRunSummary[] = [];
886
+ let failedTargets = 0;
887
+ for (const target of toExport) {
888
+ const s = await exportMatrixTarget(cwd, target, { check: options.check, preview: options.preview });
889
+ if (s) matrixSummaries.push(s);
890
+ else failedTargets++;
891
+ }
892
+ if (matrixSummaries.length > 0 && !options.check) printMatrixSummaryTable(matrixSummaries);
893
+ if (failedTargets > 0) process.exit(1);
894
+ return;
895
+ }
896
+
731
897
  const env = getEnvironment(cwd);
732
898
  const summaries: ScreenSummary[] = [];
733
899
  for (const target of toExport) {