@sun-asterisk/sungen 3.2.20-beta.1 → 3.2.20-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.
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +8 -0
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +7 -0
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/exporters/matrix/export.d.ts.map +1 -1
- package/dist/exporters/matrix/export.js +11 -0
- package/dist/exporters/matrix/export.js.map +1 -1
- package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
- package/dist/exporters/matrix/render-xlsx.js +15 -0
- package/dist/exporters/matrix/render-xlsx.js.map +1 -1
- package/dist/exporters/matrix/types.d.ts +2 -0
- package/dist/exporters/matrix/types.d.ts.map +1 -1
- package/dist/exporters/matrix/types.js.map +1 -1
- package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
- package/dist/exporters/playwright-report-parser.js +1 -0
- package/dist/exporters/playwright-report-parser.js.map +1 -1
- package/dist/exporters/types.d.ts +2 -0
- package/dist/exporters/types.d.ts.map +1 -1
- package/dist/harness/audit.d.ts +2 -0
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +79 -9
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/flow-contract.d.ts +71 -0
- package/dist/harness/flow-contract.d.ts.map +1 -0
- package/dist/harness/flow-contract.js +235 -0
- package/dist/harness/flow-contract.js.map +1 -0
- package/dist/harness/flow-plan.d.ts +3 -0
- package/dist/harness/flow-plan.d.ts.map +1 -1
- package/dist/harness/flow-plan.js +6 -2
- package/dist/harness/flow-plan.js.map +1 -1
- package/dist/harness/parse.d.ts +5 -0
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +29 -1
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/perf.d.ts +40 -0
- package/dist/harness/perf.d.ts.map +1 -0
- package/dist/harness/perf.js +136 -0
- package/dist/harness/perf.js.map +1 -0
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +13 -1
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/spec-coverage.d.ts.map +1 -1
- package/dist/harness/spec-coverage.js +22 -3
- package/dist/harness/spec-coverage.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/add-flow.md +41 -3
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +35 -16
- package/dist/orchestrator/templates/qa-context.md +14 -1
- package/package.json +3 -3
- package/src/cli/commands/audit.ts +8 -0
- package/src/cli/commands/delivery.ts +6 -0
- package/src/exporters/matrix/export.ts +11 -0
- package/src/exporters/matrix/render-xlsx.ts +15 -0
- package/src/exporters/matrix/types.ts +2 -0
- package/src/exporters/playwright-report-parser.ts +2 -0
- package/src/exporters/types.ts +2 -0
- package/src/harness/audit.ts +82 -10
- package/src/harness/flow-contract.ts +229 -0
- package/src/harness/flow-plan.ts +10 -3
- package/src/harness/parse.ts +31 -1
- package/src/harness/perf.ts +112 -0
- package/src/harness/sensors.ts +13 -1
- package/src/harness/spec-coverage.ts +19 -2
- package/src/orchestrator/templates/ai-src/commands/add-flow.md +41 -3
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +35 -16
- package/src/orchestrator/templates/qa-context.md +14 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spec-coverage.js","sourceRoot":"","sources":["../../src/harness/spec-coverage.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgEA,
|
|
1
|
+
{"version":3,"file":"spec-coverage.js","sourceRoot":"","sources":["../../src/harness/spec-coverage.ts"],"names":[],"mappings":"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"}
|
|
@@ -86,15 +86,52 @@ qa/flows/${input:flow}/
|
|
|
86
86
|
└── ui/ # Screenshots, mockups
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
### 1a.
|
|
89
|
+
### 1a. Define the flow's BOUNDARY, then its screens
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
A flow is the **smallest complete business action chain**: one clear trigger ending in ONE
|
|
92
|
+
observable, valuable outcome. Before asking for screens, walk this checklist with the user —
|
|
93
|
+
if 1, 3 or 8 fails, propose SPLITTING into separate flows:
|
|
94
|
+
|
|
95
|
+
1. Exactly **one business goal**? (cart correctness + category filtering = two flows)
|
|
96
|
+
2. A clear **trigger** and precondition?
|
|
97
|
+
3. **One observable final outcome**? (a final assertion you can write in one sentence)
|
|
98
|
+
4. Is that outcome **valuable to the actor**? (an order placed, a password reset — not "a page rendered")
|
|
99
|
+
5. Is **every step necessary** for that outcome?
|
|
100
|
+
6. Are all steps at the **same business abstraction**?
|
|
101
|
+
7. Are optional/error branches **phases of this goal** (ER/EH), not new goals?
|
|
102
|
+
8. Does **no segment** form an independently valuable flow on its own?
|
|
103
|
+
9. Can you write **a single clear final assertion**?
|
|
104
|
+
10. Can you name it "**Verb + outcome**"? (`place-order`, `reset-password` — not `cart-and-filter`)
|
|
105
|
+
|
|
106
|
+
Then ask: "Which screens does this flow visit, in order? (e.g., login → dashboard → award-form → confirmation)"
|
|
92
107
|
|
|
93
108
|
Record the screen list — you will need it for:
|
|
94
109
|
- Filling `spec.md` (Step 3)
|
|
95
110
|
- Suggesting `[Screen:Element]` namespace prefixes
|
|
96
111
|
- Capturing visuals per screen (Step 2)
|
|
97
112
|
|
|
113
|
+
### 1b. Author the Flow Contract (`requirements/flow-contract.yaml`)
|
|
114
|
+
|
|
115
|
+
Write the answers down as the flow's contract — `sungen audit` scores the flow **against it**
|
|
116
|
+
(the `flowCoverage` axis: HP/ER/EH journey phases; `FLOW-OUTCOME-UNPROVEN` when no automated
|
|
117
|
+
scenario asserts data on the outcome screen; `FLOW-SCOPE-CREEP` when scenarios never touch it):
|
|
118
|
+
|
|
119
|
+
```yaml
|
|
120
|
+
goal: "Place an order for a product added from home" # Verb + outcome
|
|
121
|
+
actor: user
|
|
122
|
+
trigger: "Add a product to the cart from the home featured list"
|
|
123
|
+
precondition: "A registered account; an empty cart"
|
|
124
|
+
outcome:
|
|
125
|
+
screen: checkout # the [Screen:...] namespace carrying the final proof
|
|
126
|
+
assertion: "The confirmation shows the order number and the paid total"
|
|
127
|
+
value: "The customer has paid; the shop has a new order"
|
|
128
|
+
phases: [HP, ER, EH] # journey phases (default); add UI only if the flow owns UI states
|
|
129
|
+
stateful: cart # the mutated collection, if any — enables regression-depth dims
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**A filled contract is an INPUT to generation — never an output.** Like `test-viewpoint.md`,
|
|
133
|
+
generation must not rewrite it to match what was generated; disagree → propose the diff and ask.
|
|
134
|
+
|
|
98
135
|
### 2. Capture visual source
|
|
99
136
|
|
|
100
137
|
**Mobile path** (`platform: mobile`):
|
|
@@ -187,7 +224,8 @@ If user picks `/sungen:create-test`, **you MUST use the Skill tool** to invoke i
|
|
|
187
224
|
- Test data namespaced by phase: `login.email`, `submission.nominee`
|
|
188
225
|
- `@flow` tag required at feature level
|
|
189
226
|
- `Background:` should only contain the starting navigation — the URL path (web) or the `--reach` nav recipe (mobile)
|
|
190
|
-
- Each scenario = one phase of the journey
|
|
227
|
+
- Each scenario = one phase of the journey; ids are `FL-<PHASE>-NNN` (`HP`/`ER`/`EH`, optional `UI`)
|
|
228
|
+
- One flow = ONE business goal with ONE observable outcome (`requirements/flow-contract.yaml`) — a segment with its own value is its own flow
|
|
191
229
|
{{#cap parallel-subagents}}
|
|
192
230
|
- Mobile flows are tagged `@platform:mobile` and run via `/sungen:run-test <flow>` (WebdriverIO, not Playwright)
|
|
193
231
|
{{/cap}}
|
|
@@ -605,22 +605,40 @@ error:
|
|
|
605
605
|
|
|
606
606
|
> **Auto-detect**: if path is `qa/flows/<name>/` → use this section. Skip Steps 1–4 above.
|
|
607
607
|
|
|
608
|
+
**Read `requirements/flow-contract.yaml` FIRST — it is the flow's boundary and the yardstick
|
|
609
|
+
`sungen audit` scores the flow against** (`flowCoverage` axis = journey phases HP/ER/EH automated;
|
|
610
|
+
`FLOW-OUTCOME-UNPROVEN`; `FLOW-SCOPE-CREEP`). No contract yet → author it with the user via the
|
|
611
|
+
boundary checklist in `add-flow` (one business goal · clear trigger · ONE observable outcome
|
|
612
|
+
valuable to the actor · name = "Verb + outcome"), THEN generate. **A filled contract is an INPUT —
|
|
613
|
+
never rewrite it to match your output** (same rule as `test-viewpoint.md`).
|
|
614
|
+
|
|
608
615
|
| Aspect | Screen | Flow |
|
|
609
616
|
|---|---|---|
|
|
610
|
-
| Section focus | UI patterns per section | Journey phases
|
|
617
|
+
| Section focus | UI patterns per section | Journey phases toward ONE declared outcome |
|
|
611
618
|
| Selector format | `[Element]` | `[Screen:Element]` (namespaced) |
|
|
612
619
|
| Test data keys | `{{variable}}` | `{{phase.variable}}` |
|
|
613
620
|
| Feature tag | `@auto` / `@smoke` etc. | `@flow` (required) |
|
|
614
|
-
|
|
|
621
|
+
| Scenario ids | `VP-<CATEGORY>-NNN` | `FL-<PHASE>-NNN` — phases: `HP` (happy path), `ER` (error recovery), `EH` (guards), `UI` (journey UI states, optional) |
|
|
615
622
|
|
|
616
|
-
**Scenarios to generate:**
|
|
623
|
+
**Scenarios to generate — every phase demanded by the contract, automated:**
|
|
617
624
|
|
|
618
|
-
|
|
|
619
|
-
|
|
620
|
-
|
|
|
621
|
-
|
|
|
622
|
-
|
|
|
623
|
-
| Cross-screen
|
|
625
|
+
| Phase | What to test | Scoring |
|
|
626
|
+
|---|---|---|
|
|
627
|
+
| `FL-HP` happy path | The complete journey ending in the contract's `outcome.assertion` — an AUTOMATED **data** assertion on `outcome.screen` (an order number, a summed total — not just "page visible"). This scenario is WHY the flow exists: it is the regression proof after a lib/framework upgrade. | uncovered → `flowCoverage` drops + `FLOW-OUTCOME-UNPROVEN` |
|
|
628
|
+
| `FL-ER` error recovery | Invalid input mid-flow → error shown → fix → the journey still completes. Validation must not trap the journey. | uncovered → `flowCoverage` drops |
|
|
629
|
+
| `FL-EH` guards | Direct URL access without the precondition · browser back · refresh · expired context — each ends in a safe observable state. | uncovered → `flowCoverage` drops |
|
|
630
|
+
| 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`) |
|
|
631
|
+
| Stateful regression (when `stateful:` declared) | Count/quantity proof · teardown (remove → empty) · multi-source add. | missing dims cap `businessDepth` (`FLOW-DEPTH`) |
|
|
632
|
+
|
|
633
|
+
**Boundary discipline while generating:** every scenario must serve the contract's goal. A scenario
|
|
634
|
+
that never touches `outcome.screen` and is not a guard (`EH`) or error-recovery (`ER`) belongs in a
|
|
635
|
+
DIFFERENT flow — propose the split instead of writing it here (`FLOW-SCOPE-CREEP` will flag it).
|
|
636
|
+
Auth persistence across transitions is part of `EH` unless the project declares it its own phase.
|
|
637
|
+
|
|
638
|
+
**Manual in flows**: always `@manual:Mx` with the reason code — bare `@manual` is flagged
|
|
639
|
+
(`MANUAL-CODE-MISSING`) because the capability planner cannot route it. Typical flow deferrals:
|
|
640
|
+
inbox/mail oracle → `M5`, network-request count → `M3`, context expiry control → `M7`. A
|
|
641
|
+
cross-screen scenario inside the flow's own goal is NOT manual — automate it here.
|
|
624
642
|
|
|
625
643
|
```gherkin
|
|
626
644
|
@flow @auth:user
|
|
@@ -630,19 +648,20 @@ Feature: Award Submission Flow
|
|
|
630
648
|
Given User is on [Login] page
|
|
631
649
|
|
|
632
650
|
@high
|
|
633
|
-
Scenario:
|
|
651
|
+
Scenario: FL-HP-001 A signed-in user's nomination is submitted and confirmed
|
|
634
652
|
When User fill [Login:Email] field with {{login.email}}
|
|
635
653
|
And User fill [Login:Password] field with {{login.password}}
|
|
636
654
|
And User click [Login:Submit] button
|
|
637
655
|
Then User see [Dashboard] page
|
|
638
|
-
|
|
639
|
-
@high
|
|
640
|
-
Scenario: User submits nomination
|
|
641
656
|
When User click [Dashboard:Awards] link
|
|
642
|
-
|
|
643
|
-
When User fill [Awards:Nominee] field with {{submission.nominee}}
|
|
657
|
+
And User fill [Awards:Nominee] field with {{submission.nominee}}
|
|
644
658
|
And User click [Awards:Submit] button
|
|
645
|
-
Then User see {{success_message}}
|
|
659
|
+
Then User see [Awards:Success Message] text with {{success_message}}
|
|
660
|
+
|
|
661
|
+
@high
|
|
662
|
+
Scenario: FL-EH-001 Direct access to the award form without login redirects to login
|
|
663
|
+
When User go to [Awards] page
|
|
664
|
+
Then User see [Login] page
|
|
646
665
|
```
|
|
647
666
|
|
|
648
667
|
```yaml
|
|
@@ -45,8 +45,21 @@ Example:
|
|
|
45
45
|
|
|
46
46
|
## Testing Strategy
|
|
47
47
|
|
|
48
|
+
Machine-readable intent — `sungen audit` reads these keys (Intent Profile). Values here are
|
|
49
|
+
live even when the surrounding text changes; an invalid value silently falls back to the default.
|
|
50
|
+
|
|
51
|
+
focus: functional
|
|
52
|
+
<!-- focus: functional | e-commerce | security | smoke — drives the audit's depth threshold -->
|
|
53
|
+
|
|
54
|
+
risk_tier: normal
|
|
55
|
+
<!-- risk_tier: high | normal | low -->
|
|
56
|
+
|
|
57
|
+
To silence driver suggestions in audit findings, add a line: capability_suggestions with value off.
|
|
58
|
+
|
|
48
59
|
**Focus areas** — what to cover thoroughly:
|
|
49
|
-
<!--
|
|
60
|
+
<!-- Prose for humans; the parseable value is the `focus:` key above.
|
|
61
|
+
Response-time budgets are NOT a focus value — declare them in qa/perf.yaml
|
|
62
|
+
(percentile + scenario_ms budgets; reported by `sungen delivery`). -->
|
|
50
63
|
<!-- Example: functional, security -->
|
|
51
64
|
|
|
52
65
|
**Mandatory coverage:**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sun-asterisk/sungen",
|
|
3
|
-
"version": "3.2.20-beta.
|
|
3
|
+
"version": "3.2.20-beta.3",
|
|
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.20-beta.
|
|
43
|
-
"@sungen/driver-ui": "3.2.20-beta.
|
|
42
|
+
"@sungen/driver-data-factory": "3.2.20-beta.3",
|
|
43
|
+
"@sungen/driver-ui": "3.2.20-beta.3",
|
|
44
44
|
"chalk": "^5.6.2",
|
|
45
45
|
"commander": "^14.0.2",
|
|
46
46
|
"dotenv": "^17.2.3",
|
|
@@ -40,6 +40,14 @@ function render(r: AuditReport): void {
|
|
|
40
40
|
if (r.gate.gaps.length) for (const g of r.gate.gaps) L(` ✗ ${g.status === 'shallow' ? 'SHALLOW' : 'MISSING'}: ${g.theme}`);
|
|
41
41
|
else L(' ✓ all critical themes covered (with data assertions)');
|
|
42
42
|
L('');
|
|
43
|
+
if (r.flowQuality?.hasContract) {
|
|
44
|
+
const fq = r.flowQuality;
|
|
45
|
+
L(` ①F Flow contract — goal: ${fq.contract!.goal}`);
|
|
46
|
+
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)}%`);
|
|
48
|
+
L(` handoffs asserted: ${fq.handoffs.asserted}/${fq.handoffs.total}${fq.offGoal.length ? ` ⚠ off-goal scenarios: ${fq.offGoal.length} (${fq.offGoalCategories.join(', ')})` : ''}`);
|
|
49
|
+
L('');
|
|
50
|
+
}
|
|
43
51
|
L(` ② Assertion depth — ${r.depth.businessCriticalShallow}/${r.depth.businessCriticalTotal} business-critical scenarios are shallow (visibility/nav only)`);
|
|
44
52
|
for (const s of r.depth.shallowBusinessCritical.slice(0, 8)) L(` ⚠ ${s.category}: ${s.name}`);
|
|
45
53
|
if (r.depth.shallowBusinessCritical.length > 8) L(` … +${r.depth.shallowBusinessCritical.length - 8} more`);
|
|
@@ -688,6 +688,12 @@ async function exportMatrixTarget(
|
|
|
688
688
|
}
|
|
689
689
|
|
|
690
690
|
log(`${COLOR.bold}${paths.label}${COLOR.reset} — ${model.manifest.itemCount} delivery item(s), ${model.manifest.variantCount} coverage variant(s), state: ${model.manifest.approvalState}`);
|
|
691
|
+
if (model.perf) {
|
|
692
|
+
const p = model.perf;
|
|
693
|
+
const tone = p.pass ? COLOR.green : COLOR.yellow;
|
|
694
|
+
log(` ${tone}PERF ${p.pass ? 'PASS' : 'OVER BUDGET'}${COLOR.reset}: p${p.percentile} ${p.metric.replace('_ms', '')} ${p.measuredMs}ms ${p.pass ? '≤' : '>'} budget ${p.budgetMs}ms (${p.samples} run${p.samples === 1 ? '' : 's'})`);
|
|
695
|
+
for (const s of p.slowest) log(` ${COLOR.yellow}slowest:${COLOR.reset} ${s.ms}ms — ${s.title.slice(0, 90)}`);
|
|
696
|
+
}
|
|
691
697
|
printFindings(model.findings);
|
|
692
698
|
|
|
693
699
|
if (opts.check) {
|
|
@@ -16,6 +16,7 @@ import { mergeFeatureAndSpec } from '../scenario-merger';
|
|
|
16
16
|
import { getPackageVersion } from '../package-info';
|
|
17
17
|
import { writeCsv } from '../csv-exporter';
|
|
18
18
|
import { writeXlsx } from '../xlsx-exporter';
|
|
19
|
+
import { loadPerfConfig, perfVerdict } from '../../harness/perf';
|
|
19
20
|
import { loadDeliveryMap, writeDeliveryMap } from './map-loader';
|
|
20
21
|
import { mapContentFingerprint } from './fingerprint';
|
|
21
22
|
import { buildMatrix, deriveVariants } from './build';
|
|
@@ -75,6 +76,16 @@ export function loadMatrixModel(paths: MatrixTargetPaths): MatrixLoadResult {
|
|
|
75
76
|
transformerVersion: getPackageVersion(),
|
|
76
77
|
specText: readSpecText(paths),
|
|
77
78
|
});
|
|
79
|
+
// Perf verdict (#569): budgets are config (qa/perf.yaml), samples are the run's
|
|
80
|
+
// per-test durations. Advisory — reported here because delivery is where runs
|
|
81
|
+
// are read; the audit's score stays a pure function of the design artifacts.
|
|
82
|
+
const perfConfig = loadPerfConfig(paths.cwd);
|
|
83
|
+
if (perfConfig && results) {
|
|
84
|
+
const samples = [...results.values()]
|
|
85
|
+
.filter((r) => (r.status === 'passed' || r.status === 'failed' || r.status === 'timedOut') && typeof r.duration === 'number')
|
|
86
|
+
.map((r) => ({ title: r.testTitle, ms: r.duration as number }));
|
|
87
|
+
model.perf = perfVerdict(perfConfig, paths.unit, samples);
|
|
88
|
+
}
|
|
78
89
|
return { model, map, mapErrors: [] };
|
|
79
90
|
}
|
|
80
91
|
|
|
@@ -315,6 +315,21 @@ function addCoverageSheet(wb: ExcelJS.Workbook, model: MatrixModel, sungenVersio
|
|
|
315
315
|
|
|
316
316
|
let rowIdx = 7;
|
|
317
317
|
|
|
318
|
+
// --- Performance budget (#569) — advisory: config (qa/perf.yaml) vs measured runs.
|
|
319
|
+
if (model.perf) {
|
|
320
|
+
const p = model.perf;
|
|
321
|
+
dataCell(ws.getRow(rowIdx).getCell(2), 'Performance budget', { bold: true });
|
|
322
|
+
rowIdx++;
|
|
323
|
+
const r = ws.getRow(rowIdx);
|
|
324
|
+
dataCell(r.getCell(2), `p${p.percentile} ${p.metric.replace('_ms', ' time')}`);
|
|
325
|
+
dataCell(r.getCell(3), p.pass ? 'PASS' : 'OVER BUDGET', { bold: true });
|
|
326
|
+
dataCell(r.getCell(4), `measured ${p.measuredMs}ms`);
|
|
327
|
+
dataCell(r.getCell(5), `budget ${p.budgetMs}ms`);
|
|
328
|
+
dataCell(r.getCell(6), `${p.samples} runs`);
|
|
329
|
+
if (!p.pass && p.slowest.length) dataCell(r.getCell(8), `slowest: ${p.slowest.map((x) => `${x.ms}ms ${x.title.slice(0, 40)}`).join(' · ')}`);
|
|
330
|
+
rowIdx += 2;
|
|
331
|
+
}
|
|
332
|
+
|
|
318
333
|
// --- Requirement coverage (review §6): every requirement id gets ONE explicit status.
|
|
319
334
|
if (model.requirements.length > 0) {
|
|
320
335
|
dataCell(ws.getRow(rowIdx).getCell(2), 'Requirement coverage — every id has an explicit status', { bold: true });
|
|
@@ -220,6 +220,8 @@ export interface MatrixModel {
|
|
|
220
220
|
requirements: RequirementCoverage[];
|
|
221
221
|
findings: MatrixFinding[];
|
|
222
222
|
manifest: MatrixManifest;
|
|
223
|
+
/** Perf verdict (#569) — present when qa/perf.yaml sets a budget AND the run produced durations. */
|
|
224
|
+
perf?: import('../../harness/perf').PerfVerdict | null;
|
|
223
225
|
}
|
|
224
226
|
|
|
225
227
|
/** Complexity warning threshold (rules draft Gate I; raised for compact grouping) — echoed in the manifest. */
|
|
@@ -54,6 +54,7 @@ interface PlaywrightSpec {
|
|
|
54
54
|
projectId?: string;
|
|
55
55
|
results?: Array<{
|
|
56
56
|
status?: string; // 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interrupted'
|
|
57
|
+
duration?: number; // ms — Playwright emits this on every result (#569 perf budgets)
|
|
57
58
|
startTime?: string;
|
|
58
59
|
error?: { message?: string; stack?: string };
|
|
59
60
|
attachments?: Array<{ name?: string; path?: string; contentType?: string }>;
|
|
@@ -120,6 +121,7 @@ export function loadPlaywrightReport(reportPath: string): Map<string, Playwright
|
|
|
120
121
|
result.set(stripped, {
|
|
121
122
|
testTitle: stripped,
|
|
122
123
|
status,
|
|
124
|
+
duration: typeof res?.duration === 'number' ? res.duration : undefined,
|
|
123
125
|
startTime: res?.startTime,
|
|
124
126
|
error: errorMsg,
|
|
125
127
|
tracePath: trace,
|
package/src/exporters/types.ts
CHANGED
|
@@ -121,6 +121,8 @@ export interface SpecFileData {
|
|
|
121
121
|
export interface PlaywrightResult {
|
|
122
122
|
testTitle: string;
|
|
123
123
|
status: 'passed' | 'failed' | 'skipped' | 'timedOut' | 'interrupted' | 'unknown';
|
|
124
|
+
/** Wall-clock run time in ms (from Playwright's results[].duration) — perf budgets (#569). */
|
|
125
|
+
duration?: number;
|
|
124
126
|
startTime?: string;
|
|
125
127
|
error?: string;
|
|
126
128
|
tracePath?: string;
|
package/src/harness/audit.ts
CHANGED
|
@@ -10,10 +10,11 @@ import * as path from 'path';
|
|
|
10
10
|
import * as fs from 'fs';
|
|
11
11
|
import { loadUnitScenarios, readUnitFeatureText, parseViewpointOverview, ScenarioInfo, ViewpointEntry } from './parse';
|
|
12
12
|
import { checkViewpointBaseline, ViewpointBaseline } from './viewpoint-baseline';
|
|
13
|
+
import { flowQuality, statefulDepthFor, FlowQualityResult } from './flow-contract';
|
|
13
14
|
import { featureFilesFor } from './unit-paths';
|
|
14
15
|
import {
|
|
15
16
|
loadCatalog, viewpointGate, assertionDepth, dataThemesFor, depthThresholdFor, coverageBalance, duplicateClusters, traceability, claimProof, taxonomyLint,
|
|
16
|
-
automatableManual, flowCoveredThemes, flowRegressionDepth, oracleStrength,
|
|
17
|
+
automatableManual, flowCoveredThemes, flowRegressionDepth, oracleStrength, declaredPageType,
|
|
17
18
|
GateResult, DepthResult, BalanceResult, DuplicateResult, TraceResult, ClaimProofResult, TaxonomyResult, Catalog, AutomatableManualResult, FlowDepthResult, OracleStrengthResult,
|
|
18
19
|
} from './sensors';
|
|
19
20
|
import { loadFlowScenarios } from './flow-check';
|
|
@@ -48,6 +49,7 @@ export interface AuditReport {
|
|
|
48
49
|
oracle: OracleStrengthResult; // H4 — facet-oracle strength (weak name-substring vs title/detail/API/DB)
|
|
49
50
|
ledger: LedgerResult; // atomic viewpoint-item coverage (per-bullet status)
|
|
50
51
|
viewpointBaseline: ViewpointBaseline; // is the yardstick still the accepted one? (#557)
|
|
52
|
+
flowQuality?: FlowQualityResult; // #569 — flow contract verification (flows only)
|
|
51
53
|
calibration: { // #8 — multi-axis score so a high overall can't hide a weak axis
|
|
52
54
|
axes: Record<string, number>;
|
|
53
55
|
weakest: { axis: string; value: number };
|
|
@@ -154,15 +156,28 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
154
156
|
// back to the in-core functions if the capability provides none.
|
|
155
157
|
// A capability gate may need project context (the API gate resolves endpoint methods from the
|
|
156
158
|
// catalog) + the focus depth threshold (so it scores depth with the SAME bar as the UI gate).
|
|
157
|
-
const capGate = scoringCap?.gateProvider as
|
|
158
|
-
((i: { scenarios: ScenarioInfo[]; viewpoints: ViewpointEntry[]; catalog: Catalog; focus: typeof intent.focus; cwd: string; screenName: string; threshold: number; businessCriticalMethods?: string[] }) => { gate: GateResult; depth: DepthResult }) | undefined;
|
|
159
|
-
const provided = capGate?.({ scenarios, viewpoints, catalog, focus: intent.focus, cwd: projectRootFromScreenDir(screenDir), screenName: catalogScreenName, threshold: depthThresholdFor(intent.focus), businessCriticalMethods: intent.businessCriticalMethods });
|
|
160
159
|
// Is the declaration this suite is measured against still the one a human accepted?
|
|
161
160
|
const viewpointBaseline = checkViewpointBaseline(
|
|
162
161
|
projectRootFromScreenDir(screenDir), catalogScreenName, viewpointPath);
|
|
163
162
|
const viewpointText = fs.existsSync(viewpointPath) ? readTextFile(viewpointPath) : '';
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
// #569 — a flow WITH a contract is measured against ITS declaration, not a screen's.
|
|
164
|
+
// Keyword page-type detection force-fit flows into screen checklists (a registration
|
|
165
|
+
// flow judged as a `form` screen scored coverage 0%), so for contract-holding flows the
|
|
166
|
+
// page-type machinery only applies when the project DECLARED a page type explicitly.
|
|
167
|
+
// The stripped catalog feeds the CAPABILITY gate too — driver-ui provides the gate on
|
|
168
|
+
// real projects, and handing it the unstripped catalog re-introduced the force-fit.
|
|
169
|
+
const isUiFlowUnit = /^flows\//.test(catalogScreenName);
|
|
170
|
+
const flowQ: FlowQualityResult = isUiFlowUnit
|
|
171
|
+
? flowQuality(screenDir, scenarios)
|
|
172
|
+
: { hasContract: false, errors: [], outcomeProven: false, outcomeManualOnly: false, offGoal: [], offGoalRatio: 0, offGoalCategories: [], phases: [], phaseRatio: 1, handoffs: { total: 0, asserted: 0, ratio: 1 } };
|
|
173
|
+
const gateCatalog = (flowQ.hasContract && !declaredPageType(viewpointText, catalog))
|
|
174
|
+
? { ...catalog, page_types: {} } as Catalog
|
|
175
|
+
: catalog;
|
|
176
|
+
const capGate = scoringCap?.gateProvider as
|
|
177
|
+
((i: { scenarios: ScenarioInfo[]; viewpoints: ViewpointEntry[]; catalog: Catalog; focus: typeof intent.focus; cwd: string; screenName: string; threshold: number; businessCriticalMethods?: string[] }) => { gate: GateResult; depth: DepthResult }) | undefined;
|
|
178
|
+
const provided = capGate?.({ scenarios, viewpoints, catalog: gateCatalog, focus: intent.focus, cwd: projectRootFromScreenDir(screenDir), screenName: catalogScreenName, threshold: depthThresholdFor(intent.focus), businessCriticalMethods: intent.businessCriticalMethods });
|
|
179
|
+
const gate = provided?.gate ?? viewpointGate(scenarios, viewpoints, gateCatalog, platform === 'mobile', viewpointText);
|
|
180
|
+
const depth = provided?.depth ?? assertionDepth(scenarios, dataThemesFor(gateCatalog, gate.pageType), intent.focus);
|
|
166
181
|
|
|
167
182
|
// TQ-4 — deferral-aware coverage credit: an inherently cross-screen theme (cart / detail /
|
|
168
183
|
// filter correctness) belongs in a FLOW, not on the screen. When a flow deeply covers a screen
|
|
@@ -200,8 +215,15 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
200
215
|
// collection, the regression dimensions (count/quantity proof · teardown · multi-source) cap the
|
|
201
216
|
// businessDepth headroom: it can reach 1.0 only when all three are exercised, so a present-but-
|
|
202
217
|
// shallow flow can't claim a perfect score (floor 0.5 — assertion depth still dominates).
|
|
203
|
-
const isUiFlow =
|
|
204
|
-
|
|
218
|
+
const isUiFlow = isUiFlowUnit;
|
|
219
|
+
let flowDepth = isUiFlow ? flowRegressionDepth(scenarios) : { stateful: false, countProof: false, teardown: false, multiSource: false, ratio: 1, missing: [] } as FlowDepthResult;
|
|
220
|
+
// Contract-declared statefulness generalizes the cart-hardcoded vocabulary: a flow that
|
|
221
|
+
// mutates ANY named collection (order, application, submission …) gets the same three
|
|
222
|
+
// regression dims. The legacy cart detection stays for contract-less flows (snapshots).
|
|
223
|
+
if (isUiFlow && !flowDepth.stateful && flowQ.contract?.stateful) {
|
|
224
|
+
const d = statefulDepthFor(flowQ.contract.stateful, scenarios);
|
|
225
|
+
flowDepth = { stateful: true, countProof: d.countProof, teardown: d.teardown, multiSource: d.multiSource, ratio: d.ratio, missing: d.missing };
|
|
226
|
+
}
|
|
205
227
|
const FLOW_DEPTH_FLOOR = 0.5;
|
|
206
228
|
// H4 — oracle strength: a weak facet oracle (name-substring "proves" category/brand membership)
|
|
207
229
|
// caps businessDepth the same way (floor 0.5). A suite with no facet claim, or a strong oracle, is
|
|
@@ -211,10 +233,14 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
211
233
|
|
|
212
234
|
// Sub-scores
|
|
213
235
|
const coverage = gate.coverageRatio;
|
|
236
|
+
// #569 — handoff integrity: a flow's depth is also whether each cross-screen transition
|
|
237
|
+
// is followed by an assertion on the carried state (same floor discipline as the other caps).
|
|
238
|
+
const HANDOFF_FLOOR = 0.5;
|
|
214
239
|
const businessDepth = Math.min(
|
|
215
240
|
depth.bcDepthRatio,
|
|
216
241
|
flowDepth.stateful ? FLOW_DEPTH_FLOOR + (1 - FLOW_DEPTH_FLOOR) * flowDepth.ratio : 1,
|
|
217
242
|
oracle.weak.length ? ORACLE_FLOOR + (1 - ORACLE_FLOOR) * oracle.ratio : 1,
|
|
243
|
+
flowQ.hasContract && flowQ.handoffs.total > 0 ? HANDOFF_FLOOR + (1 - HANDOFF_FLOOR) * flowQ.handoffs.ratio : 1,
|
|
218
244
|
);
|
|
219
245
|
// When the taxonomy drifted (most scenarios unclassified), the balance axis is unreliable — cap it
|
|
220
246
|
// at 0.5 instead of awarding a vacuous 1.0 so a stale taxonomy fails loudly, not silently (H1).
|
|
@@ -246,8 +272,15 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
246
272
|
// what a create-test run did, silently dropping the performance viewpoint on
|
|
247
273
|
// the way. While the declaration is unconfirmed, neither axis is evidence.
|
|
248
274
|
const viewpointMoved = viewpointBaseline.status === 'changed';
|
|
275
|
+
// 4. A flow with a contract is measured on ITS coverage: journey phases (HP/ER/EH
|
|
276
|
+
// automated) — not a screen's page-type themes. Same weight, same criticality,
|
|
277
|
+
// so a flow can finally EARN the coverage axis instead of always losing it to
|
|
278
|
+
// PAGE-TYPE-UNDETERMINED (or worse, being judged against a form checklist).
|
|
279
|
+
const flowScored = flowQ.hasContract;
|
|
249
280
|
const axisDefs: Array<{ key: string; value: number; weight: number; applicable: boolean; critical: boolean }> = [
|
|
250
|
-
|
|
281
|
+
flowScored
|
|
282
|
+
? { key: 'flowCoverage', value: flowQ.phaseRatio, weight: 0.22, applicable: true, critical: true }
|
|
283
|
+
: { key: 'coverage', value: coverage, weight: 0.22, applicable: !!gate.pageType && gate.themesTotal > 0, critical: true },
|
|
251
284
|
{ key: 'specFR', value: specRatio, weight: 0.15, applicable: spec.hasSpec && spec.frTotal > 0, critical: true },
|
|
252
285
|
{ key: 'atomicLedger', value: ledger.ratio, weight: 0.13, applicable: ledger.hasViewpoint && ledger.total > 0 && !viewpointMoved, critical: true },
|
|
253
286
|
{ key: 'businessDepth', value: businessDepth, weight: 0.20, applicable: true, critical: true },
|
|
@@ -297,9 +330,40 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
297
330
|
].filter(Boolean).join(', ');
|
|
298
331
|
findings.push(`VIEWPOINT-BASELINE-CHANGED: test-viewpoint.md no longer declares what it did when this unit was last accepted — ${moved || 'the declared ids were reordered or replaced'}. atomicLedger + traceability measure the suite AGAINST this file, so they are excluded from the score until the change is confirmed: a generator that rewrites the declaration scores both 100% by construction, and a viewpoint dropped from the file stops being missing from anything. Review the diff (a removed id means that coverage is now unclaimed), then run \`sungen audit --screen ${screenName} --accept-viewpoint\`.`);
|
|
299
332
|
}
|
|
300
|
-
|
|
333
|
+
// On a contract flow the page type is INTENTIONALLY not applied — the contract is the checklist.
|
|
334
|
+
if (gate.pageTypeSource === 'undetermined' && !flowScored) {
|
|
301
335
|
findings.push(`PAGE-TYPE-UNDETERMINED: no page type fits this screen with enough confidence (best ${gate.pageTypeEvidence?.hits ?? 0} keyword hit(s)), so NO critical themes were demanded and the coverage axis is excluded from the score — declare it in test-viewpoint.md (\`page-type: <id>\`) to have the theme checklist applied.`);
|
|
302
336
|
}
|
|
337
|
+
// ---- #569: flow-quality findings -----------------------------------------
|
|
338
|
+
if (isUiFlow && !flowQ.hasContract) {
|
|
339
|
+
const why = flowQ.errors.length ? ` (${flowQ.errors.join('; ')})` : '';
|
|
340
|
+
findings.push(`FLOW-CONTRACT-MISSING: this flow declares no contract${why} — without \`requirements/flow-contract.yaml\` (goal · trigger · outcome.screen · phases) the flow cannot be scored on ITS coverage (journey phases) and falls back to screen machinery. Author it with the boundary checklist: one business goal, a clear trigger, ONE observable outcome valuable to the actor, name = "Verb + outcome"; split anything that forms an independently valuable flow.`);
|
|
341
|
+
}
|
|
342
|
+
if (flowQ.hasContract) {
|
|
343
|
+
if (!flowQ.outcomeProven) {
|
|
344
|
+
findings.push(flowQ.outcomeManualOnly
|
|
345
|
+
? `FLOW-OUTCOME-UNPROVEN: the goal "${flowQ.contract!.goal}" is covered only by @manual scenarios — the flow's own outcome (\`${flowQ.contract!.outcome.screen}\`) has no AUTOMATED data assertion, so a regression run cannot prove the journey still works. Automate the happy-path proof first; it is the reason this flow exists.`
|
|
346
|
+
: `FLOW-OUTCOME-UNPROVEN: no scenario asserts data on the outcome screen \`${flowQ.contract!.outcome.screen}\` — the flow never proves its own goal ("${flowQ.contract!.goal}"). Add the happy-path scenario that ends in the declared final assertion.`);
|
|
347
|
+
}
|
|
348
|
+
if (flowQ.offGoalRatio > 0.35) {
|
|
349
|
+
findings.push(`FLOW-SCOPE-CREEP: ${flowQ.offGoal.length}/${scenarios.length} scenarios never touch the outcome screen \`${flowQ.contract!.outcome.screen}\` and are not guards/error-recovery (categories: ${flowQ.offGoalCategories.join(', ')}) — evidence of a SECOND business goal in this flow. Split them into their own flow (one flow = one goal, one observable outcome).`);
|
|
350
|
+
}
|
|
351
|
+
for (const ph of flowQ.phases.filter((p) => !p.covered || !p.automated)) {
|
|
352
|
+
findings.push(`FLOW-PHASE-${ph.phase}-MISSING: journey phase ${ph.phase} (${ph.phase === 'HP' ? 'happy path proving the outcome' : ph.phase === 'ER' ? 'error recovery — validation must not trap the journey' : 'guards — direct access / back / refresh'}) is ${ph.covered ? 'covered only by @manual' : 'not covered'} → it does not count toward flowCoverage until automated.`);
|
|
353
|
+
}
|
|
354
|
+
if (flowQ.handoffs.total > 0 && flowQ.handoffs.ratio < 0.7) {
|
|
355
|
+
findings.push(`FLOW-HANDOFF-SHALLOW: only ${flowQ.handoffs.asserted}/${flowQ.handoffs.total} cross-screen transitions are followed by an assertion in the new screen — carried state (the thing a flow exists to verify) is unproven at the handoff. businessDepth is capped until asserted.`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (isUiFlow) {
|
|
359
|
+
// Flows defer more than screens, so an UNCODED @manual is costlier here: the capability
|
|
360
|
+
// planner cannot route it to the driver that would automate it (password-reset: 11/22
|
|
361
|
+
// manual, most of them M5-inbox — invisible while tagged bare).
|
|
362
|
+
const bareManual = (featureText.match(/@manual(?![:\w])/g) || []).length;
|
|
363
|
+
if (bareManual > 0) {
|
|
364
|
+
findings.push(`MANUAL-CODE-MISSING: ${bareManual} @manual tag(s) carry no reason code — tag @manual:M1..M9 (M5 = external dependency e.g. inbox, M3 = mock/network, M7 = environment) so the capability planner can route what a driver would automate.`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
303
367
|
for (const c of flowCredits) {
|
|
304
368
|
findings.push(`COVERED-VIA-FLOW: critical theme "${c.theme}" is not covered on this screen but is deeply covered by flow "${c.flow}" — cross-screen depth correctly owned by the flow, so it is credited (not a screen gap). Verify with \`sungen flow-check\`.`);
|
|
305
369
|
}
|
|
@@ -376,6 +440,13 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
376
440
|
const shown = spec.inferredOnly.slice(0, 8).join(', ');
|
|
377
441
|
findings.push(`SPEC-TRACE-IMPLICIT: ${spec.inferredOnly.length} requirement(s) are covered by keyword inference only (${shown}${spec.inferredOnly.length > 8 ? ', …' : ''}) — add \`@spec:<id>\` to the scenario that proves each so the trace survives edits and delivery can report it.`);
|
|
378
442
|
}
|
|
443
|
+
// Safety net for the parser (#572): the feature CITES requirement ids the spec parser
|
|
444
|
+
// could not find a declaration for. Silence here would repeat the original failure —
|
|
445
|
+
// "nothing found" quietly excluding the specFR axis while the evidence sits in the file.
|
|
446
|
+
if (spec.hasSpec && spec.frTotal === 0 && /@spec:/i.test(featureText)) {
|
|
447
|
+
const cited = [...new Set([...featureText.matchAll(/@spec:([A-Za-z0-9.-]+)/g)].map((m) => m[1].toUpperCase()))];
|
|
448
|
+
findings.push(`SPEC-IDS-UNPARSED: the feature cites ${cited.length} requirement id(s) via @spec: (${cited.slice(0, 5).join(', ')}${cited.length > 5 ? ', …' : ''}) but requirements/spec.md yielded ZERO parseable requirement declarations — the spec likely declares ids in a scheme the parser cannot read. Declare each requirement on its own line as \`<ID>: <clause>\` (bullet/bold/backtick all fine; the id must end in a number) so the MUST-coverage gate and the specFR axis can run.`);
|
|
449
|
+
}
|
|
379
450
|
if (taxonomyMismatch) {
|
|
380
451
|
findings.push(`VP-TAXONOMY-MISMATCH: only ${(trace.mappedRatio * 100).toFixed(0)}% of scenarios use the viewpoint IDs declared in test-viewpoint.md — scenarios invented a generic VP-<CAT> scheme. Re-tag to the project's viewpoint IDs so the coverage matrix is accurate.`);
|
|
381
452
|
}
|
|
@@ -485,6 +556,7 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
485
556
|
scenarioCount: scenarios.length,
|
|
486
557
|
gate, depth, claim, taxonomy, balance, duplicates, trace, spec,
|
|
487
558
|
taxonomyMismatch, downstream, manualOracle: manualOracleResult, automatableManual: autoManual, flowDepth, oracle, ledger, viewpointBaseline, calibration,
|
|
559
|
+
...(isUiFlow ? { flowQuality: flowQ } : {}),
|
|
488
560
|
score: {
|
|
489
561
|
overall: Math.round(overall * 10) / 10,
|
|
490
562
|
coverage: Math.round(coverage * 100) / 100,
|