@sun-asterisk/sungen 3.2.16-beta.1 → 3.2.16-beta.11
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.
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +6 -0
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +67 -20
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/exporters/feature-parser.d.ts +16 -1
- package/dist/exporters/feature-parser.d.ts.map +1 -1
- package/dist/exporters/feature-parser.js +21 -2
- package/dist/exporters/feature-parser.js.map +1 -1
- package/dist/exporters/matrix/build.d.ts +27 -2
- package/dist/exporters/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +277 -47
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/export.d.ts +7 -4
- package/dist/exporters/matrix/export.d.ts.map +1 -1
- package/dist/exporters/matrix/export.js +22 -6
- package/dist/exporters/matrix/export.js.map +1 -1
- package/dist/exporters/matrix/gates.d.ts.map +1 -1
- package/dist/exporters/matrix/gates.js +112 -3
- package/dist/exporters/matrix/gates.js.map +1 -1
- package/dist/exporters/matrix/map-loader.d.ts.map +1 -1
- package/dist/exporters/matrix/map-loader.js +20 -0
- package/dist/exporters/matrix/map-loader.js.map +1 -1
- package/dist/exporters/matrix/render-csv.d.ts +3 -2
- package/dist/exporters/matrix/render-csv.d.ts.map +1 -1
- package/dist/exporters/matrix/render-csv.js +53 -30
- package/dist/exporters/matrix/render-csv.js.map +1 -1
- package/dist/exporters/matrix/render-xlsx.d.ts +32 -8
- package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
- package/dist/exporters/matrix/render-xlsx.js +215 -83
- package/dist/exporters/matrix/render-xlsx.js.map +1 -1
- package/dist/exporters/matrix/types.d.ts +53 -7
- package/dist/exporters/matrix/types.d.ts.map +1 -1
- package/dist/exporters/matrix/types.js +2 -2
- package/dist/exporters/matrix/types.js.map +1 -1
- package/dist/exporters/matrix/wording.d.ts +61 -0
- package/dist/exporters/matrix/wording.d.ts.map +1 -0
- package/dist/exporters/matrix/wording.js +221 -0
- package/dist/exporters/matrix/wording.js.map +1 -0
- package/dist/exporters/scenario-merger.d.ts.map +1 -1
- package/dist/exporters/scenario-merger.js +2 -1
- package/dist/exporters/scenario-merger.js.map +1 -1
- package/dist/exporters/spec-parser.d.ts.map +1 -1
- package/dist/exporters/spec-parser.js +2 -1
- package/dist/exporters/spec-parser.js.map +1 -1
- package/dist/harness/audit.d.ts +6 -0
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +80 -15
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/blindspot.d.ts.map +1 -1
- package/dist/harness/blindspot.js +2 -1
- package/dist/harness/blindspot.js.map +1 -1
- package/dist/harness/capability-plan.d.ts.map +1 -1
- package/dist/harness/capability-plan.js +3 -2
- package/dist/harness/capability-plan.js.map +1 -1
- package/dist/harness/feedback.d.ts.map +1 -1
- package/dist/harness/feedback.js +3 -2
- package/dist/harness/feedback.js.map +1 -1
- package/dist/harness/flow-check.d.ts.map +1 -1
- package/dist/harness/flow-check.js +2 -1
- package/dist/harness/flow-check.js.map +1 -1
- package/dist/harness/flow-plan.d.ts.map +1 -1
- package/dist/harness/flow-plan.js +3 -2
- package/dist/harness/flow-plan.js.map +1 -1
- package/dist/harness/intent.d.ts.map +1 -1
- package/dist/harness/intent.js +2 -1
- package/dist/harness/intent.js.map +1 -1
- package/dist/harness/journey.d.ts.map +1 -1
- package/dist/harness/journey.js +3 -2
- package/dist/harness/journey.js.map +1 -1
- package/dist/harness/ledger.d.ts.map +1 -1
- package/dist/harness/ledger.js +3 -2
- package/dist/harness/ledger.js.map +1 -1
- package/dist/harness/manifest.d.ts.map +1 -1
- package/dist/harness/manifest.js +4 -3
- package/dist/harness/manifest.js.map +1 -1
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +16 -3
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/quality-gates.d.ts.map +1 -1
- package/dist/harness/quality-gates.js +2 -1
- package/dist/harness/quality-gates.js.map +1 -1
- package/dist/harness/read-text.d.ts +22 -0
- package/dist/harness/read-text.d.ts.map +1 -0
- package/dist/harness/read-text.js +64 -0
- package/dist/harness/read-text.js.map +1 -0
- package/dist/harness/script-check.d.ts.map +1 -1
- package/dist/harness/script-check.js +3 -2
- package/dist/harness/script-check.js.map +1 -1
- package/dist/harness/sensors.d.ts +13 -1
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +63 -20
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/spec-coverage.d.ts +5 -0
- package/dist/harness/spec-coverage.d.ts.map +1 -1
- package/dist/harness/spec-coverage.js +17 -7
- package/dist/harness/spec-coverage.js.map +1 -1
- package/dist/harness/trace.d.ts.map +1 -1
- package/dist/harness/trace.js +4 -3
- package/dist/harness/trace.js.map +1 -1
- package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
- package/dist/harness/viewpoint-ledger.js +2 -1
- package/dist/harness/viewpoint-ledger.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/create-test.md +9 -0
- package/dist/orchestrator/templates/ai-src/commands/delivery.md +103 -18
- package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +77 -11
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +1 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +22 -0
- package/package.json +4 -4
- package/src/cli/commands/audit.ts +5 -0
- package/src/cli/commands/delivery.ts +68 -22
- package/src/exporters/feature-parser.ts +21 -2
- package/src/exporters/matrix/build.ts +281 -43
- package/src/exporters/matrix/export.ts +31 -6
- package/src/exporters/matrix/gates.ts +119 -3
- package/src/exporters/matrix/map-loader.ts +22 -1
- package/src/exporters/matrix/render-csv.ts +53 -30
- package/src/exporters/matrix/render-xlsx.ts +216 -85
- package/src/exporters/matrix/types.ts +58 -8
- package/src/exporters/matrix/wording.ts +221 -0
- package/src/exporters/scenario-merger.ts +2 -1
- package/src/exporters/spec-parser.ts +2 -1
- package/src/harness/audit.ts +84 -16
- package/src/harness/blindspot.ts +2 -1
- package/src/harness/capability-plan.ts +3 -2
- package/src/harness/feedback.ts +3 -2
- package/src/harness/flow-check.ts +2 -1
- package/src/harness/flow-plan.ts +3 -2
- package/src/harness/intent.ts +2 -1
- package/src/harness/journey.ts +3 -2
- package/src/harness/ledger.ts +3 -2
- package/src/harness/manifest.ts +4 -3
- package/src/harness/parse.ts +17 -3
- package/src/harness/quality-gates.ts +2 -1
- package/src/harness/read-text.ts +28 -0
- package/src/harness/script-check.ts +3 -2
- package/src/harness/sensors.ts +55 -8
- package/src/harness/spec-coverage.ts +22 -7
- package/src/harness/trace.ts +4 -3
- package/src/harness/viewpoint-ledger.ts +2 -1
- package/src/orchestrator/templates/ai-src/commands/create-test.md +9 -0
- package/src/orchestrator/templates/ai-src/commands/delivery.md +103 -18
- package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +77 -11
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +1 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +22 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: delivery
|
|
3
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
|
|
4
|
+
argument-hint: "[name...] [--format csv] [--env <locale>] [--legacy] (omit names for all)"
|
|
5
5
|
order: 50
|
|
6
6
|
claude-tools: "Bash, Read, Write, AskUserQuestion"
|
|
7
7
|
copilot-tools: "[read, execute, edit, vscode/askQuestions]"
|
|
@@ -26,7 +26,14 @@ review → approve → official render.
|
|
|
26
26
|
## Parameters
|
|
27
27
|
|
|
28
28
|
Parse from `$ARGUMENTS`:
|
|
29
|
-
- **names** — zero or more screen/flow/api names. Empty →
|
|
29
|
+
- **names** — zero or more screen/flow/api names. Empty → **a sweep of every unit**.
|
|
30
|
+
Strictness follows the scope: a **named** unit that is not ready aborts the run (the user asked
|
|
31
|
+
for that unit); a **sweep** exports every ready unit and skips the rest with an accounted
|
|
32
|
+
"Not exported" list, succeeding as long as something was written. So `/sungen:delivery` with no
|
|
33
|
+
argument is safe in a work-in-progress project — scaffolded units (added but `create-test` not
|
|
34
|
+
run) no longer block the units that are ready.
|
|
35
|
+
- **`--format <xlsx|csv|both>`** — output format. **Default `xlsx` — one artifact only.** Pass
|
|
36
|
+
`--format csv` when the user wants the flat CSV (pipelines/diffing), `--format both` for both.
|
|
30
37
|
- **`--env <locale>`** — sets `SUNGEN_ENV=<locale>` for the run (accept `--locale` as alias).
|
|
31
38
|
- **`--legacy` / `--full`** — pass through to the CLI and skip the map flow entirely.
|
|
32
39
|
|
|
@@ -37,14 +44,19 @@ Parse from `$ARGUMENTS`:
|
|
|
37
44
|
```bash
|
|
38
45
|
[ -x ./bin/sungen.js ] && ./bin/sungen.js delivery <names> || npx sungen delivery <names>
|
|
39
46
|
```
|
|
40
|
-
(prepend `SUNGEN_ENV=<locale>` when `--env` was given; append `--
|
|
41
|
-
to step 5.)
|
|
47
|
+
(prepend `SUNGEN_ENV=<locale>` when `--env` was given; append `--format <fmt>` when the user asked
|
|
48
|
+
for a non-default format; append `--legacy` when requested — then skip to step 5.)
|
|
42
49
|
|
|
43
50
|
Three outcomes per target:
|
|
44
51
|
- **Rendered** → done, go to step 5.
|
|
45
52
|
- **"no delivery map"** → go to step 2 (propose it).
|
|
46
53
|
- **Gate findings / "not approved"** → go to step 4 (review & approve).
|
|
47
54
|
|
|
55
|
+
On a sweep, read the **"Not exported"** list at the end: units marked *not authored yet* need
|
|
56
|
+
`/sungen:create-test` first, the others each printed their own reason above (missing map, gate
|
|
57
|
+
errors, or awaiting approval). Handle them one unit at a time — do not re-run the sweep expecting
|
|
58
|
+
a different result.
|
|
59
|
+
|
|
48
60
|
### 2. Propose the Delivery Map (only when missing or the user asks to regroup)
|
|
49
61
|
|
|
50
62
|
Read the unit's `.feature` (and `requirements/spec.md` for target naming). Write
|
|
@@ -58,27 +70,97 @@ groups:
|
|
|
58
70
|
target: login.email # ONE target: field/component dot-path, flow phrase, or METHOD /path
|
|
59
71
|
intent: <one behavior/rule this item verifies>
|
|
60
72
|
oracle: <the shared observable Pass/Fail statement>
|
|
61
|
-
|
|
73
|
+
dimensions: violated rule — required ×3 · format ×10 · full-width ×2 # see below
|
|
74
|
+
category: normal | abnormal | security | nfr # see the rule below
|
|
62
75
|
review: proposed # ALWAYS proposed — only QA approval flips it
|
|
63
|
-
variants: [VP-VAL-001-B, VP-VAL-001-S] #
|
|
76
|
+
variants: [VP-VAL-001-B, VP-VAL-001-S] # the scenario ids of THIS project (VP-…, SEC-123, MS-HP-001 —
|
|
77
|
+
# whatever the titles use); a bare id on a @cases scenario = all its rows
|
|
64
78
|
dispositions: # scenarios intentionally NOT delivered as test cases
|
|
65
79
|
VP-DATA-000: { as: excluded, reason: data-setup checklist }
|
|
66
80
|
# as: excluded | blocked | covered_elsewhere | accepted_risk
|
|
67
81
|
```
|
|
68
82
|
|
|
69
|
-
**Grouping rules (the aggregation signature)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
-
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
**Grouping rules (the aggregation signature) — group COMPACTLY.** The matrix exists to be
|
|
84
|
+
substantially shorter than the scenario list, so a reviewer can see missing viewpoints at a
|
|
85
|
+
glance. Merge whenever the cases share ALL of: target · test intent/business rule ·
|
|
86
|
+
precondition/condition · trigger or procedure shape · **the way the expected result is
|
|
87
|
+
determined** (its oracle *family*, not its exact message).
|
|
88
|
+
|
|
89
|
+
- **Oracle family = the determination method, parameterized.** All validation branches of ONE
|
|
90
|
+
field belong to ONE item — required, format, length, character-class are *expected branches*
|
|
91
|
+
(parameters) of "the field shows the validation message defined for the violated rule", shown
|
|
92
|
+
per variant, never separate items.
|
|
93
|
+
- MAY vary inside one item (coverage dimensions, visible on the sub-rows): data values, boundary
|
|
94
|
+
points, **account states** (a seeded/locked/deleted account next to a wrong-password case),
|
|
95
|
+
provider/browser/locale, `@cases` rows, a different trigger with the same oracle (blur vs
|
|
96
|
+
submit), **execution mode** (auto + manual mix — the parent shows `Auto n · Manual m`), and
|
|
97
|
+
**priority** (the item takes the highest; per-variant priorities stay visible).
|
|
98
|
+
- MUST split: different target, different intent/business rule, different way of determining the
|
|
99
|
+
expected result (a field-error family ≠ a session-established family), different test layer
|
|
100
|
+
(`@api`/`@query`), materially different precondition, or a different procedure shape —
|
|
101
|
+
sequence-sensitive flows (re-Given/When after a Then) stay solo. **Different risk classes never
|
|
102
|
+
merge**: XSS and SQL injection are separate items (different risk and determination), even on
|
|
103
|
+
the same field.
|
|
104
|
+
- **Look for these families before settling on a grouping** — they are where under-merging happens:
|
|
105
|
+
| Family | Merge into one item |
|
|
106
|
+
|---|---|
|
|
107
|
+
| Static render | every "element X is visible/has its content on load" scenario of the screen — title, instructions, progress step, buttons present, header/footer |
|
|
108
|
+
| Field validation branches | all rules of ONE field (required · format · length · character class · full-width) |
|
|
109
|
+
| Account/entity states | wrong-credentials · locked · deleted · unverified for the same rejection oracle |
|
|
110
|
+
| Provider / surface sets | the 3 OAuth providers, header+footer, the a11y surfaces of one behaviour |
|
|
111
|
+
| Lifetime / mode pairs | checked vs unchecked, mobile vs desktop, when the oracle is one rule with two branches |
|
|
112
|
+
On a 36-scenario screen, four separate one-variant "renders on load" items should have been one.
|
|
113
|
+
- When unsure, keep items separate — the gates and QA decide, never guess-merge.
|
|
78
114
|
- Every scenario must land in exactly one group **or** one disposition (Gate B enforces 100%
|
|
79
115
|
disposition). Data-setup blocks (`@manual:data-setup`) → `excluded`; SPEC-GAP placeholders →
|
|
80
116
|
`blocked`.
|
|
81
117
|
|
|
118
|
+
**`dimensions:` — the compact coverage digest (required for items with >3 variants).**
|
|
119
|
+
This one short line is what the collapsed parent row shows instead of listing every variant, so a
|
|
120
|
+
reviewer sees *which dimensions* the item covers without expanding it. Name the dimension, then the
|
|
121
|
+
branches with counts:
|
|
122
|
+
- `violated rule — required ×3 · format ×10 · full-width ×2`
|
|
123
|
+
- `account state — wrong password · unregistered · locked · soft-deleted`
|
|
124
|
+
- `submission method — Login button · Enter in Password · Enter in Email`
|
|
125
|
+
|
|
126
|
+
Keep it ≤120 chars (Gate W warns). **YAML caveat:** a bare `: ` inside the value breaks the parse —
|
|
127
|
+
use ` — ` as the label separator (as above) or quote the whole string.
|
|
128
|
+
|
|
129
|
+
**`category` is not free choice for two classes (Gate K checks it):** a group whose variants are
|
|
130
|
+
`VP-SEC-*` MUST be `category: security`, and `VP-NFR-*` MUST be `nfr` — otherwise the Coverage
|
|
131
|
+
sheet's security/nfr column renders empty and the grid reports a gap the unit does not have while
|
|
132
|
+
hiding the work it does have. `normal` vs `abnormal` stays your judgement.
|
|
133
|
+
|
|
134
|
+
**Wording rules for `intent`/`oracle` (customer-facing — Gate W lints these):**
|
|
135
|
+
- Plain product language, present simple, ~10–20 words, one behavior:
|
|
136
|
+
"A user can sign in with valid credentials and is redirected to the Jobs page."
|
|
137
|
+
- Oracle = the observable outcome as a definite assertion ("The Jobs page is displayed and the
|
|
138
|
+
Logout link is visible.") — no `should`, no tester actions.
|
|
139
|
+
- NEVER: `{{tokens}}`, `[Selector]` references, DSL phrasing (`User fill/click/see`), generator
|
|
140
|
+
labels (`Setup:`/`Observable:`/`Oracle:`), or vague verbs (`handles`, `surfaces`) when a precise
|
|
141
|
+
behavior exists. Use the visible UI label (the Login button, the Email field).
|
|
142
|
+
- **Preserve the source meaning exactly** — never strengthen, weaken, or reinterpret an oracle
|
|
143
|
+
(a security assertion especially: if the source says "the password appears ONLY in the HTTPS
|
|
144
|
+
POST body", do not write "no plaintext password on the network").
|
|
145
|
+
|
|
146
|
+
**Requirement coverage (`requirements:` section, optional):** `sungen delivery` scans
|
|
147
|
+
`requirements/spec.md` for FR-/TR-/NFR- ids; ids traced by `@spec:` tags are `covered`, the rest
|
|
148
|
+
are `gap` (Gate R warning). Record the reviewed status for genuine non-gaps:
|
|
149
|
+
|
|
150
|
+
```yaml
|
|
151
|
+
requirements:
|
|
152
|
+
TR-007: { status: planned, note: Performance needs Lighthouse-style tooling }
|
|
153
|
+
TR-004: { status: partially_covered, note: client-side covered by VP-SEC-003; hashing needs DB verify }
|
|
154
|
+
# status: covered | partially_covered | covered_elsewhere | planned | gap | not_applicable
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Never write `status: covered` for a requirement no variant traces to** (Gate R flags it). A note
|
|
158
|
+
saying "proven by DI-SEC-CSRF" is prose — nothing detects it when that scenario later changes. If a
|
|
159
|
+
scenario in THIS feature proves the requirement, **add `@spec:<id>` to that scenario** so the trace
|
|
160
|
+
is real, then drop the override (it derives as `covered` on its own). Use `covered_elsewhere` only
|
|
161
|
+
when another suite proves it, and name that suite; `not_applicable` when the spec itself excludes
|
|
162
|
+
the requirement.
|
|
163
|
+
|
|
82
164
|
Then validate and fix any ERROR findings:
|
|
83
165
|
|
|
84
166
|
```bash
|
|
@@ -115,6 +197,7 @@ counted in variants, never items**). Then `AskUserQuestion`:
|
|
|
115
197
|
- **Open the workbook** — inspect `qa/deliverables/<unit>-testcases.xlsx` (Testcases sheet:
|
|
116
198
|
collapse outline level 1 for the customer view; Coverage sheet: target × category grid + gaps).
|
|
117
199
|
- **Run tests to refresh results** — `/sungen:run-test <unit>`, then re-run delivery.
|
|
200
|
+
- **Also export CSV** — `sungen delivery <unit> --format csv` (flat `item`/`variant` rows for pipelines).
|
|
118
201
|
- **Export the legacy workbook too** — `sungen delivery <unit> --legacy`.
|
|
119
202
|
- **Done**
|
|
120
203
|
|
|
@@ -132,7 +215,8 @@ counted in variants, never items**). Then `AskUserQuestion`:
|
|
|
132
215
|
## CLI reference
|
|
133
216
|
|
|
134
217
|
```
|
|
135
|
-
sungen delivery [names...] # matrix (default; needs the map)
|
|
218
|
+
sungen delivery [names...] # matrix (default; needs the map) → XLSX only
|
|
219
|
+
--format <xlsx|csv|both> # output format; default xlsx (one artifact)
|
|
136
220
|
--check # gates only — validate the map, write nothing
|
|
137
221
|
--approve [DI-a,DI-b] # flip proposed→approved (+ stamp fingerprints); all groups when bare
|
|
138
222
|
--preview # render despite review findings (DRAFT watermark)
|
|
@@ -140,5 +224,6 @@ sungen delivery [names...] # matrix (default; needs the map)
|
|
|
140
224
|
--skip-preflight | --continue-on-missing | --env <env> # as before
|
|
141
225
|
```
|
|
142
226
|
|
|
143
|
-
Outputs: `qa/deliverables/<unit>-testcases.xlsx` (Testcases + Coverage sheets)
|
|
144
|
-
(flat, `Level` column `item|variant`)
|
|
227
|
+
Outputs: `qa/deliverables/<unit>-testcases.xlsx` (Testcases + Coverage sheets) by default;
|
|
228
|
+
`--format csv` writes `<unit>-testcases.csv` instead (flat, `Level` column `item|variant`),
|
|
229
|
+
`--format both` writes both. Legacy mode writes the classic files instead.
|
|
@@ -29,19 +29,85 @@ fingerprints). Schema + grouping rules live in the delivery command instructions
|
|
|
29
29
|
spec is `docs/spec/delivery-coverage-matrix-spec.md`.
|
|
30
30
|
|
|
31
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 (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
`
|
|
42
|
-
|
|
32
|
+
B mapping (every scenario in exactly one group XOR one disposition) · C aggregation (test layer
|
|
33
|
+
recomputed and equal within a group — **execution mode and priority are coverage dimensions, not
|
|
34
|
+
splits**: mixed items show `Auto n · Manual m` and take the highest variant priority; heuristic
|
|
35
|
+
oracle-shape/precondition mismatches are review-level, silenced once approved and unchanged) ·
|
|
36
|
+
D executability (precondition · condition+
|
|
37
|
+
data · trigger · oracle all renderable; every `{{var}}` resolves; **no template token may survive
|
|
38
|
+
into a rendered cell** — test-data cross-references are resolved for display) · E drift
|
|
39
|
+
(scenario fingerprint mismatch → back to review; the map's OWN reviewed
|
|
40
|
+
wording/grouping is fingerprinted as `__map__` too, so post-approval edits re-open review) · G review state (proposed groups block the official
|
|
41
|
+
render; `--preview` renders a DRAFT watermark) · K category class (a `VP-SEC-*` variant outside
|
|
42
|
+
`category: security`, or `VP-NFR-*` outside `nfr` → warning: the Coverage grid's column would render
|
|
43
|
+
empty and report a false gap) · R requirement coverage (spec FR/TR/NFR ids with no trace and no
|
|
44
|
+
`requirements:` status → warning; **and a `status: covered` override that no variant traces to** →
|
|
45
|
+
warning: tag the proving scenario `@spec:<id>` instead of asserting it in prose) · W wording lint (map intent/oracle containing
|
|
46
|
+
tokens, `[Selector]` refs, DSL phrasing, or generator labels → warning).
|
|
47
|
+
|
|
48
|
+
**Wording normalization (deterministic, after semantic normalization):** DSL steps render as
|
|
49
|
+
controlled manual-test English without changing meaning — actions in the imperative (`User fill
|
|
50
|
+
[Email] field with X` → `Enter X in the Email field.`), expected results as observable assertions
|
|
51
|
+
(`User see [Jobs] page` → `The Jobs page is displayed.`), preconditions as states (`The user is
|
|
52
|
+
signed out.`). Manual `# Tester verifies:` labels become structured fields: `Setup:` →
|
|
53
|
+
Precondition, `Action:` → Action, `Observable:` → Expected Result, `Oracle:` → a separate
|
|
54
|
+
`Verification method:` line. Sequence-sensitive flows keep event order: actions numbered with
|
|
55
|
+
mid-flow assertions inline as `Verify: …`; only the final Then block is the Expected Result.
|
|
56
|
+
Empty test values render as `(empty)`.
|
|
57
|
+
|
|
58
|
+
**Output format**: **XLSX only by default** (one artifact). `--format csv` writes the flat CSV
|
|
59
|
+
instead; `--format both` writes both.
|
|
60
|
+
|
|
61
|
+
**Scope decides strictness**: a **named** unit that is not ready aborts with exit 1 (pass
|
|
62
|
+
`--continue-on-missing` to tolerate it); a **no-argument sweep** exports every ready unit, lists
|
|
63
|
+
every unit it did not export with the reason, and exits 0 as long as it wrote something — a
|
|
64
|
+
scaffolded unit never blocks the ready ones.
|
|
65
|
+
|
|
66
|
+
**Workbook**: `Testcases` sheet — parent rows + outline-level-1 variant sub-rows for **every**
|
|
67
|
+
item (single-variant included: the sub-row carries the source VP-id, resolved data, and the
|
|
68
|
+
result/evidence entry). Sub-rows are **delta-only**: knowledge common to the whole item
|
|
69
|
+
(precondition, trigger) is written ONCE on the parent; a child repeats only what distinguishes it
|
|
70
|
+
(condition/data, precondition delta, trigger when it differs) plus its own precise oracle and
|
|
71
|
+
execution fields. The parent never carries placeholder text — when triggers differ the cell is
|
|
72
|
+
simply empty and the sub-rows carry them.
|
|
73
|
+
|
|
74
|
+
Parent preconditions are the **intersection** of the variants' and the parent's Action holds the
|
|
75
|
+
**shared leading steps**; each variant renders only its remaining steps, numbered to continue the
|
|
76
|
+
parent's list — so a shared prefix is written once, not repeated on every child. The parent's
|
|
77
|
+
Coverage cell is the map's short `dimensions:` digest (or the variant refs when none is declared),
|
|
78
|
+
never a concatenation of variant titles.
|
|
79
|
+
|
|
80
|
+
Variant Result cells hold exactly one of `Passed`/`Failed`/`Blocked`/`Pending`/`N/A` (dropdown) —
|
|
81
|
+
that vocabulary is the contract: the parent Result and the summary band are **live Excel formulas**
|
|
82
|
+
counting those words, so a parent label is always a composed `n/m Passed …` string (`All N/A` when
|
|
83
|
+
every variant is N/A) and never a bare state word. **`N/A` leaves the denominator.** Evidence and
|
|
84
|
+
Defect ID are separate columns; ID + Target frozen; ISO dates; landscape print with repeated
|
|
85
|
+
header rows.
|
|
86
|
+
|
|
87
|
+
`Coverage` sheet — requirement coverage table (every FR/TR/NFR id with an explicit status),
|
|
88
|
+
target × category grid with explicit `—` gaps, dispositions, manifest. CSV mirrors the same model
|
|
89
|
+
flat with a `Level` column (`item`/`variant`) + a requirement-coverage appendix.
|
|
43
90
|
`delivery_item_count` ≠ progress — variants are the execution metric.
|
|
44
91
|
|
|
92
|
+
**Id schemes**: the `VP-<CAT>-<NNN>` convention is a suggestion, not a requirement — a project's own
|
|
93
|
+
scheme (`SEC-123`, `PER-345`, `MS-HP-001`) is addressed the same way, and the category segment
|
|
94
|
+
(`SEC`, `PER`) still drives Gate K. Requirement ids come from the spec's own bolded declarations
|
|
95
|
+
(`- **REQ-12**: …`), so a project that does not use FR/TR/NFR still gets its coverage table.
|
|
96
|
+
|
|
97
|
+
**Runtime-captured values**: a scenario may produce its own values — `User remember [X] text as
|
|
98
|
+
{{v}}` (compared later) or a capability binding (`@query:`/`@api:`/`@dataFactory:`). These never
|
|
99
|
+
exist in test-data, so Gate D does not ask for them: the capture step renders as an instruction
|
|
100
|
+
(`Note the X text for later (as "v")`) and later references read `the captured v`.
|
|
101
|
+
|
|
102
|
+
**Data fidelity**: invisible test data is made visible, never normalized — `''` → `(empty)`,
|
|
103
|
+
whitespace-only → `(5 spaces)`, padded → `" value "` quoted verbatim. A trim/collapse here would
|
|
104
|
+
silently break the whitespace tests it describes.
|
|
105
|
+
|
|
106
|
+
**Authoring guidance the matrix rewards** (create-test side): payload/provider matrices (SQLi
|
|
107
|
+
payload lists, OAuth provider sets) belong in `@cases` datasets so each case is an atomic,
|
|
108
|
+
independently-reportable variant; keep dataset `case:` labels short and stable (`CHK-EMAIL-I1`),
|
|
109
|
+
with descriptions in other columns — the label is part of the variant's identity.
|
|
110
|
+
|
|
45
111
|
---
|
|
46
112
|
|
|
47
113
|
## Legacy mode (--legacy / --full)
|
|
@@ -288,6 +288,7 @@ Options: `nth` `exact` `scope` `match` `variant` `frame` `contenteditable` `colu
|
|
|
288
288
|
|
|
289
289
|
| Tag | Effect |
|
|
290
290
|
|---|---|
|
|
291
|
+
| `@spec:<id>` | **Traceability**: this scenario proves requirement `<id>` from `requirements/spec.md` (`@spec:FR-003`). Repeatable — a scenario may prove several. Read by `sungen audit` (requirement coverage) and by delivery (the Coverage sheet's requirement table). Without it the link is only inferred from wording and cannot survive an edit. |
|
|
291
292
|
| `@manual` | Skip in generation |
|
|
292
293
|
| `@auth:role` | Use auth storage state for role |
|
|
293
294
|
| `@no-auth` | Disable inherited auth |
|
|
@@ -4,6 +4,28 @@ description: 'Use when create-test needs to translate spec.md/Figma/UI into .fea
|
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
## Traceability — tag the requirement each scenario proves
|
|
9
|
+
|
|
10
|
+
Every requirement id in `requirements/spec.md` (`FR-`/`TR-`/`NFR-`) must be reachable from a
|
|
11
|
+
scenario, and the link must be **written down, not inferred**:
|
|
12
|
+
|
|
13
|
+
```gherkin
|
|
14
|
+
@high @spec:FR-003
|
|
15
|
+
Scenario: VP-VAL-002 An email in an invalid format shows the format error
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- Put `@spec:<id>` on the scenario that actually proves the requirement; repeat the tag when one
|
|
19
|
+
scenario proves several (`@spec:FR-003 @spec:FR-004`).
|
|
20
|
+
- One requirement may be proved by several scenarios — tag each of them.
|
|
21
|
+
- A requirement you deliberately do not cover here (performance budget, i18n suite, server-side
|
|
22
|
+
concern) needs no tag; record it later in the delivery map's `requirements:` section with a
|
|
23
|
+
status and reason.
|
|
24
|
+
- Why it matters: `sungen audit` also counts a requirement as covered when its keywords merely
|
|
25
|
+
resemble a scenario title, and reports `SPEC-TRACE-IMPLICIT` for those. Delivery's requirement
|
|
26
|
+
table follows **only** the explicit tag — an untagged requirement reads as an uncovered gap in
|
|
27
|
+
the customer-facing coverage sheet even when a scenario does prove it.
|
|
28
|
+
|
|
7
29
|
## ⚠️ Gotchas — read before generating
|
|
8
30
|
|
|
9
31
|
- **Field-level test-data follows the Data Factory standard — don't hand-invent values.** When the unit has input fields, the field values (valid / boundary / invalid + error codes) come from the **`sungen-data-factory`** catalog via a field-map + `sungen data gen` (create-test step 5.3), not free-form guessing. Weave the standardized boundary/invalid sets into `@cases` with `CHK-*` trace. This skill still owns scenario structure, viewpoints, and non-field oracles.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sun-asterisk/sungen",
|
|
3
|
-
"version": "3.2.16-beta.
|
|
3
|
+
"version": "3.2.16-beta.11",
|
|
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/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",
|
|
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/delivery-scope-strictness.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/harness/score-model.run.ts && tsx tests/harness/spec-trace-crlf.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.16-beta.
|
|
43
|
-
"@sungen/driver-ui": "3.2.16-beta.
|
|
42
|
+
"@sungen/driver-data-factory": "3.2.16-beta.11",
|
|
43
|
+
"@sungen/driver-ui": "3.2.16-beta.11",
|
|
44
44
|
"chalk": "^5.6.2",
|
|
45
45
|
"commander": "^14.0.2",
|
|
46
46
|
"dotenv": "^17.2.3",
|
|
@@ -78,7 +78,12 @@ function render(r: AuditReport): void {
|
|
|
78
78
|
if (r.calibration) {
|
|
79
79
|
const ax = Object.entries(r.calibration.axes).map(([k, v]) => `${k}=${(v * 100).toFixed(0)}%`).join(' · ');
|
|
80
80
|
L(` ⑨ Calibration — ${ax}`);
|
|
81
|
+
const na = r.calibration.notApplicable ?? [];
|
|
82
|
+
if (na.length) L(` not scored (no evidence in this unit): ${na.join(', ')}`);
|
|
81
83
|
L(` weakest: ${r.calibration.weakest.axis} ${(r.calibration.weakest.value * 100).toFixed(0)}%${r.calibration.inflated ? ' ⚠ SCORE-INFLATED-BY-BREADTH' : ''}`);
|
|
84
|
+
if (r.calibration.cappedAt !== undefined) {
|
|
85
|
+
L(` ⚠ score held at ${r.calibration.cappedAt.toFixed(1)} by the weakest axis — fix it to lift the number`);
|
|
86
|
+
}
|
|
82
87
|
L('');
|
|
83
88
|
}
|
|
84
89
|
L(' ── Findings (Repair targets) ──');
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
loadMatrixModel,
|
|
32
32
|
approveMatrix,
|
|
33
33
|
writeMatrixDeliverables,
|
|
34
|
+
MatrixFormat,
|
|
34
35
|
hasErrors,
|
|
35
36
|
hasReview,
|
|
36
37
|
} from '../../exporters/matrix/export';
|
|
@@ -621,6 +622,7 @@ function matrixPathsFor(cwd: string, target: DeliveryTarget): MatrixTargetPaths
|
|
|
621
622
|
featureFile: path.join(base, 'features', `${target.featureBaseName}.feature`),
|
|
622
623
|
testDataFile: resolveTestDataPathForTarget(cwd, target),
|
|
623
624
|
specFile: path.join(genBase, `${target.featureBaseName}.spec.ts`),
|
|
625
|
+
specMdFile: path.join(base, 'requirements', 'spec.md'),
|
|
624
626
|
resultsPath: resolveResultsPath(cwd, target),
|
|
625
627
|
mapFile: mapFilePath(base, target.featureBaseName),
|
|
626
628
|
};
|
|
@@ -650,7 +652,7 @@ interface MatrixRunSummary {
|
|
|
650
652
|
async function exportMatrixTarget(
|
|
651
653
|
cwd: string,
|
|
652
654
|
target: DeliveryTarget,
|
|
653
|
-
opts: { check?: boolean; preview?: boolean },
|
|
655
|
+
opts: { check?: boolean; preview?: boolean; format?: MatrixFormat },
|
|
654
656
|
): Promise<MatrixRunSummary | null> {
|
|
655
657
|
const paths = matrixPathsFor(cwd, target);
|
|
656
658
|
const { model, mapMissing, mapErrors } = loadMatrixModel(paths);
|
|
@@ -688,24 +690,38 @@ async function exportMatrixTarget(
|
|
|
688
690
|
return null;
|
|
689
691
|
}
|
|
690
692
|
|
|
691
|
-
const { csvPath, xlsxPath } = await writeMatrixDeliverables(paths, model);
|
|
693
|
+
const { csvPath, xlsxPath } = await writeMatrixDeliverables(paths, model, opts.format ?? 'xlsx');
|
|
692
694
|
const passed = model.items.reduce((a, i) => a + i.resultCounts.passed, 0);
|
|
693
695
|
const failed = model.items.reduce((a, i) => a + i.resultCounts.failed, 0);
|
|
694
|
-
|
|
696
|
+
const written = [xlsxPath, csvPath].filter((p): p is string => !!p).map((p) => path.relative(cwd, p));
|
|
697
|
+
for (const rel of written) log(` ${COLOR.green}→ ${rel}${COLOR.reset}`);
|
|
695
698
|
return {
|
|
696
699
|
label: paths.label,
|
|
697
700
|
items: model.manifest.itemCount,
|
|
698
701
|
variants: model.manifest.variantCount,
|
|
699
702
|
passed,
|
|
700
703
|
failed,
|
|
701
|
-
file:
|
|
704
|
+
file: written.join(' + '),
|
|
702
705
|
};
|
|
703
706
|
}
|
|
704
707
|
|
|
708
|
+
/**
|
|
709
|
+
* One place that accounts for every target the run did NOT write, so a sweep is
|
|
710
|
+
* never silently partial: scaffolds skipped by preflight and units whose gates /
|
|
711
|
+
* missing map stopped the export.
|
|
712
|
+
*/
|
|
713
|
+
function printSkipSummary(skipped: string[], notExported: string[]): void {
|
|
714
|
+
if (skipped.length === 0 && notExported.length === 0) return;
|
|
715
|
+
log(`${COLOR.gray}Not exported (${skipped.length + notExported.length}):${COLOR.reset}`);
|
|
716
|
+
for (const s of skipped) log(` ${COLOR.gray}· ${s}${COLOR.reset}`);
|
|
717
|
+
for (const n of notExported) log(` ${COLOR.gray}· ${n} (see the message above)${COLOR.reset}`);
|
|
718
|
+
log('');
|
|
719
|
+
}
|
|
720
|
+
|
|
705
721
|
function printMatrixSummaryTable(summaries: MatrixRunSummary[]): void {
|
|
706
722
|
log(`\n${COLOR.bold}Delivery matrix export complete${COLOR.reset}\n`);
|
|
707
723
|
const colWidth = Math.max(20, ...summaries.map((s) => s.label.length)) + 1;
|
|
708
|
-
log(` ${'Feature'.padEnd(colWidth)}Items Variants Passed Failed File`);
|
|
724
|
+
log(` ${'Feature'.padEnd(colWidth)}Items Variants Passed Failed File(s)`);
|
|
709
725
|
log(' ' + '-'.repeat(colWidth + 50));
|
|
710
726
|
for (const s of summaries) {
|
|
711
727
|
log(
|
|
@@ -795,9 +811,11 @@ export function registerDeliveryCommand(program: Command): void {
|
|
|
795
811
|
.option('--check', 'Matrix gates only — validate the delivery map, write nothing')
|
|
796
812
|
.option('--approve [ids]', 'Approve proposed groups (all, or a comma-separated id list) + stamp fingerprints')
|
|
797
813
|
.option('--preview', 'Render the matrix despite review-required findings (DRAFT watermark)')
|
|
814
|
+
.option('--format <format>', 'Matrix output format: xlsx (default) | csv | both', 'xlsx')
|
|
798
815
|
.action(async (names: string[], options: {
|
|
799
816
|
skipPreflight?: boolean; continueOnMissing?: boolean; env?: string;
|
|
800
817
|
legacy?: boolean; full?: boolean; check?: boolean; approve?: boolean | string; preview?: boolean;
|
|
818
|
+
format?: string;
|
|
801
819
|
}) => {
|
|
802
820
|
try {
|
|
803
821
|
// Same effect as running with SUNGEN_ENV=<env> — result-file resolution,
|
|
@@ -805,11 +823,19 @@ export function registerDeliveryCommand(program: Command): void {
|
|
|
805
823
|
if (options.env) process.env.SUNGEN_ENV = options.env;
|
|
806
824
|
const cwd = process.cwd();
|
|
807
825
|
|
|
826
|
+
// Matrix output format — one artifact by default (xlsx); csv/both on request.
|
|
827
|
+
const format = (options.format ?? 'xlsx').toLowerCase();
|
|
828
|
+
if (!['xlsx', 'csv', 'both'].includes(format)) {
|
|
829
|
+
console.error(`${COLOR.red}Invalid --format "${options.format}"${COLOR.reset} — use xlsx | csv | both`);
|
|
830
|
+
process.exit(1);
|
|
831
|
+
}
|
|
832
|
+
|
|
808
833
|
// 1. Scope detection — each positional name expands into one target
|
|
809
834
|
// per `.feature` file inside that screen/flow. Passing a specific
|
|
810
835
|
// feature basename (e.g. `home-modal`) narrows to that one file.
|
|
811
836
|
let targets: DeliveryTarget[];
|
|
812
|
-
|
|
837
|
+
const explicitScope = !!(names && names.length > 0);
|
|
838
|
+
if (explicitScope) {
|
|
813
839
|
targets = names.flatMap((n) => resolveTargetsFromArg(cwd, n));
|
|
814
840
|
} else {
|
|
815
841
|
targets = listAllTargets(cwd);
|
|
@@ -822,8 +848,13 @@ export function registerDeliveryCommand(program: Command): void {
|
|
|
822
848
|
const labels = targets.map((t) => t.isFlow ? `flow/${t.featureBaseName}` : t.featureBaseName);
|
|
823
849
|
log(`${COLOR.bold}sungen delivery${COLOR.reset} — exporting ${targets.length} feature(s): ${labels.join(', ')}\n`);
|
|
824
850
|
|
|
825
|
-
// 2. Pre-flight
|
|
851
|
+
// 2. Pre-flight — strictness follows the SCOPE. Naming a unit means "I want
|
|
852
|
+
// this one" (fail loudly); a no-argument sweep means "export everything
|
|
853
|
+
// that is ready", so scaffolded units (added but not yet authored) are
|
|
854
|
+
// skipped with a note instead of aborting the whole run — otherwise one
|
|
855
|
+
// untouched scaffold in a work-in-progress project produces zero files.
|
|
826
856
|
let toExport: DeliveryTarget[];
|
|
857
|
+
const skipped: string[] = [];
|
|
827
858
|
if (options.skipPreflight) {
|
|
828
859
|
toExport = targets;
|
|
829
860
|
} else {
|
|
@@ -832,22 +863,22 @@ export function registerDeliveryCommand(program: Command): void {
|
|
|
832
863
|
|
|
833
864
|
const blockers = checks.filter(hasBlockingMissing);
|
|
834
865
|
if (blockers.length > 0) {
|
|
835
|
-
if (options.continueOnMissing) {
|
|
836
|
-
const passedScreens = new Set(
|
|
837
|
-
checks.filter((c) => !hasBlockingMissing(c)).map((c) => c.screen)
|
|
838
|
-
);
|
|
839
|
-
toExport = targets.filter((t) => {
|
|
840
|
-
const label = t.isFlow ? `flow/${t.featureBaseName}` : t.featureBaseName;
|
|
841
|
-
return passedScreens.has(label);
|
|
842
|
-
});
|
|
843
|
-
log(`${COLOR.yellow}Continuing with ${toExport.length} ready target(s).${COLOR.reset}\n`);
|
|
844
|
-
} else {
|
|
866
|
+
if (explicitScope && !options.continueOnMissing) {
|
|
845
867
|
console.error(
|
|
846
|
-
`${COLOR.red}Aborted:${COLOR.reset} ${blockers.length} target(s) have blocking issues.\n` +
|
|
868
|
+
`${COLOR.red}Aborted:${COLOR.reset} ${blockers.length} named target(s) have blocking issues.\n` +
|
|
847
869
|
`Run the suggested commands above, or use ${COLOR.cyan}--continue-on-missing${COLOR.reset} to skip them.`
|
|
848
870
|
);
|
|
849
871
|
process.exit(1);
|
|
850
872
|
}
|
|
873
|
+
const readyLabels = new Set(
|
|
874
|
+
checks.filter((c) => !hasBlockingMissing(c)).map((c) => c.screen)
|
|
875
|
+
);
|
|
876
|
+
toExport = targets.filter((t) => {
|
|
877
|
+
const label = t.isFlow ? `flow/${t.featureBaseName}` : t.featureBaseName;
|
|
878
|
+
return readyLabels.has(label);
|
|
879
|
+
});
|
|
880
|
+
for (const b of blockers) skipped.push(`${b.screen} (not authored yet — run /sungen:create-test)`);
|
|
881
|
+
log(`${COLOR.yellow}Skipping ${blockers.length} target(s) that have no test cases yet; continuing with ${toExport.length}.${COLOR.reset}\n`);
|
|
851
882
|
} else {
|
|
852
883
|
toExport = targets;
|
|
853
884
|
}
|
|
@@ -883,14 +914,25 @@ export function registerDeliveryCommand(program: Command): void {
|
|
|
883
914
|
}
|
|
884
915
|
|
|
885
916
|
const matrixSummaries: MatrixRunSummary[] = [];
|
|
886
|
-
|
|
917
|
+
const notExported: string[] = [];
|
|
887
918
|
for (const target of toExport) {
|
|
888
|
-
const s = await exportMatrixTarget(cwd, target, {
|
|
919
|
+
const s = await exportMatrixTarget(cwd, target, {
|
|
920
|
+
check: options.check, preview: options.preview, format: format as MatrixFormat,
|
|
921
|
+
});
|
|
889
922
|
if (s) matrixSummaries.push(s);
|
|
890
|
-
else
|
|
923
|
+
else notExported.push(target.isFlow ? `flow/${target.featureBaseName}` : target.featureBaseName);
|
|
891
924
|
}
|
|
892
925
|
if (matrixSummaries.length > 0 && !options.check) printMatrixSummaryTable(matrixSummaries);
|
|
893
|
-
|
|
926
|
+
printSkipSummary(skipped, notExported);
|
|
927
|
+
// A named target that produced nothing is a failure. A sweep is a failure
|
|
928
|
+
// only when it produced nothing at all — units still waiting for a map or
|
|
929
|
+
// an approval are the normal state of a live project. Preflight skips are
|
|
930
|
+
// not counted here: reaching this point in an explicit scope means
|
|
931
|
+
// --continue-on-missing was passed, which IS the opt-in to tolerate them.
|
|
932
|
+
const failed = explicitScope
|
|
933
|
+
? notExported.length > 0
|
|
934
|
+
: matrixSummaries.length === 0;
|
|
935
|
+
if (failed) process.exit(1);
|
|
894
936
|
return;
|
|
895
937
|
}
|
|
896
938
|
|
|
@@ -903,6 +945,10 @@ export function registerDeliveryCommand(program: Command): void {
|
|
|
903
945
|
|
|
904
946
|
// 4. Summary
|
|
905
947
|
printSummaryTable(summaries);
|
|
948
|
+
printSkipSummary(skipped, []);
|
|
949
|
+
// Same rule as the matrix path: every attempted target in an explicit scope
|
|
950
|
+
// must have produced a file; a sweep only fails when nothing was written.
|
|
951
|
+
if (explicitScope ? summaries.length < toExport.length : summaries.length === 0) process.exit(1);
|
|
906
952
|
} catch (err) {
|
|
907
953
|
console.error(`${COLOR.red}Fatal:${COLOR.reset} ${err instanceof Error ? err.message : err}`);
|
|
908
954
|
process.exit(1);
|
|
@@ -172,12 +172,28 @@ export function extractAuthRole(tags: string[]): string | null {
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
/**
|
|
175
|
-
*
|
|
175
|
+
* The leading viewpoint/requirement id of a scenario title.
|
|
176
|
+
*
|
|
177
|
+
* The `VP-` prefix is the scheme sungen suggests, NOT a requirement: real projects
|
|
178
|
+
* bring their own (`SEC-123`, `PER-345`, `MS-HP-001`, `VP0-001`), and the harness
|
|
179
|
+
* has always been format-tolerant about it. The exporters were not — a project
|
|
180
|
+
* using `SEC-123` got no id at all, which made every scenario "unaddressable" and
|
|
181
|
+
* blocked the whole delivery matrix at Gate A.
|
|
182
|
+
*
|
|
183
|
+
* Shape: uppercase segments joined by `-`, ending in a number (with an optional
|
|
184
|
+
* letter or sub-code suffix). Requiring the trailing number is what keeps an
|
|
185
|
+
* ordinary title word ("A11y check…") from being mistaken for an id.
|
|
186
|
+
*/
|
|
187
|
+
export const SCENARIO_ID = /^([A-Z][A-Z0-9]*(?:-[A-Z0-9]+)*-\d+(?:[a-zA-Z]|-[A-Z0-9]+)?)/;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Extract the id and the human-readable name from a scenario title:
|
|
176
191
|
* "VP-UI-001 Modal displays all required fields" →
|
|
177
192
|
* { vpId: "VP-UI-001", category1: "Modal displays all required fields" }
|
|
193
|
+
* "SEC-123 Login is protected" → { vpId: "SEC-123", category1: "Login is protected" }
|
|
178
194
|
*/
|
|
179
195
|
export function splitVpAndName(scenarioName: string): { vpId?: string; category1: string } {
|
|
180
|
-
const match = scenarioName.match(
|
|
196
|
+
const match = scenarioName.match(new RegExp(SCENARIO_ID.source + '\\s+(.+)$'));
|
|
181
197
|
if (match) {
|
|
182
198
|
return { vpId: match[1], category1: match[2] };
|
|
183
199
|
}
|
|
@@ -194,6 +210,9 @@ export function splitVpAndName(scenarioName: string): { vpId?: string; category1
|
|
|
194
210
|
*/
|
|
195
211
|
export function mapVpToCategory2(vpId: string | undefined, scenarioName?: string): string {
|
|
196
212
|
if (!vpId) return 'Function';
|
|
213
|
+
// Compare on the category segment so a project scheme without the `VP-` prefix
|
|
214
|
+
// (`SEC-123`) classifies the same as `VP-SEC-001`.
|
|
215
|
+
vpId = /^VP-/.test(vpId) ? vpId : `VP-${vpId}`;
|
|
197
216
|
if (vpId.startsWith('VP-SEC-')) {
|
|
198
217
|
if (scenarioName && /xss|injection/i.test(scenarioName)) return 'Function';
|
|
199
218
|
return 'Accessing';
|