parseman 0.44.0 → 0.46.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 (111) hide show
  1. package/dist/analysis/choice-cost-gate.d.ts +158 -0
  2. package/dist/analysis/choice-cost-gate.d.ts.map +1 -0
  3. package/dist/analysis/choice-cost-render.d.ts +88 -0
  4. package/dist/analysis/choice-cost-render.d.ts.map +1 -0
  5. package/dist/analysis/choice-cost.d.ts +383 -0
  6. package/dist/analysis/choice-cost.d.ts.map +1 -0
  7. package/dist/analysis/corpus.d.ts +73 -0
  8. package/dist/analysis/corpus.d.ts.map +1 -0
  9. package/dist/analysis/diagnose-render.d.ts +65 -0
  10. package/dist/analysis/diagnose-render.d.ts.map +1 -0
  11. package/dist/analysis/diagnose.d.ts +133 -0
  12. package/dist/analysis/diagnose.d.ts.map +1 -0
  13. package/dist/analysis/diagnostics.cjs +11272 -0
  14. package/dist/analysis/diagnostics.cjs.map +7 -0
  15. package/dist/analysis/diagnostics.d.ts +27 -0
  16. package/dist/analysis/diagnostics.d.ts.map +1 -0
  17. package/dist/analysis/diagnostics.js +11249 -0
  18. package/dist/analysis/diagnostics.js.map +7 -0
  19. package/dist/analysis/fix-render.d.ts +43 -0
  20. package/dist/analysis/fix-render.d.ts.map +1 -0
  21. package/dist/analysis/fix.d.ts +172 -0
  22. package/dist/analysis/fix.d.ts.map +1 -0
  23. package/dist/analysis/gating.d.ts +17 -9
  24. package/dist/analysis/gating.d.ts.map +1 -1
  25. package/dist/analysis/grammar.d.ts.map +1 -1
  26. package/dist/analysis/rebuild.d.ts +61 -0
  27. package/dist/analysis/rebuild.d.ts.map +1 -0
  28. package/dist/analysis/terminal.d.ts +142 -0
  29. package/dist/analysis/terminal.d.ts.map +1 -0
  30. package/dist/cli/index.cjs +12507 -0
  31. package/dist/cli/index.cjs.map +7 -0
  32. package/dist/cli/index.d.ts +3 -0
  33. package/dist/cli/index.d.ts.map +1 -0
  34. package/dist/cli/index.js +12484 -0
  35. package/dist/cli/index.js.map +7 -0
  36. package/dist/combinators/choice.d.ts.map +1 -1
  37. package/dist/combinators/dispatch.d.ts +19 -1
  38. package/dist/combinators/dispatch.d.ts.map +1 -1
  39. package/dist/combinators/expect.d.ts.map +1 -1
  40. package/dist/combinators/first-set.d.ts.map +1 -1
  41. package/dist/combinators/node.d.ts +19 -0
  42. package/dist/combinators/node.d.ts.map +1 -1
  43. package/dist/compiler/build-arity.d.ts +28 -0
  44. package/dist/compiler/build-arity.d.ts.map +1 -1
  45. package/dist/compiler/codegen.d.ts +80 -60
  46. package/dist/compiler/codegen.d.ts.map +1 -1
  47. package/dist/compiler/degradation.d.ts +123 -0
  48. package/dist/compiler/degradation.d.ts.map +1 -0
  49. package/dist/compiler/fields.d.ts.map +1 -1
  50. package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
  51. package/dist/compiler/inline-build.d.ts.map +1 -1
  52. package/dist/compiler/ir-serialize.d.ts.map +1 -1
  53. package/dist/compiler/linker.d.ts +45 -7
  54. package/dist/compiler/linker.d.ts.map +1 -1
  55. package/dist/compiler/module-hoist.d.ts +106 -0
  56. package/dist/compiler/module-hoist.d.ts.map +1 -0
  57. package/dist/coverage.d.ts +13 -0
  58. package/dist/coverage.d.ts.map +1 -1
  59. package/dist/cst/reflection.d.ts.map +1 -1
  60. package/dist/functional/run.d.ts.map +1 -1
  61. package/dist/index.cjs +1846 -610
  62. package/dist/index.cjs.map +4 -4
  63. package/dist/index.d.ts +11 -7
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +1846 -610
  66. package/dist/index.js.map +4 -4
  67. package/dist/language-service/index.cjs +40 -30
  68. package/dist/language-service/index.cjs.map +3 -3
  69. package/dist/language-service/index.js +40 -30
  70. package/dist/language-service/index.js.map +3 -3
  71. package/dist/oracle/digest.d.ts +132 -6
  72. package/dist/oracle/digest.d.ts.map +1 -1
  73. package/dist/oracle/index.cjs +136 -305
  74. package/dist/oracle/index.cjs.map +4 -4
  75. package/dist/oracle/index.d.ts +43 -31
  76. package/dist/oracle/index.d.ts.map +1 -1
  77. package/dist/oracle/index.js +136 -305
  78. package/dist/oracle/index.js.map +4 -4
  79. package/dist/plugin/evaluator.d.ts +3 -0
  80. package/dist/plugin/evaluator.d.ts.map +1 -1
  81. package/dist/plugin/free-identifiers.d.ts +23 -0
  82. package/dist/plugin/free-identifiers.d.ts.map +1 -0
  83. package/dist/plugin/index.cjs +1529 -763
  84. package/dist/plugin/index.cjs.map +4 -4
  85. package/dist/plugin/index.d.ts +18 -0
  86. package/dist/plugin/index.d.ts.map +1 -1
  87. package/dist/plugin/index.js +1529 -763
  88. package/dist/plugin/index.js.map +4 -4
  89. package/dist/plugin/reducer-resolver.d.ts +57 -0
  90. package/dist/plugin/reducer-resolver.d.ts.map +1 -0
  91. package/dist/run/index.cjs +33 -30
  92. package/dist/run/index.cjs.map +2 -2
  93. package/dist/run/index.js +33 -30
  94. package/dist/run/index.js.map +2 -2
  95. package/dist/spec/index.cjs +222 -34
  96. package/dist/spec/index.cjs.map +4 -4
  97. package/dist/spec/index.js +222 -34
  98. package/dist/spec/index.js.map +4 -4
  99. package/dist/spec/model.d.ts.map +1 -1
  100. package/dist/types.d.ts +6 -1
  101. package/dist/types.d.ts.map +1 -1
  102. package/dist/version.d.ts +1 -1
  103. package/package.json +23 -4
  104. package/dist/combinators/guard.d.ts +0 -8
  105. package/dist/combinators/guard.d.ts.map +0 -1
  106. package/dist/cst/offset-model.d.ts +0 -119
  107. package/dist/cst/offset-model.d.ts.map +0 -1
  108. package/dist/oracle/corpus.d.ts +0 -32
  109. package/dist/oracle/corpus.d.ts.map +0 -1
  110. package/dist/oracle/identity.d.ts +0 -117
  111. package/dist/oracle/identity.d.ts.map +0 -1
@@ -1,11 +1,113 @@
1
1
  // src/oracle/digest.ts
2
2
  import { createHash } from "node:crypto";
3
3
  var DIGEST_FORMAT = 1;
4
+ var SEPARATOR = String.fromCharCode(0);
4
5
  var str = (s) => JSON.stringify(s);
5
6
  var tagOf = (v) => {
6
7
  const ctor = v.constructor;
7
8
  return typeof ctor === "function" && typeof ctor.name === "string" ? ctor.name : "";
8
9
  };
10
+ var ArraySink = class {
11
+ tokens = [];
12
+ push(token) {
13
+ this.tokens.push(token);
14
+ }
15
+ };
16
+ var StreamSink = class {
17
+ target;
18
+ buffer = [];
19
+ buffered = 0;
20
+ started = false;
21
+ constructor(target) {
22
+ this.target = target;
23
+ }
24
+ /**
25
+ * Write text that is NOT a canonical token and takes no separator — the
26
+ * `OK:` / `ERR:` discriminator, which the string form concatenates onto the
27
+ * front rather than joining.
28
+ */
29
+ prefix(text) {
30
+ if (text === "") return;
31
+ this.buffer.push(text);
32
+ this.buffered += text.length;
33
+ }
34
+ push(token) {
35
+ if (this.started) {
36
+ this.buffer.push(SEPARATOR);
37
+ this.buffered += 1;
38
+ } else {
39
+ this.started = true;
40
+ }
41
+ this.buffer.push(token);
42
+ this.buffered += token.length;
43
+ if (this.buffered >= FLUSH_CHARS) this.flush();
44
+ }
45
+ flush() {
46
+ if (this.buffer.length === 0) return;
47
+ this.target.update(this.buffer.join(""));
48
+ this.buffer = [];
49
+ this.buffered = 0;
50
+ }
51
+ };
52
+ var FLUSH_CHARS = 1 << 16;
53
+ var CanonicalBudgetError = class extends Error {
54
+ name = "CanonicalBudgetError";
55
+ constructor(budget, projected) {
56
+ super(
57
+ `canonicalize: exceeded ${budget} visits. The value is not merely large \u2014 it is almost certainly a DAG being unrolled. This projection writes a shared subtree once per PATH that reaches it (see "Sharing is not a cycle"), so a node referenced from two places at each of d levels costs 2^d. Deduplicate the shared structure, or raise \`maxVisits\` if the value really is that big.` + (projected === void 0 ? "" : ` Refused EARLY: the walk was projected to visit ${projected} objects, so it could not have finished under this budget however long it ran. See {@link projectVisits}.`)
58
+ );
59
+ }
60
+ };
61
+ var DEFAULT_MAX_VISITS = 1e8;
62
+ var EXPANSION_PROBE_VISITS = 1e6;
63
+ var EXPANSION_PROBE_NODES = 2e5;
64
+ var EXPANSION_PROBE_MARGIN = 4;
65
+ function projectVisits(root) {
66
+ const memo = /* @__PURE__ */ new Map();
67
+ const path = /* @__PURE__ */ new Set();
68
+ let nodes = 0;
69
+ let usable = true;
70
+ const count = (v) => {
71
+ if (typeof v !== "object" || v === null) return 0;
72
+ if (path.has(v)) {
73
+ usable = false;
74
+ return 1;
75
+ }
76
+ const done = memo.get(v);
77
+ if (done !== void 0) return done;
78
+ if (++nodes > EXPANSION_PROBE_NODES) {
79
+ usable = false;
80
+ return 1;
81
+ }
82
+ path.add(v);
83
+ let total = 1;
84
+ if (Array.isArray(v)) {
85
+ for (const item of v) total += count(item);
86
+ } else if (v instanceof Map) {
87
+ for (const [k, val] of v) total += count(k) + count(val);
88
+ } else if (v instanceof Set) {
89
+ for (const item of v) total += count(item);
90
+ } else if (!(v instanceof Date) && !(v instanceof RegExp)) {
91
+ for (const k of Object.keys(v)) total += count(v[k]);
92
+ }
93
+ path.delete(v);
94
+ memo.set(v, total);
95
+ return total;
96
+ };
97
+ try {
98
+ const total = count(root);
99
+ return usable ? total : void 0;
100
+ } catch {
101
+ return void 0;
102
+ }
103
+ }
104
+ function probeExpansion(state) {
105
+ state.probeAt = -1;
106
+ const projected = projectVisits(state.root);
107
+ if (projected !== void 0 && projected / EXPANSION_PROBE_MARGIN > state.budget) {
108
+ throw new CanonicalBudgetError(state.budget, projected);
109
+ }
110
+ }
9
111
  function writeScalar(v, out) {
10
112
  switch (typeof v) {
11
113
  case "undefined":
@@ -47,11 +149,15 @@ function writeScalar(v, out) {
47
149
  out.push("z");
48
150
  }
49
151
  }
50
- function write(v, path, depth, out) {
152
+ function write(v, state, depth) {
153
+ const out = state.sink;
51
154
  if (typeof v !== "object" || v === null) {
52
155
  writeScalar(v, out);
53
156
  return;
54
157
  }
158
+ if (--state.visits < 0) throw new CanonicalBudgetError(state.budget);
159
+ if (state.visits === state.probeAt) probeExpansion(state);
160
+ const path = state.path;
55
161
  const seenAt = path.get(v);
56
162
  if (seenAt !== void 0) {
57
163
  out.push(`^${depth - seenAt}`);
@@ -60,18 +166,18 @@ function write(v, path, depth, out) {
60
166
  path.set(v, depth);
61
167
  if (Array.isArray(v)) {
62
168
  out.push("[");
63
- for (const item of v) write(item, path, depth + 1, out);
169
+ for (const item of v) write(item, state, depth + 1);
64
170
  out.push("]");
65
171
  } else if (v instanceof Map) {
66
172
  out.push("M");
67
173
  for (const [k, val] of v) {
68
- write(k, path, depth + 1, out);
69
- write(val, path, depth + 1, out);
174
+ write(k, state, depth + 1);
175
+ write(val, state, depth + 1);
70
176
  }
71
177
  out.push("}");
72
178
  } else if (v instanceof Set) {
73
179
  out.push("S");
74
- for (const item of v) write(item, path, depth + 1, out);
180
+ for (const item of v) write(item, state, depth + 1);
75
181
  out.push("}");
76
182
  } else if (v instanceof Date) {
77
183
  out.push(`D${v.getTime()}`);
@@ -82,319 +188,44 @@ function write(v, path, depth, out) {
82
188
  out.push(`{${str(tag === "Object" ? "" : tag)}`);
83
189
  for (const k of Object.keys(v).sort()) {
84
190
  out.push(`k${str(k)}`);
85
- write(v[k], path, depth + 1, out);
191
+ write(v[k], state, depth + 1);
86
192
  }
87
193
  out.push("}");
88
194
  }
89
195
  path.delete(v);
90
196
  }
91
- function canonicalize(value) {
92
- const out = [];
93
- write(value, /* @__PURE__ */ new Map(), 0, out);
94
- return out.join("\0");
95
- }
96
- function hash(payload2) {
97
- return createHash("sha256").update(payload2).digest("hex");
98
- }
99
- var FINGERPRINT_HEX = 16;
100
-
101
- // src/oracle/identity.ts
102
- var defaultProjectError = (thrown) => thrown instanceof Error ? { name: thrown.name, message: thrown.message } : { thrown };
103
- function payload(surface, entry, options) {
104
- try {
105
- return { text: `OK:${canonicalize(surface.parse(entry.source, entry.id))}`, threw: false };
106
- } catch (thrown) {
107
- const project = options.projectError ?? defaultProjectError;
108
- return { text: `ERR:${canonicalize(project(thrown, entry.id, surface.name))}`, threw: true };
109
- }
110
- }
111
- var fingerprint = (text) => hash(text).slice(0, FINGERPRINT_HEX);
112
- function digestCorpus(surfaces, corpus, options = {}) {
113
- if (surfaces.length === 0) throw new Error("digestCorpus: no surfaces \u2014 there is nothing to compare.");
114
- const names = /* @__PURE__ */ new Set();
115
- for (const s of surfaces) {
116
- if (names.has(s.name)) throw new Error(`digestCorpus: duplicate surface name ${JSON.stringify(s.name)}.`);
117
- names.add(s.name);
118
- }
119
- const entries = [...corpus].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
120
- for (let n = 1; n < entries.length; n++) {
121
- if (entries[n].id === entries[n - 1].id) {
122
- throw new Error(`digestCorpus: duplicate corpus id ${JSON.stringify(entries[n].id)}.`);
123
- }
124
- }
125
- const perEntry = {};
126
- const threw = {};
127
- const texts = [];
128
- for (const s of surfaces) threw[s.name] = 0;
129
- for (const entry of entries) {
130
- const row = {};
131
- const rowTexts = /* @__PURE__ */ new Map();
132
- for (const s of surfaces) {
133
- const result = payload(s, entry, options);
134
- if (result.threw) threw[s.name]++;
135
- row[s.name] = fingerprint(result.text);
136
- rowTexts.set(s.name, result.text);
137
- }
138
- perEntry[entry.id] = row;
139
- texts.push(rowTexts);
140
- }
141
- verifyDeterminism(surfaces, entries, texts, options);
142
- return {
143
- format: DIGEST_FORMAT,
144
- harness: HARNESS_DIGEST,
145
- entries: entries.length,
146
- surfaces: surfaces.map((s) => ({
147
- name: s.name,
148
- // The surface NAME is inside its own aggregate: two surfaces that happen
149
- // to agree on every entry must still have distinct aggregates, or a
150
- // report that swapped them would compare equal.
151
- aggregate: hash(
152
- [`surface:${s.name}`, ...entries.map((e) => `${e.id}:${perEntry[e.id][s.name]}`)].join("\n")
153
- ),
154
- threw: threw[s.name]
155
- })),
156
- perEntry
157
- };
158
- }
159
- function verifyDeterminism(surfaces, entries, texts, options) {
160
- const want = options.determinismSample ?? 32;
161
- if (want <= 0 || entries.length === 0) return;
162
- const stride = Math.max(1, Math.floor(entries.length / Math.min(want, entries.length)));
163
- for (let n = 0; n < entries.length; n += stride) {
164
- const entry = entries[n];
165
- for (const s of surfaces) {
166
- if (payload(s, entry, options).text === texts[n].get(s.name)) continue;
167
- throw new Error(
168
- `digestCorpus: surface ${JSON.stringify(s.name)} is NOT DETERMINISTIC on ${JSON.stringify(entry.id)} \u2014 two parses of the same source produced different output. Every digest from this grammar would move on its own, so a comparison against it cannot mean anything. Usual causes: a timestamp or counter in the output, iteration over a keyed-by-object Map, or a node carrying a reference to mutable shared state.`
169
- );
170
- }
171
- }
172
- }
173
- function compareReports(before, after) {
174
- if (before.harness !== after.harness) {
175
- return incomparable(
176
- `harness drift: reports were produced by DIFFERENT versions of the identity harness (${before.harness.slice(0, 12)}\u2026 vs ${after.harness.slice(0, 12)}\u2026). Their digests are not comparable, and treating them as such would either invent a regression or hide one. Re-run BOTH sides on one harness.`
197
+ function newState(sink, options, root) {
198
+ const budget = options?.maxVisits ?? DEFAULT_MAX_VISITS;
199
+ if (!Number.isSafeInteger(budget) || budget < 0) {
200
+ throw new RangeError(
201
+ `canonicalize: maxVisits must be a non-negative safe integer, received ${String(budget)}. A non-finite or fractional budget does not raise the ceiling, it REMOVES it \u2014 the budget is spent with \`--visits < 0\`, which is false forever for NaN and never reached for Infinity \u2014 so the walk this bounds would run unbounded into the OOM the budget exists to prevent.`
177
202
  );
178
203
  }
179
- if (before.format !== after.format) {
180
- return incomparable(`digest format ${before.format} vs ${after.format} \u2014 re-run both sides on one version.`);
181
- }
182
- const beforeIds = new Set(Object.keys(before.perEntry));
183
- const afterIds = new Set(Object.keys(after.perEntry));
184
- const addedEntries = [...afterIds].filter((id) => !beforeIds.has(id)).sort();
185
- const removedEntries = [...beforeIds].filter((id) => !afterIds.has(id)).sort();
186
- const shared = [...beforeIds].filter((id) => afterIds.has(id)).sort();
187
- const byName = /* @__PURE__ */ new Map();
188
- for (const s of before.surfaces) byName.set(s.name, { ...byName.get(s.name), before: s });
189
- for (const s of after.surfaces) byName.set(s.name, { ...byName.get(s.name), after: s });
190
- const surfaces = [...byName.entries()].sort(([a], [b]) => a < b ? -1 : 1).map(([name, pair]) => {
191
- const moved = pair.before && pair.after ? shared.filter((id) => before.perEntry[id][name] !== after.perEntry[id][name]) : [];
192
- return {
193
- name,
194
- before: pair.before?.aggregate ?? null,
195
- after: pair.after?.aggregate ?? null,
196
- equal: pair.before !== void 0 && pair.after !== void 0 && pair.before.aggregate === pair.after.aggregate,
197
- moved,
198
- beforeThrew: pair.before?.threw ?? null,
199
- afterThrew: pair.after?.threw ?? null
200
- };
201
- });
202
- const identical = addedEntries.length === 0 && removedEntries.length === 0 && surfaces.every((s) => s.equal);
203
- return { verdict: identical ? "identical" : "moved", reason: null, addedEntries, removedEntries, surfaces };
204
+ const probeAt = budget >= EXPANSION_PROBE_VISITS ? budget - EXPANSION_PROBE_VISITS : -1;
205
+ return { path: /* @__PURE__ */ new Map(), sink, budget, root, visits: budget, probeAt };
204
206
  }
205
- function incomparable(reason) {
206
- return { verdict: "incomparable", reason, addedEntries: [], removedEntries: [], surfaces: [] };
207
+ function canonicalize(value, options) {
208
+ const out = new ArraySink();
209
+ write(value, newState(out, options, value), 0);
210
+ return out.tokens.join(SEPARATOR);
207
211
  }
208
- function formatComparison(c, options = {}) {
209
- if (c.verdict === "incomparable") return `INCOMPARABLE \u2014 ${c.reason ?? "unknown reason"}`;
210
- const max = options.maxMoved ?? 10;
211
- const lines = [];
212
- for (const s of c.surfaces) {
213
- if (s.before === null) lines.push(` + surface ${s.name} (added)`);
214
- else if (s.after === null) lines.push(` - surface ${s.name} (removed)`);
215
- else if (s.equal) lines.push(` = ${s.name} ${s.before.slice(0, 16)}\u2026 threw=${s.afterThrew}`);
216
- else {
217
- lines.push(
218
- ` ! ${s.name} ${s.before.slice(0, 16)}\u2026 -> ${s.after.slice(0, 16)}\u2026 threw ${s.beforeThrew} -> ${s.afterThrew} (${s.moved.length} entries moved)`
219
- );
220
- for (const id of s.moved.slice(0, max)) lines.push(` ${id}`);
221
- if (s.moved.length > max) lines.push(` \u2026 and ${s.moved.length - max} more`);
222
- }
223
- }
224
- if (c.addedEntries.length > 0) lines.push(` corpus GAINED ${c.addedEntries.length} entries`);
225
- if (c.removedEntries.length > 0) {
226
- lines.push(
227
- ` corpus LOST ${c.removedEntries.length} entries \u2014 a differential over a SMALLER corpus is a weaker gate, not a passing one`
228
- );
229
- }
230
- return [c.verdict === "identical" ? "IDENTICAL \u2014 output-neutral" : "MOVED \u2014 this is not a refactor", ...lines].join("\n");
231
- }
232
- function canaryReport() {
233
- class Tagged {
234
- x;
235
- constructor(x) {
236
- this.x = x;
237
- }
238
- }
239
- const shared = { shared: true };
240
- const cyclic = { name: "root" };
241
- cyclic.self = cyclic;
242
- cyclic.child = { up: cyclic };
243
- const values = {
244
- "a/scalars": [0, -0, 1.5, Number.NaN, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, 10n, true, false, null],
245
- "b/absent": [{ a: void 0 }, {}, { a: null }, [void 0], []],
246
- "c/key-order": [{ a: 1, b: 2 }, { b: 2, a: 1 }],
247
- "d/collections": [/* @__PURE__ */ new Map([["k", 1], ["j", 2]]), /* @__PURE__ */ new Set([1, 2]), /* @__PURE__ */ new Date(0), /ab+c/giu],
248
- "e/tagged": [new Tagged(1), { x: 1 }],
249
- "f/sharing": { left: shared, right: shared },
250
- "g/cycle": cyclic,
251
- "h/text": ["", "a\0b", "a b", '"quoted"', "\\"],
252
- "i/callable": [function named() {
253
- }, /* @__PURE__ */ Symbol("sym")]
254
- };
255
- const surfaces = [
256
- { name: "value", parse: (_source, id) => values[id] },
257
- {
258
- name: "thrower",
259
- parse: (_source, id) => {
260
- if (id === "a/scalars") throw new TypeError("canary");
261
- if (id === "b/absent") throw "a bare string";
262
- return { id };
263
- }
264
- }
265
- ];
266
- const corpus = Object.keys(values).map((id) => ({ id, source: id }));
267
- const perEntry = {};
268
- const threw = { value: 0, thrower: 0 };
269
- for (const entry of [...corpus].sort((a, b) => a.id < b.id ? -1 : 1)) {
270
- const row = {};
271
- for (const s of surfaces) {
272
- const result = payload(s, entry, {});
273
- if (result.threw) threw[s.name]++;
274
- row[s.name] = fingerprint(result.text);
275
- }
276
- perEntry[entry.id] = row;
277
- }
278
- const ids = Object.keys(perEntry).sort();
279
- return {
280
- format: DIGEST_FORMAT,
281
- harness: "",
282
- entries: ids.length,
283
- surfaces: surfaces.map((s) => ({
284
- name: s.name,
285
- aggregate: hash([`surface:${s.name}`, ...ids.map((id) => `${id}:${perEntry[id][s.name]}`)].join("\n")),
286
- threw: threw[s.name]
287
- })),
288
- perEntry
289
- };
290
- }
291
- var HARNESS_DIGEST = (() => {
292
- const canary = canaryReport();
293
- return hash(
294
- [`format:${DIGEST_FORMAT}`, ...canary.surfaces.map((s) => `${s.name}:${s.aggregate}:${s.threw}`)].join("\n")
295
- );
296
- })();
297
-
298
- // src/oracle/corpus.ts
299
- import { readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
300
- import { join, relative, resolve, sep } from "node:path";
301
- var DEFAULT_IGNORE = [".git", "node_modules/.cache"];
302
- function loadCorpus(options) {
303
- const base = resolve(options.base);
304
- const ignoreDirs = options.ignoreDirs ?? DEFAULT_IGNORE;
305
- const ignoreNames = new Set(ignoreDirs.filter((d) => !d.includes("/")));
306
- const ignorePaths = ignoreDirs.filter((d) => d.includes("/")).map((d) => d.replace(/^\.?\/+/, "").replace(/\/+$/, ""));
307
- const maxBytes = options.maxBytes ?? Number.POSITIVE_INFINITY;
308
- const exts = new Set(options.extensions.map((e) => e.toLowerCase()));
309
- const missingRoots = [];
310
- const skippedLarge = [];
311
- const files = /* @__PURE__ */ new Map();
312
- const isIgnored = (name, relFromRoot) => {
313
- if (ignoreNames.has(name)) return true;
314
- return ignorePaths.some((p) => relFromRoot === p || relFromRoot.endsWith(`/${p}`));
315
- };
316
- const walk = (dir, root, visited) => {
317
- let real;
318
- try {
319
- real = realpathSync(dir);
320
- } catch {
321
- return;
322
- }
323
- if (visited.has(real)) return;
324
- visited.add(real);
325
- for (const item of readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
326
- const full = join(dir, item.name);
327
- if (isIgnored(item.name, relative(root, full).split(sep).join("/"))) continue;
328
- let isDir = item.isDirectory();
329
- let isFile = item.isFile();
330
- let size = 0;
331
- if (item.isSymbolicLink() || !isDir && !isFile) {
332
- try {
333
- const st = statSync(full);
334
- isDir = st.isDirectory();
335
- isFile = st.isFile();
336
- size = st.size;
337
- } catch {
338
- continue;
339
- }
340
- }
341
- if (isDir) {
342
- walk(full, root, visited);
343
- continue;
344
- }
345
- if (!isFile) continue;
346
- const dot = item.name.lastIndexOf(".");
347
- if (dot < 0 || !exts.has(item.name.slice(dot).toLowerCase())) continue;
348
- if (size === 0) {
349
- try {
350
- size = statSync(full).size;
351
- } catch {
352
- continue;
353
- }
354
- }
355
- if (size > maxBytes) {
356
- skippedLarge.push(idOf(base, full));
357
- continue;
358
- }
359
- let realFile;
360
- try {
361
- realFile = realpathSync(full);
362
- } catch {
363
- continue;
364
- }
365
- const id = idOf(base, full);
366
- const existing = files.get(realFile);
367
- if (existing === void 0 || id < existing) files.set(realFile, id);
368
- }
369
- };
370
- for (const root of options.roots) {
371
- const full = resolve(base, root);
372
- try {
373
- if (!statSync(full).isDirectory()) throw new Error("not a directory");
374
- } catch {
375
- if (!options.allowMissingRoots) {
376
- throw new Error(
377
- `loadCorpus: root ${JSON.stringify(root)} does not resolve to a directory (${full}). A corpus that quietly shrank produces a different aggregate that looks like a grammar change. Fix the root, or pass allowMissingRoots and record \`missingRoots\` alongside the digest.`
378
- );
379
- }
380
- missingRoots.push(root);
381
- continue;
382
- }
383
- walk(full, full, /* @__PURE__ */ new Set());
384
- }
385
- const entries = [...files.values()].sort().map((id) => ({ id, source: readFileSync(resolve(base, id), "utf8") }));
386
- return { entries, missingRoots, skippedLarge: skippedLarge.sort() };
212
+ function digestInto(target, value, prefix = "", options) {
213
+ const sink = new StreamSink(target);
214
+ sink.prefix(prefix);
215
+ write(value, newState(sink, options, value), 0);
216
+ sink.flush();
387
217
  }
388
- function idOf(base, full) {
389
- return relative(base, full).split(sep).join("/");
218
+ function digestValue(value, prefix = "", options) {
219
+ const sha = createHash("sha256");
220
+ digestInto(sha, value, prefix, options);
221
+ return sha.digest("hex");
390
222
  }
391
223
  export {
224
+ CanonicalBudgetError,
225
+ DEFAULT_MAX_VISITS,
392
226
  DIGEST_FORMAT,
393
- HARNESS_DIGEST,
394
227
  canonicalize,
395
- compareReports,
396
- digestCorpus,
397
- formatComparison,
398
- loadCorpus
228
+ digestInto,
229
+ digestValue
399
230
  };
400
231
  //# sourceMappingURL=index.js.map