@sun-asterisk/sungen 3.2.23 → 3.2.24-beta.2

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 (63) hide show
  1. package/dist/cli/commands/audit.d.ts.map +1 -1
  2. package/dist/cli/commands/audit.js +40 -8
  3. package/dist/cli/commands/audit.js.map +1 -1
  4. package/dist/dashboard/templates/index.html +1 -1
  5. package/dist/exporters/csv-exporter.d.ts.map +1 -1
  6. package/dist/exporters/csv-exporter.js +3 -1
  7. package/dist/exporters/csv-exporter.js.map +1 -1
  8. package/dist/exporters/feature-parser.d.ts +7 -0
  9. package/dist/exporters/feature-parser.d.ts.map +1 -1
  10. package/dist/exporters/feature-parser.js +27 -0
  11. package/dist/exporters/feature-parser.js.map +1 -1
  12. package/dist/exporters/xlsx-report-builder.d.ts.map +1 -1
  13. package/dist/exporters/xlsx-report-builder.js +3 -1
  14. package/dist/exporters/xlsx-report-builder.js.map +1 -1
  15. package/dist/harness/audit.d.ts +22 -1
  16. package/dist/harness/audit.d.ts.map +1 -1
  17. package/dist/harness/audit.js +122 -13
  18. package/dist/harness/audit.js.map +1 -1
  19. package/dist/harness/flow-contract.d.ts +104 -0
  20. package/dist/harness/flow-contract.d.ts.map +1 -1
  21. package/dist/harness/flow-contract.js +199 -12
  22. package/dist/harness/flow-contract.js.map +1 -1
  23. package/dist/harness/parse.d.ts.map +1 -1
  24. package/dist/harness/parse.js +22 -3
  25. package/dist/harness/parse.js.map +1 -1
  26. package/dist/harness/quality-gates.d.ts +2 -1
  27. package/dist/harness/quality-gates.d.ts.map +1 -1
  28. package/dist/harness/quality-gates.js +47 -5
  29. package/dist/harness/quality-gates.js.map +1 -1
  30. package/dist/harness/sensors.d.ts.map +1 -1
  31. package/dist/harness/sensors.js +48 -5
  32. package/dist/harness/sensors.js.map +1 -1
  33. package/dist/harness/spec-coverage.d.ts +20 -0
  34. package/dist/harness/spec-coverage.d.ts.map +1 -1
  35. package/dist/harness/spec-coverage.js +35 -0
  36. package/dist/harness/spec-coverage.js.map +1 -1
  37. package/dist/harness/viewpoint-ledger.d.ts +4 -0
  38. package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
  39. package/dist/harness/viewpoint-ledger.js +59 -1
  40. package/dist/harness/viewpoint-ledger.js.map +1 -1
  41. package/dist/orchestrator/templates/ai-src/commands/create-test.md +28 -0
  42. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +98 -5
  43. package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-e-identity.md +1 -1
  44. package/dist/orchestrator/test-data-guide.d.ts.map +1 -1
  45. package/dist/orchestrator/test-data-guide.js +8 -0
  46. package/dist/orchestrator/test-data-guide.js.map +1 -1
  47. package/package.json +3 -3
  48. package/src/cli/commands/audit.ts +37 -8
  49. package/src/dashboard/templates/index.html +1 -1
  50. package/src/exporters/csv-exporter.ts +3 -1
  51. package/src/exporters/feature-parser.ts +22 -0
  52. package/src/exporters/xlsx-report-builder.ts +3 -1
  53. package/src/harness/audit.ts +131 -17
  54. package/src/harness/flow-contract.ts +258 -12
  55. package/src/harness/parse.ts +22 -3
  56. package/src/harness/quality-gates.ts +48 -5
  57. package/src/harness/sensors.ts +47 -4
  58. package/src/harness/spec-coverage.ts +38 -0
  59. package/src/harness/viewpoint-ledger.ts +58 -1
  60. package/src/orchestrator/templates/ai-src/commands/create-test.md +28 -0
  61. package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +98 -5
  62. package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-e-identity.md +1 -1
  63. package/src/orchestrator/test-data-guide.ts +8 -0
@@ -636,10 +636,21 @@ those namespaces are the **System INTEGRATION** group — keep their oracles abo
636
636
 
637
637
  **Read `requirements/flow-contract.yaml` FIRST — it is the flow's boundary and the yardstick
638
638
  `sungen audit` scores the flow against** (`flowCoverage` axis = journey phases HP/ER/EH automated;
639
- `FLOW-OUTCOME-UNPROVEN`; `FLOW-SCOPE-CREEP`). No contract yet author it with the user via the
640
- boundary checklist in `add-flow` (one business goal · clear trigger · ONE observable outcome
641
- valuable to the actor · name = "Verb + outcome"), THEN generate. **A filled contract is an INPUT —
642
- never rewrite it to match your output** (same rule as `test-viewpoint.md`).
639
+ `FLOW-OUTCOME-UNPROVEN`; `FLOW-SCOPE-CREEP`). **A filled contract is an INPUT never rewrite it
640
+ to match your output** (same rule as `test-viewpoint.md`).
641
+
642
+ > **HARD RULE the contract must be AUTHORED, never merely cited.** If
643
+ > `requirements/flow-contract.yaml` is absent, WRITE it (with the user, via the boundary checklist
644
+ > in `add-flow`: one business goal · clear trigger · ONE observable outcome valuable to the actor ·
645
+ > name = "Verb + outcome") **before** generating scenarios. Citing a contract you did not create
646
+ > leaves the flow scored as a generic screen — `flowCoverage` never applies, `coverage` falls back
647
+ > to page-type themes that do not fit a journey, and the audit reports `FLOW-CONTRACT-MISSING`.
648
+ > A contract also needs **both** guarantees, not just the happy one:
649
+ >
650
+ > - `successGuarantee` — everything true once the goal IS reached (what the Basic Flow proves).
651
+ > - `minimalGuarantee` — what must still hold when the journey **FAILS** (no duplicate record, no
652
+ > mail on a rejected submit, no half-written state). Without it an Exception Flow has nothing to
653
+ > assert against beyond "an error appeared", and `FLOW-GUARANTEE-MISSING` is reported.
643
654
 
644
655
  | Aspect | Screen | Flow |
645
656
  |---|---|---|
@@ -647,7 +658,7 @@ never rewrite it to match your output** (same rule as `test-viewpoint.md`).
647
658
  | Selector format | `[Element]` | `[Screen:Element]` (namespaced) |
648
659
  | Test data keys | `{{variable}}` | `{{phase.variable}}` |
649
660
  | Feature tag | `@auto` / `@smoke` etc. | `@flow` (required) |
650
- | Scenario ids | `VP-<CATEGORY>-NNN` | `FL-<PHASE>-NNN` — phases: `HP` (happy path), `ER` (error recovery), `EH` (guards), `UI` (journey UI states, optional) |
661
+ | Scenario ids | `VP-<CATEGORY>-NNN` | `FL-<PHASE>-NNN` — use-case phases `BF`/`AF0n`/`EF0n` (see below), or the flat `HP` (happy path) / `ER` (error recovery) / `EH` (guards) / `UI` for a short single-outcome flow |
651
662
 
652
663
  **Scenarios to generate — every phase demanded by the contract, automated:**
653
664
 
@@ -659,6 +670,88 @@ never rewrite it to match your output** (same rule as `test-viewpoint.md`).
659
670
  | Cross-screen handoff | After every screen transition, assert the CARRIED state on the new screen (the added product's name in the cart, the email echoed on the sent screen). | blind tails cap `businessDepth` (`FLOW-HANDOFF-SHALLOW`) |
660
671
  | Stateful regression (when `stateful:` declared) | Count/quantity proof · teardown (remove → empty) · multi-source add. | missing dims cap `businessDepth` (`FLOW-DEPTH`) |
661
672
 
673
+ ### Use-case decomposition — how a flow becomes 15 scenarios instead of 3
674
+
675
+ `HP`/`ER`/`EH` are phase *buckets*; they do not tell you how many flows a use case HAS. The
676
+ use-case framework does, and it is the default decomposition for a multi-screen journey:
677
+
678
+ **1 Use Case = 1 Basic Flow + N Alternate Flows + N Exception Flows.**
679
+
680
+ | Phase | Meaning | Test |
681
+ |---|---|---|
682
+ | `BF` Basic Flow | **Exactly one.** The path where nothing goes wrong, end to end. | Reaches `outcome.assertion` and satisfies `successGuarantee`. |
683
+ | `AF` Alternate Flow | A **designed-for branch** — the user does something else legitimate. NOT an error. | Its own outcome: either re-enters the BF at a named step, or ends elsewhere on purpose. |
684
+ | `EF` Exception Flow | **Blocked.** The goal is not reached. | Asserts `minimalGuarantee` — what must STILL be true now that this failed. |
685
+
686
+ `AF` is the bucket most generators skip entirely, and it is where the interesting bugs live
687
+ (going back and finding the form re-hydrated, two tabs, an escape hatch out of the journey).
688
+ Use `FL-BF-NNN` / `FL-AF0n-NNN` / `FL-EF0n-NNN` ids — `sungen audit` buckets them (BF→business
689
+ core, AF→behaviour, EF→validation/security) and traces them to the viewpoint's `FL-BF`/`FL-AF`/
690
+ `FL-EF` priority rows. Declare `phases: [BF, AF, EF]` in the contract; the FIRST phase is the
691
+ Basic Flow and is the one that must reach `outcome`.
692
+
693
+ **Declare the flow INVENTORY, not just the phases.** `phases: [BF, AF, EF]` says which
694
+ vocabulary the suite uses; it cannot say how many flows the use case has — a phase counts as
695
+ covered the moment ONE scenario carries it, so a fifteen-flow use case with three scenarios
696
+ reported full phase coverage while a reviewer counting flows read it as a third done. List every
697
+ flow in the contract, and give each one a status:
698
+
699
+ ```yaml
700
+ flows:
701
+ - id: BF
702
+ outcome: "The successGuarantee — verified, recorded, signed in"
703
+ status: covered
704
+ - id: AF02
705
+ branchFrom: "BF step 4, before submitting"
706
+ outcome: "Re-enters BF at step 4 with the buffer restored"
707
+ status: covered
708
+ - id: EF08
709
+ branchFrom: "BF step 1, the control tapped twice"
710
+ outcome: "One request, one record"
711
+ status: pending-clarification # the spec is silent — ASK, never assume a guard
712
+ reason: "The guard lives in ST_AUTH_001's spec, which this project does not hold."
713
+ ```
714
+
715
+ Four statuses, and **silence is not one of them**: `covered` · `deferred` · `pending-clarification`
716
+ (the behaviour is not agreed yet, so no scenario can be right) · `out-of-scope` (another suite owns
717
+ it). Anything but `covered` needs a `reason:` — a deferral nobody can audit is the same as a
718
+ missing flow. `sungen audit` then measures `flowCoverage` per DECLARED FLOW and reports
719
+ `FLOW-UNCOVERED` (declared, nobody wrote it), `FLOW-UNDECLARED` (a scenario claiming a flow id the
720
+ inventory never declares), `FLOW-STATUS-UNREASONED` and `FLOW-INVENTORY-MISSING`.
721
+
722
+ **One flow, one id — a viewpoint is not a flow.** Several scenarios may prove one flow: give them
723
+ the same flow id and different sequence numbers (`VP-VAL-EF01-001`, `VP-VAL-EF01-002`), never a
724
+ fresh flow id per assertion. Two shapes the audit reports as `FLOW-PHASE-MISFILED`: an `EF` that
725
+ reaches the outcome with nothing failing (that is a success-path postcondition, so it belongs to
726
+ the BF), and an `AF` with no branch point (it walks the basic path asserting extra content — also
727
+ the BF's). Both inflate the flow count while adding no branch coverage.
728
+
729
+ **Declare once, then declare only the differences.** Actor · Trigger · Goal · Precondition ·
730
+ `successGuarantee` · `minimalGuarantee` are use-case-level: they live in `flow-contract.yaml`
731
+ and are never repeated per flow. Each flow in `test-viewpoint.md` then states only three things:
732
+ **where it branches from · its own steps · its own outcome.** Repeating the precondition in
733
+ fifteen rows is noise; the branch point is the information.
734
+
735
+ **Enumerate flows with the step × risk matrix.** Walk every BF step and ask each column. A hit
736
+ with a basis in the spec becomes a flow; a hit with no basis becomes an **open question** — never
737
+ an invented behaviour:
738
+
739
+ | Risk family | The question at this step |
740
+ |---|---|
741
+ | Double submit | Is the control tapped twice guarded? One request, one record? (Spec silent → open question, not an assumed guard.) |
742
+ | Client-side buffer | Is state held client-side between this step and the next? Then test **both** directions — it survives a legitimate back, and it is **LOST** on reload. A suite proving only the surviving side has tested half the mechanism (`CONTINUITY-ONE-SIDED`). |
743
+ | Concurrency | Two tabs / two devices at this step — whose state wins? |
744
+ | Server error | This step's submit fails server-side: what is kept, what is retryable, is anything half-written? |
745
+ | Abandonment / TTL | The user stops here. Does the partial state lapse, expire, or linger forever? |
746
+ | Direct access | This step's URL opened cold, without the preceding state → refused, and the screen never renders. |
747
+ | Auth transition | Where exactly does the session begin? Before that point the actor is unauthenticated — prove it. |
748
+ | Escape hatch | Is there a documented way OUT of the journey here (back to top, cancel)? That is an `AF`, and its outcome is "goal deliberately not reached". |
749
+
750
+ **Do NOT re-derive field validation.** Per-field equivalence and boundary values belong to the
751
+ OWNING SCREEN's suite. A flow takes representative inputs (see the system-test rule above). State
752
+ that exclusion in the viewpoint's Design Decisions — and if the screen suite does not exist, that
753
+ is a gap to REPORT, not to quietly absorb.
754
+
662
755
  **Boundary discipline while generating:** every scenario must serve the contract's goal. A scenario
663
756
  that never touches `outcome.screen` and is not a guard (`EH`) or error-recovery (`ER`) belongs in a
664
757
  DIFFERENT flow — propose the split instead of writing it here (`FLOW-SCOPE-CREEP` will flag it).
@@ -107,7 +107,7 @@ See `SKILL.md` for the 4 Viewpoints, Shared Checks, and Security Tag Rules.
107
107
 
108
108
  **[VP-VAL] Edge cases**
109
109
 
110
- - [@normal] Register with a sub-address email (user+tag@gmail.com) → treated as a unique email, creation succeeds
110
+ - [@normal] Register with a sub-address email (user+tag@sun-asterisk.com) → treated as a unique email, creation succeeds
111
111
  - [@low] Browser autofill fills the fields → the form receives the correct values, no conflict with a custom input component
112
112
 
113
113
  ---
@@ -1 +1 @@
1
- {"version":3,"file":"test-data-guide.d.ts","sourceRoot":"","sources":["../../src/orchestrator/test-data-guide.ts"],"names":[],"mappings":"AAaA,oEAAoE;AACpE,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAgBvD;AA2GD;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAoB1D"}
1
+ {"version":3,"file":"test-data-guide.d.ts","sourceRoot":"","sources":["../../src/orchestrator/test-data-guide.ts"],"names":[],"mappings":"AAaA,oEAAoE;AACpE,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAgBvD;AAmHD;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAoB1D"}
@@ -85,6 +85,14 @@ payload \`<script>alert(1)</script>\`; the exact expected error message from the
85
85
  **Self-check:** *"If I swapped this for another value of the same kind, would the
86
86
  test still mean the same thing?"* → yes = INVENTED.
87
87
 
88
+ **Email domains — never a live mailbox provider.** An invented address still gets
89
+ mail sent to it: a registration or password-reset test submits it and the system
90
+ dispatches for real. \`@gmail.com\`, \`@yahoo.com\`, \`@outlook.com\` and friends all
91
+ resolve, so that mail reaches whoever owns the address — a stranger, or someone's
92
+ personal account. Use the project domain \`@sun-asterisk.com\`, or \`@example.com\`
93
+ (RFC 2606 — reserved, guaranteed never to deliver). Make each run unique with
94
+ \`{{$timestamp}}\` in the local part: \`"signup+{{\$timestamp}}@sun-asterisk.com"\`.
95
+
88
96
  ## 2) ENV-BOUND — real records of the test environment (not secret)
89
97
 
90
98
  **What it is:** the value must EXIST in the environment for the test to work — a
@@ -1 +1 @@
1
- {"version":3,"file":"test-data-guide.js","sourceRoot":"","sources":["../../src/orchestrator/test-data-guide.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAcA,wCAgBC;AA+GD,4DAoBC;AAjKD;;;;;;;;;GASG;AACH,uCAAyB;AACzB,2CAA6B;AAE7B,oEAAoE;AACpE,SAAgB,cAAc,CAAC,QAAgB;IAC7C,OAAO;QACL,KAAK,QAAQ,+DAA+D;QAC5E,+EAA+E;QAC/E,sBAAsB;QACtB,2EAA2E;QAC3E,4EAA4E;QAC5E,wEAAwE;QACxE,iCAAiC;QACjC,8EAA8E;QAC9E,yEAAyE;QACzE,8EAA8E;QAC9E,0EAA0E;QAC1E,+EAA+E;QAC/E,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4FhB,CAAC;AAEF,MAAM,cAAc,GAAG;;;;;;;;;CAStB,CAAC;AAEF;;;GAGG;AACH,SAAgB,wBAAwB,CAAC,GAAW;IAClD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,oBAAoB,CAAC,CAAC;IAC7D,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACrE,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IACjD,CAAC;IACD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;IACtD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAChC,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC;IACzD,CAAC;IACD,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IACnD,IAAI,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACvF,6EAA6E;QAC7E,oEAAoE;QACpE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1F,EAAE,CAAC,cAAc,CAAC,aAAa,EAAE,iFAAiF,CAAC,CAAC;QACtH,CAAC;IACH,CAAC;SAAM,CAAC;QACN,EAAE,CAAC,aAAa,CAAC,aAAa,EAAE,+EAA+E,EAAE,OAAO,CAAC,CAAC;IAC5H,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"test-data-guide.js","sourceRoot":"","sources":["../../src/orchestrator/test-data-guide.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAcA,wCAgBC;AAuHD,4DAoBC;AAzKD;;;;;;;;;GASG;AACH,uCAAyB;AACzB,2CAA6B;AAE7B,oEAAoE;AACpE,SAAgB,cAAc,CAAC,QAAgB;IAC7C,OAAO;QACL,KAAK,QAAQ,+DAA+D;QAC5E,+EAA+E;QAC/E,sBAAsB;QACtB,2EAA2E;QAC3E,4EAA4E;QAC5E,wEAAwE;QACxE,iCAAiC;QACjC,8EAA8E;QAC9E,yEAAyE;QACzE,8EAA8E;QAC9E,0EAA0E;QAC1E,+EAA+E;QAC/E,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoGhB,CAAC;AAEF,MAAM,cAAc,GAAG;;;;;;;;;CAStB,CAAC;AAEF;;;GAGG;AACH,SAAgB,wBAAwB,CAAC,GAAW;IAClD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,oBAAoB,CAAC,CAAC;IAC7D,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACrE,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IACjD,CAAC;IACD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;IACtD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAChC,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC;IACzD,CAAC;IACD,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IACnD,IAAI,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACvF,6EAA6E;QAC7E,oEAAoE;QACpE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1F,EAAE,CAAC,cAAc,CAAC,aAAa,EAAE,iFAAiF,CAAC,CAAC;QACtH,CAAC;IACH,CAAC;SAAM,CAAC;QACN,EAAE,CAAC,aAAa,CAAC,aAAa,EAAE,+EAA+E,EAAE,OAAO,CAAC,CAAC;IAC5H,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sun-asterisk/sungen",
3
- "version": "3.2.23",
3
+ "version": "3.2.24-beta.2",
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.23",
43
- "@sungen/driver-ui": "3.2.23",
42
+ "@sungen/driver-data-factory": "3.2.24-beta.2",
43
+ "@sungen/driver-ui": "3.2.24-beta.2",
44
44
  "chalk": "^5.6.2",
45
45
  "commander": "^14.0.2",
46
46
  "dotenv": "^17.2.3",
@@ -29,11 +29,21 @@ function render(r: AuditReport): void {
29
29
  L(` Quality score (business-weighted): ${r.score.overall}/10 [Viewpoint Gate: ${r.gateStatus}]`);
30
30
  L(` Intent: focus=${r.intent.focus} · depth threshold ${r.depth.threshold.toFixed(2)} (${r.intent.source})`);
31
31
  L(` Provenance: sungen ${r.provenance.sungenVersion} · catalog ${r.provenance.catalogHash} (same here on every machine → same score)`);
32
- L(` coverage ${bar(r.score.coverage)} ${(r.score.coverage * 100).toFixed(0)}%`);
33
- L(` businessDepth ${bar(r.score.businessDepth)} ${(r.score.businessDepth * 100).toFixed(0)}% [${r.depth.verdict.toUpperCase()}]`);
34
- L(` claimProof ${bar(r.claim.ratio)} ${(r.claim.ratio * 100).toFixed(0)}% [${r.claim.verdict.toUpperCase()}]`);
35
- L(` balance ${bar(r.score.balance)} ${(r.score.balance * 100).toFixed(0)}%`);
36
- L(` traceability ${bar(r.score.traceability)} ${(r.score.traceability * 100).toFixed(0)}%`);
32
+ // Print the axes the score was ACTUALLY built from — a fixed five-line block omitted a flow's
33
+ // `flowCoverage`, `specFR` and `atomicLedger` while showing `balance`, which a contract flow
34
+ // does not score on.
35
+ const verdictOf: Record<string, string> = {
36
+ businessDepth: ` [${r.depth.verdict.toUpperCase()}]`,
37
+ claimProof: ` [${r.claim.verdict.toUpperCase()}]`,
38
+ };
39
+ const width = Math.max(...r.score.axes.map((a) => a.key.length));
40
+ for (const a of r.score.axes) {
41
+ const label = a.key.padEnd(width);
42
+ // Two different n/a: this unit supplied nothing, vs this axis does not apply to this KIND
43
+ // of unit. Only the first is something an author can act on.
44
+ if (!a.applicable) { L(` ${label} ${' '.repeat(20)} n/a — ${a.byDesign ? 'not applicable to a contract flow' : 'no evidence'}`); continue; }
45
+ L(` ${label} ${bar(a.value)} ${(a.value * 100).toFixed(0)}%${verdictOf[a.key] ?? ''}`);
46
+ }
37
47
  L(` (${r.score.formula})`);
38
48
  L('');
39
49
  L(` ① Viewpoint Gate — page-type: ${r.gate.pageType ?? 'unknown'} — themes ${r.gate.themesCovered}/${r.gate.themesTotal} covered`);
@@ -44,7 +54,15 @@ function render(r: AuditReport): void {
44
54
  const fq = r.flowQuality;
45
55
  L(` ①F Flow contract — goal: ${fq.contract!.goal}`);
46
56
  L(` outcome [${fq.contract!.outcome.screen}]: ${fq.outcomeProven ? '✓ proven (automated data assertion)' : fq.outcomeManualOnly ? '⚠ manual-only' : '✗ UNPROVEN'}`);
47
- L(` phases: ${fq.phases.map((p) => `${p.phase}=${p.covered ? (p.automated ? '✓' : 'manual') : '✗'}`).join(' ')} → flowCoverage ${(fq.phaseRatio * 100).toFixed(0)}%`);
57
+ L(` phases: ${fq.phases.map((p) => `${p.phase}=${p.covered ? (p.automated ? '✓' : 'manual') : '✗'}`).join(' ')} → phase coverage ${(fq.phaseRatio * 100).toFixed(0)}%`);
58
+ // The inventory, when declared, is the coverage that counts — phase coverage is only
59
+ // "each of BF/AF/EF appears at least once" and printing it as `flowCoverage` next to a 89%
60
+ // axis read as a contradiction.
61
+ const inv = r.flowInventory;
62
+ if (inv) {
63
+ L(` flows: ${inv.covered.length}/${inv.covered.length + inv.uncovered.length} declared covered → flowCoverage ${(inv.ratio * 100).toFixed(0)}%`
64
+ + (inv.accounted.length ? ` (${inv.accounted.map((f) => `${f.id}=${f.status}`).join(', ')})` : ''));
65
+ }
48
66
  L(` handoffs asserted: ${fq.handoffs.asserted}/${fq.handoffs.total}${fq.offGoal.length ? ` ⚠ off-goal scenarios: ${fq.offGoal.length} (${fq.offGoalCategories.join(', ')})` : ''}`);
49
67
  L('');
50
68
  }
@@ -60,7 +78,11 @@ function render(r: AuditReport): void {
60
78
  L(` ③·5 Taxonomy — ${r.taxonomy.mislabeled.length === 0 ? '✓ VP codes match scenario semantics' : `⚠ ${r.taxonomy.mislabeled.length} mislabel(s)`}`);
61
79
  for (const m of r.taxonomy.mislabeled.slice(0, 6)) L(` ⚠ ${m.name} → VP-${m.current} reads as ${m.suggested} ("${m.signal}")`);
62
80
  L('');
63
- L(` ④ Coverage balance ${r.balance.imbalanced ? '⚠ IMBALANCED' : '✓ balanced'}`);
81
+ // A use-case decomposition is lopsided BY CONSTRUCTION (1 basic flow, N exceptions), so the
82
+ // verdict is withheld where the axis carries no weight — printing "IMBALANCED" there told
83
+ // authors to pad the suite to fix a number that was never being scored.
84
+ const balanceScored = r.score.axes.find((a) => a.key === 'balance')?.applicable !== false;
85
+ L(` ④ Coverage balance — ${balanceScored ? (r.balance.imbalanced ? '⚠ IMBALANCED' : '✓ balanced') : 'not scored for a contract flow (the phases are)'}`);
64
86
  L(` buckets: ${Object.entries(r.balance.byBucket).map(([k, v]) => `${k}=${v}`).join(' ')}`);
65
87
  L('');
66
88
  L(` ⑤ Duplicate — ${r.duplicates.clusters.length} same-shape cluster(s), ${r.duplicates.exactDuplicateCount} likely exact dup(s)`);
@@ -91,7 +113,14 @@ function render(r: AuditReport): void {
91
113
  if (na.length) L(` not scored (no evidence in this unit): ${na.join(', ')}`);
92
114
  L(` weakest: ${r.calibration.weakest.axis} ${(r.calibration.weakest.value * 100).toFixed(0)}%${r.calibration.inflated ? ' ⚠ SCORE-INFLATED-BY-BREADTH' : ''}`);
93
115
  if (r.calibration.cappedAt !== undefined) {
94
- L(` ⚠ score held at ${r.calibration.cappedAt.toFixed(1)} by the weakest axis fix it to lift the number`);
116
+ // Only blame the weakest axis when it IS weak. A cap for missing evidence printed
117
+ // "held by the weakest axis — fix it" next to "weakest: flowCoverage 100%", which sent
118
+ // authors hunting for a defect in a maxed-out axis (#595). The SCORE-CAPPED finding
119
+ // carries the real reason; say which kind of cap this is.
120
+ const weak = r.calibration.weakest.value < 0.7;
121
+ L(weak
122
+ ? ` ⚠ score held at ${r.calibration.cappedAt.toFixed(1)} by the weakest axis — fix it to lift the number`
123
+ : ` ⚠ score held at ${r.calibration.cappedAt.toFixed(1)} — no axis is weak; see the SCORE-CAPPED finding for what evidence is missing`);
95
124
  }
96
125
  L('');
97
126
  }
@@ -117,7 +117,7 @@ use chrome, FireFox or Internet Explorer 11`)}var a=e(`safe-buffer`),o=e(`random
117
117
  \r -퟿-�𐀀-􏿿`,n.S=` \r
118
118
  `,n.NAME_START_CHAR=`:A-Z_a-zÀ-ÖØ-öø-˿Ͱ-ͽͿ-῿‌‍⁰-↏Ⰰ-⿯、-퟿豈-﷏ﷰ-�𐀀-󯿿`,n.NAME_CHAR=`-`+n.NAME_START_CHAR+`.0-9·̀-ͯ‿-⁀`,n.CHAR_RE=RegExp(`^[`+n.CHAR+`]$`,`u`),n.S_RE=RegExp(`^[`+n.S+`]+$`,`u`),n.NAME_START_CHAR_RE=RegExp(`^[`+n.NAME_START_CHAR+`]$`,`u`),n.NAME_CHAR_RE=RegExp(`^[`+n.NAME_CHAR+`]$`,`u`),n.NAME_RE=RegExp(`^[`+n.NAME_START_CHAR+`][`+n.NAME_CHAR+`]*$`,`u`),n.NMTOKEN_RE=RegExp(`^[`+n.NAME_CHAR+`]+$`,`u`);function r(e){return e>=65&&e<=90||e>=97&&e<=122||e===58||e===95||e===8204||e===8205||e>=192&&e<=214||e>=216&&e<=246||e>=248&&e<=767||e>=880&&e<=893||e>=895&&e<=8191||e>=8304&&e<=8591||e>=11264&&e<=12271||e>=12289&&e<=55295||e>=63744&&e<=64975||e>=65008&&e<=65533||e>=65536&&e<=983039}n.S_LIST=[32,10,13,9],n.isChar=function(e){return e>=32&&e<=55295||e===10||e===13||e===9||e>=57344&&e<=65533||e>=65536&&e<=1114111},n.isS=function(e){return e===32||e===10||e===13||e===9},n.isNameStartChar=r,n.isNameChar=function(e){return r(e)||e>=48&&e<=57||e===45||e===46||e===183||e>=768&&e<=879||e>=8255&&e<=8256}},{}],544:[function(e,t,n){Object.defineProperty(n,"__esModule",{value:!0}),n.CHAR=`-퟿-�𐀀-􏿿`,n.RESTRICTED_CHAR=`-\b\v\f--„†-Ÿ`,n.S=` \r
119
119
  `,n.NAME_START_CHAR=`:A-Z_a-zÀ-ÖØ-öø-˿Ͱ-ͽͿ-῿‌‍⁰-↏Ⰰ-⿯、-퟿豈-﷏ﷰ-�𐀀-󯿿`,n.NAME_CHAR=`-`+n.NAME_START_CHAR+`.0-9·̀-ͯ‿-⁀`,n.CHAR_RE=RegExp(`^[`+n.CHAR+`]$`,`u`),n.RESTRICTED_CHAR_RE=RegExp(`^[`+n.RESTRICTED_CHAR+`]$`,`u`),n.S_RE=RegExp(`^[`+n.S+`]+$`,`u`),n.NAME_START_CHAR_RE=RegExp(`^[`+n.NAME_START_CHAR+`]$`,`u`),n.NAME_CHAR_RE=RegExp(`^[`+n.NAME_CHAR+`]$`,`u`),n.NAME_RE=RegExp(`^[`+n.NAME_START_CHAR+`][`+n.NAME_CHAR+`]*$`,`u`),n.NMTOKEN_RE=RegExp(`^[`+n.NAME_CHAR+`]+$`,`u`);function r(e){return e>=65&&e<=90||e>=97&&e<=122||e===58||e===95||e===8204||e===8205||e>=192&&e<=214||e>=216&&e<=246||e>=248&&e<=767||e>=880&&e<=893||e>=895&&e<=8191||e>=8304&&e<=8591||e>=11264&&e<=12271||e>=12289&&e<=55295||e>=63744&&e<=64975||e>=65008&&e<=65533||e>=65536&&e<=983039}n.S_LIST=[32,10,13,9],n.isChar=function(e){return e>=1&&e<=55295||e>=57344&&e<=65533||e>=65536&&e<=1114111},n.isRestrictedChar=function(e){return e>=1&&e<=8||e===11||e===12||e>=14&&e<=31||e>=127&&e<=132||e>=134&&e<=159},n.isCharAndNotRestricted=function(e){return e===9||e===10||e===13||e>31&&e<127||e===133||e>159&&e<=55295||e>=57344&&e<=65533||e>=65536&&e<=1114111},n.isS=function(e){return e===32||e===10||e===13||e===9},n.isNameStartChar=r,n.isNameChar=function(e){return r(e)||e>=48&&e<=57||e===45||e===46||e===183||e>=768&&e<=879||e>=8255&&e<=8256}},{}],545:[function(e,t,n){function r(e){return e>=65&&e<=90||e===95||e>=97&&e<=122||e>=192&&e<=214||e>=216&&e<=246||e>=248&&e<=767||e>=880&&e<=893||e>=895&&e<=8191||e>=8204&&e<=8205||e>=8304&&e<=8591||e>=11264&&e<=12271||e>=12289&&e<=55295||e>=63744&&e<=64975||e>=65008&&e<=65533||e>=65536&&e<=983039}Object.defineProperty(n,"__esModule",{value:!0}),n.NC_NAME_START_CHAR=`A-Z_a-zÀ-ÖØ-öø-˿Ͱ-ͽͿ-῿‌-‍⁰-↏Ⰰ-⿯、-퟿豈-﷏ﷰ-�𐀀-󯿿`,n.NC_NAME_CHAR=`-`+n.NC_NAME_START_CHAR+`.0-9·̀-ͯ‿-⁀`,n.NC_NAME_START_CHAR_RE=RegExp(`^[`+n.NC_NAME_START_CHAR+`]$`,`u`),n.NC_NAME_CHAR_RE=RegExp(`^[`+n.NC_NAME_CHAR+`]$`,`u`),n.NC_NAME_RE=RegExp(`^[`+n.NC_NAME_START_CHAR+`][`+n.NC_NAME_CHAR+`]*$`,`u`),n.isNCNameStartChar=r,n.isNCNameChar=function(e){return r(e)||e===45||e===46||e>=48&&e<=57||e===183||e>=768&&e<=879||e>=8255&&e<=8256}},{}]},{},[15])(15)}))})),aN=l(rN(),1),oN=l(iN()),sN=`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`,cN=`FFD9D2E9`,lN=`FF000000`,uN=`FFD9D2E9`,dN=`FFBFBFBF`,fN=`Times New Roman`,pN=`Arial`,mN={argb:`FF000000`},hN={argb:`FFFFFFFF`},gN={argb:`FFD9D2E9`},_N={style:`thin`,color:mN},vN={top:_N,left:_N,bottom:_N,right:_N};function yN(e){e.border={top:{style:`thin`,color:{argb:dN}},left:{style:`thin`,color:{argb:dN}},bottom:{style:`thin`,color:{argb:dN}},right:{style:`thin`,color:{argb:dN}}}}function bN(e,t,n,r,i=`BM-2-901-13`){let a=(()=>{let e=new Date;return`${String(e.getDate()).padStart(2,`0`)}/${String(e.getMonth()+1).padStart(2,`0`)}/${e.getFullYear()}`})();for(let e=1;e<=4;e++)t.getRow(e);t.mergeCells(`A1:C3`),t.mergeCells(`D1:F3`),t.mergeCells(`G1:H1`),t.mergeCells(`G2:H2`),t.mergeCells(`G3:H3`),t.mergeCells(`A4:F4`),t.mergeCells(`G4:H4`);let o=t.getCell(`A1`);o.alignment={horizontal:`center`,vertical:`middle`},o.border=vN;try{let n=e.addImage({base64:sN,extension:`png`});t.addImage(n,{tl:{nativeCol:0,nativeColOff:1237500,nativeRow:0,nativeRowOff:42862},ext:{width:90,height:51},editAs:`oneCell`})}catch{}let s=t.getCell(`D1`);s.value=n,s.font={bold:!0,size:18,name:fN},s.alignment={horizontal:`center`,vertical:`middle`,wrapText:!0},s.border=vN;let c=t.getCell(`G1`);c.value=`No: ${i}`,c.font={size:12,name:fN},c.alignment={vertical:`middle`},c.border={top:_N,left:_N,right:_N};let l=t.getCell(`G2`);l.value=`Version: ${r??`unknown`}`,l.font={size:12,name:fN},l.alignment={vertical:`middle`},l.border={left:_N,right:_N};let u=t.getCell(`G3`);u.value=`Issue Date: ${a}`,u.font={size:12,name:fN},u.alignment={vertical:`middle`},u.border={left:_N,right:_N,bottom:_N};let d=t.getCell(`A4`);d.value=`SUN ASTERISK VIETNAM CO., LTD`,d.font={bold:!0,size:10,name:fN},d.alignment={vertical:`middle`},d.border=vN;let f=t.getCell(`G4`);f.value=`ISO/IEC 27001:2022 & ISO 9001:2015`,f.font={bold:!0,size:10,name:fN},f.alignment={horizontal:`right`,vertical:`middle`,wrapText:!0},f.border=vN}var xN=[`#`,`Sheet name`,`Passed`,`Failed`,`Pending`,`N/A`,`Remain`,`Total`];function SN(e,t,n){let r=e.addWorksheet(`Overview`);r.columns=[{width:4},{width:6},{width:26},{width:13},{width:13},{width:13},{width:13},{width:13},{width:13}],bN(e,r,`${(t[0]?.summary.screen??``).toUpperCase()} TESTCASE`,n);let i=r.getRow(7);i.height=20,xN.forEach((e,t)=>{let n=i.getCell(2+t);n.value=e,n.font={bold:!0,size:10,color:mN,name:pN},n.fill={type:`pattern`,pattern:`solid`,fgColor:gN},n.alignment={horizontal:`center`,vertical:`middle`,wrapText:!0},n.border=vN});let a={passed:0,failed:0,pending:0,na:0,remain:0,total:0},o=(e,t,n)=>{let i=r.getRow(e);t.forEach((e,t)=>{let r=i.getCell(2+t);r.value=e,r.font={bold:n,size:10,color:mN,name:pN},r.alignment={horizontal:t<=1?`left`:`center`,vertical:`middle`},r.border=vN,n&&(r.fill={type:`pattern`,pattern:`solid`,fgColor:gN})})},s=e=>e.replace(/'/g,`''`),c=(e,t,n)=>({formula:`'${s(e)}'!${t}`,result:n}),l=8,u=l;t.forEach((e,t)=>{let n=e.summary,r=Math.max(0,n.total-n.passed-n.na);o(l,[t+1,e.sheetName,c(e.sheetName,`D7`,n.passed),c(e.sheetName,`E7`,n.failed),c(e.sheetName,`F7`,n.pending),c(e.sheetName,`G7`,n.na),c(e.sheetName,`H7`,r),c(e.sheetName,`C7`,n.total)],!1),a.passed+=n.passed,a.failed+=n.failed,a.pending+=n.pending,a.na+=n.na,a.remain+=r,a.total+=n.total,l+=1});let d=l-1,f=(e,t)=>({formula:`SUM(${e}${u}:${e}${d})`,result:t});o(l,[``,`Total`,f(`D`,a.passed),f(`E`,a.failed),f(`F`,a.pending),f(`G`,a.na),f(`H`,a.remain),f(`I`,a.total)],!0)}function CN(e,t,n,r,i,a){let o=e.addWorksheet(t,{views:[{state:`frozen`,ySplit:0,xSplit:0}]});o.columns=[{width:20},{width:12.5},{width:15.38},{width:16.25},{width:17},{width:25.5},{width:24.88},{width:50},{width:41},{width:12},{width:13.5},{width:15.88},{width:13.5},{width:16.5},{width:23},{width:29}],bN(e,o,`${n.screen.toUpperCase()} TESTCASE`,a);for(let e=5;e<=8;e++)o.getRow(e);for(let[e,t]of Object.entries({C:`Total TCs`,D:`Passed`,E:`Failed`,F:`Pending`,G:`N/A`,H:`Remaining`})){let n=o.getCell(`${e}6`);n.value=t,n.font={bold:!0,size:10,color:mN,name:pN},n.fill={type:`pattern`,pattern:`solid`,fgColor:gN},n.alignment={horizontal:`center`,vertical:`top`},n.border=vN}let s=1e4,c={C:{formula:`COUNTA(A14:A${s})`,result:n.total},D:{formula:`COUNTIF(L14:L${s},"Passed")`,result:n.passed},E:{formula:`COUNTIF(L14:L${s},"Failed")`,result:n.failed},F:{formula:`COUNTIF(L14:L${s},"Pending")`,result:n.pending},G:{formula:`COUNTIF(L14:L${s},"N/A")`,result:n.na},H:{formula:`C7-D7-G7`,result:n.failed+n.pending}};for(let[e,t]of Object.entries(c)){let n=o.getCell(`${e}7`);n.value=t,n.font={size:10,color:mN,name:pN},n.fill={type:`pattern`,pattern:`solid`,fgColor:hN},n.alignment={horizontal:`center`,vertical:`top`},n.border=vN,e===`H`&&(n.numFmt=`#,##0`)}let l=o.getCell(`C8`);l.font={size:10,color:mN,name:pN},l.fill={type:`pattern`,pattern:`solid`,fgColor:hN},l.alignment={horizontal:`center`,vertical:`top`},l.border=vN;let u={D:{formula:`IFERROR(D7/(C7-G7),0)`,result:n.passed/Math.max(1,n.total-n.na)},E:{formula:`IFERROR(E7/(C7-G7),0)`,result:n.failed/Math.max(1,n.total-n.na)},F:{formula:`IFERROR(F7/(C7-G7),0)`,result:n.pending/Math.max(1,n.total-n.na)},G:{formula:`IFERROR(G7/(C7-G7),0)`,result:n.na/Math.max(1,n.total-n.na)},H:{formula:`IFERROR(H7/(C7-G7),0)`,result:(n.failed+n.pending)/Math.max(1,n.total-n.na)}};for(let[e,t]of Object.entries(u)){let n=o.getCell(`${e}8`);n.value=t,n.font={size:10,color:mN,name:pN},n.fill={type:`pattern`,pattern:`solid`,fgColor:hN},n.alignment={horizontal:`center`,vertical:`top`},n.border=vN,n.numFmt=`0%`}for(;o.rowCount<9;)o.addRow([]);let d=o.addRow([`Spec/Design link:`,i]);d.getCell(1).font={bold:!0},d.getCell(2).font={color:{argb:`FF1F4E78`},underline:!0},o.addRow([]);let f=o.addRow([`*: Mandatory`]);f.getCell(1).font={italic:!0,color:{argb:`FF808080`}};let p=o.addRow([`TC ID*`,`Screen/Function`,`Big item`,`Medium item`,`Test Object`,`Pre-condition`,`Test Data`,`Steps*`,`Expected results*`,`Priority`,`Testcase type`,`Test Result*`,`Executed Date*`,`Test Executor*`,`Test Environment`,`Note
120
- (Test evidence, DefectID, Actual result)`]);p.height=38,p.eachCell(e=>{e.font={bold:!0,color:{argb:lN}},e.fill={type:`pattern`,pattern:`solid`,fgColor:{argb:cN}},e.alignment={horizontal:`center`,vertical:`middle`,wrapText:!0},yN(e)}),o.views=[{state:`normal`}];let m=[`Accessing`,`GUI`,`Function`],h=new Map;for(let e of r){let t=h.get(e.category2)||[];t.push(e),h.set(e.category2,t)}let g=new Set;function _(e,t){let n=o.addRow([]);n.getCell(2).value=e,n.getCell(2).font={bold:!0},n.eachCell({includeEmpty:!1},e=>{e.fill={type:`pattern`,pattern:`solid`,fgColor:{argb:uN}}});for(let e=1;e<=16;e++){let t=n.getCell(e);t.fill={type:`pattern`,pattern:`solid`,fgColor:{argb:uN}},yN(t)}for(let e of t){let t=o.addRow([e.tcId,``,``,``,e.category1,e.precondition,e.testData,e.steps,e.expectedResults,e.priority,e.testcaseType,e.testResult,e.executedDate,e.testExecutor,e.testEnvironment,e.note]);t.alignment={vertical:`top`,wrapText:!0},t.eachCell({includeEmpty:!0},(e,t)=>{if(yN(e),t===1&&(e.font={bold:!0}),t===12){let t=String(e.value||``);t===`Passed`?e.font={color:{argb:`FF107C41`},bold:!0}:t===`Failed`?e.font={color:{argb:`FFC00000`},bold:!0}:t===`Pending`?e.font={color:{argb:`FF996D00`},bold:!0}:t===`N/A`&&(e.font={color:{argb:`FF808080`}})}})}}for(let e of m){let t=h.get(e);!t||t.length===0||(g.add(e),_(e,t))}for(let[e,t]of h.entries())g.has(e)||_(e,t);o.autoFilter={from:{row:p.number,column:1},to:{row:o.rowCount,column:16}},o.sheetProtection={sheet:!0,objects:!1,scenarios:!1}}var wN=`FF2E5984`,TN=`FFFFFFFF`;function EN(e,t){let n=e.addWorksheet(`API detail`),r=`Arial`;n.columns=[{header:`Endpoint`,width:35},{header:`Method`,width:10},{header:`Auth / Datasource`,width:22},{header:`Request shape`,width:40},{header:`Expected-status matrix`,width:30},{header:`Flow steps`,width:40},{header:`Concurrency invariant`,width:35}];let i=n.getRow(1);i.height=30,i.eachCell(e=>{e.font={bold:!0,color:{argb:TN},name:r},e.fill={type:`pattern`,pattern:`solid`,fgColor:{argb:wN}},e.alignment={horizontal:`center`,vertical:`middle`,wrapText:!0},yN(e)}),n.views=[{state:`frozen`,ySplit:1}];for(let e of t){let t=n.addRow([e.endpoint,e.method,e.authDatasource,e.requestShape,e.expectedStatusMatrix,e.flowSteps,e.concurrencyInvariant]);t.alignment={vertical:`top`,wrapText:!0},t.eachCell({includeEmpty:!0},e=>{yN(e),e.font={name:r}})}n.autoFilter={from:{row:1,column:1},to:{row:n.rowCount,column:7}}}function DN(e,t){if(e.length===0)throw Error(`renderXlsxMultiSheet requires at least one locale sheet`);let n=new oN.default.Workbook;n.creator=`sungen delivery`,n.created=new Date,SN(n,e,t?.sungenVersion);for(let r of e)CN(n,r.sheetName,r.summary,r.rows,r.specLink,t?.sungenVersion);return n}function ON(e){let t=e.features??[],n=e=>e.platform?`${e.featureBaseName}.${e.platform}`:e.featureBaseName;if(t.length>1)return t.map(t=>({basename:n(t),featureName:t.featureName,specLink:t.specLink??e.specLink,summary:t.summary,scenarios:t.scenarios,locales:t.locales,apiDetail:t.apiDetail}));let r=t[0];return[{basename:r?n(r):e.name,featureName:r?.featureName??e.featureName,specLink:r?.specLink??e.specLink,summary:r?.summary??e.summary,scenarios:r?.scenarios??e.scenarios,locales:r?.locales,apiDetail:r?.apiDetail}]}var kN=420*60*1e3;function AN(e,t){let n=IN(e,t.screens),r=VN();for(let t of n)for(let n of ON(t)){let t=n.locales&&n.locales.length>=2?n.locales.map(e=>({locale:e.locale,summary:e.summary,scenarios:e.scenarios})):[{locale:``,summary:n.summary,scenarios:n.scenarios}];for(let i of t){let t=jN(n,e.environment.sungenVersion,i.summary,i.scenarios),a=i.locale?`.${i.locale}`:``;WN(`${n.basename}-testcases${a}_${r}.csv`,t,`text/csv;charset=utf-8`)}}}function jN(e,t,n,r){let i=BN(),a=n??e.summary,o=r??e.scenarios,s=a.total||1,c=e=>`${Math.round(e/s*100)}%`,l=`${e.basename.toUpperCase()} TESTCASE`,u=e.specLink??``,d=[];d.push(UN([``,``,``,l,``,``,`No: BM-2-901-13`,``,``,``,``,``,``,``,``,``])),d.push(UN([``,``,``,``,``,``,`Version: ${t}`,``,``,``,``,``,``,``,``,``])),d.push(UN([``,``,``,``,``,``,`Issue Date: ${i}`,``,``,``,``,``,``,``,``,``])),d.push(UN([`SUN ASTERISK VIETNAM CO., LTD`,``,``,``,``,``,`ISO/IEC 27001:2022 & ISO 9001:2015`,``,``,``,``,``,``,``,``,``])),d.push(UN([``,``,``,``,``,``,``,``,``,``,``,``,``,``,``,``])),d.push(UN([``,``,`Total TCs`,`Passed`,`Failed`,`Pending`,`N/A`,`Remaining`,``,``,``,``,``,``,``,``])),d.push(UN([``,``,a.total,a.passed,a.failed,a.pending,a.na,a.pending+a.na,``,``,``,``,``,``,``,``])),d.push(UN([``,``,``,c(a.passed),c(a.failed),c(a.pending),c(a.na),c(a.pending+a.na),``,``,``,``,``,``,``,``])),d.push(UN([``,``,``,``,``,``,``,``,``,``,``,``,``,``,``,``])),d.push(UN([`Spec/Design link:`,u,``,``,``,``,``,``,``,``,``,``,``,``,``,``])),d.push(UN([``,``,``,``,``,``,``,``,``,``,``,``,``,``,``,``])),d.push(UN([`*: Mandatory`,``,``,``,``,``,``,``,``,``,``,``,``,``,``,``])),d.push(UN([`TC ID*`,`Screen/Function`,`Big item`,`Medium item`,`Test Object`,`Pre-condition`,`Test Data`,`Steps*`,`Expected results*`,`Priority`,`Testcase type`,`Test Result*`,`Executed Date*`,`Test Executor*`,`Test Environment`,`Note
120
+ (Test evidence, DefectID, Actual result)`]);p.height=38,p.eachCell(e=>{e.font={bold:!0,color:{argb:lN}},e.fill={type:`pattern`,pattern:`solid`,fgColor:{argb:cN}},e.alignment={horizontal:`center`,vertical:`middle`,wrapText:!0},yN(e)}),o.views=[{state:`normal`}];let m=[`Basic Flow`,`Alternate Flow`,`Exception Flow`,`Accessing`,`GUI`,`Function`],h=new Map;for(let e of r){let t=h.get(e.category2)||[];t.push(e),h.set(e.category2,t)}let g=new Set;function _(e,t){let n=o.addRow([]);n.getCell(2).value=e,n.getCell(2).font={bold:!0},n.eachCell({includeEmpty:!1},e=>{e.fill={type:`pattern`,pattern:`solid`,fgColor:{argb:uN}}});for(let e=1;e<=16;e++){let t=n.getCell(e);t.fill={type:`pattern`,pattern:`solid`,fgColor:{argb:uN}},yN(t)}for(let e of t){let t=o.addRow([e.tcId,``,``,``,e.category1,e.precondition,e.testData,e.steps,e.expectedResults,e.priority,e.testcaseType,e.testResult,e.executedDate,e.testExecutor,e.testEnvironment,e.note]);t.alignment={vertical:`top`,wrapText:!0},t.eachCell({includeEmpty:!0},(e,t)=>{if(yN(e),t===1&&(e.font={bold:!0}),t===12){let t=String(e.value||``);t===`Passed`?e.font={color:{argb:`FF107C41`},bold:!0}:t===`Failed`?e.font={color:{argb:`FFC00000`},bold:!0}:t===`Pending`?e.font={color:{argb:`FF996D00`},bold:!0}:t===`N/A`&&(e.font={color:{argb:`FF808080`}})}})}}for(let e of m){let t=h.get(e);!t||t.length===0||(g.add(e),_(e,t))}for(let[e,t]of h.entries())g.has(e)||_(e,t);o.autoFilter={from:{row:p.number,column:1},to:{row:o.rowCount,column:16}},o.sheetProtection={sheet:!0,objects:!1,scenarios:!1}}var wN=`FF2E5984`,TN=`FFFFFFFF`;function EN(e,t){let n=e.addWorksheet(`API detail`),r=`Arial`;n.columns=[{header:`Endpoint`,width:35},{header:`Method`,width:10},{header:`Auth / Datasource`,width:22},{header:`Request shape`,width:40},{header:`Expected-status matrix`,width:30},{header:`Flow steps`,width:40},{header:`Concurrency invariant`,width:35}];let i=n.getRow(1);i.height=30,i.eachCell(e=>{e.font={bold:!0,color:{argb:TN},name:r},e.fill={type:`pattern`,pattern:`solid`,fgColor:{argb:wN}},e.alignment={horizontal:`center`,vertical:`middle`,wrapText:!0},yN(e)}),n.views=[{state:`frozen`,ySplit:1}];for(let e of t){let t=n.addRow([e.endpoint,e.method,e.authDatasource,e.requestShape,e.expectedStatusMatrix,e.flowSteps,e.concurrencyInvariant]);t.alignment={vertical:`top`,wrapText:!0},t.eachCell({includeEmpty:!0},e=>{yN(e),e.font={name:r}})}n.autoFilter={from:{row:1,column:1},to:{row:n.rowCount,column:7}}}function DN(e,t){if(e.length===0)throw Error(`renderXlsxMultiSheet requires at least one locale sheet`);let n=new oN.default.Workbook;n.creator=`sungen delivery`,n.created=new Date,SN(n,e,t?.sungenVersion);for(let r of e)CN(n,r.sheetName,r.summary,r.rows,r.specLink,t?.sungenVersion);return n}function ON(e){let t=e.features??[],n=e=>e.platform?`${e.featureBaseName}.${e.platform}`:e.featureBaseName;if(t.length>1)return t.map(t=>({basename:n(t),featureName:t.featureName,specLink:t.specLink??e.specLink,summary:t.summary,scenarios:t.scenarios,locales:t.locales,apiDetail:t.apiDetail}));let r=t[0];return[{basename:r?n(r):e.name,featureName:r?.featureName??e.featureName,specLink:r?.specLink??e.specLink,summary:r?.summary??e.summary,scenarios:r?.scenarios??e.scenarios,locales:r?.locales,apiDetail:r?.apiDetail}]}var kN=420*60*1e3;function AN(e,t){let n=IN(e,t.screens),r=VN();for(let t of n)for(let n of ON(t)){let t=n.locales&&n.locales.length>=2?n.locales.map(e=>({locale:e.locale,summary:e.summary,scenarios:e.scenarios})):[{locale:``,summary:n.summary,scenarios:n.scenarios}];for(let i of t){let t=jN(n,e.environment.sungenVersion,i.summary,i.scenarios),a=i.locale?`.${i.locale}`:``;WN(`${n.basename}-testcases${a}_${r}.csv`,t,`text/csv;charset=utf-8`)}}}function jN(e,t,n,r){let i=BN(),a=n??e.summary,o=r??e.scenarios,s=a.total||1,c=e=>`${Math.round(e/s*100)}%`,l=`${e.basename.toUpperCase()} TESTCASE`,u=e.specLink??``,d=[];d.push(UN([``,``,``,l,``,``,`No: BM-2-901-13`,``,``,``,``,``,``,``,``,``])),d.push(UN([``,``,``,``,``,``,`Version: ${t}`,``,``,``,``,``,``,``,``,``])),d.push(UN([``,``,``,``,``,``,`Issue Date: ${i}`,``,``,``,``,``,``,``,``,``])),d.push(UN([`SUN ASTERISK VIETNAM CO., LTD`,``,``,``,``,``,`ISO/IEC 27001:2022 & ISO 9001:2015`,``,``,``,``,``,``,``,``,``])),d.push(UN([``,``,``,``,``,``,``,``,``,``,``,``,``,``,``,``])),d.push(UN([``,``,`Total TCs`,`Passed`,`Failed`,`Pending`,`N/A`,`Remaining`,``,``,``,``,``,``,``,``])),d.push(UN([``,``,a.total,a.passed,a.failed,a.pending,a.na,a.pending+a.na,``,``,``,``,``,``,``,``])),d.push(UN([``,``,``,c(a.passed),c(a.failed),c(a.pending),c(a.na),c(a.pending+a.na),``,``,``,``,``,``,``,``])),d.push(UN([``,``,``,``,``,``,``,``,``,``,``,``,``,``,``,``])),d.push(UN([`Spec/Design link:`,u,``,``,``,``,``,``,``,``,``,``,``,``,``,``])),d.push(UN([``,``,``,``,``,``,``,``,``,``,``,``,``,``,``,``])),d.push(UN([`*: Mandatory`,``,``,``,``,``,``,``,``,``,``,``,``,``,``,``])),d.push(UN([`TC ID*`,`Screen/Function`,`Big item`,`Medium item`,`Test Object`,`Pre-condition`,`Test Data`,`Steps*`,`Expected results*`,`Priority`,`Testcase type`,`Test Result*`,`Executed Date*`,`Test Executor*`,`Test Environment`,`Note
121
121
  (Test evidence, DefectID, Actual result)`]));let f=[`Accessing`,`GUI`,`Function`],p=new Map;for(let e of o){let t=p.get(e.category2)||[];t.push(e),p.set(e.category2,t)}let m=new Set,h=(e,t)=>{d.push(UN([``,e,``,``,``,``,``,``,``,``,``,``,``,``,``,``]));for(let e of t)d.push(UN(LN(e)))};for(let e of f){let t=p.get(e);!t||t.length===0||(m.add(e),h(e,t))}for(let[e,t]of p.entries())m.has(e)||h(e,t);return``+d.join(`
122
122
  `)+`
123
123
  `}async function MN(e,t){let n=IN(e,t.screens),r=VN();for(let t of n)for(let n of ON(t)){let t=DN(FN(n),{sungenVersion:e.environment.sungenVersion});n.apiDetail&&n.apiDetail.length>0&&EN(t,n.apiDetail);let i=await KN(await t.xlsx.writeBuffer()),a=new Blob([i.buffer],{type:`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`}),o=URL.createObjectURL(a);GN(`${n.basename}-testcases_${r}.xlsx`,o),setTimeout(()=>URL.revokeObjectURL(o),1e3)}}function NN(e){let t={total:e.length,passed:0,failed:0,pending:0,na:0,notCompiled:0,passRate:0};for(let n of e)n.status===`Passed`?t.passed++:n.status===`Failed`?t.failed++:n.status===`Pending`?t.pending++:n.status===`N/A`&&t.na++,n.type===`Not compiled`&&t.notCompiled++;let n=t.passed+t.failed;return t.passRate=n>0?t.passed/n:0,t}function PN(e){return{tcId:e.tcId,category1:e.category1,category2:e.category2,precondition:e.precondition,testData:e.testData,steps:RN(e.steps),expectedResults:RN(e.expectedResults),priority:e.priority,testcaseType:e.type,testResult:e.status,executedDate:e.executedDate??``,testExecutor:e.testExecutor??``,testEnvironment:e.testEnvironment??``,note:zN(e)}}function FN(e){let t=e.specLink??``,n=(n,r)=>{let i=NN(r);return{sheetName:n,summary:{screen:e.basename,total:i.total,passed:i.passed,failed:i.failed,pending:i.pending,na:i.na},rows:r.map(PN),specLink:t}},r=[];if(e.locales&&e.locales.length>=2){let t=[];for(let i of e.locales){let e=i.scenarios.filter(e=>e.type!==`Manual`);r.push(n(`${i.displayCode} Auto`,e)),i.locale===``&&(t=i.scenarios.filter(e=>e.type===`Manual`))}t.length===0&&e.locales[0]&&(t=e.locales[0].scenarios.filter(e=>e.type===`Manual`)),r.push(n(`Manual`,t))}else{let t=e.scenarios.filter(e=>e.type!==`Manual`),i=e.scenarios.filter(e=>e.type===`Manual`);r.push(n(`Auto`,t)),r.push(n(`Manual`,i))}return r}function IN(e,t){if(!t||t.length===0)return e.screens;let n=new Set(t);return e.screens.filter(e=>n.has(e.name))}function LN(e){return[e.tcId,``,``,``,e.category1,e.precondition,e.testData,RN(e.steps),RN(e.expectedResults),e.priority,e.type,e.status,e.executedDate??``,e.testExecutor??``,e.testEnvironment??``,zN(e)]}function RN(e){return!e||e.length===0?``:e.length===1?e[0]:e.map((e,t)=>`${t+1}. ${e}`).join(`
@@ -353,7 +353,9 @@ export function renderCsv(summary: ScreenSummary, rows: TestCaseRow[], specLink:
353
353
  ]));
354
354
 
355
355
  // Group rows by Category 2 (in stable order)
356
- const order = ['Accessing', 'GUI', 'Function'];
356
+ // Flow phases lead, in use-case order (the basic flow first, then its branches, then the
357
+ // blocked paths), so a flow deliverable reads as the journey it tests (#592).
358
+ const order = ['Basic Flow', 'Alternate Flow', 'Exception Flow', 'Accessing', 'GUI', 'Function'];
357
359
  const grouped = new Map<string, TestCaseRow[]>();
358
360
  for (const row of rows) {
359
361
  const g = grouped.get(row.category2) || [];
@@ -186,9 +186,31 @@ export function splitVpAndName(scenarioName: string): { vpId?: string; category1
186
186
  * API auth viewpoints (missing/invalid/insufficient credentials) are access-control
187
187
  * tests → Accessing; every other API viewpoint (contract, error, idempotency, flow,
188
188
  * async, and the numbered baseline ids) is functional → Function.
189
+ *
190
+ * Flow ids carry the use-case decomposition (#592) and group by it, so the deliverable shows
191
+ * the reviewer WHICH flow each case belongs to instead of collapsing a whole journey into one
192
+ * "Function" block: `BF` → Basic Flow, `AF0n` → Alternate Flow, `EF0n` → Exception Flow. The
193
+ * flat scheme keeps its old mapping (`HP`/`ER` functional, `EH` guards → Accessing).
194
+ * The phase may sit anywhere in the id: a project whose viewpoint declares themes writes a
195
+ * COMPOUND id (`VP-SEC-EF02-001`) carrying theme AND phase (#595).
189
196
  */
190
197
  export function mapVpToCategory2(vpId: string | undefined, scenarioName?: string): string {
191
198
  if (!vpId) return 'Function';
199
+ // The phase segment, wherever it sits in the id. A project whose viewpoint declares themes
200
+ // uses a COMPOUND id — `VP-SEC-EF02-001` carries both the theme (traceability) and the phase
201
+ // (flow) — and anchoring on `FL-` missed every one of them, so a use-case suite still grouped
202
+ // as Accessing/Function (#595). The phase wins over the theme here: a flow deliverable is read
203
+ // flow by flow.
204
+ const flow = vpId.match(/(?:^|-)(?:FL-)?(BF|AF|EF|HP|ER|EH|UI)\d*(?=-|$)/i);
205
+ if (flow) {
206
+ const phase = flow[1].toUpperCase();
207
+ if (phase === 'BF') return 'Basic Flow';
208
+ if (phase === 'AF') return 'Alternate Flow';
209
+ if (phase === 'EF') return 'Exception Flow';
210
+ if (phase === 'EH') return 'Accessing';
211
+ if (phase === 'UI') return 'GUI';
212
+ return 'Function'; // HP / ER — the flat scheme
213
+ }
192
214
  // Compare on the category segment so a project scheme without the `VP-` prefix
193
215
  // (`SEC-123`) classifies the same as `VP-SEC-001`.
194
216
  vpId = /^VP-/.test(vpId) ? vpId : `VP-${vpId}`;
@@ -501,7 +501,9 @@ export function addTestcaseSheet(
501
501
  ws.views = [{ state: 'normal' }];
502
502
 
503
503
  // -- Data rows grouped by category --
504
- const order = ['Accessing', 'GUI', 'Function'];
504
+ // Flow phases lead, in use-case order (the basic flow first, then its branches, then the
505
+ // blocked paths), so a flow deliverable reads as the journey it tests (#592).
506
+ const order = ['Basic Flow', 'Alternate Flow', 'Exception Flow', 'Accessing', 'GUI', 'Function'];
505
507
  const grouped = new Map<string, BuilderRow[]>();
506
508
  for (const r of rows) {
507
509
  const g = grouped.get(r.category2) || [];