@quikit/quikpilot-runner 9.0.0 → 9.2.0-rc.2
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 +27 -0
- package/dist/index.js +1173 -56
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -230,7 +230,7 @@ function commitAndPush(branch, runId, installationToken, targeted, issueKey, iss
|
|
|
230
230
|
|
|
231
231
|
// src/codegen.ts
|
|
232
232
|
var import_node_fs = require("node:fs");
|
|
233
|
-
var
|
|
233
|
+
var import_node_path2 = require("node:path");
|
|
234
234
|
|
|
235
235
|
// src/deniedPaths.ts
|
|
236
236
|
function globToRegExp(pattern) {
|
|
@@ -382,6 +382,315 @@ function unwrapExecute(envelope, useCase) {
|
|
|
382
382
|
return envelope.structuredJson;
|
|
383
383
|
}
|
|
384
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 relative5 = clean === "." || clean === ".." || clean.startsWith("./") || clean.startsWith("../");
|
|
628
|
+
if (!relative5) 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
|
+
|
|
385
694
|
// src/retryFetch.ts
|
|
386
695
|
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([502, 503, 504]);
|
|
387
696
|
var RETRYABLE_NETWORK_ERROR_CODES = /* @__PURE__ */ new Set(["ECONNRESET", "ETIMEDOUT"]);
|
|
@@ -604,16 +913,38 @@ async function runCodegen(gateway, targets, workspaceListing, requiredTests, den
|
|
|
604
913
|
}
|
|
605
914
|
}
|
|
606
915
|
const namedContents = contextPaths.map((path2) => ({ path: path2, content: (0, import_node_fs.readFileSync)(path2, "utf8") }));
|
|
607
|
-
const
|
|
608
|
-
const
|
|
609
|
-
for (const s of added.skipped) {
|
|
916
|
+
const runFiles = collectRunContext(target, targets, new Set(contextPaths), deniedPaths);
|
|
917
|
+
for (const s of runFiles.skipped) {
|
|
610
918
|
warnings.push(`not shown to the test for ${target.path}: ${s.path} (${s.reason})`);
|
|
611
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];
|
|
612
941
|
const contextRecord = {
|
|
613
942
|
targetPath: target.path,
|
|
614
943
|
contextPaths,
|
|
615
944
|
addedPaths: added.entries.map((e) => e.path),
|
|
616
|
-
skipped: added.skipped
|
|
945
|
+
skipped: added.skipped,
|
|
946
|
+
...imports.importedPaths.length > 0 ? { importedPaths: imports.importedPaths } : {},
|
|
947
|
+
...imports.aliasImports > 0 ? { aliasImports: imports.aliasImports } : {}
|
|
617
948
|
};
|
|
618
949
|
console.log(JSON.stringify({ event: "codegen.context", ...contextRecord }));
|
|
619
950
|
contextLog?.push(contextRecord);
|
|
@@ -679,6 +1010,7 @@ async function runCodegen(gateway, targets, workspaceListing, requiredTests, den
|
|
|
679
1010
|
contextData.contextContents = namedContents;
|
|
680
1011
|
warnings.push(`context for ${target.path} was too large with the run's other files; retried without them`);
|
|
681
1012
|
contextRecord.addedPaths = [];
|
|
1013
|
+
delete contextRecord.importedPaths;
|
|
682
1014
|
contextRecord.retriedWithoutAdded = true;
|
|
683
1015
|
res = await callCodegen();
|
|
684
1016
|
if (!res.ok) {
|
|
@@ -794,14 +1126,14 @@ async function runCodegen(gateway, targets, workspaceListing, requiredTests, den
|
|
|
794
1126
|
}
|
|
795
1127
|
function flushCodegenResults(results) {
|
|
796
1128
|
for (const { targetPath, newContent } of results) {
|
|
797
|
-
(0, import_node_fs.mkdirSync)((0,
|
|
1129
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path2.dirname)(targetPath), { recursive: true });
|
|
798
1130
|
(0, import_node_fs.writeFileSync)(targetPath, newContent);
|
|
799
1131
|
}
|
|
800
1132
|
}
|
|
801
1133
|
|
|
802
1134
|
// src/dependencyCheck.ts
|
|
803
1135
|
var import_node_fs2 = require("node:fs");
|
|
804
|
-
var
|
|
1136
|
+
var import_node_path3 = require("node:path");
|
|
805
1137
|
var SCANNABLE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"];
|
|
806
1138
|
var IMPORT_RE = /(?:\bimport\b[^'"]*?\bfrom\s*|\bimport\s*\(\s*|\bexport\b[^'"]*?\bfrom\s*|\brequire\s*\(\s*|\bimport\s+)(['"])([^'"]+)\1/g;
|
|
807
1139
|
var NODE_BUILTINS = /* @__PURE__ */ new Set([
|
|
@@ -880,8 +1212,8 @@ function dependencyNames(pkg) {
|
|
|
880
1212
|
]);
|
|
881
1213
|
}
|
|
882
1214
|
function readDeclaredDependencies(results, workspaceRoot) {
|
|
883
|
-
const packageJsonPath = (0,
|
|
884
|
-
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);
|
|
885
1217
|
let raw;
|
|
886
1218
|
if (generated) {
|
|
887
1219
|
raw = generated.newContent;
|
|
@@ -951,10 +1283,10 @@ function stripJsonComments(text) {
|
|
|
951
1283
|
return out.replace(/,(\s*[}\]])/g, "$1");
|
|
952
1284
|
}
|
|
953
1285
|
function resolveAliasPrefixes(results, workspaceRoot) {
|
|
954
|
-
const root = (0,
|
|
1286
|
+
const root = (0, import_node_path3.resolve)(workspaceRoot ?? ".");
|
|
955
1287
|
for (const filename of ["tsconfig.json", "jsconfig.json"]) {
|
|
956
|
-
const configPath = (0,
|
|
957
|
-
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);
|
|
958
1290
|
let raw;
|
|
959
1291
|
if (generated) {
|
|
960
1292
|
raw = generated.newContent;
|
|
@@ -1161,7 +1493,7 @@ function writePhase0Marker(runId) {
|
|
|
1161
1493
|
|
|
1162
1494
|
// src/quiktest.ts
|
|
1163
1495
|
var import_node_fs4 = require("node:fs");
|
|
1164
|
-
var
|
|
1496
|
+
var import_node_path4 = require("node:path");
|
|
1165
1497
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git"]);
|
|
1166
1498
|
function walkForJUnitByName(dir) {
|
|
1167
1499
|
let entries;
|
|
@@ -1172,7 +1504,7 @@ function walkForJUnitByName(dir) {
|
|
|
1172
1504
|
}
|
|
1173
1505
|
for (const entry of entries) {
|
|
1174
1506
|
if (SKIP_DIRS.has(entry)) continue;
|
|
1175
|
-
const full = (0,
|
|
1507
|
+
const full = (0, import_node_path4.join)(dir, entry);
|
|
1176
1508
|
let stat;
|
|
1177
1509
|
try {
|
|
1178
1510
|
stat = (0, import_node_fs4.statSync)(full);
|
|
@@ -1196,7 +1528,7 @@ function firstXmlIn(dir, namePattern) {
|
|
|
1196
1528
|
return null;
|
|
1197
1529
|
}
|
|
1198
1530
|
const match = entries.find((e) => namePattern.test(e));
|
|
1199
|
-
return match ? (0,
|
|
1531
|
+
return match ? (0, import_node_path4.join)(dir, match) : null;
|
|
1200
1532
|
}
|
|
1201
1533
|
function readTestReportBytes(cwd = process.cwd()) {
|
|
1202
1534
|
try {
|
|
@@ -1207,7 +1539,7 @@ function readTestReportBytes(cwd = process.cwd()) {
|
|
|
1207
1539
|
}
|
|
1208
1540
|
}
|
|
1209
1541
|
function findJUnitReport(cwd = process.cwd()) {
|
|
1210
|
-
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$/);
|
|
1211
1543
|
}
|
|
1212
1544
|
function buildCiUrl(serverUrl, repository, githubRunId) {
|
|
1213
1545
|
return `${serverUrl}/${repository}/actions/runs/${githubRunId}`;
|
|
@@ -1231,8 +1563,8 @@ async function reportToQuikTest(params) {
|
|
|
1231
1563
|
try {
|
|
1232
1564
|
console.log(`reporting ${report}`);
|
|
1233
1565
|
const ciUrl = buildCiUrl(githubServerUrl, githubRepository, githubRunId);
|
|
1234
|
-
const { readFileSync:
|
|
1235
|
-
const body =
|
|
1566
|
+
const { readFileSync: readFileSync8 } = await import("node:fs");
|
|
1567
|
+
const body = readFileSync8(report);
|
|
1236
1568
|
let url = `${resultsUrl}?ciUrl=${encodeURIComponent(ciUrl)}&automationIdFormat=bracket`;
|
|
1237
1569
|
if (generatedFiles) {
|
|
1238
1570
|
url += `&generatedFiles=${encodeURIComponent(generatedFiles.join(","))}`;
|
|
@@ -1262,7 +1594,7 @@ async function reportToQuikTest(params) {
|
|
|
1262
1594
|
// src/baselineCheck.ts
|
|
1263
1595
|
var import_node_child_process3 = require("node:child_process");
|
|
1264
1596
|
var import_node_fs6 = require("node:fs");
|
|
1265
|
-
var
|
|
1597
|
+
var import_node_path5 = require("node:path");
|
|
1266
1598
|
|
|
1267
1599
|
// src/regressionCheck.ts
|
|
1268
1600
|
var import_node_fs5 = require("node:fs");
|
|
@@ -1448,16 +1780,16 @@ function checkableIds(ids, types) {
|
|
|
1448
1780
|
});
|
|
1449
1781
|
}
|
|
1450
1782
|
function insideCheckout(cwd, path) {
|
|
1451
|
-
const abs = (0,
|
|
1452
|
-
const rel = (0,
|
|
1453
|
-
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;
|
|
1454
1786
|
}
|
|
1455
1787
|
function writeForCheck(abs, content) {
|
|
1456
1788
|
const original = (0, import_node_fs6.existsSync)(abs) ? (0, import_node_fs6.readFileSync)(abs) : null;
|
|
1457
1789
|
const createdDirs = [];
|
|
1458
1790
|
if (original === null) {
|
|
1459
1791
|
const missing = [];
|
|
1460
|
-
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);
|
|
1461
1793
|
for (const d of missing.reverse()) {
|
|
1462
1794
|
(0, import_node_fs6.mkdirSync)(d);
|
|
1463
1795
|
createdDirs.push(d);
|
|
@@ -1609,7 +1941,7 @@ async function runBaselineCheck(params) {
|
|
|
1609
1941
|
|
|
1610
1942
|
// src/failureArtifact.ts
|
|
1611
1943
|
var import_node_fs7 = require("node:fs");
|
|
1612
|
-
var
|
|
1944
|
+
var import_node_path6 = require("node:path");
|
|
1613
1945
|
|
|
1614
1946
|
// src/failureReason.ts
|
|
1615
1947
|
var MAX_FAILURE_REASON = 1e3;
|
|
@@ -1650,18 +1982,18 @@ var FAILURE_ARTIFACT_DIR = "quikpilot-failure";
|
|
|
1650
1982
|
var REPORT_COPY = "final-test-report.xml";
|
|
1651
1983
|
var MAX_REPORT_BYTES = 5 * 1024 * 1024;
|
|
1652
1984
|
var MAX_FILES = 200;
|
|
1653
|
-
var RUNNER_VERSION = true ? "9.0.
|
|
1985
|
+
var RUNNER_VERSION = true ? "9.2.0-rc.2" : "dev";
|
|
1654
1986
|
function safeDestination(base, repoPath) {
|
|
1655
|
-
if (!repoPath || (0,
|
|
1656
|
-
const destination = (0,
|
|
1657
|
-
const rel = (0,
|
|
1658
|
-
if (!rel || rel.startsWith("..") || (0,
|
|
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;
|
|
1659
1991
|
return destination;
|
|
1660
1992
|
}
|
|
1661
1993
|
function writeFailureArtifact(input) {
|
|
1662
1994
|
if (input.results.length === 0) return { written: false };
|
|
1663
1995
|
try {
|
|
1664
|
-
const base = (0,
|
|
1996
|
+
const base = (0, import_node_path6.resolve)(input.cwd, FAILURE_ARTIFACT_DIR);
|
|
1665
1997
|
(0, import_node_fs7.rmSync)(base, { recursive: true, force: true });
|
|
1666
1998
|
(0, import_node_fs7.mkdirSync)(base, { recursive: true });
|
|
1667
1999
|
const files = [];
|
|
@@ -1672,7 +2004,7 @@ function writeFailureArtifact(input) {
|
|
|
1672
2004
|
skipped.push(result.targetPath);
|
|
1673
2005
|
continue;
|
|
1674
2006
|
}
|
|
1675
|
-
(0, import_node_fs7.mkdirSync)((0,
|
|
2007
|
+
(0, import_node_fs7.mkdirSync)((0, import_node_path6.dirname)(destination), { recursive: true });
|
|
1676
2008
|
(0, import_node_fs7.writeFileSync)(destination, result.newContent);
|
|
1677
2009
|
files.push({ path: result.targetPath, bytes: Buffer.byteLength(result.newContent) });
|
|
1678
2010
|
}
|
|
@@ -1681,7 +2013,7 @@ function writeFailureArtifact(input) {
|
|
|
1681
2013
|
if (input.testReport.byteLength > MAX_REPORT_BYTES) {
|
|
1682
2014
|
testReport = "too-large";
|
|
1683
2015
|
} else {
|
|
1684
|
-
(0, import_node_fs7.writeFileSync)((0,
|
|
2016
|
+
(0, import_node_fs7.writeFileSync)((0, import_node_path6.join)(base, REPORT_COPY), input.testReport);
|
|
1685
2017
|
testReport = "included";
|
|
1686
2018
|
}
|
|
1687
2019
|
}
|
|
@@ -1701,8 +2033,8 @@ function writeFailureArtifact(input) {
|
|
|
1701
2033
|
failureReasons: input.failureReasons
|
|
1702
2034
|
};
|
|
1703
2035
|
const text = JSON.stringify(manifest, (_key, value) => typeof value === "string" ? scrubSecrets(value, input.secrets) : value, 2);
|
|
1704
|
-
(0, import_node_fs7.writeFileSync)((0,
|
|
1705
|
-
return { written: (0, import_node_fs7.existsSync)((0,
|
|
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")) };
|
|
1706
2038
|
} catch (err) {
|
|
1707
2039
|
console.warn(`::warning::Could not keep the failed run's files: ${err instanceof Error ? err.message : String(err)}`);
|
|
1708
2040
|
return { written: false };
|
|
@@ -1786,7 +2118,685 @@ function describeScopeViolations(check) {
|
|
|
1786
2118
|
}
|
|
1787
2119
|
|
|
1788
2120
|
// src/targeting.ts
|
|
1789
|
-
var
|
|
2121
|
+
var import_node_path9 = require("node:path");
|
|
2122
|
+
|
|
2123
|
+
// src/targetingErrors.ts
|
|
2124
|
+
var DeniedPathError = class extends Error {
|
|
2125
|
+
constructor(message) {
|
|
2126
|
+
super(message);
|
|
2127
|
+
this.name = "DeniedPathError";
|
|
2128
|
+
}
|
|
2129
|
+
};
|
|
2130
|
+
var TargetingHttpError = class extends Error {
|
|
2131
|
+
status;
|
|
2132
|
+
constructor(status, statusText) {
|
|
2133
|
+
super(`Targeting call failed: ${status} ${statusText}`);
|
|
2134
|
+
this.name = "TargetingHttpError";
|
|
2135
|
+
this.status = status;
|
|
2136
|
+
}
|
|
2137
|
+
};
|
|
2138
|
+
|
|
2139
|
+
// src/targetingMode.ts
|
|
2140
|
+
var TARGETING_MODE_ENV = "QUIKPILOT_TARGETING_MODE";
|
|
2141
|
+
function parse(value) {
|
|
2142
|
+
if (typeof value !== "string") return null;
|
|
2143
|
+
const v = value.trim().toLowerCase();
|
|
2144
|
+
if (v === "loop") return "loop";
|
|
2145
|
+
if (v === "single") return "single";
|
|
2146
|
+
return null;
|
|
2147
|
+
}
|
|
2148
|
+
function resolveTargetingMode(env2 = process.env, packetSetting) {
|
|
2149
|
+
const fromSetting = parse(packetSetting);
|
|
2150
|
+
if (fromSetting) return fromSetting;
|
|
2151
|
+
return parse(env2[TARGETING_MODE_ENV]) ?? "single";
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
// src/targetingPaths.ts
|
|
2155
|
+
var import_node_path7 = require("node:path");
|
|
2156
|
+
var MAX_PATH_CHARS = 300;
|
|
2157
|
+
function hasControlChar(s) {
|
|
2158
|
+
for (let i = 0; i < s.length; i++) {
|
|
2159
|
+
const c = s.charCodeAt(i);
|
|
2160
|
+
if (c < 32 || c === 127) return true;
|
|
2161
|
+
}
|
|
2162
|
+
return false;
|
|
2163
|
+
}
|
|
2164
|
+
function normaliseToolPath(raw, options = {}) {
|
|
2165
|
+
const allowRoot = options.allowRoot === true;
|
|
2166
|
+
if ((raw === void 0 || raw === null || raw === "") && allowRoot) return { ok: true, path: "" };
|
|
2167
|
+
if (typeof raw !== "string" || raw.length === 0 || raw.length > MAX_PATH_CHARS) {
|
|
2168
|
+
return { ok: false, reason: "path_invalid" };
|
|
2169
|
+
}
|
|
2170
|
+
if (hasControlChar(raw)) return { ok: false, reason: "path_invalid" };
|
|
2171
|
+
if (raw.includes("\\")) return { ok: false, reason: "path_invalid" };
|
|
2172
|
+
if (raw.startsWith("/") || /^[A-Za-z]:/.test(raw)) return { ok: false, reason: "path_invalid" };
|
|
2173
|
+
if (raw.split("/").includes("..")) return { ok: false, reason: "outside_workspace" };
|
|
2174
|
+
const normalised = import_node_path7.posix.normalize(raw).replace(/\/+$/, "");
|
|
2175
|
+
if (normalised === "" || normalised === ".") {
|
|
2176
|
+
return allowRoot ? { ok: true, path: "" } : { ok: false, reason: "path_invalid" };
|
|
2177
|
+
}
|
|
2178
|
+
return { ok: true, path: normalised };
|
|
2179
|
+
}
|
|
2180
|
+
function matchesPrefix(path, prefix) {
|
|
2181
|
+
if (prefix === "") return true;
|
|
2182
|
+
return path === prefix || path.startsWith(`${prefix}/`);
|
|
2183
|
+
}
|
|
2184
|
+
function buildVisibleListing(listing, deniedPaths) {
|
|
2185
|
+
return listing.filter((p) => !matchesDeniedPath(p, deniedPaths)).sort();
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
// src/targetingTools.ts
|
|
2189
|
+
var import_node_fs9 = require("node:fs");
|
|
2190
|
+
var import_node_path8 = require("node:path");
|
|
2191
|
+
var DEFAULT_LIMITS = {
|
|
2192
|
+
maxTurns: 12,
|
|
2193
|
+
maxToolCalls: 25,
|
|
2194
|
+
maxCallsPerTurn: 8,
|
|
2195
|
+
readCharsPerRead: 2e4,
|
|
2196
|
+
// The per-run read cap stops the COST; the per-turn ceiling below stops the FAILURE. Different jobs.
|
|
2197
|
+
readCharsTotal: 6e4,
|
|
2198
|
+
resultCharsTotal: 8e4,
|
|
2199
|
+
// 131,072 (the smallest 128k-class window) - 8,192 reserved output - a 3,741-token real request leaves
|
|
2200
|
+
// ~116,000; 90,000 keeps ~26,000 for a repository whose listing is thousands of paths, and stays well
|
|
2201
|
+
// clear of the per-minute quota, which binds before the window does. Set by the AI Runtime owner.
|
|
2202
|
+
maxRequestTokens: 9e4,
|
|
2203
|
+
grepMaxMatches: 40,
|
|
2204
|
+
grepLineChars: 200,
|
|
2205
|
+
grepFileBytes: 262144,
|
|
2206
|
+
grepMaxFiles: 3e3,
|
|
2207
|
+
grepMaxBytes: 2e7,
|
|
2208
|
+
fileBytesMax: 1e6,
|
|
2209
|
+
listMaxEntries: 500,
|
|
2210
|
+
maxReasoningChars: 500,
|
|
2211
|
+
maxPatternChars: 200
|
|
2212
|
+
};
|
|
2213
|
+
function createNodeToolFs(rootDir = process.cwd()) {
|
|
2214
|
+
const rootReal = (0, import_node_fs9.realpathSync)(rootDir);
|
|
2215
|
+
return {
|
|
2216
|
+
rootReal,
|
|
2217
|
+
lstat(rel) {
|
|
2218
|
+
const s = (0, import_node_fs9.lstatSync)((0, import_node_path8.join)(rootDir, rel));
|
|
2219
|
+
return { isSymlink: s.isSymbolicLink(), isFile: s.isFile(), size: s.size };
|
|
2220
|
+
},
|
|
2221
|
+
realpathInside(rel) {
|
|
2222
|
+
const real = (0, import_node_fs9.realpathSync)((0, import_node_path8.join)(rootDir, rel));
|
|
2223
|
+
const r = (0, import_node_path8.relative)(rootReal, real);
|
|
2224
|
+
return r !== "" && !r.startsWith("..") && !(0, import_node_path8.isAbsolute)(r);
|
|
2225
|
+
},
|
|
2226
|
+
readBytes(rel) {
|
|
2227
|
+
return (0, import_node_fs9.readFileSync)((0, import_node_path8.join)(rootDir, rel));
|
|
2228
|
+
}
|
|
2229
|
+
};
|
|
2230
|
+
}
|
|
2231
|
+
function newLedger() {
|
|
2232
|
+
return { toolCalls: 0, readChars: 0, resultChars: 0, refused: 0, seen: /* @__PURE__ */ new Map() };
|
|
2233
|
+
}
|
|
2234
|
+
var INVALID_PATH = "(invalid path)";
|
|
2235
|
+
var CUT = "\u2026[line cut]";
|
|
2236
|
+
function isPlainObject(x) {
|
|
2237
|
+
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
2238
|
+
}
|
|
2239
|
+
function bad(message) {
|
|
2240
|
+
return { fail: { status: "bad_args", message } };
|
|
2241
|
+
}
|
|
2242
|
+
function optionalInt(v) {
|
|
2243
|
+
if (v === void 0 || v === null) return void 0;
|
|
2244
|
+
return typeof v === "number" && Number.isInteger(v) ? v : "bad";
|
|
2245
|
+
}
|
|
2246
|
+
function hasNul(bytes) {
|
|
2247
|
+
const n = Math.min(bytes.length, 8e3);
|
|
2248
|
+
for (let i = 0; i < n; i++) if (bytes[i] === 0) return true;
|
|
2249
|
+
return false;
|
|
2250
|
+
}
|
|
2251
|
+
function gatePath(raw, env2) {
|
|
2252
|
+
const n = normaliseToolPath(raw);
|
|
2253
|
+
if (!n.ok) {
|
|
2254
|
+
return { ok: false, path: INVALID_PATH, fail: { status: "refused", reason: n.reason, message: "That path is not allowed." } };
|
|
2255
|
+
}
|
|
2256
|
+
if (matchesDeniedPath(n.path, env2.deniedPaths)) {
|
|
2257
|
+
return { ok: false, path: n.path, fail: { status: "refused", reason: "denied", message: "That path is protected and cannot be read." } };
|
|
2258
|
+
}
|
|
2259
|
+
if (!env2.listing.has(n.path)) {
|
|
2260
|
+
return { ok: false, path: n.path, fail: { status: "not_found", message: "Not a tracked file. Use list_files to see the available paths." } };
|
|
2261
|
+
}
|
|
2262
|
+
return { ok: true, path: n.path };
|
|
2263
|
+
}
|
|
2264
|
+
function prepareReadFile(args, env2, ledger) {
|
|
2265
|
+
const start = optionalInt(args.startLine);
|
|
2266
|
+
const end = optionalInt(args.endLine);
|
|
2267
|
+
if (start === "bad" || end === "bad") return bad("startLine and endLine must be whole numbers.");
|
|
2268
|
+
if (start !== void 0 && end !== void 0 && end < start) return bad("endLine must not be before startLine.");
|
|
2269
|
+
if (typeof args.path !== "string") return bad("path must be a string.");
|
|
2270
|
+
const gate = gatePath(args.path, env2);
|
|
2271
|
+
if (!gate.ok) {
|
|
2272
|
+
const read = gate.fail.status === "refused" ? { path: gate.path, chars: 0, refused: true } : void 0;
|
|
2273
|
+
return { fail: gate.fail, ...read ? { read } : {} };
|
|
2274
|
+
}
|
|
2275
|
+
const path = gate.path;
|
|
2276
|
+
const key = JSON.stringify({ path, startLine: start ?? null, endLine: end ?? null });
|
|
2277
|
+
return {
|
|
2278
|
+
key,
|
|
2279
|
+
exec: () => {
|
|
2280
|
+
let size;
|
|
2281
|
+
try {
|
|
2282
|
+
const st = env2.fs.lstat(path);
|
|
2283
|
+
if (st.isSymlink) {
|
|
2284
|
+
return { result: { status: "refused", reason: "symlink", message: "Links are not followed." }, read: { path, chars: 0, refused: true } };
|
|
2285
|
+
}
|
|
2286
|
+
if (!st.isFile) return { result: { status: "unreadable", reason: "not_a_file", message: "That entry is not a regular file." } };
|
|
2287
|
+
size = st.size;
|
|
2288
|
+
if (size > env2.limits.fileBytesMax) {
|
|
2289
|
+
return { result: { status: "unreadable", reason: "too_large", message: "That file is too large to read." } };
|
|
2290
|
+
}
|
|
2291
|
+
if (!env2.fs.realpathInside(path)) {
|
|
2292
|
+
return { result: { status: "refused", reason: "outside_workspace", message: "That path is not allowed." }, read: { path, chars: 0, refused: true } };
|
|
2293
|
+
}
|
|
2294
|
+
} catch {
|
|
2295
|
+
return { result: { status: "unreadable", reason: "io", message: "That file could not be read." } };
|
|
2296
|
+
}
|
|
2297
|
+
let bytes;
|
|
2298
|
+
try {
|
|
2299
|
+
bytes = env2.fs.readBytes(path);
|
|
2300
|
+
} catch {
|
|
2301
|
+
return { result: { status: "unreadable", reason: "io", message: "That file could not be read." } };
|
|
2302
|
+
}
|
|
2303
|
+
if (hasNul(bytes)) return { result: { status: "unreadable", reason: "binary", message: "That file is binary." } };
|
|
2304
|
+
let text;
|
|
2305
|
+
try {
|
|
2306
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
2307
|
+
} catch {
|
|
2308
|
+
return { result: { status: "unreadable", reason: "not_utf8", message: "That file is not valid UTF-8 text." } };
|
|
2309
|
+
}
|
|
2310
|
+
if (text.charCodeAt(0) === 65279) text = text.slice(1);
|
|
2311
|
+
text = text.replace(/\r\n/g, "\n");
|
|
2312
|
+
const lines = text === "" ? [] : text.split("\n");
|
|
2313
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
2314
|
+
const totalLines = lines.length;
|
|
2315
|
+
const startLine = Math.max(1, start ?? 1);
|
|
2316
|
+
const endWanted = Math.min(totalLines, end ?? totalLines);
|
|
2317
|
+
if (startLine > totalLines) {
|
|
2318
|
+
return {
|
|
2319
|
+
result: { status: "ok", output: { path, startLine, endLine: totalLines, totalLines, truncated: false, content: "" } },
|
|
2320
|
+
read: { path, startLine, endLine: totalLines, chars: 0 }
|
|
2321
|
+
};
|
|
2322
|
+
}
|
|
2323
|
+
const budget = Math.max(0, Math.min(env2.limits.readCharsPerRead, env2.limits.readCharsTotal - ledger.readChars));
|
|
2324
|
+
const out = [];
|
|
2325
|
+
let used = 0;
|
|
2326
|
+
let endLine = startLine - 1;
|
|
2327
|
+
let truncated = false;
|
|
2328
|
+
for (let n = startLine; n <= endWanted; n++) {
|
|
2329
|
+
const line = `${n} ${lines[n - 1]}`;
|
|
2330
|
+
const cost = line.length + (out.length > 0 ? 1 : 0);
|
|
2331
|
+
if (used + cost <= budget) {
|
|
2332
|
+
out.push(line);
|
|
2333
|
+
used += cost;
|
|
2334
|
+
endLine = n;
|
|
2335
|
+
continue;
|
|
2336
|
+
}
|
|
2337
|
+
truncated = true;
|
|
2338
|
+
if (out.length === 0) {
|
|
2339
|
+
const keep = Math.max(0, budget - CUT.length);
|
|
2340
|
+
out.push(line.slice(0, keep) + CUT);
|
|
2341
|
+
used = out[0].length;
|
|
2342
|
+
endLine = n;
|
|
2343
|
+
}
|
|
2344
|
+
break;
|
|
2345
|
+
}
|
|
2346
|
+
let content = out.join("\n");
|
|
2347
|
+
ledger.readChars += used;
|
|
2348
|
+
const output = { path, startLine, endLine, totalLines, truncated };
|
|
2349
|
+
if (truncated) {
|
|
2350
|
+
if (endLine < totalLines) {
|
|
2351
|
+
output.nextStartLine = endLine + 1;
|
|
2352
|
+
content += `
|
|
2353
|
+
[truncated: showing lines ${startLine}-${endLine} of ${totalLines}; call read_file with startLine=${endLine + 1}]`;
|
|
2354
|
+
} else {
|
|
2355
|
+
content += `
|
|
2356
|
+
[truncated: line ${endLine} was cut]`;
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
output.content = content;
|
|
2360
|
+
return { result: { status: "ok", output }, read: { path, startLine, endLine, chars: used } };
|
|
2361
|
+
}
|
|
2362
|
+
};
|
|
2363
|
+
}
|
|
2364
|
+
function prepareListFiles(args, env2) {
|
|
2365
|
+
const max = optionalInt(args.maxEntries);
|
|
2366
|
+
if (max === "bad" || max !== void 0 && max < 1) return bad("maxEntries must be a whole number of at least 1.");
|
|
2367
|
+
if (args.prefix !== void 0 && args.prefix !== null && typeof args.prefix !== "string") return bad("prefix must be a string.");
|
|
2368
|
+
const n = normaliseToolPath(args.prefix, { allowRoot: true });
|
|
2369
|
+
if (!n.ok) return { fail: { status: "refused", reason: n.reason, message: "That path is not allowed." } };
|
|
2370
|
+
const prefix = n.path;
|
|
2371
|
+
const cap = Math.min(max ?? env2.limits.listMaxEntries, env2.limits.listMaxEntries);
|
|
2372
|
+
return {
|
|
2373
|
+
key: JSON.stringify({ prefix, maxEntries: cap }),
|
|
2374
|
+
exec: () => {
|
|
2375
|
+
const matching = env2.visible.filter((p) => matchesPrefix(p, prefix));
|
|
2376
|
+
const entries = matching.slice(0, cap);
|
|
2377
|
+
const truncated = matching.length > cap;
|
|
2378
|
+
const output = { entries, total: matching.length, truncated };
|
|
2379
|
+
if (truncated) {
|
|
2380
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2381
|
+
for (const p of matching) {
|
|
2382
|
+
const rest = prefix === "" ? p : p.slice(prefix.length + 1);
|
|
2383
|
+
const slash = rest.indexOf("/");
|
|
2384
|
+
if (slash > 0) counts.set(rest.slice(0, slash), (counts.get(rest.slice(0, slash)) ?? 0) + 1);
|
|
2385
|
+
}
|
|
2386
|
+
output.subdirs = [...counts.entries()].map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count || (a.name < b.name ? -1 : 1)).slice(0, 50);
|
|
2387
|
+
}
|
|
2388
|
+
return { result: { status: "ok", output } };
|
|
2389
|
+
}
|
|
2390
|
+
};
|
|
2391
|
+
}
|
|
2392
|
+
var WORD_CHAR = /[A-Za-z0-9_$]/;
|
|
2393
|
+
function lineMatches(line, needle, ignoreCase, wholeWord) {
|
|
2394
|
+
const hay = ignoreCase ? line.toLowerCase() : line;
|
|
2395
|
+
let from = 0;
|
|
2396
|
+
for (; ; ) {
|
|
2397
|
+
const i = hay.indexOf(needle, from);
|
|
2398
|
+
if (i === -1) return -1;
|
|
2399
|
+
if (!wholeWord) return i;
|
|
2400
|
+
const before = i > 0 ? hay[i - 1] : "";
|
|
2401
|
+
const after = i + needle.length < hay.length ? hay[i + needle.length] : "";
|
|
2402
|
+
if (!(before && WORD_CHAR.test(before)) && !(after && WORD_CHAR.test(after))) return i;
|
|
2403
|
+
from = i + 1;
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
function excerpt(line, at, max) {
|
|
2407
|
+
const t = line.trim();
|
|
2408
|
+
if (t.length <= max) return t;
|
|
2409
|
+
const lead = line.length - line.trimStart().length;
|
|
2410
|
+
const pos = Math.max(0, at - lead);
|
|
2411
|
+
const from = Math.max(0, Math.min(pos - 40, t.length - max));
|
|
2412
|
+
const piece = t.slice(from, from + max);
|
|
2413
|
+
return `${from > 0 ? "\u2026" : ""}${piece}${from + max < t.length ? "\u2026" : ""}`;
|
|
2414
|
+
}
|
|
2415
|
+
function prepareGrep(args, env2) {
|
|
2416
|
+
const pattern = args.pattern;
|
|
2417
|
+
if (typeof pattern !== "string" || pattern.length === 0 || pattern.length > env2.limits.maxPatternChars) {
|
|
2418
|
+
return bad("pattern must be a non-empty string within the length limit.");
|
|
2419
|
+
}
|
|
2420
|
+
if (/[\u0000\r\n]/.test(pattern)) return bad("pattern must be a single line of plain text.");
|
|
2421
|
+
for (const flag of ["ignoreCase", "wholeWord"]) {
|
|
2422
|
+
if (args[flag] !== void 0 && typeof args[flag] !== "boolean") return bad(`${flag} must be true or false.`);
|
|
2423
|
+
}
|
|
2424
|
+
const maxIn = optionalInt(args.maxResults);
|
|
2425
|
+
if (maxIn === "bad" || maxIn !== void 0 && maxIn < 1) return bad("maxResults must be a whole number of at least 1.");
|
|
2426
|
+
if (args.pathPrefix !== void 0 && args.pathPrefix !== null && typeof args.pathPrefix !== "string") return bad("pathPrefix must be a string.");
|
|
2427
|
+
const n = normaliseToolPath(args.pathPrefix, { allowRoot: true });
|
|
2428
|
+
if (!n.ok) return { fail: { status: "refused", reason: n.reason, message: "That path is not allowed." } };
|
|
2429
|
+
const prefix = n.path;
|
|
2430
|
+
const ignoreCase = args.ignoreCase === true;
|
|
2431
|
+
const wholeWord = args.wholeWord === true;
|
|
2432
|
+
const maxMatches = Math.min(maxIn ?? env2.limits.grepMaxMatches, env2.limits.grepMaxMatches);
|
|
2433
|
+
const needle = ignoreCase ? pattern.toLowerCase() : pattern;
|
|
2434
|
+
return {
|
|
2435
|
+
key: JSON.stringify({ pattern, pathPrefix: prefix, maxResults: maxMatches, ignoreCase, wholeWord }),
|
|
2436
|
+
exec: () => {
|
|
2437
|
+
const matches = [];
|
|
2438
|
+
const skipped = { large: 0, binary: 0 };
|
|
2439
|
+
let filesScanned = 0;
|
|
2440
|
+
let bytesScanned = 0;
|
|
2441
|
+
let scanCapped = false;
|
|
2442
|
+
let truncated = false;
|
|
2443
|
+
scan: for (const path of env2.visible) {
|
|
2444
|
+
if (!matchesPrefix(path, prefix)) continue;
|
|
2445
|
+
if (filesScanned >= env2.limits.grepMaxFiles || bytesScanned >= env2.limits.grepMaxBytes) {
|
|
2446
|
+
scanCapped = true;
|
|
2447
|
+
break;
|
|
2448
|
+
}
|
|
2449
|
+
filesScanned += 1;
|
|
2450
|
+
let bytes;
|
|
2451
|
+
try {
|
|
2452
|
+
const st = env2.fs.lstat(path);
|
|
2453
|
+
if (st.isSymlink || !st.isFile) continue;
|
|
2454
|
+
if (st.size > env2.limits.grepFileBytes) {
|
|
2455
|
+
skipped.large += 1;
|
|
2456
|
+
continue;
|
|
2457
|
+
}
|
|
2458
|
+
if (!env2.fs.realpathInside(path)) continue;
|
|
2459
|
+
bytes = env2.fs.readBytes(path);
|
|
2460
|
+
} catch {
|
|
2461
|
+
continue;
|
|
2462
|
+
}
|
|
2463
|
+
bytesScanned += bytes.length;
|
|
2464
|
+
if (hasNul(bytes)) {
|
|
2465
|
+
skipped.binary += 1;
|
|
2466
|
+
continue;
|
|
2467
|
+
}
|
|
2468
|
+
const lines = new TextDecoder("utf-8").decode(bytes).replace(/\r\n/g, "\n").split("\n");
|
|
2469
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2470
|
+
const at = lineMatches(lines[i], needle, ignoreCase, wholeWord);
|
|
2471
|
+
if (at === -1) continue;
|
|
2472
|
+
if (matches.length >= maxMatches) {
|
|
2473
|
+
truncated = true;
|
|
2474
|
+
break scan;
|
|
2475
|
+
}
|
|
2476
|
+
matches.push({ path, line: i + 1, text: excerpt(lines[i], at, env2.limits.grepLineChars) });
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
const output = { matches, truncated, filesScanned, skipped };
|
|
2480
|
+
if (scanCapped) output.scanCapped = true;
|
|
2481
|
+
return { result: { status: "ok", output } };
|
|
2482
|
+
}
|
|
2483
|
+
};
|
|
2484
|
+
}
|
|
2485
|
+
function budgetResult(reason) {
|
|
2486
|
+
return { status: "budget_exhausted", reason, message: "That limit has been reached; give your answer from what you have." };
|
|
2487
|
+
}
|
|
2488
|
+
function executeToolCall(call, env2, ledger) {
|
|
2489
|
+
const wrap = (r) => ({ id: call.id, name: call.name, ...r });
|
|
2490
|
+
const finish = (r, read) => {
|
|
2491
|
+
const result = wrap(r);
|
|
2492
|
+
ledger.resultChars += JSON.stringify(result).length;
|
|
2493
|
+
if (result.status === "refused") ledger.refused += 1;
|
|
2494
|
+
return read ? { result, read } : { result };
|
|
2495
|
+
};
|
|
2496
|
+
if (ledger.toolCalls >= env2.limits.maxToolCalls) return finish(budgetResult("tool_calls"));
|
|
2497
|
+
if (ledger.resultChars >= env2.limits.resultCharsTotal) return finish(budgetResult("result_chars"));
|
|
2498
|
+
if (call.name === "read_file" && ledger.readChars >= env2.limits.readCharsTotal) return finish(budgetResult("read_chars"));
|
|
2499
|
+
ledger.toolCalls += 1;
|
|
2500
|
+
let prepared;
|
|
2501
|
+
try {
|
|
2502
|
+
if (call.name !== "read_file" && call.name !== "list_files" && call.name !== "grep") {
|
|
2503
|
+
return finish({ status: "unknown_tool", message: "Unknown tool. Use list_files, read_file or grep." });
|
|
2504
|
+
}
|
|
2505
|
+
if (!isPlainObject(call.args)) return finish({ status: "bad_args", message: "Arguments must be an object." });
|
|
2506
|
+
prepared = call.name === "read_file" ? prepareReadFile(call.args, env2, ledger) : call.name === "list_files" ? prepareListFiles(call.args, env2) : prepareGrep(call.args, env2);
|
|
2507
|
+
} catch {
|
|
2508
|
+
return finish({ status: "bad_args", message: "Those arguments could not be used." });
|
|
2509
|
+
}
|
|
2510
|
+
if ("fail" in prepared) return finish(prepared.fail, prepared.read);
|
|
2511
|
+
const dupKey = `${call.name}:${prepared.key}`;
|
|
2512
|
+
const earlier = ledger.seen.get(dupKey);
|
|
2513
|
+
if (earlier !== void 0) {
|
|
2514
|
+
return finish({ status: "duplicate", reason: earlier, message: "Identical call already made; see its result." });
|
|
2515
|
+
}
|
|
2516
|
+
ledger.seen.set(dupKey, call.id);
|
|
2517
|
+
let executed;
|
|
2518
|
+
try {
|
|
2519
|
+
executed = prepared.exec();
|
|
2520
|
+
} catch {
|
|
2521
|
+
executed = { result: { status: "unreadable", reason: "io", message: "That could not be read." } };
|
|
2522
|
+
}
|
|
2523
|
+
return finish(executed.result, executed.read);
|
|
2524
|
+
}
|
|
2525
|
+
|
|
2526
|
+
// src/tokenEstimate.ts
|
|
2527
|
+
var TOKENS_PER_ALPHA_CHAR = 0.3;
|
|
2528
|
+
var TOKENS_PER_DIGIT_CHAR = 0.18;
|
|
2529
|
+
var TOKENS_PER_SPACE_CHAR = 0.05;
|
|
2530
|
+
var TOKENS_PER_PUNCTUATION_CHAR = 1.05;
|
|
2531
|
+
var TOKENS_PER_NON_ASCII_CHAR = 1;
|
|
2532
|
+
var PROMPT_OVERHEAD_TOKENS = 5e3;
|
|
2533
|
+
function estimateTokens(text) {
|
|
2534
|
+
let alpha = 0;
|
|
2535
|
+
let digit = 0;
|
|
2536
|
+
let space = 0;
|
|
2537
|
+
let punct = 0;
|
|
2538
|
+
let nonAscii = 0;
|
|
2539
|
+
for (const ch of text) {
|
|
2540
|
+
const code = ch.codePointAt(0);
|
|
2541
|
+
if (code >= 128) nonAscii++;
|
|
2542
|
+
else if (code >= 65 && code <= 90 || code >= 97 && code <= 122) alpha++;
|
|
2543
|
+
else if (code >= 48 && code <= 57) digit++;
|
|
2544
|
+
else if (code === 32 || code === 9 || code === 13 || code === 10) space++;
|
|
2545
|
+
else punct++;
|
|
2546
|
+
}
|
|
2547
|
+
return Math.ceil(
|
|
2548
|
+
alpha * TOKENS_PER_ALPHA_CHAR + digit * TOKENS_PER_DIGIT_CHAR + space * TOKENS_PER_SPACE_CHAR + punct * TOKENS_PER_PUNCTUATION_CHAR + nonAscii * TOKENS_PER_NON_ASCII_CHAR
|
|
2549
|
+
);
|
|
2550
|
+
}
|
|
2551
|
+
function estimateRequestTokens(contextData) {
|
|
2552
|
+
return PROMPT_OVERHEAD_TOKENS + estimateTokens(JSON.stringify(contextData ?? {}, null, 2));
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
// src/targetingLoop.ts
|
|
2556
|
+
var LOOP_CONTRACT_VERSION = "targeting-loop-1";
|
|
2557
|
+
var LoopFailure = class extends Error {
|
|
2558
|
+
reason;
|
|
2559
|
+
runtimeCode;
|
|
2560
|
+
/** What the loop had done when it failed, so reads made before the failure are not lost. */
|
|
2561
|
+
summary;
|
|
2562
|
+
constructor(reason, runtimeCode, cause) {
|
|
2563
|
+
super(`pilot.targeting loop failed: ${reason}${runtimeCode ? ` (${runtimeCode})` : ""}`);
|
|
2564
|
+
this.name = "LoopFailure";
|
|
2565
|
+
this.reason = reason;
|
|
2566
|
+
this.runtimeCode = runtimeCode;
|
|
2567
|
+
if (cause !== void 0) this.cause = cause;
|
|
2568
|
+
}
|
|
2569
|
+
};
|
|
2570
|
+
var SafetyRefusalError = class extends RuntimeReplyError {
|
|
2571
|
+
constructor(message, summary) {
|
|
2572
|
+
super(message, summary);
|
|
2573
|
+
this.name = "SafetyRefusalError";
|
|
2574
|
+
}
|
|
2575
|
+
};
|
|
2576
|
+
var NO_FALLBACK_CODES = /* @__PURE__ */ new Set(["RUN_BUDGET_EXCEEDED", "RUN_CREDENTIAL_SCOPE"]);
|
|
2577
|
+
var TOOL_NAMES = /* @__PURE__ */ new Set(["list_files", "read_file", "grep"]);
|
|
2578
|
+
var MAX_ID_CHARS = 64;
|
|
2579
|
+
var MAX_CALLS_IN_A_REPLY = 50;
|
|
2580
|
+
var MAX_ARGS_JSON_CHARS = 1e3;
|
|
2581
|
+
function isPlainObject2(x) {
|
|
2582
|
+
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
2583
|
+
}
|
|
2584
|
+
function safeArgs(args) {
|
|
2585
|
+
try {
|
|
2586
|
+
return JSON.stringify(args).length <= MAX_ARGS_JSON_CHARS ? args : { _omitted: "too large" };
|
|
2587
|
+
} catch {
|
|
2588
|
+
return { _omitted: "unreadable" };
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
function classifyTransport(err) {
|
|
2592
|
+
if (err instanceof TargetingHttpError) return new LoopFailure("http_error", void 0, err);
|
|
2593
|
+
const name = err?.name;
|
|
2594
|
+
if (name === "TimeoutError" || name === "AbortError") return new LoopFailure("turn_timeout", void 0, err);
|
|
2595
|
+
return new LoopFailure("transport", void 0, err);
|
|
2596
|
+
}
|
|
2597
|
+
async function runTargetingLoop(args, deps) {
|
|
2598
|
+
const limits = { ...DEFAULT_LIMITS, ...deps.limits };
|
|
2599
|
+
const now = deps.now ?? Date.now;
|
|
2600
|
+
const log = deps.log ?? ((e) => console.log(JSON.stringify(e)));
|
|
2601
|
+
const turnTimeoutMs = deps.turnTimeoutMs ?? 18e4;
|
|
2602
|
+
const deadlineMs = deps.deadlineMs ?? 6e5;
|
|
2603
|
+
const env2 = {
|
|
2604
|
+
listing: new Set(args.listing),
|
|
2605
|
+
visible: buildVisibleListing(args.listing, args.deniedPaths),
|
|
2606
|
+
deniedPaths: args.deniedPaths,
|
|
2607
|
+
fs: deps.fs ?? createNodeToolFs(),
|
|
2608
|
+
limits
|
|
2609
|
+
};
|
|
2610
|
+
const ledger = newLedger();
|
|
2611
|
+
const reads = [];
|
|
2612
|
+
const transcript = [];
|
|
2613
|
+
const toolResults = [];
|
|
2614
|
+
const usedIds = /* @__PURE__ */ new Set();
|
|
2615
|
+
let turns = 0;
|
|
2616
|
+
let noProgressStreak = 0;
|
|
2617
|
+
let forced = null;
|
|
2618
|
+
const started = now();
|
|
2619
|
+
const buildContext = (turn, isFinal, finalReason) => ({
|
|
2620
|
+
...args.baseContext,
|
|
2621
|
+
contractVersion: LOOP_CONTRACT_VERSION,
|
|
2622
|
+
loop: {
|
|
2623
|
+
turn,
|
|
2624
|
+
maxTurns: limits.maxTurns,
|
|
2625
|
+
final: isFinal,
|
|
2626
|
+
...isFinal ? { finalReason } : {},
|
|
2627
|
+
remaining: {
|
|
2628
|
+
toolCalls: Math.max(0, limits.maxToolCalls - ledger.toolCalls),
|
|
2629
|
+
readChars: Math.max(0, limits.readCharsTotal - ledger.readChars)
|
|
2630
|
+
},
|
|
2631
|
+
transcript,
|
|
2632
|
+
toolResults
|
|
2633
|
+
}
|
|
2634
|
+
});
|
|
2635
|
+
const snapshot = (stopReason) => ({
|
|
2636
|
+
turns,
|
|
2637
|
+
toolCalls: ledger.toolCalls,
|
|
2638
|
+
readChars: ledger.readChars,
|
|
2639
|
+
refused: ledger.refused,
|
|
2640
|
+
stopReason,
|
|
2641
|
+
reads: [...reads]
|
|
2642
|
+
});
|
|
2643
|
+
log({ event: "targeting.loop_start", mode: "loop", maxTurns: limits.maxTurns, maxToolCalls: limits.maxToolCalls, listingFiles: args.listing.length });
|
|
2644
|
+
try {
|
|
2645
|
+
for (let turn = 1; turn <= limits.maxTurns; turn++) {
|
|
2646
|
+
turns = turn;
|
|
2647
|
+
if (now() - started > deadlineMs) throw new LoopFailure("loop_deadline");
|
|
2648
|
+
const isFinal = forced !== null || turn === limits.maxTurns;
|
|
2649
|
+
const finalReason = forced ?? "max_turns";
|
|
2650
|
+
const contextData = buildContext(turn, isFinal, finalReason);
|
|
2651
|
+
const requestChars = JSON.stringify(contextData).length;
|
|
2652
|
+
const requestTokens = estimateRequestTokens(contextData);
|
|
2653
|
+
if (requestTokens > limits.maxRequestTokens) {
|
|
2654
|
+
log({ event: "targeting.loop_ceiling", turn, action: "not_sent", projectedTokens: requestTokens, ceiling: limits.maxRequestTokens });
|
|
2655
|
+
throw new LoopFailure("token_ceiling");
|
|
2656
|
+
}
|
|
2657
|
+
const turnStarted = now();
|
|
2658
|
+
let envelope;
|
|
2659
|
+
try {
|
|
2660
|
+
envelope = await deps.post(contextData, AbortSignal.timeout(turnTimeoutMs));
|
|
2661
|
+
} catch (err) {
|
|
2662
|
+
throw classifyTransport(err);
|
|
2663
|
+
}
|
|
2664
|
+
const code = envelope?.errorCode;
|
|
2665
|
+
try {
|
|
2666
|
+
checkHardFailure(envelope, "pilot.targeting");
|
|
2667
|
+
} catch (err) {
|
|
2668
|
+
throw new LoopFailure(code === "CONTRACT_REJECTED" ? "contract_rejected" : "semantic_mismatch", String(code), err);
|
|
2669
|
+
}
|
|
2670
|
+
if (envelope?.success === false) {
|
|
2671
|
+
const runtimeCode = typeof code === "string" ? code : void 0;
|
|
2672
|
+
if (runtimeCode && NO_FALLBACK_CODES.has(runtimeCode)) {
|
|
2673
|
+
let inner;
|
|
2674
|
+
try {
|
|
2675
|
+
unwrapExecute(envelope, "pilot.targeting");
|
|
2676
|
+
} catch (e) {
|
|
2677
|
+
inner = e instanceof RuntimeReplyError ? e : void 0;
|
|
2678
|
+
}
|
|
2679
|
+
const fallbackText = `pilot.targeting failed: ${runtimeCode}`;
|
|
2680
|
+
throw new SafetyRefusalError(inner?.message ?? fallbackText, inner?.summary ?? fallbackText);
|
|
2681
|
+
}
|
|
2682
|
+
throw new LoopFailure("runtime_failed", runtimeCode);
|
|
2683
|
+
}
|
|
2684
|
+
let body;
|
|
2685
|
+
try {
|
|
2686
|
+
body = unwrapExecute(envelope, "pilot.targeting");
|
|
2687
|
+
} catch (err) {
|
|
2688
|
+
throw new LoopFailure("bad_reply", void 0, err);
|
|
2689
|
+
}
|
|
2690
|
+
if (body.outcome === "TARGETED" || body.outcome === "NO_TARGET") {
|
|
2691
|
+
log({ event: "targeting.loop_turn", turn, outcome: "final", requestChars, requestTokens, elapsedMs: now() - turnStarted, calls: [] });
|
|
2692
|
+
const stopReason = isFinal ? `forced_${finalReason}` : "model_final";
|
|
2693
|
+
log({ event: "targeting.loop_done", turns, toolCalls: ledger.toolCalls, readChars: ledger.readChars, refused: ledger.refused, stopReason });
|
|
2694
|
+
return { body, summary: snapshot(stopReason) };
|
|
2695
|
+
}
|
|
2696
|
+
if (body.outcome !== "TOOL_CALLS") throw new LoopFailure("bad_reply");
|
|
2697
|
+
if (isFinal) throw new LoopFailure("final_not_produced");
|
|
2698
|
+
const rawCalls = body.toolCalls;
|
|
2699
|
+
if (!Array.isArray(rawCalls) || rawCalls.length === 0 || rawCalls.length > MAX_CALLS_IN_A_REPLY) {
|
|
2700
|
+
throw new LoopFailure("bad_reply");
|
|
2701
|
+
}
|
|
2702
|
+
const calls = [];
|
|
2703
|
+
for (const c of rawCalls) {
|
|
2704
|
+
if (!isPlainObject2(c) || typeof c.id !== "string" || c.id.length === 0 || c.id.length > MAX_ID_CHARS || typeof c.name !== "string") {
|
|
2705
|
+
throw new LoopFailure("bad_reply");
|
|
2706
|
+
}
|
|
2707
|
+
if (usedIds.has(c.id) || calls.some((x) => x.id === c.id)) throw new LoopFailure("duplicate_call_ids");
|
|
2708
|
+
calls.push({ id: c.id, name: c.name, args: c.args });
|
|
2709
|
+
}
|
|
2710
|
+
for (const c of calls) usedIds.add(c.id);
|
|
2711
|
+
const results = [];
|
|
2712
|
+
const logged = [];
|
|
2713
|
+
const turnReads = [];
|
|
2714
|
+
calls.forEach((call, index) => {
|
|
2715
|
+
let result;
|
|
2716
|
+
let read;
|
|
2717
|
+
if (index >= limits.maxCallsPerTurn) {
|
|
2718
|
+
result = {
|
|
2719
|
+
id: call.id,
|
|
2720
|
+
name: call.name,
|
|
2721
|
+
status: "budget_exhausted",
|
|
2722
|
+
reason: "too_many_calls",
|
|
2723
|
+
message: "Too many calls in one turn; this one was not run."
|
|
2724
|
+
};
|
|
2725
|
+
} else {
|
|
2726
|
+
try {
|
|
2727
|
+
({ result, read } = executeToolCall(call, env2, ledger));
|
|
2728
|
+
} catch (err) {
|
|
2729
|
+
throw new LoopFailure("executor_error", void 0, err);
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
results.push(result);
|
|
2733
|
+
turnReads[index] = read;
|
|
2734
|
+
if (read) reads.push(read);
|
|
2735
|
+
const tool = TOOL_NAMES.has(call.name) ? call.name : "unknown";
|
|
2736
|
+
const path = read && read.path !== "(invalid path)" ? { path: read.path } : {};
|
|
2737
|
+
logged.push({ tool, status: result.status, ...result.reason && result.status !== "duplicate" ? { reason: result.reason } : {}, ...path });
|
|
2738
|
+
if (result.status === "refused") {
|
|
2739
|
+
log({ event: "targeting.loop_refused", turn, tool, reason: result.reason, ...path });
|
|
2740
|
+
}
|
|
2741
|
+
});
|
|
2742
|
+
const reasoning = typeof body.reasoning === "string" ? body.reasoning.slice(0, limits.maxReasoningChars) : void 0;
|
|
2743
|
+
transcript.push({
|
|
2744
|
+
turn,
|
|
2745
|
+
...reasoning ? { reasoning } : {},
|
|
2746
|
+
toolCalls: calls.map((c) => ({ id: c.id, name: c.name, args: safeArgs(c.args) }))
|
|
2747
|
+
});
|
|
2748
|
+
toolResults.push(...results);
|
|
2749
|
+
noProgressStreak = results.every((r) => r.status === "duplicate") ? noProgressStreak + 1 : 0;
|
|
2750
|
+
if (noProgressStreak >= 2) forced = "no_progress";
|
|
2751
|
+
else if (ledger.toolCalls >= limits.maxToolCalls) forced = "tool_budget";
|
|
2752
|
+
else if (ledger.readChars >= limits.readCharsTotal || ledger.resultChars >= limits.resultCharsTotal) forced = "read_budget";
|
|
2753
|
+
const nextTurn = turn + 1;
|
|
2754
|
+
if (nextTurn <= limits.maxTurns) {
|
|
2755
|
+
const nextFinal = forced !== null || nextTurn === limits.maxTurns;
|
|
2756
|
+
const projected = estimateRequestTokens(buildContext(nextTurn, nextFinal, forced ?? "max_turns"));
|
|
2757
|
+
if (projected > limits.maxRequestTokens) {
|
|
2758
|
+
results.forEach((r, idx) => {
|
|
2759
|
+
if (r.status !== "ok") return;
|
|
2760
|
+
toolResults[toolResults.indexOf(r)] = {
|
|
2761
|
+
id: r.id,
|
|
2762
|
+
name: r.name,
|
|
2763
|
+
status: "budget_exhausted",
|
|
2764
|
+
reason: "token_ceiling",
|
|
2765
|
+
message: "Too large to include; answer from what you have."
|
|
2766
|
+
};
|
|
2767
|
+
const dropped = turnReads[idx];
|
|
2768
|
+
if (dropped) {
|
|
2769
|
+
const at = reads.indexOf(dropped);
|
|
2770
|
+
if (at >= 0) reads.splice(at, 1);
|
|
2771
|
+
}
|
|
2772
|
+
logged[idx].status = "budget_exhausted";
|
|
2773
|
+
logged[idx].reason = "token_ceiling";
|
|
2774
|
+
});
|
|
2775
|
+
forced = "read_budget";
|
|
2776
|
+
const trimmed = estimateRequestTokens(buildContext(nextTurn, true, "read_budget"));
|
|
2777
|
+
const fits = trimmed <= limits.maxRequestTokens;
|
|
2778
|
+
log({
|
|
2779
|
+
event: "targeting.loop_ceiling",
|
|
2780
|
+
turn,
|
|
2781
|
+
action: fits ? "dropped_results" : "fallback",
|
|
2782
|
+
projectedTokens: projected,
|
|
2783
|
+
ceiling: limits.maxRequestTokens
|
|
2784
|
+
});
|
|
2785
|
+
if (!fits) throw new LoopFailure("token_ceiling");
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
log({ event: "targeting.loop_turn", turn, outcome: "tool_calls", requestChars, requestTokens, elapsedMs: now() - turnStarted, calls: logged });
|
|
2789
|
+
}
|
|
2790
|
+
throw new LoopFailure("final_not_produced");
|
|
2791
|
+
} catch (err) {
|
|
2792
|
+
if (err instanceof SafetyRefusalError) throw err;
|
|
2793
|
+
const failure = err instanceof LoopFailure ? err : new LoopFailure("internal_error", void 0, err);
|
|
2794
|
+
failure.summary ??= snapshot("fallback");
|
|
2795
|
+
throw failure;
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
// src/targeting.ts
|
|
1790
2800
|
var CONTRACT_VERSION2 = "test-targets-1";
|
|
1791
2801
|
function listWorkspaceFiles(workspaceRoot) {
|
|
1792
2802
|
return listFiles(workspaceRoot ?? ".");
|
|
@@ -1794,13 +2804,13 @@ function listWorkspaceFiles(workspaceRoot) {
|
|
|
1794
2804
|
function validateTarget(target, workspaceRoot, workspaceListing, deniedPaths) {
|
|
1795
2805
|
const denied = matchesDeniedPath(target.path, deniedPaths);
|
|
1796
2806
|
if (denied) {
|
|
1797
|
-
throw new
|
|
2807
|
+
throw new DeniedPathError(
|
|
1798
2808
|
`pilot.targeting proposed a target matching a denied path (${denied}): ${target.path}`
|
|
1799
2809
|
);
|
|
1800
2810
|
}
|
|
1801
2811
|
const deniedContext = findDeniedContextPath(target.contextPaths ?? [], deniedPaths);
|
|
1802
2812
|
if (deniedContext) {
|
|
1803
|
-
throw new
|
|
2813
|
+
throw new DeniedPathError(
|
|
1804
2814
|
`pilot.targeting cited a denied path as context (${deniedContext.pattern}): ${deniedContext.path} (target ${target.path})`
|
|
1805
2815
|
);
|
|
1806
2816
|
}
|
|
@@ -1815,15 +2825,15 @@ function validateTarget(target, workspaceRoot, workspaceListing, deniedPaths) {
|
|
|
1815
2825
|
}
|
|
1816
2826
|
return;
|
|
1817
2827
|
}
|
|
1818
|
-
const root = (0,
|
|
1819
|
-
const rel = (0,
|
|
1820
|
-
if (rel.startsWith("..") || (0,
|
|
2828
|
+
const root = (0, import_node_path9.resolve)(workspaceRoot ?? ".");
|
|
2829
|
+
const rel = (0, import_node_path9.relative)(root, (0, import_node_path9.resolve)(target.path));
|
|
2830
|
+
if (rel.startsWith("..") || (0, import_node_path9.isAbsolute)(rel)) {
|
|
1821
2831
|
throw new Error(
|
|
1822
2832
|
`pilot.targeting named a NEW_FILE path that escapes the workspace root: ${target.path}`
|
|
1823
2833
|
);
|
|
1824
2834
|
}
|
|
1825
2835
|
}
|
|
1826
|
-
|
|
2836
|
+
function buildContextData(design, workspaceListing, requiredTests, deniedPaths, testDiscovery, outOfScope) {
|
|
1827
2837
|
const contextData = {
|
|
1828
2838
|
// NESTED, NOT FLATTENED — a second vocabulary for the design shape is exactly what
|
|
1829
2839
|
// QUIKTR-1034 cost.
|
|
@@ -1842,6 +2852,9 @@ async function runTargeting(gateway, design, workspaceRoot, workspaceListing, re
|
|
|
1842
2852
|
contextData.outOfScope = outOfScope;
|
|
1843
2853
|
}
|
|
1844
2854
|
contextData.testDiscovery = testDiscovery;
|
|
2855
|
+
return contextData;
|
|
2856
|
+
}
|
|
2857
|
+
async function postTargeting(gateway, contextData, signal) {
|
|
1845
2858
|
const res = await fetchWithRetry(`${gateway.baseUrl}/ai/execute`, {
|
|
1846
2859
|
method: "POST",
|
|
1847
2860
|
headers: { "content-type": "application/json", "x-run-token": gateway.token },
|
|
@@ -1851,14 +2864,15 @@ async function runTargeting(gateway, design, workspaceRoot, workspaceListing, re
|
|
|
1851
2864
|
expectedOutputType: "json",
|
|
1852
2865
|
userPrompt: "",
|
|
1853
2866
|
contextData
|
|
1854
|
-
})
|
|
2867
|
+
}),
|
|
2868
|
+
...signal ? { signal } : {}
|
|
1855
2869
|
});
|
|
1856
2870
|
if (!res.ok) {
|
|
1857
|
-
throw new
|
|
2871
|
+
throw new TargetingHttpError(res.status, res.statusText);
|
|
1858
2872
|
}
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
2873
|
+
return res.json();
|
|
2874
|
+
}
|
|
2875
|
+
function finalizeTargeting(body, workspaceRoot, workspaceListing, deniedPaths) {
|
|
1862
2876
|
if (body.outcome === "NO_TARGET") {
|
|
1863
2877
|
return { outcome: "NO_TARGET", reason: body.noTargetReason ?? null };
|
|
1864
2878
|
}
|
|
@@ -1887,6 +2901,66 @@ async function runTargeting(gateway, design, workspaceRoot, workspaceListing, re
|
|
|
1887
2901
|
...correctedKinds.length > 0 ? { correctedKinds } : {}
|
|
1888
2902
|
};
|
|
1889
2903
|
}
|
|
2904
|
+
async function runSingleShot(gateway, design, workspaceRoot, workspaceListing, requiredTests, deniedPaths, testDiscovery, outOfScope = []) {
|
|
2905
|
+
const contextData = buildContextData(design, workspaceListing, requiredTests, deniedPaths, testDiscovery, outOfScope);
|
|
2906
|
+
const envelope = await postTargeting(gateway, contextData);
|
|
2907
|
+
checkHardFailure(envelope, "pilot.targeting");
|
|
2908
|
+
const body = unwrapExecute(envelope, "pilot.targeting");
|
|
2909
|
+
return finalizeTargeting(body, workspaceRoot, workspaceListing, deniedPaths);
|
|
2910
|
+
}
|
|
2911
|
+
var CODE_LIKE = /^[A-Z0-9_]{1,40}$/;
|
|
2912
|
+
async function runTargeting(gateway, design, workspaceRoot, workspaceListing, requiredTests, deniedPaths, testDiscovery, outOfScope = [], options = {}) {
|
|
2913
|
+
const mode = options.mode ?? resolveTargetingMode();
|
|
2914
|
+
if (mode !== "loop") {
|
|
2915
|
+
return runSingleShot(gateway, design, workspaceRoot, workspaceListing, requiredTests, deniedPaths, testDiscovery, outOfScope);
|
|
2916
|
+
}
|
|
2917
|
+
const baseContext = buildContextData(design, workspaceListing, requiredTests, deniedPaths, testDiscovery, outOfScope);
|
|
2918
|
+
let summary;
|
|
2919
|
+
let runtimeCode;
|
|
2920
|
+
let reason;
|
|
2921
|
+
try {
|
|
2922
|
+
const looped = await runTargetingLoop(
|
|
2923
|
+
{ baseContext, listing: workspaceListing, deniedPaths },
|
|
2924
|
+
{ ...options.loopDeps, post: (contextData, signal) => postTargeting(gateway, contextData, signal) }
|
|
2925
|
+
);
|
|
2926
|
+
summary = looped.summary;
|
|
2927
|
+
const result = finalizeTargeting(looped.body, workspaceRoot, workspaceListing, deniedPaths);
|
|
2928
|
+
return { ...result, loop: summary };
|
|
2929
|
+
} catch (err) {
|
|
2930
|
+
if (err instanceof DeniedPathError || err instanceof SafetyRefusalError) throw err;
|
|
2931
|
+
if (err instanceof LoopFailure) {
|
|
2932
|
+
reason = err.reason;
|
|
2933
|
+
runtimeCode = err.runtimeCode;
|
|
2934
|
+
summary = err.summary ?? summary;
|
|
2935
|
+
} else {
|
|
2936
|
+
reason = "final_invalid";
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
const fellBack = {
|
|
2940
|
+
turns: summary?.turns ?? 0,
|
|
2941
|
+
toolCalls: summary?.toolCalls ?? 0,
|
|
2942
|
+
readChars: summary?.readChars ?? 0,
|
|
2943
|
+
refused: summary?.refused ?? 0,
|
|
2944
|
+
reads: summary?.reads ?? [],
|
|
2945
|
+
stopReason: "fallback",
|
|
2946
|
+
fellBack: true,
|
|
2947
|
+
fallbackReason: reason
|
|
2948
|
+
};
|
|
2949
|
+
const log = options.loopDeps?.log ?? ((e) => console.log(JSON.stringify(e)));
|
|
2950
|
+
log({
|
|
2951
|
+
event: "targeting.loop_fallback",
|
|
2952
|
+
reason,
|
|
2953
|
+
turn: fellBack.turns,
|
|
2954
|
+
toolCalls: fellBack.toolCalls,
|
|
2955
|
+
...runtimeCode && CODE_LIKE.test(runtimeCode) ? { runtimeCode } : {}
|
|
2956
|
+
});
|
|
2957
|
+
const single = await runSingleShot(gateway, design, workspaceRoot, workspaceListing, requiredTests, deniedPaths, testDiscovery, outOfScope);
|
|
2958
|
+
return {
|
|
2959
|
+
...single,
|
|
2960
|
+
loop: fellBack,
|
|
2961
|
+
warnings: [`QuikPilot's read-only targeting could not complete (${reason}); standard targeting was used instead.`]
|
|
2962
|
+
};
|
|
2963
|
+
}
|
|
1890
2964
|
|
|
1891
2965
|
// src/targetingRecord.ts
|
|
1892
2966
|
var MAX_TARGETS = 25;
|
|
@@ -1896,9 +2970,11 @@ var MAX_ADDED_PATHS = 20;
|
|
|
1896
2970
|
var MAX_SKIPPED_CONTEXT = 20;
|
|
1897
2971
|
var MAX_SKIPPED_CASES = 200;
|
|
1898
2972
|
var MAX_CONTEXT_RECORDS = 25;
|
|
1899
|
-
var
|
|
1900
|
-
var
|
|
2973
|
+
var MAX_PATH_CHARS2 = 300;
|
|
2974
|
+
var MAX_ID_CHARS2 = 100;
|
|
1901
2975
|
var MAX_REASON_CHARS = 300;
|
|
2976
|
+
var MAX_LOOP_READS = 50;
|
|
2977
|
+
var MAX_FALLBACK_REASON_CHARS = 40;
|
|
1902
2978
|
function clampList(values, max, maxChars) {
|
|
1903
2979
|
let cut = false;
|
|
1904
2980
|
const kept = [];
|
|
@@ -1913,6 +2989,41 @@ function clampList(values, max, maxChars) {
|
|
|
1913
2989
|
}
|
|
1914
2990
|
return { kept, cut };
|
|
1915
2991
|
}
|
|
2992
|
+
function wholeNumber(n) {
|
|
2993
|
+
return typeof n === "number" && Number.isInteger(n) && n >= 0 ? n : 0;
|
|
2994
|
+
}
|
|
2995
|
+
function clampLoop(loop) {
|
|
2996
|
+
let cut = false;
|
|
2997
|
+
const reads = [];
|
|
2998
|
+
for (const r of loop.reads ?? []) {
|
|
2999
|
+
if (reads.length >= MAX_LOOP_READS) {
|
|
3000
|
+
cut = true;
|
|
3001
|
+
break;
|
|
3002
|
+
}
|
|
3003
|
+
const path = typeof r.path === "string" ? r.path.trim() : "";
|
|
3004
|
+
if (path.length === 0 || path.length > MAX_PATH_CHARS2) {
|
|
3005
|
+
cut = true;
|
|
3006
|
+
continue;
|
|
3007
|
+
}
|
|
3008
|
+
reads.push({
|
|
3009
|
+
path,
|
|
3010
|
+
...Number.isInteger(r.startLine) ? { startLine: r.startLine } : {},
|
|
3011
|
+
...Number.isInteger(r.endLine) ? { endLine: r.endLine } : {},
|
|
3012
|
+
chars: wholeNumber(r.chars),
|
|
3013
|
+
...r.refused ? { refused: true } : {}
|
|
3014
|
+
});
|
|
3015
|
+
}
|
|
3016
|
+
return {
|
|
3017
|
+
turns: wholeNumber(loop.turns),
|
|
3018
|
+
toolCalls: wholeNumber(loop.toolCalls),
|
|
3019
|
+
refused: wholeNumber(loop.refused),
|
|
3020
|
+
stopReason: loop.stopReason,
|
|
3021
|
+
...loop.fellBack ? { fellBack: true } : {},
|
|
3022
|
+
...loop.fallbackReason ? { fallbackReason: String(loop.fallbackReason).slice(0, MAX_FALLBACK_REASON_CHARS) } : {},
|
|
3023
|
+
reads,
|
|
3024
|
+
...cut ? { truncated: true } : {}
|
|
3025
|
+
};
|
|
3026
|
+
}
|
|
1916
3027
|
function buildTargetingRecord(targeting, contextLog) {
|
|
1917
3028
|
let truncated = false;
|
|
1918
3029
|
const note = (cut) => {
|
|
@@ -1925,6 +3036,7 @@ function buildTargetingRecord(targeting, contextLog) {
|
|
|
1925
3036
|
record2.noTargetReason = reason.slice(0, MAX_REASON_CHARS);
|
|
1926
3037
|
if (reason.length > MAX_REASON_CHARS) record2.noTargetReasonTruncated = true;
|
|
1927
3038
|
}
|
|
3039
|
+
if (targeting.loop) record2.loop = clampLoop(targeting.loop);
|
|
1928
3040
|
return record2;
|
|
1929
3041
|
}
|
|
1930
3042
|
const targets = [];
|
|
@@ -1934,12 +3046,12 @@ function buildTargetingRecord(targeting, contextLog) {
|
|
|
1934
3046
|
break;
|
|
1935
3047
|
}
|
|
1936
3048
|
const path = typeof t.path === "string" ? t.path.trim() : "";
|
|
1937
|
-
if (path.length === 0 || path.length >
|
|
3049
|
+
if (path.length === 0 || path.length > MAX_PATH_CHARS2) {
|
|
1938
3050
|
truncated = true;
|
|
1939
3051
|
continue;
|
|
1940
3052
|
}
|
|
1941
|
-
const contextPaths = clampList(t.contextPaths, MAX_CONTEXT_PATHS,
|
|
1942
|
-
const automationIds = clampList(t.automationIds, MAX_IDS_PER_TARGET,
|
|
3053
|
+
const contextPaths = clampList(t.contextPaths, MAX_CONTEXT_PATHS, MAX_PATH_CHARS2);
|
|
3054
|
+
const automationIds = clampList(t.automationIds, MAX_IDS_PER_TARGET, MAX_ID_CHARS2);
|
|
1943
3055
|
note(contextPaths.cut);
|
|
1944
3056
|
note(automationIds.cut);
|
|
1945
3057
|
targets.push({ path, kind: t.kind, contextPaths: contextPaths.kept, automationIds: automationIds.kept });
|
|
@@ -1947,7 +3059,7 @@ function buildTargetingRecord(targeting, contextLog) {
|
|
|
1947
3059
|
const skipped = clampList(
|
|
1948
3060
|
(targeting.skippedCases ?? []).map((c) => c.automationId),
|
|
1949
3061
|
MAX_SKIPPED_CASES,
|
|
1950
|
-
|
|
3062
|
+
MAX_ID_CHARS2
|
|
1951
3063
|
);
|
|
1952
3064
|
note(skipped.cut);
|
|
1953
3065
|
const codegenContext = [];
|
|
@@ -1957,16 +3069,16 @@ function buildTargetingRecord(targeting, contextLog) {
|
|
|
1957
3069
|
break;
|
|
1958
3070
|
}
|
|
1959
3071
|
const targetPath = typeof entry.targetPath === "string" ? entry.targetPath.trim() : "";
|
|
1960
|
-
if (targetPath.length === 0 || targetPath.length >
|
|
3072
|
+
if (targetPath.length === 0 || targetPath.length > MAX_PATH_CHARS2) {
|
|
1961
3073
|
truncated = true;
|
|
1962
3074
|
continue;
|
|
1963
3075
|
}
|
|
1964
|
-
const contextPaths = clampList(entry.contextPaths, MAX_CONTEXT_PATHS,
|
|
1965
|
-
const addedPaths = clampList(entry.addedPaths, MAX_ADDED_PATHS,
|
|
3076
|
+
const contextPaths = clampList(entry.contextPaths, MAX_CONTEXT_PATHS, MAX_PATH_CHARS2);
|
|
3077
|
+
const addedPaths = clampList(entry.addedPaths, MAX_ADDED_PATHS, MAX_PATH_CHARS2);
|
|
1966
3078
|
const skippedPaths = clampList(
|
|
1967
3079
|
(entry.skipped ?? []).map((s) => s.path),
|
|
1968
3080
|
MAX_SKIPPED_CONTEXT,
|
|
1969
|
-
|
|
3081
|
+
MAX_PATH_CHARS2
|
|
1970
3082
|
);
|
|
1971
3083
|
note(contextPaths.cut);
|
|
1972
3084
|
note(addedPaths.cut);
|
|
@@ -1987,6 +3099,7 @@ function buildTargetingRecord(targeting, contextLog) {
|
|
|
1987
3099
|
codegenContext
|
|
1988
3100
|
};
|
|
1989
3101
|
if (truncated) record.truncated = true;
|
|
3102
|
+
if (targeting.loop) record.loop = clampLoop(targeting.loop);
|
|
1990
3103
|
return record;
|
|
1991
3104
|
}
|
|
1992
3105
|
|
|
@@ -2099,6 +3212,10 @@ async function run(env2) {
|
|
|
2099
3212
|
outOfScope
|
|
2100
3213
|
);
|
|
2101
3214
|
targetingResult = targeting;
|
|
3215
|
+
for (const w of targeting.warnings ?? []) {
|
|
3216
|
+
warnings.push(w);
|
|
3217
|
+
console.warn(`QuikPilot targeting warning (not blocking): ${w}`);
|
|
3218
|
+
}
|
|
2102
3219
|
if (targeting.outcome === "TARGETED") {
|
|
2103
3220
|
for (const path of targeting.correctedKinds ?? []) {
|
|
2104
3221
|
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.`;
|