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

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 (47) hide show
  1. package/dist/cli/commands/delivery.d.ts.map +1 -1
  2. package/dist/cli/commands/delivery.js +1 -0
  3. package/dist/cli/commands/delivery.js.map +1 -1
  4. package/dist/exporters/matrix/build.d.ts +8 -1
  5. package/dist/exporters/matrix/build.d.ts.map +1 -1
  6. package/dist/exporters/matrix/build.js +166 -24
  7. package/dist/exporters/matrix/build.js.map +1 -1
  8. package/dist/exporters/matrix/export.d.ts +2 -0
  9. package/dist/exporters/matrix/export.d.ts.map +1 -1
  10. package/dist/exporters/matrix/export.js +7 -0
  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 +40 -2
  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 +18 -0
  17. package/dist/exporters/matrix/map-loader.js.map +1 -1
  18. package/dist/exporters/matrix/render-csv.d.ts +3 -2
  19. package/dist/exporters/matrix/render-csv.d.ts.map +1 -1
  20. package/dist/exporters/matrix/render-csv.js +49 -29
  21. package/dist/exporters/matrix/render-csv.js.map +1 -1
  22. package/dist/exporters/matrix/render-xlsx.d.ts +18 -8
  23. package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
  24. package/dist/exporters/matrix/render-xlsx.js +125 -52
  25. package/dist/exporters/matrix/render-xlsx.js.map +1 -1
  26. package/dist/exporters/matrix/types.d.ts +29 -4
  27. package/dist/exporters/matrix/types.d.ts.map +1 -1
  28. package/dist/exporters/matrix/types.js +2 -2
  29. package/dist/exporters/matrix/types.js.map +1 -1
  30. package/dist/exporters/matrix/wording.d.ts +45 -0
  31. package/dist/exporters/matrix/wording.d.ts.map +1 -0
  32. package/dist/exporters/matrix/wording.js +150 -0
  33. package/dist/exporters/matrix/wording.js.map +1 -0
  34. package/dist/orchestrator/templates/ai-src/commands/delivery.md +45 -9
  35. package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +36 -11
  36. package/package.json +3 -3
  37. package/src/cli/commands/delivery.ts +1 -0
  38. package/src/exporters/matrix/build.ts +168 -24
  39. package/src/exporters/matrix/export.ts +10 -0
  40. package/src/exporters/matrix/gates.ts +44 -2
  41. package/src/exporters/matrix/map-loader.ts +20 -1
  42. package/src/exporters/matrix/render-csv.ts +50 -30
  43. package/src/exporters/matrix/render-xlsx.ts +131 -55
  44. package/src/exporters/matrix/types.ts +33 -4
  45. package/src/exporters/matrix/wording.ts +157 -0
  46. package/src/orchestrator/templates/ai-src/commands/delivery.md +45 -9
  47. package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +36 -11
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Wording normalization — deterministic rendering step AFTER semantic
3
+ * normalization (review feedback §10): turn sungen DSL steps into controlled
4
+ * manual-test English without changing the target, condition, trigger,
5
+ * precondition, oracle, or trace.
6
+ *
7
+ * - Actions render in the imperative: "User fill [Email] field with X"
8
+ * → "Enter X in the Email field."
9
+ * - Expected results render as observable assertions (never tester actions):
10
+ * "User see [Jobs] page" → "The Jobs page is displayed."
11
+ * - Manual `# Tester verifies:` labels (Setup:/Action:/Observable:/Oracle:)
12
+ * become structured fields instead of prose: Setup → precondition,
13
+ * Action → action, Observable → expected, Oracle → verification method.
14
+ */
15
+
16
+ // `[Email] field` → `Email field` (the visible label + its element type).
17
+ function deRef(text: string): string {
18
+ return text.replace(/\[([^\]]+)\]/g, '$1');
19
+ }
20
+
21
+ function sentence(text: string): string {
22
+ let s = text.trim().replace(/\s+/g, ' ');
23
+ if (!s) return s;
24
+ s = s.charAt(0).toUpperCase() + s.slice(1);
25
+ if (!/[.!?…]$/.test(s)) s += '.';
26
+ return s;
27
+ }
28
+
29
+ /**
30
+ * Render one action step in the imperative. Pattern table covers the common
31
+ * sungen step verbs; anything unmatched falls back to actor-stripped text —
32
+ * still readable, never a raw `User fill`.
33
+ */
34
+ export function renderAction(raw: string): string {
35
+ let s = raw.trim().replace(/^(User|The user)\s+/i, '');
36
+
37
+ const rules: Array<[RegExp, (m: RegExpMatchArray) => string]> = [
38
+ // fill [X] field with V
39
+ [/^fills? \[([^\]]+)\][a-z ]* with (.+)$/i, (m) => `Enter ${m[2]} in the ${m[1]} field`],
40
+ // clear [X] field
41
+ [/^clears? \[([^\]]+)\](.*)$/i, (m) => `Clear the ${m[1]}${m[2] || ' field'}`],
42
+ // click [X] <type>
43
+ [/^clicks? \[([^\]]+)\]\s*(\w+)?$/i, (m) => `Click the ${m[1]}${m[2] ? ` ${m[2]}` : ''}`],
44
+ // press <Key> at/in [X] field
45
+ [/^press(?:es)? (.+?) (?:at|in|inside) \[([^\]]+)\](?: field)?$/i, (m) => `Press ${m[1]} in the ${m[2]} field`],
46
+ [/^press(?:es)? (.+)$/i, (m) => `Press ${m[1]}`],
47
+ // select V in/from [X] dropdown
48
+ [/^selects? (.+?) (?:in|from) \[([^\]]+)\]\s*(\w+)?$/i, (m) => `Select ${m[1]} in the ${m[2]}${m[3] ? ` ${m[3]}` : ''}`],
49
+ // check/uncheck [X] checkbox
50
+ [/^(un)?checks? \[([^\]]+)\]\s*(\w+)?$/i, (m) => `${m[1] ? 'Uncheck' : 'Check'} the ${m[2]}${m[3] ? ` ${m[3]}` : ''}`],
51
+ // hover [X]
52
+ [/^hovers? (?:over )?\[([^\]]+)\]\s*(\w+)?$/i, (m) => `Hover over the ${m[1]}${m[2] ? ` ${m[2]}` : ''}`],
53
+ // upload V to [X]
54
+ [/^uploads? (.+?) (?:to|into) \[([^\]]+)\]\s*(\w+)?$/i, (m) => `Upload ${m[1]} to the ${m[2]}${m[3] ? ` ${m[3]}` : ''}`],
55
+ // is on [X] page (as an action = navigate)
56
+ [/^is on \[([^\]]+)\] page(.*)$/i, (m) => `Open the ${m[1]} page${m[2] ?? ''}`],
57
+ // wait for [X] <type> (is )?visible
58
+ [/^waits? for \[([^\]]+)\]\s*(\w+)?(?: is)?(?: visible)?$/i, (m) => `Wait until the ${m[1]}${m[2] ? ` ${m[2]}` : ''} is visible`],
59
+ // scroll to [X]
60
+ [/^scrolls? (?:to|into) \[([^\]]+)\]\s*(\w+)?$/i, (m) => `Scroll to the ${m[1]}${m[2] ? ` ${m[2]}` : ''}`],
61
+ ];
62
+
63
+ for (const [re, out] of rules) {
64
+ const m = s.match(re);
65
+ if (m) return sentence(deRef(out(m)));
66
+ }
67
+ return sentence(deRef(s));
68
+ }
69
+
70
+ /**
71
+ * Render one expected step as an observable assertion (no tester action, no
72
+ * `should`, no DSL `User see`).
73
+ */
74
+ export function renderExpected(raw: string): string {
75
+ let s = raw.trim().replace(/^(User|The user)\s+/i, '');
76
+
77
+ const rules: Array<[RegExp, (m: RegExpMatchArray) => string]> = [
78
+ // see [X] page
79
+ [/^sees? \[([^\]]+)\] page$/i, (m) => `The ${m[1]} page is displayed`],
80
+ // see [X] <type> with V
81
+ [/^sees? \[([^\]]+)\]\s*(\w+)? with (.+)$/i, (m) => `The ${m[1]}${m[2] ? ` ${m[2]}` : ''} shows ${m[3]}`],
82
+ // see [X] <type> contains V
83
+ [/^sees? \[([^\]]+)\]\s*(\w+)? contains (.+)$/i, (m) => `The ${m[1]}${m[2] ? ` ${m[2]}` : ''} contains ${m[3]}`],
84
+ // see [X] <type> has text V
85
+ [/^sees? \[([^\]]+)\]\s*(\w+)? has text (.+)$/i, (m) => `The ${m[1]}${m[2] ? ` ${m[2]}` : ''} shows ${m[3]}`],
86
+ // see [X] <type> is hidden / is disabled / is enabled / …
87
+ [/^sees? \[([^\]]+)\]\s*(\w+)? is (.+)$/i, (m) => `The ${m[1]}${m[2] ? ` ${m[2]}` : ''} is ${m[3]}`],
88
+ // not see [X] <type>
89
+ [/^(?:do(?:es)? )?not sees? \[([^\]]+)\]\s*(\w+)?$/i, (m) => `The ${m[1]}${m[2] ? ` ${m[2]}` : ''} is not displayed`],
90
+ // see [X] <type>
91
+ [/^sees? \[([^\]]+)\]\s*(\w+)?$/i, (m) => `The ${m[1]}${m[2] ? ` ${m[2]}` : ''} is visible`],
92
+ ];
93
+
94
+ for (const [re, out] of rules) {
95
+ const m = s.match(re);
96
+ if (m) return sentence(deRef(out(m)));
97
+ }
98
+ return sentence(deRef(s));
99
+ }
100
+
101
+ /** Precondition wording: a state, not an action ("The user is signed out."). */
102
+ export function renderPrecondition(raw: string): string {
103
+ const s = raw.trim().replace(/^(User|The user)\s+/i, '');
104
+ const m = s.match(/^is on \[([^\]]+)\] page(.*)$/i);
105
+ if (m) return sentence(`The user is on the ${m[1]} page${m[2] ?? ''}`);
106
+ return sentence(deRef(`The user ${s.charAt(0).toLowerCase()}${s.slice(1)}`));
107
+ }
108
+
109
+ // ---------------------------------------------------------------------------
110
+ // Manual `# Tester verifies:` comment classification (structured, label-free)
111
+ // ---------------------------------------------------------------------------
112
+
113
+ export interface ManualProcedure {
114
+ /** Setup/Precondition/Arrange lines — the state to establish first. */
115
+ preconditions: string[];
116
+ /** Action/unlabelled lines — the imperative procedure. */
117
+ actions: string[];
118
+ /** Observable/Expect/Result/Assert lines — the observable outcome. */
119
+ expected: string[];
120
+ /** Oracle/Verify lines — HOW to check (tools, panes, queries). */
121
+ verification: string[];
122
+ }
123
+
124
+ /**
125
+ * Split a manual scenario's numbered comment lines into the four structured
126
+ * fields. Labels are consumed (structured), never left inside the prose —
127
+ * review feedback §10.2(3). Continuation lines append to the previous item;
128
+ * pre-amble (rationale/header/dividers) is skipped.
129
+ */
130
+ export function classifyManualComments(comments: string[]): ManualProcedure {
131
+ const out: ManualProcedure = { preconditions: [], actions: [], expected: [], verification: [] };
132
+ let last: { list: string[]; idx: number } | null = null;
133
+
134
+ const bucketOf = (label: string): keyof ManualProcedure => {
135
+ if (/setup|precondition|arrange|given/i.test(label)) return 'preconditions';
136
+ if (/oracle|verify|verification|how to check/i.test(label)) return 'verification';
137
+ if (/observ|expect|result|then|assert/i.test(label)) return 'expected';
138
+ return 'actions';
139
+ };
140
+
141
+ for (const raw of comments) {
142
+ const line = raw.trim();
143
+ if (!line) continue;
144
+ if (/^[-=*_]{2,}/.test(line)) { last = null; continue; }
145
+ const m = line.match(/^\d+[.)]\s*(?:([A-Za-z][A-Za-z /]*?):\s*)?(.+)$/);
146
+ if (m) {
147
+ const label = (m[1] || '').trim();
148
+ const text = m[2].trim();
149
+ const list = out[bucketOf(label)];
150
+ list.push(text);
151
+ last = { list, idx: list.length - 1 };
152
+ } else if (last) {
153
+ last.list[last.idx] += ' ' + line;
154
+ }
155
+ }
156
+ return out;
157
+ }
@@ -66,19 +66,55 @@ dispositions: # scenarios intentionally NOT delivered as te
66
66
  # as: excluded | blocked | covered_elsewhere | accepted_risk
67
67
  ```
68
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.
69
+ **Grouping rules (the aggregation signature) — group COMPACTLY.** The matrix exists to be
70
+ substantially shorter than the scenario list, so a reviewer can see missing viewpoints at a
71
+ glance. Merge whenever the cases share ALL of: target · test intent/business rule ·
72
+ precondition/condition · trigger or procedure shape · **the way the expected result is
73
+ determined** (its oracle *family*, not its exact message).
74
+
75
+ - **Oracle family = the determination method, parameterized.** All validation branches of ONE
76
+ field belong to ONE item — required, format, length, character-class are *expected branches*
77
+ (parameters) of "the field shows the validation message defined for the violated rule", shown
78
+ per variant, never separate items.
79
+ - MAY vary inside one item (coverage dimensions, visible on the sub-rows): data values, boundary
80
+ points, **account states** (a seeded/locked/deleted account next to a wrong-password case),
81
+ provider/browser/locale, `@cases` rows, a different trigger with the same oracle (blur vs
82
+ submit), **execution mode** (auto + manual mix — the parent shows `Auto n · Manual m`), and
83
+ **priority** (the item takes the highest; per-variant priorities stay visible).
84
+ - MUST split: different target, different intent/business rule, different way of determining the
85
+ expected result (a field-error family ≠ a session-established family), different test layer
86
+ (`@api`/`@query`), materially different precondition, or a different procedure shape —
87
+ sequence-sensitive flows (re-Given/When after a Then) stay solo. **Different risk classes never
88
+ merge**: XSS and SQL injection are separate items (different risk and determination), even on
89
+ the same field.
90
+ - When unsure, keep items separate — the gates and QA decide, never guess-merge.
78
91
  - Every scenario must land in exactly one group **or** one disposition (Gate B enforces 100%
79
92
  disposition). Data-setup blocks (`@manual:data-setup`) → `excluded`; SPEC-GAP placeholders →
80
93
  `blocked`.
81
94
 
95
+ **Wording rules for `intent`/`oracle` (customer-facing — Gate W lints these):**
96
+ - Plain product language, present simple, ~10–20 words, one behavior:
97
+ "A user can sign in with valid credentials and is redirected to the Jobs page."
98
+ - Oracle = the observable outcome as a definite assertion ("The Jobs page is displayed and the
99
+ Logout link is visible.") — no `should`, no tester actions.
100
+ - NEVER: `{{tokens}}`, `[Selector]` references, DSL phrasing (`User fill/click/see`), generator
101
+ labels (`Setup:`/`Observable:`/`Oracle:`), or vague verbs (`handles`, `surfaces`) when a precise
102
+ behavior exists. Use the visible UI label (the Login button, the Email field).
103
+ - **Preserve the source meaning exactly** — never strengthen, weaken, or reinterpret an oracle
104
+ (a security assertion especially: if the source says "the password appears ONLY in the HTTPS
105
+ POST body", do not write "no plaintext password on the network").
106
+
107
+ **Requirement coverage (`requirements:` section, optional):** `sungen delivery` scans
108
+ `requirements/spec.md` for FR-/TR-/NFR- ids; ids traced by `@spec:` tags are `covered`, the rest
109
+ are `gap` (Gate R warning). Record the reviewed status for genuine non-gaps:
110
+
111
+ ```yaml
112
+ requirements:
113
+ TR-007: { status: planned, note: Performance needs Lighthouse-style tooling }
114
+ TR-004: { status: partially_covered, note: client-side covered by VP-SEC-003; hashing needs DB verify }
115
+ # status: covered | partially_covered | covered_elsewhere | planned | gap | not_applicable
116
+ ```
117
+
82
118
  Then validate and fix any ERROR findings:
83
119
 
84
120
  ```bash
@@ -29,19 +29,44 @@ 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 (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`).
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
+ (fingerprint mismatch back to review) · G review state (proposed groups block the official
40
+ render; `--preview` renders a DRAFT watermark) · R requirement coverage (spec FR/TR/NFR ids with
41
+ no trace and no `requirements:` status warning) · W wording lint (map intent/oracle containing
42
+ tokens, `[Selector]` refs, DSL phrasing, or generator labels warning).
43
+
44
+ **Wording normalization (deterministic, after semantic normalization):** DSL steps render as
45
+ controlled manual-test English without changing meaning — actions in the imperative (`User fill
46
+ [Email] field with X` → `Enter X in the Email field.`), expected results as observable assertions
47
+ (`User see [Jobs] page` → `The Jobs page is displayed.`), preconditions as states (`The user is
48
+ signed out.`). Manual `# Tester verifies:` labels become structured fields: `Setup:` →
49
+ Precondition, `Action:` → Action, `Observable:` → Expected Result, `Oracle:` → a separate
50
+ `Verification method:` line. Sequence-sensitive flows keep event order: actions numbered with
51
+ mid-flow assertions inline as `Verify: …`; only the final Then block is the Expected Result.
52
+ Empty test values render as `(empty)`.
53
+
54
+ **Workbook**: `Testcases` sheet — parent rows + outline-level-1 variant sub-rows for **every**
55
+ 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.
43
63
  `delivery_item_count` ≠ progress — variants are the execution metric.
44
64
 
65
+ **Authoring guidance the matrix rewards** (create-test side): payload/provider matrices (SQLi
66
+ payload lists, OAuth provider sets) belong in `@cases` datasets so each case is an atomic,
67
+ independently-reportable variant; keep dataset `case:` labels short and stable (`CHK-EMAIL-I1`),
68
+ with descriptions in other columns — the label is part of the variant's identity.
69
+
45
70
  ---
46
71
 
47
72
  ## Legacy mode (--legacy / --full)