@h-rig/runtime 0.0.6-alpha.3 → 0.0.6-alpha.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/bin/rig-agent-dispatch.js +1165 -785
  2. package/dist/bin/rig-agent.js +458 -389
  3. package/dist/src/control-plane/agent-wrapper.js +1191 -504
  4. package/dist/src/control-plane/authority-files.js +12 -6
  5. package/dist/src/control-plane/harness-main.js +2186 -1786
  6. package/dist/src/control-plane/hooks/completion-verification.js +2084 -1019
  7. package/dist/src/control-plane/hooks/inject-context.js +193 -139
  8. package/dist/src/control-plane/hooks/submodule-branch.js +603 -545
  9. package/dist/src/control-plane/hooks/task-runtime-start.js +603 -545
  10. package/dist/src/control-plane/materialize-task-config.js +64 -8
  11. package/dist/src/control-plane/native/git-ops.js +90 -64
  12. package/dist/src/control-plane/native/harness-cli.js +1989 -682
  13. package/dist/src/control-plane/native/pr-automation.js +1657 -54
  14. package/dist/src/control-plane/native/pr-review-gate.js +1455 -0
  15. package/dist/src/control-plane/native/repo-ops.js +3 -0
  16. package/dist/src/control-plane/native/run-ops.js +39 -13
  17. package/dist/src/control-plane/native/task-ops.js +1819 -527
  18. package/dist/src/control-plane/native/validator.js +163 -109
  19. package/dist/src/control-plane/native/verifier.js +1616 -323
  20. package/dist/src/control-plane/native/workspace-ops.js +12 -6
  21. package/dist/src/control-plane/pi-sessiond/bin.js +793 -0
  22. package/dist/src/control-plane/pi-sessiond/client.js +41 -0
  23. package/dist/src/control-plane/pi-sessiond/event-hub.js +59 -0
  24. package/dist/src/control-plane/pi-sessiond/extension-ui-context.js +198 -0
  25. package/dist/src/control-plane/pi-sessiond/launcher.js +173 -0
  26. package/dist/src/control-plane/pi-sessiond/server.js +802 -0
  27. package/dist/src/control-plane/pi-sessiond/session-service.js +540 -0
  28. package/dist/src/control-plane/pi-sessiond/types.js +1 -0
  29. package/dist/src/control-plane/plugin-host-context.js +54 -0
  30. package/dist/src/control-plane/runtime/image/fingerprint-sidecar.js +3 -0
  31. package/dist/src/control-plane/runtime/image/index.js +3 -0
  32. package/dist/src/control-plane/runtime/image-fingerprint-sidecar.js +3 -0
  33. package/dist/src/control-plane/runtime/image.js +3 -0
  34. package/dist/src/control-plane/runtime/index.js +517 -722
  35. package/dist/src/control-plane/runtime/isolation/home.js +28 -6
  36. package/dist/src/control-plane/runtime/isolation/index.js +541 -461
  37. package/dist/src/control-plane/runtime/isolation/runner.js +28 -6
  38. package/dist/src/control-plane/runtime/isolation/shared.js +9 -6
  39. package/dist/src/control-plane/runtime/isolation.js +541 -461
  40. package/dist/src/control-plane/runtime/plugin-mode.js +3 -27
  41. package/dist/src/control-plane/runtime/queue.js +458 -385
  42. package/dist/src/control-plane/runtime/snapshot/task-run.js +3 -0
  43. package/dist/src/control-plane/runtime/task-run-snapshot.js +3 -0
  44. package/dist/src/control-plane/skill-materializer.js +46 -0
  45. package/dist/src/control-plane/tasks/source-aware-task-config-source.js +14 -2
  46. package/dist/src/control-plane/tasks/source-lifecycle.js +86 -32
  47. package/dist/src/index.js +27 -298
  48. package/dist/src/layout.js +12 -7
  49. package/dist/src/local-server.js +20 -14
  50. package/native/darwin-arm64/rig-git +0 -0
  51. package/native/darwin-arm64/rig-git.build-manifest.json +1 -1
  52. package/native/darwin-arm64/rig-shell +0 -0
  53. package/native/darwin-arm64/rig-shell.build-manifest.json +1 -1
  54. package/native/darwin-arm64/rig-tools +0 -0
  55. package/native/darwin-arm64/rig-tools.build-manifest.json +1 -1
  56. package/native/darwin-arm64/runtime-native.dylib +0 -0
  57. package/package.json +8 -6
  58. package/dist/src/control-plane/runtime/plugins.js +0 -1131
  59. package/dist/src/plugins.js +0 -329
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  // packages/runtime/src/control-plane/native/task-ops.ts
3
- import { appendFileSync, existsSync as existsSync20, mkdirSync as mkdirSync10, readFileSync as readFileSync10, writeFileSync as writeFileSync10 } from "fs";
4
- import { resolve as resolve23 } from "path";
3
+ import { appendFileSync, existsSync as existsSync21, mkdirSync as mkdirSync11, readFileSync as readFileSync11, writeFileSync as writeFileSync11 } from "fs";
4
+ import { resolve as resolve24 } from "path";
5
5
 
6
6
  // packages/runtime/src/build-time-config.ts
7
7
  function normalizeBuildConfig(value) {
@@ -651,6 +651,49 @@ function safeReadJson(path) {
651
651
  }
652
652
  }
653
653
 
654
+ // packages/runtime/src/control-plane/skill-materializer.ts
655
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, readdirSync, rmSync, writeFileSync as writeFileSync3 } from "fs";
656
+ import { resolve as resolve6 } from "path";
657
+ import { loadSkill } from "@rig/skill-loader";
658
+ var MARKER_FILENAME = ".rig-plugin";
659
+ function skillDirName(id) {
660
+ return id.replace(/[^a-zA-Z0-9._-]+/g, "-");
661
+ }
662
+ async function materializeSkills(projectRoot, entries) {
663
+ const skillsRoot = resolve6(projectRoot, ".pi", "skills");
664
+ if (existsSync4(skillsRoot)) {
665
+ for (const name of readdirSync(skillsRoot)) {
666
+ const dir = resolve6(skillsRoot, name);
667
+ if (existsSync4(resolve6(dir, MARKER_FILENAME))) {
668
+ rmSync(dir, { recursive: true, force: true });
669
+ }
670
+ }
671
+ }
672
+ const written = [];
673
+ for (const { pluginName, skill } of entries) {
674
+ const sourcePath = resolve6(projectRoot, skill.path);
675
+ if (!existsSync4(sourcePath)) {
676
+ console.warn(`[plugin-host] skill "${skill.id}" from plugin "${pluginName}" not materialized: ${sourcePath} does not exist`);
677
+ continue;
678
+ }
679
+ let body;
680
+ try {
681
+ await loadSkill(sourcePath);
682
+ body = readFileSync3(sourcePath, "utf-8");
683
+ } catch (err) {
684
+ console.warn(`[plugin-host] skill "${skill.id}" from plugin "${pluginName}" not materialized: ${err instanceof Error ? err.message : err}`);
685
+ continue;
686
+ }
687
+ const dir = resolve6(skillsRoot, skillDirName(skill.id));
688
+ mkdirSync3(dir, { recursive: true });
689
+ writeFileSync3(resolve6(dir, "SKILL.md"), body, "utf-8");
690
+ writeFileSync3(resolve6(dir, MARKER_FILENAME), `${JSON.stringify({ plugin: pluginName, skillId: skill.id }, null, 2)}
691
+ `, "utf-8");
692
+ written.push({ id: skill.id, pluginName, directory: dir });
693
+ }
694
+ return written;
695
+ }
696
+
654
697
  // packages/runtime/src/control-plane/plugin-host-context.ts
655
698
  async function buildPluginHostContext(projectRoot) {
656
699
  let config;
@@ -687,6 +730,17 @@ async function buildPluginHostContext(projectRoot) {
687
730
  } catch (err) {
688
731
  console.warn(`[plugin-host] hook materialization failed: ${err instanceof Error ? err.message : err}`);
689
732
  }
733
+ try {
734
+ const skillEntries = config.plugins.flatMap((plugin) => (plugin.contributes?.skills ?? []).map((skill) => ({
735
+ pluginName: plugin.name,
736
+ skill
737
+ })));
738
+ if (skillEntries.length > 0) {
739
+ await materializeSkills(projectRoot, skillEntries);
740
+ }
741
+ } catch (err) {
742
+ console.warn(`[plugin-host] skill materialization failed: ${err instanceof Error ? err.message : err}`);
743
+ }
690
744
  return {
691
745
  config,
692
746
  pluginHost,
@@ -700,12 +754,12 @@ async function buildPluginHostContext(projectRoot) {
700
754
 
701
755
  // packages/runtime/src/control-plane/tasks/source-aware-task-config-source.ts
702
756
  import { spawnSync } from "child_process";
703
- import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync, statSync, writeFileSync as writeFileSync3 } from "fs";
704
- import { basename as basename3, join as join2, resolve as resolve7 } from "path";
757
+ import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync2, statSync, writeFileSync as writeFileSync4 } from "fs";
758
+ import { basename as basename3, join as join2, resolve as resolve8 } from "path";
705
759
 
706
760
  // packages/runtime/src/control-plane/tasks/legacy-task-config-source.ts
707
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
708
- import { resolve as resolve6 } from "path";
761
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
762
+ import { resolve as resolve7 } from "path";
709
763
 
710
764
  // packages/runtime/src/control-plane/tasks/task-record-reader.ts
711
765
  async function findTaskById(reader, id) {
@@ -728,7 +782,7 @@ class LegacyTaskConfigReadError extends Error {
728
782
  }
729
783
  }
730
784
  function createLegacyTaskConfigRecordReader(projectRoot, options = {}) {
731
- const configPath = options.configPath ?? resolve6(projectRoot, ".rig", "task-config.json");
785
+ const configPath = options.configPath ?? resolve7(projectRoot, ".rig", "task-config.json");
732
786
  const reader = {
733
787
  async listTasks() {
734
788
  return readLegacyTaskRecords(projectRoot, configPath);
@@ -739,8 +793,8 @@ function createLegacyTaskConfigRecordReader(projectRoot, options = {}) {
739
793
  };
740
794
  return reader;
741
795
  }
742
- function readLegacyTaskRecords(projectRoot, configPath = resolve6(projectRoot, ".rig", "task-config.json")) {
743
- if (!existsSync4(configPath)) {
796
+ function readLegacyTaskRecords(projectRoot, configPath = resolve7(projectRoot, ".rig", "task-config.json")) {
797
+ if (!existsSync5(configPath)) {
744
798
  return [];
745
799
  }
746
800
  const rawConfig = readLegacyTaskConfigJson(projectRoot, configPath);
@@ -748,7 +802,7 @@ function readLegacyTaskRecords(projectRoot, configPath = resolve6(projectRoot, "
748
802
  }
749
803
  function readLegacyTaskConfigJson(projectRoot, configPath) {
750
804
  try {
751
- const parsed = JSON.parse(readFileSync3(configPath, "utf8"));
805
+ const parsed = JSON.parse(readFileSync4(configPath, "utf8"));
752
806
  if (isPlainRecord(parsed)) {
753
807
  return parsed;
754
808
  }
@@ -832,7 +886,7 @@ function isPlainRecord(candidate) {
832
886
  var STATUS_LABELS = new Set(["ready", "blocked", "in-progress", "under-review", "failed", "cancelled"]);
833
887
  var FILE_TASK_PATTERN = /\.(task\.)?json$/;
834
888
  function createSourceAwareTaskConfigRecordReader(projectRoot, options = {}) {
835
- const configPath = options.configPath ?? resolve7(projectRoot, ".rig", "task-config.json");
889
+ const configPath = options.configPath ?? resolve8(projectRoot, ".rig", "task-config.json");
836
890
  const legacy = createLegacyTaskConfigRecordReader(projectRoot, { configPath });
837
891
  const spawnFn = options.spawn ?? spawnSync;
838
892
  const ghBinary = options.ghBinary ?? "gh";
@@ -915,10 +969,10 @@ function readMaterializedTaskMetadata(entry) {
915
969
  return metadata;
916
970
  }
917
971
  function readConfiguredFilesTaskSourcePath(projectRoot) {
918
- const jsonPath = resolve7(projectRoot, "rig.config.json");
919
- if (existsSync5(jsonPath)) {
972
+ const jsonPath = resolve8(projectRoot, "rig.config.json");
973
+ if (existsSync6(jsonPath)) {
920
974
  try {
921
- const parsed = JSON.parse(readFileSync4(jsonPath, "utf8"));
975
+ const parsed = JSON.parse(readFileSync5(jsonPath, "utf8"));
922
976
  if (isPlainRecord2(parsed) && isPlainRecord2(parsed.taskSource)) {
923
977
  const source = parsed.taskSource;
924
978
  return source.kind === "files" && typeof source.path === "string" ? source.path : null;
@@ -927,12 +981,12 @@ function readConfiguredFilesTaskSourcePath(projectRoot) {
927
981
  return null;
928
982
  }
929
983
  }
930
- const tsPath = resolve7(projectRoot, "rig.config.ts");
931
- if (!existsSync5(tsPath)) {
984
+ const tsPath = resolve8(projectRoot, "rig.config.ts");
985
+ if (!existsSync6(tsPath)) {
932
986
  return null;
933
987
  }
934
988
  try {
935
- const source = readFileSync4(tsPath, "utf8");
989
+ const source = readFileSync5(tsPath, "utf8");
936
990
  const taskSourceBlock = source.match(/taskSource\s*:\s*\{[\s\S]*?\}/m)?.[0] ?? "";
937
991
  const kind = taskSourceBlock.match(/kind\s*:\s*["']([^"']+)["']/)?.[1];
938
992
  if (kind !== "files") {
@@ -952,10 +1006,10 @@ function readRawTaskEntry(configPath, taskId) {
952
1006
  return isPlainRecord2(entry) ? entry : null;
953
1007
  }
954
1008
  function readRawTaskConfig(configPath) {
955
- if (!existsSync5(configPath)) {
1009
+ if (!existsSync6(configPath)) {
956
1010
  return null;
957
1011
  }
958
- const parsed = JSON.parse(readFileSync4(configPath, "utf8"));
1012
+ const parsed = JSON.parse(readFileSync5(configPath, "utf8"));
959
1013
  return isPlainRecord2(parsed) ? parsed : null;
960
1014
  }
961
1015
  function stripLegacyTaskConfigMetadata2(raw) {
@@ -963,12 +1017,12 @@ function stripLegacyTaskConfigMetadata2(raw) {
963
1017
  return tasks;
964
1018
  }
965
1019
  function listFileBackedTasks(projectRoot, sourcePath) {
966
- const directory = resolve7(projectRoot, sourcePath);
967
- if (!existsSync5(directory)) {
1020
+ const directory = resolve8(projectRoot, sourcePath);
1021
+ if (!existsSync6(directory)) {
968
1022
  return [];
969
1023
  }
970
1024
  const tasks = [];
971
- for (const name of readdirSync(directory)) {
1025
+ for (const name of readdirSync2(directory)) {
972
1026
  if (!FILE_TASK_PATTERN.test(name))
973
1027
  continue;
974
1028
  const inferredId = basename3(name).replace(FILE_TASK_PATTERN, "");
@@ -979,11 +1033,11 @@ function listFileBackedTasks(projectRoot, sourcePath) {
979
1033
  return tasks;
980
1034
  }
981
1035
  function readFileBackedTask(projectRoot, sourcePath, taskId, rawEntry) {
982
- const file = findFileBackedTaskFile(resolve7(projectRoot, sourcePath), taskId);
1036
+ const file = findFileBackedTaskFile(resolve8(projectRoot, sourcePath), taskId);
983
1037
  if (!file) {
984
1038
  return null;
985
1039
  }
986
- const raw = JSON.parse(readFileSync4(file, "utf8"));
1040
+ const raw = JSON.parse(readFileSync5(file, "utf8"));
987
1041
  if (!isPlainRecord2(raw)) {
988
1042
  return null;
989
1043
  }
@@ -996,17 +1050,17 @@ function readFileBackedTask(projectRoot, sourcePath, taskId, rawEntry) {
996
1050
  };
997
1051
  }
998
1052
  function findFileBackedTaskFile(directory, taskId) {
999
- if (!existsSync5(directory)) {
1053
+ if (!existsSync6(directory)) {
1000
1054
  return null;
1001
1055
  }
1002
- for (const name of readdirSync(directory)) {
1056
+ for (const name of readdirSync2(directory)) {
1003
1057
  if (!FILE_TASK_PATTERN.test(name))
1004
1058
  continue;
1005
1059
  const file = join2(directory, name);
1006
1060
  try {
1007
1061
  if (!statSync(file).isFile())
1008
1062
  continue;
1009
- const raw = JSON.parse(readFileSync4(file, "utf8"));
1063
+ const raw = JSON.parse(readFileSync5(file, "utf8"));
1010
1064
  const inferredId = basename3(file).replace(FILE_TASK_PATTERN, "");
1011
1065
  const id = isPlainRecord2(raw) && typeof raw.id === "string" ? raw.id : inferredId;
1012
1066
  if (id === taskId) {
@@ -1089,8 +1143,8 @@ function githubStatusFor(issue) {
1089
1143
  return "open";
1090
1144
  }
1091
1145
  function selectedGitHubEnv() {
1092
- const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() ?? "";
1093
- return { GH_TOKEN: token, GITHUB_TOKEN: token };
1146
+ const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || "";
1147
+ return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
1094
1148
  }
1095
1149
  function ghSpawnOptions() {
1096
1150
  return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
@@ -1166,8 +1220,8 @@ async function readConfiguredTaskSourceTask(projectRoot, taskId) {
1166
1220
  }
1167
1221
 
1168
1222
  // packages/runtime/src/control-plane/native/task-state.ts
1169
- import { existsSync as existsSync13, readFileSync as readFileSync8, readdirSync as readdirSync2, statSync as statSync3, writeFileSync as writeFileSync5 } from "fs";
1170
- import { basename as basename6, resolve as resolve15 } from "path";
1223
+ import { existsSync as existsSync14, readFileSync as readFileSync9, readdirSync as readdirSync3, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
1224
+ import { basename as basename6, resolve as resolve16 } from "path";
1171
1225
 
1172
1226
  // packages/runtime/src/control-plane/state-sync/types.ts
1173
1227
  var SUPPORTED_TASK_STATE_SCHEMA_VERSION = 1;
@@ -1275,30 +1329,30 @@ function readTaskStateMetadataEnvelope(raw) {
1275
1329
  };
1276
1330
  }
1277
1331
  // packages/runtime/src/control-plane/state-sync/read.ts
1278
- import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
1279
- import { resolve as resolve14 } from "path";
1332
+ import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
1333
+ import { resolve as resolve15 } from "path";
1280
1334
 
1281
1335
  // packages/runtime/src/control-plane/native/git-native.ts
1282
- import { chmodSync, copyFileSync, existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, renameSync, rmSync, writeFileSync as writeFileSync4 } from "fs";
1336
+ import { chmodSync, copyFileSync, existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
1283
1337
  import { tmpdir as tmpdir3 } from "os";
1284
- import { dirname as dirname5, isAbsolute, resolve as resolve8 } from "path";
1338
+ import { dirname as dirname5, isAbsolute, resolve as resolve9 } from "path";
1285
1339
  import { createHash } from "crypto";
1286
1340
  function isTextTreeCommitUpdate(update) {
1287
1341
  return typeof update.content === "string";
1288
1342
  }
1289
- var sharedGitNativeOutputDir = resolve8(tmpdir3(), "rig-native");
1290
- var sharedGitNativeOutputPath = resolve8(sharedGitNativeOutputDir, `rig-git-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
1343
+ var sharedGitNativeOutputDir = resolve9(tmpdir3(), "rig-native");
1344
+ var sharedGitNativeOutputPath = resolve9(sharedGitNativeOutputDir, `rig-git-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`);
1291
1345
  var trackerCommandUsageProbe = "usage: rig-git fetch-ref <repo-path> <remote> <branch>";
1292
1346
  function temporaryGitBinaryOutputPath(outputPath) {
1293
1347
  const suffix = process.platform === "win32" ? ".exe" : "";
1294
- return resolve8(dirname5(outputPath), `.rig-git-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}${suffix}`);
1348
+ return resolve9(dirname5(outputPath), `.rig-git-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}${suffix}`);
1295
1349
  }
1296
1350
  function publishGitBinary(tempOutputPath, outputPath) {
1297
1351
  try {
1298
1352
  renameSync(tempOutputPath, outputPath);
1299
1353
  } catch (error) {
1300
- if (process.platform === "win32" && existsSync6(outputPath)) {
1301
- rmSync(outputPath, { force: true });
1354
+ if (process.platform === "win32" && existsSync7(outputPath)) {
1355
+ rmSync2(outputPath, { force: true });
1302
1356
  renameSync(tempOutputPath, outputPath);
1303
1357
  return;
1304
1358
  }
@@ -1313,22 +1367,22 @@ function rigGitSourceCandidates() {
1313
1367
  const cwd = process.cwd()?.trim() || "";
1314
1368
  const projectRoot = process.env.PROJECT_RIG_ROOT?.trim() || "";
1315
1369
  const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim() || "";
1316
- const moduleRelativeSource = resolve8(import.meta.dir, "../../../native/rig-git.zig");
1370
+ const moduleRelativeSource = resolve9(import.meta.dir, "../../../native/rig-git.zig");
1317
1371
  return [...new Set([
1318
1372
  process.env.RIG_NATIVE_GIT_SOURCE?.trim() || "",
1319
1373
  moduleRelativeSource,
1320
- projectRoot ? resolve8(projectRoot, "packages/runtime/native/rig-git.zig") : "",
1321
- hostProjectRoot ? resolve8(hostProjectRoot, "packages/runtime/native/rig-git.zig") : "",
1322
- cwd ? resolve8(cwd, "packages/runtime/native/rig-git.zig") : "",
1323
- execDir ? resolve8(execDir, "..", "..", "packages/runtime/native/rig-git.zig") : "",
1324
- execDir ? resolve8(execDir, "..", "native", "rig-git.zig") : ""
1374
+ projectRoot ? resolve9(projectRoot, "packages/runtime/native/rig-git.zig") : "",
1375
+ hostProjectRoot ? resolve9(hostProjectRoot, "packages/runtime/native/rig-git.zig") : "",
1376
+ cwd ? resolve9(cwd, "packages/runtime/native/rig-git.zig") : "",
1377
+ execDir ? resolve9(execDir, "..", "..", "packages/runtime/native/rig-git.zig") : "",
1378
+ execDir ? resolve9(execDir, "..", "native", "rig-git.zig") : ""
1325
1379
  ].filter(Boolean))];
1326
1380
  }
1327
1381
  function nativePackageBinaryCandidates(fromDir, fileName) {
1328
1382
  const candidates = [];
1329
- let cursor = resolve8(fromDir);
1383
+ let cursor = resolve9(fromDir);
1330
1384
  for (let index = 0;index < 8; index += 1) {
1331
- candidates.push(resolve8(cursor, "native", `${process.platform}-${process.arch}`, fileName), resolve8(cursor, "native", `${process.platform}-${process.arch}`, "bin", fileName), resolve8(cursor, "native", fileName), resolve8(cursor, "native", "bin", fileName));
1385
+ candidates.push(resolve9(cursor, "native", `${process.platform}-${process.arch}`, fileName), resolve9(cursor, "native", `${process.platform}-${process.arch}`, "bin", fileName), resolve9(cursor, "native", fileName), resolve9(cursor, "native", "bin", fileName));
1332
1386
  const parent = dirname5(cursor);
1333
1387
  if (parent === cursor)
1334
1388
  break;
@@ -1343,15 +1397,15 @@ function rigGitBinaryCandidates() {
1343
1397
  return [...new Set([
1344
1398
  explicit,
1345
1399
  ...nativePackageBinaryCandidates(import.meta.dir, fileName),
1346
- execDir ? resolve8(execDir, fileName) : "",
1347
- execDir ? resolve8(execDir, "..", fileName) : "",
1348
- execDir ? resolve8(execDir, "..", "bin", fileName) : "",
1400
+ execDir ? resolve9(execDir, fileName) : "",
1401
+ execDir ? resolve9(execDir, "..", fileName) : "",
1402
+ execDir ? resolve9(execDir, "..", "bin", fileName) : "",
1349
1403
  sharedGitNativeOutputPath
1350
1404
  ].filter(Boolean))];
1351
1405
  }
1352
1406
  function resolveGitSourcePath() {
1353
1407
  for (const candidate of rigGitSourceCandidates()) {
1354
- if (candidate && existsSync6(candidate)) {
1408
+ if (candidate && existsSync7(candidate)) {
1355
1409
  return candidate;
1356
1410
  }
1357
1411
  }
@@ -1362,7 +1416,7 @@ function resolveGitBinaryPath() {
1362
1416
  return null;
1363
1417
  }
1364
1418
  for (const candidate of rigGitBinaryCandidates()) {
1365
- if (candidate && existsSync6(candidate)) {
1419
+ if (candidate && existsSync7(candidate)) {
1366
1420
  return candidate;
1367
1421
  }
1368
1422
  }
@@ -1392,18 +1446,18 @@ function nativeBuildManifestPath(outputPath) {
1392
1446
  return `${outputPath}.build-manifest.json`;
1393
1447
  }
1394
1448
  function hasMatchingNativeBuildManifestSync(manifestPath, buildKey) {
1395
- if (!existsSync6(manifestPath)) {
1449
+ if (!existsSync7(manifestPath)) {
1396
1450
  return false;
1397
1451
  }
1398
1452
  try {
1399
- const manifest = JSON.parse(readFileSync5(manifestPath, "utf8"));
1453
+ const manifest = JSON.parse(readFileSync6(manifestPath, "utf8"));
1400
1454
  return manifest.version === 1 && manifest.buildKey === buildKey;
1401
1455
  } catch {
1402
1456
  return false;
1403
1457
  }
1404
1458
  }
1405
1459
  function sha256FileSync(path) {
1406
- return createHash("sha256").update(readFileSync5(path)).digest("hex");
1460
+ return createHash("sha256").update(readFileSync6(path)).digest("hex");
1407
1461
  }
1408
1462
  function ensureRigGitBinaryPathSync(outputPath = preferredGitBinaryOutputPath()) {
1409
1463
  if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
@@ -1421,7 +1475,7 @@ function ensureRigGitBinaryPathSync(outputPath = preferredGitBinaryOutputPath())
1421
1475
  if (!zigBinary) {
1422
1476
  throw new Error("zig is required to build native Rig git tools.");
1423
1477
  }
1424
- mkdirSync3(dirname5(outputPath), { recursive: true });
1478
+ mkdirSync4(dirname5(outputPath), { recursive: true });
1425
1479
  const sourceDigest = sha256FileSync(sourcePath);
1426
1480
  const buildKey = JSON.stringify({
1427
1481
  version: 1,
@@ -1432,7 +1486,7 @@ function ensureRigGitBinaryPathSync(outputPath = preferredGitBinaryOutputPath())
1432
1486
  sourceDigest
1433
1487
  });
1434
1488
  const manifestPath = nativeBuildManifestPath(outputPath);
1435
- const needsBuild = !existsSync6(outputPath) || !hasMatchingNativeBuildManifestSync(manifestPath, buildKey) || !binarySupportsTrackerCommandsSync(outputPath);
1489
+ const needsBuild = !existsSync7(outputPath) || !hasMatchingNativeBuildManifestSync(manifestPath, buildKey) || !binarySupportsTrackerCommandsSync(outputPath);
1436
1490
  if (!needsBuild) {
1437
1491
  chmodSync(outputPath, 493);
1438
1492
  return outputPath;
@@ -1450,7 +1504,7 @@ function ensureRigGitBinaryPathSync(outputPath = preferredGitBinaryOutputPath())
1450
1504
  stdout: "pipe",
1451
1505
  stderr: "pipe"
1452
1506
  });
1453
- if (build.exitCode !== 0 || !existsSync6(tempOutputPath)) {
1507
+ if (build.exitCode !== 0 || !existsSync7(tempOutputPath)) {
1454
1508
  const stderr = build.stderr.toString().trim();
1455
1509
  const stdout = build.stdout.toString().trim();
1456
1510
  const details = [stderr, stdout].filter(Boolean).join(`
@@ -1458,17 +1512,17 @@ function ensureRigGitBinaryPathSync(outputPath = preferredGitBinaryOutputPath())
1458
1512
  throw new Error(`Failed to build native Rig git tools: ${details || `zig exited with code ${build.exitCode}`}`);
1459
1513
  }
1460
1514
  chmodSync(tempOutputPath, 493);
1461
- if (existsSync6(outputPath) && hasMatchingNativeBuildManifestSync(manifestPath, buildKey)) {
1462
- rmSync(tempOutputPath, { force: true });
1515
+ if (existsSync7(outputPath) && hasMatchingNativeBuildManifestSync(manifestPath, buildKey)) {
1516
+ rmSync2(tempOutputPath, { force: true });
1463
1517
  chmodSync(outputPath, 493);
1464
1518
  return outputPath;
1465
1519
  }
1466
1520
  publishGitBinary(tempOutputPath, outputPath);
1467
1521
  if (!binarySupportsTrackerCommandsSync(outputPath)) {
1468
- rmSync(outputPath, { force: true });
1522
+ rmSync2(outputPath, { force: true });
1469
1523
  throw new Error("Failed to build native Rig git tools: tracker command probe failed");
1470
1524
  }
1471
- writeFileSync4(manifestPath, `${JSON.stringify({ version: 1, buildKey }, null, 2)}
1525
+ writeFileSync5(manifestPath, `${JSON.stringify({ version: 1, buildKey }, null, 2)}
1472
1526
  `, "utf8");
1473
1527
  return outputPath;
1474
1528
  }
@@ -1490,7 +1544,7 @@ function runGitNative(command, args) {
1490
1544
  }
1491
1545
  } else {
1492
1546
  const explicitBinaryPath = process.env.RIG_NATIVE_GIT_BIN?.trim() || "";
1493
- binaryPath = explicitBinaryPath && existsSync6(explicitBinaryPath) ? explicitBinaryPath : !explicitBinaryPath ? resolveGitBinaryPath() : null;
1547
+ binaryPath = explicitBinaryPath && existsSync7(explicitBinaryPath) ? explicitBinaryPath : !explicitBinaryPath ? resolveGitBinaryPath() : null;
1494
1548
  if (!binaryPath) {
1495
1549
  try {
1496
1550
  binaryPath = ensureRigGitBinaryPathSync(preferredGitBinaryOutputPath());
@@ -1546,14 +1600,14 @@ function nativeFetchRef(repoPath, remote, branch) {
1546
1600
  return requireGitNativeString("fetch-ref", [repoPath, remote, branch]);
1547
1601
  }
1548
1602
  function nativeReadBlobAtRef(repoPath, ref, path) {
1549
- const requestDir = resolve8(sharedGitNativeOutputDir, "reads", `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
1550
- mkdirSync3(requestDir, { recursive: true });
1551
- const outputPath = resolve8(requestDir, "blob.txt");
1603
+ const requestDir = resolve9(sharedGitNativeOutputDir, "reads", `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
1604
+ mkdirSync4(requestDir, { recursive: true });
1605
+ const outputPath = resolve9(requestDir, "blob.txt");
1552
1606
  try {
1553
1607
  requireGitNative("read-blob-at-ref", [repoPath, ref, path, outputPath]);
1554
- return readFileSync5(outputPath, "utf8");
1608
+ return readFileSync6(outputPath, "utf8");
1555
1609
  } finally {
1556
- rmSync(requestDir, { recursive: true, force: true });
1610
+ rmSync2(requestDir, { recursive: true, force: true });
1557
1611
  }
1558
1612
  }
1559
1613
  function serializeTreeCommitUpdates(updates) {
@@ -1572,16 +1626,16 @@ function buildTreeCommitUpdatesJson(updates) {
1572
1626
  `;
1573
1627
  }
1574
1628
  function nativeWriteTreeCommit(repoPath, baseRef, updates, message) {
1575
- const requestDir = resolve8(sharedGitNativeOutputDir, "requests", `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
1576
- mkdirSync3(requestDir, { recursive: true });
1577
- const messagePath = resolve8(requestDir, "message.txt");
1578
- const updatesPath = resolve8(requestDir, "updates.json");
1629
+ const requestDir = resolve9(sharedGitNativeOutputDir, "requests", `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
1630
+ mkdirSync4(requestDir, { recursive: true });
1631
+ const messagePath = resolve9(requestDir, "message.txt");
1632
+ const updatesPath = resolve9(requestDir, "updates.json");
1579
1633
  try {
1580
- writeFileSync4(messagePath, message, "utf8");
1581
- writeFileSync4(updatesPath, buildTreeCommitUpdatesJson(updates), "utf8");
1634
+ writeFileSync5(messagePath, message, "utf8");
1635
+ writeFileSync5(updatesPath, buildTreeCommitUpdatesJson(updates), "utf8");
1582
1636
  return requireGitNativeString("write-tree-commit", [repoPath, baseRef, messagePath, updatesPath]);
1583
1637
  } finally {
1584
- rmSync(requestDir, { recursive: true, force: true });
1638
+ rmSync2(requestDir, { recursive: true, force: true });
1585
1639
  }
1586
1640
  }
1587
1641
  function nativePushRefWithLease(repoPath, localOid, remoteRef, expectedOldOid, remote = "origin") {
@@ -1596,35 +1650,35 @@ function nativePushRefWithLease(repoPath, localOid, remoteRef, expectedOldOid, r
1596
1650
 
1597
1651
  // packages/runtime/src/control-plane/native/utils.ts
1598
1652
  import { ptr as ptr2 } from "bun:ffi";
1599
- import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
1600
- import { resolve as resolve11 } from "path";
1653
+ import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
1654
+ import { resolve as resolve12 } from "path";
1601
1655
 
1602
1656
  // packages/runtime/src/layout.ts
1603
- import { existsSync as existsSync7 } from "fs";
1604
- import { basename as basename4, dirname as dirname6, resolve as resolve9 } from "path";
1657
+ import { existsSync as existsSync8 } from "fs";
1658
+ import { basename as basename4, dirname as dirname6, resolve as resolve10 } from "path";
1605
1659
  var RIG_DEFINITION_DIRNAME = "rig";
1606
1660
  var RIG_ARTIFACTS_DIRNAME = "artifacts";
1607
1661
  function resolveMonorepoRoot(projectRoot) {
1608
- const normalizedProjectRoot = resolve9(projectRoot);
1662
+ const normalizedProjectRoot = resolve10(projectRoot);
1609
1663
  const explicit = process.env.MONOREPO_ROOT?.trim();
1610
1664
  if (explicit) {
1611
- const explicitRoot = resolve9(explicit);
1665
+ const explicitRoot = resolve10(explicit);
1612
1666
  const explicitParent = dirname6(explicitRoot);
1613
1667
  if (basename4(explicitParent) === ".worktrees") {
1614
1668
  const owner = dirname6(explicitParent);
1615
- const ownerHasGit = existsSync7(resolve9(owner, ".git"));
1616
- const ownerHasTaskConfig = existsSync7(resolve9(owner, ".rig", "task-config.json"));
1617
- const ownerHasRigConfig = existsSync7(resolve9(owner, "rig.config.ts"));
1669
+ const ownerHasGit = existsSync8(resolve10(owner, ".git"));
1670
+ const ownerHasTaskConfig = existsSync8(resolve10(owner, ".rig", "task-config.json"));
1671
+ const ownerHasRigConfig = existsSync8(resolve10(owner, "rig.config.ts"));
1618
1672
  if (ownerHasGit && (ownerHasTaskConfig || ownerHasRigConfig)) {
1619
1673
  return owner;
1620
1674
  }
1621
1675
  throw new Error(`MONOREPO_ROOT points to worktree ${explicitRoot}, but the owner checkout is incomplete at ${owner}.`);
1622
1676
  }
1623
- if (!existsSync7(resolve9(explicitRoot, ".git"))) {
1677
+ if (!existsSync8(resolve10(explicitRoot, ".git"))) {
1624
1678
  throw new Error(`MONOREPO_ROOT points to ${explicitRoot}, but no git checkout was found there.`);
1625
1679
  }
1626
- const hasTaskConfig = existsSync7(resolve9(explicitRoot, ".rig", "task-config.json"));
1627
- const hasRigConfig = existsSync7(resolve9(explicitRoot, "rig.config.ts"));
1680
+ const hasTaskConfig = existsSync8(resolve10(explicitRoot, ".rig", "task-config.json"));
1681
+ const hasRigConfig = existsSync8(resolve10(explicitRoot, "rig.config.ts"));
1628
1682
  if (!hasTaskConfig && !hasRigConfig) {
1629
1683
  throw new Error(`MONOREPO_ROOT points to ${explicitRoot}, but neither .rig/task-config.json nor rig.config.ts exists there.`);
1630
1684
  }
@@ -1633,9 +1687,9 @@ function resolveMonorepoRoot(projectRoot) {
1633
1687
  const projectParent = dirname6(normalizedProjectRoot);
1634
1688
  if (basename4(projectParent) === ".worktrees") {
1635
1689
  const worktreeOwner = dirname6(projectParent);
1636
- const ownerHasGit = existsSync7(resolve9(worktreeOwner, ".git"));
1637
- const ownerHasTaskConfig = existsSync7(resolve9(worktreeOwner, ".rig", "task-config.json"));
1638
- const ownerHasRigConfig = existsSync7(resolve9(worktreeOwner, "rig.config.ts"));
1690
+ const ownerHasGit = existsSync8(resolve10(worktreeOwner, ".git"));
1691
+ const ownerHasTaskConfig = existsSync8(resolve10(worktreeOwner, ".rig", "task-config.json"));
1692
+ const ownerHasRigConfig = existsSync8(resolve10(worktreeOwner, "rig.config.ts"));
1639
1693
  if (ownerHasGit && (ownerHasTaskConfig || ownerHasRigConfig)) {
1640
1694
  return worktreeOwner;
1641
1695
  }
@@ -1643,28 +1697,28 @@ function resolveMonorepoRoot(projectRoot) {
1643
1697
  return normalizedProjectRoot;
1644
1698
  }
1645
1699
  function resolveRuntimeWorkspaceLayout(workspaceDir) {
1646
- const root = resolve9(workspaceDir);
1647
- const rigRoot = resolve9(root, ".rig");
1648
- const logsDir = resolve9(rigRoot, "logs");
1649
- const stateDir = resolve9(rigRoot, "state");
1650
- const runtimeDir = resolve9(rigRoot, "runtime");
1651
- const binDir = resolve9(rigRoot, "bin");
1700
+ const root = resolve10(workspaceDir);
1701
+ const rigRoot = resolve10(root, ".rig");
1702
+ const logsDir = resolve10(rigRoot, "logs");
1703
+ const stateDir = resolve10(rigRoot, "state");
1704
+ const runtimeDir = resolve10(rigRoot, "runtime");
1705
+ const binDir = resolve10(rigRoot, "bin");
1652
1706
  return {
1653
1707
  workspaceDir: root,
1654
1708
  rigRoot,
1655
1709
  stateDir,
1656
1710
  logsDir,
1657
- artifactsRoot: resolve9(root, RIG_ARTIFACTS_DIRNAME),
1711
+ artifactsRoot: resolve10(root, RIG_ARTIFACTS_DIRNAME),
1658
1712
  runtimeDir,
1659
- homeDir: resolve9(rigRoot, "home"),
1660
- tmpDir: resolve9(rigRoot, "tmp"),
1661
- cacheDir: resolve9(rigRoot, "cache"),
1662
- sessionDir: resolve9(rigRoot, "session"),
1713
+ homeDir: resolve10(rigRoot, "home"),
1714
+ tmpDir: resolve10(rigRoot, "tmp"),
1715
+ cacheDir: resolve10(rigRoot, "cache"),
1716
+ sessionDir: resolve10(rigRoot, "session"),
1663
1717
  binDir,
1664
- distDir: resolve9(rigRoot, "dist"),
1665
- pluginBinDir: resolve9(binDir, "plugins"),
1666
- contextPath: resolve9(rigRoot, "runtime-context.json"),
1667
- controlPlaneEventsFile: resolve9(logsDir, "control-plane.events.jsonl")
1718
+ distDir: resolve10(rigRoot, "dist"),
1719
+ pluginBinDir: resolve10(binDir, "plugins"),
1720
+ contextPath: resolve10(rigRoot, "runtime-context.json"),
1721
+ controlPlaneEventsFile: resolve10(logsDir, "control-plane.events.jsonl")
1668
1722
  };
1669
1723
  }
1670
1724
  function resolveActiveRuntimeWorkspaceRoot(monorepoRoot) {
@@ -1672,14 +1726,14 @@ function resolveActiveRuntimeWorkspaceRoot(monorepoRoot) {
1672
1726
  if (!explicit) {
1673
1727
  throw new Error("No active runtime workspace. Set RIG_TASK_WORKSPACE or provision a task runtime first.");
1674
1728
  }
1675
- return resolve9(explicit);
1729
+ return resolve10(explicit);
1676
1730
  }
1677
1731
  function resolveRigLayout(projectRoot) {
1678
1732
  const monorepoRoot = resolveMonorepoRoot(projectRoot);
1679
- const definitionRoot = resolve9(projectRoot, RIG_DEFINITION_DIRNAME);
1733
+ const definitionRoot = resolve10(projectRoot, RIG_DEFINITION_DIRNAME);
1680
1734
  const runtimeWorkspaceRoot = resolveActiveRuntimeWorkspaceRoot(monorepoRoot);
1681
1735
  const runtimeLayout = resolveRuntimeWorkspaceLayout(runtimeWorkspaceRoot);
1682
- const policyDir = resolve9(definitionRoot, "policy");
1736
+ const policyDir = resolve10(definitionRoot, "policy");
1683
1737
  return {
1684
1738
  projectRoot,
1685
1739
  monorepoRoot,
@@ -1687,34 +1741,34 @@ function resolveRigLayout(projectRoot) {
1687
1741
  runtimeWorkspaceRoot,
1688
1742
  stateRoot: runtimeLayout.rigRoot,
1689
1743
  artifactsRoot: runtimeLayout.artifactsRoot,
1690
- configPath: resolve9(definitionRoot, "config.sh"),
1691
- taskConfigPath: resolve9(runtimeWorkspaceRoot, ".rig", "task-config.json"),
1744
+ configPath: resolve10(definitionRoot, "config.sh"),
1745
+ taskConfigPath: resolve10(runtimeWorkspaceRoot, ".rig", "task-config.json"),
1692
1746
  policyDir,
1693
- policyFile: resolve9(policyDir, "policy.json"),
1694
- pluginsDir: resolve9(definitionRoot, "plugins"),
1695
- hooksDir: resolve9(definitionRoot, "hooks"),
1696
- toolsDir: resolve9(definitionRoot, "tools"),
1697
- templatesDir: resolve9(definitionRoot, "templates"),
1698
- validationDir: resolve9(definitionRoot, "validation"),
1747
+ policyFile: resolve10(policyDir, "policy.json"),
1748
+ pluginsDir: resolve10(definitionRoot, "plugins"),
1749
+ hooksDir: resolve10(definitionRoot, "hooks"),
1750
+ toolsDir: resolve10(definitionRoot, "tools"),
1751
+ templatesDir: resolve10(definitionRoot, "templates"),
1752
+ validationDir: resolve10(definitionRoot, "validation"),
1699
1753
  stateDir: runtimeLayout.stateDir,
1700
1754
  logsDir: runtimeLayout.logsDir,
1701
- notificationsDir: resolve9(definitionRoot, "notifications"),
1755
+ notificationsDir: resolve10(definitionRoot, "notifications"),
1702
1756
  runtimeDir: runtimeLayout.runtimeDir,
1703
1757
  distDir: runtimeLayout.distDir,
1704
1758
  binDir: runtimeLayout.binDir,
1705
1759
  pluginBinDir: runtimeLayout.pluginBinDir,
1706
- keybindingsPath: resolve9(definitionRoot, "keybindings.json"),
1760
+ keybindingsPath: resolve10(definitionRoot, "keybindings.json"),
1707
1761
  controlPlaneEventsFile: runtimeLayout.controlPlaneEventsFile
1708
1762
  };
1709
1763
  }
1710
1764
 
1711
1765
  // packages/runtime/src/control-plane/native/runtime-native.ts
1712
1766
  import { dlopen, ptr, suffix, toBuffer } from "bun:ffi";
1713
- import { copyFileSync as copyFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync4, renameSync as renameSync2, rmSync as rmSync2, statSync as statSync2 } from "fs";
1767
+ import { copyFileSync as copyFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync5, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync2 } from "fs";
1714
1768
  import { tmpdir as tmpdir4 } from "os";
1715
- import { dirname as dirname7, resolve as resolve10 } from "path";
1716
- var sharedNativeRuntimeOutputDir = resolve10(tmpdir4(), "rig-native");
1717
- var sharedNativeRuntimeOutputPath = resolve10(sharedNativeRuntimeOutputDir, `runtime-native-${process.platform}-${process.arch}.${suffix}`);
1769
+ import { dirname as dirname7, resolve as resolve11 } from "path";
1770
+ var sharedNativeRuntimeOutputDir = resolve11(tmpdir4(), "rig-native");
1771
+ var sharedNativeRuntimeOutputPath = resolve11(sharedNativeRuntimeOutputDir, `runtime-native-${process.platform}-${process.arch}.${suffix}`);
1718
1772
  var colocatedNativeRuntimeFileName = `runtime-native.${suffix}`;
1719
1773
  var nativeRuntimeLibrary = await loadNativeRuntimeLibrary();
1720
1774
  function requireNativeRuntimeLibrary(feature) {
@@ -1727,14 +1781,14 @@ async function ensureNativeRuntimeLibraryPath(outputPath = sharedNativeRuntimeOu
1727
1781
  if (await buildNativeRuntimeLibrary(outputPath, options)) {
1728
1782
  return outputPath;
1729
1783
  }
1730
- return !options.force && existsSync8(outputPath) ? outputPath : null;
1784
+ return !options.force && existsSync9(outputPath) ? outputPath : null;
1731
1785
  }
1732
1786
  async function loadNativeRuntimeLibrary() {
1733
1787
  if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
1734
1788
  return null;
1735
1789
  }
1736
1790
  for (const candidate of nativeRuntimeLibraryCandidates()) {
1737
- if (!candidate || !existsSync8(candidate)) {
1791
+ if (!candidate || !existsSync9(candidate)) {
1738
1792
  continue;
1739
1793
  }
1740
1794
  const loaded = tryDlopenNativeRuntimeLibrary(candidate);
@@ -1750,10 +1804,10 @@ async function loadNativeRuntimeLibrary() {
1750
1804
  }
1751
1805
  function nativePackageLibraryCandidates(fromDir, names) {
1752
1806
  const candidates = [];
1753
- let cursor = resolve10(fromDir);
1807
+ let cursor = resolve11(fromDir);
1754
1808
  for (let index = 0;index < 8; index += 1) {
1755
1809
  for (const name of names) {
1756
- candidates.push(resolve10(cursor, "native", `${process.platform}-${process.arch}`, name), resolve10(cursor, "native", `${process.platform}-${process.arch}`, "lib", name), resolve10(cursor, "native", name), resolve10(cursor, "native", "lib", name));
1810
+ candidates.push(resolve11(cursor, "native", `${process.platform}-${process.arch}`, name), resolve11(cursor, "native", `${process.platform}-${process.arch}`, "lib", name), resolve11(cursor, "native", name), resolve11(cursor, "native", "lib", name));
1757
1811
  }
1758
1812
  const parent = dirname7(cursor);
1759
1813
  if (parent === cursor)
@@ -1769,22 +1823,22 @@ function nativeRuntimeLibraryCandidates() {
1769
1823
  return [...new Set([
1770
1824
  explicit,
1771
1825
  ...nativePackageLibraryCandidates(import.meta.dir, [colocatedNativeRuntimeFileName, platformSpecific]),
1772
- execDir ? resolve10(execDir, colocatedNativeRuntimeFileName) : "",
1773
- execDir ? resolve10(execDir, platformSpecific) : "",
1774
- execDir ? resolve10(execDir, "..", colocatedNativeRuntimeFileName) : "",
1775
- execDir ? resolve10(execDir, "..", platformSpecific) : "",
1776
- execDir ? resolve10(execDir, "lib", colocatedNativeRuntimeFileName) : "",
1777
- execDir ? resolve10(execDir, "..", "lib", colocatedNativeRuntimeFileName) : "",
1826
+ execDir ? resolve11(execDir, colocatedNativeRuntimeFileName) : "",
1827
+ execDir ? resolve11(execDir, platformSpecific) : "",
1828
+ execDir ? resolve11(execDir, "..", colocatedNativeRuntimeFileName) : "",
1829
+ execDir ? resolve11(execDir, "..", platformSpecific) : "",
1830
+ execDir ? resolve11(execDir, "lib", colocatedNativeRuntimeFileName) : "",
1831
+ execDir ? resolve11(execDir, "..", "lib", colocatedNativeRuntimeFileName) : "",
1778
1832
  sharedNativeRuntimeOutputPath
1779
1833
  ].filter(Boolean))];
1780
1834
  }
1781
1835
  function resolveNativeRuntimeSourcePath() {
1782
1836
  const explicit = process.env.RIG_NATIVE_RUNTIME_SOURCE?.trim();
1783
- if (explicit && existsSync8(explicit)) {
1837
+ if (explicit && existsSync9(explicit)) {
1784
1838
  return explicit;
1785
1839
  }
1786
- const bundled = resolve10(import.meta.dir, "../../../native/snapshot.zig");
1787
- return existsSync8(bundled) ? bundled : null;
1840
+ const bundled = resolve11(import.meta.dir, "../../../native/snapshot.zig");
1841
+ return existsSync9(bundled) ? bundled : null;
1788
1842
  }
1789
1843
  async function buildNativeRuntimeLibrary(outputPath, options = {}) {
1790
1844
  if (process.env.RIG_DISABLE_ZIG_NATIVE === "1") {
@@ -1797,8 +1851,8 @@ async function buildNativeRuntimeLibrary(outputPath, options = {}) {
1797
1851
  }
1798
1852
  const tempOutputPath = `${outputPath}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
1799
1853
  try {
1800
- mkdirSync4(dirname7(outputPath), { recursive: true });
1801
- const needsBuild = options.force === true || !existsSync8(outputPath) || statSync2(sourcePath).mtimeMs > statSync2(outputPath).mtimeMs;
1854
+ mkdirSync5(dirname7(outputPath), { recursive: true });
1855
+ const needsBuild = options.force === true || !existsSync9(outputPath) || statSync2(sourcePath).mtimeMs > statSync2(outputPath).mtimeMs;
1802
1856
  if (!needsBuild) {
1803
1857
  return true;
1804
1858
  }
@@ -1816,14 +1870,14 @@ async function buildNativeRuntimeLibrary(outputPath, options = {}) {
1816
1870
  stderr: "pipe"
1817
1871
  });
1818
1872
  const exitCode = await build.exited;
1819
- if (exitCode !== 0 || !existsSync8(tempOutputPath)) {
1820
- rmSync2(tempOutputPath, { force: true });
1873
+ if (exitCode !== 0 || !existsSync9(tempOutputPath)) {
1874
+ rmSync3(tempOutputPath, { force: true });
1821
1875
  return false;
1822
1876
  }
1823
1877
  renameSync2(tempOutputPath, outputPath);
1824
1878
  return true;
1825
1879
  } catch {
1826
- rmSync2(tempOutputPath, { force: true });
1880
+ rmSync3(tempOutputPath, { force: true });
1827
1881
  return false;
1828
1882
  }
1829
1883
  }
@@ -1947,11 +2001,11 @@ async function runCaptureAsync(command, cwd, env, timeoutMs) {
1947
2001
  return { exitCode, stdout, stderr };
1948
2002
  }
1949
2003
  function readJsonFile(path, fallback) {
1950
- if (!existsSync9(path)) {
2004
+ if (!existsSync10(path)) {
1951
2005
  return fallback;
1952
2006
  }
1953
2007
  try {
1954
- return JSON.parse(readFileSync6(path, "utf-8"));
2008
+ return JSON.parse(readFileSync7(path, "utf-8"));
1955
2009
  } catch {
1956
2010
  return fallback;
1957
2011
  }
@@ -1965,31 +2019,31 @@ function unique(values) {
1965
2019
  function resolveHarnessPaths(projectRoot) {
1966
2020
  const hasRuntimeWorkspace = Boolean(process.env.RIG_TASK_WORKSPACE?.trim());
1967
2021
  const monorepoRoot = resolveMonorepoRoot2(projectRoot);
1968
- const harnessRoot = resolve11(projectRoot, "rig");
1969
- const stateRoot = resolve11(projectRoot, ".rig");
2022
+ const harnessRoot = resolve12(projectRoot, "rig");
2023
+ const stateRoot = resolve12(projectRoot, ".rig");
1970
2024
  const layout = hasRuntimeWorkspace ? resolveRigLayout(projectRoot) : null;
1971
- const stateDir = layout?.stateDir ?? resolve11(stateRoot, "state");
1972
- const logsDir = layout?.logsDir ?? resolve11(stateRoot, "logs");
1973
- const artifactsDir = layout?.artifactsRoot ?? resolve11(monorepoRoot, "artifacts");
1974
- const taskConfigPath = layout?.taskConfigPath ?? resolve11(monorepoRoot, ".rig", "task-config.json");
1975
- const binDir = layout?.binDir ?? resolve11(stateRoot, "bin");
2025
+ const stateDir = layout?.stateDir ?? resolve12(stateRoot, "state");
2026
+ const logsDir = layout?.logsDir ?? resolve12(stateRoot, "logs");
2027
+ const artifactsDir = layout?.artifactsRoot ?? resolve12(monorepoRoot, "artifacts");
2028
+ const taskConfigPath = layout?.taskConfigPath ?? resolve12(monorepoRoot, ".rig", "task-config.json");
2029
+ const binDir = layout?.binDir ?? resolve12(stateRoot, "bin");
1976
2030
  return {
1977
2031
  harnessRoot,
1978
2032
  stateDir: process.env.RIG_STATE_DIR || stateDir,
1979
2033
  artifactsDir,
1980
2034
  logsDir: process.env.RIG_LOGS_DIR || logsDir,
1981
2035
  binDir,
1982
- hooksDir: resolve11(harnessRoot, "hooks"),
1983
- validationDir: resolve11(harnessRoot, "validation"),
2036
+ hooksDir: resolve12(harnessRoot, "hooks"),
2037
+ validationDir: resolve12(harnessRoot, "validation"),
1984
2038
  taskConfigPath,
1985
- sessionPath: process.env.RIG_SESSION_FILE || resolve11(stateRoot, "session", "session.json"),
2039
+ sessionPath: process.env.RIG_SESSION_FILE || resolve12(stateRoot, "session", "session.json"),
1986
2040
  monorepoRoot,
1987
- tsApiTestsDir: process.env.TS_API_TESTS_DIR || resolve11(monorepoRoot, "TSAPITests"),
1988
- taskRepoCommitsPath: resolve11(stateDir, "task-repo-commits.json"),
1989
- baseRepoPinsPath: resolve11(stateDir, "base-repo-pins.json"),
1990
- failedApproachesPath: resolve11(stateDir, "failed_approaches.md"),
1991
- agentProfilePath: resolve11(stateDir, "agent-profile.json"),
1992
- reviewProfilePath: resolve11(stateDir, "review-profile.json")
2041
+ tsApiTestsDir: process.env.TS_API_TESTS_DIR || resolve12(monorepoRoot, "TSAPITests"),
2042
+ taskRepoCommitsPath: resolve12(stateDir, "task-repo-commits.json"),
2043
+ baseRepoPinsPath: resolve12(stateDir, "base-repo-pins.json"),
2044
+ failedApproachesPath: resolve12(stateDir, "failed_approaches.md"),
2045
+ agentProfilePath: resolve12(stateDir, "agent-profile.json"),
2046
+ reviewProfilePath: resolve12(stateDir, "review-profile.json")
1993
2047
  };
1994
2048
  }
1995
2049
  function normalizeRelativeScopePath(inputPath) {
@@ -2082,34 +2136,34 @@ function createNativeScopeMatcher() {
2082
2136
  }
2083
2137
 
2084
2138
  // packages/runtime/src/control-plane/state-sync/repo.ts
2085
- import { existsSync as existsSync11 } from "fs";
2086
- import { resolve as resolve13 } from "path";
2139
+ import { existsSync as existsSync12 } from "fs";
2140
+ import { resolve as resolve14 } from "path";
2087
2141
 
2088
2142
  // packages/runtime/src/control-plane/repos/layout.ts
2089
- import { existsSync as existsSync10 } from "fs";
2090
- import { basename as basename5, dirname as dirname8, join as join3, resolve as resolve12 } from "path";
2143
+ import { existsSync as existsSync11 } from "fs";
2144
+ import { basename as basename5, dirname as dirname8, join as join3, resolve as resolve13 } from "path";
2091
2145
  function resolveRepoStateDir(projectRoot) {
2092
- const normalizedProjectRoot = resolve12(projectRoot);
2146
+ const normalizedProjectRoot = resolve13(projectRoot);
2093
2147
  const projectParent = dirname8(normalizedProjectRoot);
2094
2148
  if (basename5(projectParent) === ".worktrees") {
2095
2149
  const ownerRoot = dirname8(projectParent);
2096
- const ownerHasRepoMarkers = existsSync10(resolve12(ownerRoot, ".git")) || existsSync10(resolve12(ownerRoot, ".rig", "state"));
2150
+ const ownerHasRepoMarkers = existsSync11(resolve13(ownerRoot, ".git")) || existsSync11(resolve13(ownerRoot, ".rig", "state"));
2097
2151
  if (ownerHasRepoMarkers) {
2098
- return resolve12(ownerRoot, ".rig", "state");
2152
+ return resolve13(ownerRoot, ".rig", "state");
2099
2153
  }
2100
2154
  }
2101
- return resolve12(projectRoot, ".rig", "state");
2155
+ return resolve13(projectRoot, ".rig", "state");
2102
2156
  }
2103
2157
  function resolveManagedRepoLayout(projectRoot, repoId) {
2104
- const normalizedProjectRoot = resolve12(projectRoot);
2158
+ const normalizedProjectRoot = resolve13(projectRoot);
2105
2159
  const entry = getManagedRepoEntry(repoId);
2106
2160
  const stateDir = resolveRepoStateDir(normalizedProjectRoot);
2107
2161
  const metadataRelativePath = join3("repos", entry.id);
2108
- const metadataRoot = resolve12(stateDir, metadataRelativePath);
2162
+ const metadataRoot = resolve13(stateDir, metadataRelativePath);
2109
2163
  const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
2110
- const runsInsideTaskWorktree = runtimeWorkspace && resolve12(runtimeWorkspace) === normalizedProjectRoot || basename5(dirname8(normalizedProjectRoot)) === ".worktrees";
2164
+ const runsInsideTaskWorktree = runtimeWorkspace && resolve13(runtimeWorkspace) === normalizedProjectRoot || basename5(dirname8(normalizedProjectRoot)) === ".worktrees";
2111
2165
  const isPrimaryManagedRepo = listManagedRepoEntries()[0]?.id === repoId;
2112
- const checkoutRoot = isPrimaryManagedRepo && runsInsideTaskWorktree ? resolveMonorepoRoot(normalizedProjectRoot) : entry.checkoutEnvVar && process.env[entry.checkoutEnvVar]?.trim() ? resolve12(process.env[entry.checkoutEnvVar].trim()) : resolve12(normalizedProjectRoot, entry.alias);
2166
+ const checkoutRoot = isPrimaryManagedRepo && runsInsideTaskWorktree ? resolveMonorepoRoot(normalizedProjectRoot) : entry.checkoutEnvVar && process.env[entry.checkoutEnvVar]?.trim() ? resolve13(process.env[entry.checkoutEnvVar].trim()) : resolve13(normalizedProjectRoot, entry.alias);
2113
2167
  return {
2114
2168
  projectRoot: normalizedProjectRoot,
2115
2169
  repoId: entry.id,
@@ -2117,12 +2171,12 @@ function resolveManagedRepoLayout(projectRoot, repoId) {
2117
2171
  defaultBranch: entry.defaultBranch,
2118
2172
  remoteUrl: entry.remoteEnvVar && process.env[entry.remoteEnvVar]?.trim() ? process.env[entry.remoteEnvVar].trim() : entry.defaultRemoteUrl,
2119
2173
  checkoutRoot,
2120
- worktreesRoot: resolve12(checkoutRoot, ".worktrees"),
2174
+ worktreesRoot: resolve13(checkoutRoot, ".worktrees"),
2121
2175
  stateDir,
2122
2176
  metadataRoot,
2123
2177
  metadataRelativePath,
2124
- mirrorRoot: resolve12(metadataRoot, "mirror.git"),
2125
- mirrorStatePath: resolve12(metadataRoot, "mirror-state.json"),
2178
+ mirrorRoot: resolve13(metadataRoot, "mirror.git"),
2179
+ mirrorStatePath: resolve13(metadataRoot, "mirror-state.json"),
2126
2180
  mirrorStateRelativePath: join3(metadataRelativePath, "mirror-state.json")
2127
2181
  };
2128
2182
  }
@@ -2140,7 +2194,7 @@ function resolveTrackerRepoPath(projectRoot) {
2140
2194
  const monorepoRoot = resolveMonorepoRoot2(projectRoot);
2141
2195
  try {
2142
2196
  const layout = resolveMonorepoRepoLayout(projectRoot);
2143
- if (existsSync11(resolve13(layout.mirrorRoot, "HEAD"))) {
2197
+ if (existsSync12(resolve14(layout.mirrorRoot, "HEAD"))) {
2144
2198
  return layout.mirrorRoot;
2145
2199
  }
2146
2200
  } catch {}
@@ -2151,8 +2205,8 @@ function resolveTrackerRepoPath(projectRoot) {
2151
2205
  var DEFAULT_READ_DEPS = {
2152
2206
  fetchRef: nativeFetchRef,
2153
2207
  readBlobAtRef: nativeReadBlobAtRef,
2154
- exists: existsSync12,
2155
- readFile: (path) => readFileSync7(path, "utf8")
2208
+ exists: existsSync13,
2209
+ readFile: (path) => readFileSync8(path, "utf8")
2156
2210
  };
2157
2211
  function parseIssueStatus(rawStatus) {
2158
2212
  const normalized = normalizeTaskLifecycleStatus(rawStatus);
@@ -2233,12 +2287,12 @@ function shouldPreferLocalTrackerState(options) {
2233
2287
  if (runtimeContextPath) {
2234
2288
  return true;
2235
2289
  }
2236
- return existsSync12(resolve14(runtimeWorkspace, ".rig", "runtime-context.json"));
2290
+ return existsSync13(resolve15(runtimeWorkspace, ".rig", "runtime-context.json"));
2237
2291
  }
2238
2292
  function readLocalTrackerState(projectRoot, deps) {
2239
2293
  const monorepoRoot = resolveMonorepoRoot2(projectRoot);
2240
- const issuesPath = resolve14(monorepoRoot, ".beads", "issues.jsonl");
2241
- const taskStatePath = resolve14(monorepoRoot, ".beads", "task-state.json");
2294
+ const issuesPath = resolve15(monorepoRoot, ".beads", "issues.jsonl");
2295
+ const taskStatePath = resolve15(monorepoRoot, ".beads", "task-state.json");
2242
2296
  return projectSyncedTrackerSnapshot({
2243
2297
  source: "local",
2244
2298
  issuesBaseOid: null,
@@ -2552,7 +2606,7 @@ function readValidationDescriptions(projectRoot) {
2552
2606
  return readValidationDescriptionMap(raw);
2553
2607
  }
2554
2608
  function readSourceValidationDescriptions(projectRoot) {
2555
- const rootRaw = readJsonFile(resolve15(projectRoot, "rig", "task-config.json"), {});
2609
+ const rootRaw = readJsonFile(resolve16(projectRoot, "rig", "task-config.json"), {});
2556
2610
  const sourcePath = findSourceTaskConfigPath(projectRoot);
2557
2611
  const sourceRaw = sourcePath ? readJsonFile(sourcePath, {}) : {};
2558
2612
  const rootDescriptions = readValidationDescriptionMap(rootRaw);
@@ -2628,15 +2682,15 @@ function readValidationDescriptionsFromMeta(meta) {
2628
2682
  return meta.validation_descriptions;
2629
2683
  }
2630
2684
  function readLocalSourceTaskStateEnvelope(projectRoot) {
2631
- const taskStatePath = resolve15(resolveMonorepoRoot2(projectRoot), ".beads", "task-state.json");
2685
+ const taskStatePath = resolve16(resolveMonorepoRoot2(projectRoot), ".beads", "task-state.json");
2632
2686
  return readTaskStateMetadataEnvelope(readJsonFile(taskStatePath, {}));
2633
2687
  }
2634
2688
  function readLocalSourceTaskLifecycleStatus(projectRoot, taskId) {
2635
- const issuesPath = resolve15(resolveMonorepoRoot2(projectRoot), ".beads", "issues.jsonl");
2636
- if (!existsSync13(issuesPath)) {
2689
+ const issuesPath = resolve16(resolveMonorepoRoot2(projectRoot), ".beads", "issues.jsonl");
2690
+ if (!existsSync14(issuesPath)) {
2637
2691
  return null;
2638
2692
  }
2639
- for (const line of readFileSync8(issuesPath, "utf8").split(/\r?\n/)) {
2693
+ for (const line of readFileSync9(issuesPath, "utf8").split(/\r?\n/)) {
2640
2694
  const trimmed = line.trim();
2641
2695
  if (!trimmed) {
2642
2696
  continue;
@@ -2677,25 +2731,25 @@ function lookupTask(projectRoot, input) {
2677
2731
  function artifactDirForId(projectRoot, id) {
2678
2732
  const workspaceDir = process.env.RIG_TASK_WORKSPACE?.trim();
2679
2733
  if (workspaceDir) {
2680
- const worktreeArtifacts = resolve15(workspaceDir, "artifacts", id);
2681
- if (existsSync13(worktreeArtifacts) || existsSync13(resolve15(workspaceDir, "artifacts"))) {
2734
+ const worktreeArtifacts = resolve16(workspaceDir, "artifacts", id);
2735
+ if (existsSync14(worktreeArtifacts) || existsSync14(resolve16(workspaceDir, "artifacts"))) {
2682
2736
  return worktreeArtifacts;
2683
2737
  }
2684
2738
  }
2685
2739
  try {
2686
2740
  const paths = resolveHarnessPaths(projectRoot);
2687
- return resolve15(paths.artifactsDir, id);
2741
+ return resolve16(paths.artifactsDir, id);
2688
2742
  } catch {
2689
- return resolve15(resolveMonorepoRoot2(projectRoot), "artifacts", id);
2743
+ return resolve16(resolveMonorepoRoot2(projectRoot), "artifacts", id);
2690
2744
  }
2691
2745
  }
2692
2746
  function resolveTaskConfigPath(projectRoot) {
2693
2747
  const paths = resolveHarnessPaths(projectRoot);
2694
- if (existsSync13(paths.taskConfigPath)) {
2748
+ if (existsSync14(paths.taskConfigPath)) {
2695
2749
  return paths.taskConfigPath;
2696
2750
  }
2697
2751
  for (const candidate of sourceTaskConfigCandidates(projectRoot)) {
2698
- if (existsSync13(candidate)) {
2752
+ if (existsSync14(candidate)) {
2699
2753
  return candidate;
2700
2754
  }
2701
2755
  }
@@ -2703,7 +2757,7 @@ function resolveTaskConfigPath(projectRoot) {
2703
2757
  }
2704
2758
  function findSourceTaskConfigPath(projectRoot) {
2705
2759
  for (const candidate of sourceTaskConfigCandidates(projectRoot)) {
2706
- if (existsSync13(candidate)) {
2760
+ if (existsSync14(candidate)) {
2707
2761
  return candidate;
2708
2762
  }
2709
2763
  }
@@ -2716,7 +2770,7 @@ function readAndSyncSourceTaskConfig(projectRoot) {
2716
2770
  const synced = synchronizeTaskConfigWithTracker(projectRoot, raw);
2717
2771
  if (sourcePath && synced.updated) {
2718
2772
  try {
2719
- writeFileSync5(sourcePath, `${JSON.stringify(synced.config, null, 2)}
2773
+ writeFileSync6(sourcePath, `${JSON.stringify(synced.config, null, 2)}
2720
2774
  `, "utf-8");
2721
2775
  } catch {}
2722
2776
  }
@@ -2768,12 +2822,12 @@ function shouldRefreshAutoSyncedTaskConfigEntry(entry) {
2768
2822
  return !candidate.role;
2769
2823
  }
2770
2824
  function readSourceIssueRecords(projectRoot) {
2771
- const issuesPath = resolve15(resolveMonorepoRoot2(projectRoot), ".beads", "issues.jsonl");
2772
- if (!existsSync13(issuesPath)) {
2825
+ const issuesPath = resolve16(resolveMonorepoRoot2(projectRoot), ".beads", "issues.jsonl");
2826
+ if (!existsSync14(issuesPath)) {
2773
2827
  return [];
2774
2828
  }
2775
2829
  const records = [];
2776
- for (const line of readFileSync8(issuesPath, "utf-8").split(/\r?\n/)) {
2830
+ for (const line of readFileSync9(issuesPath, "utf-8").split(/\r?\n/)) {
2777
2831
  const trimmed = line.trim();
2778
2832
  if (!trimmed) {
2779
2833
  continue;
@@ -2829,19 +2883,19 @@ function readConfiguredFileTaskConfig(projectRoot) {
2829
2883
  if (!sourcePath) {
2830
2884
  return {};
2831
2885
  }
2832
- const directory = resolve15(projectRoot, sourcePath);
2833
- if (!existsSync13(directory)) {
2886
+ const directory = resolve16(projectRoot, sourcePath);
2887
+ if (!existsSync14(directory)) {
2834
2888
  return {};
2835
2889
  }
2836
2890
  const config = {};
2837
- for (const name of readdirSync2(directory)) {
2891
+ for (const name of readdirSync3(directory)) {
2838
2892
  if (!FILE_TASK_PATTERN2.test(name))
2839
2893
  continue;
2840
- const file = resolve15(directory, name);
2894
+ const file = resolve16(directory, name);
2841
2895
  try {
2842
2896
  if (!statSync3(file).isFile())
2843
2897
  continue;
2844
- const raw = JSON.parse(readFileSync8(file, "utf8"));
2898
+ const raw = JSON.parse(readFileSync9(file, "utf8"));
2845
2899
  if (!raw || typeof raw !== "object" || Array.isArray(raw))
2846
2900
  continue;
2847
2901
  const record = raw;
@@ -2883,10 +2937,10 @@ function firstStringList2(...candidates) {
2883
2937
  return [];
2884
2938
  }
2885
2939
  function readConfiguredFilesTaskSourcePath2(projectRoot) {
2886
- const jsonPath = resolve15(projectRoot, "rig.config.json");
2887
- if (existsSync13(jsonPath)) {
2940
+ const jsonPath = resolve16(projectRoot, "rig.config.json");
2941
+ if (existsSync14(jsonPath)) {
2888
2942
  try {
2889
- const parsed = JSON.parse(readFileSync8(jsonPath, "utf8"));
2943
+ const parsed = JSON.parse(readFileSync9(jsonPath, "utf8"));
2890
2944
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2891
2945
  const taskSource = parsed.taskSource;
2892
2946
  if (taskSource && typeof taskSource === "object" && !Array.isArray(taskSource)) {
@@ -2898,12 +2952,12 @@ function readConfiguredFilesTaskSourcePath2(projectRoot) {
2898
2952
  return null;
2899
2953
  }
2900
2954
  }
2901
- const tsPath = resolve15(projectRoot, "rig.config.ts");
2902
- if (!existsSync13(tsPath)) {
2955
+ const tsPath = resolve16(projectRoot, "rig.config.ts");
2956
+ if (!existsSync14(tsPath)) {
2903
2957
  return null;
2904
2958
  }
2905
2959
  try {
2906
- const source = readFileSync8(tsPath, "utf8");
2960
+ const source = readFileSync9(tsPath, "utf8");
2907
2961
  const taskSourceBlock = source.match(/taskSource\s*:\s*\{[\s\S]*?\}/m)?.[0] ?? "";
2908
2962
  const kind = taskSourceBlock.match(/kind\s*:\s*["']([^"']+)["']/)?.[1];
2909
2963
  if (kind !== "files") {
@@ -2917,23 +2971,23 @@ function readConfiguredFilesTaskSourcePath2(projectRoot) {
2917
2971
  function sourceTaskConfigCandidates(projectRoot) {
2918
2972
  const runtimeContext = loadRuntimeContextFromEnv();
2919
2973
  return [
2920
- runtimeContext?.monorepoMainRoot ? resolve15(runtimeContext.monorepoMainRoot, ".rig", "task-config.json") : "",
2921
- process.env.MONOREPO_MAIN_ROOT?.trim() ? resolve15(process.env.MONOREPO_MAIN_ROOT.trim(), ".rig", "task-config.json") : "",
2922
- resolve15(resolveMonorepoRoot2(projectRoot), ".rig", "task-config.json")
2974
+ runtimeContext?.monorepoMainRoot ? resolve16(runtimeContext.monorepoMainRoot, ".rig", "task-config.json") : "",
2975
+ process.env.MONOREPO_MAIN_ROOT?.trim() ? resolve16(process.env.MONOREPO_MAIN_ROOT.trim(), ".rig", "task-config.json") : "",
2976
+ resolve16(resolveMonorepoRoot2(projectRoot), ".rig", "task-config.json")
2923
2977
  ].filter(Boolean);
2924
2978
  }
2925
2979
 
2926
2980
  // packages/runtime/src/control-plane/native/validator.ts
2927
- import { existsSync as existsSync17, mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "fs";
2928
- import { resolve as resolve20 } from "path";
2981
+ import { existsSync as existsSync18, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
2982
+ import { resolve as resolve21 } from "path";
2929
2983
 
2930
2984
  // packages/runtime/src/control-plane/native/validator-binaries.ts
2931
- import { existsSync as existsSync16, mkdirSync as mkdirSync6, rmSync as rmSync4, statSync as statSync4 } from "fs";
2932
- import { dirname as dirname10, resolve as resolve19 } from "path";
2985
+ import { existsSync as existsSync17, mkdirSync as mkdirSync7, rmSync as rmSync5, statSync as statSync4 } from "fs";
2986
+ import { dirname as dirname10, resolve as resolve20 } from "path";
2933
2987
 
2934
2988
  // packages/runtime/src/binary-run.ts
2935
- import { chmodSync as chmodSync2, cpSync, existsSync as existsSync14, mkdirSync as mkdirSync5, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
2936
- import { basename as basename7, dirname as dirname9, resolve as resolve16 } from "path";
2989
+ import { chmodSync as chmodSync2, cpSync, existsSync as existsSync15, mkdirSync as mkdirSync6, renameSync as renameSync3, rmSync as rmSync4, writeFileSync as writeFileSync7 } from "fs";
2990
+ import { basename as basename7, dirname as dirname9, resolve as resolve17 } from "path";
2937
2991
  import { fileURLToPath } from "url";
2938
2992
  import { drainMicrotasks, gcAndSweep } from "bun:jsc";
2939
2993
  var runtimeBinaryBuildQueue = Promise.resolve();
@@ -2959,9 +3013,9 @@ async function buildRuntimeBinary(options) {
2959
3013
  });
2960
3014
  }
2961
3015
  async function buildRuntimeBinaryInProcess(options, manifest) {
2962
- const tempBuildDir = resolve16(dirname9(options.outputPath), `.bun-build-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
2963
- const tempOutputPath = resolve16(tempBuildDir, basename7(options.outputPath));
2964
- mkdirSync5(tempBuildDir, { recursive: true });
3016
+ const tempBuildDir = resolve17(dirname9(options.outputPath), `.bun-build-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
3017
+ const tempOutputPath = resolve17(tempBuildDir, basename7(options.outputPath));
3018
+ mkdirSync6(tempBuildDir, { recursive: true });
2965
3019
  await withTemporaryEnv({
2966
3020
  ...options.env,
2967
3021
  ...options.define ? { RIG_BUILD_CONFIG_JSON: JSON.stringify(options.define) } : {}
@@ -2986,7 +3040,7 @@ async function buildRuntimeBinaryInProcess(options, manifest) {
2986
3040
  `);
2987
3041
  throw new Error(`Failed to build ${options.entrypoint}: ${details || "Bun.build() returned errors"}`);
2988
3042
  }
2989
- if (!existsSync14(tempOutputPath)) {
3043
+ if (!existsSync15(tempOutputPath)) {
2990
3044
  const emitted = buildResult.outputs.map((output) => output.path).join(", ") || "(none)";
2991
3045
  throw new Error(`Failed to build ${options.entrypoint}: Bun.build() did not emit ${tempOutputPath}. Emitted: ${emitted}`);
2992
3046
  }
@@ -3001,7 +3055,7 @@ async function buildRuntimeBinaryInProcess(options, manifest) {
3001
3055
  });
3002
3056
  }
3003
3057
  })).finally(() => {
3004
- rmSync3(tempBuildDir, { recursive: true, force: true });
3058
+ rmSync4(tempBuildDir, { recursive: true, force: true });
3005
3059
  });
3006
3060
  }
3007
3061
  function runBestEffortBuildGc() {
@@ -3018,8 +3072,8 @@ function runtimeBinaryCacheManifestPath(outputPath) {
3018
3072
  function resolveRuntimeBinaryBuildOptions(options) {
3019
3073
  return {
3020
3074
  ...options,
3021
- entrypoint: resolve16(options.cwd, options.sourcePath),
3022
- outputPath: resolve16(options.outputPath)
3075
+ entrypoint: resolve17(options.cwd, options.sourcePath),
3076
+ outputPath: resolve17(options.outputPath)
3023
3077
  };
3024
3078
  }
3025
3079
  function shouldUseRuntimeBinaryBuildWorker() {
@@ -3033,7 +3087,7 @@ function shouldUseRuntimeBinaryBuildWorker() {
3033
3087
  }
3034
3088
  async function buildRuntimeBinaryViaWorker(options) {
3035
3089
  const workerSourcePath = resolveRuntimeBinaryBuildWorkerSourcePath(options);
3036
- if (!workerSourcePath || !existsSync14(workerSourcePath)) {
3090
+ if (!workerSourcePath || !existsSync15(workerSourcePath)) {
3037
3091
  await buildRuntimeBinaryInProcess(options, {
3038
3092
  manifestPath: runtimeBinaryCacheManifestPath(options.outputPath),
3039
3093
  buildKey: createRuntimeBinaryBuildKey({
@@ -3064,13 +3118,13 @@ async function buildRuntimeBinaryViaWorker(options) {
3064
3118
  new Response(build.stdout).text(),
3065
3119
  new Response(build.stderr).text()
3066
3120
  ]);
3067
- rmSync3(payloadPath, { force: true });
3121
+ rmSync4(payloadPath, { force: true });
3068
3122
  if (exitCode !== 0) {
3069
3123
  throw new Error(`Failed to build ${options.entrypoint}: ${(stderr || stdout || `worker exited ${exitCode}`).trim()}`);
3070
3124
  }
3071
3125
  }
3072
3126
  function createRuntimeBinaryBuildWorkerPayloadPath(outputPath) {
3073
- return resolve16(dirname9(outputPath), `.bun-build-worker-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}.json`);
3127
+ return resolve17(dirname9(outputPath), `.bun-build-worker-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}.json`);
3074
3128
  }
3075
3129
  function resolveRuntimeBinaryBuildWorkerSourcePath(options) {
3076
3130
  const envRoots = [
@@ -3079,13 +3133,13 @@ function resolveRuntimeBinaryBuildWorkerSourcePath(options) {
3079
3133
  process.env.PROJECT_RIG_ROOT?.trim()
3080
3134
  ].filter(Boolean);
3081
3135
  for (const root of envRoots) {
3082
- const candidate = resolve16(root, "packages/runtime/src/binary-build-worker.ts");
3083
- if (existsSync14(candidate)) {
3136
+ const candidate = resolve17(root, "packages/runtime/src/binary-build-worker.ts");
3137
+ if (existsSync15(candidate)) {
3084
3138
  return candidate;
3085
3139
  }
3086
3140
  }
3087
- const localCandidate = resolve16(import.meta.dir, "binary-build-worker.ts");
3088
- return existsSync14(localCandidate) ? localCandidate : null;
3141
+ const localCandidate = resolve17(import.meta.dir, "binary-build-worker.ts");
3142
+ return existsSync15(localCandidate) ? localCandidate : null;
3089
3143
  }
3090
3144
  function resolveRuntimeBinaryBuildWorkerInvocation() {
3091
3145
  const bunPath = Bun.which("bun");
@@ -3121,7 +3175,7 @@ function createRuntimeBinaryBuildKey(input) {
3121
3175
  });
3122
3176
  }
3123
3177
  async function isRuntimeBinaryBuildFresh(input) {
3124
- if (!existsSync14(input.outputPath) || !existsSync14(input.manifestPath)) {
3178
+ if (!existsSync15(input.outputPath) || !existsSync15(input.manifestPath)) {
3125
3179
  return false;
3126
3180
  }
3127
3181
  let manifest = null;
@@ -3134,7 +3188,7 @@ async function isRuntimeBinaryBuildFresh(input) {
3134
3188
  return false;
3135
3189
  }
3136
3190
  for (const [filePath, expectedDigest] of Object.entries(manifest.inputs || {})) {
3137
- if (!existsSync14(filePath)) {
3191
+ if (!existsSync15(filePath)) {
3138
3192
  return false;
3139
3193
  }
3140
3194
  if (await sha256File(filePath) !== expectedDigest) {
@@ -3147,7 +3201,7 @@ async function writeRuntimeBinaryCacheManifest(input) {
3147
3201
  const inputs = {};
3148
3202
  for (const inputPath of Object.keys(input.metafile?.inputs || {}).sort()) {
3149
3203
  const normalized = normalizeBuildInputPath(input.cwd, inputPath);
3150
- if (!normalized || !existsSync14(normalized)) {
3204
+ if (!normalized || !existsSync15(normalized)) {
3151
3205
  continue;
3152
3206
  }
3153
3207
  inputs[normalized] = await sha256File(normalized);
@@ -3170,7 +3224,7 @@ function normalizeBuildInputPath(cwd, inputPath) {
3170
3224
  if (inputPath.startsWith("<")) {
3171
3225
  return null;
3172
3226
  }
3173
- return resolve16(cwd, inputPath);
3227
+ return resolve17(cwd, inputPath);
3174
3228
  }
3175
3229
  async function sha256File(path) {
3176
3230
  const hasher = new Bun.CryptoHasher("sha256");
@@ -3186,8 +3240,8 @@ function sortRecord(value) {
3186
3240
  async function runSerializedRuntimeBinaryBuild(action) {
3187
3241
  const previous = runtimeBinaryBuildQueue;
3188
3242
  let release;
3189
- runtimeBinaryBuildQueue = new Promise((resolve17) => {
3190
- release = resolve17;
3243
+ runtimeBinaryBuildQueue = new Promise((resolve18) => {
3244
+ release = resolve18;
3191
3245
  });
3192
3246
  await previous;
3193
3247
  try {
@@ -3232,11 +3286,11 @@ async function withTemporaryCwd(cwd, action) {
3232
3286
  }
3233
3287
 
3234
3288
  // packages/runtime/src/control-plane/runtime/provisioning-env.ts
3235
- import { delimiter, resolve as resolve18 } from "path";
3289
+ import { delimiter, resolve as resolve19 } from "path";
3236
3290
 
3237
3291
  // packages/runtime/src/control-plane/runtime/runtime-paths.ts
3238
- import { existsSync as existsSync15, readdirSync as readdirSync3, realpathSync } from "fs";
3239
- import { resolve as resolve17 } from "path";
3292
+ import { existsSync as existsSync16, readdirSync as readdirSync4, realpathSync } from "fs";
3293
+ import { resolve as resolve18 } from "path";
3240
3294
 
3241
3295
  // packages/runtime/src/control-plane/runtime/sandbox/utils.ts
3242
3296
  function uniq(values) {
@@ -3254,7 +3308,7 @@ function resolveBunBinaryPath() {
3254
3308
  }
3255
3309
  const home = process.env.HOME?.trim();
3256
3310
  const fallbackCandidates = [
3257
- home ? resolve17(home, ".bun/bin/bun") : "",
3311
+ home ? resolve18(home, ".bun/bin/bun") : "",
3258
3312
  "/opt/homebrew/bin/bun",
3259
3313
  "/usr/local/bin/bun",
3260
3314
  "/usr/bin/bun"
@@ -3282,8 +3336,8 @@ function resolveClaudeBinaryPath() {
3282
3336
  }
3283
3337
  const home = process.env.HOME?.trim();
3284
3338
  const fallbackCandidates = [
3285
- home ? resolve17(home, ".local/bin/claude") : "",
3286
- home ? resolve17(home, ".local/share/claude/local/claude") : "",
3339
+ home ? resolve18(home, ".local/bin/claude") : "",
3340
+ home ? resolve18(home, ".local/share/claude/local/claude") : "",
3287
3341
  "/opt/homebrew/bin/claude",
3288
3342
  "/usr/local/bin/claude",
3289
3343
  "/usr/bin/claude"
@@ -3297,51 +3351,51 @@ function resolveClaudeBinaryPath() {
3297
3351
  throw new Error("claude not found in PATH");
3298
3352
  }
3299
3353
  function resolveBunInstallDir(bunBinaryPath = resolveBunBinaryPath()) {
3300
- return resolve17(bunBinaryPath, "../..");
3354
+ return resolve18(bunBinaryPath, "../..");
3301
3355
  }
3302
3356
  function resolveClaudeInstallDir() {
3303
3357
  const realPath = resolveClaudeBinaryPath();
3304
- return resolve17(realPath, "..");
3358
+ return resolve18(realPath, "..");
3305
3359
  }
3306
3360
  function resolveNodeInstallDir() {
3307
3361
  const preferredNode = resolvePreferredNodeBinary();
3308
3362
  if (!preferredNode)
3309
3363
  return null;
3310
3364
  const explicitNode = process.env.RIG_NODE_BIN?.trim();
3311
- if (explicitNode && resolve17(explicitNode) === resolve17(preferredNode)) {
3312
- return preferredNode.endsWith("/bin/node") ? resolve17(preferredNode, "../..") : resolve17(preferredNode, "..");
3365
+ if (explicitNode && resolve18(explicitNode) === resolve18(preferredNode)) {
3366
+ return preferredNode.endsWith("/bin/node") ? resolve18(preferredNode, "../..") : resolve18(preferredNode, "..");
3313
3367
  }
3314
3368
  try {
3315
3369
  const realPath = realpathSync(preferredNode);
3316
3370
  if (realPath.endsWith("/bin/node")) {
3317
- return resolve17(realPath, "../..");
3371
+ return resolve18(realPath, "../..");
3318
3372
  }
3319
- return resolve17(realPath, "..");
3373
+ return resolve18(realPath, "..");
3320
3374
  } catch {
3321
- return resolve17(preferredNode, "..");
3375
+ return resolve18(preferredNode, "..");
3322
3376
  }
3323
3377
  }
3324
3378
  function resolvePreferredNodeBinary() {
3325
3379
  const candidates = [];
3326
3380
  const envNode = process.env.RIG_NODE_BIN?.trim();
3327
3381
  if (envNode) {
3328
- const explicit = resolve17(envNode);
3329
- if (existsSync15(explicit)) {
3382
+ const explicit = resolve18(envNode);
3383
+ if (existsSync16(explicit)) {
3330
3384
  return explicit;
3331
3385
  }
3332
3386
  }
3333
3387
  const nvmBin = process.env.NVM_BIN?.trim();
3334
3388
  if (nvmBin) {
3335
- candidates.push(resolve17(nvmBin, "node"));
3389
+ candidates.push(resolve18(nvmBin, "node"));
3336
3390
  }
3337
3391
  const home = process.env.HOME?.trim();
3338
3392
  if (home) {
3339
- const nvmVersionsDir = resolve17(home, ".nvm/versions/node");
3340
- if (existsSync15(nvmVersionsDir)) {
3393
+ const nvmVersionsDir = resolve18(home, ".nvm/versions/node");
3394
+ if (existsSync16(nvmVersionsDir)) {
3341
3395
  try {
3342
- const versionDirs = readdirSync3(nvmVersionsDir).map((entry) => entry.trim()).filter((entry) => /^v\d+\.\d+\.\d+$/.test(entry)).sort((a, b) => Bun.semver.order(b.replace(/^v/, ""), a.replace(/^v/, "")));
3396
+ const versionDirs = readdirSync4(nvmVersionsDir).map((entry) => entry.trim()).filter((entry) => /^v\d+\.\d+\.\d+$/.test(entry)).sort((a, b) => Bun.semver.order(b.replace(/^v/, ""), a.replace(/^v/, "")));
3343
3397
  for (const versionDir of versionDirs) {
3344
- candidates.push(resolve17(nvmVersionsDir, versionDir, "bin/node"));
3398
+ candidates.push(resolve18(nvmVersionsDir, versionDir, "bin/node"));
3345
3399
  }
3346
3400
  } catch {}
3347
3401
  }
@@ -3350,8 +3404,8 @@ function resolvePreferredNodeBinary() {
3350
3404
  if (whichNode) {
3351
3405
  candidates.push(whichNode);
3352
3406
  }
3353
- const deduped = uniq(candidates.map((candidate) => resolve17(candidate)));
3354
- const existing = deduped.filter((candidate) => existsSync15(candidate));
3407
+ const deduped = uniq(candidates.map((candidate) => resolve18(candidate)));
3408
+ const existing = deduped.filter((candidate) => existsSync16(candidate));
3355
3409
  if (existing.length === 0) {
3356
3410
  return null;
3357
3411
  }
@@ -3365,7 +3419,7 @@ function resolvePreferredNodeBinary() {
3365
3419
  return existing[0] ?? null;
3366
3420
  }
3367
3421
  function inferNodeMajor(nodeBinaryPath) {
3368
- const normalized = resolve17(nodeBinaryPath).replace(/\\/g, "/");
3422
+ const normalized = resolve18(nodeBinaryPath).replace(/\\/g, "/");
3369
3423
  const match = normalized.match(/(?:^|\/)(?:node-)?v?(\d+)\.\d+\.\d+(?:\/|$)/);
3370
3424
  if (!match) {
3371
3425
  return null;
@@ -3377,8 +3431,8 @@ function normalizeExecutablePath(candidate) {
3377
3431
  if (!candidate) {
3378
3432
  return "";
3379
3433
  }
3380
- const normalized = resolve17(candidate);
3381
- if (!existsSync15(normalized)) {
3434
+ const normalized = resolve18(candidate);
3435
+ if (!existsSync16(normalized)) {
3382
3436
  return "";
3383
3437
  }
3384
3438
  try {
@@ -3388,7 +3442,7 @@ function normalizeExecutablePath(candidate) {
3388
3442
  }
3389
3443
  }
3390
3444
  function looksLikeRuntimeGateway(candidate) {
3391
- const normalized = resolve17(candidate).replace(/\\/g, "/");
3445
+ const normalized = resolve18(candidate).replace(/\\/g, "/");
3392
3446
  return normalized.includes("/.rig/bin/") || normalized.endsWith("/rig-shell") || normalized.endsWith("/rig-agent");
3393
3447
  }
3394
3448
 
@@ -3409,7 +3463,7 @@ function runtimeProvisioningEnv(baseEnv = process.env) {
3409
3463
  try {
3410
3464
  return resolveClaudeInstallDir();
3411
3465
  } catch {
3412
- return resolve18(claudeBinary, "..");
3466
+ return resolve19(claudeBinary, "..");
3413
3467
  }
3414
3468
  })() : "";
3415
3469
  const nodeDir = resolveNodeInstallDir();
@@ -3419,8 +3473,8 @@ function runtimeProvisioningEnv(baseEnv = process.env) {
3419
3473
  `${bunDir}/bin`,
3420
3474
  claudeDir,
3421
3475
  nodeDir ? `${nodeDir}/bin` : "",
3422
- realHome ? resolve18(realHome, ".local/bin") : "",
3423
- realHome ? resolve18(realHome, ".cargo/bin") : "",
3476
+ realHome ? resolve19(realHome, ".local/bin") : "",
3477
+ realHome ? resolve19(realHome, ".cargo/bin") : "",
3424
3478
  ...inheritedPath,
3425
3479
  "/usr/local/bin",
3426
3480
  "/usr/local/sbin",
@@ -3451,9 +3505,9 @@ function runtimeProvisioningEnv(baseEnv = process.env) {
3451
3505
  // packages/runtime/src/control-plane/native/validator-binaries.ts
3452
3506
  function resolveValidatorBinaryPath(projectRoot, binaryName, runtimeContext) {
3453
3507
  if (runtimeContext) {
3454
- return resolve19(runtimeContext.binDir, "validators", binaryName);
3508
+ return resolve20(runtimeContext.binDir, "validators", binaryName);
3455
3509
  }
3456
- return resolve19(resolveHarnessPaths(projectRoot).binDir, "validators", binaryName);
3510
+ return resolve20(resolveHarnessPaths(projectRoot).binDir, "validators", binaryName);
3457
3511
  }
3458
3512
  async function ensureValidatorBinary(projectRoot, checkId, runtimeContext) {
3459
3513
  const match = checkId.match(/^([a-z][\w-]*):([a-z][\w-]*)$/);
@@ -3468,19 +3522,19 @@ async function ensureValidatorBinary(projectRoot, checkId, runtimeContext) {
3468
3522
  const binaryName = `${category}-${check}`;
3469
3523
  const binaryPath = resolveValidatorBinaryPath(projectRoot, binaryName, runtimeContext);
3470
3524
  const hostProjectRoot = runtimeContext?.hostProjectRoot?.trim() || projectRoot;
3471
- const sourcePath = resolve19(hostProjectRoot, "packages/runtime/src/control-plane/validators", category, `${check}.ts`);
3472
- if (!existsSync16(sourcePath)) {
3525
+ const sourcePath = resolve20(hostProjectRoot, "packages/runtime/src/control-plane/validators", category, `${check}.ts`);
3526
+ if (!existsSync17(sourcePath)) {
3473
3527
  return null;
3474
3528
  }
3475
3529
  const sourceMtime = statSync4(sourcePath).mtimeMs;
3476
- const binaryExists = existsSync16(binaryPath);
3530
+ const binaryExists = existsSync17(binaryPath);
3477
3531
  const binaryMtime = binaryExists ? statSync4(binaryPath).mtimeMs : 0;
3478
3532
  if (!binaryExists || sourceMtime > binaryMtime) {
3479
3533
  if (binaryExists) {
3480
- rmSync4(binaryPath, { force: true });
3481
- rmSync4(`${binaryPath}.build-manifest.json`, { force: true });
3534
+ rmSync5(binaryPath, { force: true });
3535
+ rmSync5(`${binaryPath}.build-manifest.json`, { force: true });
3482
3536
  }
3483
- mkdirSync6(dirname10(binaryPath), { recursive: true });
3537
+ mkdirSync7(dirname10(binaryPath), { recursive: true });
3484
3538
  await buildRuntimeBinary({
3485
3539
  sourcePath: `packages/runtime/src/control-plane/validators/${category}/${check}.ts`,
3486
3540
  outputPath: binaryPath,
@@ -3489,7 +3543,7 @@ async function ensureValidatorBinary(projectRoot, checkId, runtimeContext) {
3489
3543
  env: runtimeProvisioningEnv()
3490
3544
  });
3491
3545
  }
3492
- return existsSync16(binaryPath) ? binaryPath : null;
3546
+ return existsSync17(binaryPath) ? binaryPath : null;
3493
3547
  }
3494
3548
 
3495
3549
  // packages/runtime/src/control-plane/native/validator.ts
@@ -3526,20 +3580,20 @@ async function readTaskSourceValidation(projectRoot, taskId) {
3526
3580
  function resolveValidationPaths(projectRoot, taskId, runtimeContext) {
3527
3581
  if (runtimeContext) {
3528
3582
  return {
3529
- taskLogDir: resolve20(runtimeContext.logsDir, taskId),
3530
- artifactDir: resolve20(runtimeContext.workspaceDir, "artifacts", taskId)
3583
+ taskLogDir: resolve21(runtimeContext.logsDir, taskId),
3584
+ artifactDir: resolve21(runtimeContext.workspaceDir, "artifacts", taskId)
3531
3585
  };
3532
3586
  }
3533
3587
  const paths = resolveHarnessPaths(projectRoot);
3534
3588
  return {
3535
- taskLogDir: resolve20(paths.logsDir, taskId),
3536
- artifactDir: resolve20(paths.artifactsDir, taskId)
3589
+ taskLogDir: resolve21(paths.logsDir, taskId),
3590
+ artifactDir: resolve21(paths.artifactsDir, taskId)
3537
3591
  };
3538
3592
  }
3539
3593
  async function runValidatorBinary(projectRoot, taskId, checkId, runtimeContext) {
3540
3594
  const binaryName = checkId.replace(":", "-");
3541
3595
  const binaryPath = await ensureValidatorBinary(projectRoot, checkId, runtimeContext) ?? resolveValidatorBinaryPath(projectRoot, binaryName, runtimeContext);
3542
- if (!existsSync17(binaryPath)) {
3596
+ if (!existsSync18(binaryPath)) {
3543
3597
  return {
3544
3598
  result: {
3545
3599
  id: checkId,
@@ -3550,7 +3604,7 @@ async function runValidatorBinary(projectRoot, taskId, checkId, runtimeContext)
3550
3604
  };
3551
3605
  }
3552
3606
  const validatorCwd = runtimeContext?.workspaceDir || resolveMonorepoRoot(projectRoot);
3553
- const runtimeShellPath = runtimeContext ? resolve20(runtimeContext.binDir, "rig-shell") : "";
3607
+ const runtimeShellPath = runtimeContext ? resolve21(runtimeContext.binDir, "rig-shell") : "";
3554
3608
  const monorepoMainRoot = runtimeContext?.monorepoMainRoot || process.env.MONOREPO_MAIN_ROOT?.trim() || resolveMonorepoRoot(projectRoot);
3555
3609
  const validatorEnv = {
3556
3610
  PROJECT_RIG_ROOT: runtimeContext?.hostProjectRoot || projectRoot,
@@ -3565,7 +3619,7 @@ async function runValidatorBinary(projectRoot, taskId, checkId, runtimeContext)
3565
3619
  validatorEnv.RIG_LOGS_DIR = runtimeContext.logsDir;
3566
3620
  validatorEnv.RIG_RUNTIME_BIN_DIR = runtimeContext.binDir;
3567
3621
  }
3568
- const { exitCode, stdout, stderr } = await runCaptureAsync(runtimeShellPath && existsSync17(runtimeShellPath) ? [runtimeShellPath, "run-binary", binaryPath] : [binaryPath], validatorCwd, validatorEnv);
3622
+ const { exitCode, stdout, stderr } = await runCaptureAsync(runtimeShellPath && existsSync18(runtimeShellPath) ? [runtimeShellPath, "run-binary", binaryPath] : [binaryPath], validatorCwd, validatorEnv);
3569
3623
  try {
3570
3624
  const result = JSON.parse(stdout.trim());
3571
3625
  return { result, exitCode };
@@ -3605,8 +3659,8 @@ async function validateTask(projectRoot, taskId, runtimeContext, registry, optio
3605
3659
  const configuredValidation = stringArray(taskConfig[taskId]?.validation);
3606
3660
  const commands = resolvedContext?.validation?.length ? resolvedContext.validation : configuredValidation.length > 0 ? configuredValidation : sourceValidation.validation;
3607
3661
  const { taskLogDir, artifactDir } = resolveValidationPaths(projectRoot, taskId, resolvedContext);
3608
- mkdirSync7(taskLogDir, { recursive: true });
3609
- mkdirSync7(artifactDir, { recursive: true });
3662
+ mkdirSync8(taskLogDir, { recursive: true });
3663
+ mkdirSync8(artifactDir, { recursive: true });
3610
3664
  if (commands.length === 0) {
3611
3665
  const skipped = {
3612
3666
  status: "skipped",
@@ -3615,7 +3669,7 @@ async function validateTask(projectRoot, taskId, runtimeContext, registry, optio
3615
3669
  failed: 0,
3616
3670
  categories: []
3617
3671
  };
3618
- writeFileSync7(resolve20(artifactDir, "validation-summary.json"), `${JSON.stringify(skipped, null, 2)}
3672
+ writeFileSync8(resolve21(artifactDir, "validation-summary.json"), `${JSON.stringify(skipped, null, 2)}
3619
3673
  `, "utf-8");
3620
3674
  return skipped;
3621
3675
  }
@@ -3650,18 +3704,18 @@ async function validateTask(projectRoot, taskId, runtimeContext, registry, optio
3650
3704
  exit_code: 2,
3651
3705
  duration_seconds: 0
3652
3706
  });
3653
- const logFile2 = resolve20(taskLogDir, `invalid-entry-validation.log`);
3654
- mkdirSync7(taskLogDir, { recursive: true });
3655
- writeFileSync7(logFile2, `=== ${nowIso()} :: ${cmd} ===
3707
+ const logFile2 = resolve21(taskLogDir, `invalid-entry-validation.log`);
3708
+ mkdirSync8(taskLogDir, { recursive: true });
3709
+ writeFileSync8(logFile2, `=== ${nowIso()} :: ${cmd} ===
3656
3710
  Invalid validation entry: not a check-ID. All entries must use format "category:check-name".
3657
3711
  `, "utf-8");
3658
3712
  continue;
3659
3713
  }
3660
3714
  const { result, exitCode } = await dispatchValidator(cmd, effectiveRegistry, validatorCtx, (id) => runValidatorBinary(projectRoot, taskId, id, resolvedContext));
3661
3715
  const durationSeconds = Math.max(0, Math.round((Date.now() - startedAt) / 1000));
3662
- const logFile = resolve20(taskLogDir, `${cmd.replace(":", "-")}-validation.log`);
3663
- mkdirSync7(taskLogDir, { recursive: true });
3664
- writeFileSync7(logFile, `=== ${nowIso()} :: ${cmd} ===
3716
+ const logFile = resolve21(taskLogDir, `${cmd.replace(":", "-")}-validation.log`);
3717
+ mkdirSync8(taskLogDir, { recursive: true });
3718
+ writeFileSync8(logFile, `=== ${nowIso()} :: ${cmd} ===
3665
3719
  ${JSON.stringify(result, null, 2)}
3666
3720
  `, "utf-8");
3667
3721
  if (result.passed) {
@@ -3683,15 +3737,15 @@ ${JSON.stringify(result, null, 2)}
3683
3737
  failed,
3684
3738
  categories
3685
3739
  };
3686
- mkdirSync7(artifactDir, { recursive: true });
3687
- writeFileSync7(resolve20(artifactDir, "validation-summary.json"), `${JSON.stringify(summary, null, 2)}
3740
+ mkdirSync8(artifactDir, { recursive: true });
3741
+ writeFileSync8(resolve21(artifactDir, "validation-summary.json"), `${JSON.stringify(summary, null, 2)}
3688
3742
  `, "utf-8");
3689
3743
  return summary;
3690
3744
  }
3691
3745
 
3692
3746
  // packages/runtime/src/control-plane/native/verifier.ts
3693
- import { existsSync as existsSync19, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
3694
- import { resolve as resolve22 } from "path";
3747
+ import { existsSync as existsSync20, mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
3748
+ import { resolve as resolve23 } from "path";
3695
3749
 
3696
3750
  // packages/runtime/src/control-plane/runtime/baked-secrets.ts
3697
3751
  var BAKED_RUNTIME_SECRETS = {
@@ -3737,8 +3791,1253 @@ function resolveRuntimeSecrets(env, baked = BAKED_RUNTIME_SECRETS) {
3737
3791
  }
3738
3792
 
3739
3793
  // packages/runtime/src/control-plane/native/git-ops.ts
3740
- import { existsSync as existsSync18, lstatSync, mkdirSync as mkdirSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
3741
- import { dirname as dirname11, isAbsolute as isAbsolute2, resolve as resolve21 } from "path";
3794
+ import { existsSync as existsSync19, lstatSync, mkdirSync as mkdirSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync9 } from "fs";
3795
+ import { dirname as dirname11, isAbsolute as isAbsolute2, resolve as resolve22 } from "path";
3796
+
3797
+ // packages/runtime/src/control-plane/native/pr-review-gate.ts
3798
+ function parseJsonObject(value) {
3799
+ if (!value?.trim())
3800
+ return { value: {}, error: "empty JSON output" };
3801
+ try {
3802
+ const parsed = JSON.parse(value);
3803
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? { value: parsed } : { value: {}, error: "JSON output was not an object" };
3804
+ } catch (error) {
3805
+ return { value: {}, error: error instanceof Error ? error.message : String(error) };
3806
+ }
3807
+ }
3808
+ function flattenPaginatedArray(value) {
3809
+ if (!Array.isArray(value))
3810
+ return null;
3811
+ if (value.every((entry) => Array.isArray(entry))) {
3812
+ return value.flatMap((entry) => entry);
3813
+ }
3814
+ return value;
3815
+ }
3816
+ function parseConcatenatedJsonValues(value) {
3817
+ const text = value.trim();
3818
+ const docs = [];
3819
+ let start = null;
3820
+ let depth = 0;
3821
+ let inString = false;
3822
+ let escape = false;
3823
+ for (let index = 0;index < text.length; index += 1) {
3824
+ const char = text[index];
3825
+ if (start === null) {
3826
+ if (/\s/.test(char))
3827
+ continue;
3828
+ start = index;
3829
+ }
3830
+ if (inString) {
3831
+ if (escape) {
3832
+ escape = false;
3833
+ } else if (char === "\\") {
3834
+ escape = true;
3835
+ } else if (char === '"') {
3836
+ inString = false;
3837
+ }
3838
+ continue;
3839
+ }
3840
+ if (char === '"') {
3841
+ inString = true;
3842
+ continue;
3843
+ }
3844
+ if (char === "{" || char === "[") {
3845
+ depth += 1;
3846
+ continue;
3847
+ }
3848
+ if (char === "}" || char === "]") {
3849
+ depth -= 1;
3850
+ if (depth < 0)
3851
+ return { value: docs, error: "unexpected JSON close delimiter" };
3852
+ if (depth === 0 && start !== null) {
3853
+ const segment = text.slice(start, index + 1);
3854
+ try {
3855
+ docs.push(JSON.parse(segment));
3856
+ } catch (error) {
3857
+ return { value: docs, error: error instanceof Error ? error.message : String(error) };
3858
+ }
3859
+ start = null;
3860
+ }
3861
+ }
3862
+ }
3863
+ if (inString || depth !== 0 || start !== null)
3864
+ return { value: docs, error: "incomplete JSON stream" };
3865
+ return { value: docs };
3866
+ }
3867
+ function parseJsonArray(value) {
3868
+ if (!value?.trim())
3869
+ return { value: [], error: "empty JSON output" };
3870
+ try {
3871
+ const parsed = JSON.parse(value);
3872
+ const flattened = flattenPaginatedArray(parsed);
3873
+ return flattened ? { value: flattened } : { value: [], error: "JSON output was not an array" };
3874
+ } catch (error) {
3875
+ const streamed = parseConcatenatedJsonValues(value);
3876
+ if (streamed.error)
3877
+ return { value: [], error: error instanceof Error ? error.message : String(error) };
3878
+ const flattened = streamed.value.flatMap((entry) => flattenPaginatedArray(entry) ?? []);
3879
+ return flattened.length > 0 || streamed.value.length === 0 ? { value: flattened } : { value: [], error: "JSON output was not an array" };
3880
+ }
3881
+ }
3882
+ function parseGithubPrUrl(prUrl) {
3883
+ const match = /^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)\/?$/i.exec(prUrl.trim());
3884
+ if (!match)
3885
+ return null;
3886
+ const prNumber = Number.parseInt(match[3], 10);
3887
+ if (!Number.isFinite(prNumber))
3888
+ return null;
3889
+ return { owner: match[1], repo: match[2], repoName: `${match[1]}/${match[2]}`, prNumber };
3890
+ }
3891
+ function checkName(check) {
3892
+ return String(check.name ?? check.context ?? "").trim();
3893
+ }
3894
+ function checkState(check) {
3895
+ return String(check.conclusion ?? check.state ?? check.status ?? "").trim().toLowerCase();
3896
+ }
3897
+ function isGreptileLabel(value) {
3898
+ return String(value ?? "").toLowerCase().includes("greptile");
3899
+ }
3900
+ function isGreptileGithubLogin(value) {
3901
+ const login = String(value ?? "").toLowerCase().replace(/\[bot\]$/, "");
3902
+ return login === "greptile" || login === "greptile-ai" || login === "greptileai" || login === "greptile-apps";
3903
+ }
3904
+ function isPassingCheck(check) {
3905
+ const state = checkState(check);
3906
+ return ["success", "successful", "passed", "neutral", "skipped", "completed"].includes(state);
3907
+ }
3908
+ function isPendingCheck(check) {
3909
+ const state = checkState(check);
3910
+ return ["pending", "queued", "in_progress", "waiting", "requested", "expected", "action_required"].includes(state);
3911
+ }
3912
+ function isFailingCheck(check) {
3913
+ const state = checkState(check);
3914
+ return ["failure", "failed", "timed_out", "action_required", "cancelled", "canceled", "error"].includes(state);
3915
+ }
3916
+ function wildcardToRegExp(pattern) {
3917
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
3918
+ return new RegExp(`^${escaped}$`, "i");
3919
+ }
3920
+ function isAllowedFailure(name, allowedFailures) {
3921
+ return allowedFailures.some((pattern) => wildcardToRegExp(pattern).test(name));
3922
+ }
3923
+ function greptileScorePatterns() {
3924
+ return [
3925
+ /\b(?:confidence\s+score|confidence|rating|score)\s*:?\s*(\d+)\s*\/\s*(\d+)/gi,
3926
+ /\b(\d+)\s*\/\s*(\d+)\s*(?:confidence|rating|score)/gi,
3927
+ /\bgreptile[^\n]{0,80}?(\d+)\s*\/\s*(\d+)/gi
3928
+ ];
3929
+ }
3930
+ function parseGreptileScores(input) {
3931
+ const text = stripHtml(input);
3932
+ const seen = new Set;
3933
+ const scores = [];
3934
+ for (const pattern of greptileScorePatterns()) {
3935
+ for (const match of text.matchAll(pattern)) {
3936
+ const value = Number.parseInt(match[1] || "", 10);
3937
+ const scale = Number.parseInt(match[2] || "", 10);
3938
+ if (!Number.isFinite(value) || !Number.isFinite(scale) || scale <= 0)
3939
+ continue;
3940
+ const raw = match[0] || `${value}/${scale}`;
3941
+ const key = `${match.index ?? -1}:${value}/${scale}:${raw.toLowerCase()}`;
3942
+ if (seen.has(key))
3943
+ continue;
3944
+ seen.add(key);
3945
+ scores.push({ value, scale, raw });
3946
+ }
3947
+ }
3948
+ return scores;
3949
+ }
3950
+ function parseGreptileScore(input) {
3951
+ return parseGreptileScores(input)[0] ?? null;
3952
+ }
3953
+ function stripHtml(input) {
3954
+ return input.replace(/<[^>]+>/g, " ").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/\r/g, "").replace(/\n{3,}/g, `
3955
+
3956
+ `).trim();
3957
+ }
3958
+ function containsBlockerText(input) {
3959
+ const text = stripHtml(input).replace(/\b(?:no|without|zero)\s+blockers?\b/gi, " ").replace(/\bno\s+changes\s+requested\b/gi, " ");
3960
+ return /not safe(?: to merge)?|unsafe(?: to merge)?|do not merge|cannot merge|blockers?|must fix|changes requested|please fix|needs? fix|fix this|address this|\breject(?:ed|ion)?\b|\bskip(?:ped)?\b|status\s*:\s*(?:reject(?:ed)?|skip(?:ped)?|failed)/i.test(text);
3961
+ }
3962
+ function isStrictFiveOfFive(score) {
3963
+ return score.value === 5 && score.scale === 5;
3964
+ }
3965
+ function containsConflictingScoreText(input) {
3966
+ return parseGreptileScores(input).some((score) => !isStrictFiveOfFive(score));
3967
+ }
3968
+ function extractGreptileCommentBlock(input) {
3969
+ const match = input.match(/<!--\s*greptile_comment\s*-->([\s\S]*?)<!--\s*\/greptile_comment\s*-->/i);
3970
+ return match?.[1]?.trim() ?? null;
3971
+ }
3972
+ function extractGreptileBodyReviewedSha(input) {
3973
+ const block = extractGreptileCommentBlock(input);
3974
+ if (!block)
3975
+ return null;
3976
+ const commitLink = block.match(/\/commit\/([0-9a-f]{40})(?:\b|[^0-9a-f])/i);
3977
+ return commitLink?.[1]?.toLowerCase() ?? null;
3978
+ }
3979
+ function isoAtOrAfter(value, floor) {
3980
+ if (!value || !floor)
3981
+ return false;
3982
+ const valueMs = Date.parse(value);
3983
+ const floorMs = Date.parse(floor);
3984
+ return Number.isFinite(valueMs) && Number.isFinite(floorMs) && valueMs >= floorMs;
3985
+ }
3986
+ function greptileStatusVerdict(status) {
3987
+ const normalized = String(status ?? "").trim().toUpperCase().replace(/[\s-]+/g, "_");
3988
+ if (!normalized)
3989
+ return null;
3990
+ if (["APPROVE", "APPROVED"].includes(normalized))
3991
+ return "approved";
3992
+ if (["REJECT", "REJECTED", "CHANGES_REQUESTED", "CHANGE_REQUESTED"].includes(normalized))
3993
+ return "rejected";
3994
+ if (["SKIP", "SKIPPED"].includes(normalized))
3995
+ return "skipped";
3996
+ if (["FAIL", "FAILED", "FAILURE", "ERROR"].includes(normalized))
3997
+ return "failed";
3998
+ if (["PENDING", "QUEUED", "IN_PROGRESS", "RUNNING", "STARTED", "REQUESTED", "REVIEWING_FILES", "GENERATING_SUMMARY"].includes(normalized))
3999
+ return "pending";
4000
+ if (["COMPLETE", "COMPLETED"].includes(normalized))
4001
+ return "completed";
4002
+ return null;
4003
+ }
4004
+ function isBlockingGreptileVerdict(verdict) {
4005
+ return verdict === "rejected" || verdict === "skipped" || verdict === "failed";
4006
+ }
4007
+ function greptileRequestTimeoutMs(env) {
4008
+ const fallback = 30000;
4009
+ const parsed = Number.parseInt(env.GREPTILE_REQUEST_TIMEOUT_MS || `${fallback}`, 10);
4010
+ return Number.isFinite(parsed) && parsed >= 1000 ? parsed : fallback;
4011
+ }
4012
+ function normalizeGreptileMcpCodeReview(entry, fallbackId) {
4013
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
4014
+ return null;
4015
+ const record = entry;
4016
+ const id = typeof record.id === "string" ? record.id.trim() : fallbackId?.trim() ?? "";
4017
+ if (!id)
4018
+ return null;
4019
+ const metadataRecord = record.metadata && typeof record.metadata === "object" && !Array.isArray(record.metadata) ? record.metadata : null;
4020
+ return {
4021
+ id,
4022
+ status: typeof record.status === "string" ? record.status : null,
4023
+ createdAt: typeof record.createdAt === "string" ? record.createdAt : null,
4024
+ body: typeof record.body === "string" ? record.body : null,
4025
+ metadata: metadataRecord ? { checkHeadSha: typeof metadataRecord.checkHeadSha === "string" ? metadataRecord.checkHeadSha : null } : null
4026
+ };
4027
+ }
4028
+ function uniqueGreptileCodeReviews(reviews) {
4029
+ const seen = new Set;
4030
+ const unique2 = [];
4031
+ for (const review of reviews) {
4032
+ if (seen.has(review.id))
4033
+ continue;
4034
+ seen.add(review.id);
4035
+ unique2.push(review);
4036
+ }
4037
+ return unique2;
4038
+ }
4039
+ function selectGreptileApiReviewsForGate(reviews, headSha) {
4040
+ const sorted = [...reviews].sort((left, right) => Date.parse(right.createdAt ?? "") - Date.parse(left.createdAt ?? ""));
4041
+ const current = headSha ? sorted.filter((review) => review.metadata?.checkHeadSha === headSha) : [];
4042
+ const untied = sorted.filter((review) => !review.metadata?.checkHeadSha);
4043
+ const latest = sorted.slice(0, 1);
4044
+ return uniqueGreptileCodeReviews([...current, ...untied, ...latest]);
4045
+ }
4046
+ function greptileApiSignalFromCodeReview(review, details) {
4047
+ const selected = details ?? review;
4048
+ return {
4049
+ id: selected.id || review.id,
4050
+ body: selected.body ?? review.body ?? null,
4051
+ reviewedSha: selected.metadata?.checkHeadSha ?? review.metadata?.checkHeadSha ?? null,
4052
+ status: selected.status ?? review.status ?? null
4053
+ };
4054
+ }
4055
+ async function callGreptileMcpToolForGate(input) {
4056
+ const controller = new AbortController;
4057
+ const timeoutId = setTimeout(() => {
4058
+ controller.abort(new Error(`Greptile MCP tool ${input.name} timed out after ${input.timeoutMs}ms.`));
4059
+ }, input.timeoutMs);
4060
+ let response;
4061
+ try {
4062
+ response = await input.fetchFn(input.apiBase, {
4063
+ method: "POST",
4064
+ headers: {
4065
+ Authorization: `Bearer ${input.apiKey}`,
4066
+ "Content-Type": "application/json"
4067
+ },
4068
+ body: JSON.stringify({
4069
+ jsonrpc: "2.0",
4070
+ id: `rig-strict-gate-${input.name}-${Date.now()}`,
4071
+ method: "tools/call",
4072
+ params: { name: input.name, arguments: input.args }
4073
+ }),
4074
+ signal: controller.signal
4075
+ });
4076
+ } catch (error) {
4077
+ if (controller.signal.aborted) {
4078
+ throw controller.signal.reason instanceof Error ? controller.signal.reason : new Error(`Greptile MCP tool ${input.name} timed out after ${input.timeoutMs}ms.`);
4079
+ }
4080
+ throw error;
4081
+ } finally {
4082
+ clearTimeout(timeoutId);
4083
+ }
4084
+ const raw = await response.text();
4085
+ if (!response.ok) {
4086
+ throw new Error(`HTTP ${response.status}: ${raw}`);
4087
+ }
4088
+ let envelope;
4089
+ try {
4090
+ envelope = JSON.parse(raw);
4091
+ } catch {
4092
+ throw new Error(`Malformed MCP response: ${raw}`);
4093
+ }
4094
+ if (envelope.error?.message) {
4095
+ throw new Error(envelope.error.message);
4096
+ }
4097
+ const text = (envelope.result?.content ?? []).filter((item) => item.type === "text" && typeof item.text === "string").map((item) => item.text ?? "").join(`
4098
+ `).trim();
4099
+ if (!text) {
4100
+ throw new Error(`MCP tool ${input.name} returned no text payload.`);
4101
+ }
4102
+ return text;
4103
+ }
4104
+ async function callGreptileMcpToolJsonForGate(input) {
4105
+ const text = await callGreptileMcpToolForGate(input);
4106
+ try {
4107
+ return JSON.parse(text);
4108
+ } catch {
4109
+ throw new Error(`MCP tool ${input.name} returned malformed JSON: ${text}`);
4110
+ }
4111
+ }
4112
+ async function collectConfiguredGreptileApiSignals(input) {
4113
+ if (!input.enabled || input.options?.enabled === false) {
4114
+ return { signals: [], errors: [] };
4115
+ }
4116
+ const env = input.options?.env ?? process.env;
4117
+ const secrets = resolveRuntimeSecrets(env);
4118
+ const apiKey = secrets.GREPTILE_API_KEY?.trim() ?? "";
4119
+ if (!apiKey) {
4120
+ return { signals: [], errors: [] };
4121
+ }
4122
+ const fetchFn = input.options?.fetch ?? globalThis.fetch;
4123
+ if (typeof fetchFn !== "function") {
4124
+ return { signals: [], errors: ["Greptile API/MCP evidence read failed: fetch is not available."] };
4125
+ }
4126
+ const apiBase = secrets.GREPTILE_API_BASE?.trim() || "https://api.greptile.com/mcp";
4127
+ const remote = secrets.GREPTILE_REMOTE?.trim() || "github";
4128
+ const repository = secrets.GREPTILE_REPOSITORY?.trim() || input.repoName;
4129
+ const defaultBranch = secrets.GREPTILE_DEFAULT_BRANCH?.trim() || input.baseRefName?.trim() || "main";
4130
+ const timeoutMs = greptileRequestTimeoutMs(env);
4131
+ try {
4132
+ const listPayload = await callGreptileMcpToolJsonForGate({
4133
+ apiBase,
4134
+ apiKey,
4135
+ name: "list_code_reviews",
4136
+ args: {
4137
+ name: repository,
4138
+ remote,
4139
+ defaultBranch,
4140
+ prNumber: input.prNumber,
4141
+ limit: 20
4142
+ },
4143
+ timeoutMs,
4144
+ fetchFn
4145
+ });
4146
+ const reviews = (listPayload.codeReviews ?? []).map((entry) => normalizeGreptileMcpCodeReview(entry)).filter((review) => !!review);
4147
+ const selectedReviews = selectGreptileApiReviewsForGate(reviews, input.headSha);
4148
+ const signals = [];
4149
+ for (const review of selectedReviews) {
4150
+ const detailsPayload = await callGreptileMcpToolJsonForGate({
4151
+ apiBase,
4152
+ apiKey,
4153
+ name: "get_code_review",
4154
+ args: { codeReviewId: review.id },
4155
+ timeoutMs,
4156
+ fetchFn
4157
+ });
4158
+ const details = normalizeGreptileMcpCodeReview(detailsPayload.codeReview, review.id) ?? review;
4159
+ signals.push(greptileApiSignalFromCodeReview(review, details));
4160
+ }
4161
+ return { signals, errors: [] };
4162
+ } catch (error) {
4163
+ return {
4164
+ signals: [],
4165
+ errors: [`Greptile API/MCP evidence read failed: ${error instanceof Error ? error.message : String(error)}`]
4166
+ };
4167
+ }
4168
+ }
4169
+ function firstString(record, keys) {
4170
+ for (const key of keys) {
4171
+ const value = record[key];
4172
+ if (typeof value === "string")
4173
+ return value;
4174
+ }
4175
+ return "";
4176
+ }
4177
+ function arrayField(record, key) {
4178
+ const value = record[key];
4179
+ return Array.isArray(value) ? value : [];
4180
+ }
4181
+ async function runJsonArray(command, args, cwd) {
4182
+ const result = await command(args, { cwd });
4183
+ const label = `gh ${args.join(" ")}`;
4184
+ if (result.exitCode !== 0) {
4185
+ return { value: [], error: `${label} failed (${result.exitCode}): ${result.stderr ?? result.stdout ?? ""}`.trim() };
4186
+ }
4187
+ const parsed = parseJsonArray(result.stdout);
4188
+ return parsed.error ? { value: parsed.value, error: `${label} returned invalid JSON: ${parsed.error}` } : { value: parsed.value };
4189
+ }
4190
+ async function runJsonObject(command, args, cwd) {
4191
+ const result = await command(args, { cwd });
4192
+ const label = `gh ${args.join(" ")}`;
4193
+ if (result.exitCode !== 0) {
4194
+ return { value: {}, error: `${label} failed (${result.exitCode}): ${result.stderr ?? result.stdout ?? ""}`.trim() };
4195
+ }
4196
+ const parsed = parseJsonObject(result.stdout);
4197
+ return parsed.error ? { value: parsed.value, error: `${label} returned invalid JSON: ${parsed.error}` } : { value: parsed.value };
4198
+ }
4199
+ function normalizeStatusCheck(entry) {
4200
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
4201
+ return null;
4202
+ const record = entry;
4203
+ const name = firstString(record, ["name", "context"]);
4204
+ if (!name.trim())
4205
+ return null;
4206
+ const output = record.output && typeof record.output === "object" && !Array.isArray(record.output) ? record.output : null;
4207
+ const app = record.app && typeof record.app === "object" && !Array.isArray(record.app) ? record.app : null;
4208
+ return {
4209
+ __typename: typeof record.__typename === "string" ? record.__typename : null,
4210
+ name,
4211
+ context: typeof record.context === "string" ? record.context : null,
4212
+ status: typeof record.status === "string" ? record.status : null,
4213
+ state: typeof record.state === "string" ? record.state : null,
4214
+ conclusion: typeof record.conclusion === "string" ? record.conclusion : null,
4215
+ detailsUrl: typeof record.detailsUrl === "string" ? record.detailsUrl : typeof record.details_url === "string" ? record.details_url : typeof record.html_url === "string" ? record.html_url : typeof record.link === "string" ? record.link : null,
4216
+ link: typeof record.link === "string" ? record.link : typeof record.html_url === "string" ? record.html_url : null,
4217
+ headSha: typeof record.headSha === "string" ? record.headSha : null,
4218
+ head_sha: typeof record.head_sha === "string" ? record.head_sha : null,
4219
+ output: output ? {
4220
+ title: typeof output.title === "string" ? output.title : null,
4221
+ summary: typeof output.summary === "string" ? output.summary : null,
4222
+ text: typeof output.text === "string" ? output.text : null
4223
+ } : null,
4224
+ app: app ? {
4225
+ slug: typeof app.slug === "string" ? app.slug : null,
4226
+ name: typeof app.name === "string" ? app.name : null,
4227
+ owner: app.owner && typeof app.owner === "object" ? app.owner : null
4228
+ } : null
4229
+ };
4230
+ }
4231
+ function normalizeReview(entry) {
4232
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
4233
+ return null;
4234
+ const record = entry;
4235
+ return {
4236
+ id: typeof record.id === "string" ? record.id : typeof record.id === "number" ? String(record.id) : null,
4237
+ state: typeof record.state === "string" ? record.state : null,
4238
+ body: typeof record.body === "string" ? record.body : null,
4239
+ commit_id: typeof record.commit_id === "string" ? record.commit_id : typeof record.commitId === "string" ? record.commitId : record.commit && typeof record.commit === "object" && typeof record.commit.oid === "string" ? record.commit.oid : null,
4240
+ html_url: typeof record.html_url === "string" ? record.html_url : typeof record.url === "string" ? record.url : null,
4241
+ author: record.author && typeof record.author === "object" ? record.author : record.user && typeof record.user === "object" ? record.user : null
4242
+ };
4243
+ }
4244
+ function normalizeReviewComment(entry) {
4245
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
4246
+ return null;
4247
+ const record = entry;
4248
+ const body = typeof record.body === "string" ? record.body : null;
4249
+ const path = typeof record.path === "string" ? record.path : null;
4250
+ if (!body && !path)
4251
+ return null;
4252
+ return {
4253
+ id: typeof record.id === "string" || typeof record.id === "number" ? record.id : null,
4254
+ user: record.user && typeof record.user === "object" ? record.user : null,
4255
+ author: record.author && typeof record.author === "object" ? record.author : null,
4256
+ body,
4257
+ path,
4258
+ html_url: typeof record.html_url === "string" ? record.html_url : null,
4259
+ url: typeof record.url === "string" ? record.url : null,
4260
+ commit_id: typeof record.commit_id === "string" ? record.commit_id : null,
4261
+ original_commit_id: typeof record.original_commit_id === "string" ? record.original_commit_id : null
4262
+ };
4263
+ }
4264
+ function normalizeIssueComment(entry) {
4265
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
4266
+ return null;
4267
+ const record = entry;
4268
+ const body = typeof record.body === "string" ? record.body : null;
4269
+ if (!body)
4270
+ return null;
4271
+ return {
4272
+ id: typeof record.id === "string" || typeof record.id === "number" ? record.id : null,
4273
+ user: record.user && typeof record.user === "object" ? record.user : null,
4274
+ author: record.author && typeof record.author === "object" ? record.author : null,
4275
+ body,
4276
+ html_url: typeof record.html_url === "string" ? record.html_url : null,
4277
+ url: typeof record.url === "string" ? record.url : null,
4278
+ created_at: typeof record.created_at === "string" ? record.created_at : null
4279
+ };
4280
+ }
4281
+ function normalizeReviewThread(entry) {
4282
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
4283
+ return null;
4284
+ const record = entry;
4285
+ return {
4286
+ id: typeof record.id === "string" ? record.id : null,
4287
+ isResolved: typeof record.isResolved === "boolean" ? record.isResolved : null,
4288
+ isOutdated: typeof record.isOutdated === "boolean" ? record.isOutdated : null,
4289
+ comments: record.comments && typeof record.comments === "object" ? record.comments : null
4290
+ };
4291
+ }
4292
+ function relevantIssueComment(comment) {
4293
+ const login = comment.user?.login ?? comment.author?.login ?? "";
4294
+ const body = comment.body ?? "";
4295
+ return isGreptileGithubLogin(login) || containsBlockerText(body) || /greptile|score|confidence|\b\d+\s*\/\s*5\b/i.test(body);
4296
+ }
4297
+ function latestThreadComment(thread) {
4298
+ const nodes = thread.comments?.nodes ?? [];
4299
+ return nodes.length > 0 ? nodes[nodes.length - 1] : null;
4300
+ }
4301
+ function unresolvedThreadSummaries(threads) {
4302
+ return threads.flatMap((thread) => {
4303
+ if (thread.isResolved === true || thread.isOutdated === true)
4304
+ return [];
4305
+ const latest = latestThreadComment(thread);
4306
+ if (!latest)
4307
+ return ["Unresolved review thread"];
4308
+ const path = latest.path ? ` on ${latest.path}` : "";
4309
+ return [`Unresolved review thread${path}: ${(latest.body ?? "").trim() || "(empty comment)"}`];
4310
+ });
4311
+ }
4312
+ function collectBodies(evidence) {
4313
+ return [
4314
+ evidence.title ?? "",
4315
+ evidence.body,
4316
+ ...evidence.reviews.map((review) => review.body ?? ""),
4317
+ ...evidence.changedFileReviewComments.map((comment) => comment.body ?? ""),
4318
+ ...evidence.relevantIssueComments.map((comment) => comment.body ?? ""),
4319
+ ...evidence.reviewThreads.flatMap((thread) => thread.comments?.nodes?.map((comment) => comment.body ?? "") ?? []),
4320
+ ...(evidence.apiSignals ?? []).map((signal) => signal.body ?? "")
4321
+ ].filter((body) => body.trim().length > 0);
4322
+ }
4323
+ function bodyExcerpt(body) {
4324
+ const text = stripHtml(body).replace(/\s+/g, " ").trim();
4325
+ return text.length > 240 ? `${text.slice(0, 237)}...` : text;
4326
+ }
4327
+ function makeGreptileSignal(input) {
4328
+ const scores = parseGreptileScores(input.body);
4329
+ const reviewedSha = input.reviewedSha?.trim() || null;
4330
+ const current = reviewedSha ? reviewedSha === input.currentHeadSha : null;
4331
+ const verdict = input.verdict ?? null;
4332
+ const blocker = input.blocker ?? (isBlockingGreptileVerdict(verdict) || containsBlockerText(input.body));
4333
+ const explicitApproval = input.explicitApproval ?? false;
4334
+ return {
4335
+ source: input.source,
4336
+ trusted: input.trusted,
4337
+ authorLogin: input.authorLogin ?? null,
4338
+ reviewedSha,
4339
+ current,
4340
+ stale: current === false,
4341
+ score: scores[0] ?? null,
4342
+ scores,
4343
+ explicitApproval,
4344
+ verdict,
4345
+ blocker,
4346
+ actionable: input.actionable ?? blocker,
4347
+ bodyExcerpt: bodyExcerpt(input.body),
4348
+ body: input.body,
4349
+ allScores: scores
4350
+ };
4351
+ }
4352
+ function reviewAuthorLogin(review) {
4353
+ return review.author?.login ?? null;
4354
+ }
4355
+ function commentAuthorLogin(comment) {
4356
+ return comment.user?.login ?? comment.author?.login ?? null;
4357
+ }
4358
+ function collectGreptileSignals(evidence) {
4359
+ const signals = [];
4360
+ const greptileBodyReviewedSha = extractGreptileBodyReviewedSha(evidence.body);
4361
+ const trustedGreptileBody = Boolean(greptileBodyReviewedSha && isGreptileGithubLogin(evidence.bodyEditorLogin) && isoAtOrAfter(evidence.bodyLastEditedAt, evidence.headCommittedDate));
4362
+ const contextSources = [
4363
+ { source: "pr-title", body: evidence.title ?? "" },
4364
+ {
4365
+ source: "pr-body",
4366
+ body: evidence.body,
4367
+ trusted: trustedGreptileBody,
4368
+ authorLogin: trustedGreptileBody ? evidence.bodyEditorLogin ?? null : null,
4369
+ reviewedSha: greptileBodyReviewedSha,
4370
+ verdict: trustedGreptileBody ? "completed" : null
4371
+ }
4372
+ ];
4373
+ for (const context of contextSources) {
4374
+ if (!context.body.trim())
4375
+ continue;
4376
+ const contextBlocker = containsBlockerText(context.body);
4377
+ if (!contextBlocker && !/greptile|score|confidence|\b\d+\s*\/\s*5\b/i.test(context.body))
4378
+ continue;
4379
+ signals.push(makeGreptileSignal({
4380
+ source: context.source,
4381
+ body: context.body,
4382
+ currentHeadSha: evidence.currentHeadSha,
4383
+ trusted: context.trusted === true,
4384
+ authorLogin: context.authorLogin,
4385
+ reviewedSha: context.reviewedSha,
4386
+ verdict: context.verdict,
4387
+ blocker: contextBlocker,
4388
+ actionable: contextBlocker
4389
+ }));
4390
+ }
4391
+ for (const apiSignal of evidence.apiSignals ?? []) {
4392
+ const body = [apiSignal.status ? `Status: ${apiSignal.status}` : "", apiSignal.body ?? ""].filter(Boolean).join(`
4393
+
4394
+ `) || "Status: UNKNOWN";
4395
+ const verdict = greptileStatusVerdict(apiSignal.status);
4396
+ signals.push(makeGreptileSignal({
4397
+ source: "api",
4398
+ body,
4399
+ currentHeadSha: evidence.currentHeadSha,
4400
+ trusted: true,
4401
+ reviewedSha: apiSignal.reviewedSha ?? null,
4402
+ explicitApproval: verdict === "approved",
4403
+ verdict
4404
+ }));
4405
+ }
4406
+ for (const review of evidence.reviews) {
4407
+ const login = reviewAuthorLogin(review);
4408
+ if (!isGreptileGithubLogin(login))
4409
+ continue;
4410
+ const state = String(review.state ?? "").toUpperCase();
4411
+ const body = [state ? `Review state: ${state}` : "", review.body ?? ""].filter(Boolean).join(`
4412
+
4413
+ `);
4414
+ if (!body.trim())
4415
+ continue;
4416
+ const dismissed = state === "DISMISSED";
4417
+ signals.push(makeGreptileSignal({
4418
+ source: "github-review",
4419
+ body,
4420
+ currentHeadSha: evidence.currentHeadSha,
4421
+ trusted: !dismissed,
4422
+ authorLogin: login,
4423
+ reviewedSha: review.commit_id ?? null,
4424
+ explicitApproval: undefined,
4425
+ blocker: state === "CHANGES_REQUESTED" || undefined
4426
+ }));
4427
+ }
4428
+ for (const comment of evidence.relevantIssueComments) {
4429
+ const login = commentAuthorLogin(comment);
4430
+ const body = comment.body ?? "";
4431
+ if (!body.trim() || !isGreptileGithubLogin(login))
4432
+ continue;
4433
+ signals.push(makeGreptileSignal({
4434
+ source: "issue-comment",
4435
+ body,
4436
+ currentHeadSha: evidence.currentHeadSha,
4437
+ trusted: true,
4438
+ authorLogin: login
4439
+ }));
4440
+ }
4441
+ for (const thread of evidence.reviewThreads) {
4442
+ if (thread.isOutdated === true || thread.isResolved === true)
4443
+ continue;
4444
+ for (const comment of thread.comments?.nodes ?? []) {
4445
+ const login = comment.author?.login ?? null;
4446
+ const body = comment.body ?? "";
4447
+ if (!body.trim() || !isGreptileGithubLogin(login))
4448
+ continue;
4449
+ signals.push(makeGreptileSignal({
4450
+ source: "review-thread",
4451
+ body,
4452
+ currentHeadSha: evidence.currentHeadSha,
4453
+ trusted: true,
4454
+ authorLogin: login
4455
+ }));
4456
+ }
4457
+ }
4458
+ for (const check of evidence.checks) {
4459
+ if (!isGreptileLabel(checkName(check)))
4460
+ continue;
4461
+ const reviewedSha = check.headSha ?? check.head_sha ?? null;
4462
+ const label = `${checkName(check)} (${checkState(check) || "unknown"})`;
4463
+ const body = [label, check.output?.title ?? "", check.output?.summary ?? "", check.output?.text ?? ""].filter((entry) => entry.trim().length > 0).join(`
4464
+
4465
+ `);
4466
+ signals.push(makeGreptileSignal({
4467
+ source: "github-check",
4468
+ body,
4469
+ currentHeadSha: evidence.currentHeadSha,
4470
+ trusted: false,
4471
+ reviewedSha,
4472
+ explicitApproval: false,
4473
+ blocker: isFailingCheck(check),
4474
+ actionable: isFailingCheck(check)
4475
+ }));
4476
+ }
4477
+ return signals;
4478
+ }
4479
+ function unresolvedGreptileThreadSummaries(threads) {
4480
+ return threads.flatMap((thread) => {
4481
+ if (thread.isResolved === true || thread.isOutdated === true)
4482
+ return [];
4483
+ const comments = thread.comments?.nodes ?? [];
4484
+ if (!comments.some((comment) => isGreptileGithubLogin(comment.author?.login)))
4485
+ return [];
4486
+ const latest = latestThreadComment(thread);
4487
+ if (!latest)
4488
+ return ["Unresolved Greptile review thread"];
4489
+ const path = latest.path ? ` on ${latest.path}` : "";
4490
+ return [`Unresolved Greptile review thread${path}: ${(latest.body ?? "").trim() || "(empty comment)"}`];
4491
+ });
4492
+ }
4493
+ function actionableChangedFileCommentSummaries(_comments) {
4494
+ return [];
4495
+ }
4496
+ function issueLevelBlockerSummaries(comments) {
4497
+ return comments.flatMap((comment) => {
4498
+ const body = comment.body?.trim() ?? "";
4499
+ if (!body || !containsBlockerText(body) && !containsConflictingScoreText(body))
4500
+ return [];
4501
+ const login = commentAuthorLogin(comment) ?? "unknown";
4502
+ const author = isGreptileGithubLogin(login) ? `Greptile issue comment by ${login}` : `Issue-level PR comment by ${login}`;
4503
+ return [`${author}: ${body}`];
4504
+ });
4505
+ }
4506
+ function reviewBodyBlockerSummaries(reviews) {
4507
+ return reviews.flatMap((review) => {
4508
+ const login = reviewAuthorLogin(review) ?? "unknown";
4509
+ if (isGreptileGithubLogin(login))
4510
+ return [];
4511
+ const body = review.body?.trim() ?? "";
4512
+ if (!body || !containsBlockerText(body) && !containsConflictingScoreText(body))
4513
+ return [];
4514
+ const state = review.state ? ` (${review.state})` : "";
4515
+ return [`PR review summary by ${login}${state}: ${body}`];
4516
+ });
4517
+ }
4518
+ function signalLabel(signal) {
4519
+ const source = signal.source.replace(/-/g, " ");
4520
+ const author = signal.authorLogin ? ` by ${signal.authorLogin}` : "";
4521
+ const sha = signal.reviewedSha ? ` at ${signal.reviewedSha}` : "";
4522
+ return `${source}${author}${sha}`;
4523
+ }
4524
+ function deriveGreptileEvidence(input) {
4525
+ const rawBodies = collectBodies(input);
4526
+ const signals = collectGreptileSignals(input);
4527
+ const trustedSignals = signals.filter((signal) => signal.trusted);
4528
+ const trustedScoreEntries = trustedSignals.flatMap((signal) => signal.allScores.map((score2) => ({ score: score2, signal })));
4529
+ const contextScoreEntries = signals.filter((signal) => !signal.trusted).flatMap((signal) => signal.allScores.map((score2) => ({ score: score2, signal })));
4530
+ const allScoreEntries = [...trustedScoreEntries, ...contextScoreEntries];
4531
+ const staleSignals = signals.filter((signal) => !!signal.reviewedSha && signal.reviewedSha !== input.currentHeadSha && (signal.trusted || signal.source === "github-check"));
4532
+ const isCurrentOrUntied = (signal) => !signal.reviewedSha || signal.reviewedSha === input.currentHeadSha;
4533
+ const currentOrUntiedScoreEntries = allScoreEntries.filter((entry) => isCurrentOrUntied(entry.signal));
4534
+ const lowScoreEntries = currentOrUntiedScoreEntries.filter((entry) => !isStrictFiveOfFive(entry.score));
4535
+ const currentPendingApiSignals = trustedSignals.filter((signal) => signal.source === "api" && signal.verdict === "pending" && isCurrentOrUntied(signal));
4536
+ const signalCanApproveByScore = (signal) => {
4537
+ if (signal.source === "api")
4538
+ return signal.verdict === "approved" || signal.verdict === "completed";
4539
+ return signal.verdict !== "pending" && !isBlockingGreptileVerdict(signal.verdict);
4540
+ };
4541
+ const approvingScoreEntry = trustedScoreEntries.find((entry) => entry.signal.reviewedSha === input.currentHeadSha && entry.signal.source !== "github-check" && signalCanApproveByScore(entry.signal) && isStrictFiveOfFive(entry.score)) ?? null;
4542
+ const approvingExplicitSignal = trustedSignals.find((signal) => signal.source === "api" && signal.reviewedSha === input.currentHeadSha && signal.explicitApproval === true && !signal.blocker) ?? null;
4543
+ const approvedByScore = !!approvingScoreEntry;
4544
+ const approvedByExplicitMapping = !!approvingExplicitSignal;
4545
+ const approvingSignal = approvingScoreEntry?.signal ?? approvingExplicitSignal;
4546
+ const lowestScore = lowScoreEntries.map((entry) => entry.score).sort((left, right) => left.value - right.value)[0] ?? null;
4547
+ const score = lowestScore ?? approvingScoreEntry?.score ?? trustedScoreEntries[0]?.score ?? contextScoreEntries[0]?.score ?? null;
4548
+ const failedGreptileChecks = input.checks.filter((check) => isGreptileLabel(checkName(check)) && isFailingCheck(check)).map((check) => `${checkName(check)} (${checkState(check) || "failed"})`);
4549
+ const blockerSignals = signals.filter((signal) => (signal.blocker || signal.actionable) && (!signal.reviewedSha || signal.reviewedSha === input.currentHeadSha));
4550
+ const staleBlockingSignals = [];
4551
+ const blockers = [
4552
+ ...blockerSignals.map((signal) => `${signalLabel(signal)}: ${signal.bodyExcerpt || "blocker text"}`),
4553
+ ...reviewBodyBlockerSummaries(input.reviews),
4554
+ ...issueLevelBlockerSummaries(input.relevantIssueComments),
4555
+ ...lowScoreEntries.map((entry) => `Greptile score from ${signalLabel(entry.signal)} is ${entry.score.value}/${entry.score.scale}; strict merge requires trusted current-head 5/5.`),
4556
+ ...staleBlockingSignals.map((signal) => `Greptile blocking signal from ${signalLabel(signal)} is stale; current PR head is ${input.currentHeadSha || "unknown"}.`),
4557
+ ...failedGreptileChecks.map((entry) => `Greptile check failed: ${entry}`)
4558
+ ];
4559
+ const unresolvedComments = [
4560
+ ...unresolvedGreptileThreadSummaries(input.reviewThreads),
4561
+ ...actionableChangedFileCommentSummaries(input.changedFileReviewComments)
4562
+ ];
4563
+ const greptileChecks = input.checks.filter((check) => isGreptileLabel(checkName(check)));
4564
+ const greptileReviews = input.reviews.filter((review) => isGreptileGithubLogin(review.author?.login));
4565
+ const completedGreptileCheck = greptileChecks.some((check) => {
4566
+ const reviewedSha2 = check.headSha ?? check.head_sha ?? null;
4567
+ return reviewedSha2 === input.currentHeadSha && (isPassingCheck(check) || isFailingCheck(check));
4568
+ });
4569
+ const completedGreptileReview = greptileReviews.some((review) => {
4570
+ const state = String(review.state ?? "").toUpperCase();
4571
+ const completedState = ["APPROVED", "COMMENTED", "CHANGES_REQUESTED"].includes(state) || !!review.body?.trim();
4572
+ return completedState && review.commit_id === input.currentHeadSha;
4573
+ });
4574
+ const completedGreptileApi = trustedSignals.some((signal) => signal.source === "api" && signal.reviewedSha === input.currentHeadSha && (signal.verdict === "approved" || signal.verdict === "rejected" || signal.verdict === "skipped" || signal.verdict === "failed" || signal.verdict === "completed"));
4575
+ const approvalReviewedSha = approvingSignal?.reviewedSha ?? null;
4576
+ const reviewedSha = approvalReviewedSha ?? staleSignals[0]?.reviewedSha ?? trustedSignals.map((signal) => signal.reviewedSha ?? null).find(Boolean) ?? null;
4577
+ const fresh = !!approvalReviewedSha && approvalReviewedSha === input.currentHeadSha;
4578
+ const completed = completedGreptileCheck || completedGreptileReview || completedGreptileApi || !!approvingSignal;
4579
+ const hasGreptileEvidence = trustedSignals.length > 0 || signals.some((signal) => /greptile/i.test(signal.body));
4580
+ const approved = fresh && completed && !blockers.length && !unresolvedComments.length && currentPendingApiSignals.length === 0 && (approvedByScore || approvedByExplicitMapping);
4581
+ const mapping = !hasGreptileEvidence ? "missing" : staleSignals.length > 0 && !approvingSignal ? "stale" : approvedByScore ? "score-5-of-5" : approvedByExplicitMapping ? "explicit-approved" : "unproven";
4582
+ const source = approvingSignal?.source === "api" ? "api" : approvingSignal?.source === "github-review" ? "github-review" : approvingSignal?.source === "pr-body" || approvingSignal?.source === "pr-title" ? "pr-body" : approvingSignal?.source === "changed-file-comment" || approvingSignal?.source === "issue-comment" || approvingSignal?.source === "review-thread" ? "github-comment" : greptileReviews.length > 0 && greptileChecks.length > 0 ? "combined" : greptileReviews.length > 0 ? "github-review" : greptileChecks.length > 0 ? "github-check" : signals.some((signal) => signal.source === "pr-body" || signal.source === "pr-title") ? "pr-body" : "missing";
4583
+ return {
4584
+ source,
4585
+ currentHeadSha: input.currentHeadSha,
4586
+ reviewedSha,
4587
+ fresh,
4588
+ completed,
4589
+ approved,
4590
+ score,
4591
+ explicitApproval: approvedByExplicitMapping,
4592
+ blockers,
4593
+ unresolvedComments,
4594
+ rawBodies,
4595
+ signals: signals.map(({ body: _body, allScores: _allScores, ...signal }) => signal),
4596
+ mapping
4597
+ };
4598
+ }
4599
+ function isGreptileCheckDetail(check) {
4600
+ return isGreptileLabel(checkName(check)) || isGreptileGithubLogin(check.app?.slug) || isGreptileGithubLogin(check.app?.owner?.login) || isGreptileLabel(check.app?.name);
4601
+ }
4602
+ async function collectGreptileCheckDetails(input) {
4603
+ const checkRunsRead = await runJsonObject(input.command, [
4604
+ "api",
4605
+ `repos/${input.repoName}/commits/${input.headSha}/check-runs`,
4606
+ "-F",
4607
+ "per_page=100"
4608
+ ], input.projectRoot);
4609
+ const checkRuns = arrayField(checkRunsRead.value, "check_runs").map(normalizeStatusCheck).filter((entry) => !!entry).filter(isGreptileCheckDetail);
4610
+ return checkRunsRead.error ? { value: checkRuns, error: checkRunsRead.error } : { value: checkRuns };
4611
+ }
4612
+ async function collectPullRequestProvenance(input) {
4613
+ const response = await runJsonObject(input.command, [
4614
+ "api",
4615
+ "graphql",
4616
+ "-F",
4617
+ `owner=${input.owner}`,
4618
+ "-F",
4619
+ `name=${input.name}`,
4620
+ "-F",
4621
+ `prNumber=${input.prNumber}`,
4622
+ "-f",
4623
+ "query=query($owner: String!, $name: String!, $prNumber: Int!) { repository(owner:$owner, name:$name) { pullRequest(number:$prNumber) { lastEditedAt editor { login } commits(last: 1) { nodes { commit { oid committedDate } } } } } }"
4624
+ ], input.projectRoot);
4625
+ if (response.error)
4626
+ return { value: {}, error: response.error };
4627
+ const data = response.value.data;
4628
+ const repository = data?.repository;
4629
+ const pullRequest = repository?.pullRequest;
4630
+ if (!pullRequest)
4631
+ return { value: {}, error: "GitHub pullRequest provenance response did not include a pullRequest object" };
4632
+ const editor = pullRequest.editor;
4633
+ const commits = pullRequest.commits;
4634
+ const nodes = Array.isArray(commits?.nodes) ? commits.nodes : [];
4635
+ const latestCommitNode = nodes[nodes.length - 1];
4636
+ const latestCommit = latestCommitNode?.commit;
4637
+ return {
4638
+ value: {
4639
+ bodyEditorLogin: typeof editor?.login === "string" ? editor.login : null,
4640
+ bodyLastEditedAt: typeof pullRequest.lastEditedAt === "string" ? pullRequest.lastEditedAt : null,
4641
+ headCommittedDate: typeof latestCommit?.committedDate === "string" ? latestCommit.committedDate : null
4642
+ }
4643
+ };
4644
+ }
4645
+ async function collectReviewThreads(input) {
4646
+ const reviewThreads = [];
4647
+ let afterCursor = null;
4648
+ for (let page = 0;page < 100; page += 1) {
4649
+ const afterLiteral = afterCursor ? JSON.stringify(afterCursor) : "null";
4650
+ const threadsResponse = await runJsonObject(input.command, [
4651
+ "api",
4652
+ "graphql",
4653
+ "-F",
4654
+ `owner=${input.owner}`,
4655
+ "-F",
4656
+ `name=${input.name}`,
4657
+ "-F",
4658
+ `prNumber=${input.prNumber}`,
4659
+ "-f",
4660
+ `query=query($owner: String!, $name: String!, $prNumber: Int!) { repository(owner:$owner, name:$name) { pullRequest(number:$prNumber) { reviewThreads(first: 100, after: ${afterLiteral}) { nodes { id isResolved isOutdated comments(first: 100) { nodes { author { login } body path url createdAt } pageInfo { hasNextPage endCursor } } } pageInfo { hasNextPage endCursor } } } } }`
4661
+ ], input.projectRoot);
4662
+ if (threadsResponse.error) {
4663
+ return { value: reviewThreads, error: threadsResponse.error };
4664
+ }
4665
+ const data = threadsResponse.value.data;
4666
+ const repository = data?.repository;
4667
+ const pullRequest = repository?.pullRequest;
4668
+ const threads = pullRequest?.reviewThreads;
4669
+ const nodes = threads?.nodes;
4670
+ if (!Array.isArray(nodes)) {
4671
+ return { value: reviewThreads, error: "GitHub reviewThreads response did not include a nodes array" };
4672
+ }
4673
+ const normalized = nodes.map(normalizeReviewThread).filter((entry) => !!entry);
4674
+ reviewThreads.push(...normalized);
4675
+ const truncatedCommentThread = normalized.find((thread) => thread.comments?.pageInfo?.hasNextPage === true);
4676
+ if (truncatedCommentThread) {
4677
+ return { value: reviewThreads, error: `GitHub review thread ${truncatedCommentThread.id ?? "unknown"} has more than 100 comments; nested pagination is incomplete` };
4678
+ }
4679
+ const pageInfo = threads?.pageInfo;
4680
+ if (!pageInfo) {
4681
+ if (nodes.length >= 100) {
4682
+ return { value: reviewThreads, error: "GitHub reviewThreads pagination metadata missing after a full page" };
4683
+ }
4684
+ return { value: reviewThreads };
4685
+ }
4686
+ if (pageInfo.hasNextPage !== true) {
4687
+ return { value: reviewThreads };
4688
+ }
4689
+ if (typeof pageInfo.endCursor !== "string" || !pageInfo.endCursor.trim()) {
4690
+ return { value: reviewThreads, error: "GitHub reviewThreads pagination reported hasNextPage without endCursor" };
4691
+ }
4692
+ afterCursor = pageInfo.endCursor;
4693
+ }
4694
+ return { value: reviewThreads, error: "GitHub reviewThreads pagination exceeded 100 pages" };
4695
+ }
4696
+ async function collectPrReviewEvidence(input) {
4697
+ const parsed = parseGithubPrUrl(input.prUrl);
4698
+ if (!parsed) {
4699
+ throw new Error(`Cannot parse GitHub PR URL: ${input.prUrl}`);
4700
+ }
4701
+ const readErrors = [];
4702
+ const viewRead = await runJsonObject(input.command, [
4703
+ "pr",
4704
+ "view",
4705
+ input.prUrl,
4706
+ "--json",
4707
+ "title,body,headRefOid,headRefName,baseRefName,state,isDraft,mergeable,mergeStateStatus,reviewDecision,reviews,statusCheckRollup"
4708
+ ], input.projectRoot);
4709
+ if (viewRead.error)
4710
+ readErrors.push(viewRead.error);
4711
+ const view = viewRead.value;
4712
+ if (!Array.isArray(view.statusCheckRollup)) {
4713
+ readErrors.push("gh pr view did not return required statusCheckRollup array");
4714
+ }
4715
+ if (!Array.isArray(view.reviews)) {
4716
+ readErrors.push("gh pr view did not return required reviews array");
4717
+ }
4718
+ const headSha = firstString(view, ["headRefOid", "headSha", "head_sha"]);
4719
+ const baseRefName = firstString(view, ["baseRefName"]);
4720
+ const statusCheckRollup = arrayField(view, "statusCheckRollup").map(normalizeStatusCheck).filter((entry) => !!entry);
4721
+ const reviews = arrayField(view, "reviews").map(normalizeReview).filter((entry) => !!entry);
4722
+ const provenanceRead = await collectPullRequestProvenance({
4723
+ command: input.command,
4724
+ projectRoot: input.projectRoot,
4725
+ owner: parsed.owner,
4726
+ name: parsed.repo,
4727
+ prNumber: parsed.prNumber
4728
+ });
4729
+ const provenance = provenanceRead.value;
4730
+ const reviewCommentsRead = await runJsonArray(input.command, ["api", `repos/${parsed.repoName}/pulls/${parsed.prNumber}/comments`, "--paginate"], input.projectRoot);
4731
+ if (reviewCommentsRead.error)
4732
+ readErrors.push(reviewCommentsRead.error);
4733
+ const reviewComments = reviewCommentsRead.value.map(normalizeReviewComment).filter((entry) => !!entry);
4734
+ const issueCommentsRead = await runJsonArray(input.command, ["api", `repos/${parsed.repoName}/issues/${parsed.prNumber}/comments`, "--paginate"], input.projectRoot);
4735
+ if (issueCommentsRead.error)
4736
+ readErrors.push(issueCommentsRead.error);
4737
+ const issueComments = issueCommentsRead.value.map(normalizeIssueComment).filter((entry) => !!entry).filter(relevantIssueComment);
4738
+ const reviewThreadsRead = await collectReviewThreads({
4739
+ command: input.command,
4740
+ projectRoot: input.projectRoot,
4741
+ owner: parsed.owner,
4742
+ name: parsed.repo,
4743
+ prNumber: parsed.prNumber
4744
+ });
4745
+ if (reviewThreadsRead.error)
4746
+ readErrors.push(reviewThreadsRead.error);
4747
+ const reviewThreads = reviewThreadsRead.value;
4748
+ const greptileRollupChecks = statusCheckRollup.filter((check) => isGreptileLabel(checkName(check)));
4749
+ let greptileCheckDetails = [];
4750
+ if (headSha && greptileRollupChecks.length > 0) {
4751
+ const checkDetailsRead = await collectGreptileCheckDetails({
4752
+ command: input.command,
4753
+ projectRoot: input.projectRoot,
4754
+ repoName: parsed.repoName,
4755
+ headSha
4756
+ });
4757
+ greptileCheckDetails = checkDetailsRead.value;
4758
+ }
4759
+ const checksWithGreptileDetails = [...statusCheckRollup, ...greptileCheckDetails];
4760
+ const shouldCollectConfiguredGreptileApi = input.greptileApi?.enabled !== false;
4761
+ const configuredGreptileApiRead = await collectConfiguredGreptileApiSignals({
4762
+ enabled: shouldCollectConfiguredGreptileApi,
4763
+ options: input.greptileApi,
4764
+ repoName: parsed.repoName,
4765
+ prNumber: parsed.prNumber,
4766
+ headSha,
4767
+ baseRefName
4768
+ });
4769
+ readErrors.push(...configuredGreptileApiRead.errors);
4770
+ const apiSignals = [...input.apiSignals ?? [], ...configuredGreptileApiRead.signals];
4771
+ const checkFailures = statusCheckRollup.filter((check) => !isGreptileLabel(checkName(check)) && isFailingCheck(check) && !isAllowedFailure(checkName(check), input.allowedFailures ?? [])).map((check) => `Check failed: ${checkName(check)}${check.detailsUrl || check.link ? ` (${check.detailsUrl ?? check.link})` : ""}`);
4772
+ const pendingChecks = statusCheckRollup.filter((check) => isPendingCheck(check) && (isGreptileLabel(checkName(check)) || !isAllowedFailure(checkName(check), input.allowedFailures ?? []))).map((check) => `Check pending: ${checkName(check)}`);
4773
+ const evidenceBase = {
4774
+ title: firstString(view, ["title"]),
4775
+ body: firstString(view, ["body"]),
4776
+ bodyEditorLogin: provenance.bodyEditorLogin ?? null,
4777
+ bodyLastEditedAt: provenance.bodyLastEditedAt ?? null,
4778
+ headCommittedDate: provenance.headCommittedDate ?? null,
4779
+ reviews,
4780
+ changedFileReviewComments: reviewComments,
4781
+ relevantIssueComments: issueComments,
4782
+ reviewThreads,
4783
+ checks: checksWithGreptileDetails,
4784
+ currentHeadSha: headSha,
4785
+ apiSignals
4786
+ };
4787
+ const greptile = deriveGreptileEvidence(evidenceBase);
4788
+ return {
4789
+ prUrl: input.prUrl,
4790
+ prNumber: parsed.prNumber,
4791
+ repoName: parsed.repoName,
4792
+ title: evidenceBase.title,
4793
+ body: evidenceBase.body,
4794
+ bodyEditorLogin: evidenceBase.bodyEditorLogin,
4795
+ bodyLastEditedAt: evidenceBase.bodyLastEditedAt,
4796
+ headCommittedDate: evidenceBase.headCommittedDate,
4797
+ headSha,
4798
+ headRefName: firstString(view, ["headRefName"]),
4799
+ baseRefName,
4800
+ state: firstString(view, ["state"]),
4801
+ isDraft: typeof view.isDraft === "boolean" ? view.isDraft : null,
4802
+ mergeable: firstString(view, ["mergeable"]),
4803
+ mergeStateStatus: firstString(view, ["mergeStateStatus"]),
4804
+ reviewDecision: firstString(view, ["reviewDecision"]),
4805
+ reviews,
4806
+ reviewThreads,
4807
+ changedFileReviewComments: reviewComments,
4808
+ relevantIssueComments: issueComments,
4809
+ statusCheckRollup: checksWithGreptileDetails,
4810
+ checkFailures,
4811
+ pendingChecks,
4812
+ readErrors,
4813
+ greptile
4814
+ };
4815
+ }
4816
+ function capGateMessage(value, maxChars = 1200) {
4817
+ const normalized = value.trim();
4818
+ return normalized.length > maxChars ? `${normalized.slice(0, maxChars)}
4819
+ [truncated for gate summary; see full evidence artifact]` : normalized;
4820
+ }
4821
+ function evaluateEvidence(evidence) {
4822
+ const reasonDetails = [];
4823
+ const warnings = [];
4824
+ const seen = new Set;
4825
+ const addReason = (reason) => {
4826
+ const capped = { ...reason, message: capGateMessage(reason.message) };
4827
+ const key = `${capped.code}:${capped.message}`;
4828
+ if (seen.has(key))
4829
+ return;
4830
+ seen.add(key);
4831
+ reasonDetails.push(capped);
4832
+ };
4833
+ const greptile = evidence.greptile;
4834
+ const staleSignal = greptile.signals.find((signal) => signal.reviewedSha && signal.reviewedSha !== evidence.headSha);
4835
+ const hasPendingGreptileCheck = evidence.pendingChecks.some((check) => /greptile/i.test(check));
4836
+ const pendingGreptileApiSignals = greptile.signals.filter((signal) => signal.source === "api" && signal.verdict === "pending" && (!signal.reviewedSha || signal.reviewedSha === evidence.headSha));
4837
+ const unknownGreptileApiSignals = greptile.signals.filter((signal) => signal.source === "api" && !signal.verdict && (!signal.reviewedSha || signal.reviewedSha === evidence.headSha));
4838
+ const awaitingFreshGreptileProof = hasPendingGreptileCheck || pendingGreptileApiSignals.length > 0 || !greptile.completed || greptile.mapping === "missing" || greptile.mapping === "stale";
4839
+ for (const error of evidence.readErrors) {
4840
+ addReason({
4841
+ code: "read_error",
4842
+ reasonClass: "reject",
4843
+ surface: error.startsWith("Greptile API/MCP") ? "greptile" : "github",
4844
+ suggestedAction: "needs_attention",
4845
+ message: `Required PR evidence surface could not be read completely: ${error}`,
4846
+ headSha: evidence.headSha || null
4847
+ });
4848
+ }
4849
+ if (!evidence.headSha) {
4850
+ addReason({
4851
+ code: "missing_head_sha",
4852
+ reasonClass: "reject",
4853
+ surface: "github",
4854
+ suggestedAction: "needs_attention",
4855
+ message: "PR head SHA could not be read; current-head Greptile approval cannot be proven.",
4856
+ headSha: null
4857
+ });
4858
+ }
4859
+ for (const failure of evidence.checkFailures) {
4860
+ addReason({
4861
+ code: "ci_failed",
4862
+ reasonClass: "reject",
4863
+ surface: "ci",
4864
+ suggestedAction: "fix",
4865
+ message: failure,
4866
+ headSha: evidence.headSha || null
4867
+ });
4868
+ }
4869
+ for (const pendingCheck of evidence.pendingChecks) {
4870
+ addReason({
4871
+ code: "check_pending",
4872
+ reasonClass: "pending",
4873
+ surface: "ci",
4874
+ suggestedAction: "wait",
4875
+ message: pendingCheck,
4876
+ headSha: evidence.headSha || null
4877
+ });
4878
+ }
4879
+ const reviewDecision = String(evidence.reviewDecision ?? "").toUpperCase();
4880
+ if (reviewDecision === "CHANGES_REQUESTED" || reviewDecision === "REVIEW_REQUIRED") {
4881
+ addReason({
4882
+ code: "review_decision_blocking",
4883
+ reasonClass: "reject",
4884
+ surface: "review",
4885
+ suggestedAction: "fix",
4886
+ message: `Required review is unresolved (${evidence.reviewDecision}).`,
4887
+ headSha: evidence.headSha || null
4888
+ });
4889
+ }
4890
+ for (const thread of unresolvedThreadSummaries(evidence.reviewThreads)) {
4891
+ addReason({
4892
+ code: "review_thread_unresolved",
4893
+ reasonClass: "reject",
4894
+ surface: "review",
4895
+ suggestedAction: "fix",
4896
+ message: thread,
4897
+ headSha: evidence.headSha || null
4898
+ });
4899
+ }
4900
+ if (greptile.mapping === "missing") {
4901
+ addReason({
4902
+ code: "greptile_missing",
4903
+ reasonClass: "pending",
4904
+ surface: "greptile",
4905
+ suggestedAction: "wait",
4906
+ message: "Missing Greptile check/review evidence for this PR.",
4907
+ headSha: evidence.headSha || null
4908
+ });
4909
+ }
4910
+ if (greptile.mapping === "stale" || greptile.reviewedSha && greptile.reviewedSha !== evidence.headSha || !greptile.approved && staleSignal) {
4911
+ addReason({
4912
+ code: "greptile_stale",
4913
+ reasonClass: "pending",
4914
+ surface: "greptile",
4915
+ suggestedAction: "wait",
4916
+ message: `Greptile evidence is stale (reviewed ${greptile.reviewedSha ?? staleSignal?.reviewedSha ?? "unknown"}, current ${evidence.headSha || "unknown"}).`,
4917
+ headSha: evidence.headSha || null,
4918
+ reviewedSha: greptile.reviewedSha ?? staleSignal?.reviewedSha ?? null
4919
+ });
4920
+ }
4921
+ for (const signal of pendingGreptileApiSignals) {
4922
+ addReason({
4923
+ code: "greptile_pending",
4924
+ reasonClass: "pending",
4925
+ surface: "greptile",
4926
+ suggestedAction: "wait",
4927
+ message: `Greptile API/MCP review is pending for the current PR head${signal.bodyExcerpt ? `: ${signal.bodyExcerpt}` : "."}`,
4928
+ headSha: evidence.headSha || null,
4929
+ reviewedSha: signal.reviewedSha ?? null
4930
+ });
4931
+ }
4932
+ for (const signal of unknownGreptileApiSignals) {
4933
+ addReason({
4934
+ code: "greptile_api_status_unknown",
4935
+ reasonClass: "reject",
4936
+ surface: "greptile",
4937
+ suggestedAction: "needs_attention",
4938
+ message: `Greptile API/MCP review status is unknown; merge requires a known terminal APPROVED/COMPLETED 5/5 result or a known conservative status${signal.bodyExcerpt ? `: ${signal.bodyExcerpt}` : "."}`,
4939
+ headSha: evidence.headSha || null,
4940
+ reviewedSha: signal.reviewedSha ?? null
4941
+ });
4942
+ }
4943
+ if (!greptile.completed) {
4944
+ addReason({
4945
+ code: "greptile_pending",
4946
+ reasonClass: "pending",
4947
+ surface: "greptile",
4948
+ suggestedAction: "wait",
4949
+ message: "Greptile check/review has not completed for the current PR head.",
4950
+ headSha: evidence.headSha || null,
4951
+ reviewedSha: greptile.reviewedSha ?? null
4952
+ });
4953
+ }
4954
+ if (!greptile.fresh) {
4955
+ addReason({
4956
+ code: "greptile_not_current_head",
4957
+ reasonClass: awaitingFreshGreptileProof ? "pending" : "reject",
4958
+ surface: "greptile",
4959
+ suggestedAction: awaitingFreshGreptileProof ? "wait" : "ask_greptile",
4960
+ message: "Greptile approval is not tied to the current PR head SHA.",
4961
+ headSha: evidence.headSha || null,
4962
+ reviewedSha: greptile.reviewedSha ?? null
4963
+ });
4964
+ }
4965
+ if (greptile.score && !(greptile.score.scale === 5 && greptile.score.value === 5)) {
4966
+ addReason({
4967
+ code: "greptile_score_not_5",
4968
+ reasonClass: "reject",
4969
+ surface: "greptile",
4970
+ suggestedAction: "fix",
4971
+ message: `Greptile score is ${greptile.score.value}/${greptile.score.scale}; strict merge requires trusted current-head 5/5.`,
4972
+ headSha: evidence.headSha || null,
4973
+ reviewedSha: greptile.reviewedSha ?? null
4974
+ });
4975
+ }
4976
+ const hasApprovedMapping = greptile.mapping === "score-5-of-5" || greptile.mapping === "explicit-approved";
4977
+ if (!greptile.score && !hasApprovedMapping) {
4978
+ addReason({
4979
+ code: "greptile_score_missing",
4980
+ reasonClass: awaitingFreshGreptileProof ? "pending" : "reject",
4981
+ surface: "greptile",
4982
+ suggestedAction: awaitingFreshGreptileProof ? "wait" : "ask_greptile",
4983
+ message: "No parseable Greptile 5/5 score or direct current-head Greptile API APPROVED mapping was found from trusted evidence; merge is blocked.",
4984
+ headSha: evidence.headSha || null,
4985
+ reviewedSha: greptile.reviewedSha ?? null
4986
+ });
4987
+ }
4988
+ if (greptile.mapping === "unproven") {
4989
+ addReason({
4990
+ code: "greptile_mapping_unproven",
4991
+ reasonClass: awaitingFreshGreptileProof ? "pending" : "reject",
4992
+ surface: "greptile",
4993
+ suggestedAction: awaitingFreshGreptileProof ? "wait" : "ask_greptile",
4994
+ message: "Greptile approval mapping is unproven; PR body/title or a green check alone cannot approve merge.",
4995
+ headSha: evidence.headSha || null,
4996
+ reviewedSha: greptile.reviewedSha ?? null
4997
+ });
4998
+ }
4999
+ for (const blocker of greptile.blockers) {
5000
+ addReason({
5001
+ code: "greptile_blocker_text",
5002
+ reasonClass: "reject",
5003
+ surface: "greptile",
5004
+ suggestedAction: "fix",
5005
+ message: `Greptile/blocker text: ${blocker}`,
5006
+ headSha: evidence.headSha || null,
5007
+ reviewedSha: greptile.reviewedSha ?? null
5008
+ });
5009
+ }
5010
+ for (const comment of greptile.unresolvedComments) {
5011
+ addReason({
5012
+ code: "greptile_unresolved_comment",
5013
+ reasonClass: "reject",
5014
+ surface: "greptile",
5015
+ suggestedAction: "fix",
5016
+ message: comment,
5017
+ headSha: evidence.headSha || null,
5018
+ reviewedSha: greptile.reviewedSha ?? null
5019
+ });
5020
+ }
5021
+ if (!greptile.approved)
5022
+ warnings.push(`Greptile approval mapping is ${greptile.mapping}.`);
5023
+ const pending = reasonDetails.length > 0 && reasonDetails.every((reason) => reason.reasonClass === "pending");
5024
+ return { reasons: reasonDetails.map((reason) => reason.message), reasonDetails, warnings, pending };
5025
+ }
5026
+ function evaluateStrictPrMergeGate(evidence) {
5027
+ const evaluated = evaluateEvidence(evidence);
5028
+ const approved = evaluated.reasonDetails.length === 0 && evidence.greptile.approved;
5029
+ return {
5030
+ approved,
5031
+ pending: evaluated.pending,
5032
+ reasons: evaluated.reasons,
5033
+ reasonDetails: evaluated.reasonDetails,
5034
+ warnings: evaluated.warnings,
5035
+ actionableFeedback: evaluated.reasons,
5036
+ evidence
5037
+ };
5038
+ }
5039
+
5040
+ // packages/runtime/src/control-plane/native/git-ops.ts
3742
5041
  var TASK_ARTIFACT_STAGE_FALLBACK = new Set([
3743
5042
  "changed-files.txt",
3744
5043
  "contract-changes.md",
@@ -3750,12 +5049,12 @@ var TASK_ARTIFACT_STAGE_FALLBACK = new Set([
3750
5049
  "validation-summary.json"
3751
5050
  ]);
3752
5051
  function readPrMetadata(projectRoot, taskId) {
3753
- const path = resolve21(artifactDirForId(projectRoot, taskId), "pr-state.json");
3754
- if (!existsSync18(path)) {
5052
+ const path = resolve22(artifactDirForId(projectRoot, taskId), "pr-state.json");
5053
+ if (!existsSync19(path)) {
3755
5054
  return [];
3756
5055
  }
3757
5056
  try {
3758
- const parsed = JSON.parse(readFileSync9(path, "utf-8"));
5057
+ const parsed = JSON.parse(readFileSync10(path, "utf-8"));
3759
5058
  if (!parsed || typeof parsed !== "object") {
3760
5059
  return [];
3761
5060
  }
@@ -3781,11 +5080,11 @@ async function verifyTask(options) {
3781
5080
  const taskId = options.taskId;
3782
5081
  const normalizedTaskId = lookupTask(options.projectRoot, taskId);
3783
5082
  const artifactDir = artifactDirForId(options.projectRoot, taskId);
3784
- mkdirSync9(artifactDir, { recursive: true });
3785
- const validationSummaryPath = resolve22(artifactDir, "validation-summary.json");
3786
- const reviewFeedbackPath = resolve22(artifactDir, "review-feedback.md");
3787
- const reviewStatePath = resolve22(artifactDir, "review-state.json");
3788
- const greptileRawPath = resolve22(artifactDir, "review-greptile-raw.json");
5083
+ mkdirSync10(artifactDir, { recursive: true });
5084
+ const validationSummaryPath = resolve23(artifactDir, "validation-summary.json");
5085
+ const reviewFeedbackPath = resolve23(artifactDir, "review-feedback.md");
5086
+ const reviewStatePath = resolve23(artifactDir, "review-state.json");
5087
+ const greptileRawPath = resolve23(artifactDir, "review-greptile-raw.json");
3789
5088
  const prStates = readPrMetadata(options.projectRoot, taskId);
3790
5089
  const prState = prStates[0] || null;
3791
5090
  const localReasons = [];
@@ -3797,7 +5096,7 @@ async function verifyTask(options) {
3797
5096
  if (!normalizedTaskId && !await hasConfiguredSourceTask(options.projectRoot, taskId)) {
3798
5097
  localReasons.push(`[Task Config] Unknown task id '${taskId}' in task-config or configured task source.`);
3799
5098
  }
3800
- if (!existsSync19(validationSummaryPath)) {
5099
+ if (!existsSync20(validationSummaryPath)) {
3801
5100
  localReasons.push(`[Artifact Quality] validation-summary.json not found at ${validationSummaryPath}.`);
3802
5101
  } else {
3803
5102
  const summary = await parseValidationSummary(validationSummaryPath);
@@ -3806,13 +5105,13 @@ async function verifyTask(options) {
3806
5105
  }
3807
5106
  }
3808
5107
  for (const file of ["task-result.json", "decision-log.md", "next-actions.md", "changed-files.txt"]) {
3809
- const requiredPath = resolve22(artifactDir, file);
3810
- if (!existsSync19(requiredPath)) {
5108
+ const requiredPath = resolve23(artifactDir, file);
5109
+ if (!existsSync20(requiredPath)) {
3811
5110
  localReasons.push(`[Artifact Quality] Missing required artifact file: ${requiredPath}`);
3812
5111
  }
3813
5112
  }
3814
- const taskResultPath = resolve22(artifactDir, "task-result.json");
3815
- if (existsSync19(taskResultPath)) {
5113
+ const taskResultPath = resolve23(artifactDir, "task-result.json");
5114
+ if (existsSync20(taskResultPath)) {
3816
5115
  const taskResult = await readJsonFile2(taskResultPath);
3817
5116
  const artifactStatus = typeof taskResult?.status === "string" ? taskResult.status.trim().toLowerCase() : "";
3818
5117
  if (artifactStatus === "partial") {
@@ -3825,8 +5124,8 @@ async function verifyTask(options) {
3825
5124
  localReasons.push("[Artifact Quality] task-result.json next actions indicate remaining implementation scope.");
3826
5125
  }
3827
5126
  }
3828
- const nextActionsPath = resolve22(artifactDir, "next-actions.md");
3829
- if (existsSync19(nextActionsPath)) {
5127
+ const nextActionsPath = resolve23(artifactDir, "next-actions.md");
5128
+ if (existsSync20(nextActionsPath)) {
3830
5129
  const nextActionsContent = await Bun.file(nextActionsPath).text();
3831
5130
  if (nextActionsContent.includes("TODO: Replace this scaffold") || nextActionsContent.includes("bd-<downstream-task-id>")) {
3832
5131
  localReasons.push("[Artifact Quality] next-actions.md still contains scaffold placeholder text. Replace with real recommendations.");
@@ -3839,12 +5138,6 @@ async function verifyTask(options) {
3839
5138
  if (sourceCloseoutIssueId) {
3840
5139
  localReasons.push(...evaluateGithubSourceIssuePrCloseout(options.projectRoot, prStates, sourceCloseoutIssueId));
3841
5140
  }
3842
- const pluginResults = await options.plugins.runValidators(taskId);
3843
- for (const result of pluginResults) {
3844
- if (!result.passed) {
3845
- localReasons.push(`[Plugin Validator] ${result.id}: ${result.summary}`);
3846
- }
3847
- }
3848
5141
  const reviewMode = await loadReviewMode(paths.reviewProfilePath, process.env.AI_REVIEW_MODE || "advisory");
3849
5142
  const reviewProvider = await loadReviewProvider(paths.reviewProfilePath, process.env.AI_REVIEW_PROVIDER || "greptile");
3850
5143
  if (!options.skipAiReview && localReasons.length === 0 && reviewProvider === "greptile" && reviewMode !== "off") {
@@ -3863,7 +5156,7 @@ async function verifyTask(options) {
3863
5156
  aiReasons.push(`[AI Review] Required mode needs a completed Greptile approval; current verdict is ${ai.verdict}.`);
3864
5157
  }
3865
5158
  if (persistArtifacts && ai.rawResponse) {
3866
- writeFileSync9(greptileRawPath, `${ai.rawResponse}
5159
+ writeFileSync10(greptileRawPath, `${ai.rawResponse}
3867
5160
  `, "utf-8");
3868
5161
  }
3869
5162
  } else if (!options.skipAiReview && reviewMode === "off") {
@@ -4202,7 +5495,7 @@ function isAcceptedValidationSummary(summary) {
4202
5495
  return summary.status === "skipped" && summary.total === 0 && summary.failed === 0;
4203
5496
  }
4204
5497
  async function loadReviewMode(reviewProfilePath, fallback) {
4205
- const parsed = existsSync19(reviewProfilePath) ? await readJsonFile2(reviewProfilePath) : null;
5498
+ const parsed = existsSync20(reviewProfilePath) ? await readJsonFile2(reviewProfilePath) : null;
4206
5499
  const mode = parsed?.mode;
4207
5500
  if (mode === "off" || mode === "advisory" || mode === "required") {
4208
5501
  return mode;
@@ -4213,7 +5506,7 @@ async function loadReviewMode(reviewProfilePath, fallback) {
4213
5506
  return "advisory";
4214
5507
  }
4215
5508
  async function loadReviewProvider(reviewProfilePath, fallback) {
4216
- const parsed = existsSync19(reviewProfilePath) ? await readJsonFile2(reviewProfilePath) : null;
5509
+ const parsed = existsSync20(reviewProfilePath) ? await readJsonFile2(reviewProfilePath) : null;
4217
5510
  const provider = parsed?.provider;
4218
5511
  if (typeof provider === "string" && provider.trim().length > 0) {
4219
5512
  return provider;
@@ -4372,7 +5665,7 @@ function writeFeedbackFile(options) {
4372
5665
  if (options.aiRawFeedback) {
4373
5666
  lines.push("## Raw Reviewer Feedback", "", "```text", options.aiRawFeedback, "```", "");
4374
5667
  }
4375
- writeFileSync9(options.output, `${lines.join(`
5668
+ writeFileSync10(options.output, `${lines.join(`
4376
5669
  `)}
4377
5670
  `, "utf-8");
4378
5671
  }
@@ -4389,7 +5682,7 @@ function writeReviewStateFile(options) {
4389
5682
  ai_warnings: options.aiWarnings,
4390
5683
  updated_at: nowIso()
4391
5684
  };
4392
- writeFileSync9(options.output, `${JSON.stringify(payload, null, 2)}
5685
+ writeFileSync10(options.output, `${JSON.stringify(payload, null, 2)}
4393
5686
  `, "utf-8");
4394
5687
  }
4395
5688
  async function runGreptileReviewForPr(options) {
@@ -4571,7 +5864,8 @@ async function runGreptileReviewForPr(options) {
4571
5864
  }
4572
5865
  };
4573
5866
  }
4574
- if (/not safe to merge|unsafe to merge|do not merge|blocker/i.test(reviewBody)) {
5867
+ const blockerScanBody = stripHtml(reviewBody).replace(/\b(?:no|without|zero)\s+blockers?\b/gi, " ").replace(/\bno\s+changes\s+requested\b/gi, " ");
5868
+ if (/not safe(?: to merge)?|unsafe(?: to merge)?|do not merge|cannot merge|blockers?|must fix|changes requested|please fix|needs? fix|fix this|address this|\breject(?:ed|ion)?\b|\bskip(?:ped)?\b|status\s*:\s*(?:reject(?:ed)?|skip(?:ped)?|failed)/i.test(blockerScanBody)) {
4575
5869
  reasons.push(`[AI Review] ${repoName}#${prNumber} summary indicates the PR is not safe to merge.`);
4576
5870
  return {
4577
5871
  verdict: "REJECT",
@@ -4587,44 +5881,79 @@ async function runGreptileReviewForPr(options) {
4587
5881
  }
4588
5882
  };
4589
5883
  }
4590
- if (score) {
4591
- if (score.scale === 5 && score.value < 5 && options.reviewMode === "required") {
4592
- reasons.push(`[AI Review] ${repoName}#${prNumber} completed with Greptile confidence ${score.value}/${score.scale}; required mode needs 5/5 before merge.`);
4593
- return {
4594
- verdict: "REJECT",
4595
- feedback,
4596
- reasons,
4597
- warnings,
4598
- rawPayload: {
4599
- pr: options.prState,
4600
- codeReviews: reviewsPayload,
4601
- selectedReview,
4602
- reviewDetails,
4603
- comments: commentsPayload,
4604
- score
4605
- }
4606
- };
4607
- }
4608
- if (score.scale === 5 && score.value <= 2) {
4609
- reasons.push(`[AI Review] ${repoName}#${prNumber} completed with Greptile confidence ${score.value}/${score.scale}; this requires rework before merge.`);
4610
- return {
4611
- verdict: "REJECT",
4612
- feedback,
4613
- reasons,
4614
- warnings,
4615
- rawPayload: {
4616
- pr: options.prState,
4617
- codeReviews: reviewsPayload,
4618
- selectedReview,
4619
- reviewDetails,
4620
- comments: commentsPayload,
4621
- score
5884
+ if (score?.scale === 5 && score.value < 5) {
5885
+ reasons.push(`[AI Review] ${repoName}#${prNumber} completed with Greptile confidence ${score.value}/${score.scale}; strict review requires 5/5 before merge.`);
5886
+ return {
5887
+ verdict: "REJECT",
5888
+ feedback,
5889
+ reasons,
5890
+ warnings,
5891
+ rawPayload: {
5892
+ pr: options.prState,
5893
+ codeReviews: reviewsPayload,
5894
+ selectedReview,
5895
+ reviewDetails,
5896
+ comments: commentsPayload,
5897
+ score
5898
+ }
5899
+ };
5900
+ }
5901
+ const prUrl = options.prState.url || `https://github.com/${repoName}/pull/${prNumber}`;
5902
+ let strictGate = null;
5903
+ try {
5904
+ const strictEvidence = await collectStrictPrEvidenceForVerifier({
5905
+ projectRoot: options.projectRoot,
5906
+ taskId: options.taskId,
5907
+ prUrl,
5908
+ apiSignals: [{
5909
+ id: selectedReview.id,
5910
+ body: reviewBody,
5911
+ reviewedSha: selectedReview.metadata?.checkHeadSha ?? null,
5912
+ status: selectedReview.status
5913
+ }]
5914
+ });
5915
+ strictGate = evaluateStrictPrMergeGate(strictEvidence);
5916
+ } catch (error) {
5917
+ reasons.push(`[AI Review] Strict Greptile evidence collection failed for ${repoName}#${prNumber}: ${error instanceof Error ? error.message : String(error)}`);
5918
+ return {
5919
+ verdict: "REJECT",
5920
+ feedback,
5921
+ reasons,
5922
+ warnings,
5923
+ rawPayload: {
5924
+ pr: options.prState,
5925
+ codeReviews: reviewsPayload,
5926
+ selectedReview,
5927
+ reviewDetails,
5928
+ comments: commentsPayload,
5929
+ score
5930
+ }
5931
+ };
5932
+ }
5933
+ if (!strictGate.approved) {
5934
+ return {
5935
+ verdict: strictGate.pending ? "SKIP" : "REJECT",
5936
+ feedback,
5937
+ reasons: strictGate.reasons.map((reason) => reason.startsWith("[AI Review]") ? reason : `[AI Review] ${reason}`),
5938
+ warnings: [...warnings, ...strictGate.warnings],
5939
+ rawPayload: {
5940
+ pr: options.prState,
5941
+ codeReviews: reviewsPayload,
5942
+ selectedReview,
5943
+ reviewDetails,
5944
+ comments: commentsPayload,
5945
+ score,
5946
+ strictGate: {
5947
+ approved: strictGate.approved,
5948
+ pending: strictGate.pending,
5949
+ reasons: strictGate.reasons,
5950
+ reasonDetails: strictGate.reasonDetails,
5951
+ warnings: strictGate.warnings,
5952
+ greptile: strictGate.evidence.greptile,
5953
+ readErrors: strictGate.evidence.readErrors
4622
5954
  }
4623
- };
4624
- }
4625
- if (score.scale === 5 && score.value < 5) {
4626
- warnings.push(`[AI Review] ${repoName}#${prNumber} completed with Greptile confidence ${score.value}/${score.scale}; continue only after reviewing remaining risk.`);
4627
- }
5955
+ }
5956
+ };
4628
5957
  }
4629
5958
  return {
4630
5959
  verdict: "APPROVE",
@@ -4636,7 +5965,16 @@ async function runGreptileReviewForPr(options) {
4636
5965
  codeReviews: reviewsPayload,
4637
5966
  selectedReview,
4638
5967
  reviewDetails,
4639
- comments: commentsPayload
5968
+ comments: commentsPayload,
5969
+ strictGate: {
5970
+ approved: strictGate.approved,
5971
+ pending: strictGate.pending,
5972
+ reasons: strictGate.reasons,
5973
+ reasonDetails: strictGate.reasonDetails,
5974
+ warnings: strictGate.warnings,
5975
+ greptile: strictGate.evidence.greptile,
5976
+ readErrors: strictGate.evidence.readErrors
5977
+ }
4640
5978
  }
4641
5979
  };
4642
5980
  }
@@ -4660,7 +5998,7 @@ async function runGithubGreptileFallbackReviewForPr(options) {
4660
5998
  let threads = [];
4661
5999
  let actionableThreads = [];
4662
6000
  let checkRollup = [];
4663
- let checkState = { pending: false, completed: false };
6001
+ let checkState2 = { pending: false, completed: false };
4664
6002
  for (let attempt = 0;; attempt += 1) {
4665
6003
  reviews = runGhJson(options.projectRoot, ["api", `repos/${repoName}/pulls/${prNumber}/reviews`]);
4666
6004
  selectedReview = pickRelevantGithubGreptileReview(reviews, expectedHeadSha);
@@ -4669,15 +6007,15 @@ async function runGithubGreptileFallbackReviewForPr(options) {
4669
6007
  threads = loadGithubReviewThreads(options.projectRoot, repoName, prNumber);
4670
6008
  actionableThreads = filterActionableGithubGreptileThreads(threads);
4671
6009
  checkRollup = loadGithubPullRequestCheckRollup(options.projectRoot, repoName, prNumber);
4672
- checkState = classifyGithubGreptileCheckState(checkRollup);
4673
- const approvedViaReviewedAncestor2 = !selectedReview && !!fallbackReview?.commit_id && !!expectedHeadSha && isCommitAncestorOfPrHead(options.projectRoot, options.prState, fallbackReview.commit_id, expectedHeadSha);
6010
+ checkState2 = classifyGithubGreptileCheckState(checkRollup);
6011
+ const approvedViaReviewedAncestor = !selectedReview && !!fallbackReview?.commit_id && !!expectedHeadSha && isCommitAncestorOfPrHead(options.projectRoot, options.prState, fallbackReview.commit_id, expectedHeadSha);
4674
6012
  if (!shouldContinueGithubGreptileFallbackPolling({
4675
6013
  attempt,
4676
6014
  pollAttempts: options.pollAttempts,
4677
- checkState,
6015
+ checkState: checkState2,
4678
6016
  fallbackReview,
4679
6017
  selectedReview,
4680
- approvedViaReviewedAncestor: approvedViaReviewedAncestor2
6018
+ approvedViaReviewedAncestor
4681
6019
  })) {
4682
6020
  break;
4683
6021
  }
@@ -4705,7 +6043,7 @@ async function runGithubGreptileFallbackReviewForPr(options) {
4705
6043
  ].filter(Boolean).join(`
4706
6044
  `);
4707
6045
  const warnings = buildGithubGreptileFallbackWarnings(options);
4708
- if (checkState.pending) {
6046
+ if (checkState2.pending) {
4709
6047
  return {
4710
6048
  verdict: "SKIP",
4711
6049
  feedback,
@@ -4716,34 +6054,20 @@ async function runGithubGreptileFallbackReviewForPr(options) {
4716
6054
  rawPayload: { ...buildGithubGreptileFallbackRawPayload(options), reviews, threads, checkRollup }
4717
6055
  };
4718
6056
  }
4719
- const approvedViaCompletedCheck = isGithubGreptileCheckApproved(checkRollup);
4720
- if (!fallbackReview) {
4721
- if (approvedViaCompletedCheck) {
4722
- warnings.push(`[AI Review Warning] ${repoName}#${prNumber} has no GitHub Greptile review object, but the Greptile check completed successfully and no unresolved Greptile threads remain.`);
4723
- return {
4724
- verdict: "APPROVE",
4725
- feedback,
4726
- reasons: [],
4727
- warnings,
4728
- rawPayload: { ...buildGithubGreptileFallbackRawPayload(options), reviews, threads, checkRollup }
4729
- };
4730
- }
4731
- return {
4732
- verdict: "SKIP",
4733
- feedback,
4734
- reasons: [
4735
- `[AI Review] Greptile GitHub review for ${repoName}#${prNumber} is not available.`
4736
- ],
4737
- warnings,
4738
- rawPayload: { ...buildGithubGreptileFallbackRawPayload(options), reviews, threads, checkRollup }
4739
- };
4740
- }
4741
- const approvedViaReviewedAncestor = !selectedReview && !!fallbackReview.commit_id && !!expectedHeadSha && isCommitAncestorOfPrHead(options.projectRoot, options.prState, fallbackReview.commit_id, expectedHeadSha);
4742
- if (actionableThreads.length > 0) {
6057
+ const prUrl = options.prState.url || `https://github.com/${repoName}/pull/${prNumber}`;
6058
+ let strictGate;
6059
+ try {
6060
+ const strictEvidence = await collectStrictPrEvidenceForVerifier({
6061
+ projectRoot: options.projectRoot,
6062
+ taskId: options.taskId,
6063
+ prUrl
6064
+ });
6065
+ strictGate = evaluateStrictPrMergeGate(strictEvidence);
6066
+ } catch (error) {
4743
6067
  return {
4744
6068
  verdict: "REJECT",
4745
6069
  feedback,
4746
- reasons: actionableThreads.map((comment) => `[AI Review] ${repoName}#${prNumber} has unresolved Greptile comment on ${comment.path}: ${summarizeComment(comment.body || "")}`),
6070
+ reasons: [`[AI Review] Strict Greptile evidence collection failed for ${repoName}#${prNumber}: ${error instanceof Error ? error.message : String(error)}`],
4747
6071
  warnings,
4748
6072
  rawPayload: {
4749
6073
  pr: options.prState,
@@ -4756,44 +6080,31 @@ async function runGithubGreptileFallbackReviewForPr(options) {
4756
6080
  }
4757
6081
  };
4758
6082
  }
4759
- if (!selectedReview && !approvedViaReviewedAncestor) {
4760
- if (approvedViaCompletedCheck) {
4761
- warnings.push(`[AI Review Warning] ${repoName}#${prNumber} has no fresh Greptile GitHub review on the current head, but the Greptile check completed successfully and all Greptile threads are resolved.`);
4762
- return {
4763
- verdict: "APPROVE",
4764
- feedback,
4765
- reasons: [],
4766
- warnings,
4767
- rawPayload: {
4768
- pr: options.prState,
4769
- selectedReview: fallbackReview,
4770
- reviews,
4771
- threads,
4772
- checkRollup,
4773
- ...buildGithubGreptileFallbackRawPayload(options)
4774
- }
4775
- };
4776
- }
6083
+ if (!strictGate.approved) {
4777
6084
  return {
4778
- verdict: "SKIP",
6085
+ verdict: strictGate.pending ? "SKIP" : "REJECT",
4779
6086
  feedback,
4780
- reasons: [
4781
- `[AI Review] Greptile GitHub review for ${repoName}#${prNumber} is not available for the current head.`
4782
- ],
4783
- warnings,
6087
+ reasons: strictGate.reasons.map((reason) => reason.startsWith("[AI Review]") ? reason : `[AI Review] ${reason}`),
6088
+ warnings: [...warnings, ...strictGate.warnings],
4784
6089
  rawPayload: {
4785
6090
  pr: options.prState,
4786
6091
  selectedReview: fallbackReview,
4787
6092
  reviews,
4788
6093
  threads,
4789
6094
  checkRollup,
6095
+ actionableThreads,
6096
+ strictGate: {
6097
+ approved: strictGate.approved,
6098
+ pending: strictGate.pending,
6099
+ reasons: strictGate.reasons,
6100
+ reasonDetails: strictGate.reasonDetails,
6101
+ warnings: strictGate.warnings,
6102
+ greptile: strictGate.evidence.greptile
6103
+ },
4790
6104
  ...buildGithubGreptileFallbackRawPayload(options)
4791
6105
  }
4792
6106
  };
4793
6107
  }
4794
- if (approvedViaReviewedAncestor) {
4795
- warnings.push(`[AI Review Warning] ${repoName}#${prNumber} has no fresh Greptile review on the current head, but the latest reviewed commit is an ancestor and all Greptile threads are resolved.`);
4796
- }
4797
6108
  return {
4798
6109
  verdict: "APPROVE",
4799
6110
  feedback,
@@ -4805,6 +6116,14 @@ async function runGithubGreptileFallbackReviewForPr(options) {
4805
6116
  reviews,
4806
6117
  threads,
4807
6118
  checkRollup,
6119
+ strictGate: {
6120
+ approved: strictGate.approved,
6121
+ pending: strictGate.pending,
6122
+ reasons: strictGate.reasons,
6123
+ reasonDetails: strictGate.reasonDetails,
6124
+ warnings: strictGate.warnings,
6125
+ greptile: strictGate.evidence.greptile
6126
+ },
4808
6127
  ...buildGithubGreptileFallbackRawPayload(options)
4809
6128
  }
4810
6129
  };
@@ -4917,19 +6236,25 @@ function shouldTriggerGreptileReview(existingReview, expectedHeadSha) {
4917
6236
  if ((existingReview.metadata?.checkHeadSha || "") !== expectedHeadSha) {
4918
6237
  return true;
4919
6238
  }
4920
- return isGreptileReviewTerminal(existingReview.status);
6239
+ return false;
4921
6240
  }
4922
6241
  function shouldContinueGreptileMcpPolling(options) {
4923
6242
  if (options.githubCheckState.completed) {
4924
6243
  return false;
4925
6244
  }
6245
+ if (options.attempt + 1 >= options.pollAttempts) {
6246
+ return false;
6247
+ }
4926
6248
  if (options.selectedReview && !isGreptileReviewTerminal(options.selectedReview.status)) {
4927
6249
  return true;
4928
6250
  }
4929
- return options.attempt + 1 < options.pollAttempts;
6251
+ return true;
4930
6252
  }
4931
6253
  function shouldContinueGithubGreptileFallbackPolling(options) {
4932
6254
  const waitingForVisiblePendingReview = options.checkState.pending && (!options.fallbackReview || !options.selectedReview && !options.approvedViaReviewedAncestor);
6255
+ if (options.attempt + 1 >= options.pollAttempts) {
6256
+ return false;
6257
+ }
4933
6258
  if (waitingForVisiblePendingReview) {
4934
6259
  return true;
4935
6260
  }
@@ -4990,6 +6315,20 @@ function runGhJson(projectRoot, args) {
4990
6315
  throw new Error(`gh ${args.join(" ")} returned malformed JSON: ${result.stdout}`);
4991
6316
  }
4992
6317
  }
6318
+ async function collectStrictPrEvidenceForVerifier(input) {
6319
+ return collectPrReviewEvidence({
6320
+ projectRoot: input.projectRoot,
6321
+ prUrl: input.prUrl,
6322
+ taskId: input.taskId,
6323
+ runId: "verifier",
6324
+ cycle: 0,
6325
+ apiSignals: input.apiSignals ?? [],
6326
+ command: async (args, options) => {
6327
+ const result = runCapture(["gh", ...args], options?.cwd ?? input.projectRoot);
6328
+ return { exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr };
6329
+ }
6330
+ });
6331
+ }
4993
6332
  function deriveRepoName(projectRoot, prState) {
4994
6333
  const fromUrl = /github\.com\/([^/]+\/[^/]+)\/pull\/\d+/.exec(prState.url || "");
4995
6334
  if (fromUrl?.[1]) {
@@ -5004,8 +6343,9 @@ function resolvePrHeadSha(projectRoot, prState) {
5004
6343
  const repoRoot = resolvePrRepoRoot(projectRoot, prState);
5005
6344
  return runCapture(["git", "-C", repoRoot, "rev-parse", "HEAD"], projectRoot).stdout.trim();
5006
6345
  }
5007
- function isGreptileGithubLogin(login) {
5008
- return (login || "").replace(/\[bot\]$/, "") === "greptile-apps";
6346
+ function isGreptileGithubLogin2(login) {
6347
+ const normalized = (login || "").toLowerCase().replace(/\[bot\]$/, "");
6348
+ return normalized === "greptile" || normalized === "greptile-ai" || normalized === "greptileai" || normalized === "greptile-apps";
5009
6349
  }
5010
6350
  function pickRelevantGithubGreptileReview(reviews, expectedHeadSha) {
5011
6351
  const matching = sortGithubGreptileReviews(reviews);
@@ -5022,7 +6362,7 @@ function pickLatestGithubGreptileReview(reviews) {
5022
6362
  return sortGithubGreptileReviews(reviews)[0] || null;
5023
6363
  }
5024
6364
  function sortGithubGreptileReviews(reviews) {
5025
- return reviews.filter((review) => isGreptileGithubLogin(review.user?.login)).sort((left, right) => Date.parse(right.submitted_at || "") - Date.parse(left.submitted_at || ""));
6365
+ return reviews.filter((review) => isGreptileGithubLogin2(review.user?.login)).sort((left, right) => Date.parse(right.submitted_at || "") - Date.parse(left.submitted_at || ""));
5026
6366
  }
5027
6367
  function loadGithubPullRequestCheckRollup(projectRoot, repoName, prNumber) {
5028
6368
  const response = runGhJson(projectRoot, [
@@ -5095,32 +6435,6 @@ function classifyGithubGreptileCheckState(checks) {
5095
6435
  }
5096
6436
  return { pending: false, completed: false };
5097
6437
  }
5098
- function isGithubGreptileCheckApproved(checks) {
5099
- const greptileChecks = checks.filter((check) => {
5100
- const label = (check.name || check.context || "").toLowerCase();
5101
- return label.includes("greptile");
5102
- });
5103
- if (greptileChecks.length === 0) {
5104
- return false;
5105
- }
5106
- for (const check of greptileChecks) {
5107
- if ((check.__typename || "") === "CheckRun") {
5108
- if ((check.status || "").toUpperCase() !== "COMPLETED") {
5109
- return false;
5110
- }
5111
- const conclusion = (check.conclusion || "").toUpperCase();
5112
- if (!["SUCCESS", "NEUTRAL", "SKIPPED"].includes(conclusion)) {
5113
- return false;
5114
- }
5115
- continue;
5116
- }
5117
- const state = (check.state || "").toUpperCase();
5118
- if (!["SUCCESS", "NEUTRAL", "SKIPPED"].includes(state)) {
5119
- return false;
5120
- }
5121
- }
5122
- return true;
5123
- }
5124
6438
  function loadGithubReviewThreads(projectRoot, repoName, prNumber) {
5125
6439
  const [owner, name] = repoName.split("/");
5126
6440
  if (!owner || !name) {
@@ -5146,7 +6460,7 @@ function filterActionableGithubGreptileThreads(threads) {
5146
6460
  return [];
5147
6461
  }
5148
6462
  const comments = thread.comments?.nodes || [];
5149
- const latestGreptileComment = [...comments].reverse().find((comment) => isGreptileGithubLogin(comment.author?.login));
6463
+ const latestGreptileComment = [...comments].reverse().find((comment) => isGreptileGithubLogin2(comment.author?.login));
5150
6464
  if (!latestGreptileComment?.path?.trim()) {
5151
6465
  return [];
5152
6466
  }
@@ -5155,7 +6469,7 @@ function filterActionableGithubGreptileThreads(threads) {
5155
6469
  }
5156
6470
  function resolvePrRepoRoot(projectRoot, prState) {
5157
6471
  const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
5158
- if (prState.target === "monorepo" && runtimeWorkspace && existsSync19(resolve22(runtimeWorkspace, ".git"))) {
6472
+ if (prState.target === "monorepo" && runtimeWorkspace && existsSync20(resolve23(runtimeWorkspace, ".git"))) {
5159
6473
  return runtimeWorkspace;
5160
6474
  }
5161
6475
  const paths = resolveHarnessPaths(projectRoot);
@@ -5168,11 +6482,6 @@ function isCommitAncestorOfPrHead(projectRoot, prState, reviewedCommit, headComm
5168
6482
  const repoRoot = resolvePrRepoRoot(projectRoot, prState);
5169
6483
  return runCapture(["git", "-C", repoRoot, "merge-base", "--is-ancestor", reviewedCommit, headCommit], projectRoot).exitCode === 0;
5170
6484
  }
5171
- function stripHtml(input) {
5172
- return input.replace(/<[^>]+>/g, " ").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/\r/g, "").replace(/\n{3,}/g, `
5173
-
5174
- `).trim();
5175
- }
5176
6485
  function summarizeComment(input) {
5177
6486
  const text = stripHtml(input).replace(/\s+/g, " ").trim();
5178
6487
  return text.length > 160 ? `${text.slice(0, 157)}...` : text;
@@ -5181,31 +6490,14 @@ function asGreptileInfrastructureWarning(reason) {
5181
6490
  return reason.startsWith("[AI Review]") ? reason.replace("[AI Review]", "[AI Review Warning]") : reason;
5182
6491
  }
5183
6492
  function isAiReviewApproved(input) {
6493
+ if (input.aiVerdict === "REJECT" && input.aiReasons.length > 0) {
6494
+ return false;
6495
+ }
5184
6496
  if (input.reviewMode !== "required") {
5185
6497
  return true;
5186
6498
  }
5187
6499
  return input.aiVerdict === "APPROVE" && input.aiReasons.length === 0;
5188
6500
  }
5189
- function parseGreptileScore(input) {
5190
- const text = stripHtml(input);
5191
- const patterns = [
5192
- /confidence score:\s*(\d+)\s*\/\s*(\d+)/i,
5193
- /\bscore:\s*(\d+)\s*\/\s*(\d+)/i,
5194
- /\b(\d+)\s*\/\s*(\d+)\s*(?:confidence|score)/i
5195
- ];
5196
- for (const pattern of patterns) {
5197
- const match = pattern.exec(text);
5198
- if (!match) {
5199
- continue;
5200
- }
5201
- const value = Number.parseInt(match[1] || "", 10);
5202
- const scale = Number.parseInt(match[2] || "", 10);
5203
- if (Number.isFinite(value) && Number.isFinite(scale) && scale > 0) {
5204
- return { value, scale };
5205
- }
5206
- }
5207
- return null;
5208
- }
5209
6501
 
5210
6502
  // packages/runtime/src/control-plane/provider/runtime-instructions.ts
5211
6503
  var CLAUDE_ROUTER_TOOL_NAMES = [
@@ -5557,16 +6849,16 @@ async function taskDeps(projectRoot, taskId) {
5557
6849
  for (const dep of deps) {
5558
6850
  const artifactDir = artifactDirForId(projectRoot, dep);
5559
6851
  console.log(`=== ${dep} ===`);
5560
- if (!existsSync20(artifactDir)) {
6852
+ if (!existsSync21(artifactDir)) {
5561
6853
  console.log(` (no artifacts yet)
5562
6854
  `);
5563
6855
  continue;
5564
6856
  }
5565
- printArtifactSection(resolve23(artifactDir, "decision-log.md"), "--- Decisions ---");
5566
- printArtifactSection(resolve23(artifactDir, "next-actions.md"), "--- Next Actions (for you) ---");
5567
- const changedFiles = resolve23(artifactDir, "changed-files.txt");
5568
- if (existsSync20(changedFiles)) {
5569
- const lines = readFileSync10(changedFiles, "utf-8").split(/\r?\n/).filter(Boolean);
6857
+ printArtifactSection(resolve24(artifactDir, "decision-log.md"), "--- Decisions ---");
6858
+ printArtifactSection(resolve24(artifactDir, "next-actions.md"), "--- Next Actions (for you) ---");
6859
+ const changedFiles = resolve24(artifactDir, "changed-files.txt");
6860
+ if (existsSync21(changedFiles)) {
6861
+ const lines = readFileSync11(changedFiles, "utf-8").split(/\r?\n/).filter(Boolean);
5570
6862
  console.log(`--- Changed Files (${lines.length}) ---`);
5571
6863
  for (const line of lines) {
5572
6864
  console.log(line);
@@ -5611,12 +6903,12 @@ function taskRecord(projectRoot, type, text, taskId) {
5611
6903
  throw new Error("No active task.");
5612
6904
  }
5613
6905
  const paths = resolveHarnessPaths(projectRoot);
5614
- mkdirSync10(paths.stateDir, { recursive: true });
6906
+ mkdirSync11(paths.stateDir, { recursive: true });
5615
6907
  if (type === "decision") {
5616
- const artifactDir = resolve23(paths.artifactsDir, activeTask);
5617
- mkdirSync10(artifactDir, { recursive: true });
6908
+ const artifactDir = resolve24(paths.artifactsDir, activeTask);
6909
+ mkdirSync11(artifactDir, { recursive: true });
5618
6910
  const timestamp = nowIso();
5619
- appendFileSync(resolve23(artifactDir, "decision-log.md"), `
6911
+ appendFileSync(resolve24(artifactDir, "decision-log.md"), `
5620
6912
  ### ${timestamp}
5621
6913
 
5622
6914
  ${text}
@@ -5626,15 +6918,15 @@ ${text}
5626
6918
  return;
5627
6919
  }
5628
6920
  const failedPath = paths.failedApproachesPath;
5629
- if (!existsSync20(failedPath)) {
5630
- writeFileSync10(failedPath, `# Failed Approaches Log
6921
+ if (!existsSync21(failedPath)) {
6922
+ writeFileSync11(failedPath, `# Failed Approaches Log
5631
6923
 
5632
6924
  This file records approaches that did not work.
5633
6925
 
5634
6926
  `, "utf-8");
5635
6927
  }
5636
- const content = readFileSync10(failedPath, "utf-8");
5637
- const attempts = (content.match(new RegExp(`^## ${escapeRegExp2(activeTask)}\\b`, "gm")) || []).length + 1;
6928
+ const content = readFileSync11(failedPath, "utf-8");
6929
+ const attempts = (content.match(new RegExp(`^## ${escapeRegExp(activeTask)}\\b`, "gm")) || []).length + 1;
5638
6930
  appendFileSync(failedPath, `
5639
6931
  ## ${activeTask} - Attempt ${attempts} (${nowIso()})
5640
6932
 
@@ -5650,40 +6942,40 @@ function taskArtifacts(projectRoot, taskId) {
5650
6942
  throw new Error("No active task.");
5651
6943
  }
5652
6944
  const paths = resolveHarnessPaths(projectRoot);
5653
- const artifactDir = resolve23(paths.artifactsDir, activeTask);
5654
- mkdirSync10(artifactDir, { recursive: true });
6945
+ const artifactDir = resolve24(paths.artifactsDir, activeTask);
6946
+ mkdirSync11(artifactDir, { recursive: true });
5655
6947
  const changed = changedFilesForTask(projectRoot, activeTask, true);
5656
- writeFileSync10(resolve23(artifactDir, "changed-files.txt"), `${changed.join(`
6948
+ writeFileSync11(resolve24(artifactDir, "changed-files.txt"), `${changed.join(`
5657
6949
  `)}
5658
6950
  `, "utf-8");
5659
6951
  console.log(`changed-files.txt: ${changed.length} files`);
5660
- const taskResultPath = resolve23(artifactDir, "task-result.json");
5661
- if (!existsSync20(taskResultPath)) {
6952
+ const taskResultPath = resolve24(artifactDir, "task-result.json");
6953
+ if (!existsSync21(taskResultPath)) {
5662
6954
  const template = {
5663
6955
  task_id: activeTask,
5664
6956
  status: "completed",
5665
6957
  summary: "TODO: Write a one-line summary of what you did",
5666
6958
  completed_at: nowIso()
5667
6959
  };
5668
- writeFileSync10(taskResultPath, `${JSON.stringify(template, null, 2)}
6960
+ writeFileSync11(taskResultPath, `${JSON.stringify(template, null, 2)}
5669
6961
  `, "utf-8");
5670
6962
  console.log("task-result.json: created (update the summary!)");
5671
6963
  } else {
5672
6964
  console.log("task-result.json: already exists");
5673
6965
  }
5674
- const decisionLogPath = resolve23(artifactDir, "decision-log.md");
5675
- if (!existsSync20(decisionLogPath)) {
6966
+ const decisionLogPath = resolve24(artifactDir, "decision-log.md");
6967
+ if (!existsSync21(decisionLogPath)) {
5676
6968
  const content = `# Decision Log: ${activeTask}
5677
6969
 
5678
6970
  Record key decisions here using: rig-agent record decision "..."
5679
6971
  `;
5680
- writeFileSync10(decisionLogPath, content, "utf-8");
6972
+ writeFileSync11(decisionLogPath, content, "utf-8");
5681
6973
  console.log("decision-log.md: created (record your decisions!)");
5682
6974
  } else {
5683
6975
  console.log("decision-log.md: already exists");
5684
6976
  }
5685
- const nextActionsPath = resolve23(artifactDir, "next-actions.md");
5686
- if (!existsSync20(nextActionsPath)) {
6977
+ const nextActionsPath = resolve24(artifactDir, "next-actions.md");
6978
+ if (!existsSync21(nextActionsPath)) {
5687
6979
  const content = [
5688
6980
  `# Next Actions: ${activeTask}`,
5689
6981
  "",
@@ -5700,13 +6992,13 @@ Record key decisions here using: rig-agent record decision "..."
5700
6992
  ""
5701
6993
  ].join(`
5702
6994
  `);
5703
- writeFileSync10(nextActionsPath, content, "utf-8");
6995
+ writeFileSync11(nextActionsPath, content, "utf-8");
5704
6996
  console.log("next-actions.md: created (add recommendations for downstream tasks!)");
5705
6997
  } else {
5706
6998
  console.log("next-actions.md: already exists");
5707
6999
  }
5708
- const validationSummaryPath = resolve23(artifactDir, "validation-summary.json");
5709
- if (existsSync20(validationSummaryPath)) {
7000
+ const validationSummaryPath = resolve24(artifactDir, "validation-summary.json");
7001
+ if (existsSync21(validationSummaryPath)) {
5710
7002
  console.log("validation-summary.json: already exists");
5711
7003
  } else {
5712
7004
  console.log("validation-summary.json: not yet created (run: rig-agent validate)");
@@ -5720,8 +7012,8 @@ function taskArtifactDir(projectRoot, taskId) {
5720
7012
  throw new Error("No active task.");
5721
7013
  }
5722
7014
  const paths = resolveHarnessPaths(projectRoot);
5723
- const artifactDir = resolve23(paths.artifactsDir, activeTask);
5724
- mkdirSync10(artifactDir, { recursive: true });
7015
+ const artifactDir = resolve24(paths.artifactsDir, activeTask);
7016
+ mkdirSync11(artifactDir, { recursive: true });
5725
7017
  return artifactDir;
5726
7018
  }
5727
7019
  function taskArtifactWrite(projectRoot, filename, content, taskId) {
@@ -5730,10 +7022,10 @@ function taskArtifactWrite(projectRoot, filename, content, taskId) {
5730
7022
  throw new Error("No active task.");
5731
7023
  }
5732
7024
  const paths = resolveHarnessPaths(projectRoot);
5733
- const artifactDir = resolve23(paths.artifactsDir, activeTask);
5734
- mkdirSync10(artifactDir, { recursive: true });
5735
- const targetPath = resolve23(artifactDir, filename);
5736
- writeFileSync10(targetPath, content, "utf-8");
7025
+ const artifactDir = resolve24(paths.artifactsDir, activeTask);
7026
+ mkdirSync11(artifactDir, { recursive: true });
7027
+ const targetPath = resolve24(artifactDir, filename);
7028
+ writeFileSync11(targetPath, content, "utf-8");
5737
7029
  console.log(`Wrote: ${targetPath}`);
5738
7030
  }
5739
7031
  function taskArtifactRead(projectRoot, filename, options) {
@@ -5742,11 +7034,11 @@ function taskArtifactRead(projectRoot, filename, options) {
5742
7034
  throw new Error("No active task.");
5743
7035
  }
5744
7036
  const maxBytes = options?.maxBytes ?? 64 * 1024;
5745
- const targetPath = resolve23(taskArtifactDir(projectRoot, activeTask), filename);
5746
- if (!existsSync20(targetPath)) {
7037
+ const targetPath = resolve24(taskArtifactDir(projectRoot, activeTask), filename);
7038
+ if (!existsSync21(targetPath)) {
5747
7039
  throw new Error(`Artifact not found: ${targetPath}`);
5748
7040
  }
5749
- const buffer = readFileSync10(targetPath);
7041
+ const buffer = readFileSync11(targetPath);
5750
7042
  const preview = buffer.subarray(0, maxBytes);
5751
7043
  return {
5752
7044
  path: targetPath,
@@ -5791,12 +7083,12 @@ async function taskValidate(projectRoot, taskId, validatorRegistry) {
5791
7083
  console.log(`Validation passed: ${summary.passed}/${summary.total}`);
5792
7084
  return true;
5793
7085
  }
5794
- async function taskVerify(projectRoot, plugins, taskId) {
7086
+ async function taskVerify(projectRoot, taskId) {
5795
7087
  const activeTask = taskId || currentTaskId(projectRoot);
5796
7088
  if (!activeTask) {
5797
7089
  throw new Error("No active task.");
5798
7090
  }
5799
- const outcome = await verifyTask({ projectRoot, taskId: activeTask, plugins });
7091
+ const outcome = await verifyTask({ projectRoot, taskId: activeTask });
5800
7092
  if (!outcome.approved) {
5801
7093
  console.log("REJECT:");
5802
7094
  for (const reason of outcome.localReasons) {
@@ -5896,7 +7188,7 @@ function collectTaskChangedFiles(projectRoot, taskId, includeCommitted) {
5896
7188
  [projectRoot, ""],
5897
7189
  [monorepoRepoRoot, ""]
5898
7190
  ]) {
5899
- if (!existsSync20(resolve23(repo, ".git"))) {
7191
+ if (!existsSync21(resolve24(repo, ".git"))) {
5900
7192
  continue;
5901
7193
  }
5902
7194
  if (includeCommitted && repo === monorepoRepoRoot) {
@@ -5934,8 +7226,8 @@ function filterTaskChangedFiles(projectRoot, taskId, files, scoped) {
5934
7226
  }
5935
7227
  function resolveTaskMonorepoRoot(projectRoot) {
5936
7228
  const runtimeWorkspace = loadRuntimeContextFromEnv()?.workspaceDir || process.env.RIG_TASK_WORKSPACE?.trim();
5937
- if (runtimeWorkspace && existsSync20(resolve23(runtimeWorkspace, ".git"))) {
5938
- return resolve23(runtimeWorkspace);
7229
+ if (runtimeWorkspace && existsSync21(resolve24(runtimeWorkspace, ".git"))) {
7230
+ return resolve24(runtimeWorkspace);
5939
7231
  }
5940
7232
  return resolveHarnessPaths(projectRoot).monorepoRoot;
5941
7233
  }
@@ -5963,7 +7255,7 @@ function resolveRuntimeInitialHeadCommit(projectRoot, repo) {
5963
7255
  const runtimeContext = loadRuntimeContextFromEnv();
5964
7256
  if (runtimeContext?.initialHeadCommits?.monorepo?.trim()) {
5965
7257
  const monorepoRoot = resolveTaskMonorepoRoot(projectRoot);
5966
- if (resolve23(monorepoRoot) === resolve23(repo)) {
7258
+ if (resolve24(monorepoRoot) === resolve24(repo)) {
5967
7259
  return runtimeContext.initialHeadCommits.monorepo.trim();
5968
7260
  }
5969
7261
  }
@@ -5973,7 +7265,7 @@ function resolveMonorepoBaseCommit(projectRoot, repo) {
5973
7265
  const runtimeContext = loadRuntimeContextFromEnv();
5974
7266
  if (runtimeContext?.monorepoBaseCommit?.trim()) {
5975
7267
  const monorepoRoot = resolveTaskMonorepoRoot(projectRoot);
5976
- if (resolve23(monorepoRoot) === resolve23(repo)) {
7268
+ if (resolve24(monorepoRoot) === resolve24(repo)) {
5977
7269
  return runtimeContext.monorepoBaseCommit.trim();
5978
7270
  }
5979
7271
  }
@@ -6007,7 +7299,7 @@ function resolveRuntimeDirtyBaseline(projectRoot, repo) {
6007
7299
  return new Set;
6008
7300
  }
6009
7301
  const monorepoRoot = resolveTaskMonorepoRoot(projectRoot);
6010
- const selected = resolve23(repo) === resolve23(monorepoRoot) ? dirtyFiles.monorepo : resolve23(repo) === resolve23(projectRoot) ? dirtyFiles.project : undefined;
7302
+ const selected = resolve24(repo) === resolve24(monorepoRoot) ? dirtyFiles.monorepo : resolve24(repo) === resolve24(projectRoot) ? dirtyFiles.project : undefined;
6011
7303
  return new Set((selected || []).map((file) => normalizeChangedFilePath(file)).filter(Boolean));
6012
7304
  }
6013
7305
  function normalizeChangedFilePath(file) {
@@ -6107,12 +7399,12 @@ function printIndented(text) {
6107
7399
  }
6108
7400
  }
6109
7401
  function readLocalBeadsTasks(projectRoot) {
6110
- const issuesPath = resolve23(resolveMonorepoRoot2(projectRoot), ".beads/issues.jsonl");
6111
- if (!existsSync20(issuesPath)) {
7402
+ const issuesPath = resolve24(resolveMonorepoRoot2(projectRoot), ".beads/issues.jsonl");
7403
+ if (!existsSync21(issuesPath)) {
6112
7404
  return [];
6113
7405
  }
6114
7406
  const tasks = [];
6115
- for (const line of readFileSync10(issuesPath, "utf-8").split(/\r?\n/)) {
7407
+ for (const line of readFileSync11(issuesPath, "utf-8").split(/\r?\n/)) {
6116
7408
  const trimmed = line.trim();
6117
7409
  if (!trimmed) {
6118
7410
  continue;
@@ -6283,14 +7575,14 @@ function taskDependencies(projectRoot, taskId, tracker) {
6283
7575
  return [...ids].sort();
6284
7576
  }
6285
7577
  function printArtifactSection(path, header) {
6286
- if (!existsSync20(path)) {
7578
+ if (!existsSync21(path)) {
6287
7579
  return;
6288
7580
  }
6289
7581
  console.log(header);
6290
- process.stdout.write(readFileSync10(path, "utf-8"));
7582
+ process.stdout.write(readFileSync11(path, "utf-8"));
6291
7583
  console.log("");
6292
7584
  }
6293
- function escapeRegExp2(value) {
7585
+ function escapeRegExp(value) {
6294
7586
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6295
7587
  }
6296
7588
  function changedFilesForTask(projectRoot, taskId, scoped) {