@fairfox/polly 0.82.0 → 0.83.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.
Files changed (45) hide show
  1. package/dist/cli/polly.js +22 -1
  2. package/dist/cli/polly.js.map +3 -3
  3. package/dist/tools/bdd/src/args.d.ts +21 -0
  4. package/dist/tools/bdd/src/bus-driver.d.ts +36 -0
  5. package/dist/tools/bdd/src/check-verify.d.ts +15 -0
  6. package/dist/tools/bdd/src/cli.d.ts +2 -0
  7. package/dist/tools/bdd/src/cli.js +701 -0
  8. package/dist/tools/bdd/src/cli.js.map +19 -0
  9. package/dist/tools/bdd/src/config.d.ts +9 -0
  10. package/dist/tools/bdd/src/extract.d.ts +2 -0
  11. package/dist/tools/bdd/src/index.d.ts +19 -0
  12. package/dist/tools/bdd/src/index.js +540 -0
  13. package/dist/tools/bdd/src/index.js.map +17 -0
  14. package/dist/tools/bdd/src/parse.d.ts +3 -0
  15. package/dist/tools/bdd/src/report.d.ts +6 -0
  16. package/dist/tools/bdd/src/run.d.ts +8 -0
  17. package/dist/tools/bdd/src/scaffold.d.ts +7 -0
  18. package/dist/tools/bdd/src/steps.d.ts +55 -0
  19. package/dist/tools/bdd/src/types.d.ts +145 -0
  20. package/dist/tools/bdd/src/witness.d.ts +23 -0
  21. package/dist/tools/gallery/src/cli.js +4 -3
  22. package/dist/tools/gallery/src/cli.js.map +3 -3
  23. package/dist/tools/mutate/src/cli.js +8 -1
  24. package/dist/tools/mutate/src/cli.js.map +3 -3
  25. package/dist/tools/quality/src/cli.js +304 -15
  26. package/dist/tools/quality/src/cli.js.map +6 -4
  27. package/dist/tools/quality/src/index.d.ts +2 -0
  28. package/dist/tools/quality/src/index.js +309 -15
  29. package/dist/tools/quality/src/index.js.map +6 -4
  30. package/dist/tools/quality/src/no-fixed-waits.d.ts +52 -0
  31. package/dist/tools/quality/src/no-tautology-ensures.d.ts +67 -0
  32. package/dist/tools/quality/src/plugins/core.d.ts +1 -1
  33. package/dist/tools/test/src/coverage-policy/cli.js +5 -1
  34. package/dist/tools/test/src/coverage-policy/cli.js.map +3 -3
  35. package/dist/tools/test/src/tiers/args.d.ts +5 -0
  36. package/dist/tools/test/src/tiers/cli.js +97 -23
  37. package/dist/tools/test/src/tiers/cli.js.map +9 -8
  38. package/dist/tools/test/src/tiers/index.d.ts +2 -1
  39. package/dist/tools/test/src/tiers/order.d.ts +25 -0
  40. package/dist/tools/test/src/tiers/types.d.ts +15 -0
  41. package/dist/tools/verify/src/cli.js +540 -15
  42. package/dist/tools/verify/src/cli.js.map +8 -4
  43. package/dist/tools/visualize/src/cli.js +17 -13
  44. package/dist/tools/visualize/src/cli.js.map +3 -3
  45. package/package.json +9 -16
@@ -0,0 +1,540 @@
1
+ import { createRequire } from "node:module";
2
+ var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true,
12
+ configurable: true,
13
+ set: __exportSetter.bind(all, name)
14
+ });
15
+ };
16
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
18
+
19
+ // tools/bdd/src/parse.ts
20
+ import { AstBuilder, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherkin";
21
+ import { IdGenerator } from "@cucumber/messages";
22
+ function newParser() {
23
+ return new Parser(new AstBuilder(IdGenerator.uuid()), new GherkinClassicTokenMatcher);
24
+ }
25
+ function normalizeKeyword(raw, prev) {
26
+ const k = raw.trim().toLowerCase();
27
+ if (k === "given")
28
+ return "given";
29
+ if (k === "when")
30
+ return "when";
31
+ if (k === "then")
32
+ return "then";
33
+ return prev ?? "given";
34
+ }
35
+ function normalizeSteps(rawSteps) {
36
+ const out = [];
37
+ let prev = null;
38
+ for (const s of rawSteps) {
39
+ const keyword = normalizeKeyword(s.keyword, prev);
40
+ prev = keyword;
41
+ out.push({
42
+ keyword,
43
+ rawKeyword: s.keyword.trim(),
44
+ text: s.text.trim(),
45
+ line: s.location?.line ?? 0
46
+ });
47
+ }
48
+ return out;
49
+ }
50
+ function tagNames(tags) {
51
+ return (tags ?? []).map((t) => t.name.replace(/^@/, ""));
52
+ }
53
+ function fillOutline(text, headers, cells) {
54
+ let filled = text;
55
+ headers.forEach((h, i) => {
56
+ filled = filled.split(`<${h}>`).join(cells[i] ?? "");
57
+ });
58
+ return filled;
59
+ }
60
+ function buildScenarios(sc) {
61
+ const baseSteps = sc.steps ?? [];
62
+ const tags = tagNames(sc.tags);
63
+ const examples = sc.examples ?? [];
64
+ if (examples.length === 0) {
65
+ return [
66
+ { name: sc.name, tags, steps: normalizeSteps(baseSteps), line: sc.location?.line ?? 0 }
67
+ ];
68
+ }
69
+ const out = [];
70
+ for (const ex of examples) {
71
+ const headers = (ex.tableHeader?.cells ?? []).map((c) => c.value);
72
+ for (const row of ex.tableBody ?? []) {
73
+ const cells = (row.cells ?? []).map((c) => c.value);
74
+ const rowSteps = baseSteps.map((s) => ({
75
+ keyword: s.keyword,
76
+ text: fillOutline(s.text, headers, cells),
77
+ location: s.location
78
+ }));
79
+ const label = headers.map((h, i) => `${h}=${cells[i] ?? ""}`).join(", ");
80
+ out.push({
81
+ name: `${sc.name} [${label}]`,
82
+ tags: [...tags, ...tagNames(ex.tags)],
83
+ steps: normalizeSteps(rowSteps),
84
+ line: row.location?.line ?? sc.location?.line ?? 0,
85
+ fromOutline: true
86
+ });
87
+ }
88
+ }
89
+ return out;
90
+ }
91
+ function parseFeatureText(text, file) {
92
+ const doc = newParser().parse(text);
93
+ const feature = doc.feature;
94
+ if (!feature) {
95
+ return { name: "", description: "", tags: [], background: [], scenarios: [], file };
96
+ }
97
+ let background = [];
98
+ const scenarios = [];
99
+ for (const child of feature.children ?? []) {
100
+ if (child.background) {
101
+ background = normalizeSteps(child.background.steps ?? []);
102
+ } else if (child.scenario) {
103
+ scenarios.push(...buildScenarios(child.scenario));
104
+ }
105
+ }
106
+ return {
107
+ name: feature.name,
108
+ description: (feature.description ?? "").trim(),
109
+ tags: tagNames(feature.tags),
110
+ background,
111
+ scenarios,
112
+ file
113
+ };
114
+ }
115
+ async function parseFeatureFile(path) {
116
+ const text = await Bun.file(path).text();
117
+ return parseFeatureText(text, path);
118
+ }
119
+ var init_parse = () => {};
120
+
121
+ // tools/bdd/src/steps.ts
122
+ function state() {
123
+ globalThis.__pollyBddRegistry__ ??= { bindings: [], worldDef: null };
124
+ return globalThis.__pollyBddRegistry__;
125
+ }
126
+ function compilePattern(pattern) {
127
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
128
+ const withGroups = escaped.replace(/\\\{string\\\}/g, `(?:"([^"]*)"|'([^']*)')`).replace(/\\\{int\\\}/g, "([-+]?\\d+)").replace(/\\\{float\\\}/g, "([-+]?\\d*\\.?\\d+)").replace(/\\\{word\\\}/g, "([^\\s]+)");
129
+ return new RegExp(`^${withGroups}$`);
130
+ }
131
+ function defineStep(binding) {
132
+ state().bindings.push({ binding, regex: compilePattern(binding.pattern) });
133
+ }
134
+ function defineWorld(def) {
135
+ state().worldDef = def;
136
+ }
137
+ function getWorldDef() {
138
+ return state().worldDef;
139
+ }
140
+ function resetRegistry() {
141
+ const s = state();
142
+ s.bindings.length = 0;
143
+ s.worldDef = null;
144
+ }
145
+ function matchStep(text, keyword) {
146
+ let textOnlyFallback = null;
147
+ for (const { binding, regex } of state().bindings) {
148
+ const m = regex.exec(text);
149
+ if (!m)
150
+ continue;
151
+ const args = m.slice(1).filter((g) => g !== undefined);
152
+ if (!keyword || binding[keyword])
153
+ return { binding, args };
154
+ textOnlyFallback ??= { binding, args };
155
+ }
156
+ return textOnlyFallback;
157
+ }
158
+ function registeredBindings() {
159
+ return state().bindings.map((c) => c.binding);
160
+ }
161
+
162
+ // tools/bdd/src/witness.ts
163
+ var exports_witness = {};
164
+ __export(exports_witness, {
165
+ extractWitnesses: () => extractWitnesses
166
+ });
167
+ async function loadStepModules3(stepFiles) {
168
+ resetRegistry();
169
+ for (const file of stepFiles) {
170
+ await import(`${file}?t=${Bun.nanoseconds()}`);
171
+ }
172
+ }
173
+ function substituteArgs(expr, args) {
174
+ return expr.replace(/\{(\d+)\}/g, (whole, index) => args[Number(index)] ?? whole);
175
+ }
176
+ function fieldsIn2(expr) {
177
+ const noStrings = expr.replace(/"[^"]*"|'[^']*'/g, "");
178
+ const ids = noStrings.match(/[a-zA-Z_$][\w$]*(?:\.[a-zA-Z_$][\w$]*)*/g) ?? [];
179
+ const ignore = new Set(["true", "false", "null", "undefined", "length", "value"]);
180
+ return ids.filter((id) => !ignore.has(id) && Number.isNaN(Number(id))).map((id) => id.replace(/\.length$/, ""));
181
+ }
182
+ function reduceScenario(feature, scenario) {
183
+ const thenSteps = [...feature.background, ...scenario.steps].filter((s) => s.keyword === "then");
184
+ const conjuncts = [];
185
+ const skipped = [];
186
+ for (const step of thenSteps) {
187
+ const match = matchStep(step.text, "then");
188
+ const expr = match?.binding.stateExpr;
189
+ if (!expr) {
190
+ skipped.push(step.text);
191
+ continue;
192
+ }
193
+ const resolved = substituteArgs(expr, match.args);
194
+ if (!COMPARISON.test(resolved)) {
195
+ skipped.push(step.text);
196
+ continue;
197
+ }
198
+ conjuncts.push(resolved);
199
+ }
200
+ const predicate = conjuncts.length > 0 ? conjuncts.join(" && ") : null;
201
+ const fields = [...new Set(conjuncts.flatMap(fieldsIn2))];
202
+ return {
203
+ feature: feature.name,
204
+ scenario: scenario.name,
205
+ tags: [...feature.tags, ...scenario.tags],
206
+ file: feature.file,
207
+ predicate,
208
+ fields,
209
+ skipped
210
+ };
211
+ }
212
+ async function extractWitnesses(featureFiles, stepFiles) {
213
+ await loadStepModules3(stepFiles);
214
+ const witnesses = [];
215
+ for (const file of featureFiles) {
216
+ const feature = await parseFeatureFile(file);
217
+ for (const scenario of feature.scenarios) {
218
+ witnesses.push(reduceScenario({
219
+ name: feature.name,
220
+ tags: feature.tags,
221
+ background: feature.background,
222
+ file: feature.file
223
+ }, scenario));
224
+ }
225
+ }
226
+ return witnesses;
227
+ }
228
+ var COMPARISON;
229
+ var init_witness = __esm(() => {
230
+ init_parse();
231
+ COMPARISON = /===|!==|==|!=|<=|>=|<|>/;
232
+ });
233
+
234
+ // tools/bdd/src/bus-driver.ts
235
+ function driveBus(bus, driveOpts = {}) {
236
+ const source = driveOpts.source ?? "popup";
237
+ const defaultTarget = driveOpts.target ?? "background";
238
+ return {
239
+ async send(payload, options) {
240
+ const target = options?.target ?? defaultTarget;
241
+ const targets = Array.isArray(target) ? target : [target];
242
+ const message = {
243
+ id: crypto.randomUUID(),
244
+ source,
245
+ targets,
246
+ timestamp: Date.now(),
247
+ payload,
248
+ ...options?.tabId === undefined ? {} : { tabId: options.tabId }
249
+ };
250
+ const response = await bus.handleMessage(message);
251
+ return response?.data;
252
+ }
253
+ };
254
+ }
255
+ // tools/bdd/src/check-verify.ts
256
+ import { resolve } from "node:path";
257
+
258
+ // tools/bdd/src/extract.ts
259
+ init_parse();
260
+ async function loadStepModules(stepFiles) {
261
+ resetRegistry();
262
+ for (const file of stepFiles) {
263
+ await import(`${file}?t=${Bun.nanoseconds()}`);
264
+ }
265
+ }
266
+ function toTraceStep(text, keyword) {
267
+ const match = matchStep(text, keyword);
268
+ if (!match)
269
+ return { text, keyword, unbound: true };
270
+ return {
271
+ text,
272
+ keyword,
273
+ message: match.binding.message,
274
+ stateExpr: match.binding.stateExpr
275
+ };
276
+ }
277
+ async function extractTraces(featureFiles, stepFiles) {
278
+ await loadStepModules(stepFiles);
279
+ const traces = [];
280
+ for (const file of featureFiles) {
281
+ const feature = await parseFeatureFile(file);
282
+ for (const scenario of feature.scenarios) {
283
+ const allSteps = [...feature.background, ...scenario.steps];
284
+ const trace = {
285
+ feature: feature.name,
286
+ scenario: scenario.name,
287
+ tags: [...feature.tags, ...scenario.tags],
288
+ given: [],
289
+ when: [],
290
+ then: [],
291
+ file
292
+ };
293
+ for (const step of allSteps) {
294
+ trace[step.keyword].push(toTraceStep(step.text, step.keyword));
295
+ }
296
+ traces.push(trace);
297
+ }
298
+ }
299
+ return traces;
300
+ }
301
+
302
+ // tools/bdd/src/check-verify.ts
303
+ async function loadVerifyConfig(configPath) {
304
+ const mod = await import(`file://${resolve(configPath)}?t=${Bun.nanoseconds()}`);
305
+ const config = mod.verificationConfig ?? mod.default;
306
+ if (!config)
307
+ throw new Error(`no verificationConfig/default export in ${configPath}`);
308
+ return config;
309
+ }
310
+ function messageSet(config) {
311
+ const set = new Set;
312
+ for (const t of config.messages?.include ?? [])
313
+ set.add(t);
314
+ for (const t of Object.keys(config.messages?.perMessageBounds ?? {}))
315
+ set.add(t);
316
+ for (const sub of Object.values(config.subsystems ?? {})) {
317
+ for (const h of sub.handlers ?? [])
318
+ set.add(h);
319
+ }
320
+ return set;
321
+ }
322
+ function stateKeys(config) {
323
+ const keys = new Set(Object.keys(config.state ?? {}));
324
+ for (const sub of Object.values(config.subsystems ?? {})) {
325
+ for (const f of sub.state ?? [])
326
+ keys.add(f);
327
+ }
328
+ return [...keys];
329
+ }
330
+ function fieldsIn(expr) {
331
+ const noStrings = expr.replace(/"[^"]*"|'[^']*'/g, "");
332
+ const ids = noStrings.match(/[a-zA-Z_$][\w$]*(?:\.[a-zA-Z_$][\w$]*)*/g) ?? [];
333
+ const ignore = new Set(["true", "false", "null", "undefined", "length", "value"]);
334
+ return ids.filter((id) => !ignore.has(id) && Number.isNaN(Number(id)));
335
+ }
336
+ function fieldKnown(field, keys) {
337
+ return keys.some((k) => k === field || field.startsWith(`${k}.`) || k.startsWith(`${field}.`));
338
+ }
339
+ var NEGATIVE_TAGS = new Set(["negative", "formal"]);
340
+ function checkTrace(trace, messages, keys, findings) {
341
+ const id = `${trace.feature} › ${trace.scenario}`;
342
+ for (const step of [...trace.given, ...trace.when, ...trace.then]) {
343
+ if (step.unbound) {
344
+ findings.push({ kind: "warn", scenario: id, message: `step has no binding: "${step.text}"` });
345
+ }
346
+ }
347
+ for (const step of trace.when) {
348
+ if (step.message && !messages.has(step.message)) {
349
+ findings.push({
350
+ kind: "error",
351
+ scenario: id,
352
+ message: `When sends "${step.message}", which the verification config does not model`
353
+ });
354
+ }
355
+ }
356
+ for (const step of [...trace.given, ...trace.then]) {
357
+ if (!step.stateExpr)
358
+ continue;
359
+ for (const field of fieldsIn(step.stateExpr)) {
360
+ if (!fieldKnown(field, keys)) {
361
+ findings.push({
362
+ kind: "error",
363
+ scenario: id,
364
+ message: `${step.keyword} asserts on "${field}", absent from the config's state map`
365
+ });
366
+ }
367
+ }
368
+ }
369
+ }
370
+ function featureNeedsNegative(traces) {
371
+ return traces.some((t) => t.then.some((s) => /\bnot\b|exclud|reject|empty|invalid|limit|forbidden/i.test(s.text)));
372
+ }
373
+ function checkNegativeComplement(traces, findings) {
374
+ const byFeature = new Map;
375
+ for (const t of traces) {
376
+ const arr = byFeature.get(t.feature) ?? [];
377
+ arr.push(t);
378
+ byFeature.set(t.feature, arr);
379
+ }
380
+ for (const [feature, group] of byFeature) {
381
+ const hasNegative = group.some((t) => t.tags.some((tag) => NEGATIVE_TAGS.has(tag)));
382
+ if (!hasNegative && featureNeedsNegative(group)) {
383
+ findings.push({
384
+ kind: "warn",
385
+ scenario: feature,
386
+ message: "feature filters/selects/validates but has no negative complement (a @negative or @formal scenario) — an over-permissive build would still pass"
387
+ });
388
+ }
389
+ }
390
+ }
391
+ async function checkAgainstVerify(opts) {
392
+ const config = await loadVerifyConfig(opts.configPath);
393
+ const messages = messageSet(config);
394
+ const keys = stateKeys(config);
395
+ const traces = await extractTraces(opts.featureFiles, opts.stepFiles);
396
+ const findings = [];
397
+ for (const trace of traces)
398
+ checkTrace(trace, messages, keys, findings);
399
+ checkNegativeComplement(traces, findings);
400
+ return {
401
+ ok: findings.every((f) => f.kind !== "error"),
402
+ checked: traces.length,
403
+ findings
404
+ };
405
+ }
406
+
407
+ // tools/bdd/src/index.ts
408
+ init_parse();
409
+
410
+ // tools/bdd/src/run.ts
411
+ init_parse();
412
+ var FORMAL_TAG = "formal";
413
+ async function loadStepModules2(stepFiles) {
414
+ resetRegistry();
415
+ for (const file of stepFiles) {
416
+ await import(`${file}?t=${Bun.nanoseconds()}`);
417
+ }
418
+ }
419
+ function tagMatches(tags, filter) {
420
+ if (!filter)
421
+ return true;
422
+ if (filter.startsWith("~"))
423
+ return !tags.includes(filter.slice(1));
424
+ return tags.includes(filter);
425
+ }
426
+ async function runStep(world, step) {
427
+ const base = { text: step.text, rawKeyword: step.rawKeyword };
428
+ const match = matchStep(step.text, step.keyword);
429
+ if (!match) {
430
+ return { ...base, outcome: "undefined", message: `no binding matches "${step.text}"` };
431
+ }
432
+ const fn = match.binding[step.keyword];
433
+ if (!fn) {
434
+ return {
435
+ ...base,
436
+ outcome: "undefined",
437
+ message: `binding for "${step.text}" has no '${step.keyword}' callback`
438
+ };
439
+ }
440
+ try {
441
+ const ret = await fn(world, ...match.args);
442
+ if (ret !== undefined)
443
+ world.lastResponse = ret;
444
+ return { ...base, outcome: "pass" };
445
+ } catch (err) {
446
+ world.lastError = err;
447
+ return { ...base, outcome: "fail", message: err instanceof Error ? err.message : String(err) };
448
+ }
449
+ }
450
+ async function runScenario(world, feature, scenario, reset) {
451
+ const result = {
452
+ feature: feature.name,
453
+ scenario: scenario.name,
454
+ tags: scenario.tags,
455
+ outcome: "pass",
456
+ steps: [],
457
+ file: feature.file
458
+ };
459
+ await reset(world);
460
+ world.vars = {};
461
+ world.lastResponse = undefined;
462
+ world.lastError = undefined;
463
+ const steps = [...feature.background, ...scenario.steps];
464
+ let aborted = false;
465
+ for (const step of steps) {
466
+ if (aborted) {
467
+ result.steps.push({ text: step.text, rawKeyword: step.rawKeyword, outcome: "skipped" });
468
+ continue;
469
+ }
470
+ const sr = await runStep(world, step);
471
+ result.steps.push(sr);
472
+ if (sr.outcome === "fail") {
473
+ result.outcome = "fail";
474
+ aborted = true;
475
+ } else if (sr.outcome === "undefined") {
476
+ result.outcome = result.outcome === "fail" ? "fail" : "undefined";
477
+ aborted = true;
478
+ }
479
+ }
480
+ return result;
481
+ }
482
+ async function runFeatures(options) {
483
+ await loadStepModules2(options.stepFiles);
484
+ const worldDef = getWorldDef();
485
+ if (!worldDef) {
486
+ throw new Error("no world defined. A step module must call defineWorld({ create, reset }) — see tools/bdd/README.md.");
487
+ }
488
+ const world = await worldDef.create();
489
+ const features = await Promise.all(options.featureFiles.map((f) => parseFeatureFile(f)));
490
+ const scenarios = [];
491
+ for (const feature of features) {
492
+ for (const scenario of feature.scenarios) {
493
+ const tags = [...feature.tags, ...scenario.tags];
494
+ if (!tagMatches(tags, options.tagFilter))
495
+ continue;
496
+ if (tags.includes(FORMAL_TAG)) {
497
+ scenarios.push({
498
+ feature: feature.name,
499
+ scenario: scenario.name,
500
+ tags,
501
+ outcome: "deferred-formal",
502
+ steps: [],
503
+ file: feature.file
504
+ });
505
+ continue;
506
+ }
507
+ scenarios.push(await runScenario(world, feature, { ...scenario, tags }, worldDef.reset));
508
+ }
509
+ }
510
+ const passed = scenarios.filter((s) => s.outcome === "pass").length;
511
+ const failed = scenarios.filter((s) => s.outcome === "fail").length;
512
+ const undef = scenarios.filter((s) => s.outcome === "undefined").length;
513
+ const deferred = scenarios.filter((s) => s.outcome === "deferred-formal").length;
514
+ return {
515
+ scenarios,
516
+ passed,
517
+ failed,
518
+ undefinedSteps: undef,
519
+ deferred,
520
+ ok: failed === 0 && undef === 0
521
+ };
522
+ }
523
+
524
+ // tools/bdd/src/index.ts
525
+ init_witness();
526
+ export {
527
+ runFeatures,
528
+ registeredBindings,
529
+ parseFeatureText,
530
+ parseFeatureFile,
531
+ matchStep,
532
+ extractWitnesses,
533
+ extractTraces,
534
+ driveBus,
535
+ defineWorld,
536
+ defineStep,
537
+ checkAgainstVerify
538
+ };
539
+
540
+ //# debugId=8FDB3C27E529750664756E2164756E21
@@ -0,0 +1,17 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../tools/bdd/src/parse.ts", "../tools/bdd/src/steps.ts", "../tools/bdd/src/witness.ts", "../tools/bdd/src/bus-driver.ts", "../tools/bdd/src/check-verify.ts", "../tools/bdd/src/extract.ts", "../tools/bdd/src/index.ts", "../tools/bdd/src/run.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * Parse `.feature` files into the runner's normalized AST.\n *\n * Thin wrapper over `@cucumber/gherkin` (parser only — no cucumber runtime):\n * we keep our own {@link ParsedFeature} shape so the rest of the tool never\n * touches the gherkin message types, and swapping the parser stays a one-file\n * change. Responsibilities beyond raw parsing:\n * - normalize And/But to the preceding concrete keyword (Given/When/Then);\n * - fold Background steps into a feature-level list;\n * - expand each Scenario Outline into one concrete scenario per Examples row.\n */\nimport { AstBuilder, GherkinClassicTokenMatcher, Parser } from \"@cucumber/gherkin\";\nimport type { GherkinDocument, Scenario } from \"@cucumber/messages\";\nimport { IdGenerator } from \"@cucumber/messages\";\nimport type { ParsedFeature, ParsedScenario, ParsedStep, StepKeyword } from \"./types.ts\";\n\nfunction newParser() {\n return new Parser(new AstBuilder(IdGenerator.uuid()), new GherkinClassicTokenMatcher());\n}\n\n/** And/But inherit the previous concrete keyword; a leading And/But defaults to Given. */\nfunction normalizeKeyword(raw: string, prev: StepKeyword | null): StepKeyword {\n const k = raw.trim().toLowerCase();\n if (k === \"given\") return \"given\";\n if (k === \"when\") return \"when\";\n if (k === \"then\") return \"then\";\n return prev ?? \"given\";\n}\n\ninterface RawStep {\n keyword: string;\n text: string;\n location?: { line?: number };\n}\n\nfunction normalizeSteps(rawSteps: readonly RawStep[]): ParsedStep[] {\n const out: ParsedStep[] = [];\n let prev: StepKeyword | null = null;\n for (const s of rawSteps) {\n const keyword = normalizeKeyword(s.keyword, prev);\n prev = keyword;\n out.push({\n keyword,\n rawKeyword: s.keyword.trim(),\n text: s.text.trim(),\n line: s.location?.line ?? 0,\n });\n }\n return out;\n}\n\nfunction tagNames(tags: ReadonlyArray<{ name: string }> | undefined): string[] {\n return (tags ?? []).map((t) => t.name.replace(/^@/, \"\"));\n}\n\n/** Substitute `<placeholder>` cells from an Examples row into step text. */\nfunction fillOutline(text: string, headers: string[], cells: string[]): string {\n let filled = text;\n headers.forEach((h, i) => {\n filled = filled.split(`<${h}>`).join(cells[i] ?? \"\");\n });\n return filled;\n}\n\n/** A plain scenario, or one concrete scenario per Scenario Outline Examples row. */\nfunction buildScenarios(sc: Scenario): ParsedScenario[] {\n const baseSteps = sc.steps ?? [];\n const tags = tagNames(sc.tags);\n const examples = sc.examples ?? [];\n\n if (examples.length === 0) {\n return [\n { name: sc.name, tags, steps: normalizeSteps(baseSteps), line: sc.location?.line ?? 0 },\n ];\n }\n\n const out: ParsedScenario[] = [];\n for (const ex of examples) {\n const headers = (ex.tableHeader?.cells ?? []).map((c) => c.value);\n for (const row of ex.tableBody ?? []) {\n const cells = (row.cells ?? []).map((c) => c.value);\n const rowSteps = baseSteps.map((s) => ({\n keyword: s.keyword,\n text: fillOutline(s.text, headers, cells),\n location: s.location,\n }));\n const label = headers.map((h, i) => `${h}=${cells[i] ?? \"\"}`).join(\", \");\n out.push({\n name: `${sc.name} [${label}]`,\n tags: [...tags, ...tagNames(ex.tags)],\n steps: normalizeSteps(rowSteps),\n line: row.location?.line ?? sc.location?.line ?? 0,\n fromOutline: true,\n });\n }\n }\n return out;\n}\n\nexport function parseFeatureText(text: string, file: string): ParsedFeature {\n const doc: GherkinDocument = newParser().parse(text);\n const feature = doc.feature;\n if (!feature) {\n return { name: \"\", description: \"\", tags: [], background: [], scenarios: [], file };\n }\n\n let background: ParsedStep[] = [];\n const scenarios: ParsedScenario[] = [];\n\n for (const child of feature.children ?? []) {\n if (child.background) {\n background = normalizeSteps(child.background.steps ?? []);\n } else if (child.scenario) {\n scenarios.push(...buildScenarios(child.scenario));\n }\n }\n\n return {\n name: feature.name,\n description: (feature.description ?? \"\").trim(),\n tags: tagNames(feature.tags),\n background,\n scenarios,\n file,\n };\n}\n\nexport async function parseFeatureFile(path: string): Promise<ParsedFeature> {\n const text = await Bun.file(path).text();\n return parseFeatureText(text, path);\n}\n",
6
+ "/**\n * The step-binding registry and matcher.\n *\n * `defineStep()` is the consumer-facing keystone: one call registers a binding\n * that the runner executes (`given`/`when`/`then`) AND the verify extractor\n * reads (`message`/`stateExpr`). `defineWorld()` records how to build and\n * cold-reset the real-factory world.\n *\n * Patterns use a small Cucumber-expression subset: `{string}`, `{int}`,\n * `{float}`, `{word}`. Everything else is matched literally. That covers\n * declarative steps without dragging in the full cucumber-expressions library.\n *\n * The registry lives on `globalThis`, not in a module-level array, on purpose:\n * `polly bdd` ships as two bundles (the CLI and the `@fairfox/polly/bdd`\n * library export). `build-lib` runs with `splitting: false`, so this module is\n * *inlined separately into each bundle* — a module-level array would give the\n * runner and the consumer's `defineWorld` two different registries. A single\n * global slot is the standard cross-bundle-singleton fix.\n */\nimport type { StepBinding, StepKeyword, WorldDef } from \"./types.ts\";\n\ninterface CompiledBinding {\n binding: StepBinding;\n regex: RegExp;\n}\n\ninterface RegistryState {\n bindings: CompiledBinding[];\n worldDef: WorldDef | null;\n}\n\ndeclare global {\n // An ambient global declaration — `var` is the only legal form here.\n var __pollyBddRegistry__: RegistryState | undefined;\n}\n\nfunction state(): RegistryState {\n globalThis.__pollyBddRegistry__ ??= { bindings: [], worldDef: null };\n return globalThis.__pollyBddRegistry__;\n}\n\n/** Translate a Cucumber-expression pattern into an anchored RegExp. */\nexport function compilePattern(pattern: string): RegExp {\n // Escape regex metacharacters, then re-introduce capture groups for {tokens}.\n const escaped = pattern.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const withGroups = escaped\n // {string} — double- or single-quoted; capture the inner text. The\n // alternation MUST be wrapped in a non-capturing group, or the `|` would\n // bind to the whole pattern and shatter any step with two+ tokens.\n .replace(/\\\\\\{string\\\\\\}/g, `(?:\"([^\"]*)\"|'([^']*)')`)\n .replace(/\\\\\\{int\\\\\\}/g, \"([-+]?\\\\d+)\")\n .replace(/\\\\\\{float\\\\\\}/g, \"([-+]?\\\\d*\\\\.?\\\\d+)\")\n .replace(/\\\\\\{word\\\\\\}/g, \"([^\\\\s]+)\");\n return new RegExp(`^${withGroups}$`);\n}\n\nexport function defineStep(binding: StepBinding): void {\n state().bindings.push({ binding, regex: compilePattern(binding.pattern) });\n}\n\nexport function defineWorld(def: WorldDef): void {\n state().worldDef = def;\n}\n\nexport function getWorldDef(): WorldDef | null {\n return state().worldDef;\n}\n\n/** Drop all registrations — used between isolated runs/tests. */\nexport function resetRegistry(): void {\n const s = state();\n s.bindings.length = 0;\n s.worldDef = null;\n}\n\nexport interface StepMatch {\n binding: StepBinding;\n args: string[];\n}\n\n/**\n * Find the binding whose pattern matches `text`. Captured groups become the\n * step arguments (the `{string}` alternation yields two groups per token, so\n * we drop the undefined half).\n *\n * When `keyword` is given, a binding that *has* the callback for that keyword\n * wins over one that merely matches the text — so the same phrase can serve as\n * a `given` precondition in one scenario and a `then` assertion in another.\n */\nexport function matchStep(text: string, keyword?: StepKeyword): StepMatch | null {\n let textOnlyFallback: StepMatch | null = null;\n for (const { binding, regex } of state().bindings) {\n const m = regex.exec(text);\n if (!m) continue;\n const args = m.slice(1).filter((g) => g !== undefined);\n if (!keyword || binding[keyword]) return { binding, args };\n textOnlyFallback ??= { binding, args };\n }\n return textOnlyFallback;\n}\n\n/** Snapshot of all registered bindings (for the verify extractor). */\nexport function registeredBindings(): StepBinding[] {\n return state().bindings.map((c) => c.binding);\n}\n",
7
+ "/**\n * The deeper verify cross-link: reduce each scenario to its *Then-predicate* —\n * the observable post-state a passing scenario claims is reachable.\n *\n * Where {@link extractTraces} (and `polly bdd check`) ask \"does this scenario\n * speak the config's vocabulary?\", a witness asks the *formal* question: \"can\n * the exhaustive model actually reach the state this scenario asserts?\" A\n * scenario whose Then-state the model proves unreachable is a scenario that\n * lies — green in the runner, impossible in the model. `polly verify --witness`\n * turns each predicate produced here into a per-scenario TLC reachability check\n * (tools/verify); this module never runs a model checker — it only reduces.\n *\n * The reduction reuses the same dual-use `stateExpr` metadata the static check\n * reads, but it needs the *whole* predicate, not just the field names. So it\n * substitutes the values captured from the step text into the binding's\n * `stateExpr` placeholders: a `then` bound to `user.role === \"{0}\"` matched\n * against `their role is \"admin\"` reduces to `user.role === \"admin\"`. Then-steps\n * whose `stateExpr` is a bare field reference (no comparison) or absent (a\n * runtime-only assertion like `the change is refused`) are not state-observable\n * outcomes — they are reported as skipped, never silently dropped.\n */\nimport { parseFeatureFile } from \"./parse.ts\";\nimport { matchStep, resetRegistry } from \"./steps.ts\";\n\n/** A scenario reduced to the post-state it claims, ready for the verify witness. */\nexport interface ScenarioWitness {\n feature: string;\n scenario: string;\n tags: string[];\n file: string;\n /**\n * The conjoined Then-predicate (TS, `signal.field` dialect, values\n * substituted) — e.g. `user.loggedIn === true && user.role === \"admin\"`.\n * `null` when the scenario has no state-observable outcome to witness.\n */\n predicate: string | null;\n /** Dotted state-field paths the predicate references (for subsystem routing). */\n fields: string[];\n /** Then-step texts skipped: a bare field ref, no binding, or a runtime-only check. */\n skipped: string[];\n}\n\nasync function loadStepModules(stepFiles: string[]): Promise<void> {\n resetRegistry();\n for (const file of stepFiles) {\n await import(`${file}?t=${Bun.nanoseconds()}`);\n }\n}\n\n/** A predicate is witnessable only if it actually compares — a bare field cannot. */\nconst COMPARISON = /===|!==|==|!=|<=|>=|<|>/;\n\n/** Substitute `{0}`, `{1}`, … in a stateExpr with the values captured from the step text. */\nfunction substituteArgs(expr: string, args: string[]): string {\n return expr.replace(/\\{(\\d+)\\}/g, (whole, index) => args[Number(index)] ?? whole);\n}\n\n/** Dotted identifier paths in an expression, minus string literals and keywords. */\nfunction fieldsIn(expr: string): string[] {\n const noStrings = expr.replace(/\"[^\"]*\"|'[^']*'/g, \"\");\n const ids = noStrings.match(/[a-zA-Z_$][\\w$]*(?:\\.[a-zA-Z_$][\\w$]*)*/g) ?? [];\n const ignore = new Set([\"true\", \"false\", \"null\", \"undefined\", \"length\", \"value\"]);\n return (\n ids\n .filter((id) => !ignore.has(id) && Number.isNaN(Number(id)))\n // Drop a trailing `.length` so routing sees the underlying field (`todos`).\n .map((id) => id.replace(/\\.length$/, \"\"))\n );\n}\n\n/**\n * Reduce one scenario's Then steps to a witness. Conjoins every Then whose\n * bound `stateExpr` resolves to a comparison; records the rest as skipped.\n */\nfunction reduceScenario(\n feature: { name: string; tags: string[]; background: ParsedStepLike[]; file: string },\n scenario: { name: string; tags: string[]; steps: ParsedStepLike[] }\n): ScenarioWitness {\n const thenSteps = [...feature.background, ...scenario.steps].filter((s) => s.keyword === \"then\");\n const conjuncts: string[] = [];\n const skipped: string[] = [];\n\n for (const step of thenSteps) {\n const match = matchStep(step.text, \"then\");\n const expr = match?.binding.stateExpr;\n if (!expr) {\n skipped.push(step.text);\n continue;\n }\n const resolved = substituteArgs(expr, match.args);\n if (!COMPARISON.test(resolved)) {\n // A bare field reference (`todos`, `user.role`) names a field but asserts\n // nothing checkable — useful to `polly bdd check`, not to a witness.\n skipped.push(step.text);\n continue;\n }\n conjuncts.push(resolved);\n }\n\n const predicate = conjuncts.length > 0 ? conjuncts.join(\" && \") : null;\n const fields = [...new Set(conjuncts.flatMap(fieldsIn))];\n return {\n feature: feature.name,\n scenario: scenario.name,\n tags: [...feature.tags, ...scenario.tags],\n file: feature.file,\n predicate,\n fields,\n skipped,\n };\n}\n\ninterface ParsedStepLike {\n keyword: \"given\" | \"when\" | \"then\";\n text: string;\n}\n\n/**\n * Reduce every scenario in the given feature files to a {@link ScenarioWitness}.\n * Step modules are loaded for their `defineStep` side effects (the same import\n * trick {@link extractTraces} uses) so `matchStep` can resolve each Then.\n */\nexport async function extractWitnesses(\n featureFiles: string[],\n stepFiles: string[]\n): Promise<ScenarioWitness[]> {\n await loadStepModules(stepFiles);\n const witnesses: ScenarioWitness[] = [];\n for (const file of featureFiles) {\n const feature = await parseFeatureFile(file);\n for (const scenario of feature.scenarios) {\n witnesses.push(\n reduceScenario(\n {\n name: feature.name,\n tags: feature.tags,\n background: feature.background,\n file: feature.file,\n },\n scenario\n )\n );\n }\n }\n return witnesses;\n}\n",
8
+ "/**\n * Drive a real polly MessageBus from step definitions.\n *\n * `createBackground()` wires a MessageRouter whose request/response loopback\n * runs over ports — which don't exist in a single in-process world, so a\n * self-targeted `bus.send` would just time out. `handleMessage` is the exact\n * method the router calls for a background-targeted message\n * (`routeToSingleTarget` → `this.bus.handleMessage(message)`): it runs the\n * registered handler and returns its response. So we build the inbound\n * RoutedMessage a UI context would send and hand it to `handleMessage`.\n *\n * This crosses the real handler + state-signal path through the real,\n * factory-built bus. The only thing it does NOT exercise is cross-context port\n * transport — and that boundary is covered for real by the mesh e2e scenario,\n * not faked here. (The polly#57 lesson is about not letting a hand-wired bus\n * paper over a factory gap; here the factory built the bus and registered the\n * handlers, and we drive its own dispatch entry point.)\n */\nimport type { BusLike } from \"./types.ts\";\n\n/**\n * Anything with the bus's dispatch entry point. Declared as a *method* (not an\n * arrow property) so its parameter is bivariant — a real\n * `MessageBus<TMessage>`, whose `handleMessage` takes the narrower\n * `RoutedMessage<TMessage>`, is assignable here.\n */\nexport interface DispatchBus {\n handleMessage(message: unknown): Promise<unknown>;\n}\n\ninterface DriveOptions {\n /** The context the message appears to come from (a UI context → background). */\n source?: string;\n /** Default delivery target. */\n target?: string;\n}\n\nexport function driveBus(bus: DispatchBus, driveOpts: DriveOptions = {}): BusLike {\n const source = driveOpts.source ?? \"popup\";\n const defaultTarget = driveOpts.target ?? \"background\";\n\n return {\n async send(payload, options) {\n const target = options?.target ?? defaultTarget;\n const targets = Array.isArray(target) ? target : [target];\n const message = {\n id: crypto.randomUUID(),\n source,\n targets,\n timestamp: Date.now(),\n payload,\n ...(options?.tabId === undefined ? {} : { tabId: options.tabId }),\n };\n const response = (await bus.handleMessage(message)) as\n | { success?: boolean; data?: unknown; error?: string }\n | undefined;\n // Return the handler's own return value (response.data); steps assert on it.\n return response?.data;\n },\n };\n}\n",
9
+ "/**\n * The verify cross-link, half two: hold every scenario trace against the\n * verification config — the always-on, static, cheap gate (`polly bdd check`).\n * The deeper TLC reachability *witness* rides `polly verify --witness`\n * (tools/verify); this file never runs a model checker.\n *\n * Three checks, each the BDD analogue of a verify guarantee:\n * 1. every `When` message type is one the config actually models (and so has\n * a handler the model knows about) — a scenario that drives a phantom\n * message is a scenario about nothing;\n * 2. every `Given`/`Then` state expression names a field the config tracks —\n * an assertion on an unmodeled field can't be cross-checked or verified;\n * 3. every feature that filters/selects/validates carries a negative\n * complement (a scenario tagged `@negative` or `@formal`) — the mechanical\n * enforcer of the Tester's \"prove the system also says no\" rule, which is\n * exactly what mutation testing then makes teeth on.\n */\nimport { resolve } from \"node:path\";\nimport { extractTraces } from \"./extract.ts\";\nimport type { ScenarioTrace } from \"./types.ts\";\n\nexport interface Finding {\n kind: \"error\" | \"warn\";\n scenario: string;\n message: string;\n}\n\nexport interface CrossCheckResult {\n ok: boolean;\n checked: number;\n findings: Finding[];\n}\n\ninterface VerifyConfigShape {\n state?: Record<string, unknown>;\n messages?: { include?: string[]; perMessageBounds?: Record<string, number> };\n subsystems?: Record<string, { state?: string[]; handlers?: string[] }>;\n}\n\nasync function loadVerifyConfig(configPath: string): Promise<VerifyConfigShape> {\n const mod = await import(`file://${resolve(configPath)}?t=${Bun.nanoseconds()}`);\n const config = mod.verificationConfig ?? mod.default;\n if (!config) throw new Error(`no verificationConfig/default export in ${configPath}`);\n return config as VerifyConfigShape;\n}\n\n/** Union of every message type the config models. */\nfunction messageSet(config: VerifyConfigShape): Set<string> {\n const set = new Set<string>();\n for (const t of config.messages?.include ?? []) set.add(t);\n for (const t of Object.keys(config.messages?.perMessageBounds ?? {})) set.add(t);\n for (const sub of Object.values(config.subsystems ?? {})) {\n for (const h of sub.handlers ?? []) set.add(h);\n }\n return set;\n}\n\n/** Every state field key the config tracks. */\nfunction stateKeys(config: VerifyConfigShape): string[] {\n const keys = new Set(Object.keys(config.state ?? {}));\n for (const sub of Object.values(config.subsystems ?? {})) {\n for (const f of sub.state ?? []) keys.add(f);\n }\n return [...keys];\n}\n\n/** Dotted identifier paths in an expression, minus string literals and keywords. */\nfunction fieldsIn(expr: string): string[] {\n const noStrings = expr.replace(/\"[^\"]*\"|'[^']*'/g, \"\");\n const ids = noStrings.match(/[a-zA-Z_$][\\w$]*(?:\\.[a-zA-Z_$][\\w$]*)*/g) ?? [];\n const ignore = new Set([\"true\", \"false\", \"null\", \"undefined\", \"length\", \"value\"]);\n return ids.filter((id) => !ignore.has(id) && Number.isNaN(Number(id)));\n}\n\n/** A field matches a config key if either is a dotted prefix of the other. */\nfunction fieldKnown(field: string, keys: string[]): boolean {\n return keys.some((k) => k === field || field.startsWith(`${k}.`) || k.startsWith(`${field}.`));\n}\n\nconst NEGATIVE_TAGS = new Set([\"negative\", \"formal\"]);\n\nfunction checkTrace(\n trace: ScenarioTrace,\n messages: Set<string>,\n keys: string[],\n findings: Finding[]\n): void {\n const id = `${trace.feature} › ${trace.scenario}`;\n\n for (const step of [...trace.given, ...trace.when, ...trace.then]) {\n if (step.unbound) {\n findings.push({ kind: \"warn\", scenario: id, message: `step has no binding: \"${step.text}\"` });\n }\n }\n\n for (const step of trace.when) {\n if (step.message && !messages.has(step.message)) {\n findings.push({\n kind: \"error\",\n scenario: id,\n message: `When sends \"${step.message}\", which the verification config does not model`,\n });\n }\n }\n\n for (const step of [...trace.given, ...trace.then]) {\n if (!step.stateExpr) continue;\n for (const field of fieldsIn(step.stateExpr)) {\n if (!fieldKnown(field, keys)) {\n findings.push({\n kind: \"error\",\n scenario: id,\n message: `${step.keyword} asserts on \"${field}\", absent from the config's state map`,\n });\n }\n }\n }\n}\n\n/** A feature filters/selects/validates if any Then mentions an exclusion/limit notion. */\nfunction featureNeedsNegative(traces: ScenarioTrace[]): boolean {\n return traces.some((t) =>\n t.then.some((s) => /\\bnot\\b|exclud|reject|empty|invalid|limit|forbidden/i.test(s.text))\n );\n}\n\nfunction checkNegativeComplement(traces: ScenarioTrace[], findings: Finding[]): void {\n const byFeature = new Map<string, ScenarioTrace[]>();\n for (const t of traces) {\n const arr = byFeature.get(t.feature) ?? [];\n arr.push(t);\n byFeature.set(t.feature, arr);\n }\n for (const [feature, group] of byFeature) {\n const hasNegative = group.some((t) => t.tags.some((tag) => NEGATIVE_TAGS.has(tag)));\n if (!hasNegative && featureNeedsNegative(group)) {\n findings.push({\n kind: \"warn\",\n scenario: feature,\n message:\n \"feature filters/selects/validates but has no negative complement (a @negative or @formal scenario) — an over-permissive build would still pass\",\n });\n }\n }\n}\n\nexport async function checkAgainstVerify(opts: {\n featureFiles: string[];\n stepFiles: string[];\n configPath: string;\n}): Promise<CrossCheckResult> {\n const config = await loadVerifyConfig(opts.configPath);\n const messages = messageSet(config);\n const keys = stateKeys(config);\n\n const traces = await extractTraces(opts.featureFiles, opts.stepFiles);\n const findings: Finding[] = [];\n\n for (const trace of traces) checkTrace(trace, messages, keys, findings);\n checkNegativeComplement(traces, findings);\n\n return {\n ok: findings.every((f) => f.kind !== \"error\"),\n checked: traces.length,\n findings,\n };\n}\n",
10
+ "/**\n * The verify cross-link, half one: reduce each scenario to a {@link ScenarioTrace}\n * over the *same vocabulary the verification config speaks* — Given (initial\n * state exprs) → When (message types) → Then (state exprs).\n *\n * This is to `.feature` files what `extractCondition` (tools/analysis) is to a\n * `requires()` call: it reads the statically-declared `message`/`stateExpr`\n * metadata off each matched step binding and hands check-verify a structure it\n * can compare against the config and the TLA+ model. Steps that match no\n * binding are kept as `unbound` so gaps are visible rather than silently empty.\n */\nimport { parseFeatureFile } from \"./parse.ts\";\nimport { matchStep, resetRegistry } from \"./steps.ts\";\nimport type { ScenarioTrace, TraceStep } from \"./types.ts\";\n\nasync function loadStepModules(stepFiles: string[]): Promise<void> {\n resetRegistry();\n for (const file of stepFiles) {\n await import(`${file}?t=${Bun.nanoseconds()}`);\n }\n}\n\nfunction toTraceStep(text: string, keyword: TraceStep[\"keyword\"]): TraceStep {\n const match = matchStep(text, keyword);\n if (!match) return { text, keyword, unbound: true };\n return {\n text,\n keyword,\n message: match.binding.message,\n stateExpr: match.binding.stateExpr,\n };\n}\n\nexport async function extractTraces(\n featureFiles: string[],\n stepFiles: string[]\n): Promise<ScenarioTrace[]> {\n await loadStepModules(stepFiles);\n const traces: ScenarioTrace[] = [];\n\n for (const file of featureFiles) {\n const feature = await parseFeatureFile(file);\n for (const scenario of feature.scenarios) {\n const allSteps = [...feature.background, ...scenario.steps];\n const trace: ScenarioTrace = {\n feature: feature.name,\n scenario: scenario.name,\n tags: [...feature.tags, ...scenario.tags],\n given: [],\n when: [],\n // biome-ignore lint/suspicious/noThenProperty: `then` is Gherkin vocabulary here, not a thenable.\n then: [],\n file,\n };\n for (const step of allSteps) {\n trace[step.keyword].push(toTraceStep(step.text, step.keyword));\n }\n traces.push(trace);\n }\n }\n return traces;\n}\n",
11
+ "/**\n * @fairfox/polly/bdd — author executable Gherkin against polly's own model.\n *\n * The CLI (`polly bdd`) is the primary entry point; this barrel exposes the\n * pieces a consumer's step module needs (`defineStep`, `defineWorld`) plus the\n * programmatic surface for driving the runner and the verify cross-link.\n *\n * The binding declared by `defineStep` is dual-use — its `given`/`when`/`then`\n * drive the real factory bus, and its `message`/`stateExpr` are read statically\n * by the verify extractor. See tools/bdd/README.md.\n */\n\nexport { type DispatchBus, driveBus } from \"./bus-driver.ts\";\nexport { type CrossCheckResult, checkAgainstVerify } from \"./check-verify.ts\";\nexport { extractTraces } from \"./extract.ts\";\nexport { parseFeatureFile, parseFeatureText } from \"./parse.ts\";\nexport { type RunOptions, runFeatures } from \"./run.ts\";\nexport { defineStep, defineWorld, matchStep, registeredBindings } from \"./steps.ts\";\nexport type {\n BusLike,\n ParsedFeature,\n ParsedScenario,\n ParsedStep,\n RunResult,\n ScenarioResult,\n ScenarioTrace,\n SignalLike,\n StepBinding,\n StepFn,\n TraceStep,\n World,\n WorldDef,\n} from \"./types.ts\";\nexport { extractWitnesses, type ScenarioWitness } from \"./witness.ts\";\n",
12
+ "/**\n * The runtime engine: parse features, load step modules, drive each scenario\n * against the *real* factory world the consumer built in `defineWorld`.\n *\n * What this stratum can and cannot check is deliberate. Runtime BDD asserts on\n * *observable* behaviour — state-signal changes and the response a handler\n * returns. Polly's `requires()` preconditions are runtime no-ops (they exist\n * only for the TLA+ model), so a precondition-only negative (e.g. \"a guest\n * login is rejected\") is NOT runtime-observable. Tag those `@formal`: the\n * runner defers them with a clear note, and `polly verify` is where they're\n * actually checked — the `requires()` guard is extracted into the TLA+ model.\n * This is the three strata dividing the work.\n */\nimport { parseFeatureFile } from \"./parse.ts\";\nimport { getWorldDef, matchStep, resetRegistry } from \"./steps.ts\";\nimport type {\n ParsedFeature,\n ParsedScenario,\n ParsedStep,\n RunResult,\n ScenarioResult,\n StepResult,\n World,\n} from \"./types.ts\";\n\nconst FORMAL_TAG = \"formal\";\n\nexport interface RunOptions {\n featureFiles: string[];\n stepFiles: string[];\n /** Only run scenarios carrying this tag (without leading @); ~tag negates. */\n tagFilter?: string;\n}\n\n/** Import each step module so its `defineStep`/`defineWorld` side effects run. */\nasync function loadStepModules(stepFiles: string[]): Promise<void> {\n resetRegistry();\n for (const file of stepFiles) {\n // Cache-bust so repeated in-process runs re-register cleanly.\n await import(`${file}?t=${Bun.nanoseconds()}`);\n }\n}\n\nfunction tagMatches(tags: string[], filter?: string): boolean {\n if (!filter) return true;\n if (filter.startsWith(\"~\")) return !tags.includes(filter.slice(1));\n return tags.includes(filter);\n}\n\nasync function runStep(world: World, step: ParsedStep): Promise<StepResult> {\n const base = { text: step.text, rawKeyword: step.rawKeyword };\n const match = matchStep(step.text, step.keyword);\n if (!match) {\n return { ...base, outcome: \"undefined\", message: `no binding matches \"${step.text}\"` };\n }\n const fn = match.binding[step.keyword];\n if (!fn) {\n return {\n ...base,\n outcome: \"undefined\",\n message: `binding for \"${step.text}\" has no '${step.keyword}' callback`,\n };\n }\n try {\n const ret = await fn(world, ...match.args);\n // A `when` that returns the send promise gets its response captured.\n if (ret !== undefined) world.lastResponse = ret;\n return { ...base, outcome: \"pass\" };\n } catch (err) {\n world.lastError = err;\n return { ...base, outcome: \"fail\", message: err instanceof Error ? err.message : String(err) };\n }\n}\n\nasync function runScenario(\n world: World,\n feature: ParsedFeature,\n scenario: ParsedScenario,\n reset: (w: World) => void | Promise<void>\n): Promise<ScenarioResult> {\n const result: ScenarioResult = {\n feature: feature.name,\n scenario: scenario.name,\n tags: scenario.tags,\n outcome: \"pass\",\n steps: [],\n file: feature.file,\n };\n\n // Cold start: reset the world (signals → initial) and clear per-scenario scratch.\n await reset(world);\n world.vars = {};\n world.lastResponse = undefined;\n world.lastError = undefined;\n\n const steps = [...feature.background, ...scenario.steps];\n let aborted = false;\n for (const step of steps) {\n if (aborted) {\n result.steps.push({ text: step.text, rawKeyword: step.rawKeyword, outcome: \"skipped\" });\n continue;\n }\n const sr = await runStep(world, step);\n result.steps.push(sr);\n if (sr.outcome === \"fail\") {\n result.outcome = \"fail\";\n aborted = true;\n } else if (sr.outcome === \"undefined\") {\n result.outcome = result.outcome === \"fail\" ? \"fail\" : \"undefined\";\n aborted = true;\n }\n }\n return result;\n}\n\nexport async function runFeatures(options: RunOptions): Promise<RunResult> {\n await loadStepModules(options.stepFiles);\n\n const worldDef = getWorldDef();\n if (!worldDef) {\n throw new Error(\n \"no world defined. A step module must call defineWorld({ create, reset }) — see tools/bdd/README.md.\"\n );\n }\n const world = await worldDef.create();\n\n const features = await Promise.all(options.featureFiles.map((f) => parseFeatureFile(f)));\n const scenarios: ScenarioResult[] = [];\n\n for (const feature of features) {\n for (const scenario of feature.scenarios) {\n const tags = [...feature.tags, ...scenario.tags];\n if (!tagMatches(tags, options.tagFilter)) continue;\n\n if (tags.includes(FORMAL_TAG)) {\n scenarios.push({\n feature: feature.name,\n scenario: scenario.name,\n tags,\n outcome: \"deferred-formal\",\n steps: [],\n file: feature.file,\n });\n continue;\n }\n scenarios.push(await runScenario(world, feature, { ...scenario, tags }, worldDef.reset));\n }\n }\n\n const passed = scenarios.filter((s) => s.outcome === \"pass\").length;\n const failed = scenarios.filter((s) => s.outcome === \"fail\").length;\n const undef = scenarios.filter((s) => s.outcome === \"undefined\").length;\n const deferred = scenarios.filter((s) => s.outcome === \"deferred-formal\").length;\n\n return {\n scenarios,\n passed,\n failed,\n undefinedSteps: undef,\n deferred,\n ok: failed === 0 && undef === 0,\n };\n}\n"
13
+ ],
14
+ "mappings": "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",
15
+ "debugId": "8FDB3C27E529750664756E2164756E21",
16
+ "names": []
17
+ }
@@ -0,0 +1,3 @@
1
+ import type { ParsedFeature } from "./types.ts";
2
+ export declare function parseFeatureText(text: string, file: string): ParsedFeature;
3
+ export declare function parseFeatureFile(path: string): Promise<ParsedFeature>;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Human- and machine-readable formatting of a {@link RunResult}.
3
+ */
4
+ import type { RunResult } from "./types.ts";
5
+ export declare function formatRun(result: RunResult, cwd: string): string;
6
+ export declare function toJson(result: RunResult): string;
@@ -0,0 +1,8 @@
1
+ import type { RunResult } from "./types.ts";
2
+ export interface RunOptions {
3
+ featureFiles: string[];
4
+ stepFiles: string[];
5
+ /** Only run scenarios carrying this tag (without leading @); ~tag negates. */
6
+ tagFilter?: string;
7
+ }
8
+ export declare function runFeatures(options: RunOptions): Promise<RunResult>;