@quikit/quikpilot-runner 9.0.0 → 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 CHANGED
@@ -26,6 +26,16 @@ Releases follow [SemVer](https://semver.org/): a major version bump means the wo
26
26
  shape changed (inputs, permissions) and requires a new pull request regardless of this package's
27
27
  version; minor/patch releases are runner-logic fixes safe to pick up on the next re-bootstrap.
28
28
 
29
+ **9.1.0 — the code-writing step sees the files the changed file imports.** For every existing file a run changes (code or
30
+ test), the runner now reads that file's own imports (JavaScript and TypeScript, one hop, never what those files import)
31
+ and sends the repository files they point to as extra context. They come after the files targeting named and never in
32
+ place of them. Packages, path aliases (`@/…`), stylesheets and images are not followed; a denied path is never read; the
33
+ extra files share the existing size limits, and if the runtime still finds the context too large the call is retried
34
+ without them. The `codegen.context` log line now also carries `importedPaths`, what was skipped and why (`import: too
35
+ large`, `import: over budget`, `import: unreadable`, `import: denied`, `import: not in workspace`) and how many path
36
+ aliases were not followed. The workflow file is unchanged, so a repository on 9.0.0 picks this up with its next
37
+ "runner version" pull request.
38
+
29
39
  **9.0.0 — a failed run keeps its files.** When a run fails after code generation, the runner writes the files it
30
40
  generated, a copy of the last test report and a short `manifest.json` (run, targets, files, the reason it stopped;
31
41
  no credentials) to `quikpilot-failure/`, and the workflow's new `Keep the files of a failed run` step uploads that folder
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 import_node_path = require("node:path");
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 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
+
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 added = collectRunContext(target, targets, new Set(contextPaths), deniedPaths);
608
- const contextContents = [...namedContents, ...added.entries];
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, import_node_path.dirname)(targetPath), { recursive: true });
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 import_node_path2 = require("node:path");
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, import_node_path2.join)((0, import_node_path2.resolve)(workspaceRoot ?? "."), "package.json");
884
- const generated = results.find((r) => (0, import_node_path2.resolve)(r.targetPath) === packageJsonPath);
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, import_node_path2.resolve)(workspaceRoot ?? ".");
1286
+ const root = (0, import_node_path3.resolve)(workspaceRoot ?? ".");
955
1287
  for (const filename of ["tsconfig.json", "jsconfig.json"]) {
956
- const configPath = (0, import_node_path2.join)(root, filename);
957
- const generated = results.find((r) => (0, import_node_path2.resolve)(r.targetPath) === configPath);
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 import_node_path3 = require("node:path");
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, import_node_path3.join)(dir, entry);
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, import_node_path3.join)(dir, match) : null;
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, import_node_path3.join)(cwd, "test-results"), /\.xml$/) ?? firstXmlIn((0, import_node_path3.join)(cwd, "reports"), /^junit.*\.xml$/) ?? firstXmlIn((0, import_node_path3.join)(cwd, "target", "surefire-reports"), /\.xml$/);
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}`;
@@ -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 import_node_path4 = require("node:path");
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, import_node_path4.resolve)(cwd, path);
1452
- const rel = (0, import_node_path4.relative)(cwd, abs);
1453
- return rel === "" || rel.startsWith("..") || (0, import_node_path4.isAbsolute)(rel) ? null : abs;
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, import_node_path4.dirname)(abs); !(0, import_node_fs6.existsSync)(d); d = (0, import_node_path4.dirname)(d)) missing.push(d);
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 import_node_path5 = require("node:path");
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.0" : "dev";
1985
+ var RUNNER_VERSION = true ? "9.1.0" : "dev";
1654
1986
  function safeDestination(base, repoPath) {
1655
- if (!repoPath || (0, import_node_path5.isAbsolute)(repoPath)) return null;
1656
- const destination = (0, import_node_path5.resolve)(base, repoPath);
1657
- const rel = (0, import_node_path5.relative)(base, destination);
1658
- if (!rel || rel.startsWith("..") || (0, import_node_path5.isAbsolute)(rel)) return null;
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, import_node_path5.resolve)(input.cwd, FAILURE_ARTIFACT_DIR);
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, import_node_path5.dirname)(destination), { recursive: true });
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, import_node_path5.join)(base, REPORT_COPY), input.testReport);
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, import_node_path5.join)(base, "manifest.json"), text);
1705
- return { written: (0, import_node_fs7.existsSync)((0, import_node_path5.join)(base, "manifest.json")) };
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,7 @@ function describeScopeViolations(check) {
1786
2118
  }
1787
2119
 
1788
2120
  // src/targeting.ts
1789
- var import_node_path6 = require("node:path");
2121
+ var import_node_path7 = require("node:path");
1790
2122
  var CONTRACT_VERSION2 = "test-targets-1";
1791
2123
  function listWorkspaceFiles(workspaceRoot) {
1792
2124
  return listFiles(workspaceRoot ?? ".");
@@ -1815,9 +2147,9 @@ function validateTarget(target, workspaceRoot, workspaceListing, deniedPaths) {
1815
2147
  }
1816
2148
  return;
1817
2149
  }
1818
- const root = (0, import_node_path6.resolve)(workspaceRoot ?? ".");
1819
- const rel = (0, import_node_path6.relative)(root, (0, import_node_path6.resolve)(target.path));
1820
- if (rel.startsWith("..") || (0, import_node_path6.isAbsolute)(rel)) {
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)) {
1821
2153
  throw new Error(
1822
2154
  `pilot.targeting named a NEW_FILE path that escapes the workspace root: ${target.path}`
1823
2155
  );