@sun-asterisk/sungen 3.2.17 → 3.2.18
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 +23 -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 +209 -16
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/index.js +5 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
- package/dist/dashboard/snapshot-builder.js +8 -1
- package/dist/dashboard/snapshot-builder.js.map +1 -1
- package/dist/dashboard/templates/index.html +1 -1
- package/dist/exporters/api-environment.d.ts +11 -0
- package/dist/exporters/api-environment.d.ts.map +1 -0
- package/dist/exporters/api-environment.js +129 -0
- package/dist/exporters/api-environment.js.map +1 -0
- package/dist/exporters/feature-parser.d.ts +16 -1
- package/dist/exporters/feature-parser.d.ts.map +1 -1
- package/dist/exporters/feature-parser.js +21 -2
- package/dist/exporters/feature-parser.js.map +1 -1
- package/dist/exporters/matrix/build.d.ts +70 -0
- package/dist/exporters/matrix/build.d.ts.map +1 -0
- package/dist/exporters/matrix/build.js +516 -0
- package/dist/exporters/matrix/build.js.map +1 -0
- package/dist/exporters/matrix/export.d.ts +50 -0
- package/dist/exporters/matrix/export.d.ts.map +1 -0
- package/dist/exporters/matrix/export.js +152 -0
- package/dist/exporters/matrix/export.js.map +1 -0
- package/dist/exporters/matrix/fingerprint.d.ts +20 -0
- package/dist/exporters/matrix/fingerprint.d.ts.map +1 -0
- package/dist/exporters/matrix/fingerprint.js +83 -0
- package/dist/exporters/matrix/fingerprint.js.map +1 -0
- package/dist/exporters/matrix/gates.d.ts +21 -0
- package/dist/exporters/matrix/gates.d.ts.map +1 -0
- package/dist/exporters/matrix/gates.js +367 -0
- package/dist/exporters/matrix/gates.js.map +1 -0
- package/dist/exporters/matrix/map-loader.d.ts +22 -0
- package/dist/exporters/matrix/map-loader.d.ts.map +1 -0
- package/dist/exporters/matrix/map-loader.js +193 -0
- package/dist/exporters/matrix/map-loader.js.map +1 -0
- package/dist/exporters/matrix/render-csv.d.ts +9 -0
- package/dist/exporters/matrix/render-csv.d.ts.map +1 -0
- package/dist/exporters/matrix/render-csv.js +92 -0
- package/dist/exporters/matrix/render-csv.js.map +1 -0
- package/dist/exporters/matrix/render-xlsx.d.ts +41 -0
- package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -0
- package/dist/exporters/matrix/render-xlsx.js +430 -0
- package/dist/exporters/matrix/render-xlsx.js.map +1 -0
- package/dist/exporters/matrix/types.d.ts +199 -0
- package/dist/exporters/matrix/types.d.ts.map +1 -0
- package/dist/exporters/matrix/types.js +15 -0
- package/dist/exporters/matrix/types.js.map +1 -0
- package/dist/exporters/matrix/wording.d.ts +61 -0
- package/dist/exporters/matrix/wording.d.ts.map +1 -0
- package/dist/exporters/matrix/wording.js +221 -0
- package/dist/exporters/matrix/wording.js.map +1 -0
- package/dist/exporters/scenario-merger.d.ts.map +1 -1
- package/dist/exporters/scenario-merger.js +2 -1
- package/dist/exporters/scenario-merger.js.map +1 -1
- package/dist/exporters/spec-parser.d.ts.map +1 -1
- package/dist/exporters/spec-parser.js +2 -1
- package/dist/exporters/spec-parser.js.map +1 -1
- package/dist/exporters/xlsx-report-builder.d.ts +1 -1
- package/dist/exporters/xlsx-report-builder.d.ts.map +1 -1
- package/dist/exporters/xlsx-report-builder.js +3 -3
- package/dist/exporters/xlsx-report-builder.js.map +1 -1
- package/dist/harness/audit.d.ts +10 -0
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +98 -16
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/blindspot.d.ts.map +1 -1
- package/dist/harness/blindspot.js +2 -1
- package/dist/harness/blindspot.js.map +1 -1
- package/dist/harness/capability-plan.d.ts +2 -0
- package/dist/harness/capability-plan.d.ts.map +1 -1
- package/dist/harness/capability-plan.js +17 -4
- package/dist/harness/capability-plan.js.map +1 -1
- package/dist/harness/feedback.d.ts.map +1 -1
- package/dist/harness/feedback.js +3 -2
- package/dist/harness/feedback.js.map +1 -1
- package/dist/harness/flow-check.d.ts.map +1 -1
- package/dist/harness/flow-check.js +2 -1
- package/dist/harness/flow-check.js.map +1 -1
- package/dist/harness/flow-plan.d.ts.map +1 -1
- package/dist/harness/flow-plan.js +3 -2
- package/dist/harness/flow-plan.js.map +1 -1
- package/dist/harness/intent.d.ts.map +1 -1
- package/dist/harness/intent.js +2 -1
- package/dist/harness/intent.js.map +1 -1
- package/dist/harness/journey.d.ts.map +1 -1
- package/dist/harness/journey.js +3 -2
- package/dist/harness/journey.js.map +1 -1
- package/dist/harness/ledger.d.ts.map +1 -1
- package/dist/harness/ledger.js +3 -2
- package/dist/harness/ledger.js.map +1 -1
- package/dist/harness/manifest.d.ts.map +1 -1
- package/dist/harness/manifest.js +4 -3
- package/dist/harness/manifest.js.map +1 -1
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +16 -3
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/quality-gates.d.ts.map +1 -1
- package/dist/harness/quality-gates.js +2 -1
- package/dist/harness/quality-gates.js.map +1 -1
- package/dist/harness/read-text.d.ts +22 -0
- package/dist/harness/read-text.d.ts.map +1 -0
- package/dist/harness/read-text.js +64 -0
- package/dist/harness/read-text.js.map +1 -0
- package/dist/harness/script-check.d.ts.map +1 -1
- package/dist/harness/script-check.js +3 -2
- package/dist/harness/script-check.js.map +1 -1
- package/dist/harness/sensors.d.ts +13 -1
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +63 -20
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/spec-coverage.d.ts +5 -0
- package/dist/harness/spec-coverage.d.ts.map +1 -1
- package/dist/harness/spec-coverage.js +17 -7
- package/dist/harness/spec-coverage.js.map +1 -1
- package/dist/harness/trace.d.ts.map +1 -1
- package/dist/harness/trace.js +4 -3
- package/dist/harness/trace.js.map +1 -1
- package/dist/harness/viewpoint-baseline.d.ts +49 -0
- package/dist/harness/viewpoint-baseline.d.ts.map +1 -0
- package/dist/harness/viewpoint-baseline.js +141 -0
- package/dist/harness/viewpoint-baseline.js.map +1 -0
- package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
- package/dist/harness/viewpoint-ledger.js +2 -1
- package/dist/harness/viewpoint-ledger.js.map +1 -1
- package/dist/orchestrator/assets-drift.d.ts +24 -0
- package/dist/orchestrator/assets-drift.d.ts.map +1 -0
- package/dist/orchestrator/assets-drift.js +80 -0
- package/dist/orchestrator/assets-drift.js.map +1 -0
- package/dist/orchestrator/templates/ai-src/commands/create-test.md +24 -0
- package/dist/orchestrator/templates/ai-src/commands/delivery.md +203 -120
- package/dist/orchestrator/templates/ai-src/config/claude.md +1 -1
- package/dist/orchestrator/templates/ai-src/config/copilot.md +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +102 -6
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +1 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +23 -0
- package/package.json +3 -3
- package/src/cli/commands/audit.ts +23 -1
- package/src/cli/commands/delivery.ts +240 -18
- package/src/cli/index.ts +6 -0
- package/src/dashboard/snapshot-builder.ts +8 -1
- package/src/dashboard/templates/index.html +1 -1
- package/src/exporters/api-environment.ts +97 -0
- package/src/exporters/feature-parser.ts +21 -2
- package/src/exporters/matrix/build.ts +551 -0
- package/src/exporters/matrix/export.ts +149 -0
- package/src/exporters/matrix/fingerprint.ts +49 -0
- package/src/exporters/matrix/gates.ts +400 -0
- package/src/exporters/matrix/map-loader.ts +163 -0
- package/src/exporters/matrix/render-csv.ts +94 -0
- package/src/exporters/matrix/render-xlsx.ts +420 -0
- package/src/exporters/matrix/types.ts +226 -0
- package/src/exporters/matrix/wording.ts +221 -0
- package/src/exporters/scenario-merger.ts +2 -1
- package/src/exporters/spec-parser.ts +2 -1
- package/src/exporters/xlsx-report-builder.ts +3 -2
- package/src/harness/audit.ts +104 -18
- package/src/harness/blindspot.ts +2 -1
- package/src/harness/capability-plan.ts +17 -4
- package/src/harness/feedback.ts +3 -2
- package/src/harness/flow-check.ts +2 -1
- package/src/harness/flow-plan.ts +3 -2
- package/src/harness/intent.ts +2 -1
- package/src/harness/journey.ts +3 -2
- package/src/harness/ledger.ts +3 -2
- package/src/harness/manifest.ts +4 -3
- package/src/harness/parse.ts +17 -3
- package/src/harness/quality-gates.ts +2 -1
- package/src/harness/read-text.ts +28 -0
- package/src/harness/script-check.ts +3 -2
- package/src/harness/sensors.ts +55 -8
- package/src/harness/spec-coverage.ts +22 -7
- package/src/harness/trace.ts +4 -3
- package/src/harness/viewpoint-baseline.ts +128 -0
- package/src/harness/viewpoint-ledger.ts +2 -1
- package/src/orchestrator/assets-drift.ts +64 -0
- package/src/orchestrator/templates/ai-src/commands/create-test.md +24 -0
- package/src/orchestrator/templates/ai-src/commands/delivery.md +203 -120
- package/src/orchestrator/templates/ai-src/config/claude.md +1 -1
- package/src/orchestrator/templates/ai-src/config/copilot.md +1 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +102 -6
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +1 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +23 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delivery v2 — Test Case & Coverage Matrix model (docs/spec/delivery-coverage-matrix-spec.md).
|
|
3
|
+
*
|
|
4
|
+
* Gherkin stays the source of test meaning; the matrix is a derived presentation:
|
|
5
|
+
* one delivery item = one target + one intent + one oracle family, containing one or
|
|
6
|
+
* more atomic coverage variants (scenario / @cases row). Grouping semantics live in the
|
|
7
|
+
* committed Delivery Map (`qa/<kind>/<unit>/delivery/<unit>.map.yaml`) — AI-proposed,
|
|
8
|
+
* gate-validated, QA-approved.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { PlaywrightResult } from '../types';
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Delivery Map (the committed grouping artifact)
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
export type MapCategory = 'normal' | 'abnormal' | 'security' | 'nfr';
|
|
18
|
+
export type ReviewState = 'proposed' | 'approved';
|
|
19
|
+
export type DispositionKind = 'excluded' | 'blocked' | 'covered_elsewhere' | 'accepted_risk';
|
|
20
|
+
|
|
21
|
+
export interface MapGroup {
|
|
22
|
+
/** Stable delivery id, unique per unit, order-independent (e.g. DI-EMAIL-FORMAT). */
|
|
23
|
+
id: string;
|
|
24
|
+
/** ONE primary target: field/component dot-path, function/flow phrase, or `METHOD /path`. */
|
|
25
|
+
target: string;
|
|
26
|
+
/** One coherent test intent (the primary behavior/rule verified). */
|
|
27
|
+
intent: string;
|
|
28
|
+
/** Oracle family in words — the shared Pass/Fail observation. */
|
|
29
|
+
oracle: string;
|
|
30
|
+
category: MapCategory;
|
|
31
|
+
review: ReviewState;
|
|
32
|
+
/** Optional short digest of the coverage dimensions this item varies over
|
|
33
|
+
* (e.g. "required ×3 · format ×9 · full-width ×2"). Rendered on the parent row
|
|
34
|
+
* instead of concatenating every variant's title — the compact review view. */
|
|
35
|
+
dimensions?: string;
|
|
36
|
+
/** Variant refs: `VP-ID` (all @cases rows when the scenario has a dataset) or `VP-ID#label`. */
|
|
37
|
+
variants: string[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface MapDisposition {
|
|
41
|
+
as: DispositionKind;
|
|
42
|
+
reason?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type RequirementStatus =
|
|
46
|
+
| 'covered' | 'partially_covered' | 'covered_elsewhere' | 'planned' | 'gap' | 'not_applicable';
|
|
47
|
+
|
|
48
|
+
export interface RequirementOverride {
|
|
49
|
+
status: RequirementStatus;
|
|
50
|
+
note?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface DeliveryMap {
|
|
54
|
+
version: number;
|
|
55
|
+
unit: string;
|
|
56
|
+
/** ISO form number rendered in the workbook masthead (default BM-2-901-13). */
|
|
57
|
+
formNo?: string;
|
|
58
|
+
groups: MapGroup[];
|
|
59
|
+
/** VP-id → intentionally-not-grouped disposition. */
|
|
60
|
+
dispositions: Record<string, MapDisposition>;
|
|
61
|
+
/** Requirement-id → reviewed status override (partially_covered / not_applicable / …).
|
|
62
|
+
* Without an override a requirement is `covered` when a variant traces to it, else `gap`. */
|
|
63
|
+
requirements: Record<string, RequirementOverride>;
|
|
64
|
+
/** VP-id → scenario fingerprint stamped at approval time (drift detector input).
|
|
65
|
+
* The reserved key `__map__` holds the fingerprint of the map's own semantic
|
|
66
|
+
* content (groups + dispositions) so edited wording re-opens review (GAP-09). */
|
|
67
|
+
fingerprints: Record<string, string>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Derived model (deterministic, computed from the .feature at build time)
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
/** Execution layers a variant verifies on — cross-checked with the verification scope. */
|
|
75
|
+
export type MatrixLayer = 'ui' | 'api' | 'db';
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* One atomic coverage variant — the unit of execution, result, and traceability.
|
|
79
|
+
* Backed by one scenario, or one `@cases` dataset row of a scenario.
|
|
80
|
+
*/
|
|
81
|
+
export interface CoverageVariant {
|
|
82
|
+
/** `VP-ID` or `VP-ID#label` for a dataset row. */
|
|
83
|
+
ref: string;
|
|
84
|
+
vpId: string;
|
|
85
|
+
/** Dataset-row label when this variant is one `@cases` row. */
|
|
86
|
+
caseLabel?: string;
|
|
87
|
+
/** Human title (scenario name without the VP prefix; row label appended for @cases). */
|
|
88
|
+
title: string;
|
|
89
|
+
/** Viewpoint category from the VP prefix (LOGIC/VAL/UI/SEC/…). */
|
|
90
|
+
vpCategory: string;
|
|
91
|
+
priority: string; // High | Normal | Low
|
|
92
|
+
/** 'auto' | 'manual'; manual keeps its @manual:Mx reason. */
|
|
93
|
+
mode: 'auto' | 'manual';
|
|
94
|
+
manualReason?: string;
|
|
95
|
+
layers: MatrixLayer[];
|
|
96
|
+
/** @spec:* requirement traces. */
|
|
97
|
+
traces: string[];
|
|
98
|
+
/** Variables this scenario produces at RUN TIME (`remember … as {{v}}`, capability
|
|
99
|
+
* bindings) — they never exist in test-data, so Gate D must not demand them. */
|
|
100
|
+
runtimeVars: string[];
|
|
101
|
+
/** Condition summary: differentiating dimension shown on the sub-row. */
|
|
102
|
+
condition: string;
|
|
103
|
+
/** Resolved data pairs (test-data values / dataset-row columns), `key: value` lines. */
|
|
104
|
+
data: string[];
|
|
105
|
+
/** Normalized trigger shape (When steps, `{{var}}` collapsed) — signature input. */
|
|
106
|
+
triggerShape: string[];
|
|
107
|
+
/** Trigger for display (When steps, vars resolved). */
|
|
108
|
+
trigger: string[];
|
|
109
|
+
/** Normalized oracle shape (Then steps / manual Observable+Oracle) — signature input. */
|
|
110
|
+
oracleShape: string[];
|
|
111
|
+
/** Oracle for display. */
|
|
112
|
+
oracle: string[];
|
|
113
|
+
/** HOW to check (manual `Oracle:` lines — tools, panes, queries). Rendered as
|
|
114
|
+
* "Verification method" under the expected result, never inside it. */
|
|
115
|
+
verification: string[];
|
|
116
|
+
/** Precondition profile: auth role + scenario-level Given shapes — signature input. */
|
|
117
|
+
preconditionProfile: string;
|
|
118
|
+
/** Human preconditions (auth + own Given steps). */
|
|
119
|
+
precondition: string[];
|
|
120
|
+
/** 'simple' (G*W*T*) | 'sequence' (mid-flow Given / multi round-trip) | 'manual'. */
|
|
121
|
+
procedureProfile: 'simple' | 'sequence' | 'manual';
|
|
122
|
+
/** Canonical scenario fingerprint (drift detection). */
|
|
123
|
+
fingerprint: string;
|
|
124
|
+
/** Execution result, when the run report has one for this variant. */
|
|
125
|
+
result?: PlaywrightResult;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export type ItemResult = 'passed' | 'failed' | 'blocked' | 'partial' | 'not_run' | 'na';
|
|
129
|
+
|
|
130
|
+
/** The exact word rendered in a variant's Result cell — the single vocabulary the
|
|
131
|
+
* parent's roll-up formula counts (COUNTIF is exact-match). */
|
|
132
|
+
export type VariantState = 'Passed' | 'Failed' | 'Blocked' | 'Pending' | 'N/A';
|
|
133
|
+
|
|
134
|
+
/** One summarized row in the matrix — a group of variants sharing the signature. */
|
|
135
|
+
export interface DeliveryItem {
|
|
136
|
+
id: string;
|
|
137
|
+
target: string;
|
|
138
|
+
intent: string;
|
|
139
|
+
oracle: string;
|
|
140
|
+
/** Short coverage-dimension digest from the map (compact parent view). */
|
|
141
|
+
dimensions?: string;
|
|
142
|
+
category: MapCategory;
|
|
143
|
+
review: ReviewState;
|
|
144
|
+
/** Highest priority among the variants (per-variant priorities stay on the sub-rows). */
|
|
145
|
+
priority: string;
|
|
146
|
+
/** Execution mode is a coverage dimension, not a split — 'mixed' when auto and
|
|
147
|
+
* manual variants share one intent (e.g. account states needing seeded data). */
|
|
148
|
+
mode: 'auto' | 'manual' | 'mixed';
|
|
149
|
+
layers: MatrixLayer[];
|
|
150
|
+
/** Union of variant traces (exact per-variant traces stay on the variants). */
|
|
151
|
+
traces: string[];
|
|
152
|
+
/** Preconditions shared by EVERY variant (the intersection) — written once here. */
|
|
153
|
+
precondition: string[];
|
|
154
|
+
/** Leading trigger steps shared by every variant — written once here; each
|
|
155
|
+
* variant renders only its remaining steps (`triggerDeltas`). */
|
|
156
|
+
trigger: string[];
|
|
157
|
+
variants: CoverageVariant[];
|
|
158
|
+
/** variant ref → its precondition lines that are not on the parent. */
|
|
159
|
+
preconditionDeltas: Record<string, string[]>;
|
|
160
|
+
/** variant ref → its trigger steps after the shared prefix. */
|
|
161
|
+
triggerDeltas: Record<string, string[]>;
|
|
162
|
+
/** Derived roll-up — never entered independently (Gate F). */
|
|
163
|
+
result: ItemResult;
|
|
164
|
+
resultCounts: { passed: number; failed: number; blocked: number; notRun: number; na: number };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface MatrixDisposition {
|
|
168
|
+
vpId: string;
|
|
169
|
+
title: string;
|
|
170
|
+
as: DispositionKind;
|
|
171
|
+
reason: string;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** One row of the requirement-coverage table (Coverage sheet). */
|
|
175
|
+
export interface RequirementCoverage {
|
|
176
|
+
id: string; // FR-003 / TR-001 / NFR-…
|
|
177
|
+
status: RequirementStatus;
|
|
178
|
+
/** Delivery items whose variants trace to this requirement. */
|
|
179
|
+
items: string[];
|
|
180
|
+
variantCount: number;
|
|
181
|
+
note: string;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
// Gate findings
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
|
|
188
|
+
export type GateId = 'A' | 'B' | 'C' | 'D' | 'E' | 'G' | 'K' | 'R' | 'W';
|
|
189
|
+
export type FindingSeverity = 'error' | 'review' | 'warning';
|
|
190
|
+
|
|
191
|
+
export interface MatrixFinding {
|
|
192
|
+
gate: GateId;
|
|
193
|
+
severity: FindingSeverity;
|
|
194
|
+
/** Delivery item / VP-id the finding anchors to, when applicable. */
|
|
195
|
+
ref?: string;
|
|
196
|
+
message: string;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
// The assembled matrix
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
export interface MatrixManifest {
|
|
204
|
+
unit: string;
|
|
205
|
+
transformerVersion: string;
|
|
206
|
+
sourceFingerprint: string; // canonical hash over all scenario fingerprints
|
|
207
|
+
mapFingerprint: string; // canonical hash of the map content
|
|
208
|
+
approvalState: 'approved' | 'draft';
|
|
209
|
+
itemCount: number;
|
|
210
|
+
variantCount: number;
|
|
211
|
+
thresholds: { maxVariantsPerItem: number };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export interface MatrixModel {
|
|
215
|
+
unit: string;
|
|
216
|
+
formNo: string;
|
|
217
|
+
items: DeliveryItem[];
|
|
218
|
+
dispositions: MatrixDisposition[];
|
|
219
|
+
/** Requirement coverage — empty when the unit has no requirements/spec.md ids. */
|
|
220
|
+
requirements: RequirementCoverage[];
|
|
221
|
+
findings: MatrixFinding[];
|
|
222
|
+
manifest: MatrixManifest;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Complexity warning threshold (rules draft Gate I; raised for compact grouping) — echoed in the manifest. */
|
|
226
|
+
export const MAX_VARIANTS_PER_ITEM = 20;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wording normalization — deterministic rendering step AFTER semantic
|
|
3
|
+
* normalization (review feedback §10): turn sungen DSL steps into controlled
|
|
4
|
+
* manual-test English without changing the target, condition, trigger,
|
|
5
|
+
* precondition, oracle, or trace.
|
|
6
|
+
*
|
|
7
|
+
* - Actions render in the imperative: "User fill [Email] field with X"
|
|
8
|
+
* → "Enter X in the Email field."
|
|
9
|
+
* - Expected results render as observable assertions (never tester actions):
|
|
10
|
+
* "User see [Jobs] page" → "The Jobs page is displayed."
|
|
11
|
+
* - Manual `# Tester verifies:` labels (Setup:/Action:/Observable:/Oracle:)
|
|
12
|
+
* become structured fields instead of prose: Setup → precondition,
|
|
13
|
+
* Action → action, Observable → expected, Oracle → verification method.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** A step that CAPTURES a runtime value: `User remember [X] text as {{var}}`. */
|
|
17
|
+
export const RUNTIME_CAPTURE = /\bremember\b/i;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Variable names a scenario defines at RUN TIME rather than reading from
|
|
21
|
+
* test-data: values captured from the page (`remember … as {{v}}`) and values
|
|
22
|
+
* bound by a capability precondition tag (`@query:`, `@api:`, `@dataFactory:`).
|
|
23
|
+
* The compiler already treats these as "captured" (DataResolver.registerCaptured);
|
|
24
|
+
* the delivery matrix must agree or it demands a static value that cannot exist.
|
|
25
|
+
*/
|
|
26
|
+
export function runtimeVarsOf(stepTexts: string[], tags: string[]): Set<string> {
|
|
27
|
+
const out = new Set<string>();
|
|
28
|
+
for (const text of stepTexts) {
|
|
29
|
+
if (!RUNTIME_CAPTURE.test(text)) continue;
|
|
30
|
+
const m = text.match(/\bas\s+\{\{\s*([^}]+?)\s*\}\}/i);
|
|
31
|
+
if (m) out.add(m[1].trim());
|
|
32
|
+
}
|
|
33
|
+
// A capability precondition binds its result under the endpoint/query/recipe
|
|
34
|
+
// name, so `{{<name>}}` and `{{<name>.field}}` exist only during the run.
|
|
35
|
+
for (const t of tags) {
|
|
36
|
+
const m = t.match(/^@(?:query|api|dataFactory):([^(\s]+)/);
|
|
37
|
+
if (m) out.add(m[1]);
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// `[Email] field` → `Email field` (the visible label + its element type).
|
|
43
|
+
function deRef(text: string): string {
|
|
44
|
+
return text.replace(/\[([^\]]+)\]/g, '$1');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function sentence(text: string): string {
|
|
48
|
+
// Trim the ENDS only — internal whitespace may be the test data itself
|
|
49
|
+
// (a padded email, a spaces-only value); collapsing it would silently
|
|
50
|
+
// change what the tester types (review GAP-04).
|
|
51
|
+
let s = text.trim();
|
|
52
|
+
if (!s) return s;
|
|
53
|
+
s = s.charAt(0).toUpperCase() + s.slice(1);
|
|
54
|
+
if (!/[.!?…"]$/.test(s)) s += '.';
|
|
55
|
+
return s;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Render one action step in the imperative. Pattern table covers the common
|
|
60
|
+
* sungen step verbs; anything unmatched falls back to actor-stripped text —
|
|
61
|
+
* still readable, never a raw `User fill`.
|
|
62
|
+
*/
|
|
63
|
+
export function renderAction(raw: string): string {
|
|
64
|
+
let s = raw.trim().replace(/^(User|The user)\s+/i, '');
|
|
65
|
+
// `remember … as {{v}}` DECLARES a runtime variable — the trailing token is a
|
|
66
|
+
// name, not a value to resolve. Quote it so no `{{brace}}` reaches the sheet
|
|
67
|
+
// whatever rule matches below.
|
|
68
|
+
if (RUNTIME_CAPTURE.test(s)) s = s.replace(/\bas\s+\{\{\s*([^}]+?)\s*\}\}/i, 'as "$1"');
|
|
69
|
+
|
|
70
|
+
const rules: Array<[RegExp, (m: RegExpMatchArray) => string]> = [
|
|
71
|
+
// remember [X] (text|value) as "v" — capture a value for a later comparison
|
|
72
|
+
[/^remembers? \[([^\]]+)\]\s*(text|value)?\s*as "([^"]+)"$/i,
|
|
73
|
+
(m) => `Note the ${m[1]} ${m[2] || 'text'} for later (as "${m[3]}")`],
|
|
74
|
+
// fill [X] field with V
|
|
75
|
+
[/^fills? \[([^\]]+)\][a-z ]* with (.+)$/i, (m) => `Enter ${m[2]} in the ${m[1]} field`],
|
|
76
|
+
// clear [X] field
|
|
77
|
+
[/^clears? \[([^\]]+)\](.*)$/i, (m) => `Clear the ${m[1]}${m[2] || ' field'}`],
|
|
78
|
+
// click [X] <type>
|
|
79
|
+
[/^clicks? \[([^\]]+)\]\s*(\w+)?$/i, (m) => `Click the ${m[1]}${m[2] ? ` ${m[2]}` : ''}`],
|
|
80
|
+
// press <Key> at/in [X] field
|
|
81
|
+
[/^press(?:es)? (.+?) (?:at|in|on|inside) \[([^\]]+)\](?: field)?$/i, (m) => `Press ${m[1]} in the ${m[2]} field`],
|
|
82
|
+
[/^press(?:es)? (.+)$/i, (m) => `Press ${m[1]}`],
|
|
83
|
+
// select V in/from [X] dropdown
|
|
84
|
+
[/^selects? (.+?) (?:in|from) \[([^\]]+)\]\s*(\w+)?$/i, (m) => `Select ${m[1]} in the ${m[2]}${m[3] ? ` ${m[3]}` : ''}`],
|
|
85
|
+
// check/uncheck [X] checkbox
|
|
86
|
+
[/^(un)?checks? \[([^\]]+)\]\s*(\w+)?$/i, (m) => `${m[1] ? 'Uncheck' : 'Check'} the ${m[2]}${m[3] ? ` ${m[3]}` : ''}`],
|
|
87
|
+
// hover [X]
|
|
88
|
+
[/^hovers? (?:over )?\[([^\]]+)\]\s*(\w+)?$/i, (m) => `Hover over the ${m[1]}${m[2] ? ` ${m[2]}` : ''}`],
|
|
89
|
+
// upload V to [X]
|
|
90
|
+
[/^uploads? (.+?) (?:to|into) \[([^\]]+)\]\s*(\w+)?$/i, (m) => `Upload ${m[1]} to the ${m[2]}${m[3] ? ` ${m[3]}` : ''}`],
|
|
91
|
+
// is on [X] page (as an action = navigate)
|
|
92
|
+
[/^is on \[([^\]]+)\] page(.*)$/i, (m) => `Open the ${m[1]} page${m[2] ?? ''}`],
|
|
93
|
+
// wait for [X] <type> (is )?visible
|
|
94
|
+
[/^waits? for \[([^\]]+)\]\s*(\w+)?(?: is)?(?: visible)?$/i, (m) => `Wait until the ${m[1]}${m[2] ? ` ${m[2]}` : ''} is visible`],
|
|
95
|
+
// scroll to [X]
|
|
96
|
+
[/^scrolls? (?:to|into) \[([^\]]+)\]\s*(\w+)?$/i, (m) => `Scroll to the ${m[1]}${m[2] ? ` ${m[2]}` : ''}`],
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
for (const [re, out] of rules) {
|
|
100
|
+
const m = s.match(re);
|
|
101
|
+
if (m) return sentence(deRef(out(m)));
|
|
102
|
+
}
|
|
103
|
+
return sentence(deRef(s));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Render one expected step as an observable assertion (no tester action, no
|
|
108
|
+
* `should`, no DSL `User see`).
|
|
109
|
+
*/
|
|
110
|
+
export function renderExpected(raw: string): string {
|
|
111
|
+
// A capture step is an INSTRUCTION wherever it sits — Gherkin often chains
|
|
112
|
+
// `remember … / remember … / expect a is not b` inside one Then block. Render
|
|
113
|
+
// it as the note-it-down action, never as an assertion (and never leaving the
|
|
114
|
+
// declared `{{name}}` token behind).
|
|
115
|
+
if (RUNTIME_CAPTURE.test(raw) && /\bas\s+\{\{/.test(raw)) return renderAction(raw);
|
|
116
|
+
let s = raw.trim().replace(/^(User|The user)\s+/i, '');
|
|
117
|
+
|
|
118
|
+
const rules: Array<[RegExp, (m: RegExpMatchArray) => string]> = [
|
|
119
|
+
// expect A is (not) B — comparing two captured values (order matters: "is not" first)
|
|
120
|
+
[/^expects? (.+?) is not (.+)$/i, (m) => `${m[1]} differs from ${m[2]}`],
|
|
121
|
+
[/^expects? (.+?) is (.+)$/i, (m) => `${m[1]} equals ${m[2]}`],
|
|
122
|
+
// see all [X] <type> contain(s) V — every matching element, not just the first
|
|
123
|
+
[/^sees? all \[([^\]]+)\]\s*(\w+)? (?:contains?|match(?:es)?|belongs?) (.+)$/i,
|
|
124
|
+
(m) => `Every ${m[1]}${m[2] ? ` ${m[2]}` : ''} contains ${m[3]}`],
|
|
125
|
+
// see all [X] <type> contain [Child] <type> — structural per-item proof
|
|
126
|
+
[/^sees? all \[([^\]]+)\]\s*(\w+)? (?:contains?|includes?) \[([^\]]+)\]\s*(\w+)?$/i,
|
|
127
|
+
(m) => `Every ${m[1]}${m[2] ? ` ${m[2]}` : ''} contains a ${m[3]}${m[4] ? ` ${m[4]}` : ''}`],
|
|
128
|
+
// see [X] page
|
|
129
|
+
[/^sees? \[([^\]]+)\] page$/i, (m) => `The ${m[1]} page is displayed`],
|
|
130
|
+
// see [X] <type> with V
|
|
131
|
+
[/^sees? \[([^\]]+)\]\s*(\w+)? with (.+)$/i, (m) => `The ${m[1]}${m[2] ? ` ${m[2]}` : ''} shows ${m[3]}`],
|
|
132
|
+
// see [X] <type> contains V
|
|
133
|
+
[/^sees? \[([^\]]+)\]\s*(\w+)? contains (.+)$/i, (m) => `The ${m[1]}${m[2] ? ` ${m[2]}` : ''} contains ${m[3]}`],
|
|
134
|
+
// see [X] <type> has text V
|
|
135
|
+
[/^sees? \[([^\]]+)\]\s*(\w+)? has text (.+)$/i, (m) => `The ${m[1]}${m[2] ? ` ${m[2]}` : ''} shows ${m[3]}`],
|
|
136
|
+
// see [X] <type> is hidden / is disabled / is enabled / …
|
|
137
|
+
[/^sees? \[([^\]]+)\]\s*(\w+)? is (.+)$/i, (m) => `The ${m[1]}${m[2] ? ` ${m[2]}` : ''} is ${m[3]}`],
|
|
138
|
+
// not see [X] <type>
|
|
139
|
+
[/^(?:do(?:es)? )?not sees? \[([^\]]+)\]\s*(\w+)?$/i, (m) => `The ${m[1]}${m[2] ? ` ${m[2]}` : ''} is not displayed`],
|
|
140
|
+
// see [X] <type>
|
|
141
|
+
[/^sees? \[([^\]]+)\]\s*(\w+)?$/i, (m) => `The ${m[1]}${m[2] ? ` ${m[2]}` : ''} is visible`],
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
for (const [re, out] of rules) {
|
|
145
|
+
const m = s.match(re);
|
|
146
|
+
if (m) return sentence(deRef(out(m)));
|
|
147
|
+
}
|
|
148
|
+
return sentence(deRef(s));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Precondition wording: a state, not an action ("The user is signed out."). */
|
|
152
|
+
export function renderPrecondition(raw: string): string {
|
|
153
|
+
const s = raw.trim().replace(/^(User|The user)\s+/i, '');
|
|
154
|
+
const m = s.match(/^is on \[([^\]]+)\] page(.*)$/i);
|
|
155
|
+
if (m) return sentence(`The user is on the ${m[1]} page${m[2] ?? ''}`);
|
|
156
|
+
// "wait for [X] <type> (is) visible" is a STATE once established, not an action.
|
|
157
|
+
const w = s.match(/^waits? for \[([^\]]+)\]\s*(\w+)?(?: is)?(?: visible)?$/i);
|
|
158
|
+
if (w) return sentence(`The ${w[1]}${w[2] ? ` ${w[2]}` : ''} is visible`);
|
|
159
|
+
const auth = s.match(/^is (signed|logged) (in|out)(.*)$/i);
|
|
160
|
+
if (auth) return sentence(`The user is ${auth[1]} ${auth[2]}${auth[3] ?? ''}`);
|
|
161
|
+
return sentence(deRef(`The user ${s.charAt(0).toLowerCase()}${s.slice(1)}`));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* A manual `Setup:` line is an INSTRUCTION the tester performs to establish the
|
|
166
|
+
* state — imperative reads correctly ("Seed the locked account."), while
|
|
167
|
+
* "The user seed …" is broken grammar (review GAP-08).
|
|
168
|
+
*/
|
|
169
|
+
export function renderSetupInstruction(raw: string): string {
|
|
170
|
+
return renderAction(raw);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// Manual `# Tester verifies:` comment classification (structured, label-free)
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
export interface ManualProcedure {
|
|
178
|
+
/** Setup/Precondition/Arrange lines — the state to establish first. */
|
|
179
|
+
preconditions: string[];
|
|
180
|
+
/** Action/unlabelled lines — the imperative procedure. */
|
|
181
|
+
actions: string[];
|
|
182
|
+
/** Observable/Expect/Result/Assert lines — the observable outcome. */
|
|
183
|
+
expected: string[];
|
|
184
|
+
/** Oracle/Verify lines — HOW to check (tools, panes, queries). */
|
|
185
|
+
verification: string[];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Split a manual scenario's numbered comment lines into the four structured
|
|
190
|
+
* fields. Labels are consumed (structured), never left inside the prose —
|
|
191
|
+
* review feedback §10.2(3). Continuation lines append to the previous item;
|
|
192
|
+
* pre-amble (rationale/header/dividers) is skipped.
|
|
193
|
+
*/
|
|
194
|
+
export function classifyManualComments(comments: string[]): ManualProcedure {
|
|
195
|
+
const out: ManualProcedure = { preconditions: [], actions: [], expected: [], verification: [] };
|
|
196
|
+
let last: { list: string[]; idx: number } | null = null;
|
|
197
|
+
|
|
198
|
+
const bucketOf = (label: string): keyof ManualProcedure => {
|
|
199
|
+
if (/setup|precondition|arrange|given/i.test(label)) return 'preconditions';
|
|
200
|
+
if (/oracle|verify|verification|how to check/i.test(label)) return 'verification';
|
|
201
|
+
if (/observ|expect|result|then|assert/i.test(label)) return 'expected';
|
|
202
|
+
return 'actions';
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
for (const raw of comments) {
|
|
206
|
+
const line = raw.trim();
|
|
207
|
+
if (!line) continue;
|
|
208
|
+
if (/^[-=*_]{2,}/.test(line)) { last = null; continue; }
|
|
209
|
+
const m = line.match(/^\d+[.)]\s*(?:([A-Za-z][A-Za-z /]*?):\s*)?(.+)$/);
|
|
210
|
+
if (m) {
|
|
211
|
+
const label = (m[1] || '').trim();
|
|
212
|
+
const text = m[2].trim();
|
|
213
|
+
const list = out[bucketOf(label)];
|
|
214
|
+
list.push(text);
|
|
215
|
+
last = { list, idx: list.length - 1 };
|
|
216
|
+
} else if (last) {
|
|
217
|
+
last.list[last.idx] += ' ' + line;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return out;
|
|
221
|
+
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { FeatureMetadata, OrderedStep, ScenarioMetadata, SpecFileData, SpecTest } from './types';
|
|
12
12
|
import { isStepsBaseScenario, isSampleScaffoldScenario, extractTestcaseType } from './feature-parser';
|
|
13
|
+
import { SCENARIO_ID } from './feature-parser';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Parse a @manual scenario's `# Tester verifies:` comment block into procedure vs
|
|
@@ -162,7 +163,7 @@ function findMatchingSpecTest(scenario: ScenarioMetadata, tests: SpecTest[]): Sp
|
|
|
162
163
|
if (exact) return exact;
|
|
163
164
|
|
|
164
165
|
// Fallback: match by VP-ID prefix (ignore trailing text differences)
|
|
165
|
-
const vpMatch = scenario.name.match(
|
|
166
|
+
const vpMatch = scenario.name.match(SCENARIO_ID);
|
|
166
167
|
if (vpMatch) {
|
|
167
168
|
const vpId = vpMatch[1];
|
|
168
169
|
const byVp = tests.find((t) => t.vpId === vpId);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import * as fs from 'fs';
|
|
7
7
|
import { SpecTest, SpecFileData } from './types';
|
|
8
|
+
import { SCENARIO_ID } from './feature-parser';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Extract the inner body of a test('title', async (...) => { BODY }) block.
|
|
@@ -165,7 +166,7 @@ function classifyBlock(block: CodeBlock): 'precondition' | 'step' | 'expectation
|
|
|
165
166
|
* Try to extract VP ID (e.g., VP-UI-001) from the start of a scenario title.
|
|
166
167
|
*/
|
|
167
168
|
function extractVpId(title: string): string | undefined {
|
|
168
|
-
const match = title.match(
|
|
169
|
+
const match = title.match(new RegExp(SCENARIO_ID.source + '\\b'));
|
|
169
170
|
return match ? match[1] : undefined;
|
|
170
171
|
}
|
|
171
172
|
|
|
@@ -143,6 +143,7 @@ export function renderReportHeaderBand(
|
|
|
143
143
|
ws: ExcelJS.Worksheet,
|
|
144
144
|
titleLabel: string,
|
|
145
145
|
sungenVersion?: string,
|
|
146
|
+
formNo: string = 'BM-2-901-13',
|
|
146
147
|
): void {
|
|
147
148
|
const issueDate = (() => {
|
|
148
149
|
const d = new Date();
|
|
@@ -187,9 +188,9 @@ export function renderReportHeaderBand(
|
|
|
187
188
|
d1.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true };
|
|
188
189
|
d1.border = allBordersBlack;
|
|
189
190
|
|
|
190
|
-
// G1 — No:
|
|
191
|
+
// G1 — No: <form number> (the company ISO form id; configurable via the delivery map)
|
|
191
192
|
const g1 = ws.getCell('G1');
|
|
192
|
-
g1.value =
|
|
193
|
+
g1.value = `No: ${formNo}`;
|
|
193
194
|
g1.font = { size: 12, name: TIMES };
|
|
194
195
|
g1.alignment = { vertical: 'middle' };
|
|
195
196
|
g1.border = { top: thinBlack, left: thinBlack, right: thinBlack };
|