@vijaypjavvadi/bdd2pw 4.3.0 → 4.4.0
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/CHANGELOG.md +182 -0
- package/README.md +66 -10
- package/dist/cli.js +79 -0
- package/dist/cli.js.map +1 -1
- package/dist/http/schemas.d.ts +4 -4
- package/dist/index.js +27 -1
- package/dist/index.js.map +1 -1
- package/dist/llm/anthropicClient.d.ts +7 -0
- package/dist/llm/anthropicClient.js +12 -1
- package/dist/llm/anthropicClient.js.map +1 -1
- package/dist/llm/geminiClient.d.ts +2 -0
- package/dist/llm/geminiClient.js +6 -0
- package/dist/llm/geminiClient.js.map +1 -1
- package/dist/llm/llmStepMatcher.js +43 -4
- package/dist/llm/llmStepMatcher.js.map +1 -1
- package/dist/llm/openaiClient.d.ts +2 -0
- package/dist/llm/openaiClient.js +6 -0
- package/dist/llm/openaiClient.js.map +1 -1
- package/dist/llm/telemetry.js +2 -1
- package/dist/llm/telemetry.js.map +1 -1
- package/dist/llm/types.d.ts +14 -0
- package/dist/reports/coverage.d.ts +132 -0
- package/dist/reports/coverage.js +360 -0
- package/dist/reports/coverage.js.map +1 -0
- package/dist/reports/metaSidecar.d.ts +21 -0
- package/dist/reports/metaSidecar.js +20 -0
- package/dist/reports/metaSidecar.js.map +1 -1
- package/dist/reports/quarantineStats.d.ts +102 -0
- package/dist/reports/quarantineStats.js +319 -0
- package/dist/reports/quarantineStats.js.map +1 -0
- package/dist/transformers/stepMatcher.d.ts +10 -0
- package/dist/transformers/stepMatcher.js +59 -5
- package/dist/transformers/stepMatcher.js.map +1 -1
- package/dist/transformers/stepNormalizer.js +1 -1
- package/dist/transformers/stepNormalizer.js.map +1 -1
- package/dist/types.d.ts +52 -0
- package/package.json +1 -1
|
@@ -39,6 +39,27 @@ export interface MetaSidecarStep {
|
|
|
39
39
|
locator?: string;
|
|
40
40
|
/** Matcher name or method name when the binding has one. */
|
|
41
41
|
assertion?: string;
|
|
42
|
+
/**
|
|
43
|
+
* v4.4.0 — provenance. `null` when this step was bound by the LLM
|
|
44
|
+
* fallback; a `{ ruleId, pack? }` object when bound by the
|
|
45
|
+
* deterministic rule table. Absent only when the binding was
|
|
46
|
+
* emitted before v4.4 (old sidecars remain parseable).
|
|
47
|
+
*/
|
|
48
|
+
rule?: null | {
|
|
49
|
+
ruleId: string;
|
|
50
|
+
pack?: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* v4.4.0 — populated iff this step was bound by the LLM fallback.
|
|
54
|
+
* Consumed by `bdd2pw quarantine-stats` and `bdd2pw coverage`.
|
|
55
|
+
*/
|
|
56
|
+
llm?: {
|
|
57
|
+
provider: "anthropic" | "openai" | "gemini";
|
|
58
|
+
model: string;
|
|
59
|
+
cached: boolean;
|
|
60
|
+
governance: "sanitised" | "skipped";
|
|
61
|
+
at: string;
|
|
62
|
+
};
|
|
42
63
|
}
|
|
43
64
|
export interface MetaSidecarScenario {
|
|
44
65
|
name: string;
|
|
@@ -80,6 +80,26 @@ function bindingsToMetaSteps(bindings) {
|
|
|
80
80
|
step.locator = b.assertion.locator;
|
|
81
81
|
step.assertion = b.assertion.matcher;
|
|
82
82
|
}
|
|
83
|
+
// v4.4.0 — provenance passthrough.
|
|
84
|
+
// LLM binding → `rule: null` + full `llm` block.
|
|
85
|
+
// Rule binding → `rule: { ruleId, pack? }`.
|
|
86
|
+
// No provenance (legacy binding) → both fields absent.
|
|
87
|
+
if (b.provenance?.source === "llm" && b.provenance.llm) {
|
|
88
|
+
step.rule = null;
|
|
89
|
+
step.llm = {
|
|
90
|
+
provider: b.provenance.llm.provider,
|
|
91
|
+
model: b.provenance.llm.model,
|
|
92
|
+
cached: b.provenance.llm.cached,
|
|
93
|
+
governance: b.provenance.llm.governance,
|
|
94
|
+
at: b.provenance.llm.at,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
else if (b.provenance?.source === "rule" && b.provenance.ruleId) {
|
|
98
|
+
step.rule = {
|
|
99
|
+
ruleId: b.provenance.ruleId,
|
|
100
|
+
...(b.provenance.pack ? { pack: b.provenance.pack } : {}),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
83
103
|
return step;
|
|
84
104
|
});
|
|
85
105
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metaSidecar.js","sourceRoot":"","sources":["../../src/reports/metaSidecar.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;;
|
|
1
|
+
{"version":3,"file":"metaSidecar.js","sourceRoot":"","sources":["../../src/reports/metaSidecar.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v4.4.0 Slice 1 — `bdd2pw quarantine-stats <repo>`.
|
|
3
|
+
*
|
|
4
|
+
* Aggregates the LLM-provenance data written by v4.4.0's scaffold path
|
|
5
|
+
* (`*.spec.meta.json` sidecars + `artefacts/candidate-rules.jsonl`)
|
|
6
|
+
* into a per-feature / per-pack report. The point is to make the two
|
|
7
|
+
* lanes measurable:
|
|
8
|
+
*
|
|
9
|
+
* trusted lane — scenarios with NO `@llm-generated` tag; every
|
|
10
|
+
* step bound by the deterministic rule table.
|
|
11
|
+
* review lane — scenarios with the `@llm-generated` tag; at
|
|
12
|
+
* least one step bound by the LLM fallback.
|
|
13
|
+
*
|
|
14
|
+
* Report shape (both stdout and `artefacts/quarantine-stats.json`):
|
|
15
|
+
*
|
|
16
|
+
* {
|
|
17
|
+
* "version": "<bdd2pw version>",
|
|
18
|
+
* "generatedAt": "<ISO-8601>",
|
|
19
|
+
* "repo": "<absolute path>",
|
|
20
|
+
* "totals": {
|
|
21
|
+
* "sidecars": <int>,
|
|
22
|
+
* "scenarios": { "total": N, "llmGenerated": N, "deterministic": N },
|
|
23
|
+
* "steps": { "total": N, "byRule": N, "byLlm": N },
|
|
24
|
+
* "llmCacheHitShare": <0..1 | null>
|
|
25
|
+
* },
|
|
26
|
+
* "byFeature": [{ source, scenarios, steps }, ...],
|
|
27
|
+
* "promotions": {
|
|
28
|
+
* "candidateStepsSurveyed": <int>,
|
|
29
|
+
* "promoted": <int>,
|
|
30
|
+
* "promotedStepTexts": [ ... ]
|
|
31
|
+
* },
|
|
32
|
+
* "sidecarsScanned": ["<path>", ...]
|
|
33
|
+
* }
|
|
34
|
+
*
|
|
35
|
+
* The reader is best-effort — a malformed sidecar is skipped with a
|
|
36
|
+
* warning and does not crash the run. Only I/O failures on the repo
|
|
37
|
+
* root itself surface as non-zero exit codes; the report is not a gate.
|
|
38
|
+
*/
|
|
39
|
+
/** Top-level per-repo summary. */
|
|
40
|
+
export interface QuarantineStats {
|
|
41
|
+
version: string;
|
|
42
|
+
generatedAt: string;
|
|
43
|
+
repo: string;
|
|
44
|
+
totals: {
|
|
45
|
+
sidecars: number;
|
|
46
|
+
scenarios: {
|
|
47
|
+
total: number;
|
|
48
|
+
llmGenerated: number;
|
|
49
|
+
deterministic: number;
|
|
50
|
+
};
|
|
51
|
+
steps: {
|
|
52
|
+
total: number;
|
|
53
|
+
byRule: number;
|
|
54
|
+
byLlm: number;
|
|
55
|
+
};
|
|
56
|
+
/** null when no LLM steps were recorded (avoids fake precision). */
|
|
57
|
+
llmCacheHitShare: number | null;
|
|
58
|
+
};
|
|
59
|
+
byFeature: {
|
|
60
|
+
source: string;
|
|
61
|
+
scenarios: {
|
|
62
|
+
total: number;
|
|
63
|
+
llmGenerated: number;
|
|
64
|
+
deterministic: number;
|
|
65
|
+
};
|
|
66
|
+
steps: {
|
|
67
|
+
total: number;
|
|
68
|
+
byRule: number;
|
|
69
|
+
byLlm: number;
|
|
70
|
+
};
|
|
71
|
+
}[];
|
|
72
|
+
promotions: {
|
|
73
|
+
candidateStepsSurveyed: number;
|
|
74
|
+
promoted: number;
|
|
75
|
+
promotedStepTexts: string[];
|
|
76
|
+
};
|
|
77
|
+
sidecarsScanned: string[];
|
|
78
|
+
warnings: string[];
|
|
79
|
+
}
|
|
80
|
+
export interface AnalyseQuarantineStatsOptions {
|
|
81
|
+
/** Repo root containing tests/, artefacts/. */
|
|
82
|
+
repo: string;
|
|
83
|
+
/** Package version to embed. Propagated from the CLI. */
|
|
84
|
+
version: string;
|
|
85
|
+
/** Optional override for the output path. Default: <repo>/artefacts/quarantine-stats.json. */
|
|
86
|
+
outputPath?: string;
|
|
87
|
+
}
|
|
88
|
+
export interface AnalyseQuarantineStatsResult {
|
|
89
|
+
stats: QuarantineStats;
|
|
90
|
+
outputPath: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Walk `<repo>/tests/**\/*.spec.meta.json` and produce the aggregate
|
|
94
|
+
* report. Writes the JSON payload alongside the artefacts directory.
|
|
95
|
+
*/
|
|
96
|
+
export declare function analyseQuarantineStats(opts: AnalyseQuarantineStatsOptions): Promise<AnalyseQuarantineStatsResult>;
|
|
97
|
+
/**
|
|
98
|
+
* Human-readable one-page render. Kept separate from the report
|
|
99
|
+
* generation so tests can assert on the JSON shape without parsing
|
|
100
|
+
* text.
|
|
101
|
+
*/
|
|
102
|
+
export declare function renderQuarantineStatsTable(stats: QuarantineStats): string;
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* v4.4.0 Slice 1 — `bdd2pw quarantine-stats <repo>`.
|
|
4
|
+
*
|
|
5
|
+
* Aggregates the LLM-provenance data written by v4.4.0's scaffold path
|
|
6
|
+
* (`*.spec.meta.json` sidecars + `artefacts/candidate-rules.jsonl`)
|
|
7
|
+
* into a per-feature / per-pack report. The point is to make the two
|
|
8
|
+
* lanes measurable:
|
|
9
|
+
*
|
|
10
|
+
* trusted lane — scenarios with NO `@llm-generated` tag; every
|
|
11
|
+
* step bound by the deterministic rule table.
|
|
12
|
+
* review lane — scenarios with the `@llm-generated` tag; at
|
|
13
|
+
* least one step bound by the LLM fallback.
|
|
14
|
+
*
|
|
15
|
+
* Report shape (both stdout and `artefacts/quarantine-stats.json`):
|
|
16
|
+
*
|
|
17
|
+
* {
|
|
18
|
+
* "version": "<bdd2pw version>",
|
|
19
|
+
* "generatedAt": "<ISO-8601>",
|
|
20
|
+
* "repo": "<absolute path>",
|
|
21
|
+
* "totals": {
|
|
22
|
+
* "sidecars": <int>,
|
|
23
|
+
* "scenarios": { "total": N, "llmGenerated": N, "deterministic": N },
|
|
24
|
+
* "steps": { "total": N, "byRule": N, "byLlm": N },
|
|
25
|
+
* "llmCacheHitShare": <0..1 | null>
|
|
26
|
+
* },
|
|
27
|
+
* "byFeature": [{ source, scenarios, steps }, ...],
|
|
28
|
+
* "promotions": {
|
|
29
|
+
* "candidateStepsSurveyed": <int>,
|
|
30
|
+
* "promoted": <int>,
|
|
31
|
+
* "promotedStepTexts": [ ... ]
|
|
32
|
+
* },
|
|
33
|
+
* "sidecarsScanned": ["<path>", ...]
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* The reader is best-effort — a malformed sidecar is skipped with a
|
|
37
|
+
* warning and does not crash the run. Only I/O failures on the repo
|
|
38
|
+
* root itself surface as non-zero exit codes; the report is not a gate.
|
|
39
|
+
*/
|
|
40
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
41
|
+
if (k2 === undefined) k2 = k;
|
|
42
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
43
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
44
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
45
|
+
}
|
|
46
|
+
Object.defineProperty(o, k2, desc);
|
|
47
|
+
}) : (function(o, m, k, k2) {
|
|
48
|
+
if (k2 === undefined) k2 = k;
|
|
49
|
+
o[k2] = m[k];
|
|
50
|
+
}));
|
|
51
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
52
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
53
|
+
}) : function(o, v) {
|
|
54
|
+
o["default"] = v;
|
|
55
|
+
});
|
|
56
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
57
|
+
var ownKeys = function(o) {
|
|
58
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
59
|
+
var ar = [];
|
|
60
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
61
|
+
return ar;
|
|
62
|
+
};
|
|
63
|
+
return ownKeys(o);
|
|
64
|
+
};
|
|
65
|
+
return function (mod) {
|
|
66
|
+
if (mod && mod.__esModule) return mod;
|
|
67
|
+
var result = {};
|
|
68
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
69
|
+
__setModuleDefault(result, mod);
|
|
70
|
+
return result;
|
|
71
|
+
};
|
|
72
|
+
})();
|
|
73
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
74
|
+
exports.analyseQuarantineStats = analyseQuarantineStats;
|
|
75
|
+
exports.renderQuarantineStatsTable = renderQuarantineStatsTable;
|
|
76
|
+
const fs = __importStar(require("fs-extra"));
|
|
77
|
+
const path = __importStar(require("path"));
|
|
78
|
+
/**
|
|
79
|
+
* Walk `<repo>/tests/**\/*.spec.meta.json` and produce the aggregate
|
|
80
|
+
* report. Writes the JSON payload alongside the artefacts directory.
|
|
81
|
+
*/
|
|
82
|
+
async function analyseQuarantineStats(opts) {
|
|
83
|
+
const repo = path.resolve(opts.repo);
|
|
84
|
+
const testsDir = path.join(repo, "tests");
|
|
85
|
+
const warnings = [];
|
|
86
|
+
const sidecarsScanned = [];
|
|
87
|
+
const files = [];
|
|
88
|
+
if (await fs.pathExists(testsDir)) {
|
|
89
|
+
await walkForSidecars(testsDir, files);
|
|
90
|
+
}
|
|
91
|
+
let scenariosTotal = 0;
|
|
92
|
+
let scenariosLlm = 0;
|
|
93
|
+
let stepsTotal = 0;
|
|
94
|
+
let stepsByRule = 0;
|
|
95
|
+
let stepsByLlm = 0;
|
|
96
|
+
let cachedLlmSteps = 0;
|
|
97
|
+
const byFeature = [];
|
|
98
|
+
for (const file of files) {
|
|
99
|
+
let sidecar;
|
|
100
|
+
try {
|
|
101
|
+
sidecar = JSON.parse(await fs.readFile(file, "utf8"));
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
warnings.push(`Malformed sidecar (skipped): ${path.relative(repo, file)} — ${e instanceof Error ? e.message : String(e)}`);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (!sidecar.scenarios || sidecar.scenarios.length === 0)
|
|
108
|
+
continue;
|
|
109
|
+
sidecarsScanned.push(path.relative(repo, file));
|
|
110
|
+
let featureScenariosLlm = 0;
|
|
111
|
+
let featureStepsByRule = 0;
|
|
112
|
+
let featureStepsByLlm = 0;
|
|
113
|
+
let featureStepsTotal = 0;
|
|
114
|
+
for (const scenario of sidecar.scenarios) {
|
|
115
|
+
const scenarioIsLlm = (scenario.tags ?? []).includes("@llm-generated");
|
|
116
|
+
scenariosTotal += 1;
|
|
117
|
+
if (scenarioIsLlm) {
|
|
118
|
+
scenariosLlm += 1;
|
|
119
|
+
featureScenariosLlm += 1;
|
|
120
|
+
}
|
|
121
|
+
for (const step of scenario.steps ?? []) {
|
|
122
|
+
stepsTotal += 1;
|
|
123
|
+
featureStepsTotal += 1;
|
|
124
|
+
if (step.llm) {
|
|
125
|
+
stepsByLlm += 1;
|
|
126
|
+
featureStepsByLlm += 1;
|
|
127
|
+
if (step.llm.cached)
|
|
128
|
+
cachedLlmSteps += 1;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
stepsByRule += 1;
|
|
132
|
+
featureStepsByRule += 1;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const scenarioCount = sidecar.scenarios.length;
|
|
137
|
+
byFeature.push({
|
|
138
|
+
source: sidecar.source ?? path.relative(repo, file),
|
|
139
|
+
scenarios: {
|
|
140
|
+
total: scenarioCount,
|
|
141
|
+
llmGenerated: featureScenariosLlm,
|
|
142
|
+
deterministic: scenarioCount - featureScenariosLlm,
|
|
143
|
+
},
|
|
144
|
+
steps: {
|
|
145
|
+
total: featureStepsTotal,
|
|
146
|
+
byRule: featureStepsByRule,
|
|
147
|
+
byLlm: featureStepsByLlm,
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const promotions = await computePromotions(repo, warnings);
|
|
152
|
+
const llmCacheHitShare = stepsByLlm > 0 ? cachedLlmSteps / stepsByLlm : null;
|
|
153
|
+
const stats = {
|
|
154
|
+
version: opts.version,
|
|
155
|
+
generatedAt: new Date().toISOString(),
|
|
156
|
+
repo,
|
|
157
|
+
totals: {
|
|
158
|
+
sidecars: sidecarsScanned.length,
|
|
159
|
+
scenarios: {
|
|
160
|
+
total: scenariosTotal,
|
|
161
|
+
llmGenerated: scenariosLlm,
|
|
162
|
+
deterministic: scenariosTotal - scenariosLlm,
|
|
163
|
+
},
|
|
164
|
+
steps: {
|
|
165
|
+
total: stepsTotal,
|
|
166
|
+
byRule: stepsByRule,
|
|
167
|
+
byLlm: stepsByLlm,
|
|
168
|
+
},
|
|
169
|
+
llmCacheHitShare,
|
|
170
|
+
},
|
|
171
|
+
byFeature,
|
|
172
|
+
promotions,
|
|
173
|
+
sidecarsScanned,
|
|
174
|
+
warnings,
|
|
175
|
+
};
|
|
176
|
+
const outputPath = opts.outputPath ?? path.join(repo, "artefacts", "quarantine-stats.json");
|
|
177
|
+
await fs.ensureDir(path.dirname(outputPath));
|
|
178
|
+
await fs.writeFile(outputPath, JSON.stringify(stats, null, 2) + "\n", "utf8");
|
|
179
|
+
return { stats, outputPath };
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Depth-first walk of `dir`, appending every path ending in
|
|
183
|
+
* `.spec.meta.json`. Node's built-in `fs.readdir` is enough; we don't
|
|
184
|
+
* take a dep on glob for one pattern.
|
|
185
|
+
*/
|
|
186
|
+
async function walkForSidecars(dir, out) {
|
|
187
|
+
let entries;
|
|
188
|
+
try {
|
|
189
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
for (const entry of entries) {
|
|
195
|
+
const full = path.join(dir, entry.name);
|
|
196
|
+
if (entry.isDirectory()) {
|
|
197
|
+
await walkForSidecars(full, out);
|
|
198
|
+
}
|
|
199
|
+
else if (entry.isFile() && entry.name.endsWith(".spec.meta.json")) {
|
|
200
|
+
out.push(full);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Load `artefacts/candidate-rules.jsonl` and figure out which LLM-
|
|
206
|
+
* emitted step texts are NO LONGER present as `@llm-generated`
|
|
207
|
+
* scenarios in the current sidecars — i.e. the rule table caught up
|
|
208
|
+
* and the promotion completed.
|
|
209
|
+
*
|
|
210
|
+
* The measure is intentionally coarse (unique step-text intersection);
|
|
211
|
+
* a finer version lives in Slice 2's coverage analyzer, which joins
|
|
212
|
+
* step text against the rule table's regex patterns.
|
|
213
|
+
*/
|
|
214
|
+
async function computePromotions(repo, warnings) {
|
|
215
|
+
const candidatesPath = path.join(repo, "artefacts", "candidate-rules.jsonl");
|
|
216
|
+
const surveyedStepTexts = new Set();
|
|
217
|
+
if (await fs.pathExists(candidatesPath)) {
|
|
218
|
+
let raw;
|
|
219
|
+
try {
|
|
220
|
+
raw = await fs.readFile(candidatesPath, "utf8");
|
|
221
|
+
}
|
|
222
|
+
catch (e) {
|
|
223
|
+
warnings.push(`Could not read artefacts/candidate-rules.jsonl: ${e instanceof Error ? e.message : String(e)}`);
|
|
224
|
+
return { candidateStepsSurveyed: 0, promoted: 0, promotedStepTexts: [] };
|
|
225
|
+
}
|
|
226
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
227
|
+
const trimmed = line.trim();
|
|
228
|
+
if (!trimmed)
|
|
229
|
+
continue;
|
|
230
|
+
try {
|
|
231
|
+
const entry = JSON.parse(trimmed);
|
|
232
|
+
if (typeof entry.stepText === "string" && entry.stepText.length > 0) {
|
|
233
|
+
surveyedStepTexts.add(entry.stepText);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
// Ignore one malformed line — the JSONL is best-effort telemetry.
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// Any step-text in the candidates log whose current sidecar entry
|
|
242
|
+
// no longer has an `llm` block is a promotion. Collect them.
|
|
243
|
+
const stillLlmStepTexts = new Set();
|
|
244
|
+
const testsDir = path.join(repo, "tests");
|
|
245
|
+
if (await fs.pathExists(testsDir)) {
|
|
246
|
+
const files = [];
|
|
247
|
+
await walkForSidecars(testsDir, files);
|
|
248
|
+
for (const file of files) {
|
|
249
|
+
try {
|
|
250
|
+
const sidecar = JSON.parse(await fs.readFile(file, "utf8"));
|
|
251
|
+
for (const scenario of sidecar.scenarios ?? []) {
|
|
252
|
+
for (const step of scenario.steps ?? []) {
|
|
253
|
+
if (step.llm && typeof step.text === "string") {
|
|
254
|
+
stillLlmStepTexts.add(step.text);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// Already warned above during the main scan.
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const promoted = [];
|
|
265
|
+
for (const t of surveyedStepTexts) {
|
|
266
|
+
if (!stillLlmStepTexts.has(t))
|
|
267
|
+
promoted.push(t);
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
candidateStepsSurveyed: surveyedStepTexts.size,
|
|
271
|
+
promoted: promoted.length,
|
|
272
|
+
promotedStepTexts: promoted.sort(),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Human-readable one-page render. Kept separate from the report
|
|
277
|
+
* generation so tests can assert on the JSON shape without parsing
|
|
278
|
+
* text.
|
|
279
|
+
*/
|
|
280
|
+
function renderQuarantineStatsTable(stats) {
|
|
281
|
+
const lines = [];
|
|
282
|
+
lines.push("bdd2pw quarantine-stats (v" + stats.version + ")");
|
|
283
|
+
lines.push("repo: " + stats.repo);
|
|
284
|
+
lines.push("generated: " + stats.generatedAt);
|
|
285
|
+
lines.push("");
|
|
286
|
+
lines.push("scenarios: " +
|
|
287
|
+
stats.totals.scenarios.total +
|
|
288
|
+
" total · " +
|
|
289
|
+
stats.totals.scenarios.deterministic +
|
|
290
|
+
" deterministic · " +
|
|
291
|
+
stats.totals.scenarios.llmGenerated +
|
|
292
|
+
" @llm-generated");
|
|
293
|
+
lines.push("steps: " +
|
|
294
|
+
stats.totals.steps.total +
|
|
295
|
+
" total · " +
|
|
296
|
+
stats.totals.steps.byRule +
|
|
297
|
+
" by rule · " +
|
|
298
|
+
stats.totals.steps.byLlm +
|
|
299
|
+
" by LLM");
|
|
300
|
+
if (stats.totals.llmCacheHitShare !== null) {
|
|
301
|
+
lines.push("LLM cache hit share: " +
|
|
302
|
+
(stats.totals.llmCacheHitShare * 100).toFixed(1) +
|
|
303
|
+
"%");
|
|
304
|
+
}
|
|
305
|
+
lines.push("");
|
|
306
|
+
lines.push("promotions: " +
|
|
307
|
+
stats.promotions.promoted +
|
|
308
|
+
"/" +
|
|
309
|
+
stats.promotions.candidateStepsSurveyed +
|
|
310
|
+
" candidate step(s) now rule-bound");
|
|
311
|
+
if (stats.warnings.length > 0) {
|
|
312
|
+
lines.push("");
|
|
313
|
+
lines.push("warnings:");
|
|
314
|
+
for (const w of stats.warnings)
|
|
315
|
+
lines.push(" - " + w);
|
|
316
|
+
}
|
|
317
|
+
return lines.join("\n") + "\n";
|
|
318
|
+
}
|
|
319
|
+
//# sourceMappingURL=quarantineStats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quarantineStats.js","sourceRoot":"","sources":["../../src/reports/quarantineStats.ts"],"names":[],"mappings":"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"}
|
|
@@ -27,6 +27,16 @@ import type { PageObjectIR, ScenarioIR, StepBinding, StepIR } from "../types";
|
|
|
27
27
|
*/
|
|
28
28
|
export type DomainName = "banking" | "healthcare" | "insurance" | "retail" | "gov" | "education" | "telecom";
|
|
29
29
|
export declare function setActiveDomains(domains: readonly DomainName[] | undefined): void;
|
|
30
|
+
/**
|
|
31
|
+
* v4.4.0 Slice 2 — returns the full list of every rule id currently in
|
|
32
|
+
* the registry, ordered as `matchStep()` walks them. Consumed by
|
|
33
|
+
* `bdd2pw coverage` to seed the "dead rules" report (a rule appears in
|
|
34
|
+
* this list but not in any sidecar).
|
|
35
|
+
*/
|
|
36
|
+
export declare function listAllRuleIds(): {
|
|
37
|
+
ruleId: string;
|
|
38
|
+
pack?: DomainName;
|
|
39
|
+
}[];
|
|
30
40
|
export interface MatchStepsInput {
|
|
31
41
|
scenarios: ScenarioIR[];
|
|
32
42
|
pom: PageObjectIR;
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
19
|
exports.setActiveDomains = setActiveDomains;
|
|
20
|
+
exports.listAllRuleIds = listAllRuleIds;
|
|
20
21
|
exports.matchSteps = matchSteps;
|
|
21
22
|
exports.matchStep = matchStep;
|
|
22
23
|
exports.diagnoseStep = diagnoseStep;
|
|
@@ -42,22 +43,56 @@ const DOMAIN_RULES = {
|
|
|
42
43
|
telecom: telecom_1.TELECOM_RULES,
|
|
43
44
|
};
|
|
44
45
|
let activeDomainRules = [];
|
|
46
|
+
/**
|
|
47
|
+
* v4.4.0 Slice 2 — parallel array to `activeDomainRules`. Index N
|
|
48
|
+
* names the domain pack that owns `activeDomainRules[N]`. Consumed by
|
|
49
|
+
* `matchStep()` when it decorates a rule-produced binding with
|
|
50
|
+
* `provenance.pack`.
|
|
51
|
+
*/
|
|
52
|
+
let activeDomainRulePacks = [];
|
|
45
53
|
function setActiveDomains(domains) {
|
|
46
54
|
if (!domains || domains.length === 0) {
|
|
47
55
|
activeDomainRules = [];
|
|
56
|
+
activeDomainRulePacks = [];
|
|
48
57
|
return;
|
|
49
58
|
}
|
|
50
59
|
const seen = new Set();
|
|
51
60
|
const out = [];
|
|
61
|
+
const packs = [];
|
|
52
62
|
for (const d of domains) {
|
|
53
63
|
if (seen.has(d))
|
|
54
64
|
continue;
|
|
55
65
|
seen.add(d);
|
|
56
66
|
const pack = DOMAIN_RULES[d];
|
|
57
|
-
if (pack)
|
|
58
|
-
|
|
67
|
+
if (pack) {
|
|
68
|
+
for (const r of pack) {
|
|
69
|
+
out.push(r);
|
|
70
|
+
packs.push(d);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
59
73
|
}
|
|
60
74
|
activeDomainRules = out;
|
|
75
|
+
activeDomainRulePacks = packs;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* v4.4.0 Slice 2 — returns the full list of every rule id currently in
|
|
79
|
+
* the registry, ordered as `matchStep()` walks them. Consumed by
|
|
80
|
+
* `bdd2pw coverage` to seed the "dead rules" report (a rule appears in
|
|
81
|
+
* this list but not in any sidecar).
|
|
82
|
+
*/
|
|
83
|
+
function listAllRuleIds() {
|
|
84
|
+
const domainCount = activeDomainRules.length;
|
|
85
|
+
const out = [];
|
|
86
|
+
for (let i = 0; i < domainCount; i++) {
|
|
87
|
+
out.push({
|
|
88
|
+
ruleId: `domain-${String(i + 1).padStart(2, "0")}`,
|
|
89
|
+
pack: activeDomainRulePacks[i],
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
for (let i = 0; i < RULES.length; i++) {
|
|
93
|
+
out.push({ ruleId: `rule-${String(i + 1).padStart(2, "0")}` });
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
61
96
|
}
|
|
62
97
|
/**
|
|
63
98
|
* Match every step in every scenario to a POM call / assertion / TODO.
|
|
@@ -94,12 +129,31 @@ function matchStep(step, pom, pageVar) {
|
|
|
94
129
|
const effectiveRules = activeDomainRules.length > 0
|
|
95
130
|
? [...activeDomainRules, ...RULES]
|
|
96
131
|
: RULES;
|
|
97
|
-
|
|
132
|
+
const domainCount = activeDomainRules.length;
|
|
133
|
+
for (let idx = 0; idx < effectiveRules.length; idx++) {
|
|
134
|
+
const rule = effectiveRules[idx];
|
|
98
135
|
const m = normalisedStep.text.match(rule.pattern);
|
|
99
136
|
if (m) {
|
|
100
137
|
const result = rule.build(m, normalisedStep, pom, pageVar);
|
|
101
|
-
if (result)
|
|
102
|
-
|
|
138
|
+
if (result) {
|
|
139
|
+
// v4.4.0 Slice 2 — attach rule provenance so the meta-sidecar
|
|
140
|
+
// + `bdd2pw coverage` CLI can report which rule bound each
|
|
141
|
+
// step. If the rule's own build() already set a provenance
|
|
142
|
+
// block (extremely rare — no rule does today), leave it alone.
|
|
143
|
+
if (result.provenance)
|
|
144
|
+
return result;
|
|
145
|
+
const isDomain = idx < domainCount;
|
|
146
|
+
return {
|
|
147
|
+
...result,
|
|
148
|
+
provenance: {
|
|
149
|
+
source: "rule",
|
|
150
|
+
ruleId: isDomain
|
|
151
|
+
? `domain-${String(idx + 1).padStart(2, "0")}`
|
|
152
|
+
: `rule-${String(idx - domainCount + 1).padStart(2, "0")}`,
|
|
153
|
+
pack: isDomain ? activeDomainRulePacks[idx] : undefined,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
103
157
|
}
|
|
104
158
|
}
|
|
105
159
|
return {
|