@sun-asterisk/sungen 3.2.16-beta.3 → 3.2.16-beta.5

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 (45) hide show
  1. package/dist/cli/commands/delivery.d.ts.map +1 -1
  2. package/dist/cli/commands/delivery.js +16 -5
  3. package/dist/cli/commands/delivery.js.map +1 -1
  4. package/dist/exporters/matrix/build.d.ts +20 -2
  5. package/dist/exporters/matrix/build.d.ts.map +1 -1
  6. package/dist/exporters/matrix/build.js +78 -25
  7. package/dist/exporters/matrix/build.js.map +1 -1
  8. package/dist/exporters/matrix/export.d.ts +5 -4
  9. package/dist/exporters/matrix/export.d.ts.map +1 -1
  10. package/dist/exporters/matrix/export.js +15 -6
  11. package/dist/exporters/matrix/export.js.map +1 -1
  12. package/dist/exporters/matrix/gates.d.ts.map +1 -1
  13. package/dist/exporters/matrix/gates.js +36 -1
  14. package/dist/exporters/matrix/gates.js.map +1 -1
  15. package/dist/exporters/matrix/map-loader.d.ts.map +1 -1
  16. package/dist/exporters/matrix/map-loader.js +2 -0
  17. package/dist/exporters/matrix/map-loader.js.map +1 -1
  18. package/dist/exporters/matrix/render-csv.d.ts.map +1 -1
  19. package/dist/exporters/matrix/render-csv.js +12 -9
  20. package/dist/exporters/matrix/render-csv.js.map +1 -1
  21. package/dist/exporters/matrix/render-xlsx.d.ts +14 -0
  22. package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
  23. package/dist/exporters/matrix/render-xlsx.js +107 -48
  24. package/dist/exporters/matrix/render-xlsx.js.map +1 -1
  25. package/dist/exporters/matrix/types.d.ts +21 -3
  26. package/dist/exporters/matrix/types.d.ts.map +1 -1
  27. package/dist/exporters/matrix/types.js.map +1 -1
  28. package/dist/exporters/matrix/wording.d.ts +6 -0
  29. package/dist/exporters/matrix/wording.d.ts.map +1 -1
  30. package/dist/exporters/matrix/wording.js +22 -3
  31. package/dist/exporters/matrix/wording.js.map +1 -1
  32. package/dist/orchestrator/templates/ai-src/commands/delivery.md +23 -6
  33. package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +31 -8
  34. package/package.json +3 -3
  35. package/src/cli/commands/delivery.ts +19 -6
  36. package/src/exporters/matrix/build.ts +77 -22
  37. package/src/exporters/matrix/export.ts +21 -6
  38. package/src/exporters/matrix/gates.ts +36 -1
  39. package/src/exporters/matrix/map-loader.ts +2 -0
  40. package/src/exporters/matrix/render-csv.ts +13 -10
  41. package/src/exporters/matrix/render-xlsx.ts +104 -49
  42. package/src/exporters/matrix/types.ts +22 -4
  43. package/src/exporters/matrix/wording.ts +20 -3
  44. package/src/orchestrator/templates/ai-src/commands/delivery.md +23 -6
  45. package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +31 -8
@@ -29,6 +29,10 @@ export interface MapGroup {
29
29
  oracle: string;
30
30
  category: MapCategory;
31
31
  review: ReviewState;
32
+ /** Optional short digest of the coverage dimensions this item varies over
33
+ * (e.g. "required ×3 · format ×9 · full-width ×2"). Rendered on the parent row
34
+ * instead of concatenating every variant's title — the compact review view. */
35
+ dimensions?: string;
32
36
  /** Variant refs: `VP-ID` (all @cases rows when the scenario has a dataset) or `VP-ID#label`. */
33
37
  variants: string[];
34
38
  }
@@ -57,7 +61,9 @@ export interface DeliveryMap {
57
61
  /** Requirement-id → reviewed status override (partially_covered / not_applicable / …).
58
62
  * Without an override a requirement is `covered` when a variant traces to it, else `gap`. */
59
63
  requirements: Record<string, RequirementOverride>;
60
- /** VP-id → scenario fingerprint stamped at approval time (drift detector input). */
64
+ /** VP-id → scenario fingerprint stamped at approval time (drift detector input).
65
+ * The reserved key `__map__` holds the fingerprint of the map's own semantic
66
+ * content (groups + dispositions) so edited wording re-opens review (GAP-09). */
61
67
  fingerprints: Record<string, string>;
62
68
  }
63
69
 
@@ -116,7 +122,11 @@ export interface CoverageVariant {
116
122
  result?: PlaywrightResult;
117
123
  }
118
124
 
119
- export type ItemResult = 'passed' | 'failed' | 'blocked' | 'partial' | 'not_run';
125
+ export type ItemResult = 'passed' | 'failed' | 'blocked' | 'partial' | 'not_run' | 'na';
126
+
127
+ /** The exact word rendered in a variant's Result cell — the single vocabulary the
128
+ * parent's roll-up formula counts (COUNTIF is exact-match). */
129
+ export type VariantState = 'Passed' | 'Failed' | 'Blocked' | 'Pending' | 'N/A';
120
130
 
121
131
  /** One summarized row in the matrix — a group of variants sharing the signature. */
122
132
  export interface DeliveryItem {
@@ -124,6 +134,8 @@ export interface DeliveryItem {
124
134
  target: string;
125
135
  intent: string;
126
136
  oracle: string;
137
+ /** Short coverage-dimension digest from the map (compact parent view). */
138
+ dimensions?: string;
127
139
  category: MapCategory;
128
140
  review: ReviewState;
129
141
  /** Highest priority among the variants (per-variant priorities stay on the sub-rows). */
@@ -134,13 +146,19 @@ export interface DeliveryItem {
134
146
  layers: MatrixLayer[];
135
147
  /** Union of variant traces (exact per-variant traces stay on the variants). */
136
148
  traces: string[];
149
+ /** Preconditions shared by EVERY variant (the intersection) — written once here. */
137
150
  precondition: string[];
138
- /** Common trigger when all variants share one shape, else '(per variant)'. */
151
+ /** Leading trigger steps shared by every variant written once here; each
152
+ * variant renders only its remaining steps (`triggerDeltas`). */
139
153
  trigger: string[];
140
154
  variants: CoverageVariant[];
155
+ /** variant ref → its precondition lines that are not on the parent. */
156
+ preconditionDeltas: Record<string, string[]>;
157
+ /** variant ref → its trigger steps after the shared prefix. */
158
+ triggerDeltas: Record<string, string[]>;
141
159
  /** Derived roll-up — never entered independently (Gate F). */
142
160
  result: ItemResult;
143
- resultCounts: { passed: number; failed: number; blocked: number; notRun: number };
161
+ resultCounts: { passed: number; failed: number; blocked: number; notRun: number; na: number };
144
162
  }
145
163
 
146
164
  export interface MatrixDisposition {
@@ -19,10 +19,13 @@ function deRef(text: string): string {
19
19
  }
20
20
 
21
21
  function sentence(text: string): string {
22
- let s = text.trim().replace(/\s+/g, ' ');
22
+ // Trim the ENDS only — internal whitespace may be the test data itself
23
+ // (a padded email, a spaces-only value); collapsing it would silently
24
+ // change what the tester types (review GAP-04).
25
+ let s = text.trim();
23
26
  if (!s) return s;
24
27
  s = s.charAt(0).toUpperCase() + s.slice(1);
25
- if (!/[.!?…]$/.test(s)) s += '.';
28
+ if (!/[.!?…"]$/.test(s)) s += '.';
26
29
  return s;
27
30
  }
28
31
 
@@ -42,7 +45,7 @@ export function renderAction(raw: string): string {
42
45
  // click [X] <type>
43
46
  [/^clicks? \[([^\]]+)\]\s*(\w+)?$/i, (m) => `Click the ${m[1]}${m[2] ? ` ${m[2]}` : ''}`],
44
47
  // press <Key> at/in [X] field
45
- [/^press(?:es)? (.+?) (?:at|in|inside) \[([^\]]+)\](?: field)?$/i, (m) => `Press ${m[1]} in the ${m[2]} field`],
48
+ [/^press(?:es)? (.+?) (?:at|in|on|inside) \[([^\]]+)\](?: field)?$/i, (m) => `Press ${m[1]} in the ${m[2]} field`],
46
49
  [/^press(?:es)? (.+)$/i, (m) => `Press ${m[1]}`],
47
50
  // select V in/from [X] dropdown
48
51
  [/^selects? (.+?) (?:in|from) \[([^\]]+)\]\s*(\w+)?$/i, (m) => `Select ${m[1]} in the ${m[2]}${m[3] ? ` ${m[3]}` : ''}`],
@@ -103,9 +106,23 @@ export function renderPrecondition(raw: string): string {
103
106
  const s = raw.trim().replace(/^(User|The user)\s+/i, '');
104
107
  const m = s.match(/^is on \[([^\]]+)\] page(.*)$/i);
105
108
  if (m) return sentence(`The user is on the ${m[1]} page${m[2] ?? ''}`);
109
+ // "wait for [X] <type> (is) visible" is a STATE once established, not an action.
110
+ const w = s.match(/^waits? for \[([^\]]+)\]\s*(\w+)?(?: is)?(?: visible)?$/i);
111
+ if (w) return sentence(`The ${w[1]}${w[2] ? ` ${w[2]}` : ''} is visible`);
112
+ const auth = s.match(/^is (signed|logged) (in|out)(.*)$/i);
113
+ if (auth) return sentence(`The user is ${auth[1]} ${auth[2]}${auth[3] ?? ''}`);
106
114
  return sentence(deRef(`The user ${s.charAt(0).toLowerCase()}${s.slice(1)}`));
107
115
  }
108
116
 
117
+ /**
118
+ * A manual `Setup:` line is an INSTRUCTION the tester performs to establish the
119
+ * state — imperative reads correctly ("Seed the locked account."), while
120
+ * "The user seed …" is broken grammar (review GAP-08).
121
+ */
122
+ export function renderSetupInstruction(raw: string): string {
123
+ return renderAction(raw);
124
+ }
125
+
109
126
  // ---------------------------------------------------------------------------
110
127
  // Manual `# Tester verifies:` comment classification (structured, label-free)
111
128
  // ---------------------------------------------------------------------------
@@ -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; --env for locale-specific export)"
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]"
@@ -27,6 +27,8 @@ review → approve → official render.
27
27
 
28
28
  Parse from `$ARGUMENTS`:
29
29
  - **names** — zero or more screen/flow/api names. Empty → all targets.
30
+ - **`--format <xlsx|csv|both>`** — output format. **Default `xlsx` — one artifact only.** Pass
31
+ `--format csv` when the user wants the flat CSV (pipelines/diffing), `--format both` for both.
30
32
  - **`--env <locale>`** — sets `SUNGEN_ENV=<locale>` for the run (accept `--locale` as alias).
31
33
  - **`--legacy` / `--full`** — pass through to the CLI and skip the map flow entirely.
32
34
 
@@ -37,8 +39,8 @@ Parse from `$ARGUMENTS`:
37
39
  ```bash
38
40
  [ -x ./bin/sungen.js ] && ./bin/sungen.js delivery <names> || npx sungen delivery <names>
39
41
  ```
40
- (prepend `SUNGEN_ENV=<locale>` when `--env` was given; append `--legacy` when requested then skip
41
- to step 5.)
42
+ (prepend `SUNGEN_ENV=<locale>` when `--env` was given; append `--format <fmt>` when the user asked
43
+ for a non-default format; append `--legacy` when requested — then skip to step 5.)
42
44
 
43
45
  Three outcomes per target:
44
46
  - **Rendered** → done, go to step 5.
@@ -58,6 +60,7 @@ groups:
58
60
  target: login.email # ONE target: field/component dot-path, flow phrase, or METHOD /path
59
61
  intent: <one behavior/rule this item verifies>
60
62
  oracle: <the shared observable Pass/Fail statement>
63
+ dimensions: violated rule — required ×3 · format ×10 · full-width ×2 # see below
61
64
  category: normal | abnormal | security | nfr
62
65
  review: proposed # ALWAYS proposed — only QA approval flips it
63
66
  variants: [VP-VAL-001-B, VP-VAL-001-S] # VP-ids; bare id on a @cases scenario = all its rows
@@ -92,6 +95,17 @@ determined** (its oracle *family*, not its exact message).
92
95
  disposition). Data-setup blocks (`@manual:data-setup`) → `excluded`; SPEC-GAP placeholders →
93
96
  `blocked`.
94
97
 
98
+ **`dimensions:` — the compact coverage digest (required for items with >3 variants).**
99
+ This one short line is what the collapsed parent row shows instead of listing every variant, so a
100
+ reviewer sees *which dimensions* the item covers without expanding it. Name the dimension, then the
101
+ branches with counts:
102
+ - `violated rule — required ×3 · format ×10 · full-width ×2`
103
+ - `account state — wrong password · unregistered · locked · soft-deleted`
104
+ - `submission method — Login button · Enter in Password · Enter in Email`
105
+
106
+ Keep it ≤120 chars (Gate W warns). **YAML caveat:** a bare `: ` inside the value breaks the parse —
107
+ use ` — ` as the label separator (as above) or quote the whole string.
108
+
95
109
  **Wording rules for `intent`/`oracle` (customer-facing — Gate W lints these):**
96
110
  - Plain product language, present simple, ~10–20 words, one behavior:
97
111
  "A user can sign in with valid credentials and is redirected to the Jobs page."
@@ -151,6 +165,7 @@ counted in variants, never items**). Then `AskUserQuestion`:
151
165
  - **Open the workbook** — inspect `qa/deliverables/<unit>-testcases.xlsx` (Testcases sheet:
152
166
  collapse outline level 1 for the customer view; Coverage sheet: target × category grid + gaps).
153
167
  - **Run tests to refresh results** — `/sungen:run-test <unit>`, then re-run delivery.
168
+ - **Also export CSV** — `sungen delivery <unit> --format csv` (flat `item`/`variant` rows for pipelines).
154
169
  - **Export the legacy workbook too** — `sungen delivery <unit> --legacy`.
155
170
  - **Done**
156
171
 
@@ -168,7 +183,8 @@ counted in variants, never items**). Then `AskUserQuestion`:
168
183
  ## CLI reference
169
184
 
170
185
  ```
171
- sungen delivery [names...] # matrix (default; needs the map)
186
+ sungen delivery [names...] # matrix (default; needs the map) → XLSX only
187
+ --format <xlsx|csv|both> # output format; default xlsx (one artifact)
172
188
  --check # gates only — validate the map, write nothing
173
189
  --approve [DI-a,DI-b] # flip proposed→approved (+ stamp fingerprints); all groups when bare
174
190
  --preview # render despite review findings (DRAFT watermark)
@@ -176,5 +192,6 @@ sungen delivery [names...] # matrix (default; needs the map)
176
192
  --skip-preflight | --continue-on-missing | --env <env> # as before
177
193
  ```
178
194
 
179
- Outputs: `qa/deliverables/<unit>-testcases.xlsx` (Testcases + Coverage sheets) + `.csv`
180
- (flat, `Level` column `item|variant`). Legacy mode writes the classic files instead.
195
+ Outputs: `qa/deliverables/<unit>-testcases.xlsx` (Testcases + Coverage sheets) by default;
196
+ `--format csv` writes `<unit>-testcases.csv` instead (flat, `Level` column `item|variant`),
197
+ `--format both` writes both. Legacy mode writes the classic files instead.
@@ -36,7 +36,8 @@ oracle-shape/precondition mismatches are review-level, silenced once approved an
36
36
  D executability (precondition · condition+
37
37
  data · trigger · oracle all renderable; every `{{var}}` resolves; **no template token may survive
38
38
  into a rendered cell** — test-data cross-references are resolved for display) · E drift
39
- (fingerprint mismatch → back to review) · G review state (proposed groups block the official
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
40
41
  render; `--preview` renders a DRAFT watermark) · R requirement coverage (spec FR/TR/NFR ids with
41
42
  no trace and no `requirements:` status → warning) · W wording lint (map intent/oracle containing
42
43
  tokens, `[Selector]` refs, DSL phrasing, or generator labels → warning).
@@ -51,17 +52,39 @@ Precondition, `Action:` → Action, `Observable:` → Expected Result, `Oracle:`
51
52
  mid-flow assertions inline as `Verify: …`; only the final Then block is the Expected Result.
52
53
  Empty test values render as `(empty)`.
53
54
 
55
+ **Output format**: **XLSX only by default** (one artifact). `--format csv` writes the flat CSV
56
+ instead; `--format both` writes both.
57
+
54
58
  **Workbook**: `Testcases` sheet — parent rows + outline-level-1 variant sub-rows for **every**
55
59
  item (single-variant included: the sub-row carries the source VP-id, resolved data, and the
56
- result/evidence entry). Collapse outline for the customer view, expand to execute. Variant Result
57
- cells have a dropdown (Passed/Failed/Blocked/Pending/N/A) and the parent Result is a **live Excel
58
- formula** over its children (failed→blocked→pending→partial→passed, e.g. `2/3 Passed · 1 Failed`)
59
- a parent can never contradict its variants, even after manual edits. ID + Target columns are
60
- frozen; dates are ISO (`2026-08-04`). `Coverage` sheet — requirement coverage table (every FR/TR/
61
- NFR id with an explicit status), target × category grid with explicit `—` gaps, dispositions,
62
- manifest. CSV is flat with a `Level` column (`item`/`variant`) + a requirement-coverage appendix.
60
+ result/evidence entry). Sub-rows are **delta-only**: knowledge common to the whole item
61
+ (precondition, trigger) is written ONCE on the parent; a child repeats only what distinguishes it
62
+ (condition/data, precondition delta, trigger when it differs) plus its own precise oracle and
63
+ execution fields. The parent never carries placeholder text when triggers differ the cell is
64
+ simply empty and the sub-rows carry them.
65
+
66
+ Parent preconditions are the **intersection** of the variants' and the parent's Action holds the
67
+ **shared leading steps**; each variant renders only its remaining steps, numbered to continue the
68
+ parent's list — so a shared prefix is written once, not repeated on every child. The parent's
69
+ Coverage cell is the map's short `dimensions:` digest (or the variant refs when none is declared),
70
+ never a concatenation of variant titles.
71
+
72
+ Variant Result cells hold exactly one of `Passed`/`Failed`/`Blocked`/`Pending`/`N/A` (dropdown) —
73
+ that vocabulary is the contract: the parent Result and the summary band are **live Excel formulas**
74
+ counting those words, so a parent label is always a composed `n/m Passed …` string (`All N/A` when
75
+ every variant is N/A) and never a bare state word. **`N/A` leaves the denominator.** Evidence and
76
+ Defect ID are separate columns; ID + Target frozen; ISO dates; landscape print with repeated
77
+ header rows.
78
+
79
+ `Coverage` sheet — requirement coverage table (every FR/TR/NFR id with an explicit status),
80
+ target × category grid with explicit `—` gaps, dispositions, manifest. CSV mirrors the same model
81
+ flat with a `Level` column (`item`/`variant`) + a requirement-coverage appendix.
63
82
  `delivery_item_count` ≠ progress — variants are the execution metric.
64
83
 
84
+ **Data fidelity**: invisible test data is made visible, never normalized — `''` → `(empty)`,
85
+ whitespace-only → `(5 spaces)`, padded → `" value "` quoted verbatim. A trim/collapse here would
86
+ silently break the whitespace tests it describes.
87
+
65
88
  **Authoring guidance the matrix rewards** (create-test side): payload/provider matrices (SQLi
66
89
  payload lists, OAuth provider sets) belong in `@cases` datasets so each case is an atomic,
67
90
  independently-reportable variant; keep dataset `case:` labels short and stable (`CHK-EMAIL-I1`),