@quikit/quikpilot-runner 8.2.8 → 9.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/README.md +17 -0
- package/dist/index.js +744 -64
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -73,13 +73,14 @@ async function describeQuikTrackFailure(res) {
|
|
|
73
73
|
|
|
74
74
|
// src/callback.ts
|
|
75
75
|
async function reportBack(params) {
|
|
76
|
-
const { callbackUrl, runToken, status, branch, warnings, targeting, baselineFindings, unresolved } = params;
|
|
76
|
+
const { callbackUrl, runToken, status, branch, warnings, targeting, baselineFindings, vanishedTests, unresolved } = params;
|
|
77
77
|
const body = { status };
|
|
78
78
|
if (branch) body.branch = branch;
|
|
79
79
|
if (warnings && warnings.length > 0) body.warnings = warnings;
|
|
80
80
|
if (unresolved && unresolved.length > 0) body.unresolved = unresolved;
|
|
81
81
|
if (targeting) body.targeting = targeting;
|
|
82
82
|
if (baselineFindings) body.baselineFindings = baselineFindings;
|
|
83
|
+
if (vanishedTests) body.vanishedTests = vanishedTests;
|
|
83
84
|
const res = await fetch(callbackUrl, {
|
|
84
85
|
method: "POST",
|
|
85
86
|
headers: { Authorization: `Bearer ${runToken}`, "Content-Type": "application/json" },
|
|
@@ -111,20 +112,20 @@ function runTestCommand(command) {
|
|
|
111
112
|
}
|
|
112
113
|
function runTestCommandAsync(command) {
|
|
113
114
|
if (!command) return Promise.resolve("success");
|
|
114
|
-
return new Promise((
|
|
115
|
+
return new Promise((resolve5) => {
|
|
115
116
|
const child = (0, import_node_child_process.spawn)(command, { shell: "/bin/bash", stdio: "inherit" });
|
|
116
|
-
child.on("exit", (code) =>
|
|
117
|
-
child.on("error", () =>
|
|
117
|
+
child.on("exit", (code) => resolve5(code === 0 ? "success" : "failure"));
|
|
118
|
+
child.on("error", () => resolve5("failure"));
|
|
118
119
|
});
|
|
119
120
|
}
|
|
120
121
|
function runCommandWithTimeout(command, timeoutMs) {
|
|
121
|
-
return new Promise((
|
|
122
|
+
return new Promise((resolve5) => {
|
|
122
123
|
let settled = false;
|
|
123
124
|
const finish = (outcome) => {
|
|
124
125
|
if (settled) return;
|
|
125
126
|
settled = true;
|
|
126
127
|
clearTimeout(timer);
|
|
127
|
-
|
|
128
|
+
resolve5(outcome);
|
|
128
129
|
};
|
|
129
130
|
const child = (0, import_node_child_process.spawn)(command, { shell: "/bin/bash", stdio: "inherit", detached: true });
|
|
130
131
|
const timer = setTimeout(() => {
|
|
@@ -229,7 +230,7 @@ function commitAndPush(branch, runId, installationToken, targeted, issueKey, iss
|
|
|
229
230
|
|
|
230
231
|
// src/codegen.ts
|
|
231
232
|
var import_node_fs = require("node:fs");
|
|
232
|
-
var
|
|
233
|
+
var import_node_path2 = require("node:path");
|
|
233
234
|
|
|
234
235
|
// src/deniedPaths.ts
|
|
235
236
|
function globToRegExp(pattern) {
|
|
@@ -251,6 +252,96 @@ function findDeniedContextPath(contextPaths, patterns) {
|
|
|
251
252
|
return null;
|
|
252
253
|
}
|
|
253
254
|
|
|
255
|
+
// src/editsApply.ts
|
|
256
|
+
function detectLineEnding(text) {
|
|
257
|
+
const crlf = text.split("\r\n").length - 1;
|
|
258
|
+
const bareLf = text.split("\n").length - 1 - crlf;
|
|
259
|
+
if (crlf > 0 && bareLf > 0) return "mixed";
|
|
260
|
+
return crlf > 0 ? "CRLF" : "LF";
|
|
261
|
+
}
|
|
262
|
+
function toLf(text) {
|
|
263
|
+
return text.replace(/\r\n/g, "\n");
|
|
264
|
+
}
|
|
265
|
+
function fromLf(text, ending) {
|
|
266
|
+
return ending === "CRLF" ? text.replace(/\n/g, "\r\n") : text;
|
|
267
|
+
}
|
|
268
|
+
function occurrences(haystack, needle) {
|
|
269
|
+
const found = [];
|
|
270
|
+
let index = haystack.indexOf(needle);
|
|
271
|
+
while (index !== -1) {
|
|
272
|
+
found.push(index);
|
|
273
|
+
index = haystack.indexOf(needle, index + 1);
|
|
274
|
+
}
|
|
275
|
+
return found;
|
|
276
|
+
}
|
|
277
|
+
function applyEdits(originalLf, edits) {
|
|
278
|
+
if (!Array.isArray(edits) || edits.length === 0) {
|
|
279
|
+
return {
|
|
280
|
+
ok: false,
|
|
281
|
+
editCount: 0,
|
|
282
|
+
refusals: [{ kind: "no_edits", edits: [], matchCount: null, message: "the response carried no edits" }]
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
const refusals = [];
|
|
286
|
+
const ranges = [];
|
|
287
|
+
edits.forEach((edit, index) => {
|
|
288
|
+
const number = index + 1;
|
|
289
|
+
const record = edit !== null && typeof edit === "object" ? edit : null;
|
|
290
|
+
const find = record?.find;
|
|
291
|
+
const replace = record?.replace;
|
|
292
|
+
if (typeof find !== "string" || typeof replace !== "string") {
|
|
293
|
+
refusals.push({
|
|
294
|
+
kind: "empty",
|
|
295
|
+
edits: [number],
|
|
296
|
+
matchCount: null,
|
|
297
|
+
message: `edit ${number} is missing a string find or replace`
|
|
298
|
+
});
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (find === "") {
|
|
302
|
+
refusals.push({ kind: "empty", edits: [number], matchCount: null, message: `edit ${number} has an empty anchor` });
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const starts = occurrences(originalLf, find);
|
|
306
|
+
if (starts.length === 0) {
|
|
307
|
+
refusals.push({ kind: "no_match", edits: [number], matchCount: 0, message: `edit ${number}: the anchor matched nothing` });
|
|
308
|
+
} else if (starts.length > 1) {
|
|
309
|
+
refusals.push({
|
|
310
|
+
kind: "ambiguous",
|
|
311
|
+
edits: [number],
|
|
312
|
+
matchCount: starts.length,
|
|
313
|
+
message: `edit ${number}: the anchor matched ${starts.length} times`
|
|
314
|
+
});
|
|
315
|
+
} else {
|
|
316
|
+
ranges.push({ start: starts[0], end: starts[0] + find.length, number, replace });
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
for (let i = 0; i < ranges.length; i++) {
|
|
320
|
+
for (let j = i + 1; j < ranges.length; j++) {
|
|
321
|
+
const a = ranges[i];
|
|
322
|
+
const b = ranges[j];
|
|
323
|
+
if (a.start < b.end && b.start < a.end) {
|
|
324
|
+
const [first, second] = a.number < b.number ? [a.number, b.number] : [b.number, a.number];
|
|
325
|
+
refusals.push({
|
|
326
|
+
kind: "overlap",
|
|
327
|
+
edits: [first, second],
|
|
328
|
+
matchCount: null,
|
|
329
|
+
message: `edits ${first} and ${second} overlap`
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (refusals.length > 0) return { ok: false, refusals, editCount: edits.length };
|
|
335
|
+
const pieces = [];
|
|
336
|
+
let cursor = 0;
|
|
337
|
+
for (const range of [...ranges].sort((x, y) => x.start - y.start)) {
|
|
338
|
+
pieces.push(originalLf.slice(cursor, range.start), range.replace);
|
|
339
|
+
cursor = range.end;
|
|
340
|
+
}
|
|
341
|
+
pieces.push(originalLf.slice(cursor));
|
|
342
|
+
return { ok: true, content: pieces.join(""), editCount: edits.length };
|
|
343
|
+
}
|
|
344
|
+
|
|
254
345
|
// src/runtimeReplyError.ts
|
|
255
346
|
var RuntimeReplyError = class extends Error {
|
|
256
347
|
summary;
|
|
@@ -291,6 +382,315 @@ function unwrapExecute(envelope, useCase) {
|
|
|
291
382
|
return envelope.structuredJson;
|
|
292
383
|
}
|
|
293
384
|
|
|
385
|
+
// src/importContext.ts
|
|
386
|
+
var import_node_path = require("node:path");
|
|
387
|
+
var REGEX_PRECEDING_KEYWORDS = /* @__PURE__ */ new Set([
|
|
388
|
+
"return",
|
|
389
|
+
"typeof",
|
|
390
|
+
"instanceof",
|
|
391
|
+
"in",
|
|
392
|
+
"of",
|
|
393
|
+
"new",
|
|
394
|
+
"delete",
|
|
395
|
+
"void",
|
|
396
|
+
"throw",
|
|
397
|
+
"case",
|
|
398
|
+
"do",
|
|
399
|
+
"else",
|
|
400
|
+
"yield",
|
|
401
|
+
"await"
|
|
402
|
+
]);
|
|
403
|
+
var SPACE_CODES = /* @__PURE__ */ new Set([32, 9, 10, 11, 12, 13, 160, 5760, 8232, 8233, 8239, 8287, 12288, 65279]);
|
|
404
|
+
var isSpace = (c) => {
|
|
405
|
+
const code = c.charCodeAt(0);
|
|
406
|
+
return SPACE_CODES.has(code) || code >= 8192 && code <= 8202;
|
|
407
|
+
};
|
|
408
|
+
var isIdStart = (c) => !isSpace(c) && (/[A-Za-z_$]/.test(c) || c.charCodeAt(0) > 127);
|
|
409
|
+
var isIdPart = (c) => !isSpace(c) && (/[A-Za-z0-9_$]/.test(c) || c.charCodeAt(0) > 127);
|
|
410
|
+
function tokenize(src) {
|
|
411
|
+
const out = [];
|
|
412
|
+
const n = src.length;
|
|
413
|
+
function regexAllowed() {
|
|
414
|
+
const prev = out[out.length - 1];
|
|
415
|
+
if (!prev) return true;
|
|
416
|
+
if (prev.k === "p") return prev.v !== ")" && prev.v !== "]" && prev.v !== "}";
|
|
417
|
+
if (prev.k === "id") return REGEX_PRECEDING_KEYWORDS.has(prev.v);
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
function scan(start, inSubstitution) {
|
|
421
|
+
let i = start;
|
|
422
|
+
let depth = 0;
|
|
423
|
+
while (i < n) {
|
|
424
|
+
const c = src[i];
|
|
425
|
+
if (isSpace(c)) {
|
|
426
|
+
i++;
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
if (c === "/" && src[i + 1] === "/") {
|
|
430
|
+
while (i < n && src[i] !== "\n") i++;
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (c === "/" && src[i + 1] === "*") {
|
|
434
|
+
const end = src.indexOf("*/", i + 2);
|
|
435
|
+
i = end === -1 ? n : end + 2;
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
if (c === "'" || c === '"') {
|
|
439
|
+
let j = i + 1;
|
|
440
|
+
let closed = false;
|
|
441
|
+
while (j < n) {
|
|
442
|
+
const d = src[j];
|
|
443
|
+
if (d === "\\") {
|
|
444
|
+
j += 2;
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (d === c) {
|
|
448
|
+
closed = true;
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
if (d === "\n" || d === "\r") break;
|
|
452
|
+
j++;
|
|
453
|
+
}
|
|
454
|
+
if (closed) out.push({ k: "str", v: src.slice(i + 1, j) });
|
|
455
|
+
i = closed ? j + 1 : j;
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
if (c === "`") {
|
|
459
|
+
let j = i + 1;
|
|
460
|
+
let hasSubstitution = false;
|
|
461
|
+
let closed = false;
|
|
462
|
+
const textStart = j;
|
|
463
|
+
let textEnd = j;
|
|
464
|
+
while (j < n) {
|
|
465
|
+
const d = src[j];
|
|
466
|
+
if (d === "\\") {
|
|
467
|
+
j += 2;
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
if (d === "`") {
|
|
471
|
+
closed = true;
|
|
472
|
+
textEnd = hasSubstitution ? textEnd : j;
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
if (d === "$" && src[j + 1] === "{") {
|
|
476
|
+
hasSubstitution = true;
|
|
477
|
+
j = scan(j + 2, true);
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
j++;
|
|
481
|
+
}
|
|
482
|
+
if (closed) out.push(hasSubstitution ? { k: "tpl", v: "" } : { k: "str", v: src.slice(textStart, textEnd) });
|
|
483
|
+
i = closed ? j + 1 : n;
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
if (c === "/" && regexAllowed()) {
|
|
487
|
+
let j = i + 1;
|
|
488
|
+
let inClass = false;
|
|
489
|
+
let closed = false;
|
|
490
|
+
while (j < n) {
|
|
491
|
+
const d = src[j];
|
|
492
|
+
if (d === "\\") {
|
|
493
|
+
j += 2;
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
if (d === "\n" || d === "\r") break;
|
|
497
|
+
if (d === "[") inClass = true;
|
|
498
|
+
else if (d === "]") inClass = false;
|
|
499
|
+
else if (d === "/" && !inClass) {
|
|
500
|
+
closed = true;
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
j++;
|
|
504
|
+
}
|
|
505
|
+
if (closed) {
|
|
506
|
+
j++;
|
|
507
|
+
while (j < n && /[a-z]/i.test(src[j])) j++;
|
|
508
|
+
out.push({ k: "num", v: "/re/" });
|
|
509
|
+
i = j;
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
out.push({ k: "p", v: "/" });
|
|
513
|
+
i++;
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
if (/[0-9]/.test(c)) {
|
|
517
|
+
let j = i + 1;
|
|
518
|
+
while (j < n && /[0-9A-Za-z_.]/.test(src[j])) j++;
|
|
519
|
+
out.push({ k: "num", v: src.slice(i, j) });
|
|
520
|
+
i = j;
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (isIdStart(c)) {
|
|
524
|
+
let j = i + 1;
|
|
525
|
+
while (j < n && isIdPart(src[j])) j++;
|
|
526
|
+
out.push({ k: "id", v: src.slice(i, j) });
|
|
527
|
+
i = j;
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
if (inSubstitution) {
|
|
531
|
+
if (c === "{") depth++;
|
|
532
|
+
else if (c === "}") {
|
|
533
|
+
if (depth === 0) return i + 1;
|
|
534
|
+
depth--;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
out.push({ k: "p", v: c });
|
|
538
|
+
i++;
|
|
539
|
+
}
|
|
540
|
+
return n;
|
|
541
|
+
}
|
|
542
|
+
scan(0, false);
|
|
543
|
+
return out;
|
|
544
|
+
}
|
|
545
|
+
function extractImportSpecifiers(source) {
|
|
546
|
+
const t = tokenize(source);
|
|
547
|
+
const found = [];
|
|
548
|
+
const seen = /* @__PURE__ */ new Set();
|
|
549
|
+
const add = (s) => {
|
|
550
|
+
if (!seen.has(s)) {
|
|
551
|
+
seen.add(s);
|
|
552
|
+
found.push(s);
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
const isP = (tok, v) => tok?.k === "p" && tok.v === v;
|
|
556
|
+
for (let i = 0; i < t.length; i++) {
|
|
557
|
+
const tok = t[i];
|
|
558
|
+
if (tok.k !== "id") continue;
|
|
559
|
+
if (isP(t[i - 1], ".")) continue;
|
|
560
|
+
if (tok.v === "require") {
|
|
561
|
+
if (isP(t[i + 1], "(") && t[i + 2]?.k === "str" && isP(t[i + 3], ")")) add(t[i + 2].v);
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
if (tok.v !== "import" && tok.v !== "export") continue;
|
|
565
|
+
const next = t[i + 1];
|
|
566
|
+
if (tok.v === "import") {
|
|
567
|
+
if (next?.k === "str") {
|
|
568
|
+
add(next.v);
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
if (isP(next, "(")) {
|
|
572
|
+
if (t[i + 2]?.k === "str" && (isP(t[i + 3], ")") || isP(t[i + 3], ","))) add(t[i + 2].v);
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
for (let j = i + 1; j < t.length; j++) {
|
|
577
|
+
const c = t[j];
|
|
578
|
+
if (c.k === "id") {
|
|
579
|
+
if (c.v === "import" || c.v === "export") break;
|
|
580
|
+
if (c.v === "from" && t[j + 1]?.k === "str") {
|
|
581
|
+
add(t[j + 1].v);
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
if (c.k === "p" && (c.v === "{" || c.v === "}" || c.v === "," || c.v === "*")) continue;
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return found;
|
|
591
|
+
}
|
|
592
|
+
function supportsImportScan(path) {
|
|
593
|
+
return /\.(?:[cm]?[jt]sx?)$/i.test(path);
|
|
594
|
+
}
|
|
595
|
+
var CODE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts", ".json"];
|
|
596
|
+
var ASSET_EXTS = /* @__PURE__ */ new Set([
|
|
597
|
+
".css",
|
|
598
|
+
".scss",
|
|
599
|
+
".sass",
|
|
600
|
+
".less",
|
|
601
|
+
".svg",
|
|
602
|
+
".png",
|
|
603
|
+
".jpg",
|
|
604
|
+
".jpeg",
|
|
605
|
+
".gif",
|
|
606
|
+
".webp",
|
|
607
|
+
".avif",
|
|
608
|
+
".ico",
|
|
609
|
+
".bmp",
|
|
610
|
+
".woff",
|
|
611
|
+
".woff2",
|
|
612
|
+
".ttf",
|
|
613
|
+
".otf",
|
|
614
|
+
".eot",
|
|
615
|
+
".mp3",
|
|
616
|
+
".mp4",
|
|
617
|
+
".webm",
|
|
618
|
+
".wav",
|
|
619
|
+
".pdf",
|
|
620
|
+
".zip",
|
|
621
|
+
".wasm"
|
|
622
|
+
]);
|
|
623
|
+
var TS_SIBLINGS = { ".js": [".ts", ".tsx"], ".jsx": [".tsx"], ".mjs": [".mts"], ".cjs": [".cts"] };
|
|
624
|
+
function resolveImport(fromPath, specifier, listing) {
|
|
625
|
+
if (/^(?:@\/|~\/|#|\$|\/)/.test(specifier)) return { kind: "unresolved", reason: "alias" };
|
|
626
|
+
const clean = specifier.replace(/[?#].*$/, "");
|
|
627
|
+
const relative4 = clean === "." || clean === ".." || clean.startsWith("./") || clean.startsWith("../");
|
|
628
|
+
if (!relative4) return { kind: "external" };
|
|
629
|
+
const base = import_node_path.posix.normalize(import_node_path.posix.join(import_node_path.posix.dirname(fromPath), clean));
|
|
630
|
+
if (base === ".." || base.startsWith("../") || import_node_path.posix.isAbsolute(base)) return { kind: "unresolved", reason: "not found" };
|
|
631
|
+
const ext = import_node_path.posix.extname(base);
|
|
632
|
+
if (ASSET_EXTS.has(ext.toLowerCase())) return { kind: "unresolved", reason: "asset" };
|
|
633
|
+
const candidates = [];
|
|
634
|
+
if (CODE_EXTS.includes(ext)) {
|
|
635
|
+
candidates.push(base);
|
|
636
|
+
for (const sibling of TS_SIBLINGS[ext] ?? []) candidates.push(base.slice(0, -ext.length) + sibling);
|
|
637
|
+
}
|
|
638
|
+
for (const e of CODE_EXTS) candidates.push(base + e);
|
|
639
|
+
candidates.push(base + ".d.ts");
|
|
640
|
+
for (const e of CODE_EXTS) candidates.push(`${base}/index${e}`);
|
|
641
|
+
for (const candidate of candidates) {
|
|
642
|
+
if (listing.has(candidate)) return { kind: "repo", path: candidate };
|
|
643
|
+
}
|
|
644
|
+
return { kind: "unresolved", reason: "not found", attempted: base };
|
|
645
|
+
}
|
|
646
|
+
var ACTIONABLE_IMPORT_SKIPS = /* @__PURE__ */ new Set(["import: too large", "import: over budget", "import: unreadable"]);
|
|
647
|
+
function collectImportContext(p) {
|
|
648
|
+
const out = { entries: [], skipped: [], importedPaths: [], aliasImports: 0 };
|
|
649
|
+
if (!supportsImportScan(p.targetPath)) return out;
|
|
650
|
+
const seen = new Set(p.alreadyNamed);
|
|
651
|
+
seen.add(p.targetPath);
|
|
652
|
+
let total = p.spentChars;
|
|
653
|
+
for (const specifier of extractImportSpecifiers(p.source)) {
|
|
654
|
+
const resolved = resolveImport(p.targetPath, specifier, p.listing);
|
|
655
|
+
if (resolved.kind === "external") continue;
|
|
656
|
+
if (resolved.kind === "unresolved") {
|
|
657
|
+
if (resolved.reason === "alias") out.aliasImports++;
|
|
658
|
+
else if (resolved.reason === "not found") {
|
|
659
|
+
const path2 = resolved.attempted ?? specifier;
|
|
660
|
+
if (!seen.has(path2)) {
|
|
661
|
+
seen.add(path2);
|
|
662
|
+
out.skipped.push({ path: path2, reason: "import: not in workspace" });
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
const path = resolved.path;
|
|
668
|
+
if (seen.has(path)) continue;
|
|
669
|
+
seen.add(path);
|
|
670
|
+
if (matchesDeniedPath(path, p.deniedPaths)) {
|
|
671
|
+
out.skipped.push({ path, reason: "import: denied" });
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
let content;
|
|
675
|
+
try {
|
|
676
|
+
content = p.readFile(path);
|
|
677
|
+
} catch {
|
|
678
|
+
out.skipped.push({ path, reason: "import: unreadable" });
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
if (content.length > p.maxFileChars) {
|
|
682
|
+
out.skipped.push({ path, reason: "import: too large" });
|
|
683
|
+
} else if (total + content.length > p.maxTotalChars) {
|
|
684
|
+
out.skipped.push({ path, reason: "import: over budget" });
|
|
685
|
+
} else {
|
|
686
|
+
total += content.length;
|
|
687
|
+
out.entries.push({ path, content });
|
|
688
|
+
out.importedPaths.push(path);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return out;
|
|
692
|
+
}
|
|
693
|
+
|
|
294
694
|
// src/retryFetch.ts
|
|
295
695
|
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([502, 503, 504]);
|
|
296
696
|
var RETRYABLE_NETWORK_ERROR_CODES = /* @__PURE__ */ new Set(["ECONNRESET", "ETIMEDOUT"]);
|
|
@@ -413,6 +813,58 @@ function looksLikeTruncatedRewrite(target, currentContent, newContent) {
|
|
|
413
813
|
return null;
|
|
414
814
|
}
|
|
415
815
|
var CONTRACT_VERSION = "test-targets-1";
|
|
816
|
+
var CONTRACT_VERSION_EDITS = "edits-1";
|
|
817
|
+
var ANCHOR_REFUSED_CODE = "CODEGEN_ANCHOR_REFUSED";
|
|
818
|
+
function summariseLocalRefusals(refusals) {
|
|
819
|
+
const parts = [];
|
|
820
|
+
const matchCounts = [];
|
|
821
|
+
for (const r of refusals) {
|
|
822
|
+
if (r.kind === "ambiguous") {
|
|
823
|
+
parts.push(`an anchor matched ${r.matchCount} times`);
|
|
824
|
+
if (r.matchCount !== null) matchCounts.push(r.matchCount);
|
|
825
|
+
} else if (r.kind === "no_match") {
|
|
826
|
+
parts.push("an anchor matched nothing");
|
|
827
|
+
} else if (r.kind === "overlap") {
|
|
828
|
+
parts.push(`edits ${r.edits.join(" and ")} overlap`);
|
|
829
|
+
} else if (r.kind === "no_edits") {
|
|
830
|
+
parts.push("the reply had no edits");
|
|
831
|
+
} else {
|
|
832
|
+
parts.push("an edit was missing its anchor or replacement");
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
return { text: [...new Set(parts)].join("; "), matchCounts };
|
|
836
|
+
}
|
|
837
|
+
function summariseRuntimeRefusal(error) {
|
|
838
|
+
const text = typeof error === "string" ? error : "";
|
|
839
|
+
const matchCounts = [...text.matchAll(/anchor matched (\d+) times/g)].map((m) => Number(m[1]));
|
|
840
|
+
const parts = matchCounts.map((n) => `an anchor matched ${n} times`);
|
|
841
|
+
if (/no match/i.test(text)) parts.push("an anchor matched nothing");
|
|
842
|
+
if (/overlap/i.test(text)) parts.push("edits overlapped");
|
|
843
|
+
if (/empty anchor|missing a string/i.test(text)) parts.push("an edit was missing its anchor or replacement");
|
|
844
|
+
return { text: parts.length > 0 ? [...new Set(parts)].join("; ") : "the runtime could not place the edits", matchCounts };
|
|
845
|
+
}
|
|
846
|
+
function throwOnTerminalRefusal(envelope, targetPath) {
|
|
847
|
+
if (typeof envelope?.errorCode === "string" && NOT_RETRIED_CODES.has(envelope.errorCode)) {
|
|
848
|
+
const detail = envelope.error ? `: ${envelope.error}` : "";
|
|
849
|
+
throw new RuntimeReplyError(
|
|
850
|
+
`Codegen refused ${targetPath}: ${envelope.errorCode} (not retried)${detail}`,
|
|
851
|
+
`Codegen refused ${targetPath}: ${envelope.errorCode} (not retried)`
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
if (envelope?.errorCode === JUDGED_BY_TOO_LARGE_CODE) {
|
|
855
|
+
const detail = envelope.error ? `: ${envelope.error}` : "";
|
|
856
|
+
throw new RuntimeReplyError(
|
|
857
|
+
`Codegen refused ${targetPath}: ${envelope.errorCode} (after narrowing)${detail}`,
|
|
858
|
+
`Codegen refused ${targetPath}: ${envelope.errorCode} (after narrowing)`
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
function codegenRefusedError(targetPath, body) {
|
|
863
|
+
return new RuntimeReplyError(
|
|
864
|
+
`Codegen refused ${targetPath}${body.refusalReason ? `: ${body.refusalReason}` : ""}`,
|
|
865
|
+
`Codegen refused ${targetPath}`
|
|
866
|
+
);
|
|
867
|
+
}
|
|
416
868
|
function buildJudgedByPool(requiredTests, targets, skippedCases) {
|
|
417
869
|
const claimedIds = /* @__PURE__ */ new Set();
|
|
418
870
|
for (const t of targets) {
|
|
@@ -444,6 +896,8 @@ async function runCodegen(gateway, targets, workspaceListing, requiredTests, den
|
|
|
444
896
|
for (const target of targets) {
|
|
445
897
|
const isNewFile = target.kind === "NEW_FILE";
|
|
446
898
|
const currentContent = isNewFile ? "" : (0, import_node_fs.readFileSync)(target.path, "utf8");
|
|
899
|
+
const lineEnding = isNewFile ? "LF" : detectLineEnding(currentContent);
|
|
900
|
+
const wantsEdits = !isNewFile && lineEnding !== "mixed";
|
|
447
901
|
const contextPaths = target.contextPaths ?? [];
|
|
448
902
|
const deniedContext = findDeniedContextPath(contextPaths, deniedPaths);
|
|
449
903
|
if (deniedContext) {
|
|
@@ -458,28 +912,51 @@ async function runCodegen(gateway, targets, workspaceListing, requiredTests, den
|
|
|
458
912
|
);
|
|
459
913
|
}
|
|
460
914
|
}
|
|
461
|
-
const namedContents = contextPaths.map((
|
|
462
|
-
const
|
|
463
|
-
const
|
|
464
|
-
for (const s of added.skipped) {
|
|
915
|
+
const namedContents = contextPaths.map((path2) => ({ path: path2, content: (0, import_node_fs.readFileSync)(path2, "utf8") }));
|
|
916
|
+
const runFiles = collectRunContext(target, targets, new Set(contextPaths), deniedPaths);
|
|
917
|
+
for (const s of runFiles.skipped) {
|
|
465
918
|
warnings.push(`not shown to the test for ${target.path}: ${s.path} (${s.reason})`);
|
|
466
919
|
}
|
|
920
|
+
const imports = isNewFile ? { entries: [], skipped: [], importedPaths: [], aliasImports: 0 } : collectImportContext({
|
|
921
|
+
targetPath: target.path,
|
|
922
|
+
source: currentContent,
|
|
923
|
+
alreadyNamed: /* @__PURE__ */ new Set([...contextPaths, ...runFiles.entries.map((e) => e.path)]),
|
|
924
|
+
deniedPaths,
|
|
925
|
+
listing,
|
|
926
|
+
readFile: (p) => (0, import_node_fs.readFileSync)(p, "utf8"),
|
|
927
|
+
maxFileChars: MAX_ADDED_FILE_CHARS,
|
|
928
|
+
maxTotalChars: MAX_ADDED_CONTEXT_CHARS,
|
|
929
|
+
spentChars: runFiles.entries.reduce((n, e) => n + e.content.length, 0)
|
|
930
|
+
});
|
|
931
|
+
for (const s of imports.skipped) {
|
|
932
|
+
if (ACTIONABLE_IMPORT_SKIPS.has(s.reason)) {
|
|
933
|
+
warnings.push(`not shown with ${target.path}: ${s.path} (imported by it; ${s.reason.replace(/^import: /, "")})`);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
const added = {
|
|
937
|
+
entries: [...runFiles.entries, ...imports.entries],
|
|
938
|
+
skipped: [...runFiles.skipped, ...imports.skipped]
|
|
939
|
+
};
|
|
940
|
+
const contextContents = [...namedContents, ...added.entries];
|
|
467
941
|
const contextRecord = {
|
|
468
942
|
targetPath: target.path,
|
|
469
943
|
contextPaths,
|
|
470
944
|
addedPaths: added.entries.map((e) => e.path),
|
|
471
|
-
skipped: added.skipped
|
|
945
|
+
skipped: added.skipped,
|
|
946
|
+
...imports.importedPaths.length > 0 ? { importedPaths: imports.importedPaths } : {},
|
|
947
|
+
...imports.aliasImports > 0 ? { aliasImports: imports.aliasImports } : {}
|
|
472
948
|
};
|
|
473
949
|
console.log(JSON.stringify({ event: "codegen.context", ...contextRecord }));
|
|
474
950
|
contextLog?.push(contextRecord);
|
|
475
951
|
const contextData = {
|
|
476
952
|
targetPath: target.path,
|
|
477
|
-
|
|
953
|
+
// Every anchor and replacement is LF, always (Doc 62 §2), so the file is sent as LF.
|
|
954
|
+
currentContent: wantsEdits ? toLf(currentContent) : currentContent,
|
|
478
955
|
planSteps: target.planStepsImplemented,
|
|
479
956
|
kind: target.kind,
|
|
480
957
|
contextContents,
|
|
481
958
|
// QUIKTR-1083 (§15.6) — required on every call on the versioned path.
|
|
482
|
-
contractVersion: CONTRACT_VERSION
|
|
959
|
+
contractVersion: wantsEdits ? CONTRACT_VERSION_EDITS : CONTRACT_VERSION
|
|
483
960
|
};
|
|
484
961
|
if (isNewFile) {
|
|
485
962
|
contextData.workspaceListing = workspaceListing;
|
|
@@ -533,6 +1010,7 @@ async function runCodegen(gateway, targets, workspaceListing, requiredTests, den
|
|
|
533
1010
|
contextData.contextContents = namedContents;
|
|
534
1011
|
warnings.push(`context for ${target.path} was too large with the run's other files; retried without them`);
|
|
535
1012
|
contextRecord.addedPaths = [];
|
|
1013
|
+
delete contextRecord.importedPaths;
|
|
536
1014
|
contextRecord.retriedWithoutAdded = true;
|
|
537
1015
|
res = await callCodegen();
|
|
538
1016
|
if (!res.ok) {
|
|
@@ -541,29 +1019,91 @@ async function runCodegen(gateway, targets, workspaceListing, requiredTests, den
|
|
|
541
1019
|
envelope = await res.json();
|
|
542
1020
|
checkHardFailure(envelope, "pilot.codegen", target.path);
|
|
543
1021
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
1022
|
+
throwOnTerminalRefusal(envelope, target.path);
|
|
1023
|
+
let newContent = null;
|
|
1024
|
+
let failedEdits = null;
|
|
1025
|
+
let editCount;
|
|
1026
|
+
let path = "whole_file";
|
|
1027
|
+
let reason = isNewFile ? "new_file" : wantsEdits ? void 0 : "mixed_line_endings";
|
|
1028
|
+
if (wantsEdits && envelope?.errorCode === ANCHOR_REFUSED_CODE) {
|
|
1029
|
+
failedEdits = { reason: "anchor_refused", summary: summariseRuntimeRefusal(envelope.error) };
|
|
1030
|
+
} else {
|
|
1031
|
+
const body = unwrapExecute(envelope, "pilot.codegen");
|
|
1032
|
+
if (wantsEdits) {
|
|
1033
|
+
if (body.outcome === "REFUSED") throw codegenRefusedError(target.path, body);
|
|
1034
|
+
if (body.outcome !== "GENERATED" || typeof body.newContent === "string") {
|
|
1035
|
+
throw new RuntimeReplyError(
|
|
1036
|
+
`Codegen reply for ${target.path} was not in the edits-1 shape and was not applied`,
|
|
1037
|
+
`Codegen reply for ${target.path} was not in the edits-1 shape`
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
const applied = applyEdits(toLf(currentContent), body.edits);
|
|
1041
|
+
if (applied.ok) {
|
|
1042
|
+
newContent = fromLf(applied.content, lineEnding);
|
|
1043
|
+
path = "edits";
|
|
1044
|
+
editCount = applied.editCount;
|
|
1045
|
+
} else if (applied.refusals.some((r) => r.kind === "no_edits")) {
|
|
1046
|
+
throw new RuntimeReplyError(
|
|
1047
|
+
`Codegen reply for ${target.path} carried no edits (not retried)`,
|
|
1048
|
+
`Codegen reply for ${target.path} carried no edits`
|
|
1049
|
+
);
|
|
1050
|
+
} else {
|
|
1051
|
+
failedEdits = { reason: "local_refusal", summary: summariseLocalRefusals(applied.refusals) };
|
|
1052
|
+
}
|
|
1053
|
+
} else {
|
|
1054
|
+
if (body.outcome !== "GENERATED" || typeof body.newContent !== "string") {
|
|
1055
|
+
throw codegenRefusedError(target.path, body);
|
|
1056
|
+
}
|
|
1057
|
+
newContent = body.newContent;
|
|
1058
|
+
}
|
|
550
1059
|
}
|
|
551
|
-
if (
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
`Codegen refused ${target.path}: ${envelope.errorCode} (after narrowing)${detail}`,
|
|
555
|
-
`Codegen refused ${target.path}: ${envelope.errorCode} (after narrowing)`
|
|
1060
|
+
if (failedEdits) {
|
|
1061
|
+
warnings.push(
|
|
1062
|
+
`edits for ${target.path} could not be placed (${failedEdits.summary.text}); asked again for the whole file`
|
|
556
1063
|
);
|
|
1064
|
+
contextData.currentContent = currentContent;
|
|
1065
|
+
contextData.contractVersion = CONTRACT_VERSION;
|
|
1066
|
+
try {
|
|
1067
|
+
res = await callCodegen();
|
|
1068
|
+
if (!res.ok) {
|
|
1069
|
+
throw new Error(`Codegen call failed for ${target.path}: ${res.status} ${res.statusText}`);
|
|
1070
|
+
}
|
|
1071
|
+
envelope = await res.json();
|
|
1072
|
+
checkHardFailure(envelope, "pilot.codegen", target.path);
|
|
1073
|
+
throwOnTerminalRefusal(envelope, target.path);
|
|
1074
|
+
const whole = unwrapExecute(envelope, "pilot.codegen");
|
|
1075
|
+
if (whole.outcome !== "GENERATED" || typeof whole.newContent !== "string") {
|
|
1076
|
+
throw codegenRefusedError(target.path, whole);
|
|
1077
|
+
}
|
|
1078
|
+
newContent = whole.newContent;
|
|
1079
|
+
} catch (error) {
|
|
1080
|
+
if (error instanceof RuntimeReplyError) {
|
|
1081
|
+
throw new RuntimeReplyError(
|
|
1082
|
+
`${error.message} (this was the whole-file request made after the edits for ${target.path} were refused: ${failedEdits.summary.text})`,
|
|
1083
|
+
`${error.summary} (the whole-file request, made after the edits were refused)`
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
throw error;
|
|
1087
|
+
}
|
|
1088
|
+
path = "fallback";
|
|
1089
|
+
reason = failedEdits.reason;
|
|
557
1090
|
}
|
|
558
|
-
|
|
559
|
-
if (
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
1091
|
+
if (newContent === null) throw new Error(`Codegen produced no content for ${target.path}`);
|
|
1092
|
+
if (!isNewFile) {
|
|
1093
|
+
console.log(
|
|
1094
|
+
JSON.stringify({
|
|
1095
|
+
event: "codegen.edits_path",
|
|
1096
|
+
targetPath: target.path,
|
|
1097
|
+
path,
|
|
1098
|
+
...reason ? { reason } : {},
|
|
1099
|
+
...editCount !== void 0 ? { editCount } : {},
|
|
1100
|
+
...failedEdits && failedEdits.summary.matchCounts.length > 0 ? { matchCounts: failedEdits.summary.matchCounts } : {},
|
|
1101
|
+
lineEnding
|
|
1102
|
+
})
|
|
563
1103
|
);
|
|
564
1104
|
}
|
|
565
1105
|
if (!isNewFile) {
|
|
566
|
-
const suspectReason = looksLikeTruncatedRewrite(target, currentContent,
|
|
1106
|
+
const suspectReason = looksLikeTruncatedRewrite(target, currentContent, newContent);
|
|
567
1107
|
if (suspectReason) {
|
|
568
1108
|
throw new Error(
|
|
569
1109
|
`Codegen output for ${target.path} looks like a truncated/abbreviated rewrite (${suspectReason}) \u2014 refusing to apply it. Interim guard per \xA716.7; the AI Runtime's own output-cap check is not live yet.`
|
|
@@ -576,24 +1116,24 @@ async function runCodegen(gateway, targets, workspaceListing, requiredTests, den
|
|
|
576
1116
|
targetPath: target.path,
|
|
577
1117
|
kind: target.kind,
|
|
578
1118
|
inputLength: currentContent.length,
|
|
579
|
-
outputLength:
|
|
580
|
-
removedLines: isNewFile ? null : countRemovedLines(currentContent,
|
|
1119
|
+
outputLength: newContent.length,
|
|
1120
|
+
removedLines: isNewFile ? null : countRemovedLines(currentContent, newContent)
|
|
581
1121
|
})
|
|
582
1122
|
);
|
|
583
|
-
results.push({ targetPath: target.path, newContent
|
|
1123
|
+
results.push({ targetPath: target.path, newContent });
|
|
584
1124
|
}
|
|
585
1125
|
return results;
|
|
586
1126
|
}
|
|
587
1127
|
function flushCodegenResults(results) {
|
|
588
1128
|
for (const { targetPath, newContent } of results) {
|
|
589
|
-
(0, import_node_fs.mkdirSync)((0,
|
|
1129
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path2.dirname)(targetPath), { recursive: true });
|
|
590
1130
|
(0, import_node_fs.writeFileSync)(targetPath, newContent);
|
|
591
1131
|
}
|
|
592
1132
|
}
|
|
593
1133
|
|
|
594
1134
|
// src/dependencyCheck.ts
|
|
595
1135
|
var import_node_fs2 = require("node:fs");
|
|
596
|
-
var
|
|
1136
|
+
var import_node_path3 = require("node:path");
|
|
597
1137
|
var SCANNABLE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"];
|
|
598
1138
|
var IMPORT_RE = /(?:\bimport\b[^'"]*?\bfrom\s*|\bimport\s*\(\s*|\bexport\b[^'"]*?\bfrom\s*|\brequire\s*\(\s*|\bimport\s+)(['"])([^'"]+)\1/g;
|
|
599
1139
|
var NODE_BUILTINS = /* @__PURE__ */ new Set([
|
|
@@ -672,8 +1212,8 @@ function dependencyNames(pkg) {
|
|
|
672
1212
|
]);
|
|
673
1213
|
}
|
|
674
1214
|
function readDeclaredDependencies(results, workspaceRoot) {
|
|
675
|
-
const packageJsonPath = (0,
|
|
676
|
-
const generated = results.find((r) => (0,
|
|
1215
|
+
const packageJsonPath = (0, import_node_path3.join)((0, import_node_path3.resolve)(workspaceRoot ?? "."), "package.json");
|
|
1216
|
+
const generated = results.find((r) => (0, import_node_path3.resolve)(r.targetPath) === packageJsonPath);
|
|
677
1217
|
let raw;
|
|
678
1218
|
if (generated) {
|
|
679
1219
|
raw = generated.newContent;
|
|
@@ -743,10 +1283,10 @@ function stripJsonComments(text) {
|
|
|
743
1283
|
return out.replace(/,(\s*[}\]])/g, "$1");
|
|
744
1284
|
}
|
|
745
1285
|
function resolveAliasPrefixes(results, workspaceRoot) {
|
|
746
|
-
const root = (0,
|
|
1286
|
+
const root = (0, import_node_path3.resolve)(workspaceRoot ?? ".");
|
|
747
1287
|
for (const filename of ["tsconfig.json", "jsconfig.json"]) {
|
|
748
|
-
const configPath = (0,
|
|
749
|
-
const generated = results.find((r) => (0,
|
|
1288
|
+
const configPath = (0, import_node_path3.join)(root, filename);
|
|
1289
|
+
const generated = results.find((r) => (0, import_node_path3.resolve)(r.targetPath) === configPath);
|
|
750
1290
|
let raw;
|
|
751
1291
|
if (generated) {
|
|
752
1292
|
raw = generated.newContent;
|
|
@@ -953,7 +1493,7 @@ function writePhase0Marker(runId) {
|
|
|
953
1493
|
|
|
954
1494
|
// src/quiktest.ts
|
|
955
1495
|
var import_node_fs4 = require("node:fs");
|
|
956
|
-
var
|
|
1496
|
+
var import_node_path4 = require("node:path");
|
|
957
1497
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git"]);
|
|
958
1498
|
function walkForJUnitByName(dir) {
|
|
959
1499
|
let entries;
|
|
@@ -964,7 +1504,7 @@ function walkForJUnitByName(dir) {
|
|
|
964
1504
|
}
|
|
965
1505
|
for (const entry of entries) {
|
|
966
1506
|
if (SKIP_DIRS.has(entry)) continue;
|
|
967
|
-
const full = (0,
|
|
1507
|
+
const full = (0, import_node_path4.join)(dir, entry);
|
|
968
1508
|
let stat;
|
|
969
1509
|
try {
|
|
970
1510
|
stat = (0, import_node_fs4.statSync)(full);
|
|
@@ -988,10 +1528,18 @@ function firstXmlIn(dir, namePattern) {
|
|
|
988
1528
|
return null;
|
|
989
1529
|
}
|
|
990
1530
|
const match = entries.find((e) => namePattern.test(e));
|
|
991
|
-
return match ? (0,
|
|
1531
|
+
return match ? (0, import_node_path4.join)(dir, match) : null;
|
|
1532
|
+
}
|
|
1533
|
+
function readTestReportBytes(cwd = process.cwd()) {
|
|
1534
|
+
try {
|
|
1535
|
+
const report = findJUnitReport(cwd);
|
|
1536
|
+
return report ? (0, import_node_fs4.readFileSync)(report) : null;
|
|
1537
|
+
} catch {
|
|
1538
|
+
return null;
|
|
1539
|
+
}
|
|
992
1540
|
}
|
|
993
1541
|
function findJUnitReport(cwd = process.cwd()) {
|
|
994
|
-
return walkForJUnitByName(cwd) ?? firstXmlIn((0,
|
|
1542
|
+
return walkForJUnitByName(cwd) ?? firstXmlIn((0, import_node_path4.join)(cwd, "test-results"), /\.xml$/) ?? firstXmlIn((0, import_node_path4.join)(cwd, "reports"), /^junit.*\.xml$/) ?? firstXmlIn((0, import_node_path4.join)(cwd, "target", "surefire-reports"), /\.xml$/);
|
|
995
1543
|
}
|
|
996
1544
|
function buildCiUrl(serverUrl, repository, githubRunId) {
|
|
997
1545
|
return `${serverUrl}/${repository}/actions/runs/${githubRunId}`;
|
|
@@ -1015,8 +1563,8 @@ async function reportToQuikTest(params) {
|
|
|
1015
1563
|
try {
|
|
1016
1564
|
console.log(`reporting ${report}`);
|
|
1017
1565
|
const ciUrl = buildCiUrl(githubServerUrl, githubRepository, githubRunId);
|
|
1018
|
-
const { readFileSync:
|
|
1019
|
-
const body =
|
|
1566
|
+
const { readFileSync: readFileSync7 } = await import("node:fs");
|
|
1567
|
+
const body = readFileSync7(report);
|
|
1020
1568
|
let url = `${resultsUrl}?ciUrl=${encodeURIComponent(ciUrl)}&automationIdFormat=bracket`;
|
|
1021
1569
|
if (generatedFiles) {
|
|
1022
1570
|
url += `&generatedFiles=${encodeURIComponent(generatedFiles.join(","))}`;
|
|
@@ -1046,7 +1594,7 @@ async function reportToQuikTest(params) {
|
|
|
1046
1594
|
// src/baselineCheck.ts
|
|
1047
1595
|
var import_node_child_process3 = require("node:child_process");
|
|
1048
1596
|
var import_node_fs6 = require("node:fs");
|
|
1049
|
-
var
|
|
1597
|
+
var import_node_path5 = require("node:path");
|
|
1050
1598
|
|
|
1051
1599
|
// src/regressionCheck.ts
|
|
1052
1600
|
var import_node_fs5 = require("node:fs");
|
|
@@ -1106,9 +1654,11 @@ function parseJUnitOutcomes(xml) {
|
|
|
1106
1654
|
TESTCASE_RE.lastIndex = 0;
|
|
1107
1655
|
while (match = TESTCASE_RE.exec(xml)) {
|
|
1108
1656
|
const [, attrs, body = ""] = match;
|
|
1109
|
-
const
|
|
1110
|
-
if (!
|
|
1111
|
-
const
|
|
1657
|
+
const rawName = NAME_ATTR_RE.exec(attrs)?.[1];
|
|
1658
|
+
if (!rawName) continue;
|
|
1659
|
+
const name = unescapeXmlEntities(rawName);
|
|
1660
|
+
const rawClassname = CLASSNAME_ATTR_RE.exec(attrs)?.[1];
|
|
1661
|
+
const classname = rawClassname === void 0 ? void 0 : unescapeXmlEntities(rawClassname);
|
|
1112
1662
|
const id = classname ? `${classname}::${name}` : name;
|
|
1113
1663
|
const failed = FAILURE_CHILD_RE.test(body);
|
|
1114
1664
|
outcomes.set(id, !failed);
|
|
@@ -1230,16 +1780,16 @@ function checkableIds(ids, types) {
|
|
|
1230
1780
|
});
|
|
1231
1781
|
}
|
|
1232
1782
|
function insideCheckout(cwd, path) {
|
|
1233
|
-
const abs = (0,
|
|
1234
|
-
const rel = (0,
|
|
1235
|
-
return rel === "" || rel.startsWith("..") || (0,
|
|
1783
|
+
const abs = (0, import_node_path5.resolve)(cwd, path);
|
|
1784
|
+
const rel = (0, import_node_path5.relative)(cwd, abs);
|
|
1785
|
+
return rel === "" || rel.startsWith("..") || (0, import_node_path5.isAbsolute)(rel) ? null : abs;
|
|
1236
1786
|
}
|
|
1237
1787
|
function writeForCheck(abs, content) {
|
|
1238
1788
|
const original = (0, import_node_fs6.existsSync)(abs) ? (0, import_node_fs6.readFileSync)(abs) : null;
|
|
1239
1789
|
const createdDirs = [];
|
|
1240
1790
|
if (original === null) {
|
|
1241
1791
|
const missing = [];
|
|
1242
|
-
for (let d = (0,
|
|
1792
|
+
for (let d = (0, import_node_path5.dirname)(abs); !(0, import_node_fs6.existsSync)(d); d = (0, import_node_path5.dirname)(d)) missing.push(d);
|
|
1243
1793
|
for (const d of missing.reverse()) {
|
|
1244
1794
|
(0, import_node_fs6.mkdirSync)(d);
|
|
1245
1795
|
createdDirs.push(d);
|
|
@@ -1389,6 +1939,10 @@ async function runBaselineCheck(params) {
|
|
|
1389
1939
|
}
|
|
1390
1940
|
}
|
|
1391
1941
|
|
|
1942
|
+
// src/failureArtifact.ts
|
|
1943
|
+
var import_node_fs7 = require("node:fs");
|
|
1944
|
+
var import_node_path6 = require("node:path");
|
|
1945
|
+
|
|
1392
1946
|
// src/failureReason.ts
|
|
1393
1947
|
var MAX_FAILURE_REASON = 1e3;
|
|
1394
1948
|
var MIN_SECRET_LENGTH = 8;
|
|
@@ -1409,18 +1963,86 @@ function failureSummary(err) {
|
|
|
1409
1963
|
if (err instanceof RuntimeReplyError) return err.summary;
|
|
1410
1964
|
return err instanceof Error ? err.message : String(err);
|
|
1411
1965
|
}
|
|
1412
|
-
function
|
|
1966
|
+
function scrubSecrets(message, secrets = []) {
|
|
1413
1967
|
let text = message;
|
|
1414
1968
|
for (const secret of secrets) {
|
|
1415
1969
|
if (secret.length >= MIN_SECRET_LENGTH) text = text.split(secret).join("***");
|
|
1416
1970
|
}
|
|
1417
1971
|
for (const pattern of CREDENTIAL_PATTERNS) text = text.replace(pattern, "***");
|
|
1972
|
+
return text;
|
|
1973
|
+
}
|
|
1974
|
+
function describeFailureReason(message, secrets = []) {
|
|
1975
|
+
let text = scrubSecrets(message, secrets);
|
|
1418
1976
|
text = text.replace(/\s*\n\s*/g, " | ").trim();
|
|
1419
1977
|
return text.length > MAX_FAILURE_REASON ? `${text.slice(0, MAX_FAILURE_REASON)}\u2026` : text;
|
|
1420
1978
|
}
|
|
1421
1979
|
|
|
1980
|
+
// src/failureArtifact.ts
|
|
1981
|
+
var FAILURE_ARTIFACT_DIR = "quikpilot-failure";
|
|
1982
|
+
var REPORT_COPY = "final-test-report.xml";
|
|
1983
|
+
var MAX_REPORT_BYTES = 5 * 1024 * 1024;
|
|
1984
|
+
var MAX_FILES = 200;
|
|
1985
|
+
var RUNNER_VERSION = true ? "9.1.0" : "dev";
|
|
1986
|
+
function safeDestination(base, repoPath) {
|
|
1987
|
+
if (!repoPath || (0, import_node_path6.isAbsolute)(repoPath)) return null;
|
|
1988
|
+
const destination = (0, import_node_path6.resolve)(base, repoPath);
|
|
1989
|
+
const rel = (0, import_node_path6.relative)(base, destination);
|
|
1990
|
+
if (!rel || rel.startsWith("..") || (0, import_node_path6.isAbsolute)(rel)) return null;
|
|
1991
|
+
return destination;
|
|
1992
|
+
}
|
|
1993
|
+
function writeFailureArtifact(input) {
|
|
1994
|
+
if (input.results.length === 0) return { written: false };
|
|
1995
|
+
try {
|
|
1996
|
+
const base = (0, import_node_path6.resolve)(input.cwd, FAILURE_ARTIFACT_DIR);
|
|
1997
|
+
(0, import_node_fs7.rmSync)(base, { recursive: true, force: true });
|
|
1998
|
+
(0, import_node_fs7.mkdirSync)(base, { recursive: true });
|
|
1999
|
+
const files = [];
|
|
2000
|
+
const skipped = [];
|
|
2001
|
+
for (const result of input.results) {
|
|
2002
|
+
const destination = files.length < MAX_FILES ? safeDestination(base, result.targetPath) : null;
|
|
2003
|
+
if (!destination) {
|
|
2004
|
+
skipped.push(result.targetPath);
|
|
2005
|
+
continue;
|
|
2006
|
+
}
|
|
2007
|
+
(0, import_node_fs7.mkdirSync)((0, import_node_path6.dirname)(destination), { recursive: true });
|
|
2008
|
+
(0, import_node_fs7.writeFileSync)(destination, result.newContent);
|
|
2009
|
+
files.push({ path: result.targetPath, bytes: Buffer.byteLength(result.newContent) });
|
|
2010
|
+
}
|
|
2011
|
+
let testReport = "none";
|
|
2012
|
+
if (input.testReport) {
|
|
2013
|
+
if (input.testReport.byteLength > MAX_REPORT_BYTES) {
|
|
2014
|
+
testReport = "too-large";
|
|
2015
|
+
} else {
|
|
2016
|
+
(0, import_node_fs7.writeFileSync)((0, import_node_path6.join)(base, REPORT_COPY), input.testReport);
|
|
2017
|
+
testReport = "included";
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
const manifest = {
|
|
2021
|
+
runId: input.runId,
|
|
2022
|
+
issueKey: input.issueKey,
|
|
2023
|
+
runnerVersion: RUNNER_VERSION,
|
|
2024
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2025
|
+
targets: input.targets.map((t) => ({
|
|
2026
|
+
path: t.path,
|
|
2027
|
+
kind: t.kind,
|
|
2028
|
+
...t.automationIds && t.automationIds.length > 0 ? { automationIds: t.automationIds } : {}
|
|
2029
|
+
})),
|
|
2030
|
+
files,
|
|
2031
|
+
skipped,
|
|
2032
|
+
testReport,
|
|
2033
|
+
failureReasons: input.failureReasons
|
|
2034
|
+
};
|
|
2035
|
+
const text = JSON.stringify(manifest, (_key, value) => typeof value === "string" ? scrubSecrets(value, input.secrets) : value, 2);
|
|
2036
|
+
(0, import_node_fs7.writeFileSync)((0, import_node_path6.join)(base, "manifest.json"), text);
|
|
2037
|
+
return { written: (0, import_node_fs7.existsSync)((0, import_node_path6.join)(base, "manifest.json")) };
|
|
2038
|
+
} catch (err) {
|
|
2039
|
+
console.warn(`::warning::Could not keep the failed run's files: ${err instanceof Error ? err.message : String(err)}`);
|
|
2040
|
+
return { written: false };
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
|
|
1422
2044
|
// src/scopeCheck.ts
|
|
1423
|
-
var
|
|
2045
|
+
var import_node_fs8 = require("node:fs");
|
|
1424
2046
|
var ROUTE_METHOD_RE = /\b(GET|POST|PUT|PATCH|DELETE)\b/i;
|
|
1425
2047
|
var ROUTE_PATH_RE = /\/[A-Za-z0-9_\-{}[\]]+(?:\/[A-Za-z0-9_\-:{}[\]]+)+/;
|
|
1426
2048
|
var ROUTE_EXPORT_RE = /\bexport\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE)\s*\(/;
|
|
@@ -1477,7 +2099,7 @@ function checkApiScope(results, targets, apiChanges) {
|
|
|
1477
2099
|
} else {
|
|
1478
2100
|
let currentContent;
|
|
1479
2101
|
try {
|
|
1480
|
-
currentContent = (0,
|
|
2102
|
+
currentContent = (0, import_node_fs8.readFileSync)(targetPath, "utf8");
|
|
1481
2103
|
} catch {
|
|
1482
2104
|
currentContent = "";
|
|
1483
2105
|
}
|
|
@@ -1496,7 +2118,7 @@ function describeScopeViolations(check) {
|
|
|
1496
2118
|
}
|
|
1497
2119
|
|
|
1498
2120
|
// src/targeting.ts
|
|
1499
|
-
var
|
|
2121
|
+
var import_node_path7 = require("node:path");
|
|
1500
2122
|
var CONTRACT_VERSION2 = "test-targets-1";
|
|
1501
2123
|
function listWorkspaceFiles(workspaceRoot) {
|
|
1502
2124
|
return listFiles(workspaceRoot ?? ".");
|
|
@@ -1525,9 +2147,9 @@ function validateTarget(target, workspaceRoot, workspaceListing, deniedPaths) {
|
|
|
1525
2147
|
}
|
|
1526
2148
|
return;
|
|
1527
2149
|
}
|
|
1528
|
-
const root = (0,
|
|
1529
|
-
const rel = (0,
|
|
1530
|
-
if (rel.startsWith("..") || (0,
|
|
2150
|
+
const root = (0, import_node_path7.resolve)(workspaceRoot ?? ".");
|
|
2151
|
+
const rel = (0, import_node_path7.relative)(root, (0, import_node_path7.resolve)(target.path));
|
|
2152
|
+
if (rel.startsWith("..") || (0, import_node_path7.isAbsolute)(rel)) {
|
|
1531
2153
|
throw new Error(
|
|
1532
2154
|
`pilot.targeting named a NEW_FILE path that escapes the workspace root: ${target.path}`
|
|
1533
2155
|
);
|
|
@@ -1582,10 +2204,19 @@ async function runTargeting(gateway, design, workspaceRoot, workspaceListing, re
|
|
|
1582
2204
|
for (const target of body.targets) {
|
|
1583
2205
|
validateTarget(target, workspaceRoot, listing, deniedPaths);
|
|
1584
2206
|
}
|
|
2207
|
+
const correctedKinds = [];
|
|
2208
|
+
const targets = body.targets.map((target) => {
|
|
2209
|
+
if (target.kind === "NEW_FILE" && listing.has(target.path)) {
|
|
2210
|
+
correctedKinds.push(target.path);
|
|
2211
|
+
return { ...target, kind: "EXISTING" };
|
|
2212
|
+
}
|
|
2213
|
+
return target;
|
|
2214
|
+
});
|
|
1585
2215
|
return {
|
|
1586
2216
|
outcome: "TARGETED",
|
|
1587
|
-
targets
|
|
1588
|
-
skippedCases: body.skippedCases ?? []
|
|
2217
|
+
targets,
|
|
2218
|
+
skippedCases: body.skippedCases ?? [],
|
|
2219
|
+
...correctedKinds.length > 0 ? { correctedKinds } : {}
|
|
1589
2220
|
};
|
|
1590
2221
|
}
|
|
1591
2222
|
|
|
@@ -1748,6 +2379,19 @@ async function exchangeForRunToken(tokenUrl, oidcToken) {
|
|
|
1748
2379
|
return body.data.runToken;
|
|
1749
2380
|
}
|
|
1750
2381
|
|
|
2382
|
+
// src/vanishedTests.ts
|
|
2383
|
+
var MAX_VANISHED_NAMES = 50;
|
|
2384
|
+
function findVanishedTests(baseline, after) {
|
|
2385
|
+
if (!baseline.results || !after.results) return null;
|
|
2386
|
+
const missing = [...baseline.results.keys()].filter((id) => !after.results.has(id));
|
|
2387
|
+
if (missing.length === 0) return null;
|
|
2388
|
+
return { names: missing.slice(0, MAX_VANISHED_NAMES), total: missing.length };
|
|
2389
|
+
}
|
|
2390
|
+
function describeVanishedTests(vanished) {
|
|
2391
|
+
const noun = vanished.total === 1 ? "1 test" : `${vanished.total} tests`;
|
|
2392
|
+
return `${noun} that ran before this change ${vanished.total === 1 ? "is" : "are"} missing from the final test report; the pull request lists ${vanished.total === 1 ? "it" : "them"}.`;
|
|
2393
|
+
}
|
|
2394
|
+
|
|
1751
2395
|
// src/run.ts
|
|
1752
2396
|
async function run(env2) {
|
|
1753
2397
|
let hardFailure = false;
|
|
@@ -1764,8 +2408,11 @@ async function run(env2) {
|
|
|
1764
2408
|
};
|
|
1765
2409
|
let targeted = false;
|
|
1766
2410
|
let generatedFilePaths = [];
|
|
2411
|
+
let generatedResults = [];
|
|
2412
|
+
let finalTestReport = null;
|
|
1767
2413
|
let targetingResult;
|
|
1768
2414
|
let baselineFindings;
|
|
2415
|
+
let vanishedTests;
|
|
1769
2416
|
const contextLog = [];
|
|
1770
2417
|
try {
|
|
1771
2418
|
runToken = await exchangeForRunToken(env2.tokenUrl, await fetchGithubOidcToken());
|
|
@@ -1784,6 +2431,14 @@ async function run(env2) {
|
|
|
1784
2431
|
outOfScope
|
|
1785
2432
|
);
|
|
1786
2433
|
targetingResult = targeting;
|
|
2434
|
+
if (targeting.outcome === "TARGETED") {
|
|
2435
|
+
for (const path of targeting.correctedKinds ?? []) {
|
|
2436
|
+
const w = `QuikPilot named ${path} as a new file, but it already exists in the repository. It was treated as an existing file so its current content was kept in view.`;
|
|
2437
|
+
warnings.push(w);
|
|
2438
|
+
console.warn(`QuikPilot targeting warning (not blocking): ${w}`);
|
|
2439
|
+
console.log(JSON.stringify({ event: "targeting.kind_corrected", path }));
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
1787
2442
|
if (targeting.outcome === "NO_TARGET") {
|
|
1788
2443
|
writePhase0Marker(env2.runId);
|
|
1789
2444
|
runCommand(packet.commands.setup, "Setup");
|
|
@@ -1833,6 +2488,7 @@ async function run(env2) {
|
|
|
1833
2488
|
outOfScope
|
|
1834
2489
|
)
|
|
1835
2490
|
]);
|
|
2491
|
+
generatedResults = results;
|
|
1836
2492
|
const depCheck = checkImportDependencies(
|
|
1837
2493
|
results,
|
|
1838
2494
|
readDeclaredDependencies(results, packet.workspaceRoot),
|
|
@@ -1878,6 +2534,13 @@ async function run(env2) {
|
|
|
1878
2534
|
}
|
|
1879
2535
|
runCommand(packet.commands.lint, "Lint");
|
|
1880
2536
|
const after = captureTestSnapshot(packet.commands.test);
|
|
2537
|
+
finalTestReport = readTestReportBytes();
|
|
2538
|
+
vanishedTests = findVanishedTests(baseline, after) ?? void 0;
|
|
2539
|
+
if (vanishedTests) {
|
|
2540
|
+
const w = describeVanishedTests(vanishedTests);
|
|
2541
|
+
warnings.push(w);
|
|
2542
|
+
console.warn(`QuikPilot test warning (not blocking): ${w}`);
|
|
2543
|
+
}
|
|
1881
2544
|
const check = checkForRegressions(baseline, after);
|
|
1882
2545
|
if (check.blocked) {
|
|
1883
2546
|
throw new Error(
|
|
@@ -1933,6 +2596,22 @@ async function run(env2) {
|
|
|
1933
2596
|
recordFailure(err);
|
|
1934
2597
|
}
|
|
1935
2598
|
}
|
|
2599
|
+
if (hardFailure && generatedResults.length > 0) {
|
|
2600
|
+
try {
|
|
2601
|
+
writeFailureArtifact({
|
|
2602
|
+
cwd: process.cwd(),
|
|
2603
|
+
runId: env2.runId,
|
|
2604
|
+
issueKey: packet?.issueKey ?? null,
|
|
2605
|
+
targets: targetingResult?.outcome === "TARGETED" ? targetingResult.targets : [],
|
|
2606
|
+
results: generatedResults,
|
|
2607
|
+
testReport: finalTestReport,
|
|
2608
|
+
failureReasons,
|
|
2609
|
+
secrets: runToken ? [runToken] : []
|
|
2610
|
+
});
|
|
2611
|
+
} catch (err) {
|
|
2612
|
+
console.warn(`::warning::Could not keep the failed run's files: ${err instanceof Error ? err.message : String(err)}`);
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
1936
2615
|
await reportBack({
|
|
1937
2616
|
callbackUrl: env2.callbackUrl,
|
|
1938
2617
|
runToken,
|
|
@@ -1942,6 +2621,7 @@ async function run(env2) {
|
|
|
1942
2621
|
// Omitted when targeting itself never produced a result (nothing exists to record).
|
|
1943
2622
|
targeting: targetingResult ? buildTargetingRecord(targetingResult, contextLog) : void 0,
|
|
1944
2623
|
baselineFindings,
|
|
2624
|
+
vanishedTests,
|
|
1945
2625
|
unresolved: failureReasons.length > 0 ? failureReasons : void 0
|
|
1946
2626
|
});
|
|
1947
2627
|
return { hardFailure };
|