@sun-asterisk/sungen 3.2.16-beta.2 → 3.2.16-beta.4
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/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +16 -5
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/exporters/matrix/build.d.ts +6 -0
- package/dist/exporters/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +32 -8
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/export.d.ts +5 -4
- package/dist/exporters/matrix/export.d.ts.map +1 -1
- package/dist/exporters/matrix/export.js +15 -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 +23 -2
- package/dist/exporters/matrix/gates.js.map +1 -1
- package/dist/exporters/matrix/render-csv.d.ts.map +1 -1
- package/dist/exporters/matrix/render-csv.js +11 -6
- package/dist/exporters/matrix/render-csv.js.map +1 -1
- package/dist/exporters/matrix/render-xlsx.d.ts +2 -0
- package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
- package/dist/exporters/matrix/render-xlsx.js +73 -25
- package/dist/exporters/matrix/render-xlsx.js.map +1 -1
- package/dist/exporters/matrix/types.d.ts +9 -4
- 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 +6 -0
- package/dist/exporters/matrix/wording.d.ts.map +1 -1
- package/dist/exporters/matrix/wording.js +22 -3
- package/dist/exporters/matrix/wording.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/delivery.md +33 -18
- package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +30 -11
- package/package.json +3 -3
- package/src/cli/commands/delivery.ts +19 -6
- package/src/exporters/matrix/build.ts +31 -9
- package/src/exporters/matrix/export.ts +21 -6
- package/src/exporters/matrix/gates.ts +23 -2
- package/src/exporters/matrix/render-csv.ts +12 -7
- package/src/exporters/matrix/render-xlsx.ts +73 -25
- package/src/exporters/matrix/types.ts +9 -4
- package/src/exporters/matrix/wording.ts +20 -3
- package/src/orchestrator/templates/ai-src/commands/delivery.md +33 -18
- package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +30 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wording.js","sourceRoot":"","sources":["../../../src/exporters/matrix/wording.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;
|
|
1
|
+
{"version":3,"file":"wording.js","sourceRoot":"","sources":["../../../src/exporters/matrix/wording.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;AAuBH,oCAkCC;AAMD,wCAyBC;AAGD,gDAUC;AAOD,wDAEC;AAuBD,wDA2BC;AA9JD,0EAA0E;AAC1E,SAAS,KAAK,CAAC,IAAY;IACzB,OAAO,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;AAC7C,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY;IAC5B,uEAAuE;IACvE,sEAAsE;IACtE,gDAAgD;IAChD,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IACpB,IAAI,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IACjB,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,CAAC,IAAI,GAAG,CAAC;IAClC,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;GAIG;AACH,SAAgB,YAAY,CAAC,GAAW;IACtC,IAAI,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC,CAAC;IAEvD,MAAM,KAAK,GAAqD;QAC9D,wBAAwB;QACxB,CAAC,yCAAyC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QACxF,kBAAkB;QAClB,CAAC,6BAA6B,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,QAAQ,EAAE,CAAC;QAC9E,mBAAmB;QACnB,CAAC,kCAAkC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QACzF,8BAA8B;QAC9B,CAAC,mEAAmE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QAClH,CAAC,sBAAsB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAChD,gCAAgC;QAChC,CAAC,qDAAqD,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QACxH,6BAA6B;QAC7B,CAAC,uCAAuC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QACtH,YAAY;QACZ,CAAC,4CAA4C,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QACxG,kBAAkB;QAClB,CAAC,qDAAqD,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QACxH,2CAA2C;QAC3C,CAAC,gCAAgC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAC/E,oCAAoC;QACpC,CAAC,0DAA0D,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC;QACjI,gBAAgB;QAChB,CAAC,+CAA+C,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;KAC3G,CAAC;IAEF,KAAK,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,KAAK,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACtB,IAAI,CAAC;YAAE,OAAO,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5B,CAAC;AAED;;;GAGG;AACH,SAAgB,cAAc,CAAC,GAAW;IACxC,IAAI,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC,CAAC;IAEvD,MAAM,KAAK,GAAqD;QAC9D,eAAe;QACf,CAAC,4BAA4B,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,oBAAoB,CAAC;QACtE,wBAAwB;QACxB,CAAC,0CAA0C,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACzG,4BAA4B;QAC5B,CAAC,8CAA8C,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAChH,4BAA4B;QAC5B,CAAC,8CAA8C,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7G,0DAA0D;QAC1D,CAAC,wCAAwC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACpG,qBAAqB;QACrB,CAAC,mDAAmD,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,mBAAmB,CAAC;QACrH,iBAAiB;QACjB,CAAC,gCAAgC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC;KAC7F,CAAC;IAEF,KAAK,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,KAAK,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACtB,IAAI,CAAC;YAAE,OAAO,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5B,CAAC;AAED,gFAAgF;AAChF,SAAgB,kBAAkB,CAAC,GAAW;IAC5C,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC,CAAC;IACzD,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,IAAI,CAAC;QAAE,OAAO,QAAQ,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACvE,iFAAiF;IACjF,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,0DAA0D,CAAC,CAAC;IAC9E,IAAI,CAAC;QAAE,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC;IAC1E,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;IAC3D,IAAI,IAAI;QAAE,OAAO,QAAQ,CAAC,eAAe,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC/E,OAAO,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED;;;;GAIG;AACH,SAAgB,sBAAsB,CAAC,GAAW;IAChD,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC;AAC3B,CAAC;AAiBD;;;;;GAKG;AACH,SAAgB,sBAAsB,CAAC,QAAkB;IACvD,MAAM,GAAG,GAAoB,EAAE,aAAa,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC;IAChG,IAAI,IAAI,GAA2C,IAAI,CAAC;IAExD,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAyB,EAAE;QACxD,IAAI,mCAAmC,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,eAAe,CAAC;QAC5E,IAAI,0CAA0C,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,cAAc,CAAC;QAClF,IAAI,mCAAmC,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,UAAU,CAAC;QACvE,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC;IAEF,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACxB,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAAC,IAAI,GAAG,IAAI,CAAC;YAAC,SAAS;QAAC,CAAC;QACxD,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACxE,IAAI,CAAC,EAAE,CAAC;YACN,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YAClC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACzB,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;YAClC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAChB,IAAI,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxC,CAAC;aAAM,IAAI,IAAI,EAAE,CAAC;YAChB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,IAAI,CAAC;QACpC,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -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]"
|
|
@@ -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 `--
|
|
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.
|
|
@@ -66,18 +68,28 @@ dispositions: # scenarios intentionally NOT delivered as te
|
|
|
66
68
|
# as: excluded | blocked | covered_elsewhere | accepted_risk
|
|
67
69
|
```
|
|
68
70
|
|
|
69
|
-
**Grouping rules (the aggregation signature)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
71
|
+
**Grouping rules (the aggregation signature) — group COMPACTLY.** The matrix exists to be
|
|
72
|
+
substantially shorter than the scenario list, so a reviewer can see missing viewpoints at a
|
|
73
|
+
glance. Merge whenever the cases share ALL of: target · test intent/business rule ·
|
|
74
|
+
precondition/condition · trigger or procedure shape · **the way the expected result is
|
|
75
|
+
determined** (its oracle *family*, not its exact message).
|
|
76
|
+
|
|
77
|
+
- **Oracle family = the determination method, parameterized.** All validation branches of ONE
|
|
78
|
+
field belong to ONE item — required, format, length, character-class are *expected branches*
|
|
79
|
+
(parameters) of "the field shows the validation message defined for the violated rule", shown
|
|
80
|
+
per variant, never separate items.
|
|
81
|
+
- MAY vary inside one item (coverage dimensions, visible on the sub-rows): data values, boundary
|
|
82
|
+
points, **account states** (a seeded/locked/deleted account next to a wrong-password case),
|
|
83
|
+
provider/browser/locale, `@cases` rows, a different trigger with the same oracle (blur vs
|
|
84
|
+
submit), **execution mode** (auto + manual mix — the parent shows `Auto n · Manual m`), and
|
|
85
|
+
**priority** (the item takes the highest; per-variant priorities stay visible).
|
|
86
|
+
- MUST split: different target, different intent/business rule, different way of determining the
|
|
87
|
+
expected result (a field-error family ≠ a session-established family), different test layer
|
|
88
|
+
(`@api`/`@query`), materially different precondition, or a different procedure shape —
|
|
89
|
+
sequence-sensitive flows (re-Given/When after a Then) stay solo. **Different risk classes never
|
|
90
|
+
merge**: XSS and SQL injection are separate items (different risk and determination), even on
|
|
91
|
+
the same field.
|
|
92
|
+
- When unsure, keep items separate — the gates and QA decide, never guess-merge.
|
|
81
93
|
- Every scenario must land in exactly one group **or** one disposition (Gate B enforces 100%
|
|
82
94
|
disposition). Data-setup blocks (`@manual:data-setup`) → `excluded`; SPEC-GAP placeholders →
|
|
83
95
|
`blocked`.
|
|
@@ -141,6 +153,7 @@ counted in variants, never items**). Then `AskUserQuestion`:
|
|
|
141
153
|
- **Open the workbook** — inspect `qa/deliverables/<unit>-testcases.xlsx` (Testcases sheet:
|
|
142
154
|
collapse outline level 1 for the customer view; Coverage sheet: target × category grid + gaps).
|
|
143
155
|
- **Run tests to refresh results** — `/sungen:run-test <unit>`, then re-run delivery.
|
|
156
|
+
- **Also export CSV** — `sungen delivery <unit> --format csv` (flat `item`/`variant` rows for pipelines).
|
|
144
157
|
- **Export the legacy workbook too** — `sungen delivery <unit> --legacy`.
|
|
145
158
|
- **Done**
|
|
146
159
|
|
|
@@ -158,7 +171,8 @@ counted in variants, never items**). Then `AskUserQuestion`:
|
|
|
158
171
|
## CLI reference
|
|
159
172
|
|
|
160
173
|
```
|
|
161
|
-
sungen delivery [names...] # matrix (default; needs the map)
|
|
174
|
+
sungen delivery [names...] # matrix (default; needs the map) → XLSX only
|
|
175
|
+
--format <xlsx|csv|both> # output format; default xlsx (one artifact)
|
|
162
176
|
--check # gates only — validate the map, write nothing
|
|
163
177
|
--approve [DI-a,DI-b] # flip proposed→approved (+ stamp fingerprints); all groups when bare
|
|
164
178
|
--preview # render despite review findings (DRAFT watermark)
|
|
@@ -166,5 +180,6 @@ sungen delivery [names...] # matrix (default; needs the map)
|
|
|
166
180
|
--skip-preflight | --continue-on-missing | --env <env> # as before
|
|
167
181
|
```
|
|
168
182
|
|
|
169
|
-
Outputs: `qa/deliverables/<unit>-testcases.xlsx` (Testcases + Coverage sheets)
|
|
170
|
-
(flat, `Level` column `item|variant`)
|
|
183
|
+
Outputs: `qa/deliverables/<unit>-testcases.xlsx` (Testcases + Coverage sheets) by default;
|
|
184
|
+
`--format csv` writes `<unit>-testcases.csv` instead (flat, `Level` column `item|variant`),
|
|
185
|
+
`--format both` writes both. Legacy mode writes the classic files instead.
|
|
@@ -29,12 +29,15 @@ 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
|
-
|
|
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+
|
|
35
37
|
data · trigger · oracle all renderable; every `{{var}}` resolves; **no template token may survive
|
|
36
38
|
into a rendered cell** — test-data cross-references are resolved for display) · E drift
|
|
37
|
-
(fingerprint mismatch → back to review
|
|
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
|
|
38
41
|
render; `--preview` renders a DRAFT watermark) · R requirement coverage (spec FR/TR/NFR ids with
|
|
39
42
|
no trace and no `requirements:` status → warning) · W wording lint (map intent/oracle containing
|
|
40
43
|
tokens, `[Selector]` refs, DSL phrasing, or generator labels → warning).
|
|
@@ -49,17 +52,33 @@ Precondition, `Action:` → Action, `Observable:` → Expected Result, `Oracle:`
|
|
|
49
52
|
mid-flow assertions inline as `Verify: …`; only the final Then block is the Expected Result.
|
|
50
53
|
Empty test values render as `(empty)`.
|
|
51
54
|
|
|
55
|
+
**Output format**: **XLSX only by default** (one artifact). `--format csv` writes the flat CSV
|
|
56
|
+
instead; `--format both` writes both.
|
|
57
|
+
|
|
52
58
|
**Workbook**: `Testcases` sheet — parent rows + outline-level-1 variant sub-rows for **every**
|
|
53
59
|
item (single-variant included: the sub-row carries the source VP-id, resolved data, and the
|
|
54
|
-
result/evidence entry).
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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' — variant-specific setup stays on
|
|
67
|
+
the variant, so no variant inherits a wrong start state. Variant Result cells have a dropdown
|
|
68
|
+
(Passed/Failed/Blocked/Pending/N/A) and the parent Result is a **live Excel formula** over its
|
|
69
|
+
children (failed→blocked→pending→partial→passed; **`N/A` leaves the denominator**, all-`N/A` →
|
|
70
|
+
`N/A`). The summary band counts results with live formulas too. Evidence and Defect ID are
|
|
71
|
+
separate columns; ID + Target frozen; ISO dates; landscape print with repeated header rows.
|
|
72
|
+
|
|
73
|
+
`Coverage` sheet — requirement coverage table (every FR/TR/NFR id with an explicit status),
|
|
74
|
+
target × category grid with explicit `—` gaps, dispositions, manifest. CSV mirrors the same model
|
|
75
|
+
flat with a `Level` column (`item`/`variant`) + a requirement-coverage appendix.
|
|
61
76
|
`delivery_item_count` ≠ progress — variants are the execution metric.
|
|
62
77
|
|
|
78
|
+
**Data fidelity**: invisible test data is made visible, never normalized — `''` → `(empty)`,
|
|
79
|
+
whitespace-only → `(5 spaces)`, padded → `" value "` quoted verbatim. A trim/collapse here would
|
|
80
|
+
silently break the whitespace tests it describes.
|
|
81
|
+
|
|
63
82
|
**Authoring guidance the matrix rewards** (create-test side): payload/provider matrices (SQLi
|
|
64
83
|
payload lists, OAuth provider sets) belong in `@cases` datasets so each case is an atomic,
|
|
65
84
|
independently-reportable variant; keep dataset `case:` labels short and stable (`CHK-EMAIL-I1`),
|
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.4",
|
|
4
4
|
"description": "Deterministic E2E Test Compiler - Gherkin + Selectors → Playwright tests",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -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.4",
|
|
43
|
+
"@sungen/driver-ui": "3.2.16-beta.4",
|
|
44
44
|
"chalk": "^5.6.2",
|
|
45
45
|
"commander": "^14.0.2",
|
|
46
46
|
"dotenv": "^17.2.3",
|
|
@@ -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';
|
|
@@ -651,7 +652,7 @@ interface MatrixRunSummary {
|
|
|
651
652
|
async function exportMatrixTarget(
|
|
652
653
|
cwd: string,
|
|
653
654
|
target: DeliveryTarget,
|
|
654
|
-
opts: { check?: boolean; preview?: boolean },
|
|
655
|
+
opts: { check?: boolean; preview?: boolean; format?: MatrixFormat },
|
|
655
656
|
): Promise<MatrixRunSummary | null> {
|
|
656
657
|
const paths = matrixPathsFor(cwd, target);
|
|
657
658
|
const { model, mapMissing, mapErrors } = loadMatrixModel(paths);
|
|
@@ -689,24 +690,25 @@ async function exportMatrixTarget(
|
|
|
689
690
|
return null;
|
|
690
691
|
}
|
|
691
692
|
|
|
692
|
-
const { csvPath, xlsxPath } = await writeMatrixDeliverables(paths, model);
|
|
693
|
+
const { csvPath, xlsxPath } = await writeMatrixDeliverables(paths, model, opts.format ?? 'xlsx');
|
|
693
694
|
const passed = model.items.reduce((a, i) => a + i.resultCounts.passed, 0);
|
|
694
695
|
const failed = model.items.reduce((a, i) => a + i.resultCounts.failed, 0);
|
|
695
|
-
|
|
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}`);
|
|
696
698
|
return {
|
|
697
699
|
label: paths.label,
|
|
698
700
|
items: model.manifest.itemCount,
|
|
699
701
|
variants: model.manifest.variantCount,
|
|
700
702
|
passed,
|
|
701
703
|
failed,
|
|
702
|
-
file:
|
|
704
|
+
file: written.join(' + '),
|
|
703
705
|
};
|
|
704
706
|
}
|
|
705
707
|
|
|
706
708
|
function printMatrixSummaryTable(summaries: MatrixRunSummary[]): void {
|
|
707
709
|
log(`\n${COLOR.bold}Delivery matrix export complete${COLOR.reset}\n`);
|
|
708
710
|
const colWidth = Math.max(20, ...summaries.map((s) => s.label.length)) + 1;
|
|
709
|
-
log(` ${'Feature'.padEnd(colWidth)}Items Variants Passed Failed File`);
|
|
711
|
+
log(` ${'Feature'.padEnd(colWidth)}Items Variants Passed Failed File(s)`);
|
|
710
712
|
log(' ' + '-'.repeat(colWidth + 50));
|
|
711
713
|
for (const s of summaries) {
|
|
712
714
|
log(
|
|
@@ -796,9 +798,11 @@ export function registerDeliveryCommand(program: Command): void {
|
|
|
796
798
|
.option('--check', 'Matrix gates only — validate the delivery map, write nothing')
|
|
797
799
|
.option('--approve [ids]', 'Approve proposed groups (all, or a comma-separated id list) + stamp fingerprints')
|
|
798
800
|
.option('--preview', 'Render the matrix despite review-required findings (DRAFT watermark)')
|
|
801
|
+
.option('--format <format>', 'Matrix output format: xlsx (default) | csv | both', 'xlsx')
|
|
799
802
|
.action(async (names: string[], options: {
|
|
800
803
|
skipPreflight?: boolean; continueOnMissing?: boolean; env?: string;
|
|
801
804
|
legacy?: boolean; full?: boolean; check?: boolean; approve?: boolean | string; preview?: boolean;
|
|
805
|
+
format?: string;
|
|
802
806
|
}) => {
|
|
803
807
|
try {
|
|
804
808
|
// Same effect as running with SUNGEN_ENV=<env> — result-file resolution,
|
|
@@ -806,6 +810,13 @@ export function registerDeliveryCommand(program: Command): void {
|
|
|
806
810
|
if (options.env) process.env.SUNGEN_ENV = options.env;
|
|
807
811
|
const cwd = process.cwd();
|
|
808
812
|
|
|
813
|
+
// Matrix output format — one artifact by default (xlsx); csv/both on request.
|
|
814
|
+
const format = (options.format ?? 'xlsx').toLowerCase();
|
|
815
|
+
if (!['xlsx', 'csv', 'both'].includes(format)) {
|
|
816
|
+
console.error(`${COLOR.red}Invalid --format "${options.format}"${COLOR.reset} — use xlsx | csv | both`);
|
|
817
|
+
process.exit(1);
|
|
818
|
+
}
|
|
819
|
+
|
|
809
820
|
// 1. Scope detection — each positional name expands into one target
|
|
810
821
|
// per `.feature` file inside that screen/flow. Passing a specific
|
|
811
822
|
// feature basename (e.g. `home-modal`) narrows to that one file.
|
|
@@ -886,7 +897,9 @@ export function registerDeliveryCommand(program: Command): void {
|
|
|
886
897
|
const matrixSummaries: MatrixRunSummary[] = [];
|
|
887
898
|
let failedTargets = 0;
|
|
888
899
|
for (const target of toExport) {
|
|
889
|
-
const s = await exportMatrixTarget(cwd, target, {
|
|
900
|
+
const s = await exportMatrixTarget(cwd, target, {
|
|
901
|
+
check: options.check, preview: options.preview, format: format as MatrixFormat,
|
|
902
|
+
});
|
|
890
903
|
if (s) matrixSummaries.push(s);
|
|
891
904
|
else failedTargets++;
|
|
892
905
|
}
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
splitVpAndName,
|
|
15
15
|
} from '../feature-parser';
|
|
16
16
|
import { getCasesDatasetRows, resolveResultVariants } from '../result-variants';
|
|
17
|
-
import { classifyManualComments, renderAction, renderExpected, renderPrecondition } from './wording';
|
|
17
|
+
import { classifyManualComments, renderAction, renderExpected, renderPrecondition, renderSetupInstruction } from './wording';
|
|
18
18
|
import { scenarioFingerprint, combinedFingerprint, mapContentFingerprint } from './fingerprint';
|
|
19
19
|
import {
|
|
20
20
|
CoverageVariant,
|
|
@@ -72,12 +72,12 @@ function resolveDataPairs(
|
|
|
72
72
|
if (row) {
|
|
73
73
|
for (const [k, v] of Object.entries(row)) {
|
|
74
74
|
if (k.startsWith('__') || k === 'case' || k === 'name' || k === 'label') continue;
|
|
75
|
-
pairs.push(`${k}: ${String(v)}`);
|
|
75
|
+
pairs.push(`${k}: ${displayValue(String(v))}`);
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
for (const v of vars) {
|
|
79
79
|
const val = row && v in row ? undefined : testData?.[v]; // row columns already listed
|
|
80
|
-
if (val !== undefined) pairs.push(`${v}: ${val}`);
|
|
80
|
+
if (val !== undefined) pairs.push(`${v}: ${displayValue(val)}`);
|
|
81
81
|
}
|
|
82
82
|
return pairs;
|
|
83
83
|
}
|
|
@@ -141,7 +141,7 @@ export function deriveVariants(inputs: Pick<BuildInputs, 'feature' | 'merged' |
|
|
|
141
141
|
...(authRole ? [authRole === 'no-auth' ? 'The user is signed out.' : `The user is signed in as ${authRole}.`] : []),
|
|
142
142
|
...inputs.feature.backgroundGivenSteps.map(renderPrecondition),
|
|
143
143
|
...m.feature.rawGivenSteps.map(renderPrecondition),
|
|
144
|
-
...(manual?.preconditions ?? []).map((t) =>
|
|
144
|
+
...(manual?.preconditions ?? []).map((t) => renderSetupInstruction(t)),
|
|
145
145
|
]));
|
|
146
146
|
|
|
147
147
|
const base = {
|
|
@@ -247,11 +247,22 @@ export function deriveVariants(inputs: Pick<BuildInputs, 'feature' | 'merged' |
|
|
|
247
247
|
function substituteDisplayVars(text: string, vars: Record<string, string>): string {
|
|
248
248
|
return text.replace(/\{\{\s*([^}\s]+)\s*\}\}/g, (m, key: string) => {
|
|
249
249
|
if (!(key in vars)) return m; // unknown stays literal → Gate D flags it
|
|
250
|
-
|
|
251
|
-
return v === '' ? '(empty)' : v;
|
|
250
|
+
return displayValue(vars[key]);
|
|
252
251
|
});
|
|
253
252
|
}
|
|
254
253
|
|
|
254
|
+
/**
|
|
255
|
+
* Make invisible test data VISIBLE instead of losing it (review GAP-04):
|
|
256
|
+
* '' → (empty) · whitespace-only → (N spaces) · padded → quoted verbatim.
|
|
257
|
+
* The canonical value is never changed — only its presentation.
|
|
258
|
+
*/
|
|
259
|
+
export function displayValue(v: string): string {
|
|
260
|
+
if (v === '') return '(empty)';
|
|
261
|
+
if (/^\s+$/.test(v)) return `(${v.length} space${v.length > 1 ? 's' : ''})`;
|
|
262
|
+
if (v !== v.trim()) return `"${v}"`;
|
|
263
|
+
return v;
|
|
264
|
+
}
|
|
265
|
+
|
|
255
266
|
function sentenceOf(text: string): string {
|
|
256
267
|
const s = text.trim();
|
|
257
268
|
if (!s) return s;
|
|
@@ -368,6 +379,12 @@ export function buildMatrix(inputs: BuildInputs): MatrixModel {
|
|
|
368
379
|
const { result, counts } = rollUp(groupVariants);
|
|
369
380
|
const first = groupVariants[0];
|
|
370
381
|
const triggerShapes = new Set(groupVariants.map((v) => v.triggerShape.join(' ; ')));
|
|
382
|
+
const modes = new Set(groupVariants.map((v) => v.mode));
|
|
383
|
+
// Item priority = highest variant priority (per-variant priorities stay visible).
|
|
384
|
+
const priorityRank: Record<string, number> = { High: 0, Normal: 1, Low: 2 };
|
|
385
|
+
const priority = groupVariants
|
|
386
|
+
.map((v) => v.priority)
|
|
387
|
+
.sort((a, b) => (priorityRank[a] ?? 1) - (priorityRank[b] ?? 1))[0] ?? 'Normal';
|
|
371
388
|
return {
|
|
372
389
|
id: g.id,
|
|
373
390
|
target: g.target,
|
|
@@ -376,11 +393,16 @@ export function buildMatrix(inputs: BuildInputs): MatrixModel {
|
|
|
376
393
|
category: g.category,
|
|
377
394
|
// A stale (drifted) group presents as proposed regardless of its stored state.
|
|
378
395
|
review: stale.has(g.id) ? 'proposed' : g.review,
|
|
379
|
-
priority
|
|
380
|
-
mode: first?.mode ?? 'auto',
|
|
396
|
+
priority,
|
|
397
|
+
mode: modes.size > 1 ? 'mixed' : (first?.mode ?? 'auto'),
|
|
381
398
|
layers: Array.from(new Set(groupVariants.flatMap((v) => v.layers))),
|
|
382
399
|
traces: Array.from(new Set(groupVariants.flatMap((v) => v.traces))),
|
|
383
|
-
|
|
400
|
+
// Only the preconditions COMMON to every variant belong on the parent —
|
|
401
|
+
// copying the first variant's setup mis-states the start state of the
|
|
402
|
+
// others (review GAP-03). Variant-specific lines render as the sub-row's
|
|
403
|
+
// precondition delta.
|
|
404
|
+
precondition: (first?.precondition ?? []).filter((line) =>
|
|
405
|
+
groupVariants.every((v) => v.precondition.includes(line))),
|
|
384
406
|
trigger: triggerShapes.size === 1 ? (first?.trigger ?? []) : ['(per variant)'],
|
|
385
407
|
variants: groupVariants,
|
|
386
408
|
result,
|
|
@@ -17,6 +17,7 @@ import { getPackageVersion } from '../package-info';
|
|
|
17
17
|
import { writeCsv } from '../csv-exporter';
|
|
18
18
|
import { writeXlsx } from '../xlsx-exporter';
|
|
19
19
|
import { loadDeliveryMap, writeDeliveryMap } from './map-loader';
|
|
20
|
+
import { mapContentFingerprint } from './fingerprint';
|
|
20
21
|
import { buildMatrix, deriveVariants } from './build';
|
|
21
22
|
import { renderMatrixXlsx } from './render-xlsx';
|
|
22
23
|
import { renderMatrixCsv } from './render-csv';
|
|
@@ -121,14 +122,28 @@ export function approveMatrix(paths: MatrixTargetPaths, groupIds?: string[]): {
|
|
|
121
122
|
g.review = 'approved';
|
|
122
123
|
approved.push(g.id);
|
|
123
124
|
}
|
|
125
|
+
// Freeze the reviewed map semantics as well (GAP-09) — computed AFTER the
|
|
126
|
+
// review flips so re-running approve on an unchanged map is idempotent.
|
|
127
|
+
map.fingerprints.__map__ = mapContentFingerprint(map.groups, map.dispositions);
|
|
124
128
|
writeDeliveryMap(paths.mapFile, map);
|
|
125
129
|
return { findings: model.findings.filter((f) => f.severity !== 'error'), approved };
|
|
126
130
|
}
|
|
127
131
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
export type MatrixFormat = 'xlsx' | 'csv' | 'both';
|
|
133
|
+
|
|
134
|
+
/** Render + write the requested format(s). Caller has already enforced the gate policy. */
|
|
135
|
+
export async function writeMatrixDeliverables(
|
|
136
|
+
paths: MatrixTargetPaths,
|
|
137
|
+
model: MatrixModel,
|
|
138
|
+
format: MatrixFormat = 'xlsx',
|
|
139
|
+
): Promise<{ csvPath?: string; xlsxPath?: string }> {
|
|
140
|
+
const out: { csvPath?: string; xlsxPath?: string } = {};
|
|
141
|
+
if (format === 'csv' || format === 'both') {
|
|
142
|
+
out.csvPath = writeCsv(paths.cwd, paths.unit, renderMatrixCsv(model));
|
|
143
|
+
}
|
|
144
|
+
if (format === 'xlsx' || format === 'both') {
|
|
145
|
+
const wb = renderMatrixXlsx(model, getPackageVersion());
|
|
146
|
+
out.xlsxPath = await writeXlsx(paths.cwd, paths.unit, wb);
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
134
149
|
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { splitVpAndName, extractTestcaseType } from '../feature-parser';
|
|
15
|
+
import { mapContentFingerprint } from './fingerprint';
|
|
15
16
|
import { CoverageVariant, MatrixFinding, MAX_VARIANTS_PER_ITEM } from './types';
|
|
16
17
|
import { expandMapRef, BuildInputs } from './build';
|
|
17
18
|
|
|
@@ -156,10 +157,11 @@ function gateCAggregation(ctx: GateContext, findings: MatrixFinding[]): void {
|
|
|
156
157
|
const heuristicsConfirmed = g.review === 'approved' && !stale;
|
|
157
158
|
|
|
158
159
|
// Hard signature parts — recomputed, never trusted from the map. ERROR on mismatch.
|
|
160
|
+
// Execution mode and priority are deliberately NOT here: they are coverage
|
|
161
|
+
// dimensions (account states may need seeded manual variants next to auto ones;
|
|
162
|
+
// an item takes the highest variant priority) — shown per variant, never split on.
|
|
159
163
|
const hard: Array<[string, (v: CoverageVariant) => string]> = [
|
|
160
|
-
['execution mode', (v) => v.mode],
|
|
161
164
|
['test layer', (v) => [...v.layers].sort().join('+')],
|
|
162
|
-
['priority', (v) => v.priority],
|
|
163
165
|
];
|
|
164
166
|
for (const [label, keyOf] of hard) {
|
|
165
167
|
const distinct = new Set(vs.map(keyOf));
|
|
@@ -265,6 +267,25 @@ function gateEDrift(ctx: GateContext, findings: MatrixFinding[]): void {
|
|
|
265
267
|
const { map } = ctx.inputs;
|
|
266
268
|
const { variantsByVp } = ctx;
|
|
267
269
|
|
|
270
|
+
// The map's own semantics (targets/intents/oracles/grouping) are part of what
|
|
271
|
+
// was approved — an edit after approval must be re-reviewed, not silently
|
|
272
|
+
// published (review GAP-09).
|
|
273
|
+
const stampedMap = map.fingerprints.__map__;
|
|
274
|
+
const liveMap = mapContentFingerprint(map.groups, map.dispositions);
|
|
275
|
+
if (map.groups.some((g) => g.review === 'approved')) {
|
|
276
|
+
if (!stampedMap) {
|
|
277
|
+
findings.push({
|
|
278
|
+
gate: 'E', severity: 'review',
|
|
279
|
+
message: 'the delivery map has no stamped content fingerprint — run `sungen delivery --approve` to record the reviewed wording',
|
|
280
|
+
});
|
|
281
|
+
} else if (stampedMap !== liveMap) {
|
|
282
|
+
findings.push({
|
|
283
|
+
gate: 'E', severity: 'review',
|
|
284
|
+
message: 'the delivery map wording/grouping changed since approval — re-review, then `sungen delivery --approve`',
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
268
289
|
for (const g of map.groups) {
|
|
269
290
|
if (g.review !== 'approved') continue; // proposed groups are already under review
|
|
270
291
|
for (const ref of g.variants) {
|
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { statusToTestResult } from '../playwright-report-parser';
|
|
9
|
-
import { itemResultLabel } from './render-xlsx';
|
|
9
|
+
import { itemModeLabel, itemResultLabel } from './render-xlsx';
|
|
10
10
|
import { MatrixModel } from './types';
|
|
11
11
|
|
|
12
12
|
const HEADERS = [
|
|
13
13
|
'Level', 'ID', 'Target', 'Test Intent / Condition', 'Category', 'Priority',
|
|
14
14
|
'Precondition', 'Coverage / Test Data', 'Action / Trigger', 'Expected Result',
|
|
15
|
-
'Mode', 'Trace', 'Result', 'Executed Date', 'Note',
|
|
15
|
+
'Mode', 'Trace', 'Result', 'Executed Date', 'Note / Evidence', 'Defect ID',
|
|
16
16
|
];
|
|
17
17
|
|
|
18
18
|
function esc(value: string): string {
|
|
@@ -36,7 +36,8 @@ export function renderMatrixCsv(model: MatrixModel): string {
|
|
|
36
36
|
|
|
37
37
|
for (const item of model.items) {
|
|
38
38
|
const triggerTexts = new Set(item.variants.map((v) => v.trigger.join('\n')));
|
|
39
|
-
const
|
|
39
|
+
const triggerIsCommon = triggerTexts.size === 1;
|
|
40
|
+
const commonPre = new Set(item.precondition);
|
|
40
41
|
lines.push([
|
|
41
42
|
'item',
|
|
42
43
|
item.id,
|
|
@@ -46,13 +47,15 @@ export function renderMatrixCsv(model: MatrixModel): string {
|
|
|
46
47
|
item.priority,
|
|
47
48
|
item.precondition.join('\n'),
|
|
48
49
|
`${item.variants.length} variant(s): ${item.variants.map((v) => v.condition).join(' · ')}`,
|
|
49
|
-
|
|
50
|
+
// No placeholder text: when triggers differ the variant rows carry them (GAP-01).
|
|
51
|
+
triggerIsCommon ? item.variants[0].trigger.join('\n') : '',
|
|
50
52
|
item.oracle,
|
|
51
|
-
item
|
|
53
|
+
itemModeLabel(item),
|
|
52
54
|
item.traces.join(' '),
|
|
53
55
|
itemResultLabel(item),
|
|
54
56
|
'',
|
|
55
57
|
item.review === 'proposed' ? 'REVIEW REQUIRED' : '',
|
|
58
|
+
'',
|
|
56
59
|
].map(esc).join(','));
|
|
57
60
|
|
|
58
61
|
for (const v of item.variants) {
|
|
@@ -65,15 +68,17 @@ export function renderMatrixCsv(model: MatrixModel): string {
|
|
|
65
68
|
'',
|
|
66
69
|
v.condition,
|
|
67
70
|
'', '',
|
|
68
|
-
|
|
71
|
+
// Precondition DELTA — variant-specific setup only (review GAP-03).
|
|
72
|
+
v.precondition.filter((line) => !commonPre.has(line)).join('\n'),
|
|
69
73
|
v.data.join('\n'),
|
|
70
|
-
v.trigger.join('\n'),
|
|
74
|
+
triggerIsCommon ? '' : v.trigger.join('\n'),
|
|
71
75
|
expected,
|
|
72
76
|
v.mode === 'manual' ? 'Manual' : 'Auto',
|
|
73
77
|
v.traces.join(' '),
|
|
74
78
|
v.result ? statusToTestResult(v.result.status) : 'Pending',
|
|
75
79
|
isoDate(v.result?.startTime),
|
|
76
80
|
v.result?.error ? String(v.result.error).slice(0, 200) : '',
|
|
81
|
+
'',
|
|
77
82
|
].map(esc).join(','));
|
|
78
83
|
}
|
|
79
84
|
}
|