@gethmy/harness 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -584,7 +584,6 @@ var init_stageHandoff = __esm(() => {
584
584
 
585
585
  // ../harmony-shared/dist/types.js
586
586
  var init_types = () => {};
587
-
588
587
  // ../harmony-shared/dist/index.js
589
588
  var init_dist = __esm(() => {
590
589
  init_agentStaleness();
@@ -610,6 +609,492 @@ var init_dist = __esm(() => {
610
609
  init_types();
611
610
  });
612
611
 
612
+ // src/confine-to-repo.ts
613
+ import { realpathSync } from "node:fs";
614
+ import { dirname, isAbsolute, parse, resolve, sep } from "node:path";
615
+ function isGitMetadata(repoRoot, candidate) {
616
+ const rel = candidate.startsWith(repoRoot) ? candidate.slice(repoRoot.length) : candidate;
617
+ return rel.split(/[\\/]/).some((segment) => segment.toLowerCase() === ".git");
618
+ }
619
+ function patternEscapes(pattern) {
620
+ if (isAbsolute(pattern))
621
+ return true;
622
+ if (/[{}[\]~()!+@]/.test(pattern))
623
+ return true;
624
+ return pattern.split(/[\\/]/).some((segment) => segment === "..");
625
+ }
626
+ function pathArgsFor(mode) {
627
+ return mode === "write" ? { ...READ_PATH_ARGS, ...WRITE_PATH_ARGS } : READ_PATH_ARGS;
628
+ }
629
+ function realPathOrNearest(p) {
630
+ const abs = isAbsolute(p) ? p : resolve(p);
631
+ const { root } = parse(abs);
632
+ let real = root;
633
+ for (const part of abs.slice(root.length).split(sep)) {
634
+ if (part === "" || part === ".")
635
+ continue;
636
+ if (part === "..") {
637
+ real = dirname(real);
638
+ continue;
639
+ }
640
+ const next = real.endsWith(sep) ? real + part : real + sep + part;
641
+ try {
642
+ real = realpathSync(next);
643
+ } catch {
644
+ real = next;
645
+ }
646
+ }
647
+ return real;
648
+ }
649
+ function isInsideTree(root, target) {
650
+ const normalizedRoot = realPathOrNearest(root);
651
+ const normalizedTarget = realPathOrNearest(target);
652
+ if (normalizedTarget === normalizedRoot)
653
+ return true;
654
+ return normalizedTarget.startsWith(normalizedRoot + sep);
655
+ }
656
+ function decideConfinedTool(repoRoot, toolName, input, mode = "read") {
657
+ const pathArgs = pathArgsFor(mode)[toolName];
658
+ if (!pathArgs) {
659
+ return {
660
+ behavior: "deny",
661
+ message: `${toolName} is not available to this run.`
662
+ };
663
+ }
664
+ const verb = toolName in WRITE_PATH_ARGS ? "write" : "read";
665
+ for (const key of PATTERN_ARG_BY_TOOL[toolName] ?? []) {
666
+ const value = input[key];
667
+ if (typeof value !== "string" || value.length === 0)
668
+ continue;
669
+ if (patternEscapes(value)) {
670
+ return {
671
+ behavior: "deny",
672
+ message: `${toolName} patterns must stay inside the repository — no absolute path and no "..". Refused: ${value}`
673
+ };
674
+ }
675
+ }
676
+ for (const key of pathArgs) {
677
+ const value = input[key];
678
+ if (typeof value !== "string" || value.length === 0)
679
+ continue;
680
+ const candidate = isAbsolute(value) ? value : `${repoRoot}${sep}${value}`;
681
+ if (isGitMetadata(repoRoot, candidate)) {
682
+ return {
683
+ behavior: "deny",
684
+ message: `${toolName} may not touch the repository's git metadata. Refused: ${value}`
685
+ };
686
+ }
687
+ if (!isInsideTree(repoRoot, candidate)) {
688
+ return {
689
+ behavior: "deny",
690
+ message: `${toolName} may only ${verb} inside the repository. Refused: ${value}`
691
+ };
692
+ }
693
+ }
694
+ return { behavior: "allow" };
695
+ }
696
+ function confineToRepo(repoRoot, mode = "read") {
697
+ return async (toolName, input) => decideConfinedTool(repoRoot, toolName, input, mode);
698
+ }
699
+ var READ_PATH_ARGS, WRITE_PATH_ARGS, CONFINED_READ_TOOLS, CONFINED_WRITE_TOOLS, PATTERN_ARG_BY_TOOL;
700
+ var init_confine_to_repo = __esm(() => {
701
+ READ_PATH_ARGS = {
702
+ Read: ["file_path", "path", "notebook_path"],
703
+ Grep: ["path"],
704
+ Glob: ["path"]
705
+ };
706
+ WRITE_PATH_ARGS = {
707
+ Write: ["file_path"],
708
+ Edit: ["file_path"],
709
+ MultiEdit: ["file_path"],
710
+ NotebookEdit: ["notebook_path"]
711
+ };
712
+ CONFINED_READ_TOOLS = Object.freeze(Object.keys(READ_PATH_ARGS));
713
+ CONFINED_WRITE_TOOLS = Object.freeze([
714
+ ...Object.keys(READ_PATH_ARGS),
715
+ ...Object.keys(WRITE_PATH_ARGS).filter((t) => t !== "MultiEdit")
716
+ ]);
717
+ PATTERN_ARG_BY_TOOL = {
718
+ Glob: ["pattern"],
719
+ Grep: ["glob"]
720
+ };
721
+ });
722
+
723
+ // src/runner.ts
724
+ import { getConfigDir } from "@gethmy/mcp/src/config.js";
725
+ function mayHoldCredentials(role) {
726
+ return role === "author" || role === "reviewer";
727
+ }
728
+ function credentialReadDeny() {
729
+ return `Read(/${getConfigDir()}/**)`;
730
+ }
731
+ function credentialAccessDeny() {
732
+ const dir = `/${getConfigDir()}/**`;
733
+ return [`Read(${dir})`, `Grep(${dir})`, `Glob(${dir})`];
734
+ }
735
+ function buildRoleLaunch(args) {
736
+ const role = normalizeStageRole(args.role);
737
+ const keep = mayHoldCredentials(role);
738
+ const env = {};
739
+ for (const [key, value] of Object.entries(args.parentEnv)) {
740
+ if (value === undefined)
741
+ continue;
742
+ if (!keep && HARMONY_CREDENTIAL_KEYS.includes(key))
743
+ continue;
744
+ env[key] = value;
745
+ }
746
+ return {
747
+ role,
748
+ prompt: args.prompt,
749
+ repoPath: args.repoPath,
750
+ env,
751
+ disallowedTools: keep ? [] : [credentialReadDeny()]
752
+ };
753
+ }
754
+ function envKeysDroppedByLaunch(parentEnv, launch) {
755
+ return Object.keys(parentEnv).filter((key) => parentEnv[key] !== undefined && !Object.hasOwn(launch.env, key));
756
+ }
757
+ var HARMONY_CREDENTIAL_KEYS;
758
+ var init_runner = __esm(() => {
759
+ init_dist();
760
+ HARMONY_CREDENTIAL_KEYS = [
761
+ "HARMONY_API_KEY",
762
+ "HARMONY_API_URL",
763
+ "HARMONY_WORKSPACE_ID",
764
+ "SUPABASE_ANON_KEY",
765
+ "SUPABASE_SERVICE_ROLE_KEY",
766
+ "SUPABASE_URL"
767
+ ];
768
+ });
769
+
770
+ // src/run-containment.ts
771
+ import { createHash } from "node:crypto";
772
+ import { readFileSync } from "node:fs";
773
+ import { createRequire as createRequire2 } from "node:module";
774
+ import { homedir, tmpdir } from "node:os";
775
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join } from "node:path";
776
+ import { getConfigDir as getConfigDir2 } from "@gethmy/mcp/src/config.js";
777
+ function credentialDirectories() {
778
+ const home = homedir();
779
+ return [
780
+ getConfigDir2(),
781
+ join(home, ".claude"),
782
+ join(home, ".claude.json"),
783
+ join(home, ".ssh"),
784
+ join(home, ".gnupg"),
785
+ join(home, ".aws"),
786
+ join(home, ".codex"),
787
+ join(home, ".gemini"),
788
+ join(home, ".config", "gh"),
789
+ join(home, ".config", "gcloud"),
790
+ join(home, ".config", "anthropic"),
791
+ join(home, ".config", "op"),
792
+ join(home, ".docker"),
793
+ join(home, ".kube"),
794
+ join(home, ".netrc"),
795
+ join(home, ".npmrc"),
796
+ join(home, ".git-credentials")
797
+ ];
798
+ }
799
+ function writeOnlyDenyPaths() {
800
+ const paths = [join(homedir(), ".gitconfig")];
801
+ const xdg = process.env.XDG_CONFIG_HOME;
802
+ paths.push(xdg && isAbsolute2(xdg) ? join(xdg, "git") : join(homedir(), ".config", "git"));
803
+ return paths;
804
+ }
805
+ function credentialToolDeny() {
806
+ return credentialDirectories().flatMap((dir) => [
807
+ `Read(/${dir})`,
808
+ `Read(/${dir}/**)`
809
+ ]);
810
+ }
811
+ function toolchainCacheDirectories(worktree) {
812
+ const home = homedir();
813
+ const scratch = `harmony-run-${createHash("sha256").update(worktree).digest("hex").slice(0, 16)}`;
814
+ const candidate = process.env.TMPDIR ?? tmpdir();
815
+ const tmpRoot = isAbsolute2(candidate) ? candidate : "/tmp";
816
+ return [
817
+ join(home, ".bun", "install", "cache"),
818
+ join(home, ".npm", "_cacache"),
819
+ join(tmpRoot, scratch)
820
+ ];
821
+ }
822
+ function secretEnvKeysToStrip(parentEnv = process.env) {
823
+ const stripped = new Set(HARMONY_CREDENTIAL_KEYS);
824
+ for (const key of Object.keys(parentEnv)) {
825
+ if (KEEP_ENV_KEYS.has(key))
826
+ continue;
827
+ if (SECRET_ENV_PATTERN.test(key))
828
+ stripped.add(key);
829
+ }
830
+ return [...stripped];
831
+ }
832
+ function assertNoProjectSandboxOverride(worktree) {
833
+ for (const name of ["settings.json", "settings.local.json"]) {
834
+ const path = join(worktree, ".claude", name);
835
+ let raw;
836
+ try {
837
+ raw = readFileSync(path, "utf-8");
838
+ } catch {
839
+ continue;
840
+ }
841
+ let parsed;
842
+ try {
843
+ parsed = JSON.parse(raw);
844
+ } catch {
845
+ continue;
846
+ }
847
+ if (parsed === null || typeof parsed !== "object")
848
+ continue;
849
+ const offending = Object.keys(parsed).filter((key) => !INERT_PROJECT_SETTING_KEYS.has(key));
850
+ if (offending.length > 0) {
851
+ throw new ProjectSandboxOverrideError(path, offending);
852
+ }
853
+ }
854
+ }
855
+ function containedEnv(parentEnv = process.env) {
856
+ const strip = new Set(secretEnvKeysToStrip(parentEnv));
857
+ const out = {};
858
+ for (const [key, value] of Object.entries(parentEnv)) {
859
+ if (value === undefined || strip.has(key))
860
+ continue;
861
+ out[key] = value;
862
+ }
863
+ return out;
864
+ }
865
+ function gitMetadataDenyPaths(worktree) {
866
+ const paths = new Set;
867
+ const dotGit = join(worktree, ".git");
868
+ const require2 = createRequire2(import.meta.url);
869
+ const { execFileSync } = require2("node:child_process");
870
+ const { statSync } = require2("node:fs");
871
+ const gitDirs = new Set;
872
+ try {
873
+ const out = execFileSync("git", [...GIT_NO_HOOKS, "rev-parse", "--git-dir", "--git-common-dir"], { cwd: worktree, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
874
+ for (const line of out.split(`
875
+ `)) {
876
+ const trimmed = line.trim();
877
+ if (!trimmed)
878
+ continue;
879
+ gitDirs.add(isAbsolute2(trimmed) ? trimmed : join(worktree, trimmed));
880
+ }
881
+ } catch {}
882
+ gitDirs.add(dotGit);
883
+ for (const dir of gitDirs) {
884
+ paths.add(join(dir, "config"));
885
+ paths.add(join(dir, "config.worktree"));
886
+ paths.add(join(dir, "hooks"));
887
+ }
888
+ try {
889
+ if (statSync(dotGit).isFile())
890
+ paths.add(dotGit);
891
+ } catch {}
892
+ return [...paths];
893
+ }
894
+ function hostPersistencePaths() {
895
+ const home = homedir();
896
+ return [
897
+ join(home, ".zshenv"),
898
+ join(home, ".zprofile"),
899
+ join(home, ".zshrc"),
900
+ join(home, ".zlogin"),
901
+ join(home, ".bashrc"),
902
+ join(home, ".bash_profile"),
903
+ join(home, ".bash_login"),
904
+ join(home, ".profile"),
905
+ join(home, ".config", "fish", "config.fish"),
906
+ join(home, ".config", "fish", "conf.d"),
907
+ join(home, "Library", "LaunchAgents"),
908
+ join(home, ".config", "systemd", "user"),
909
+ join(home, ".config", "autostart")
910
+ ];
911
+ }
912
+ function credentialWriteToolDeny(worktree) {
913
+ return [
914
+ ...credentialDirectories(),
915
+ ...writeOnlyDenyPaths(),
916
+ ...hostPersistencePaths(),
917
+ ...gitMetadataDenyPaths(worktree)
918
+ ].flatMap((p) => [`Edit(/${p})`, `Edit(/${p}/**)`]);
919
+ }
920
+ function implementRunToolPolicy(args) {
921
+ const writable = [args.worktree, ...toolchainCacheDirectories(args.worktree)];
922
+ const gitMeta = gitMetadataDenyPaths(args.worktree);
923
+ const secrets = credentialDirectories();
924
+ return async (toolName, input) => {
925
+ const allow = { behavior: "allow", updatedInput: input };
926
+ const deny = (message) => ({ behavior: "deny", message });
927
+ if (toolName.startsWith("mcp__"))
928
+ return allow;
929
+ if (toolName === "Bash" || toolName === "BashOutput") {
930
+ return allow;
931
+ }
932
+ const writePaths = WRITE_TOOL_PATHS[toolName];
933
+ if (writePaths) {
934
+ if (args.readOnly === true) {
935
+ return deny(`${toolName} is not available to a review run.`);
936
+ }
937
+ for (const key of writePaths) {
938
+ const value = input[key];
939
+ if (typeof value !== "string" || value.length === 0)
940
+ continue;
941
+ const target = isAbsolute2(value) ? value : join(args.worktree, value);
942
+ if (gitMeta.some((p) => isInsideTree(p, target) || p === target)) {
943
+ return deny(`${toolName} may not touch git metadata. Refused: ${value}`);
944
+ }
945
+ if (!writable.some((root) => isInsideTree(root, target))) {
946
+ return deny(`${toolName} may only write inside this run's worktree. Refused: ${value}`);
947
+ }
948
+ }
949
+ return allow;
950
+ }
951
+ const readPaths = READ_TOOL_PATHS[toolName];
952
+ if (readPaths) {
953
+ for (const key of readPaths) {
954
+ const value = input[key];
955
+ if (typeof value !== "string" || value.length === 0)
956
+ continue;
957
+ const target = isAbsolute2(value) ? value : join(args.worktree, value);
958
+ if (secrets.some((dir) => isInsideTree(dir, target))) {
959
+ return deny(`${toolName} may not read the credential directories. Refused: ${value}`);
960
+ }
961
+ }
962
+ return allow;
963
+ }
964
+ return allow;
965
+ };
966
+ }
967
+ function harmonyMcpServer() {
968
+ const require2 = createRequire2(import.meta.url);
969
+ const cli = join(dirname2(require2.resolve("@gethmy/mcp")), "cli.js");
970
+ return {
971
+ harmony: {
972
+ type: "stdio",
973
+ command: process.execPath,
974
+ args: [cli, "serve"]
975
+ }
976
+ };
977
+ }
978
+ function implementRunContainment(args) {
979
+ assertNoProjectSandboxOverride(args.worktree);
980
+ return {
981
+ sandbox: {
982
+ enabled: true,
983
+ failIfUnavailable: true,
984
+ allowUnsandboxedCommands: false,
985
+ autoAllowBashIfSandboxed: args.readOnly !== true,
986
+ network: {
987
+ allowedDomains: [...IMPLEMENT_ALLOWED_DOMAINS],
988
+ allowLocalBinding: false
989
+ },
990
+ filesystem: {
991
+ allowWrite: [
992
+ args.worktree,
993
+ ...toolchainCacheDirectories(args.worktree)
994
+ ],
995
+ denyRead: credentialDirectories(),
996
+ denyWrite: [
997
+ ...credentialDirectories(),
998
+ ...writeOnlyDenyPaths(),
999
+ ...hostPersistencePaths(),
1000
+ ...gitMetadataDenyPaths(args.worktree)
1001
+ ]
1002
+ }
1003
+ },
1004
+ canUseTool: implementRunToolPolicy(args),
1005
+ gateEveryToolCall: true,
1006
+ settingSources: args.readOnly === true ? [] : ["project"],
1007
+ mcpServers: harmonyMcpServer(),
1008
+ strictMcpConfig: true,
1009
+ stripEnvKeys: secretEnvKeysToStrip(),
1010
+ disallowedTools: [
1011
+ ...args.extraDisallowedTools ?? [],
1012
+ ...credentialToolDeny(),
1013
+ ...credentialWriteToolDeny(args.worktree)
1014
+ ]
1015
+ };
1016
+ }
1017
+ function implementRunContainmentCliArgs(args) {
1018
+ const containment = implementRunContainment(args);
1019
+ return [
1020
+ "--settings",
1021
+ JSON.stringify({ sandbox: containment.sandbox }),
1022
+ "--setting-sources",
1023
+ containment.settingSources.join(","),
1024
+ "--mcp-config",
1025
+ JSON.stringify({ mcpServers: containment.mcpServers }),
1026
+ "--strict-mcp-config",
1027
+ "--disallowedTools",
1028
+ containment.disallowedTools.join(",")
1029
+ ];
1030
+ }
1031
+ var SECRET_ENV_PATTERN, KEEP_ENV_KEYS, ProjectSandboxOverrideError, INERT_PROJECT_SETTING_KEYS, WRITE_TOOL_PATHS, READ_TOOL_PATHS, GIT_NO_HOOKS, IMPLEMENT_ALLOWED_DOMAINS;
1032
+ var init_run_containment = __esm(() => {
1033
+ init_confine_to_repo();
1034
+ init_runner();
1035
+ SECRET_ENV_PATTERN = /(TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|API_?KEY|_PAT$|^PAT_|PRIVATE_KEY|ACCESS_KEY)/i;
1036
+ KEEP_ENV_KEYS = new Set([
1037
+ "ANTHROPIC_API_KEY",
1038
+ "ANTHROPIC_AUTH_TOKEN",
1039
+ "ANTHROPIC_BASE_URL",
1040
+ "CLAUDE_CODE_OAUTH_TOKEN",
1041
+ "SSH_AUTH_SOCK",
1042
+ "GIT_AUTHOR_NAME",
1043
+ "GIT_AUTHOR_EMAIL",
1044
+ "GIT_COMMITTER_NAME",
1045
+ "GIT_COMMITTER_EMAIL",
1046
+ "XAUTHORITY"
1047
+ ]);
1048
+ ProjectSandboxOverrideError = class ProjectSandboxOverrideError extends Error {
1049
+ settingsPath;
1050
+ offendingKeys;
1051
+ constructor(settingsPath, offendingKeys) {
1052
+ super(`Refusing to spawn: ${settingsPath} sets ${offendingKeys.map((k) => `"${k}"`).join(", ")}. ` + "Project settings are loaded so the run can read CLAUDE.md, and that same layer " + "can widen the sandbox (`sandbox.filesystem.allowWrite`) or execute commands " + "outside it (`hooks`, `env`). A run that can write this file could therefore " + "widen its own bounds, so only keys known not to affect execution are accepted. " + "Remove the key, or the run stays refused.");
1053
+ this.settingsPath = settingsPath;
1054
+ this.offendingKeys = offendingKeys;
1055
+ this.name = "ProjectSandboxOverrideError";
1056
+ }
1057
+ };
1058
+ INERT_PROJECT_SETTING_KEYS = new Set([
1059
+ "$schema",
1060
+ "cleanupPeriodDays",
1061
+ "includeCoAuthoredBy",
1062
+ "language",
1063
+ "outputStyle",
1064
+ "spinnerTipsEnabled",
1065
+ "theme",
1066
+ "verbose"
1067
+ ]);
1068
+ WRITE_TOOL_PATHS = {
1069
+ Write: ["file_path"],
1070
+ Edit: ["file_path"],
1071
+ MultiEdit: ["file_path"],
1072
+ NotebookEdit: ["notebook_path"]
1073
+ };
1074
+ READ_TOOL_PATHS = {
1075
+ Read: ["file_path", "path", "notebook_path"],
1076
+ Grep: ["path"],
1077
+ Glob: ["path"]
1078
+ };
1079
+ GIT_NO_HOOKS = [
1080
+ "-c",
1081
+ "core.hooksPath=",
1082
+ "-c",
1083
+ "core.fsmonitor=",
1084
+ "-c",
1085
+ "core.pager=cat"
1086
+ ];
1087
+ IMPLEMENT_ALLOWED_DOMAINS = [
1088
+ "api.anthropic.com",
1089
+ "*.anthropic.com",
1090
+ "registry.npmjs.org",
1091
+ "*.npmjs.org",
1092
+ "github.com",
1093
+ "*.github.com",
1094
+ "*.githubusercontent.com"
1095
+ ];
1096
+ });
1097
+
613
1098
  // src/git-pr.ts
614
1099
  var exports_git_pr = {};
615
1100
  __export(exports_git_pr, {
@@ -652,7 +1137,7 @@ function execFileAsync2() {
652
1137
  }
653
1138
  function detectGitProvider(cwd) {
654
1139
  try {
655
- const url = execFileSync7("git", ["remote", "get-url", "origin"], {
1140
+ const url = execFileSync7("git", [...GIT_NO_HOOKS, "remote", "get-url", "origin"], {
656
1141
  cwd,
657
1142
  encoding: "utf-8"
658
1143
  }).trim();
@@ -950,7 +1435,7 @@ async function mergePullRequest(prUrl, cwd, provider, strategy, deleteBranch) {
950
1435
  }
951
1436
  function getHeadSha(cwd) {
952
1437
  try {
953
- return execFileSync7("git", ["rev-parse", "HEAD"], {
1438
+ return execFileSync7("git", [...GIT_NO_HOOKS, "rev-parse", "HEAD"], {
954
1439
  cwd,
955
1440
  encoding: "utf-8"
956
1441
  }).trim();
@@ -1127,7 +1612,13 @@ function resolvePrUrl(description, branchName, cwd, provider) {
1127
1612
  }
1128
1613
  function remoteBranchExists(branchName, cwd) {
1129
1614
  try {
1130
- execFileSync7("git", ["ls-remote", "--exit-code", "origin", `refs/heads/${branchName}`], { cwd, stdio: "pipe" });
1615
+ execFileSync7("git", [
1616
+ ...GIT_NO_HOOKS,
1617
+ "ls-remote",
1618
+ "--exit-code",
1619
+ "origin",
1620
+ `refs/heads/${branchName}`
1621
+ ], { cwd, stdio: "pipe" });
1131
1622
  return true;
1132
1623
  } catch {
1133
1624
  return false;
@@ -1138,21 +1629,21 @@ function pushBranch(branchName, cwd) {
1138
1629
  log.info(TAG13, `Remote branch ${branchName} exists (rework), force-pushing`);
1139
1630
  let expectedSha = null;
1140
1631
  try {
1141
- execFileSync7("git", ["fetch", "origin", branchName], {
1632
+ execFileSync7("git", [...GIT_NO_HOOKS, "fetch", "origin", branchName], {
1142
1633
  cwd,
1143
1634
  stdio: "pipe"
1144
1635
  });
1145
- expectedSha = execFileSync7("git", ["rev-parse", `refs/remotes/origin/${branchName}`], { cwd, encoding: "utf-8" }).trim();
1636
+ expectedSha = execFileSync7("git", [...GIT_NO_HOOKS, "rev-parse", `refs/remotes/origin/${branchName}`], { cwd, encoding: "utf-8" }).trim();
1146
1637
  } catch (err) {
1147
1638
  log.warn(TAG13, `could not resolve remote tip for ${branchName}, falling back to weak lease: ${err instanceof Error ? err.message : err}`);
1148
1639
  }
1149
1640
  const lease = expectedSha ? `--force-with-lease=refs/heads/${branchName}:${expectedSha}` : "--force-with-lease";
1150
- execFileSync7("git", ["push", lease, "-u", "origin", branchName], {
1641
+ execFileSync7("git", [...GIT_NO_HOOKS, "push", lease, "-u", "origin", branchName], {
1151
1642
  cwd,
1152
1643
  stdio: "pipe"
1153
1644
  });
1154
1645
  } else {
1155
- execFileSync7("git", ["push", "-u", "origin", branchName], {
1646
+ execFileSync7("git", [...GIT_NO_HOOKS, "push", "-u", "origin", branchName], {
1156
1647
  cwd,
1157
1648
  stdio: "pipe"
1158
1649
  });
@@ -1163,7 +1654,7 @@ function renameRemoteBranch(oldRef, newRef, cwd) {
1163
1654
  return;
1164
1655
  let sha;
1165
1656
  try {
1166
- sha = execFileSync7("git", ["rev-parse", "HEAD"], {
1657
+ sha = execFileSync7("git", [...GIT_NO_HOOKS, "rev-parse", "HEAD"], {
1167
1658
  cwd,
1168
1659
  encoding: "utf-8"
1169
1660
  }).trim();
@@ -1171,9 +1662,15 @@ function renameRemoteBranch(oldRef, newRef, cwd) {
1171
1662
  throw new Error(`renameRemoteBranch: could not resolve HEAD: ${err instanceof Error ? err.message : err}`);
1172
1663
  }
1173
1664
  log.info(TAG13, `Renaming remote ${oldRef} → ${newRef}`);
1174
- execFileSync7("git", ["push", "origin", `${sha}:refs/heads/${newRef}`, "--force-with-lease"], { cwd, stdio: "pipe" });
1665
+ execFileSync7("git", [
1666
+ ...GIT_NO_HOOKS,
1667
+ "push",
1668
+ "origin",
1669
+ `${sha}:refs/heads/${newRef}`,
1670
+ "--force-with-lease"
1671
+ ], { cwd, stdio: "pipe" });
1175
1672
  try {
1176
- execFileSync7("git", ["push", "origin", `:refs/heads/${oldRef}`], {
1673
+ execFileSync7("git", [...GIT_NO_HOOKS, "push", "origin", `:refs/heads/${oldRef}`], {
1177
1674
  cwd,
1178
1675
  stdio: "pipe"
1179
1676
  });
@@ -1181,7 +1678,7 @@ function renameRemoteBranch(oldRef, newRef, cwd) {
1181
1678
  log.warn(TAG13, `renameRemoteBranch: could not delete old ref ${oldRef}: ${err instanceof Error ? err.message : err}`);
1182
1679
  }
1183
1680
  try {
1184
- execFileSync7("git", ["branch", "-m", oldRef, newRef], {
1681
+ execFileSync7("git", [...GIT_NO_HOOKS, "branch", "-m", oldRef, newRef], {
1185
1682
  cwd,
1186
1683
  stdio: "pipe"
1187
1684
  });
@@ -1189,7 +1686,7 @@ function renameRemoteBranch(oldRef, newRef, cwd) {
1189
1686
  }
1190
1687
  function getBranchWebUrl(branchName, cwd) {
1191
1688
  try {
1192
- const remoteUrl = execFileSync7("git", ["remote", "get-url", "origin"], {
1689
+ const remoteUrl = execFileSync7("git", [...GIT_NO_HOOKS, "remote", "get-url", "origin"], {
1193
1690
  cwd,
1194
1691
  encoding: "utf-8"
1195
1692
  }).trim();
@@ -1242,7 +1739,12 @@ function createPullRequest(card, branchName, worktreePath, config, provider, exi
1242
1739
  }
1243
1740
  let commitLog = "";
1244
1741
  try {
1245
- commitLog = execFileSync7("git", ["log", "--oneline", `origin/${config.worktree.baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8" }).trim();
1742
+ commitLog = execFileSync7("git", [
1743
+ ...GIT_NO_HOOKS,
1744
+ "log",
1745
+ "--oneline",
1746
+ `origin/${config.worktree.baseBranch}..HEAD`
1747
+ ], { cwd: worktreePath, encoding: "utf-8" }).trim();
1246
1748
  } catch {
1247
1749
  commitLog = "(unable to retrieve commit log)";
1248
1750
  }
@@ -1350,6 +1852,7 @@ var cachedExecFileAsync2, TAG13 = "git-pr", VALID_PR_URL_RE, PR_URL_RE, REVIEWED
1350
1852
  var init_git_pr = __esm(() => {
1351
1853
  init_dist();
1352
1854
  init_log();
1855
+ init_run_containment();
1353
1856
  VALID_PR_URL_RE = /^https:\/\/(github\.com|gitlab\.com|dev\.azure\.com|bitbucket\.org)\//;
1354
1857
  PR_URL_RE = /PR:\s*(https?:\/\/[^\s)]+)/;
1355
1858
  REVIEWED_SHA_RE = /^Reviewed-SHA:\s*([0-9a-f]{7,40})\s*$/im;
@@ -1635,6 +2138,7 @@ class SdkAgentRunner {
1635
2138
  ...this.cfg.settingSources ? { settingSources: this.cfg.settingSources } : {},
1636
2139
  ...this.cfg.mcpServers ? { mcpServers: this.cfg.mcpServers } : {},
1637
2140
  ...this.cfg.strictMcpConfig ? { strictMcpConfig: true } : {},
2141
+ ...this.cfg.sandbox ? { sandbox: this.cfg.sandbox } : {},
1638
2142
  stderr: (data) => {
1639
2143
  this.capturedStderr += data;
1640
2144
  },
@@ -2051,6 +2555,7 @@ class ArtifactCollector {
2051
2555
  }
2052
2556
  // src/ci-failure.ts
2053
2557
  init_log();
2558
+ init_run_containment();
2054
2559
  import { execFile, execFileSync } from "node:child_process";
2055
2560
  import { promisify } from "node:util";
2056
2561
  function createExecFileAsync() {
@@ -2078,7 +2583,7 @@ function isPrOnOrigin(prUrl, cwd) {
2078
2583
  if (!prSlug)
2079
2584
  return false;
2080
2585
  try {
2081
- const remote = execFileSync("git", ["remote", "get-url", "origin"], {
2586
+ const remote = execFileSync("git", [...GIT_NO_HOOKS, "remote", "get-url", "origin"], {
2082
2587
  cwd,
2083
2588
  encoding: "utf-8",
2084
2589
  stdio: ["ignore", "pipe", "pipe"]
@@ -2230,14 +2735,14 @@ var MAX_METRIC_TIMEOUT_MS = 900000;
2230
2735
  var METRIC_SIGINT_GRACE_MS = 2000;
2231
2736
  var METRIC_SIGTERM_GRACE_MS = 3000;
2232
2737
  var STDIO_DRAIN_GRACE_MS = 500;
2233
- function parseParseMode(parse) {
2234
- if (typeof parse !== "string" || parse.length === 0) {
2738
+ function parseParseMode(parse2) {
2739
+ if (typeof parse2 !== "string" || parse2.length === 0) {
2235
2740
  return { kind: "invalid", reason: "`parse` must be a non-empty string" };
2236
2741
  }
2237
- if (parse === "number")
2742
+ if (parse2 === "number")
2238
2743
  return { kind: "number" };
2239
- if (parse.startsWith("json:")) {
2240
- const path = parse.slice("json:".length).trim();
2744
+ if (parse2.startsWith("json:")) {
2745
+ const path = parse2.slice("json:".length).trim();
2241
2746
  if (!path) {
2242
2747
  return { kind: "invalid", reason: '`parse` "json:" is missing a path' };
2243
2748
  }
@@ -2245,7 +2750,7 @@ function parseParseMode(parse) {
2245
2750
  }
2246
2751
  return {
2247
2752
  kind: "invalid",
2248
- reason: `unknown \`parse\` mode "${parse}" (expected "number" or "json:<path>")`
2753
+ reason: `unknown \`parse\` mode "${parse2}" (expected "number" or "json:<path>")`
2249
2754
  };
2250
2755
  }
2251
2756
  function parseMetricValue(mode, stdout) {
@@ -2291,7 +2796,7 @@ function parseMetricValue(mode, stdout) {
2291
2796
  return { ok: true, value: resolved };
2292
2797
  }
2293
2798
  function runMetricCommand(args) {
2294
- return new Promise((resolve, reject) => {
2799
+ return new Promise((resolve2, reject) => {
2295
2800
  let child;
2296
2801
  try {
2297
2802
  child = spawnInGroup(args.command, args.args, {
@@ -2322,7 +2827,7 @@ function runMetricCommand(args) {
2322
2827
  if (failure)
2323
2828
  reject(failure);
2324
2829
  else
2325
- resolve(Buffer.concat(chunks).toString("utf8"));
2830
+ resolve2(Buffer.concat(chunks).toString("utf8"));
2326
2831
  };
2327
2832
  const killTree = (reason) => {
2328
2833
  if (settled || killReason)
@@ -2470,123 +2975,20 @@ function describeRunFailure(err, timeoutMs) {
2470
2975
  function truncate(value, max) {
2471
2976
  return value.length <= max ? value : `${value.slice(0, max)}…[truncated]`;
2472
2977
  }
2473
- // src/confine-to-repo.ts
2474
- import { realpathSync } from "node:fs";
2475
- import { dirname, isAbsolute, parse, resolve, sep } from "node:path";
2476
- var READ_PATH_ARGS = {
2477
- Read: ["file_path", "path", "notebook_path"],
2478
- Grep: ["path"],
2479
- Glob: ["path"]
2480
- };
2481
- var WRITE_PATH_ARGS = {
2482
- Write: ["file_path"],
2483
- Edit: ["file_path"],
2484
- MultiEdit: ["file_path"],
2485
- NotebookEdit: ["notebook_path"]
2486
- };
2487
- var CONFINED_READ_TOOLS = Object.freeze(Object.keys(READ_PATH_ARGS));
2488
- var CONFINED_WRITE_TOOLS = Object.freeze([
2489
- ...Object.keys(READ_PATH_ARGS),
2490
- ...Object.keys(WRITE_PATH_ARGS).filter((t) => t !== "MultiEdit")
2491
- ]);
2492
- var PATTERN_ARG_BY_TOOL = {
2493
- Glob: ["pattern"],
2494
- Grep: ["glob"]
2495
- };
2496
- function isGitMetadata(repoRoot, candidate) {
2497
- const rel = candidate.startsWith(repoRoot) ? candidate.slice(repoRoot.length) : candidate;
2498
- return rel.split(/[\\/]/).some((segment) => segment.toLowerCase() === ".git");
2499
- }
2500
- function patternEscapes(pattern) {
2501
- if (isAbsolute(pattern))
2502
- return true;
2503
- if (/[{}[\]~()!+@]/.test(pattern))
2504
- return true;
2505
- return pattern.split(/[\\/]/).some((segment) => segment === "..");
2506
- }
2507
- function pathArgsFor(mode) {
2508
- return mode === "write" ? { ...READ_PATH_ARGS, ...WRITE_PATH_ARGS } : READ_PATH_ARGS;
2509
- }
2510
- function realPathOrNearest(p) {
2511
- const abs = isAbsolute(p) ? p : resolve(p);
2512
- const { root } = parse(abs);
2513
- let real = root;
2514
- for (const part of abs.slice(root.length).split(sep)) {
2515
- if (part === "" || part === ".")
2516
- continue;
2517
- if (part === "..") {
2518
- real = dirname(real);
2519
- continue;
2520
- }
2521
- const next = real.endsWith(sep) ? real + part : real + sep + part;
2522
- try {
2523
- real = realpathSync(next);
2524
- } catch {
2525
- real = next;
2526
- }
2527
- }
2528
- return real;
2529
- }
2530
- function isInsideTree(root, target) {
2531
- const normalizedRoot = realPathOrNearest(root);
2532
- const normalizedTarget = realPathOrNearest(target);
2533
- if (normalizedTarget === normalizedRoot)
2534
- return true;
2535
- return normalizedTarget.startsWith(normalizedRoot + sep);
2536
- }
2537
- function decideConfinedTool(repoRoot, toolName, input, mode = "read") {
2538
- const pathArgs = pathArgsFor(mode)[toolName];
2539
- if (!pathArgs) {
2540
- return {
2541
- behavior: "deny",
2542
- message: `${toolName} is not available to this run.`
2543
- };
2544
- }
2545
- const verb = toolName in WRITE_PATH_ARGS ? "write" : "read";
2546
- for (const key of PATTERN_ARG_BY_TOOL[toolName] ?? []) {
2547
- const value = input[key];
2548
- if (typeof value !== "string" || value.length === 0)
2549
- continue;
2550
- if (patternEscapes(value)) {
2551
- return {
2552
- behavior: "deny",
2553
- message: `${toolName} patterns must stay inside the repository — no absolute path and no "..". Refused: ${value}`
2554
- };
2555
- }
2556
- }
2557
- for (const key of pathArgs) {
2558
- const value = input[key];
2559
- if (typeof value !== "string" || value.length === 0)
2560
- continue;
2561
- const candidate = isAbsolute(value) ? value : `${repoRoot}${sep}${value}`;
2562
- if (isGitMetadata(repoRoot, candidate)) {
2563
- return {
2564
- behavior: "deny",
2565
- message: `${toolName} may not touch the repository's git metadata. Refused: ${value}`
2566
- };
2567
- }
2568
- if (!isInsideTree(repoRoot, candidate)) {
2569
- return {
2570
- behavior: "deny",
2571
- message: `${toolName} may only ${verb} inside the repository. Refused: ${value}`
2572
- };
2573
- }
2574
- }
2575
- return { behavior: "allow" };
2576
- }
2577
- function confineToRepo(repoRoot, mode = "read") {
2578
- return async (toolName, input) => decideConfinedTool(repoRoot, toolName, input, mode);
2579
- }
2978
+
2979
+ // src/index.ts
2980
+ init_confine_to_repo();
2981
+
2580
2982
  // src/gate-collectors.ts
2581
2983
  init_dist();
2582
2984
  init_log();
2583
2985
 
2584
2986
  // src/oracle-collector.ts
2585
- import { createHash } from "node:crypto";
2987
+ import { createHash as createHash2 } from "node:crypto";
2586
2988
  init_log();
2587
2989
 
2588
2990
  // src/oracle.ts
2589
- import { lstatSync, readFileSync, statSync } from "node:fs";
2991
+ import { lstatSync, readFileSync as readFileSync2, statSync } from "node:fs";
2590
2992
  import {
2591
2993
  chmod,
2592
2994
  lstat,
@@ -2596,13 +2998,13 @@ import {
2596
2998
  rm,
2597
2999
  writeFile
2598
3000
  } from "node:fs/promises";
2599
- import { tmpdir } from "node:os";
2600
- import { dirname as dirname2, isAbsolute as isAbsolute2, join, resolve as resolve2, sep as sep2 } from "node:path";
3001
+ import { tmpdir as tmpdir2 } from "node:os";
3002
+ import { dirname as dirname3, isAbsolute as isAbsolute3, join as join2, resolve as resolve2, sep as sep2 } from "node:path";
2601
3003
  import { StringDecoder } from "node:string_decoder";
2602
3004
  init_log();
2603
3005
  var TAG5 = "oracle";
2604
3006
  async function resolveContained(repoPath, relativePath) {
2605
- if (isAbsolute2(relativePath)) {
3007
+ if (isAbsolute3(relativePath)) {
2606
3008
  throw new Error(`refusing to place an oracle at an absolute path: ${relativePath}`);
2607
3009
  }
2608
3010
  if (relativePath === "" || relativePath === ".") {
@@ -2625,7 +3027,7 @@ async function resolveContained(repoPath, relativePath) {
2625
3027
  }
2626
3028
  async function place(repoPath, oracle) {
2627
3029
  const target = await resolveContained(repoPath, oracle.path);
2628
- await mkdir(dirname2(target), { recursive: true });
3030
+ await mkdir(dirname3(target), { recursive: true });
2629
3031
  await writeFile(target, oracle.content, "utf8");
2630
3032
  }
2631
3033
  async function remove(repoPath, oracle) {
@@ -2733,8 +3135,8 @@ function argvPath(path) {
2733
3135
  }
2734
3136
  async function runHeldOracle(repoPath, oracle, timeoutMs = DEFAULT_METRIC_TIMEOUT_MS) {
2735
3137
  const spec = resolveOracleRunnerSpec(oracle);
2736
- const reportDir = await mkdtemp(join(tmpdir(), reportDirPrefix()));
2737
- const reportPath = join(reportDir, spec.report.file);
3138
+ const reportDir = await mkdtemp(join2(tmpdir2(), reportDirPrefix()));
3139
+ const reportPath = join2(reportDir, spec.report.file);
2738
3140
  try {
2739
3141
  return await spawnHeldOracle(spec, repoPath, oracle, reportPath, timeoutMs);
2740
3142
  } finally {
@@ -2745,7 +3147,7 @@ function reportDirPrefix() {
2745
3147
  return "harmony-oracle-report-";
2746
3148
  }
2747
3149
  function assertUntampered(reportPath) {
2748
- const dir = statSync(dirname2(reportPath));
3150
+ const dir = statSync(dirname3(reportPath));
2749
3151
  if ((dir.mode & 511) !== 448) {
2750
3152
  throw new Error(`the oracle report directory's mode changed to ${(dir.mode & 511).toString(8)} — refusing the report`);
2751
3153
  }
@@ -2808,7 +3210,7 @@ ${output}` : output;
2808
3210
  const captureReport = () => {
2809
3211
  try {
2810
3212
  assertUntampered(reportPath);
2811
- report = spec.report.parse(readFileSync(reportPath, "utf8"));
3213
+ report = spec.report.parse(readFileSync2(reportPath, "utf8"));
2812
3214
  } catch (err) {
2813
3215
  report = null;
2814
3216
  const message = err instanceof Error ? err.message : String(err);
@@ -2926,7 +3328,7 @@ class HeldOracleCollector {
2926
3328
  async runHeld(oracle) {
2927
3329
  const identity = {
2928
3330
  oracleId: oracle.id ?? null,
2929
- contentHash: createHash("sha256").update(oracle.content).digest("hex")
3331
+ contentHash: createHash2("sha256").update(oracle.content).digest("hex")
2930
3332
  };
2931
3333
  await this.deps.place(this.deps.repoPath, oracle);
2932
3334
  try {
@@ -3042,6 +3444,7 @@ import { execFileSync as execFileSync5, spawn as spawn2 } from "node:child_proce
3042
3444
 
3043
3445
  // src/pm.ts
3044
3446
  init_log();
3447
+ init_run_containment();
3045
3448
  import { execFileSync as execFileSync2 } from "node:child_process";
3046
3449
  import { existsSync } from "node:fs";
3047
3450
  var TAG7 = "pm";
@@ -3051,7 +3454,7 @@ function detectPackageManager() {
3051
3454
  return cached;
3052
3455
  let repoRoot;
3053
3456
  try {
3054
- repoRoot = execFileSync2("git", ["rev-parse", "--show-toplevel"], {
3457
+ repoRoot = execFileSync2("git", [...GIT_NO_HOOKS, "rev-parse", "--show-toplevel"], {
3055
3458
  encoding: "utf-8"
3056
3459
  }).trim();
3057
3460
  } catch {
@@ -3094,7 +3497,7 @@ function spawnRunArgs(script, ...extra) {
3094
3497
  // src/project-type.ts
3095
3498
  init_log();
3096
3499
  import { execFileSync as execFileSync3 } from "node:child_process";
3097
- import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "node:fs";
3500
+ import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3 } from "node:fs";
3098
3501
  var TAG8 = "project-type";
3099
3502
  var _cache = new Map;
3100
3503
  function _resetCache() {
@@ -3197,7 +3600,7 @@ var NPM_PLACEHOLDER_TEST = /no test specified/i;
3197
3600
  function hasNodeTestScript(dir) {
3198
3601
  let script;
3199
3602
  try {
3200
- const pkg = JSON.parse(readFileSync2(`${dir}/package.json`, "utf-8"));
3603
+ const pkg = JSON.parse(readFileSync3(`${dir}/package.json`, "utf-8"));
3201
3604
  script = pkg.scripts?.test;
3202
3605
  } catch (err) {
3203
3606
  log.warn(TAG8, `Could not read package.json in ${dir}: ${err instanceof Error ? err.message : err}`);
@@ -3214,7 +3617,7 @@ function hasNodeTestScript(dir) {
3214
3617
  function firstNodeScript(dir, candidates) {
3215
3618
  let scripts;
3216
3619
  try {
3217
- const pkg = JSON.parse(readFileSync2(`${dir}/package.json`, "utf-8"));
3620
+ const pkg = JSON.parse(readFileSync3(`${dir}/package.json`, "utf-8"));
3218
3621
  scripts = pkg.scripts ?? {};
3219
3622
  } catch (err) {
3220
3623
  log.warn(TAG8, `Could not read package.json in ${dir}: ${err instanceof Error ? err.message : err}`);
@@ -3271,6 +3674,7 @@ function resolveXcodeScheme(pt) {
3271
3674
 
3272
3675
  // src/revert-guard.ts
3273
3676
  init_log();
3677
+ init_run_containment();
3274
3678
  import { execFileSync as execFileSync4 } from "node:child_process";
3275
3679
  var TAG9 = "revert-guard";
3276
3680
  var TEST_FILE = /(?:^|\/)__tests__\/|\.(?:test|spec)\.[cm]?[jt]sx?$/;
@@ -3282,7 +3686,7 @@ function filterTestFiles(paths) {
3282
3686
  }
3283
3687
  function refetchBase(worktreePath, baseBranch) {
3284
3688
  try {
3285
- execFileSync4("git", ["fetch", "origin", baseBranch], {
3689
+ execFileSync4("git", [...GIT_NO_HOOKS, "fetch", "origin", baseBranch], {
3286
3690
  cwd: worktreePath,
3287
3691
  stdio: "pipe"
3288
3692
  });
@@ -3292,7 +3696,13 @@ function refetchBase(worktreePath, baseBranch) {
3292
3696
  }
3293
3697
  function listDeletedFilesAgainstBase(worktreePath, baseBranch) {
3294
3698
  try {
3295
- const out = execFileSync4("git", ["diff", "--diff-filter=D", "--name-only", `origin/${baseBranch}...HEAD`], { cwd: worktreePath, encoding: "utf-8" });
3699
+ const out = execFileSync4("git", [
3700
+ ...GIT_NO_HOOKS,
3701
+ "diff",
3702
+ "--diff-filter=D",
3703
+ "--name-only",
3704
+ `origin/${baseBranch}...HEAD`
3705
+ ], { cwd: worktreePath, encoding: "utf-8" });
3296
3706
  return out.split(`
3297
3707
  `).map((l) => l.trim()).filter((l) => l.length > 0);
3298
3708
  } catch (err) {
@@ -3306,6 +3716,7 @@ function findDeletedTestFiles(worktreePath, baseBranch) {
3306
3716
  }
3307
3717
 
3308
3718
  // src/verification.ts
3719
+ init_run_containment();
3309
3720
  var TAG10 = "verification";
3310
3721
  var MAX_OUTPUT_BUFFER2 = 64 * 1024 * 1024;
3311
3722
  async function runVerification(worktreePath, config, workerId) {
@@ -3379,7 +3790,8 @@ function runBuild(worktreePath, timeout) {
3379
3790
  cwd: worktreePath,
3380
3791
  timeout,
3381
3792
  stdio: "pipe",
3382
- maxBuffer: MAX_OUTPUT_BUFFER2
3793
+ maxBuffer: MAX_OUTPUT_BUFFER2,
3794
+ env: containedEnv()
3383
3795
  });
3384
3796
  return [];
3385
3797
  } catch (err) {
@@ -3397,7 +3809,8 @@ function runTests(worktreePath, timeout) {
3397
3809
  cwd: worktreePath,
3398
3810
  timeout,
3399
3811
  stdio: "pipe",
3400
- maxBuffer: MAX_OUTPUT_BUFFER2
3812
+ maxBuffer: MAX_OUTPUT_BUFFER2,
3813
+ env: containedEnv()
3401
3814
  });
3402
3815
  return [];
3403
3816
  } catch (err) {
@@ -3416,7 +3829,8 @@ function runFormatFix(worktreePath, timeout, workerId) {
3416
3829
  cwd: worktreePath,
3417
3830
  timeout,
3418
3831
  stdio: "pipe",
3419
- maxBuffer: MAX_OUTPUT_BUFFER2
3832
+ maxBuffer: MAX_OUTPUT_BUFFER2,
3833
+ env: containedEnv()
3420
3834
  });
3421
3835
  log.info(TAG10, `[worker:${workerId}] Auto-formatted worktree before commit/push`);
3422
3836
  } catch (err) {
@@ -3434,7 +3848,8 @@ function runLint(worktreePath, timeout) {
3434
3848
  cwd: worktreePath,
3435
3849
  timeout,
3436
3850
  stdio: "pipe",
3437
- maxBuffer: MAX_OUTPUT_BUFFER2
3851
+ maxBuffer: MAX_OUTPUT_BUFFER2,
3852
+ env: containedEnv()
3438
3853
  });
3439
3854
  return [];
3440
3855
  } catch (err) {
@@ -3452,7 +3867,8 @@ async function runDeepReview(worktreePath, config, workerId) {
3452
3867
  const [cmd, args] = spawnRunArgs("dev", "--port", String(port));
3453
3868
  devServer = spawn2(cmd, args, {
3454
3869
  cwd: worktreePath,
3455
- stdio: ["ignore", "pipe", "pipe"]
3870
+ stdio: ["ignore", "pipe", "pipe"],
3871
+ env: containedEnv()
3456
3872
  });
3457
3873
  try {
3458
3874
  await waitForDevServer(devServer, 30000);
@@ -3463,7 +3879,7 @@ async function runDeepReview(worktreePath, config, workerId) {
3463
3879
  }
3464
3880
  let diff = "";
3465
3881
  try {
3466
- diff = execFileSync5("git", ["diff", `origin/${config.worktree.baseBranch}..HEAD`], {
3882
+ diff = execFileSync5("git", [...GIT_NO_HOOKS, "diff", `origin/${config.worktree.baseBranch}..HEAD`], {
3467
3883
  cwd: worktreePath,
3468
3884
  encoding: "utf-8",
3469
3885
  timeout: 30000,
@@ -3484,14 +3900,16 @@ async function runDeepReview(worktreePath, config, workerId) {
3484
3900
  "```"
3485
3901
  ].join(`
3486
3902
  `);
3487
- const leanSources = config.claude.leanSettingSources;
3488
3903
  const output = execFileSync5("claude", [
3489
3904
  "--print",
3490
3905
  "--model",
3491
3906
  "sonnet",
3492
3907
  "--max-turns",
3493
3908
  "10",
3494
- ...leanSources ? ["--setting-sources", leanSources] : [],
3909
+ ...implementRunContainmentCliArgs({
3910
+ worktree: worktreePath,
3911
+ readOnly: true
3912
+ }),
3495
3913
  "--",
3496
3914
  reviewPrompt
3497
3915
  ], {
@@ -3499,7 +3917,8 @@ async function runDeepReview(worktreePath, config, workerId) {
3499
3917
  encoding: "utf-8",
3500
3918
  timeout: config.verification.timeout,
3501
3919
  stdio: "pipe",
3502
- maxBuffer: MAX_OUTPUT_BUFFER2
3920
+ maxBuffer: MAX_OUTPUT_BUFFER2,
3921
+ env: containedEnv()
3503
3922
  });
3504
3923
  return parseReviewFindings(output);
3505
3924
  } catch (err) {
@@ -3529,7 +3948,6 @@ function attemptAutoFix(worktreePath, config, errors) {
3529
3948
  "```"
3530
3949
  ].join(`
3531
3950
  `);
3532
- const leanSources = config.claude.leanSettingSources;
3533
3951
  const args = [
3534
3952
  "--print",
3535
3953
  "--model",
@@ -3538,7 +3956,7 @@ function attemptAutoFix(worktreePath, config, errors) {
3538
3956
  "50",
3539
3957
  "--allowedTools",
3540
3958
  "Bash,Read,Write,Edit,Glob,Grep",
3541
- ...leanSources ? ["--setting-sources", leanSources] : [],
3959
+ ...implementRunContainmentCliArgs({ worktree: worktreePath }),
3542
3960
  "--",
3543
3961
  fixPrompt
3544
3962
  ];
@@ -3547,7 +3965,8 @@ function attemptAutoFix(worktreePath, config, errors) {
3547
3965
  cwd: worktreePath,
3548
3966
  timeout: config.verification.timeout,
3549
3967
  stdio: "pipe",
3550
- maxBuffer: MAX_OUTPUT_BUFFER2
3968
+ maxBuffer: MAX_OUTPUT_BUFFER2,
3969
+ env: containedEnv()
3551
3970
  });
3552
3971
  }
3553
3972
  async function reportFindings(client, cardId, result, recovery) {
@@ -3880,6 +4299,7 @@ async function collectGateEvidence(registry, context) {
3880
4299
  }
3881
4300
  // src/git-diff-stat.ts
3882
4301
  init_log();
4302
+ init_run_containment();
3883
4303
  import { execFileSync as execFileSync6 } from "node:child_process";
3884
4304
  var TAG12 = "git-diff-stat";
3885
4305
  var MAX_CHANGED_FILES = 30;
@@ -3960,7 +4380,7 @@ function formatDiffSummary(diff, maxFiles = 100) {
3960
4380
  }
3961
4381
  function captureDiffStat(worktreePath, baseBranch, maxFiles = MAX_CHANGED_FILES) {
3962
4382
  try {
3963
- const raw = execFileSync6("git", ["diff", "--numstat", `${baseBranch}...HEAD`], { cwd: worktreePath, encoding: "utf-8", timeout: 30000 });
4383
+ const raw = execFileSync6("git", [...GIT_NO_HOOKS, "diff", "--numstat", `${baseBranch}...HEAD`], { cwd: worktreePath, encoding: "utf-8", timeout: 30000 });
3964
4384
  return parseNumstat(raw, maxFiles);
3965
4385
  } catch (err) {
3966
4386
  log.warn(TAG12, "git diff --numstat failed", {
@@ -4204,54 +4624,14 @@ async function runInSandbox(args) {
4204
4624
  return { passed: false, output };
4205
4625
  }
4206
4626
  }
4207
- // src/run-sizing.ts
4208
- init_dist();
4209
4627
 
4210
- // src/runner.ts
4211
- init_dist();
4212
- import { getConfigDir } from "@gethmy/mcp/src/config.js";
4213
- var HARMONY_CREDENTIAL_KEYS = [
4214
- "HARMONY_API_KEY",
4215
- "HARMONY_API_URL",
4216
- "HARMONY_WORKSPACE_ID",
4217
- "SUPABASE_ANON_KEY",
4218
- "SUPABASE_SERVICE_ROLE_KEY",
4219
- "SUPABASE_URL"
4220
- ];
4221
- function mayHoldCredentials(role) {
4222
- return role === "author" || role === "reviewer";
4223
- }
4224
- function credentialReadDeny() {
4225
- return `Read(/${getConfigDir()}/**)`;
4226
- }
4227
- function credentialAccessDeny() {
4228
- const dir = `/${getConfigDir()}/**`;
4229
- return [`Read(${dir})`, `Grep(${dir})`, `Glob(${dir})`];
4230
- }
4231
- function buildRoleLaunch(args) {
4232
- const role = normalizeStageRole(args.role);
4233
- const keep = mayHoldCredentials(role);
4234
- const env = {};
4235
- for (const [key, value] of Object.entries(args.parentEnv)) {
4236
- if (value === undefined)
4237
- continue;
4238
- if (!keep && HARMONY_CREDENTIAL_KEYS.includes(key))
4239
- continue;
4240
- env[key] = value;
4241
- }
4242
- return {
4243
- role,
4244
- prompt: args.prompt,
4245
- repoPath: args.repoPath,
4246
- env,
4247
- disallowedTools: keep ? [] : [credentialReadDeny()]
4248
- };
4249
- }
4250
- function envKeysDroppedByLaunch(parentEnv, launch) {
4251
- return Object.keys(parentEnv).filter((key) => parentEnv[key] !== undefined && !Object.hasOwn(launch.env, key));
4252
- }
4628
+ // src/index.ts
4629
+ init_run_containment();
4253
4630
 
4254
4631
  // src/run-sizing.ts
4632
+ init_dist();
4633
+ init_confine_to_repo();
4634
+ init_runner();
4255
4635
  var SIZING_MODEL = "haiku";
4256
4636
  var SIZING_MAX_TURNS = 25;
4257
4637
  var SIZING_MAX_BUDGET_USD = 0.75;
@@ -4425,6 +4805,10 @@ async function sizeRun(deps) {
4425
4805
  clearTimeout(timer);
4426
4806
  }
4427
4807
  }
4808
+
4809
+ // src/index.ts
4810
+ init_runner();
4811
+
4428
4812
  // src/stage-run.ts
4429
4813
  async function runStage(request, deps) {
4430
4814
  const events = [];
@@ -4454,6 +4838,7 @@ init_log();
4454
4838
  import { execFileSync as execFileSync8, execSync } from "node:child_process";
4455
4839
  import { existsSync as existsSync3, readdirSync as readdirSync2, rmSync } from "node:fs";
4456
4840
  import { resolve as resolve3 } from "node:path";
4841
+ init_run_containment();
4457
4842
  var TAG16 = "worktree";
4458
4843
 
4459
4844
  class WorktreeBaseError extends Error {
@@ -4462,7 +4847,7 @@ class WorktreeBaseError extends Error {
4462
4847
  this.name = "WorktreeBaseError";
4463
4848
  }
4464
4849
  }
4465
- function fetchBaseBranch(repoRoot, baseBranch, attempts = 3, fetchImpl = (root, branch) => execFileSync8("git", ["fetch", "origin", branch], {
4850
+ function fetchBaseBranch(repoRoot, baseBranch, attempts = 3, fetchImpl = (root, branch) => execFileSync8("git", [...GIT_NO_HOOKS, "fetch", "origin", branch], {
4466
4851
  cwd: root,
4467
4852
  stdio: "pipe"
4468
4853
  })) {
@@ -4515,7 +4900,14 @@ function resolveContinuationTarget(branchName, continueRequested, failedBranchPr
4515
4900
  reason: "fresh"
4516
4901
  };
4517
4902
  }
4518
- function fetchExistingBranch(repoRoot, branchName, attempts = 3, lsRemoteImpl = (root, branch) => execFileSync8("git", ["ls-remote", "--exit-code", "origin", `refs/heads/${branch}`], { cwd: root, stdio: "pipe" }), fetchImpl = (root, branch) => execFileSync8("git", [
4903
+ function fetchExistingBranch(repoRoot, branchName, attempts = 3, lsRemoteImpl = (root, branch) => execFileSync8("git", [
4904
+ ...GIT_NO_HOOKS,
4905
+ "ls-remote",
4906
+ "--exit-code",
4907
+ "origin",
4908
+ `refs/heads/${branch}`
4909
+ ], { cwd: root, stdio: "pipe" }), fetchImpl = (root, branch) => execFileSync8("git", [
4910
+ ...GIT_NO_HOOKS,
4519
4911
  "fetch",
4520
4912
  "origin",
4521
4913
  `+refs/heads/${branch}:refs/remotes/origin/${branch}`
@@ -4549,7 +4941,7 @@ function fetchExistingBranch(repoRoot, branchName, attempts = 3, lsRemoteImpl =
4549
4941
  }
4550
4942
  function readWorktreeHead(worktreePath) {
4551
4943
  try {
4552
- return execFileSync8("git", ["rev-parse", "HEAD"], {
4944
+ return execFileSync8("git", [...GIT_NO_HOOKS, "rev-parse", "HEAD"], {
4553
4945
  cwd: worktreePath,
4554
4946
  encoding: "utf-8"
4555
4947
  }).trim();
@@ -4558,7 +4950,7 @@ function readWorktreeHead(worktreePath) {
4558
4950
  }
4559
4951
  }
4560
4952
  function createWorktree(basePath, baseBranch, branchName, opts = {}) {
4561
- const repoRoot = execFileSync8("git", ["rev-parse", "--show-toplevel"], {
4953
+ const repoRoot = execFileSync8("git", [...GIT_NO_HOOKS, "rev-parse", "--show-toplevel"], {
4562
4954
  encoding: "utf-8"
4563
4955
  }).trim();
4564
4956
  const worktreeDir = resolve3(repoRoot, basePath, branchName);
@@ -4567,7 +4959,7 @@ function createWorktree(basePath, baseBranch, branchName, opts = {}) {
4567
4959
  cleanupWorktree(worktreeDir, branchName);
4568
4960
  }
4569
4961
  try {
4570
- execFileSync8("git", ["worktree", "prune", "--expire=now"], {
4962
+ execFileSync8("git", [...GIT_NO_HOOKS, "worktree", "prune", "--expire=now"], {
4571
4963
  cwd: repoRoot,
4572
4964
  stdio: "pipe"
4573
4965
  });
@@ -4576,37 +4968,54 @@ function createWorktree(basePath, baseBranch, branchName, opts = {}) {
4576
4968
  const startRef = resolveWorktreeStartRef(baseBranch, branchName, opts.continueExisting ?? false, () => opts.branchExistsOnOrigin ?? fetchExistingBranch(repoRoot, branchName));
4577
4969
  log.info(TAG16, `Creating worktree: ${worktreeDir} (branch: ${branchName}, base: ${startRef})`);
4578
4970
  try {
4579
- execFileSync8("git", ["worktree", "add", "-B", branchName, worktreeDir, startRef], { cwd: repoRoot, stdio: "pipe" });
4971
+ execFileSync8("git", [
4972
+ ...GIT_NO_HOOKS,
4973
+ "worktree",
4974
+ "add",
4975
+ "-B",
4976
+ branchName,
4977
+ worktreeDir,
4978
+ startRef
4979
+ ], { cwd: repoRoot, stdio: "pipe" });
4580
4980
  } catch (err) {
4581
4981
  const msg = err instanceof Error ? err.message : String(err);
4582
4982
  log.warn(TAG16, `worktree add failed, attempting forced recovery: ${msg}`);
4583
4983
  removeWorktreeHoldingBranch(repoRoot, branchName, worktreeDir);
4584
4984
  try {
4585
- execFileSync8("git", ["worktree", "remove", worktreeDir, "--force"], {
4985
+ execFileSync8("git", [...GIT_NO_HOOKS, "worktree", "remove", worktreeDir, "--force"], {
4586
4986
  cwd: repoRoot,
4587
4987
  stdio: "pipe"
4588
4988
  });
4589
4989
  } catch {}
4590
4990
  try {
4591
- execFileSync8("git", ["worktree", "prune", "--expire=now"], {
4991
+ execFileSync8("git", [...GIT_NO_HOOKS, "worktree", "prune", "--expire=now"], {
4592
4992
  cwd: repoRoot,
4593
4993
  stdio: "pipe"
4594
4994
  });
4595
4995
  } catch {}
4596
4996
  try {
4597
- execFileSync8("git", ["branch", "-D", branchName], {
4997
+ execFileSync8("git", [...GIT_NO_HOOKS, "branch", "-D", branchName], {
4598
4998
  cwd: repoRoot,
4599
4999
  stdio: "pipe"
4600
5000
  });
4601
5001
  } catch {}
4602
- execFileSync8("git", ["worktree", "add", "-B", branchName, worktreeDir, startRef], { cwd: repoRoot, stdio: "pipe" });
5002
+ execFileSync8("git", [
5003
+ ...GIT_NO_HOOKS,
5004
+ "worktree",
5005
+ "add",
5006
+ "-B",
5007
+ branchName,
5008
+ worktreeDir,
5009
+ startRef
5010
+ ], { cwd: repoRoot, stdio: "pipe" });
4603
5011
  }
4604
5012
  log.info(TAG16, "Installing dependencies in worktree...");
4605
5013
  try {
4606
- execSync(installCommand(), {
5014
+ execSync(installCommand(true), {
4607
5015
  cwd: worktreeDir,
4608
5016
  stdio: "pipe",
4609
- timeout: 60000
5017
+ timeout: 60000,
5018
+ env: containedEnv()
4610
5019
  });
4611
5020
  } catch {
4612
5021
  log.warn(TAG16, "Install failed (may be fine if deps are hoisted)");
@@ -4625,7 +5034,7 @@ function containsForeignWorktrees(dir) {
4625
5034
  return children.some((child) => existsSync3(resolve3(dir, child, ".git")));
4626
5035
  }
4627
5036
  function cleanupWorktree(worktreePath, branchName) {
4628
- const repoRoot = execFileSync8("git", ["rev-parse", "--show-toplevel"], {
5037
+ const repoRoot = execFileSync8("git", [...GIT_NO_HOOKS, "rev-parse", "--show-toplevel"], {
4629
5038
  encoding: "utf-8"
4630
5039
  }).trim();
4631
5040
  if (existsSync3(worktreePath)) {
@@ -4633,7 +5042,7 @@ function cleanupWorktree(worktreePath, branchName) {
4633
5042
  throw new Error(`Refusing to remove ${worktreePath}: it is not a git worktree itself, ` + `but git worktrees live directly beneath it. Removing it would ` + `destroy them — pass the individual worktree paths instead (#928).`);
4634
5043
  }
4635
5044
  try {
4636
- execFileSync8("git", ["worktree", "remove", worktreePath, "--force"], {
5045
+ execFileSync8("git", [...GIT_NO_HOOKS, "worktree", "remove", worktreePath, "--force"], {
4637
5046
  cwd: repoRoot,
4638
5047
  stdio: "pipe"
4639
5048
  });
@@ -4644,7 +5053,7 @@ function cleanupWorktree(worktreePath, branchName) {
4644
5053
  rmSync(worktreePath, { recursive: true, force: true });
4645
5054
  }
4646
5055
  try {
4647
- execFileSync8("git", ["worktree", "prune", "--expire=now"], {
5056
+ execFileSync8("git", [...GIT_NO_HOOKS, "worktree", "prune", "--expire=now"], {
4648
5057
  cwd: repoRoot,
4649
5058
  stdio: "pipe"
4650
5059
  });
@@ -4652,7 +5061,7 @@ function cleanupWorktree(worktreePath, branchName) {
4652
5061
  }
4653
5062
  } else {
4654
5063
  try {
4655
- execFileSync8("git", ["worktree", "prune", "--expire=now"], {
5064
+ execFileSync8("git", [...GIT_NO_HOOKS, "worktree", "prune", "--expire=now"], {
4656
5065
  cwd: repoRoot,
4657
5066
  stdio: "pipe"
4658
5067
  });
@@ -4660,7 +5069,7 @@ function cleanupWorktree(worktreePath, branchName) {
4660
5069
  }
4661
5070
  if (branchName) {
4662
5071
  try {
4663
- execFileSync8("git", ["branch", "-D", branchName], {
5072
+ execFileSync8("git", [...GIT_NO_HOOKS, "branch", "-D", branchName], {
4664
5073
  cwd: repoRoot,
4665
5074
  stdio: "pipe"
4666
5075
  });
@@ -4670,7 +5079,7 @@ function cleanupWorktree(worktreePath, branchName) {
4670
5079
  function removeWorktreeHoldingBranch(repoRoot, branchName, exceptDir) {
4671
5080
  let listing;
4672
5081
  try {
4673
- listing = execFileSync8("git", ["worktree", "list", "--porcelain"], {
5082
+ listing = execFileSync8("git", [...GIT_NO_HOOKS, "worktree", "list", "--porcelain"], {
4674
5083
  cwd: repoRoot,
4675
5084
  encoding: "utf-8",
4676
5085
  stdio: ["ignore", "pipe", "pipe"]
@@ -4698,7 +5107,7 @@ function removeWorktreeHoldingBranch(repoRoot, branchName, exceptDir) {
4698
5107
  if (exceptDir && resolve3(holderPath) === resolve3(exceptDir))
4699
5108
  return null;
4700
5109
  try {
4701
- execFileSync8("git", ["worktree", "remove", holderPath, "--force"], {
5110
+ execFileSync8("git", [...GIT_NO_HOOKS, "worktree", "remove", holderPath, "--force"], {
4702
5111
  cwd: repoRoot,
4703
5112
  stdio: "pipe"
4704
5113
  });
@@ -4708,7 +5117,7 @@ function removeWorktreeHoldingBranch(repoRoot, branchName, exceptDir) {
4708
5117
  return null;
4709
5118
  }
4710
5119
  try {
4711
- execFileSync8("git", ["worktree", "prune", "--expire=now"], {
5120
+ execFileSync8("git", [...GIT_NO_HOOKS, "worktree", "prune", "--expire=now"], {
4712
5121
  cwd: repoRoot,
4713
5122
  stdio: "pipe"
4714
5123
  });
@@ -4716,13 +5125,19 @@ function removeWorktreeHoldingBranch(repoRoot, branchName, exceptDir) {
4716
5125
  return holderPath;
4717
5126
  }
4718
5127
  function resolveRepoRoot() {
4719
- return execFileSync8("git", ["rev-parse", "--show-toplevel"], {
5128
+ return execFileSync8("git", [...GIT_NO_HOOKS, "rev-parse", "--show-toplevel"], {
4720
5129
  encoding: "utf-8"
4721
5130
  }).trim();
4722
5131
  }
4723
5132
  function localBranchExists(branchName, repoRoot) {
4724
5133
  try {
4725
- execFileSync8("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`], { cwd: repoRoot, stdio: "ignore" });
5134
+ execFileSync8("git", [
5135
+ ...GIT_NO_HOOKS,
5136
+ "show-ref",
5137
+ "--verify",
5138
+ "--quiet",
5139
+ `refs/heads/${branchName}`
5140
+ ], { cwd: repoRoot, stdio: "ignore" });
4726
5141
  return true;
4727
5142
  } catch {
4728
5143
  return false;
@@ -4732,7 +5147,7 @@ function branchAheadOfItsRemote(branchName, repoRoot = resolveRepoRoot()) {
4732
5147
  if (!localBranchExists(branchName, repoRoot))
4733
5148
  return false;
4734
5149
  try {
4735
- const out = execFileSync8("git", ["rev-list", branchName, "--not", "--remotes=origin"], { cwd: repoRoot, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).trim();
5150
+ const out = execFileSync8("git", [...GIT_NO_HOOKS, "rev-list", branchName, "--not", "--remotes=origin"], { cwd: repoRoot, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).trim();
4736
5151
  return out.length > 0;
4737
5152
  } catch {
4738
5153
  return false;
@@ -4785,11 +5200,13 @@ function makeBranchName(shortId, title, prefix = "agent-attempts/") {
4785
5200
  // src/index.ts
4786
5201
  var MOTOR_NAME = "harmony-harness";
4787
5202
  export {
5203
+ writeOnlyDenyPaths,
4788
5204
  waitForDevServer,
4789
5205
  validateGitProviderCli,
4790
5206
  upsertReviewedSha,
4791
5207
  upsertCiReviewRequestedSha,
4792
5208
  updateExistingPr,
5209
+ toolchainCacheDirectories,
4793
5210
  testCommand,
4794
5211
  terminateGroup,
4795
5212
  teardownWorktree,
@@ -4802,6 +5219,7 @@ export {
4802
5219
  sizingEventSource,
4803
5220
  sizeRun,
4804
5221
  signalGroup,
5222
+ secretEnvKeysToStrip,
4805
5223
  sanitizeCiText,
4806
5224
  sandboxRunArgs,
4807
5225
  sandboxAvailable,
@@ -4856,7 +5274,13 @@ export {
4856
5274
  isPrOnOrigin,
4857
5275
  isInsideTree,
4858
5276
  installCommand,
5277
+ implementRunToolPolicy,
5278
+ implementRunContainmentCliArgs,
5279
+ implementRunContainment,
5280
+ hostPersistencePaths,
5281
+ harmonyMcpServer,
4859
5282
  gradeOracleRed,
5283
+ gitMetadataDenyPaths,
4860
5284
  getPrStatus,
4861
5285
  getPrFailedChecks,
4862
5286
  getHeadSha,
@@ -4886,10 +5310,14 @@ export {
4886
5310
  deriveCiStatus,
4887
5311
  decidePrBranch,
4888
5312
  decideConfinedTool,
5313
+ credentialWriteToolDeny,
5314
+ credentialToolDeny,
5315
+ credentialDirectories,
4889
5316
  credentialAccessDeny,
4890
5317
  createWorktree,
4891
5318
  createPullRequest,
4892
5319
  cooldownMsFor,
5320
+ containedEnv,
4893
5321
  confineToRepo,
4894
5322
  collectSizingOutput,
4895
5323
  collectGateEvidence,
@@ -4908,6 +5336,7 @@ export {
4908
5336
  buildCommand,
4909
5337
  branchAheadOfItsRemote,
4910
5338
  attemptAutoFix,
5339
+ assertNoProjectSandboxOverride,
4911
5340
  _resetCache,
4912
5341
  __resetSandboxProbe,
4913
5342
  WorktreeBaseError,
@@ -4916,6 +5345,7 @@ export {
4916
5345
  SDK_ALLOWED_TOOLS,
4917
5346
  SANDBOX_MOUNT,
4918
5347
  ReviewPassedCollector,
5348
+ ProjectSandboxOverrideError,
4919
5349
  OracleRedCollector,
4920
5350
  OracleCollector,
4921
5351
  ORACLE_RUNNER_HINTS,
@@ -4925,8 +5355,10 @@ export {
4925
5355
  MAX_IMPLEMENT_MODEL,
4926
5356
  MAX_CHANGED_FILES,
4927
5357
  JUDGE_MODEL,
5358
+ IMPLEMENT_ALLOWED_DOMAINS,
4928
5359
  HarmonyClient,
4929
5360
  HARMONY_CREDENTIAL_KEYS,
5361
+ GIT_NO_HOOKS,
4930
5362
  GATE_CONFIG_ERROR_MARK,
4931
5363
  GATE_CONFIG_ERROR_KEY,
4932
5364
  DevServerReadinessError,