@tryaura/aura-testkit 0.1.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/dist/index.js ADDED
@@ -0,0 +1,2005 @@
1
+ import { spawn } from "node:child_process";
2
+ import { dirname, isAbsolute, join, normalize, relative, sep } from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import { chmod, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rm, writeFile } from "node:fs/promises";
5
+ import { PassThrough, Readable } from "node:stream";
6
+ import { runCli } from "@tryaura/aura-cli";
7
+ import { tmpdir } from "node:os";
8
+ import { DEFAULT_HTTP_TIMEOUT_MS, MAX_HTTP_RESPONSE_BYTES, MAX_HTTP_TIMEOUT_MS } from "@tryaura/aura-sdk";
9
+ import { createServer } from "node:http";
10
+ //#region ../../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/base.js
11
+ var Diff = class {
12
+ diff(oldStr, newStr, options = {}) {
13
+ let callback;
14
+ if (typeof options === "function") {
15
+ callback = options;
16
+ options = {};
17
+ } else if ("callback" in options) callback = options.callback;
18
+ const oldString = this.castInput(oldStr, options);
19
+ const newString = this.castInput(newStr, options);
20
+ const oldTokens = this.removeEmpty(this.tokenize(oldString, options));
21
+ const newTokens = this.removeEmpty(this.tokenize(newString, options));
22
+ return this.diffWithOptionsObj(oldTokens, newTokens, options, callback);
23
+ }
24
+ diffWithOptionsObj(oldTokens, newTokens, options, callback) {
25
+ var _a;
26
+ const done = (value) => {
27
+ value = this.postProcess(value, options);
28
+ if (callback) {
29
+ setTimeout(function() {
30
+ callback(value);
31
+ }, 0);
32
+ return;
33
+ } else return value;
34
+ };
35
+ const newLen = newTokens.length, oldLen = oldTokens.length;
36
+ let editLength = 1;
37
+ let maxEditLength = newLen + oldLen;
38
+ if (options.maxEditLength != null) maxEditLength = Math.min(maxEditLength, options.maxEditLength);
39
+ const maxExecutionTime = (_a = options.timeout) !== null && _a !== void 0 ? _a : Infinity;
40
+ const abortAfterTimestamp = Date.now() + maxExecutionTime;
41
+ const bestPath = [{
42
+ oldPos: -1,
43
+ lastComponent: void 0
44
+ }];
45
+ let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options);
46
+ if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens));
47
+ let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;
48
+ const execEditLength = () => {
49
+ for (let diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
50
+ let basePath;
51
+ const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];
52
+ if (removePath) bestPath[diagonalPath - 1] = void 0;
53
+ let canAdd = false;
54
+ if (addPath) {
55
+ const addPathNewPos = addPath.oldPos - diagonalPath;
56
+ canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
57
+ }
58
+ const canRemove = removePath && removePath.oldPos + 1 < oldLen;
59
+ if (!canAdd && !canRemove) {
60
+ bestPath[diagonalPath] = void 0;
61
+ continue;
62
+ }
63
+ if (!canRemove || canAdd && removePath.oldPos < addPath.oldPos) basePath = this.addToPath(addPath, true, false, 0, options);
64
+ else basePath = this.addToPath(removePath, false, true, 1, options);
65
+ newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options);
66
+ if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || true;
67
+ else {
68
+ bestPath[diagonalPath] = basePath;
69
+ if (basePath.oldPos + 1 >= oldLen) maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
70
+ if (newPos + 1 >= newLen) minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
71
+ }
72
+ }
73
+ editLength++;
74
+ };
75
+ if (callback) (function exec() {
76
+ setTimeout(function() {
77
+ if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) return callback(void 0);
78
+ if (!execEditLength()) exec();
79
+ }, 0);
80
+ })();
81
+ else while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
82
+ const ret = execEditLength();
83
+ if (ret) return ret;
84
+ }
85
+ }
86
+ addToPath(path, added, removed, oldPosInc, options) {
87
+ const last = path.lastComponent;
88
+ if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) return {
89
+ oldPos: path.oldPos + oldPosInc,
90
+ lastComponent: {
91
+ count: last.count + 1,
92
+ added,
93
+ removed,
94
+ previousComponent: last.previousComponent
95
+ }
96
+ };
97
+ else return {
98
+ oldPos: path.oldPos + oldPosInc,
99
+ lastComponent: {
100
+ count: 1,
101
+ added,
102
+ removed,
103
+ previousComponent: last
104
+ }
105
+ };
106
+ }
107
+ extractCommon(basePath, newTokens, oldTokens, diagonalPath, options) {
108
+ const newLen = newTokens.length, oldLen = oldTokens.length;
109
+ let oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0;
110
+ while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) {
111
+ newPos++;
112
+ oldPos++;
113
+ commonCount++;
114
+ if (options.oneChangePerToken) basePath.lastComponent = {
115
+ count: 1,
116
+ previousComponent: basePath.lastComponent,
117
+ added: false,
118
+ removed: false
119
+ };
120
+ }
121
+ if (commonCount && !options.oneChangePerToken) basePath.lastComponent = {
122
+ count: commonCount,
123
+ previousComponent: basePath.lastComponent,
124
+ added: false,
125
+ removed: false
126
+ };
127
+ basePath.oldPos = oldPos;
128
+ return newPos;
129
+ }
130
+ equals(left, right, options) {
131
+ if (options.comparator) return options.comparator(left, right);
132
+ else return left === right || !!options.ignoreCase && left.toLowerCase() === right.toLowerCase();
133
+ }
134
+ removeEmpty(array) {
135
+ const ret = [];
136
+ for (let i = 0; i < array.length; i++) if (array[i]) ret.push(array[i]);
137
+ return ret;
138
+ }
139
+ castInput(value, options) {
140
+ return value;
141
+ }
142
+ tokenize(value, options) {
143
+ return Array.from(value);
144
+ }
145
+ join(chars) {
146
+ return chars.join("");
147
+ }
148
+ postProcess(changeObjects, options) {
149
+ return changeObjects;
150
+ }
151
+ get useLongestToken() {
152
+ return false;
153
+ }
154
+ buildValues(lastComponent, newTokens, oldTokens) {
155
+ const components = [];
156
+ let nextComponent;
157
+ while (lastComponent) {
158
+ components.push(lastComponent);
159
+ nextComponent = lastComponent.previousComponent;
160
+ delete lastComponent.previousComponent;
161
+ lastComponent = nextComponent;
162
+ }
163
+ components.reverse();
164
+ const componentLen = components.length;
165
+ let componentPos = 0, newPos = 0, oldPos = 0;
166
+ for (; componentPos < componentLen; componentPos++) {
167
+ const component = components[componentPos];
168
+ if (!component.removed) {
169
+ if (!component.added && this.useLongestToken) {
170
+ let value = newTokens.slice(newPos, newPos + component.count);
171
+ value = value.map(function(value, i) {
172
+ const oldValue = oldTokens[oldPos + i];
173
+ return oldValue.length > value.length ? oldValue : value;
174
+ });
175
+ component.value = this.join(value);
176
+ } else component.value = this.join(newTokens.slice(newPos, newPos + component.count));
177
+ newPos += component.count;
178
+ if (!component.added) oldPos += component.count;
179
+ } else {
180
+ component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count));
181
+ oldPos += component.count;
182
+ }
183
+ }
184
+ return components;
185
+ }
186
+ };
187
+ //#endregion
188
+ //#region ../../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/character.js
189
+ var CharacterDiff = class extends Diff {};
190
+ new CharacterDiff();
191
+ //#endregion
192
+ //#region ../../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/util/string.js
193
+ function longestCommonPrefix(str1, str2) {
194
+ let i;
195
+ for (i = 0; i < str1.length && i < str2.length; i++) if (str1[i] != str2[i]) return str1.slice(0, i);
196
+ return str1.slice(0, i);
197
+ }
198
+ function longestCommonSuffix(str1, str2) {
199
+ let i;
200
+ if (!str1 || !str2 || str1[str1.length - 1] != str2[str2.length - 1]) return "";
201
+ for (i = 0; i < str1.length && i < str2.length; i++) if (str1[str1.length - (i + 1)] != str2[str2.length - (i + 1)]) return str1.slice(-i);
202
+ return str1.slice(-i);
203
+ }
204
+ function replacePrefix(string, oldPrefix, newPrefix) {
205
+ if (string.slice(0, oldPrefix.length) != oldPrefix) throw Error(`string ${JSON.stringify(string)} doesn't start with prefix ${JSON.stringify(oldPrefix)}; this is a bug`);
206
+ return newPrefix + string.slice(oldPrefix.length);
207
+ }
208
+ function replaceSuffix(string, oldSuffix, newSuffix) {
209
+ if (!oldSuffix) return string + newSuffix;
210
+ if (string.slice(-oldSuffix.length) != oldSuffix) throw Error(`string ${JSON.stringify(string)} doesn't end with suffix ${JSON.stringify(oldSuffix)}; this is a bug`);
211
+ return string.slice(0, -oldSuffix.length) + newSuffix;
212
+ }
213
+ function removePrefix(string, oldPrefix) {
214
+ return replacePrefix(string, oldPrefix, "");
215
+ }
216
+ function removeSuffix(string, oldSuffix) {
217
+ return replaceSuffix(string, oldSuffix, "");
218
+ }
219
+ function maximumOverlap(string1, string2) {
220
+ return string2.slice(0, overlapCount(string1, string2));
221
+ }
222
+ function overlapCount(a, b) {
223
+ let startA = 0;
224
+ if (a.length > b.length) startA = a.length - b.length;
225
+ let endB = b.length;
226
+ if (a.length < b.length) endB = a.length;
227
+ const map = Array(endB);
228
+ let k = 0;
229
+ map[0] = 0;
230
+ for (let j = 1; j < endB; j++) {
231
+ if (b[j] == b[k]) map[j] = map[k];
232
+ else map[j] = k;
233
+ while (k > 0 && b[j] != b[k]) k = map[k];
234
+ if (b[j] == b[k]) k++;
235
+ }
236
+ k = 0;
237
+ for (let i = startA; i < a.length; i++) {
238
+ while (k > 0 && a[i] != b[k]) k = map[k];
239
+ if (a[i] == b[k]) k++;
240
+ }
241
+ return k;
242
+ }
243
+ /**
244
+ * Split a string into segments using a word segmenter, merging consecutive
245
+ * segments if they are both whitespace segments. Whitespace segments can
246
+ * appear adjacent to one another for two reasons:
247
+ * - newlines always get their own segment
248
+ * - where a diacritic is attached to a whitespace character in the text, the
249
+ * segment ends after the diacritic, so e.g. " \u0300 " becomes two segments.
250
+ * This function therefore runs the segmenter's .segment() method and then
251
+ * merges consecutive segments of whitespace into a single part.
252
+ */
253
+ function segment(string, segmenter) {
254
+ const parts = [];
255
+ for (const segmentObj of Array.from(segmenter.segment(string))) {
256
+ const segment = segmentObj.segment;
257
+ if (parts.length && /\s/.test(parts[parts.length - 1]) && /\s/.test(segment)) parts[parts.length - 1] += segment;
258
+ else parts.push(segment);
259
+ }
260
+ return parts;
261
+ }
262
+ function trailingWs(string, segmenter) {
263
+ if (segmenter) return leadingAndTrailingWs(string, segmenter)[1];
264
+ let i;
265
+ for (i = string.length - 1; i >= 0; i--) if (!string[i].match(/\s/)) break;
266
+ return string.substring(i + 1);
267
+ }
268
+ function leadingWs(string, segmenter) {
269
+ if (segmenter) return leadingAndTrailingWs(string, segmenter)[0];
270
+ const match = string.match(/^\s*/);
271
+ return match ? match[0] : "";
272
+ }
273
+ function leadingAndTrailingWs(string, segmenter) {
274
+ if (!segmenter) return [leadingWs(string), trailingWs(string)];
275
+ if (segmenter.resolvedOptions().granularity != "word") throw new Error("The segmenter passed must have a granularity of \"word\"");
276
+ const segments = segment(string, segmenter);
277
+ const firstSeg = segments[0];
278
+ const lastSeg = segments[segments.length - 1];
279
+ return [/\s/.test(firstSeg) ? firstSeg : "", /\s/.test(lastSeg) ? lastSeg : ""];
280
+ }
281
+ //#endregion
282
+ //#region ../../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/word.js
283
+ const extendedWordChars = "a-zA-Z0-9_\\u{AD}\\u{C0}-\\u{D6}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}";
284
+ const tokenizeIncludingWhitespace = new RegExp(`[${extendedWordChars}]+|\\s+|[^${extendedWordChars}]`, "ug");
285
+ var WordDiff = class extends Diff {
286
+ equals(left, right, options) {
287
+ if (options.ignoreCase) {
288
+ left = left.toLowerCase();
289
+ right = right.toLowerCase();
290
+ }
291
+ return left.trim() === right.trim();
292
+ }
293
+ tokenize(value, options = {}) {
294
+ let parts;
295
+ if (options.intlSegmenter) {
296
+ const segmenter = options.intlSegmenter;
297
+ if (segmenter.resolvedOptions().granularity != "word") throw new Error("The segmenter passed must have a granularity of \"word\"");
298
+ parts = segment(value, segmenter);
299
+ } else parts = value.match(tokenizeIncludingWhitespace) || [];
300
+ const tokens = [];
301
+ let prevPart = null;
302
+ parts.forEach((part) => {
303
+ if (/\s/.test(part)) {
304
+ if (prevPart == null) tokens.push(part);
305
+ else tokens.push(tokens.pop() + part);
306
+ } else if (prevPart != null && /\s/.test(prevPart)) {
307
+ if (tokens[tokens.length - 1] == prevPart) tokens.push(tokens.pop() + part);
308
+ else tokens.push(prevPart + part);
309
+ } else tokens.push(part);
310
+ prevPart = part;
311
+ });
312
+ return tokens;
313
+ }
314
+ join(tokens) {
315
+ return tokens.map((token, i) => {
316
+ if (i == 0) return token;
317
+ else return token.replace(/^\s+/, "");
318
+ }).join("");
319
+ }
320
+ postProcess(changes, options) {
321
+ if (!changes || options.oneChangePerToken) return changes;
322
+ let lastKeep = null;
323
+ let insertion = null;
324
+ let deletion = null;
325
+ changes.forEach((change) => {
326
+ if (change.added) insertion = change;
327
+ else if (change.removed) deletion = change;
328
+ else {
329
+ if (insertion || deletion) dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, change, options.intlSegmenter);
330
+ lastKeep = change;
331
+ insertion = null;
332
+ deletion = null;
333
+ }
334
+ });
335
+ if (insertion || deletion) dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, null, options.intlSegmenter);
336
+ return changes;
337
+ }
338
+ };
339
+ new WordDiff();
340
+ function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep, segmenter) {
341
+ if (deletion && insertion) {
342
+ const [oldWsPrefix, oldWsSuffix] = leadingAndTrailingWs(deletion.value, segmenter);
343
+ const [newWsPrefix, newWsSuffix] = leadingAndTrailingWs(insertion.value, segmenter);
344
+ if (startKeep) {
345
+ const commonWsPrefix = longestCommonPrefix(oldWsPrefix, newWsPrefix);
346
+ startKeep.value = replaceSuffix(startKeep.value, newWsPrefix, commonWsPrefix);
347
+ deletion.value = removePrefix(deletion.value, commonWsPrefix);
348
+ insertion.value = removePrefix(insertion.value, commonWsPrefix);
349
+ }
350
+ if (endKeep) {
351
+ const commonWsSuffix = longestCommonSuffix(oldWsSuffix, newWsSuffix);
352
+ endKeep.value = replacePrefix(endKeep.value, newWsSuffix, commonWsSuffix);
353
+ deletion.value = removeSuffix(deletion.value, commonWsSuffix);
354
+ insertion.value = removeSuffix(insertion.value, commonWsSuffix);
355
+ }
356
+ } else if (insertion) {
357
+ if (startKeep) {
358
+ const ws = leadingWs(insertion.value, segmenter);
359
+ insertion.value = insertion.value.substring(ws.length);
360
+ }
361
+ if (endKeep) {
362
+ const ws = leadingWs(endKeep.value, segmenter);
363
+ endKeep.value = endKeep.value.substring(ws.length);
364
+ }
365
+ } else if (startKeep && endKeep) {
366
+ const newWsFull = leadingWs(endKeep.value, segmenter), [delWsStart, delWsEnd] = leadingAndTrailingWs(deletion.value, segmenter);
367
+ const newWsStart = longestCommonPrefix(newWsFull, delWsStart);
368
+ deletion.value = removePrefix(deletion.value, newWsStart);
369
+ const newWsEnd = longestCommonSuffix(removePrefix(newWsFull, newWsStart), delWsEnd);
370
+ deletion.value = removeSuffix(deletion.value, newWsEnd);
371
+ endKeep.value = replacePrefix(endKeep.value, newWsFull, newWsEnd);
372
+ startKeep.value = replaceSuffix(startKeep.value, newWsFull, newWsFull.slice(0, newWsFull.length - newWsEnd.length));
373
+ } else if (endKeep) {
374
+ const endKeepWsPrefix = leadingWs(endKeep.value, segmenter);
375
+ const overlap = maximumOverlap(trailingWs(deletion.value, segmenter), endKeepWsPrefix);
376
+ deletion.value = removeSuffix(deletion.value, overlap);
377
+ } else if (startKeep) {
378
+ const overlap = maximumOverlap(trailingWs(startKeep.value, segmenter), leadingWs(deletion.value, segmenter));
379
+ deletion.value = removePrefix(deletion.value, overlap);
380
+ }
381
+ }
382
+ var WordsWithSpaceDiff = class extends Diff {
383
+ tokenize(value) {
384
+ const regex = new RegExp(`(\\r?\\n)|[${extendedWordChars}]+|[^\\S\\n\\r]+|[^${extendedWordChars}]`, "ug");
385
+ return value.match(regex) || [];
386
+ }
387
+ };
388
+ new WordsWithSpaceDiff();
389
+ //#endregion
390
+ //#region ../../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/line.js
391
+ var LineDiff = class extends Diff {
392
+ constructor() {
393
+ super(...arguments);
394
+ this.tokenize = tokenize;
395
+ }
396
+ equals(left, right, options) {
397
+ if (options.ignoreWhitespace) {
398
+ if (!options.newlineIsToken || !left.includes("\n")) left = left.trim();
399
+ if (!options.newlineIsToken || !right.includes("\n")) right = right.trim();
400
+ } else if (options.ignoreNewlineAtEof && !options.newlineIsToken) {
401
+ if (left.endsWith("\n")) left = left.slice(0, -1);
402
+ if (right.endsWith("\n")) right = right.slice(0, -1);
403
+ }
404
+ return super.equals(left, right, options);
405
+ }
406
+ };
407
+ const lineDiff = new LineDiff();
408
+ function diffLines(oldStr, newStr, options) {
409
+ return lineDiff.diff(oldStr, newStr, options);
410
+ }
411
+ function tokenize(value, options) {
412
+ if (options.stripTrailingCr) value = value.replace(/\r\n/g, "\n");
413
+ const retLines = [], linesAndNewlines = value.split(/(\n|\r\n)/);
414
+ if (!linesAndNewlines[linesAndNewlines.length - 1]) linesAndNewlines.pop();
415
+ for (let i = 0; i < linesAndNewlines.length; i++) {
416
+ const line = linesAndNewlines[i];
417
+ if (i % 2 && !options.newlineIsToken) retLines[retLines.length - 1] += line;
418
+ else retLines.push(line);
419
+ }
420
+ return retLines;
421
+ }
422
+ //#endregion
423
+ //#region ../../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/sentence.js
424
+ function isSentenceEndPunct(char) {
425
+ return char == "." || char == "!" || char == "?";
426
+ }
427
+ var SentenceDiff = class extends Diff {
428
+ tokenize(value) {
429
+ var _a;
430
+ const result = [];
431
+ let tokenStartI = 0;
432
+ for (let i = 0; i < value.length; i++) {
433
+ if (i == value.length - 1) {
434
+ result.push(value.slice(tokenStartI));
435
+ break;
436
+ }
437
+ if (isSentenceEndPunct(value[i]) && value[i + 1].match(/\s/)) {
438
+ result.push(value.slice(tokenStartI, i + 1));
439
+ i = tokenStartI = i + 1;
440
+ while ((_a = value[i + 1]) === null || _a === void 0 ? void 0 : _a.match(/\s/)) i++;
441
+ result.push(value.slice(tokenStartI, i + 1));
442
+ tokenStartI = i + 1;
443
+ }
444
+ }
445
+ return result;
446
+ }
447
+ };
448
+ new SentenceDiff();
449
+ //#endregion
450
+ //#region ../../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/css.js
451
+ var CssDiff = class extends Diff {
452
+ tokenize(value) {
453
+ return value.split(/([{}:;,]|\s+)/);
454
+ }
455
+ };
456
+ new CssDiff();
457
+ //#endregion
458
+ //#region ../../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/json.js
459
+ var JsonDiff = class extends Diff {
460
+ constructor() {
461
+ super(...arguments);
462
+ this.tokenize = tokenize;
463
+ }
464
+ get useLongestToken() {
465
+ return true;
466
+ }
467
+ castInput(value, options) {
468
+ const { undefinedReplacement, stringifyReplacer = (k, v) => typeof v === "undefined" ? undefinedReplacement : v } = options;
469
+ return typeof value === "string" ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), null, " ");
470
+ }
471
+ equals(left, right, options) {
472
+ return super.equals(left.replace(/,([\r\n])/g, "$1"), right.replace(/,([\r\n])/g, "$1"), options);
473
+ }
474
+ };
475
+ new JsonDiff();
476
+ function canonicalize(obj, stack, replacementStack, replacer, key) {
477
+ stack = stack || [];
478
+ replacementStack = replacementStack || [];
479
+ if (replacer) obj = replacer(key === void 0 ? "" : key, obj);
480
+ let i;
481
+ for (i = 0; i < stack.length; i += 1) if (stack[i] === obj) return replacementStack[i];
482
+ let canonicalizedObj;
483
+ if ("[object Array]" === Object.prototype.toString.call(obj)) {
484
+ stack.push(obj);
485
+ canonicalizedObj = new Array(obj.length);
486
+ replacementStack.push(canonicalizedObj);
487
+ for (i = 0; i < obj.length; i += 1) canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, String(i));
488
+ stack.pop();
489
+ replacementStack.pop();
490
+ return canonicalizedObj;
491
+ }
492
+ if (obj && obj.toJSON) obj = obj.toJSON();
493
+ if (typeof obj === "object" && obj !== null) {
494
+ stack.push(obj);
495
+ canonicalizedObj = {};
496
+ replacementStack.push(canonicalizedObj);
497
+ const sortedKeys = [];
498
+ let key;
499
+ for (key in obj)
500
+ /* istanbul ignore else */
501
+ if (Object.prototype.hasOwnProperty.call(obj, key)) sortedKeys.push(key);
502
+ sortedKeys.sort();
503
+ for (i = 0; i < sortedKeys.length; i += 1) {
504
+ key = sortedKeys[i];
505
+ canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack, replacer, key);
506
+ }
507
+ stack.pop();
508
+ replacementStack.pop();
509
+ } else canonicalizedObj = obj;
510
+ return canonicalizedObj;
511
+ }
512
+ //#endregion
513
+ //#region ../../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/array.js
514
+ var ArrayDiff = class extends Diff {
515
+ tokenize(value) {
516
+ return value.slice();
517
+ }
518
+ join(value) {
519
+ return value;
520
+ }
521
+ removeEmpty(value) {
522
+ return value;
523
+ }
524
+ };
525
+ new ArrayDiff();
526
+ //#endregion
527
+ //#region ../../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/patch/create.js
528
+ /**
529
+ * Returns true if the filename contains characters that require C-style
530
+ * quoting (as used by Git and GNU diffutils in diff output).
531
+ */
532
+ function needsQuoting(s) {
533
+ for (let i = 0; i < s.length; i++) if (s[i] < " " || s[i] > "~" || s[i] === "\"" || s[i] === "\\") return true;
534
+ return false;
535
+ }
536
+ /**
537
+ * C-style quotes a filename, encoding special characters as escape sequences
538
+ * and non-ASCII bytes as octal escapes. This is the inverse of
539
+ * `parseQuotedFileName` in parse.ts.
540
+ *
541
+ * Non-ASCII bytes are encoded as UTF-8 before being emitted as octal escapes.
542
+ * This matches the behaviour of both Git and GNU diffutils, which always emit
543
+ * UTF-8 octal escapes regardless of the underlying filesystem encoding (e.g.
544
+ * Git for Windows converts from NTFS's UTF-16 to UTF-8 internally).
545
+ *
546
+ * If the filename doesn't need quoting, returns it as-is.
547
+ */
548
+ function quoteFileNameIfNeeded(s) {
549
+ if (!needsQuoting(s)) return s;
550
+ let result = "\"";
551
+ const bytes = new TextEncoder().encode(s);
552
+ let i = 0;
553
+ while (i < bytes.length) {
554
+ const b = bytes[i];
555
+ if (b === 7) result += "\\a";
556
+ else if (b === 8) result += "\\b";
557
+ else if (b === 9) result += "\\t";
558
+ else if (b === 10) result += "\\n";
559
+ else if (b === 11) result += "\\v";
560
+ else if (b === 12) result += "\\f";
561
+ else if (b === 13) result += "\\r";
562
+ else if (b === 34) result += "\\\"";
563
+ else if (b === 92) result += "\\\\";
564
+ else if (b >= 32 && b <= 126) result += String.fromCharCode(b);
565
+ else result += "\\" + b.toString(8).padStart(3, "0");
566
+ i++;
567
+ }
568
+ result += "\"";
569
+ return result;
570
+ }
571
+ const INCLUDE_HEADERS = {
572
+ includeIndex: true,
573
+ includeUnderline: true,
574
+ includeFileHeaders: true
575
+ };
576
+ function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {
577
+ let optionsObj;
578
+ if (!options) optionsObj = {};
579
+ else if (typeof options === "function") optionsObj = { callback: options };
580
+ else optionsObj = options;
581
+ if (typeof optionsObj.context === "undefined") optionsObj.context = 4;
582
+ const context = optionsObj.context;
583
+ if (optionsObj.newlineIsToken) throw new Error("newlineIsToken may not be used with patch-generation functions, only with diffing functions");
584
+ if (!optionsObj.callback) return diffLinesResultToPatch(diffLines(oldStr, newStr, optionsObj));
585
+ else {
586
+ const { callback } = optionsObj;
587
+ diffLines(oldStr, newStr, Object.assign(Object.assign({}, optionsObj), { callback: (diff) => {
588
+ const patch = diffLinesResultToPatch(diff);
589
+ callback(patch);
590
+ } }));
591
+ }
592
+ function diffLinesResultToPatch(diff) {
593
+ if (!diff) return;
594
+ diff.push({
595
+ value: "",
596
+ lines: []
597
+ });
598
+ function contextLines(lines) {
599
+ return lines.map(function(entry) {
600
+ return " " + entry;
601
+ });
602
+ }
603
+ const hunks = [];
604
+ let oldRangeStart = 0, newRangeStart = 0, curRange = [], oldLine = 1, newLine = 1;
605
+ for (let i = 0; i < diff.length; i++) {
606
+ const current = diff[i], lines = current.lines || splitLines(current.value);
607
+ current.lines = lines;
608
+ if (current.added || current.removed) {
609
+ if (!oldRangeStart) {
610
+ const prev = diff[i - 1];
611
+ oldRangeStart = oldLine;
612
+ newRangeStart = newLine;
613
+ if (prev) {
614
+ curRange = context > 0 ? contextLines(prev.lines.slice(-context)) : [];
615
+ oldRangeStart -= curRange.length;
616
+ newRangeStart -= curRange.length;
617
+ }
618
+ }
619
+ for (const line of lines) curRange.push((current.added ? "+" : "-") + line);
620
+ if (current.added) newLine += lines.length;
621
+ else oldLine += lines.length;
622
+ } else {
623
+ if (oldRangeStart) {
624
+ if (lines.length <= context * 2 && i < diff.length - 2) for (const line of contextLines(lines)) curRange.push(line);
625
+ else {
626
+ const contextSize = Math.min(lines.length, context);
627
+ for (const line of contextLines(lines.slice(0, contextSize))) curRange.push(line);
628
+ const hunk = {
629
+ oldStart: oldRangeStart,
630
+ oldLines: oldLine - oldRangeStart + contextSize,
631
+ newStart: newRangeStart,
632
+ newLines: newLine - newRangeStart + contextSize,
633
+ lines: curRange
634
+ };
635
+ hunks.push(hunk);
636
+ oldRangeStart = 0;
637
+ newRangeStart = 0;
638
+ curRange = [];
639
+ }
640
+ }
641
+ oldLine += lines.length;
642
+ newLine += lines.length;
643
+ }
644
+ }
645
+ for (const hunk of hunks) for (let i = 0; i < hunk.lines.length; i++) if (hunk.lines[i].endsWith("\n")) hunk.lines[i] = hunk.lines[i].slice(0, -1);
646
+ else {
647
+ hunk.lines.splice(i + 1, 0, "\");
648
+ i++;
649
+ }
650
+ return {
651
+ oldFileName,
652
+ newFileName,
653
+ oldHeader,
654
+ newHeader,
655
+ hunks
656
+ };
657
+ }
658
+ }
659
+ /**
660
+ * creates a unified diff patch.
661
+ *
662
+ * @param patch either a single structured patch object (as returned by `structuredPatch`) or an
663
+ * array of them (as returned by `parsePatch`).
664
+ * @param headerOptions behaves the same as the `headerOptions` option of `createTwoFilesPatch`.
665
+ * Ignored for patches where `isGit` is `true`.
666
+ *
667
+ * When a patch has `isGit: true`, `formatPatch` output is changed to more closely match Git's
668
+ * output: it emits a `diff --git` header, emits Git extended headers as appropriate based on
669
+ * properties like `isRename`, `isCreate`, `newMode`, etc, and will omit `---`/`+++` file
670
+ * headers for patches with no hunks (e.g. renames without content changes).
671
+ */
672
+ function formatPatch(patch, headerOptions) {
673
+ var _a, _b, _c, _d, _e, _f;
674
+ if (!headerOptions) headerOptions = INCLUDE_HEADERS;
675
+ if (Array.isArray(patch)) {
676
+ if (patch.length > 1 && !headerOptions.includeFileHeaders && !patch.every((p) => p.isGit)) throw new Error("Cannot omit file headers on a multi-file patch. (The result would be unparseable; how would a tool trying to apply the patch know which changes are to which file?)");
677
+ return patch.map((p) => formatPatch(p, headerOptions)).join("\n");
678
+ }
679
+ const ret = [];
680
+ if (patch.isGit) {
681
+ headerOptions = INCLUDE_HEADERS;
682
+ if (!patch.oldFileName) throw new Error("oldFileName must be specified for Git patches");
683
+ if (!patch.newFileName) throw new Error("newFileName must be specified for Git patches");
684
+ let gitOldName = patch.oldFileName;
685
+ let gitNewName = patch.newFileName;
686
+ if (patch.isCreate && gitOldName === "/dev/null") gitOldName = gitNewName.replace(/^b\//, "a/");
687
+ else if (patch.isDelete && gitNewName === "/dev/null") gitNewName = gitOldName.replace(/^a\//, "b/");
688
+ ret.push("diff --git " + quoteFileNameIfNeeded(gitOldName) + " " + quoteFileNameIfNeeded(gitNewName));
689
+ if (patch.isDelete) ret.push("deleted file mode " + ((_a = patch.oldMode) !== null && _a !== void 0 ? _a : "100644"));
690
+ if (patch.isCreate) ret.push("new file mode " + ((_b = patch.newMode) !== null && _b !== void 0 ? _b : "100644"));
691
+ if (patch.oldMode && patch.newMode && !patch.isDelete && !patch.isCreate) {
692
+ ret.push("old mode " + patch.oldMode);
693
+ ret.push("new mode " + patch.newMode);
694
+ }
695
+ if (patch.isRename) {
696
+ ret.push("rename from " + quoteFileNameIfNeeded(((_c = patch.oldFileName) !== null && _c !== void 0 ? _c : "").replace(/^a\//, "")));
697
+ ret.push("rename to " + quoteFileNameIfNeeded(((_d = patch.newFileName) !== null && _d !== void 0 ? _d : "").replace(/^b\//, "")));
698
+ }
699
+ if (patch.isCopy) {
700
+ ret.push("copy from " + quoteFileNameIfNeeded(((_e = patch.oldFileName) !== null && _e !== void 0 ? _e : "").replace(/^a\//, "")));
701
+ ret.push("copy to " + quoteFileNameIfNeeded(((_f = patch.newFileName) !== null && _f !== void 0 ? _f : "").replace(/^b\//, "")));
702
+ }
703
+ } else {
704
+ if (headerOptions.includeIndex && patch.oldFileName == patch.newFileName && patch.oldFileName !== void 0) ret.push("Index: " + patch.oldFileName);
705
+ if (headerOptions.includeUnderline) ret.push("===================================================================");
706
+ }
707
+ const hasHunks = patch.hunks.length > 0;
708
+ if (headerOptions.includeFileHeaders && patch.oldFileName !== void 0 && patch.newFileName !== void 0 && (!patch.isGit || hasHunks)) {
709
+ ret.push("--- " + quoteFileNameIfNeeded(patch.oldFileName) + (patch.oldHeader ? " " + patch.oldHeader : ""));
710
+ ret.push("+++ " + quoteFileNameIfNeeded(patch.newFileName) + (patch.newHeader ? " " + patch.newHeader : ""));
711
+ }
712
+ for (let i = 0; i < patch.hunks.length; i++) {
713
+ const hunk = patch.hunks[i];
714
+ const oldStart = hunk.oldLines === 0 ? hunk.oldStart - 1 : hunk.oldStart;
715
+ const newStart = hunk.newLines === 0 ? hunk.newStart - 1 : hunk.newStart;
716
+ ret.push("@@ -" + oldStart + "," + hunk.oldLines + " +" + newStart + "," + hunk.newLines + " @@");
717
+ for (const line of hunk.lines) ret.push(line);
718
+ }
719
+ return ret.join("\n") + "\n";
720
+ }
721
+ function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {
722
+ if (typeof options === "function") options = { callback: options };
723
+ if (!(options === null || options === void 0 ? void 0 : options.callback)) {
724
+ const patchObj = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options);
725
+ if (!patchObj) return;
726
+ return formatPatch(patchObj, options === null || options === void 0 ? void 0 : options.headerOptions);
727
+ } else {
728
+ const { callback } = options;
729
+ structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, Object.assign(Object.assign({}, options), { callback: (patchObj) => {
730
+ if (!patchObj) callback(void 0);
731
+ else callback(formatPatch(patchObj, options.headerOptions));
732
+ } }));
733
+ }
734
+ }
735
+ /**
736
+ * Split `text` into an array of lines, including the trailing newline character (where present)
737
+ */
738
+ function splitLines(text) {
739
+ const hasTrailingNl = text.endsWith("\n");
740
+ const result = text.split("\n").map((line) => line + "\n");
741
+ if (hasTrailingNl) result.pop();
742
+ else result.push(result.pop().slice(0, -1));
743
+ return result;
744
+ }
745
+ //#endregion
746
+ //#region src/filesystem.ts
747
+ const NULL_PATH = "/dev/null";
748
+ const UTF8 = new TextDecoder("utf8", { fatal: true });
749
+ async function captureFilesystem(seed) {
750
+ const entries = /* @__PURE__ */ new Map();
751
+ await Promise.all([captureDirectory(seed.homeDir, seed.homeDir, "<HOME>", entries), captureDirectory(seed.workspaceDir, seed.workspaceDir, "<WORKSPACE>", entries)]);
752
+ return entries;
753
+ }
754
+ function diffFilesystem(before, after, normalize) {
755
+ const paths = [.../* @__PURE__ */ new Set([...before.keys(), ...after.keys()])].sort();
756
+ const diffs = [];
757
+ for (const path of paths) {
758
+ const previous = before.get(path);
759
+ const current = after.get(path);
760
+ if (equalEntry(previous, current)) continue;
761
+ const status = changeStatus(previous, current);
762
+ const patch = createTwoFilesPatch(previous === void 0 ? NULL_PATH : path, current === void 0 ? NULL_PATH : path, normalize(renderEntry(previous)), normalize(renderEntry(current)), "before", "after", { context: 3 });
763
+ diffs.push(Object.freeze({
764
+ patch,
765
+ path,
766
+ status
767
+ }));
768
+ }
769
+ return Object.freeze(diffs);
770
+ }
771
+ async function captureDirectory(root, directory, label, entries) {
772
+ const children = await readdir(directory, { withFileTypes: true });
773
+ await Promise.all(children.map((child) => captureChild(root, join(directory, child.name), child, label, entries)));
774
+ }
775
+ async function captureChild(root, path, child, label, entries) {
776
+ const logicalPath = `${label}/${toPortablePath(relative(root, path))}`;
777
+ if (child.isDirectory()) {
778
+ entries.set(logicalPath, {
779
+ kind: "directory",
780
+ mode: await readMode(path)
781
+ });
782
+ await captureDirectory(root, path, label, entries);
783
+ return;
784
+ }
785
+ if (child.isFile()) {
786
+ entries.set(logicalPath, await readFileEntry(path));
787
+ return;
788
+ }
789
+ if (child.isSymbolicLink()) {
790
+ entries.set(logicalPath, {
791
+ kind: "symlink",
792
+ target: await readlink(path)
793
+ });
794
+ return;
795
+ }
796
+ throw new Error(`Cannot snapshot unsupported fixture entry at ${logicalPath}.`);
797
+ }
798
+ async function readFileEntry(path) {
799
+ const [content, mode] = await Promise.all([readFile(path), readMode(path)]);
800
+ const text = decodeText(content);
801
+ if (text === void 0) return {
802
+ byteLength: content.byteLength,
803
+ digest: createHash("sha256").update(content).digest("hex"),
804
+ kind: "binary",
805
+ mode
806
+ };
807
+ return {
808
+ kind: "file",
809
+ mode,
810
+ text
811
+ };
812
+ }
813
+ /** Bytes that are not UTF-8 text, and text holding NUL, cannot be rendered into a readable patch. */
814
+ function decodeText(content) {
815
+ if (content.includes(0)) return;
816
+ try {
817
+ return UTF8.decode(content);
818
+ } catch {
819
+ return;
820
+ }
821
+ }
822
+ async function readMode(path) {
823
+ return (await lstat(path)).mode & 511;
824
+ }
825
+ function equalEntry(left, right) {
826
+ if (left === void 0 || right === void 0) return left === right;
827
+ return renderEntry(left) === renderEntry(right);
828
+ }
829
+ function changeStatus(before, after) {
830
+ if (before === void 0) return "added";
831
+ return after === void 0 ? "removed" : "modified";
832
+ }
833
+ /**
834
+ * Renders one entry as the text its patch diffs.
835
+ *
836
+ * Permission bits lead every entry that has them, which is what makes a `chmod` with no other
837
+ * change show up as a diff at all.
838
+ */
839
+ function renderEntry(entry) {
840
+ if (entry === void 0) return "";
841
+ if (entry.kind === "symlink") return `symlink -> ${entry.target}\n`;
842
+ const header = `mode ${entry.mode.toString(8).padStart(3, "0")}\n`;
843
+ if (entry.kind === "directory") return `${header}directory\n`;
844
+ if (entry.kind === "binary") return `${header}binary ${String(entry.byteLength)} bytes sha256:${entry.digest}\n`;
845
+ return `${header}${entry.text}`;
846
+ }
847
+ function toPortablePath(path) {
848
+ return sep === "/" ? path : path.split(sep).join("/");
849
+ }
850
+ //#endregion
851
+ //#region src/guards.ts
852
+ /** Narrows to a plain JSON-shaped object, excluding arrays and null. */
853
+ function isRecord(value) {
854
+ return typeof value === "object" && value !== null && !Array.isArray(value);
855
+ }
856
+ /** Freezes a JSON-shaped value all the way down. */
857
+ function deepFreeze(value) {
858
+ if (!isRecord(value) && !Array.isArray(value)) return value;
859
+ for (const property of Object.values(value)) deepFreeze(property);
860
+ return Object.freeze(value);
861
+ }
862
+ //#endregion
863
+ //#region src/report.ts
864
+ /** Reads and validates the complete `check --json` document without rebuilding it. */
865
+ function parseReport(stdout, fail) {
866
+ let document;
867
+ try {
868
+ document = JSON.parse(stdout);
869
+ } catch {
870
+ throw fail("Check runner expected one JSON report on stdout.");
871
+ }
872
+ const problems = [];
873
+ if (!isCheckReport(document, problems)) throw fail(["Check runner received a JSON report it cannot read:", ...problems.map(indent)].join("\n"));
874
+ return deepFreeze(document);
875
+ }
876
+ function indent(problem) {
877
+ return ` - ${problem}`;
878
+ }
879
+ function isCheckReport(value, problems) {
880
+ return shape(REPORT)(value, "report", problems);
881
+ }
882
+ function reject(path, expectation, problems) {
883
+ problems.push(`${path}: expected ${expectation}`);
884
+ return false;
885
+ }
886
+ /** Validates the declared keys and ignores every other one, which is what lets the report grow. */
887
+ function shape(fields) {
888
+ return (value, path, problems) => {
889
+ if (!isRecord(value)) return reject(path, "an object", problems);
890
+ let valid = true;
891
+ for (const [key, check] of Object.entries(fields)) valid = check(value[key], `${path}.${key}`, problems) && valid;
892
+ return valid;
893
+ };
894
+ }
895
+ function arrayOf(check) {
896
+ return (value, path, problems) => {
897
+ if (!Array.isArray(value)) return reject(path, "an array", problems);
898
+ let valid = true;
899
+ for (const [index, element] of value.entries()) valid = check(element, `${path}[${String(index)}]`, problems) && valid;
900
+ return valid;
901
+ };
902
+ }
903
+ function recordOf(check) {
904
+ return (value, path, problems) => {
905
+ if (!isRecord(value)) return reject(path, "an object", problems);
906
+ let valid = true;
907
+ for (const [key, entry] of Object.entries(value)) valid = check(entry, `${path}.${key}`, problems) && valid;
908
+ return valid;
909
+ };
910
+ }
911
+ function optional(check) {
912
+ return (value, path, problems) => value === void 0 || check(value, path, problems);
913
+ }
914
+ function oneOf(...allowed) {
915
+ const expectation = `one of ${allowed.map((option) => JSON.stringify(option)).join(", ")}`;
916
+ return (value, path, problems) => allowed.some((option) => option === value) || reject(path, expectation, problems);
917
+ }
918
+ const text = (value, path, problems) => typeof value === "string" || reject(path, "a string", problems);
919
+ const boolean = (value, path, problems) => typeof value === "boolean" || reject(path, "a boolean", problems);
920
+ const counter = (value, path, problems) => typeof value === "number" && Number.isInteger(value) && value >= 0 || reject(path, "a count", problems);
921
+ const position = (value, path, problems) => typeof value === "number" && Number.isInteger(value) && value > 0 || reject(path, "a 1-based position", problems);
922
+ const jsonObject = (value, path, problems) => isJsonObject(value) || reject(path, "a JSON object", problems);
923
+ const location = shape({
924
+ column: optional(position),
925
+ line: optional(position),
926
+ path: text
927
+ });
928
+ const findingFields = shape({
929
+ checkId: text,
930
+ details: optional(text),
931
+ findingId: text,
932
+ fixability: oneOf("auto", "guided", "manual"),
933
+ locations: optional(arrayOf(location)),
934
+ message: text,
935
+ metadata: optional(jsonObject),
936
+ presentation: optional(shape({
937
+ columns: arrayOf(shape({
938
+ align: optional(oneOf("left", "right")),
939
+ falseLabel: optional(text),
940
+ format: optional(oneOf("boolean", "integer", "percentage", "text")),
941
+ heading: text,
942
+ key: text,
943
+ trueLabel: optional(text)
944
+ })),
945
+ kind: oneOf("metadata-table"),
946
+ rowsKey: text
947
+ })),
948
+ scope: oneOf("global", "project"),
949
+ severity: oneOf("error", "info", "warn")
950
+ });
951
+ /**
952
+ * A finding, plus the one thing about a metadata table its own shape cannot say.
953
+ *
954
+ * `rowsKey` and every column `key` name something in `metadata`, and a name that matches nothing
955
+ * fails quietly where it is meant to be read: the table vanishes from human output, or a column
956
+ * renders blank down its whole length, while the JSON stays perfectly well-formed. A column that
957
+ * only some rows carry is ordinary — the check that reports one file per row need not give every
958
+ * file every property — so what is demanded here is that each row answer to at least one column.
959
+ */
960
+ const finding = (value, path, problems) => {
961
+ const valid = findingFields(value, path, problems);
962
+ return isRecord(value) ? tableRowsAreReadable(value, path, problems) && valid : valid;
963
+ };
964
+ function tableRowsAreReadable(value, path, problems) {
965
+ const presentation = value["presentation"];
966
+ if (!isRecord(presentation) || presentation["kind"] !== "metadata-table") return true;
967
+ const rowsKey = presentation["rowsKey"];
968
+ const metadata = value["metadata"];
969
+ const rows = typeof rowsKey === "string" && isRecord(metadata) ? metadata[rowsKey] : void 0;
970
+ if (!Array.isArray(rows) || !rows.every(isRecord)) return reject(`${path}.metadata.${typeof rowsKey === "string" ? rowsKey : "?"}`, "an array of objects, because presentation.rowsKey names it", problems);
971
+ const keys = columnKeys(presentation["columns"]);
972
+ if (keys.length === 0) return true;
973
+ return rows.every((row, index) => keys.some((key) => row[key] !== void 0) ? true : reject(`${path}.metadata.${String(rowsKey)}[${String(index)}]`, `a row carrying at least one column key (${keys.join(", ")})`, problems));
974
+ }
975
+ function columnKeys(columns) {
976
+ if (!Array.isArray(columns)) return [];
977
+ return columns.flatMap((column) => {
978
+ const key = isRecord(column) ? column["key"] : void 0;
979
+ return typeof key === "string" ? [key] : [];
980
+ });
981
+ }
982
+ const REPORT = {
983
+ apps: arrayOf(shape({
984
+ appId: text,
985
+ detection: shape({
986
+ authenticated: optional(boolean),
987
+ installed: boolean,
988
+ version: optional(text)
989
+ }),
990
+ displayName: text,
991
+ support: optional(shape({
992
+ status: oneOf("supported", "unknown", "unsupported"),
993
+ supportedRange: text,
994
+ version: optional(text)
995
+ }))
996
+ })),
997
+ diagnostics: arrayOf(shape({
998
+ detail: optional(text),
999
+ id: text,
1000
+ message: text,
1001
+ path: optional(text),
1002
+ phase: oneOf("check", "detect", "files", "fix", "parse", "read", "support")
1003
+ })),
1004
+ findings: arrayOf(finding),
1005
+ fixes: optional(arrayOf(shape({
1006
+ checkId: text,
1007
+ findingId: text,
1008
+ manualSteps: arrayOf(text),
1009
+ message: optional(text),
1010
+ operations: arrayOf(shape({
1011
+ conflict: optional(text),
1012
+ diff: optional(text),
1013
+ effect: oneOf("archive", "conflict", "create", "move", "noop", "remove", "symlink", "update"),
1014
+ paths: arrayOf(text)
1015
+ })),
1016
+ status: oneOf("applied", "failed", "partial", "planned"),
1017
+ summary: text
1018
+ }))),
1019
+ kind: oneOf("check-report"),
1020
+ passedChecks: arrayOf(shape({
1021
+ id: text,
1022
+ title: text
1023
+ })),
1024
+ schemaVersion: oneOf(1),
1025
+ status: oneOf("clean", "empty", "error", "operational-error", "warning"),
1026
+ summary: shape({
1027
+ categories: recordOf(shape({
1028
+ errors: counter,
1029
+ informational: counter,
1030
+ passed: counter,
1031
+ warnings: counter
1032
+ })),
1033
+ diagnostics: counter,
1034
+ errors: counter,
1035
+ exitCode: oneOf(0, 1, 2, 3),
1036
+ informational: counter,
1037
+ passed: counter,
1038
+ warnings: counter
1039
+ })
1040
+ };
1041
+ function isJsonObject(value) {
1042
+ return isRecord(value) && Object.values(value).every(isJsonValue);
1043
+ }
1044
+ function isJsonValue(value) {
1045
+ return value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string" || Array.isArray(value) && value.every(isJsonValue) || isJsonObject(value);
1046
+ }
1047
+ //#endregion
1048
+ //#region src/run-result.ts
1049
+ /** Converts either CLI execution boundary into the testkit's stable result contract. */
1050
+ async function collectCheckResult(seed, execute) {
1051
+ const before = await captureFilesystem(seed);
1052
+ const capture = await execute();
1053
+ const normalize = createNormalizer(seed);
1054
+ const spawned = capture.boundary === "spawned";
1055
+ const transcript = {
1056
+ exitCode: capture.exitCode,
1057
+ signal: spawned ? capture.signal : null,
1058
+ stderr: normalize(capture.stderr),
1059
+ stdout: normalize(capture.stdout),
1060
+ timedOutAfterMs: spawned ? capture.timedOutAfterMs : void 0
1061
+ };
1062
+ const exitCode = requireExitCode(transcript);
1063
+ if (capture.boundary === "in-process") requireAppliedExitCode(capture.appliedExitCode, exitCode, transcript);
1064
+ const report = parseReport(transcript.stdout, (message) => runError(message, transcript));
1065
+ if (report.summary.exitCode !== exitCode) throw runError("Check runner saw a report and process with disagreeing exit codes.", transcript);
1066
+ const after = await captureFilesystem(seed);
1067
+ return Object.freeze({
1068
+ diffs: diffFilesystem(before, after, normalize),
1069
+ exitCode,
1070
+ findings: report.findings,
1071
+ report,
1072
+ stderr: transcript.stderr,
1073
+ stdout: transcript.stdout
1074
+ });
1075
+ }
1076
+ /**
1077
+ * Proves the in-process run applied the exit code it returned.
1078
+ *
1079
+ * Only this boundary can disagree with itself: a child process has one exit status and no second
1080
+ * opinion to compare it against.
1081
+ */
1082
+ function requireAppliedExitCode(appliedExitCode, exitCode, transcript) {
1083
+ if (appliedExitCode === void 0) throw runError("Check runner never reported the exit code it applied.", transcript);
1084
+ if (appliedExitCode !== exitCode) throw runError("Check runner saw two disagreeing exit codes for one run.", transcript);
1085
+ }
1086
+ function requireExitCode(transcript) {
1087
+ if (transcript.timedOutAfterMs !== void 0) throw runError(`Check runner killed the run after ${String(transcript.timedOutAfterMs)}ms without an exit.`, transcript);
1088
+ if (transcript.signal !== null) throw runError(`Check runner terminated from signal ${transcript.signal}.`, transcript);
1089
+ if (transcript.exitCode === null) throw runError("Check runner terminated without an exit code.", transcript);
1090
+ if (transcript.exitCode !== 0 && transcript.exitCode !== 1 && transcript.exitCode !== 2 && transcript.exitCode !== 3) throw runError(`Check runner returned unsupported exit code ${String(transcript.exitCode)}.`, transcript);
1091
+ return transcript.exitCode;
1092
+ }
1093
+ const TRANSCRIPT_LIMIT = 2e3;
1094
+ function runError(message, transcript) {
1095
+ return new Error([
1096
+ message,
1097
+ ` exit code: ${describeExit(transcript)}`,
1098
+ ` stdout: ${describeStream(transcript.stdout)}`,
1099
+ ` stderr: ${describeStream(transcript.stderr)}`
1100
+ ].join("\n"));
1101
+ }
1102
+ function describeExit(transcript) {
1103
+ if (transcript.signal !== null) return `signal ${transcript.signal}`;
1104
+ return transcript.exitCode === null ? "<missing>" : String(transcript.exitCode);
1105
+ }
1106
+ function describeStream(value) {
1107
+ if (value === "") return "<empty>";
1108
+ const trimmed = value.length > TRANSCRIPT_LIMIT ? value.slice(0, TRANSCRIPT_LIMIT) : value;
1109
+ const suffix = value.length > TRANSCRIPT_LIMIT ? "… (truncated)" : "";
1110
+ return `${trimmed.replaceAll("\n", "\n ")}${suffix}`;
1111
+ }
1112
+ /** Replaces a seed's machine-specific paths with stable labels, longest path first. */
1113
+ function createNormalizer(seed) {
1114
+ const ordered = [...[
1115
+ [seed.homeDir, "<HOME>"],
1116
+ [seed.pathDir, "<PATH>"],
1117
+ [seed.workspaceDir, "<WORKSPACE>"],
1118
+ [dirname(seed.homeDir), "<SEED>"]
1119
+ ]].sort((left, right) => right[0].length - left[0].length);
1120
+ return (value) => {
1121
+ let normalized = value;
1122
+ for (const [path, label] of ordered) normalized = normalized.replaceAll(path, label);
1123
+ return normalized;
1124
+ };
1125
+ }
1126
+ //#endregion
1127
+ //#region src/binary-runner.ts
1128
+ /**
1129
+ * How long a compiled run may take before the runner stops waiting.
1130
+ *
1131
+ * Generous on purpose: this is the bound that turns a hang into a readable failure, not a budget
1132
+ * anyone should be tuning a passing test against.
1133
+ */
1134
+ const DEFAULT_TIMEOUT_MS = 3e4;
1135
+ /** Flags the runner supplies itself, which a caller repeating would silently duplicate. */
1136
+ const RESERVED_FLAGS = /* @__PURE__ */ new Set([
1137
+ "--home",
1138
+ "--json",
1139
+ "--path"
1140
+ ]);
1141
+ /** Runs a compiled Aura distribution's `check --json` command against one deterministic seed. */
1142
+ async function runBinaryCheck(options) {
1143
+ if (!isAbsolute(options.binaryPath)) throw new Error(`Compiled binary path must be absolute. Received: ${options.binaryPath}`);
1144
+ rejectReservedFlags(options.args ?? []);
1145
+ return collectCheckResult(options.seed, () => executeBinary(options));
1146
+ }
1147
+ /**
1148
+ * Refuses an argument the runner already passes.
1149
+ *
1150
+ * Repeating one is not an error the CLI reports — it parses as a second occurrence and quietly wins
1151
+ * or loses depending on the flag — so the test that did it would fail somewhere else entirely.
1152
+ */
1153
+ function rejectReservedFlags(args) {
1154
+ for (const arg of args) {
1155
+ const [flag] = arg.split("=");
1156
+ if (flag !== void 0 && RESERVED_FLAGS.has(flag)) throw new Error(`Check runner already supplies ${flag}. Remove it from args; the seed decides its value.`);
1157
+ }
1158
+ }
1159
+ function executeBinary(options) {
1160
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1161
+ return new Promise((resolve, reject) => {
1162
+ try {
1163
+ const child = spawn(options.binaryPath, [
1164
+ "check",
1165
+ "--json",
1166
+ "--home",
1167
+ options.seed.homeDir,
1168
+ "--path",
1169
+ options.seed.pathDir,
1170
+ ...options.args ?? []
1171
+ ], {
1172
+ cwd: options.seed.workspaceDir,
1173
+ env: {
1174
+ HOME: options.seed.homeDir,
1175
+ NO_COLOR: "1",
1176
+ PATH: options.seed.pathDir
1177
+ },
1178
+ stdio: [
1179
+ "ignore",
1180
+ "pipe",
1181
+ "pipe"
1182
+ ]
1183
+ });
1184
+ const stderr = [];
1185
+ const stdout = [];
1186
+ let timedOut = false;
1187
+ const timer = setTimeout(() => {
1188
+ timedOut = true;
1189
+ child.kill("SIGKILL");
1190
+ }, timeoutMs);
1191
+ child.stderr.setEncoding("utf8");
1192
+ child.stdout.setEncoding("utf8");
1193
+ child.stderr.on("data", (chunk) => {
1194
+ stderr.push(chunk);
1195
+ });
1196
+ child.stdout.on("data", (chunk) => {
1197
+ stdout.push(chunk);
1198
+ });
1199
+ child.once("error", (error) => {
1200
+ clearTimeout(timer);
1201
+ reject(new Error(`Could not launch compiled Aura binary: ${error.message}`, { cause: error }));
1202
+ });
1203
+ child.once("close", (exitCode, signal) => {
1204
+ clearTimeout(timer);
1205
+ resolve({
1206
+ boundary: "spawned",
1207
+ exitCode,
1208
+ signal,
1209
+ stderr: stderr.join(""),
1210
+ stdout: stdout.join(""),
1211
+ ...timedOut ? { timedOutAfterMs: timeoutMs } : {}
1212
+ });
1213
+ });
1214
+ } catch (error) {
1215
+ const message = error instanceof Error ? error.message : String(error);
1216
+ reject(new Error(`Could not launch compiled Aura binary: ${message}`, { cause: error }));
1217
+ }
1218
+ });
1219
+ }
1220
+ //#endregion
1221
+ //#region ../core/src/http.ts
1222
+ /**
1223
+ * Hosts allowed to serve plain `http:`.
1224
+ *
1225
+ * Literal loopback addresses only: `localhost` can resolve anywhere the resolver decides, so a
1226
+ * credential sent to it could still leave the machine.
1227
+ */
1228
+ const LOOPBACK_HOSTS$1 = ["127.0.0.1", "[::1]"];
1229
+ /**
1230
+ * Creates {@link Environment.httpGet}: a bounded, TLS-only GET that never rejects.
1231
+ *
1232
+ * Redirects are refused (`redirect: "error"`) so a directory cannot bounce a bearer token to an
1233
+ * address the caller never named. The response body is streamed against the byte cap rather than
1234
+ * buffered first, so an oversized body is abandoned mid-transfer.
1235
+ */
1236
+ function createHttpGet() {
1237
+ return async (request) => {
1238
+ const vetted = vetHttpUrl(request.url);
1239
+ if (!(vetted instanceof URL)) return {
1240
+ kind: "failure",
1241
+ reason: vetted
1242
+ };
1243
+ const maxBytes = clamp(request.maxResponseBytes, MAX_HTTP_RESPONSE_BYTES, MAX_HTTP_RESPONSE_BYTES);
1244
+ try {
1245
+ return await readBody(await fetch(vetted, {
1246
+ headers: request.headers ?? {},
1247
+ method: "GET",
1248
+ redirect: "error",
1249
+ signal: AbortSignal.timeout(clampHttpTimeout(request.timeoutMs))
1250
+ }), maxBytes, request.responseType === "bytes");
1251
+ } catch (error) {
1252
+ return {
1253
+ kind: "failure",
1254
+ reason: failureReason(error)
1255
+ };
1256
+ }
1257
+ };
1258
+ }
1259
+ /** Parses a URL and applies the TLS-only rule, collapsing both refusals into failure reasons. */
1260
+ function vetHttpUrl(raw) {
1261
+ let url;
1262
+ try {
1263
+ url = new URL(raw);
1264
+ } catch {
1265
+ return "invalid-url";
1266
+ }
1267
+ return isAllowedHttpUrl(url) ? url : "insecure-url";
1268
+ }
1269
+ /** Applies the shared timeout default and ceiling every HTTP client in the kernel honors. */
1270
+ function clampHttpTimeout(value) {
1271
+ return clamp(value, DEFAULT_HTTP_TIMEOUT_MS, MAX_HTTP_TIMEOUT_MS);
1272
+ }
1273
+ /** Replaces an absent, zero, negative, or non-finite value by the default; caps at the ceiling. */
1274
+ function clamp(value, fallback, ceiling) {
1275
+ if (value === void 0 || !Number.isFinite(value) || value <= 0) return fallback;
1276
+ return Math.min(value, ceiling);
1277
+ }
1278
+ function isLoopbackHttp(url) {
1279
+ return url.protocol === "http:" && LOOPBACK_HOSTS$1.includes(url.hostname);
1280
+ }
1281
+ /**
1282
+ * Whether a parsed URL satisfies the TLS rule {@link createHttpGet} enforces.
1283
+ *
1284
+ * Exposed so configuration that carries a URL — a registered skill directory, a preset entry — can
1285
+ * be refused at validation time with a named problem instead of failing later mid-request.
1286
+ */
1287
+ function isAllowedHttpUrl(url) {
1288
+ return url.protocol === "https:" || isLoopbackHttp(url);
1289
+ }
1290
+ /** Streams the body against the byte cap; decoding happens only once the cap is known to hold. */
1291
+ async function readBody(response, maxBytes, binary) {
1292
+ const reader = response.body?.getReader();
1293
+ if (reader === void 0) return binary ? {
1294
+ body: /* @__PURE__ */ new Uint8Array(),
1295
+ kind: "binary-response",
1296
+ status: response.status
1297
+ } : {
1298
+ body: "",
1299
+ kind: "response",
1300
+ status: response.status
1301
+ };
1302
+ const chunks = [];
1303
+ let received = 0;
1304
+ for (;;) {
1305
+ const { done, value } = await reader.read();
1306
+ if (done) break;
1307
+ received += value.byteLength;
1308
+ if (received > maxBytes) {
1309
+ await reader.cancel();
1310
+ return {
1311
+ kind: "failure",
1312
+ reason: "response-too-large"
1313
+ };
1314
+ }
1315
+ chunks.push(value);
1316
+ }
1317
+ const body = concatenate(chunks, received);
1318
+ return binary ? {
1319
+ body,
1320
+ kind: "binary-response",
1321
+ status: response.status
1322
+ } : {
1323
+ body: new TextDecoder().decode(body),
1324
+ kind: "response",
1325
+ status: response.status
1326
+ };
1327
+ }
1328
+ function concatenate(chunks, length) {
1329
+ const joined = new Uint8Array(length);
1330
+ let offset = 0;
1331
+ for (const chunk of chunks) {
1332
+ joined.set(chunk, offset);
1333
+ offset += chunk.byteLength;
1334
+ }
1335
+ return joined;
1336
+ }
1337
+ /**
1338
+ * Collapses every throw into the closed failure vocabulary.
1339
+ *
1340
+ * Error text is deliberately dropped: runtime messages can echo the request, and the request may
1341
+ * carry credentials in its headers.
1342
+ */
1343
+ function failureReason(error) {
1344
+ const name = error instanceof Error ? error.name : "";
1345
+ return name === "TimeoutError" || name === "AbortError" ? "timeout" : "network";
1346
+ }
1347
+ //#endregion
1348
+ //#region src/http.ts
1349
+ const LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "[::1]"]);
1350
+ const realHttpGet = createHttpGet();
1351
+ /**
1352
+ * The network policy every testkit runner injects: loopback servers only.
1353
+ *
1354
+ * A test run is hermetic by construction — a distribution that registers a real skill directory
1355
+ * must not make its integration tests reach that host. Mock servers listen on `127.0.0.1`, which
1356
+ * this passes straight through to the kernel's own client, caps and all.
1357
+ */
1358
+ const loopbackOnlyHttpGet = (request) => {
1359
+ let url;
1360
+ try {
1361
+ url = new URL(request.url);
1362
+ } catch {
1363
+ return Promise.resolve({
1364
+ kind: "failure",
1365
+ reason: "invalid-url"
1366
+ });
1367
+ }
1368
+ if (!LOOPBACK_HOSTS.has(url.hostname)) return Promise.resolve({
1369
+ kind: "failure",
1370
+ reason: "network"
1371
+ });
1372
+ return realHttpGet(request);
1373
+ };
1374
+ //#endregion
1375
+ //#region src/text-capture.ts
1376
+ /** A writable stream that accumulates everything written to it as text. */
1377
+ function createTextCapture() {
1378
+ const chunks = [];
1379
+ const stream = new PassThrough();
1380
+ stream.setEncoding("utf8");
1381
+ stream.on("data", (chunk) => {
1382
+ chunks.push(chunk);
1383
+ });
1384
+ return {
1385
+ read: () => chunks.join(""),
1386
+ stream
1387
+ };
1388
+ }
1389
+ //#endregion
1390
+ //#region src/runner.ts
1391
+ /** Runs `check --json` without reading process state and returns snapshot-ready output. */
1392
+ async function runCheck(options) {
1393
+ return collectCheckResult(options.seed, async () => {
1394
+ const stderr = createTextCapture();
1395
+ const stdout = createTextCapture();
1396
+ let appliedExitCode;
1397
+ const exitCode = await runCli(options.distro, {
1398
+ argv: [
1399
+ "check",
1400
+ "--json",
1401
+ ...options.args ?? []
1402
+ ],
1403
+ colorDepth: 0,
1404
+ cwd: options.seed.workspaceDir,
1405
+ environmentVariables: { PATH: options.seed.pathDir },
1406
+ homeDir: options.seed.homeDir,
1407
+ httpGet: loopbackOnlyHttpGet,
1408
+ setExitCode: (code) => {
1409
+ appliedExitCode = code;
1410
+ },
1411
+ stderr: stderr.stream,
1412
+ stdin: Readable.from([]),
1413
+ stdout: stdout.stream
1414
+ });
1415
+ return {
1416
+ appliedExitCode,
1417
+ boundary: "in-process",
1418
+ exitCode,
1419
+ stderr: stderr.read(),
1420
+ stdout: stdout.read()
1421
+ };
1422
+ });
1423
+ }
1424
+ //#endregion
1425
+ //#region src/setup-runner.ts
1426
+ /**
1427
+ * Runs `setup --yes` in-process against a seed and returns snapshot-ready output.
1428
+ *
1429
+ * `--yes` because this boundary has no terminal: every question resolves to the default the wizard
1430
+ * would have proposed, which is exactly what the acceptance tests need to prove about converge and
1431
+ * end-on-green behaviour. Interactive keypress flows are covered by the CLI's own engine tests.
1432
+ */
1433
+ async function runSetup(options) {
1434
+ const before = await captureFilesystem(options.seed);
1435
+ const stderr = createTextCapture();
1436
+ const stdout = createTextCapture();
1437
+ let exitCode = -1;
1438
+ await runCli(options.distro, {
1439
+ argv: [
1440
+ "setup",
1441
+ "--yes",
1442
+ ...options.args ?? []
1443
+ ],
1444
+ colorDepth: 0,
1445
+ cwd: options.seed.workspaceDir,
1446
+ environmentVariables: {
1447
+ ...options.environmentVariables,
1448
+ PATH: options.seed.pathDir
1449
+ },
1450
+ homeDir: options.seed.homeDir,
1451
+ httpGet: loopbackOnlyHttpGet,
1452
+ setExitCode: (code) => {
1453
+ exitCode = code;
1454
+ },
1455
+ stderr: stderr.stream,
1456
+ stdin: Readable.from([]),
1457
+ stdout: stdout.stream
1458
+ });
1459
+ const normalize = createNormalizer(options.seed);
1460
+ const after = await captureFilesystem(options.seed);
1461
+ return Object.freeze({
1462
+ diffs: diffFilesystem(before, after, normalize),
1463
+ exitCode,
1464
+ stderr: normalize(stderr.read()),
1465
+ stdout: normalize(stdout.read())
1466
+ });
1467
+ }
1468
+ //#endregion
1469
+ //#region src/convergence.ts
1470
+ const JOURNAL_ENTRY = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z(?:-\d{4})?$/u;
1471
+ const CONVERGED_MARKERS = ["Already converged — nothing to do.", "Nothing to fix."];
1472
+ /**
1473
+ * Runs one converge boundary twice and proves the second pass had no work to plan or journal.
1474
+ *
1475
+ * Throws framework-independent errors so distributions can use this helper from any test runner.
1476
+ */
1477
+ async function expectConvergedTwice(seed, run) {
1478
+ const first = await run();
1479
+ requireSuccessful("first", first);
1480
+ const afterFirst = await captureFilesystem(seed);
1481
+ const journalAfterFirst = await journalEntries(seed);
1482
+ const second = await run();
1483
+ requireSuccessful("second", second);
1484
+ const transcript = `${second.stdout}\n${second.stderr}`;
1485
+ if (!CONVERGED_MARKERS.some((marker) => transcript.includes(marker))) throw convergenceError("second run did not report that it was already converged", second);
1486
+ if (transcript.includes("Applied ")) throw convergenceError("second run reported applied operations", second);
1487
+ const reportedDiffs = second.diffs.filter((diff) => !isJournalPath(diff.path));
1488
+ if (reportedDiffs.length > 0) throw convergenceError(`second run reported ${String(reportedDiffs.length)} filesystem diff(s): ${reportedDiffs.map((diff) => diff.path).join(", ")}`, second);
1489
+ const snapshotDiffs = diffFilesystem(afterFirst, await captureFilesystem(seed), (value) => value).filter((diff) => !isJournalPath(diff.path));
1490
+ if (snapshotDiffs.length > 0) throw convergenceError(`second run changed ${String(snapshotDiffs.length)} captured path(s): ${snapshotDiffs.map((diff) => diff.path).join(", ")}`, second);
1491
+ if ((await journalEntries(seed)).join("\n") !== journalAfterFirst.join("\n")) throw convergenceError("second run changed the undo journal entries", second);
1492
+ return Object.freeze({
1493
+ first,
1494
+ second
1495
+ });
1496
+ }
1497
+ /**
1498
+ * Whether a path belongs to the undo journal rather than to the state being converged.
1499
+ *
1500
+ * The journal is where a run records what it replaced, and it holds the target-lock directory a run
1501
+ * touches whether or not it writes anything. Convergence is a claim about the machine's
1502
+ * configuration, so journal paths are excluded here and asserted separately, by entry name.
1503
+ */
1504
+ function isJournalPath(path) {
1505
+ return path.includes("/.backups/") || path.endsWith("/.backups");
1506
+ }
1507
+ function requireSuccessful(label, result) {
1508
+ if (result.exitCode !== 0) throw convergenceError(`${label} run exited with ${String(result.exitCode)}`, result);
1509
+ }
1510
+ async function journalEntries(seed) {
1511
+ try {
1512
+ return (await readdir(join(seed.homeDir, "agents", ".backups"))).filter((name) => JOURNAL_ENTRY.test(name)).sort();
1513
+ } catch (error) {
1514
+ if (errorCode(error) === "ENOENT") return [];
1515
+ throw error;
1516
+ }
1517
+ }
1518
+ function errorCode(error) {
1519
+ if (typeof error !== "object" || error === null || !("code" in error)) return;
1520
+ const code = error.code;
1521
+ return typeof code === "string" ? code : void 0;
1522
+ }
1523
+ function convergenceError(message, result) {
1524
+ return new Error([
1525
+ `Expected converge-twice invariant: ${message}.`,
1526
+ ` stdout: ${result.stdout === "" ? "<empty>" : result.stdout}`,
1527
+ ` stderr: ${result.stderr === "" ? "<empty>" : result.stderr}`
1528
+ ].join("\n"));
1529
+ }
1530
+ //#endregion
1531
+ //#region src/types.ts
1532
+ /**
1533
+ * Matches any single argument in that position.
1534
+ *
1535
+ * Arity still has to match, so a response stays an exact description of one shape of invocation —
1536
+ * this only frees the positions whose value a test cannot predict, such as a temporary path.
1537
+ */
1538
+ const ANY_ARGUMENT = Symbol("aura-testkit.anyArgument");
1539
+ //#endregion
1540
+ //#region src/shims.ts
1541
+ const COMMAND_NAME = /^[A-Za-z0-9._+-]+$/u;
1542
+ const RESERVED_NAMES = /* @__PURE__ */ new Set([".", ".."]);
1543
+ async function writeShim(options) {
1544
+ const { command, logDir, pathDir, responses } = options;
1545
+ validateShim(command, responses);
1546
+ const path = join(pathDir, command);
1547
+ await writeFile(path, renderShim(command, join(logDir, command), responses), "utf8");
1548
+ await chmod(path, 493);
1549
+ }
1550
+ /**
1551
+ * Reads back every invocation one shim recorded.
1552
+ *
1553
+ * The log frames each record as its argument count followed by that many arguments, all
1554
+ * NUL-terminated. Arguments cannot contain NUL — {@link validateShim} rejects that — so the framing
1555
+ * stays unambiguous for empty arguments and for arguments holding newlines.
1556
+ */
1557
+ async function readInvocations(logDir, command) {
1558
+ if (!isPortableCommandName(command)) return [];
1559
+ let log;
1560
+ try {
1561
+ log = await readFile(join(logDir, command), "utf8");
1562
+ } catch (error) {
1563
+ if (isNotFound(error)) return [];
1564
+ throw error;
1565
+ }
1566
+ const fields = log.split("\0");
1567
+ fields.pop();
1568
+ const invocations = [];
1569
+ let cursor = 0;
1570
+ while (cursor < fields.length) {
1571
+ const count = Number(fields[cursor]);
1572
+ if (!Number.isInteger(count) || count < 0 || cursor + 1 + count > fields.length) throw new Error(`Shim ${command} wrote an invocation log this runner cannot read.`);
1573
+ invocations.push(Object.freeze(fields.slice(cursor + 1, cursor + 1 + count)));
1574
+ cursor += 1 + count;
1575
+ }
1576
+ return Object.freeze(invocations);
1577
+ }
1578
+ function validateShim(command, responses) {
1579
+ if (!isPortableCommandName(command)) throw new Error(`Shim command must be a portable executable name. Received: ${command}`);
1580
+ if (responses.length === 0) throw new Error(`Shim ${command} must declare at least one response.`);
1581
+ const invocations = /* @__PURE__ */ new Set();
1582
+ for (const response of responses) {
1583
+ const key = describeArgs(response.args);
1584
+ if (invocations.has(key)) throw new Error(`Shim ${command} declares the same arguments more than once: ${key}`);
1585
+ invocations.add(key);
1586
+ validateResponse(command, response);
1587
+ }
1588
+ }
1589
+ /** The name has to be a single path segment: `.` and `..` pass the character test but are not. */
1590
+ function isPortableCommandName(command) {
1591
+ return COMMAND_NAME.test(command) && !RESERVED_NAMES.has(command);
1592
+ }
1593
+ function describeArgs(args) {
1594
+ return JSON.stringify(args.map((argument) => isAnyArgument(argument) ? null : argument));
1595
+ }
1596
+ function isAnyArgument(argument) {
1597
+ return argument === ANY_ARGUMENT;
1598
+ }
1599
+ function validateResponse(command, response) {
1600
+ const exitCode = response.exitCode ?? 0;
1601
+ if (!Number.isInteger(exitCode) || exitCode < 0 || exitCode > 255) throw new Error(`Shim ${command} exit code must be an integer from 0 to 255.`);
1602
+ const literals = response.args.filter((argument) => !isAnyArgument(argument));
1603
+ for (const value of [
1604
+ command,
1605
+ ...literals,
1606
+ response.stdout ?? "",
1607
+ response.stderr ?? ""
1608
+ ]) if (value.includes("\0")) throw new Error(`Shim ${command} values cannot contain a NUL character.`);
1609
+ }
1610
+ function renderShim(command, logPath, responses) {
1611
+ return [
1612
+ "#!/bin/sh",
1613
+ "{",
1614
+ ` printf '%s\\0' "$#"`,
1615
+ " for aura_recorded in \"$@\"; do",
1616
+ ` printf '%s\\0' "$aura_recorded"`,
1617
+ " done",
1618
+ `} >> ${shellQuote(logPath)}`,
1619
+ ...responses.map(renderResponse),
1620
+ `printf '%s' ${shellQuote(`aura-testkit: unmatched invocation: ${command}`)} >&2`,
1621
+ "for aura_reported in \"$@\"; do",
1622
+ " printf \" %s\" \"$aura_reported\" >&2",
1623
+ "done",
1624
+ "printf '\\n' >&2",
1625
+ "exit 2",
1626
+ ""
1627
+ ].join("\n");
1628
+ }
1629
+ function renderResponse(response) {
1630
+ return [
1631
+ `if ${[`[ "$#" -eq ${String(response.args.length)} ]`, ...response.args.flatMap((argument, index) => isAnyArgument(argument) ? [] : [`[ "$${String(index + 1)}" = ${shellQuote(argument)} ]`])].join(" && ")}; then`,
1632
+ ...response.stdout === void 0 ? [] : [` printf '%s' ${shellQuote(response.stdout)}`],
1633
+ ...response.stderr === void 0 ? [] : [` printf '%s' ${shellQuote(response.stderr)} >&2`],
1634
+ ` exit ${String(response.exitCode ?? 0)}`,
1635
+ "fi"
1636
+ ].join("\n");
1637
+ }
1638
+ function shellQuote(value) {
1639
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
1640
+ }
1641
+ function isNotFound(error) {
1642
+ return isRecord(error) && error["code"] === "ENOENT";
1643
+ }
1644
+ //#endregion
1645
+ //#region src/seed.ts
1646
+ var SeedBuilder = class {
1647
+ #homeFiles = /* @__PURE__ */ new Map();
1648
+ #shims = /* @__PURE__ */ new Map();
1649
+ #workspaceFiles = /* @__PURE__ */ new Map();
1650
+ homeFile(path, content) {
1651
+ addFile(this.#homeFiles, "HOME", path, content);
1652
+ return this;
1653
+ }
1654
+ shim(command, responses) {
1655
+ validateShim(command, responses);
1656
+ if (this.#shims.has(command)) throw new Error(`Shim command is already seeded: ${command}`);
1657
+ this.#shims.set(command, {
1658
+ command,
1659
+ responses: responses.map((response) => ({
1660
+ ...response,
1661
+ args: [...response.args]
1662
+ }))
1663
+ });
1664
+ return this;
1665
+ }
1666
+ workspaceFile(path, content) {
1667
+ addFile(this.#workspaceFiles, "workspace", path, content);
1668
+ return this;
1669
+ }
1670
+ async build() {
1671
+ const root = await realpath(await mkdtemp(join(tmpdir(), "aura-testkit-")));
1672
+ const homeDir = join(root, "home");
1673
+ const logDir = join(root, "invocations");
1674
+ const pathDir = join(root, "bin");
1675
+ const workspaceDir = join(root, "workspace");
1676
+ const commands = new Set(this.#shims.keys());
1677
+ try {
1678
+ await Promise.all([
1679
+ mkdir(homeDir, { recursive: true }),
1680
+ mkdir(logDir, { recursive: true }),
1681
+ mkdir(pathDir, { recursive: true }),
1682
+ mkdir(workspaceDir, { recursive: true })
1683
+ ]);
1684
+ const roots = {
1685
+ homeDir,
1686
+ workspaceDir
1687
+ };
1688
+ await Promise.all([writeFiles(homeDir, this.#homeFiles.values(), roots), writeFiles(workspaceDir, this.#workspaceFiles.values(), roots)]);
1689
+ await Promise.all([...this.#shims.values()].map((shim) => writeShim({
1690
+ command: shim.command,
1691
+ logDir,
1692
+ pathDir,
1693
+ responses: shim.responses
1694
+ })));
1695
+ } catch (error) {
1696
+ await rm(root, {
1697
+ force: true,
1698
+ recursive: true
1699
+ });
1700
+ throw error;
1701
+ }
1702
+ let cleanupPromise;
1703
+ const cleanup = () => {
1704
+ cleanupPromise ??= rm(root, {
1705
+ force: true,
1706
+ recursive: true
1707
+ });
1708
+ return cleanupPromise;
1709
+ };
1710
+ return Object.freeze({
1711
+ cleanup,
1712
+ homeDir,
1713
+ invocations: (command) => commands.has(command) ? readInvocations(logDir, command) : Promise.resolve([]),
1714
+ pathDir,
1715
+ workspaceDir,
1716
+ [Symbol.asyncDispose]: cleanup
1717
+ });
1718
+ }
1719
+ };
1720
+ /** Starts a fluent description of one isolated fake machine. */
1721
+ function createSeedBuilder() {
1722
+ return new SeedBuilder();
1723
+ }
1724
+ function addFile(files, scope, path, content) {
1725
+ const normalized = normalizeSeedPath(path);
1726
+ if (files.has(normalized)) throw new Error(`${scope} file is already seeded: ${normalized}`);
1727
+ files.set(normalized, {
1728
+ content,
1729
+ path: normalized
1730
+ });
1731
+ }
1732
+ function normalizeSeedPath(path) {
1733
+ if (path.length === 0 || path.includes("\0") || isAbsolute(path)) throw new Error(`Seed file path must be a non-empty relative path. Received: ${path}`);
1734
+ const normalized = normalize(path);
1735
+ if (normalized === "." || normalized === ".." || normalized.startsWith(`..${sep}`)) throw new Error(`Seed file path must stay inside its root. Received: ${path}`);
1736
+ return normalized;
1737
+ }
1738
+ /**
1739
+ * Materializes one root's files, resolving any content declared as a function of the roots.
1740
+ *
1741
+ * Sequential on purpose: two declared paths can disagree about whether a name is a file or a
1742
+ * directory, and writing them in declaration order makes which one fails deterministic.
1743
+ */
1744
+ async function writeFiles(root, files, roots) {
1745
+ for (const file of files) {
1746
+ const destination = join(root, file.path);
1747
+ const fromRoot = relative(root, destination);
1748
+ if (fromRoot === ".." || fromRoot.startsWith(`..${sep}`) || isAbsolute(fromRoot)) throw new Error(`Seed file escaped its root: ${file.path}`);
1749
+ const content = typeof file.content === "string" ? file.content : file.content(roots);
1750
+ await mkdir(dirname(destination), { recursive: true });
1751
+ await writeFile(destination, content, "utf8");
1752
+ }
1753
+ }
1754
+ //#endregion
1755
+ //#region src/mock-directory.ts
1756
+ /** A local `node:http` skill directory speaking the standard protocol, one call per scenario. */
1757
+ function createMockDirectoryBuilder() {
1758
+ const listings = [];
1759
+ const files = /* @__PURE__ */ new Map();
1760
+ const payloads = /* @__PURE__ */ new Map();
1761
+ let requiredToken;
1762
+ const builder = {
1763
+ build: () => start(listings, files, payloads, requiredToken),
1764
+ payloadBytes: (skillId, bytes) => {
1765
+ payloads.set(skillId, bytes);
1766
+ return builder;
1767
+ },
1768
+ rawFileEntry: (skillId, entry) => {
1769
+ const existing = files.get(skillId) ?? [];
1770
+ existing.push(entry);
1771
+ files.set(skillId, existing);
1772
+ return builder;
1773
+ },
1774
+ requireToken: (token) => {
1775
+ requiredToken = token;
1776
+ return builder;
1777
+ },
1778
+ skill: (listing, skillFiles) => {
1779
+ listings.push(listing);
1780
+ const existing = files.get(listing.id) ?? [];
1781
+ existing.push(...skillFiles);
1782
+ files.set(listing.id, existing);
1783
+ return builder;
1784
+ }
1785
+ };
1786
+ return builder;
1787
+ }
1788
+ async function start(listings, files, payloads, requiredToken) {
1789
+ const requests = [];
1790
+ const server = createServer((request, response) => {
1791
+ requests.push({
1792
+ authorization: request.headers.authorization,
1793
+ method: request.method ?? "",
1794
+ path: request.url ?? ""
1795
+ });
1796
+ respond(request, response, listings, files, payloads, requiredToken);
1797
+ });
1798
+ await new Promise((resolve) => {
1799
+ server.listen(0, "127.0.0.1", resolve);
1800
+ });
1801
+ const address = server.address();
1802
+ const close = () => new Promise((resolve, reject) => {
1803
+ server.close((error) => error === void 0 ? resolve() : reject(error));
1804
+ });
1805
+ return {
1806
+ close,
1807
+ requests,
1808
+ url: `http://127.0.0.1:${String(address.port)}`,
1809
+ [Symbol.asyncDispose]: close
1810
+ };
1811
+ }
1812
+ function respond(request, response, listings, files, payloads, requiredToken) {
1813
+ if (requiredToken !== void 0 && request.headers.authorization !== `Bearer ${requiredToken}`) {
1814
+ response.writeHead(401, { "content-type": "text/plain" });
1815
+ response.end("unauthorized");
1816
+ return;
1817
+ }
1818
+ const body = request.url === "/index.json" ? JSON.stringify(listings) : skillBody(request.url, listings, files, payloads);
1819
+ if (body === void 0) {
1820
+ response.writeHead(404, { "content-type": "text/plain" });
1821
+ response.end("not found");
1822
+ return;
1823
+ }
1824
+ response.writeHead(200, { "content-type": "application/json" });
1825
+ response.end(body);
1826
+ }
1827
+ /** The content response for a `/skills/<id>` path, or nothing when the path names no skill. */
1828
+ function skillBody(url, listings, files, payloads) {
1829
+ if (url?.startsWith("/skills/") !== true) return;
1830
+ const skillId = url.slice(8);
1831
+ const payload = payloads.get(skillId);
1832
+ if (payload !== void 0) return "x".repeat(payload);
1833
+ const listing = listings.find((candidate) => candidate.id === skillId);
1834
+ if (listing === void 0) return;
1835
+ return JSON.stringify({
1836
+ ...listing,
1837
+ files: files.get(skillId) ?? []
1838
+ });
1839
+ }
1840
+ //#endregion
1841
+ //#region src/fixtures/claude-code.ts
1842
+ /** The `claude` executable's answers, for composing multi-app seeds on one builder. */
1843
+ function claudeCodeShimResponses(options) {
1844
+ return [{
1845
+ args: ["--version"],
1846
+ stdout: `${options.version} (Claude Code)\n`
1847
+ }, {
1848
+ args: ["auth", "status"],
1849
+ exitCode: options.authenticated ? 0 : 1,
1850
+ stdout: JSON.stringify({ loggedIn: options.authenticated }) + "\n"
1851
+ }];
1852
+ }
1853
+ /** Builds documented Claude Code global and project configuration against an exact CLI version. */
1854
+ function createClaudeCodeSeed(options) {
1855
+ return createSeedBuilder().homeFile(".claude/CLAUDE.md", [
1856
+ "# Global Claude Code instructions",
1857
+ "",
1858
+ "@~/agents/AGENTS.md",
1859
+ "See @team.md for team-specific details.",
1860
+ "Ask @alice about @tryaura/core; neither is an import.",
1861
+ "Keep `@literal.md` as an example.",
1862
+ "```md",
1863
+ "@fenced.md",
1864
+ "```",
1865
+ ""
1866
+ ].join("\n")).homeFile(".claude/team.md", "# Team instructions\n").homeFile(".claude/settings.json", "{\"permissions\":{\"allow\":[\"Bash(pnpm test)\"]}}\n").homeFile("agents/AGENTS.md", "# Shared agent instructions\n").homeFile(".claude.json", ({ workspaceDir }) => claudeConfiguration(workspaceDir, options.inlineSecrets !== false)).workspaceFile("CLAUDE.md", [
1867
+ "# Project Claude Code instructions",
1868
+ "",
1869
+ "@./docs/project.md",
1870
+ "@~/agents/AGENTS.md",
1871
+ ""
1872
+ ].join("\n")).workspaceFile("docs/project.md", "# Project-specific instructions\n").workspaceFile(".mcp.json", JSON.stringify({ mcpServers: { projectDocs: {
1873
+ args: ["--project"],
1874
+ command: "project-docs-server",
1875
+ env: { PROJECT_TOKEN: "${PROJECT_TOKEN}" }
1876
+ } } }, void 0, 2) + "\n").shim("claude", claudeCodeShimResponses(options)).shim("npx", [{ args: [] }]).shim("project-docs-server", [{ args: [] }]).build();
1877
+ }
1878
+ function claudeConfiguration(workspaceDir, inlineSecrets) {
1879
+ return JSON.stringify({
1880
+ mcpServers: {
1881
+ docs: {
1882
+ args: ["-y", "@example/docs-mcp"],
1883
+ command: "npx",
1884
+ env: { DOCS_TOKEN: "${DOCS_TOKEN}" }
1885
+ },
1886
+ legacy: {
1887
+ args: [
1888
+ "@example/legacy-mcp",
1889
+ "--api-key",
1890
+ inlineSecrets ? "sk-fixture-secret" : "${LEGACY_TOKEN}"
1891
+ ],
1892
+ command: "npx"
1893
+ },
1894
+ sentry: {
1895
+ headers: { Authorization: "Bearer ${SENTRY_TOKEN}" },
1896
+ type: "http",
1897
+ url: "https://mcp.sentry.dev"
1898
+ },
1899
+ streaming: {
1900
+ type: "sse",
1901
+ url: `https://sse.example.com/mcp?token=${inlineSecrets ? "sk-fixture-secret" : "${STREAMING_TOKEN}"}`
1902
+ }
1903
+ },
1904
+ projects: {
1905
+ "/unrelated/project": { mcpServers: { ignored: { command: "ignored" } } },
1906
+ [workspaceDir]: { mcpServers: { local: {
1907
+ headers: { Authorization: "Bearer ${LOCAL_TOKEN}" },
1908
+ type: "http",
1909
+ url: `https://local.example.com/mcp?token=${inlineSecrets ? "sk-local-secret" : "${LOCAL_URL_TOKEN}"}`
1910
+ } } }
1911
+ }
1912
+ }, void 0, 2) + "\n";
1913
+ }
1914
+ //#endregion
1915
+ //#region src/fixtures/codex.ts
1916
+ /** Workspace-relative directory the `"nested"` layout puts its second `AGENTS.md` in. */
1917
+ const CODEX_NESTED_PACKAGE = "packages/app";
1918
+ /** The `codex` executable's answers, for composing multi-app seeds on one builder. */
1919
+ function codexShimResponses(options) {
1920
+ return [{
1921
+ args: ["--version"],
1922
+ stdout: `codex-cli ${options.version}\n`
1923
+ }, {
1924
+ args: ["login", "status"],
1925
+ exitCode: options.authenticated ? 0 : 1,
1926
+ stdout: options.authenticated ? "Logged in using ChatGPT\n" : "Not logged in\n"
1927
+ }];
1928
+ }
1929
+ /** Builds one documented Codex configuration against an exact CLI version. */
1930
+ function createCodexSeed(options) {
1931
+ let builder = createSeedBuilder().homeFile(".codex/AGENTS.md", [
1932
+ "# Global Codex instructions",
1933
+ "",
1934
+ "Follow the shared Aura instructions.",
1935
+ ""
1936
+ ].join("\n")).homeFile(".codex/config.toml", ({ workspaceDir }) => codexConfiguration(workspaceDir, options.projectTrust)).homeFile("agents/AGENTS.md", "# Shared agent instructions\n").shim("codex", codexShimResponses(options));
1937
+ if (options.projectInstructions !== void 0) builder = builder.workspaceFile("AGENTS.md", instructions("Project"));
1938
+ if (options.projectInstructions === "override") builder = builder.workspaceFile("AGENTS.override.md", instructions("Project override"));
1939
+ if (options.projectInstructions === "nested") builder = builder.workspaceFile(".git", "gitdir: /dev/null\n").workspaceFile(`${CODEX_NESTED_PACKAGE}/AGENTS.md`, instructions("Package"));
1940
+ return builder.build();
1941
+ }
1942
+ function instructions(title) {
1943
+ return [
1944
+ `# ${title} Codex instructions`,
1945
+ "",
1946
+ `Prefer the ${title.toLowerCase()} conventions.`,
1947
+ ""
1948
+ ].join("\n");
1949
+ }
1950
+ function codexConfiguration(workspaceDir, trust) {
1951
+ return [
1952
+ "model = \"gpt-5\"",
1953
+ "",
1954
+ ...trust === void 0 ? [] : [
1955
+ `[projects.${JSON.stringify(workspaceDir)}]`,
1956
+ `trust_level = "${trust}"`,
1957
+ ""
1958
+ ],
1959
+ "[mcp_servers.docs]",
1960
+ "command = \"npx\"",
1961
+ "args = [\"-y\", \"@example/docs-mcp\"]",
1962
+ "env = { DOCS_TOKEN = \"inline-fixture-secret\" }",
1963
+ "env_vars = [\"LOCAL_TOKEN\", { name = \"REMOTE_TOKEN\", source = \"remote\" }]",
1964
+ "",
1965
+ "[mcp_servers.legacy]",
1966
+ "command = \"npx\"",
1967
+ "args = [\"@example/legacy-mcp\", \"--api-key\", \"sk-fixture-secret\"]",
1968
+ "",
1969
+ "[mcp_servers.sentry]",
1970
+ "url = \"https://mcp.sentry.dev/mcp?token=sk-fixture-secret\"",
1971
+ "bearer_token_env_var = \"SENTRY_TOKEN\"",
1972
+ "http_headers = { \"X-Static\" = \"inline-fixture-secret\" }",
1973
+ "env_http_headers = { Authorization = \"AUTH_TOKEN\" }",
1974
+ "",
1975
+ "[mcp_servers.disabled]",
1976
+ "command = \"ignored\"",
1977
+ "enabled = false",
1978
+ ""
1979
+ ].join("\n");
1980
+ }
1981
+ //#endregion
1982
+ //#region src/fixtures/cursor.ts
1983
+ /** The `cursor` executable's answers, for composing multi-app seeds on one builder. */
1984
+ function cursorShimResponses(options) {
1985
+ return [{
1986
+ args: ["--version"],
1987
+ stdout: `${options.version}\nfixture-commit\narm64\n`
1988
+ }];
1989
+ }
1990
+ /** Builds documented Cursor rule and MCP configuration against an exact editor version. */
1991
+ function createCursorSeed(options) {
1992
+ let builder = createSeedBuilder().homeFile(".cursor/mcp.json", JSON.stringify({ mcpServers: { docs: {
1993
+ args: ["-y", "@example/docs-mcp"],
1994
+ command: "npx",
1995
+ env: { DOCS_TOKEN: "${env:DOCS_TOKEN}" }
1996
+ } } }, void 0, 2) + "\n").workspaceFile(".cursor/mcp.json", JSON.stringify({ mcpServers: { sentry: {
1997
+ headers: { Authorization: "Bearer ${env:SENTRY_TOKEN}" },
1998
+ url: "https://mcp.sentry.dev/mcp?token=sk-fixture-secret"
1999
+ } } }, void 0, 2) + "\n").shim("cursor", cursorShimResponses(options));
2000
+ if (options.rules === "legacy") builder = builder.workspaceFile(".cursorrules", "# Legacy Cursor instructions\n\nUse the repository conventions.\n").workspaceFile(".cursor/rules/ignored.md", "# Not a Cursor project rule\n");
2001
+ else builder = builder.workspaceFile("AGENTS.md", "# Project agents guide\n\nFollow the workspace conventions.\n").workspaceFile(".cursor/rules/project.mdc", "---\nalwaysApply: true\n---\n\nUse @project-template.ts.\n").workspaceFile(".cursor/rules/project-template.ts", "export const template = true;\n").workspaceFile(".cursor/rules/backend/database.mdc", "---\ndescription: Database conventions\nalwaysApply: false\n---\n\nRead @schema.sql.\n").workspaceFile(".cursor/rules/backend/schema.sql", "select 1;\n").workspaceFile(".cursor/rules/ignored.md", "# Not a Cursor project rule\n");
2002
+ return builder.build();
2003
+ }
2004
+ //#endregion
2005
+ export { ANY_ARGUMENT, CODEX_NESTED_PACKAGE, captureFilesystem, claudeCodeShimResponses, codexShimResponses, createClaudeCodeSeed, createCodexSeed, createCursorSeed, createMockDirectoryBuilder, createSeedBuilder, cursorShimResponses, expectConvergedTwice, loopbackOnlyHttpGet, runBinaryCheck, runCheck, runSetup };