@gethmy/harness 1.3.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
@@ -556,6 +556,13 @@ var init_playbookStage = __esm(() => {
556
556
 
557
557
  // ../harmony-shared/dist/projectTemplates.js
558
558
  var init_projectTemplates = () => {};
559
+
560
+ // ../harmony-shared/dist/realtimeChannel.js
561
+ var inFlightDetach;
562
+ var init_realtimeChannel = __esm(() => {
563
+ init_logger();
564
+ inFlightDetach = new WeakMap;
565
+ });
559
566
  // ../harmony-shared/dist/reviewTools.js
560
567
  var REVIEW_DISALLOWED_TOOLS;
561
568
  var init_reviewTools = __esm(() => {
@@ -577,7 +584,6 @@ var init_stageHandoff = __esm(() => {
577
584
 
578
585
  // ../harmony-shared/dist/types.js
579
586
  var init_types = () => {};
580
-
581
587
  // ../harmony-shared/dist/index.js
582
588
  var init_dist = __esm(() => {
583
589
  init_agentStaleness();
@@ -597,11 +603,498 @@ var init_dist = __esm(() => {
597
603
  init_playbookCatalog();
598
604
  init_playbookStage();
599
605
  init_projectTemplates();
606
+ init_realtimeChannel();
600
607
  init_reviewTools();
601
608
  init_stageHandoff();
602
609
  init_types();
603
610
  });
604
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
+
605
1098
  // src/git-pr.ts
606
1099
  var exports_git_pr = {};
607
1100
  __export(exports_git_pr, {
@@ -644,7 +1137,7 @@ function execFileAsync2() {
644
1137
  }
645
1138
  function detectGitProvider(cwd) {
646
1139
  try {
647
- const url = execFileSync7("git", ["remote", "get-url", "origin"], {
1140
+ const url = execFileSync7("git", [...GIT_NO_HOOKS, "remote", "get-url", "origin"], {
648
1141
  cwd,
649
1142
  encoding: "utf-8"
650
1143
  }).trim();
@@ -942,7 +1435,7 @@ async function mergePullRequest(prUrl, cwd, provider, strategy, deleteBranch) {
942
1435
  }
943
1436
  function getHeadSha(cwd) {
944
1437
  try {
945
- return execFileSync7("git", ["rev-parse", "HEAD"], {
1438
+ return execFileSync7("git", [...GIT_NO_HOOKS, "rev-parse", "HEAD"], {
946
1439
  cwd,
947
1440
  encoding: "utf-8"
948
1441
  }).trim();
@@ -1119,7 +1612,13 @@ function resolvePrUrl(description, branchName, cwd, provider) {
1119
1612
  }
1120
1613
  function remoteBranchExists(branchName, cwd) {
1121
1614
  try {
1122
- 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" });
1123
1622
  return true;
1124
1623
  } catch {
1125
1624
  return false;
@@ -1130,21 +1629,21 @@ function pushBranch(branchName, cwd) {
1130
1629
  log.info(TAG13, `Remote branch ${branchName} exists (rework), force-pushing`);
1131
1630
  let expectedSha = null;
1132
1631
  try {
1133
- execFileSync7("git", ["fetch", "origin", branchName], {
1632
+ execFileSync7("git", [...GIT_NO_HOOKS, "fetch", "origin", branchName], {
1134
1633
  cwd,
1135
1634
  stdio: "pipe"
1136
1635
  });
1137
- 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();
1138
1637
  } catch (err) {
1139
1638
  log.warn(TAG13, `could not resolve remote tip for ${branchName}, falling back to weak lease: ${err instanceof Error ? err.message : err}`);
1140
1639
  }
1141
1640
  const lease = expectedSha ? `--force-with-lease=refs/heads/${branchName}:${expectedSha}` : "--force-with-lease";
1142
- execFileSync7("git", ["push", lease, "-u", "origin", branchName], {
1641
+ execFileSync7("git", [...GIT_NO_HOOKS, "push", lease, "-u", "origin", branchName], {
1143
1642
  cwd,
1144
1643
  stdio: "pipe"
1145
1644
  });
1146
1645
  } else {
1147
- execFileSync7("git", ["push", "-u", "origin", branchName], {
1646
+ execFileSync7("git", [...GIT_NO_HOOKS, "push", "-u", "origin", branchName], {
1148
1647
  cwd,
1149
1648
  stdio: "pipe"
1150
1649
  });
@@ -1155,7 +1654,7 @@ function renameRemoteBranch(oldRef, newRef, cwd) {
1155
1654
  return;
1156
1655
  let sha;
1157
1656
  try {
1158
- sha = execFileSync7("git", ["rev-parse", "HEAD"], {
1657
+ sha = execFileSync7("git", [...GIT_NO_HOOKS, "rev-parse", "HEAD"], {
1159
1658
  cwd,
1160
1659
  encoding: "utf-8"
1161
1660
  }).trim();
@@ -1163,9 +1662,15 @@ function renameRemoteBranch(oldRef, newRef, cwd) {
1163
1662
  throw new Error(`renameRemoteBranch: could not resolve HEAD: ${err instanceof Error ? err.message : err}`);
1164
1663
  }
1165
1664
  log.info(TAG13, `Renaming remote ${oldRef} → ${newRef}`);
1166
- 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" });
1167
1672
  try {
1168
- execFileSync7("git", ["push", "origin", `:refs/heads/${oldRef}`], {
1673
+ execFileSync7("git", [...GIT_NO_HOOKS, "push", "origin", `:refs/heads/${oldRef}`], {
1169
1674
  cwd,
1170
1675
  stdio: "pipe"
1171
1676
  });
@@ -1173,7 +1678,7 @@ function renameRemoteBranch(oldRef, newRef, cwd) {
1173
1678
  log.warn(TAG13, `renameRemoteBranch: could not delete old ref ${oldRef}: ${err instanceof Error ? err.message : err}`);
1174
1679
  }
1175
1680
  try {
1176
- execFileSync7("git", ["branch", "-m", oldRef, newRef], {
1681
+ execFileSync7("git", [...GIT_NO_HOOKS, "branch", "-m", oldRef, newRef], {
1177
1682
  cwd,
1178
1683
  stdio: "pipe"
1179
1684
  });
@@ -1181,7 +1686,7 @@ function renameRemoteBranch(oldRef, newRef, cwd) {
1181
1686
  }
1182
1687
  function getBranchWebUrl(branchName, cwd) {
1183
1688
  try {
1184
- const remoteUrl = execFileSync7("git", ["remote", "get-url", "origin"], {
1689
+ const remoteUrl = execFileSync7("git", [...GIT_NO_HOOKS, "remote", "get-url", "origin"], {
1185
1690
  cwd,
1186
1691
  encoding: "utf-8"
1187
1692
  }).trim();
@@ -1234,7 +1739,12 @@ function createPullRequest(card, branchName, worktreePath, config, provider, exi
1234
1739
  }
1235
1740
  let commitLog = "";
1236
1741
  try {
1237
- 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();
1238
1748
  } catch {
1239
1749
  commitLog = "(unable to retrieve commit log)";
1240
1750
  }
@@ -1342,6 +1852,7 @@ var cachedExecFileAsync2, TAG13 = "git-pr", VALID_PR_URL_RE, PR_URL_RE, REVIEWED
1342
1852
  var init_git_pr = __esm(() => {
1343
1853
  init_dist();
1344
1854
  init_log();
1855
+ init_run_containment();
1345
1856
  VALID_PR_URL_RE = /^https:\/\/(github\.com|gitlab\.com|dev\.azure\.com|bitbucket\.org)\//;
1346
1857
  PR_URL_RE = /PR:\s*(https?:\/\/[^\s)]+)/;
1347
1858
  REVIEWED_SHA_RE = /^Reviewed-SHA:\s*([0-9a-f]{7,40})\s*$/im;
@@ -1627,6 +2138,7 @@ class SdkAgentRunner {
1627
2138
  ...this.cfg.settingSources ? { settingSources: this.cfg.settingSources } : {},
1628
2139
  ...this.cfg.mcpServers ? { mcpServers: this.cfg.mcpServers } : {},
1629
2140
  ...this.cfg.strictMcpConfig ? { strictMcpConfig: true } : {},
2141
+ ...this.cfg.sandbox ? { sandbox: this.cfg.sandbox } : {},
1630
2142
  stderr: (data) => {
1631
2143
  this.capturedStderr += data;
1632
2144
  },
@@ -2043,6 +2555,7 @@ class ArtifactCollector {
2043
2555
  }
2044
2556
  // src/ci-failure.ts
2045
2557
  init_log();
2558
+ init_run_containment();
2046
2559
  import { execFile, execFileSync } from "node:child_process";
2047
2560
  import { promisify } from "node:util";
2048
2561
  function createExecFileAsync() {
@@ -2070,7 +2583,7 @@ function isPrOnOrigin(prUrl, cwd) {
2070
2583
  if (!prSlug)
2071
2584
  return false;
2072
2585
  try {
2073
- const remote = execFileSync("git", ["remote", "get-url", "origin"], {
2586
+ const remote = execFileSync("git", [...GIT_NO_HOOKS, "remote", "get-url", "origin"], {
2074
2587
  cwd,
2075
2588
  encoding: "utf-8",
2076
2589
  stdio: ["ignore", "pipe", "pipe"]
@@ -2222,14 +2735,14 @@ var MAX_METRIC_TIMEOUT_MS = 900000;
2222
2735
  var METRIC_SIGINT_GRACE_MS = 2000;
2223
2736
  var METRIC_SIGTERM_GRACE_MS = 3000;
2224
2737
  var STDIO_DRAIN_GRACE_MS = 500;
2225
- function parseParseMode(parse) {
2226
- if (typeof parse !== "string" || parse.length === 0) {
2738
+ function parseParseMode(parse2) {
2739
+ if (typeof parse2 !== "string" || parse2.length === 0) {
2227
2740
  return { kind: "invalid", reason: "`parse` must be a non-empty string" };
2228
2741
  }
2229
- if (parse === "number")
2742
+ if (parse2 === "number")
2230
2743
  return { kind: "number" };
2231
- if (parse.startsWith("json:")) {
2232
- const path = parse.slice("json:".length).trim();
2744
+ if (parse2.startsWith("json:")) {
2745
+ const path = parse2.slice("json:".length).trim();
2233
2746
  if (!path) {
2234
2747
  return { kind: "invalid", reason: '`parse` "json:" is missing a path' };
2235
2748
  }
@@ -2237,7 +2750,7 @@ function parseParseMode(parse) {
2237
2750
  }
2238
2751
  return {
2239
2752
  kind: "invalid",
2240
- reason: `unknown \`parse\` mode "${parse}" (expected "number" or "json:<path>")`
2753
+ reason: `unknown \`parse\` mode "${parse2}" (expected "number" or "json:<path>")`
2241
2754
  };
2242
2755
  }
2243
2756
  function parseMetricValue(mode, stdout) {
@@ -2283,7 +2796,7 @@ function parseMetricValue(mode, stdout) {
2283
2796
  return { ok: true, value: resolved };
2284
2797
  }
2285
2798
  function runMetricCommand(args) {
2286
- return new Promise((resolve, reject) => {
2799
+ return new Promise((resolve2, reject) => {
2287
2800
  let child;
2288
2801
  try {
2289
2802
  child = spawnInGroup(args.command, args.args, {
@@ -2314,7 +2827,7 @@ function runMetricCommand(args) {
2314
2827
  if (failure)
2315
2828
  reject(failure);
2316
2829
  else
2317
- resolve(Buffer.concat(chunks).toString("utf8"));
2830
+ resolve2(Buffer.concat(chunks).toString("utf8"));
2318
2831
  };
2319
2832
  const killTree = (reason) => {
2320
2833
  if (settled || killReason)
@@ -2462,123 +2975,20 @@ function describeRunFailure(err, timeoutMs) {
2462
2975
  function truncate(value, max) {
2463
2976
  return value.length <= max ? value : `${value.slice(0, max)}…[truncated]`;
2464
2977
  }
2465
- // src/confine-to-repo.ts
2466
- import { realpathSync } from "node:fs";
2467
- import { dirname, isAbsolute, parse, resolve, sep } from "node:path";
2468
- var READ_PATH_ARGS = {
2469
- Read: ["file_path", "path", "notebook_path"],
2470
- Grep: ["path"],
2471
- Glob: ["path"]
2472
- };
2473
- var WRITE_PATH_ARGS = {
2474
- Write: ["file_path"],
2475
- Edit: ["file_path"],
2476
- MultiEdit: ["file_path"],
2477
- NotebookEdit: ["notebook_path"]
2478
- };
2479
- var CONFINED_READ_TOOLS = Object.freeze(Object.keys(READ_PATH_ARGS));
2480
- var CONFINED_WRITE_TOOLS = Object.freeze([
2481
- ...Object.keys(READ_PATH_ARGS),
2482
- ...Object.keys(WRITE_PATH_ARGS).filter((t) => t !== "MultiEdit")
2483
- ]);
2484
- var PATTERN_ARG_BY_TOOL = {
2485
- Glob: ["pattern"],
2486
- Grep: ["glob"]
2487
- };
2488
- function isGitMetadata(repoRoot, candidate) {
2489
- const rel = candidate.startsWith(repoRoot) ? candidate.slice(repoRoot.length) : candidate;
2490
- return rel.split(/[\\/]/).some((segment) => segment.toLowerCase() === ".git");
2491
- }
2492
- function patternEscapes(pattern) {
2493
- if (isAbsolute(pattern))
2494
- return true;
2495
- if (/[{}[\]~()!+@]/.test(pattern))
2496
- return true;
2497
- return pattern.split(/[\\/]/).some((segment) => segment === "..");
2498
- }
2499
- function pathArgsFor(mode) {
2500
- return mode === "write" ? { ...READ_PATH_ARGS, ...WRITE_PATH_ARGS } : READ_PATH_ARGS;
2501
- }
2502
- function realPathOrNearest(p) {
2503
- const abs = isAbsolute(p) ? p : resolve(p);
2504
- const { root } = parse(abs);
2505
- let real = root;
2506
- for (const part of abs.slice(root.length).split(sep)) {
2507
- if (part === "" || part === ".")
2508
- continue;
2509
- if (part === "..") {
2510
- real = dirname(real);
2511
- continue;
2512
- }
2513
- const next = real.endsWith(sep) ? real + part : real + sep + part;
2514
- try {
2515
- real = realpathSync(next);
2516
- } catch {
2517
- real = next;
2518
- }
2519
- }
2520
- return real;
2521
- }
2522
- function isInsideTree(root, target) {
2523
- const normalizedRoot = realPathOrNearest(root);
2524
- const normalizedTarget = realPathOrNearest(target);
2525
- if (normalizedTarget === normalizedRoot)
2526
- return true;
2527
- return normalizedTarget.startsWith(normalizedRoot + sep);
2528
- }
2529
- function decideConfinedTool(repoRoot, toolName, input, mode = "read") {
2530
- const pathArgs = pathArgsFor(mode)[toolName];
2531
- if (!pathArgs) {
2532
- return {
2533
- behavior: "deny",
2534
- message: `${toolName} is not available to this run.`
2535
- };
2536
- }
2537
- const verb = toolName in WRITE_PATH_ARGS ? "write" : "read";
2538
- for (const key of PATTERN_ARG_BY_TOOL[toolName] ?? []) {
2539
- const value = input[key];
2540
- if (typeof value !== "string" || value.length === 0)
2541
- continue;
2542
- if (patternEscapes(value)) {
2543
- return {
2544
- behavior: "deny",
2545
- message: `${toolName} patterns must stay inside the repository — no absolute path and no "..". Refused: ${value}`
2546
- };
2547
- }
2548
- }
2549
- for (const key of pathArgs) {
2550
- const value = input[key];
2551
- if (typeof value !== "string" || value.length === 0)
2552
- continue;
2553
- const candidate = isAbsolute(value) ? value : `${repoRoot}${sep}${value}`;
2554
- if (isGitMetadata(repoRoot, candidate)) {
2555
- return {
2556
- behavior: "deny",
2557
- message: `${toolName} may not touch the repository's git metadata. Refused: ${value}`
2558
- };
2559
- }
2560
- if (!isInsideTree(repoRoot, candidate)) {
2561
- return {
2562
- behavior: "deny",
2563
- message: `${toolName} may only ${verb} inside the repository. Refused: ${value}`
2564
- };
2565
- }
2566
- }
2567
- return { behavior: "allow" };
2568
- }
2569
- function confineToRepo(repoRoot, mode = "read") {
2570
- return async (toolName, input) => decideConfinedTool(repoRoot, toolName, input, mode);
2571
- }
2978
+
2979
+ // src/index.ts
2980
+ init_confine_to_repo();
2981
+
2572
2982
  // src/gate-collectors.ts
2573
2983
  init_dist();
2574
2984
  init_log();
2575
2985
 
2576
2986
  // src/oracle-collector.ts
2577
- import { createHash } from "node:crypto";
2987
+ import { createHash as createHash2 } from "node:crypto";
2578
2988
  init_log();
2579
2989
 
2580
2990
  // src/oracle.ts
2581
- import { lstatSync, readFileSync, statSync } from "node:fs";
2991
+ import { lstatSync, readFileSync as readFileSync2, statSync } from "node:fs";
2582
2992
  import {
2583
2993
  chmod,
2584
2994
  lstat,
@@ -2588,13 +2998,13 @@ import {
2588
2998
  rm,
2589
2999
  writeFile
2590
3000
  } from "node:fs/promises";
2591
- import { tmpdir } from "node:os";
2592
- 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";
2593
3003
  import { StringDecoder } from "node:string_decoder";
2594
3004
  init_log();
2595
3005
  var TAG5 = "oracle";
2596
3006
  async function resolveContained(repoPath, relativePath) {
2597
- if (isAbsolute2(relativePath)) {
3007
+ if (isAbsolute3(relativePath)) {
2598
3008
  throw new Error(`refusing to place an oracle at an absolute path: ${relativePath}`);
2599
3009
  }
2600
3010
  if (relativePath === "" || relativePath === ".") {
@@ -2617,7 +3027,7 @@ async function resolveContained(repoPath, relativePath) {
2617
3027
  }
2618
3028
  async function place(repoPath, oracle) {
2619
3029
  const target = await resolveContained(repoPath, oracle.path);
2620
- await mkdir(dirname2(target), { recursive: true });
3030
+ await mkdir(dirname3(target), { recursive: true });
2621
3031
  await writeFile(target, oracle.content, "utf8");
2622
3032
  }
2623
3033
  async function remove(repoPath, oracle) {
@@ -2725,8 +3135,8 @@ function argvPath(path) {
2725
3135
  }
2726
3136
  async function runHeldOracle(repoPath, oracle, timeoutMs = DEFAULT_METRIC_TIMEOUT_MS) {
2727
3137
  const spec = resolveOracleRunnerSpec(oracle);
2728
- const reportDir = await mkdtemp(join(tmpdir(), reportDirPrefix()));
2729
- const reportPath = join(reportDir, spec.report.file);
3138
+ const reportDir = await mkdtemp(join2(tmpdir2(), reportDirPrefix()));
3139
+ const reportPath = join2(reportDir, spec.report.file);
2730
3140
  try {
2731
3141
  return await spawnHeldOracle(spec, repoPath, oracle, reportPath, timeoutMs);
2732
3142
  } finally {
@@ -2737,7 +3147,7 @@ function reportDirPrefix() {
2737
3147
  return "harmony-oracle-report-";
2738
3148
  }
2739
3149
  function assertUntampered(reportPath) {
2740
- const dir = statSync(dirname2(reportPath));
3150
+ const dir = statSync(dirname3(reportPath));
2741
3151
  if ((dir.mode & 511) !== 448) {
2742
3152
  throw new Error(`the oracle report directory's mode changed to ${(dir.mode & 511).toString(8)} — refusing the report`);
2743
3153
  }
@@ -2800,7 +3210,7 @@ ${output}` : output;
2800
3210
  const captureReport = () => {
2801
3211
  try {
2802
3212
  assertUntampered(reportPath);
2803
- report = spec.report.parse(readFileSync(reportPath, "utf8"));
3213
+ report = spec.report.parse(readFileSync2(reportPath, "utf8"));
2804
3214
  } catch (err) {
2805
3215
  report = null;
2806
3216
  const message = err instanceof Error ? err.message : String(err);
@@ -2918,7 +3328,7 @@ class HeldOracleCollector {
2918
3328
  async runHeld(oracle) {
2919
3329
  const identity = {
2920
3330
  oracleId: oracle.id ?? null,
2921
- contentHash: createHash("sha256").update(oracle.content).digest("hex")
3331
+ contentHash: createHash2("sha256").update(oracle.content).digest("hex")
2922
3332
  };
2923
3333
  await this.deps.place(this.deps.repoPath, oracle);
2924
3334
  try {
@@ -3034,6 +3444,7 @@ import { execFileSync as execFileSync5, spawn as spawn2 } from "node:child_proce
3034
3444
 
3035
3445
  // src/pm.ts
3036
3446
  init_log();
3447
+ init_run_containment();
3037
3448
  import { execFileSync as execFileSync2 } from "node:child_process";
3038
3449
  import { existsSync } from "node:fs";
3039
3450
  var TAG7 = "pm";
@@ -3043,7 +3454,7 @@ function detectPackageManager() {
3043
3454
  return cached;
3044
3455
  let repoRoot;
3045
3456
  try {
3046
- repoRoot = execFileSync2("git", ["rev-parse", "--show-toplevel"], {
3457
+ repoRoot = execFileSync2("git", [...GIT_NO_HOOKS, "rev-parse", "--show-toplevel"], {
3047
3458
  encoding: "utf-8"
3048
3459
  }).trim();
3049
3460
  } catch {
@@ -3086,7 +3497,7 @@ function spawnRunArgs(script, ...extra) {
3086
3497
  // src/project-type.ts
3087
3498
  init_log();
3088
3499
  import { execFileSync as execFileSync3 } from "node:child_process";
3089
- import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "node:fs";
3500
+ import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3 } from "node:fs";
3090
3501
  var TAG8 = "project-type";
3091
3502
  var _cache = new Map;
3092
3503
  function _resetCache() {
@@ -3189,7 +3600,7 @@ var NPM_PLACEHOLDER_TEST = /no test specified/i;
3189
3600
  function hasNodeTestScript(dir) {
3190
3601
  let script;
3191
3602
  try {
3192
- const pkg = JSON.parse(readFileSync2(`${dir}/package.json`, "utf-8"));
3603
+ const pkg = JSON.parse(readFileSync3(`${dir}/package.json`, "utf-8"));
3193
3604
  script = pkg.scripts?.test;
3194
3605
  } catch (err) {
3195
3606
  log.warn(TAG8, `Could not read package.json in ${dir}: ${err instanceof Error ? err.message : err}`);
@@ -3206,7 +3617,7 @@ function hasNodeTestScript(dir) {
3206
3617
  function firstNodeScript(dir, candidates) {
3207
3618
  let scripts;
3208
3619
  try {
3209
- const pkg = JSON.parse(readFileSync2(`${dir}/package.json`, "utf-8"));
3620
+ const pkg = JSON.parse(readFileSync3(`${dir}/package.json`, "utf-8"));
3210
3621
  scripts = pkg.scripts ?? {};
3211
3622
  } catch (err) {
3212
3623
  log.warn(TAG8, `Could not read package.json in ${dir}: ${err instanceof Error ? err.message : err}`);
@@ -3263,6 +3674,7 @@ function resolveXcodeScheme(pt) {
3263
3674
 
3264
3675
  // src/revert-guard.ts
3265
3676
  init_log();
3677
+ init_run_containment();
3266
3678
  import { execFileSync as execFileSync4 } from "node:child_process";
3267
3679
  var TAG9 = "revert-guard";
3268
3680
  var TEST_FILE = /(?:^|\/)__tests__\/|\.(?:test|spec)\.[cm]?[jt]sx?$/;
@@ -3274,7 +3686,7 @@ function filterTestFiles(paths) {
3274
3686
  }
3275
3687
  function refetchBase(worktreePath, baseBranch) {
3276
3688
  try {
3277
- execFileSync4("git", ["fetch", "origin", baseBranch], {
3689
+ execFileSync4("git", [...GIT_NO_HOOKS, "fetch", "origin", baseBranch], {
3278
3690
  cwd: worktreePath,
3279
3691
  stdio: "pipe"
3280
3692
  });
@@ -3284,7 +3696,13 @@ function refetchBase(worktreePath, baseBranch) {
3284
3696
  }
3285
3697
  function listDeletedFilesAgainstBase(worktreePath, baseBranch) {
3286
3698
  try {
3287
- 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" });
3288
3706
  return out.split(`
3289
3707
  `).map((l) => l.trim()).filter((l) => l.length > 0);
3290
3708
  } catch (err) {
@@ -3298,6 +3716,7 @@ function findDeletedTestFiles(worktreePath, baseBranch) {
3298
3716
  }
3299
3717
 
3300
3718
  // src/verification.ts
3719
+ init_run_containment();
3301
3720
  var TAG10 = "verification";
3302
3721
  var MAX_OUTPUT_BUFFER2 = 64 * 1024 * 1024;
3303
3722
  async function runVerification(worktreePath, config, workerId) {
@@ -3371,7 +3790,8 @@ function runBuild(worktreePath, timeout) {
3371
3790
  cwd: worktreePath,
3372
3791
  timeout,
3373
3792
  stdio: "pipe",
3374
- maxBuffer: MAX_OUTPUT_BUFFER2
3793
+ maxBuffer: MAX_OUTPUT_BUFFER2,
3794
+ env: containedEnv()
3375
3795
  });
3376
3796
  return [];
3377
3797
  } catch (err) {
@@ -3389,7 +3809,8 @@ function runTests(worktreePath, timeout) {
3389
3809
  cwd: worktreePath,
3390
3810
  timeout,
3391
3811
  stdio: "pipe",
3392
- maxBuffer: MAX_OUTPUT_BUFFER2
3812
+ maxBuffer: MAX_OUTPUT_BUFFER2,
3813
+ env: containedEnv()
3393
3814
  });
3394
3815
  return [];
3395
3816
  } catch (err) {
@@ -3408,7 +3829,8 @@ function runFormatFix(worktreePath, timeout, workerId) {
3408
3829
  cwd: worktreePath,
3409
3830
  timeout,
3410
3831
  stdio: "pipe",
3411
- maxBuffer: MAX_OUTPUT_BUFFER2
3832
+ maxBuffer: MAX_OUTPUT_BUFFER2,
3833
+ env: containedEnv()
3412
3834
  });
3413
3835
  log.info(TAG10, `[worker:${workerId}] Auto-formatted worktree before commit/push`);
3414
3836
  } catch (err) {
@@ -3426,7 +3848,8 @@ function runLint(worktreePath, timeout) {
3426
3848
  cwd: worktreePath,
3427
3849
  timeout,
3428
3850
  stdio: "pipe",
3429
- maxBuffer: MAX_OUTPUT_BUFFER2
3851
+ maxBuffer: MAX_OUTPUT_BUFFER2,
3852
+ env: containedEnv()
3430
3853
  });
3431
3854
  return [];
3432
3855
  } catch (err) {
@@ -3444,7 +3867,8 @@ async function runDeepReview(worktreePath, config, workerId) {
3444
3867
  const [cmd, args] = spawnRunArgs("dev", "--port", String(port));
3445
3868
  devServer = spawn2(cmd, args, {
3446
3869
  cwd: worktreePath,
3447
- stdio: ["ignore", "pipe", "pipe"]
3870
+ stdio: ["ignore", "pipe", "pipe"],
3871
+ env: containedEnv()
3448
3872
  });
3449
3873
  try {
3450
3874
  await waitForDevServer(devServer, 30000);
@@ -3455,7 +3879,7 @@ async function runDeepReview(worktreePath, config, workerId) {
3455
3879
  }
3456
3880
  let diff = "";
3457
3881
  try {
3458
- diff = execFileSync5("git", ["diff", `origin/${config.worktree.baseBranch}..HEAD`], {
3882
+ diff = execFileSync5("git", [...GIT_NO_HOOKS, "diff", `origin/${config.worktree.baseBranch}..HEAD`], {
3459
3883
  cwd: worktreePath,
3460
3884
  encoding: "utf-8",
3461
3885
  timeout: 30000,
@@ -3476,14 +3900,16 @@ async function runDeepReview(worktreePath, config, workerId) {
3476
3900
  "```"
3477
3901
  ].join(`
3478
3902
  `);
3479
- const leanSources = config.claude.leanSettingSources;
3480
3903
  const output = execFileSync5("claude", [
3481
3904
  "--print",
3482
3905
  "--model",
3483
3906
  "sonnet",
3484
3907
  "--max-turns",
3485
3908
  "10",
3486
- ...leanSources ? ["--setting-sources", leanSources] : [],
3909
+ ...implementRunContainmentCliArgs({
3910
+ worktree: worktreePath,
3911
+ readOnly: true
3912
+ }),
3487
3913
  "--",
3488
3914
  reviewPrompt
3489
3915
  ], {
@@ -3491,7 +3917,8 @@ async function runDeepReview(worktreePath, config, workerId) {
3491
3917
  encoding: "utf-8",
3492
3918
  timeout: config.verification.timeout,
3493
3919
  stdio: "pipe",
3494
- maxBuffer: MAX_OUTPUT_BUFFER2
3920
+ maxBuffer: MAX_OUTPUT_BUFFER2,
3921
+ env: containedEnv()
3495
3922
  });
3496
3923
  return parseReviewFindings(output);
3497
3924
  } catch (err) {
@@ -3521,7 +3948,6 @@ function attemptAutoFix(worktreePath, config, errors) {
3521
3948
  "```"
3522
3949
  ].join(`
3523
3950
  `);
3524
- const leanSources = config.claude.leanSettingSources;
3525
3951
  const args = [
3526
3952
  "--print",
3527
3953
  "--model",
@@ -3530,7 +3956,7 @@ function attemptAutoFix(worktreePath, config, errors) {
3530
3956
  "50",
3531
3957
  "--allowedTools",
3532
3958
  "Bash,Read,Write,Edit,Glob,Grep",
3533
- ...leanSources ? ["--setting-sources", leanSources] : [],
3959
+ ...implementRunContainmentCliArgs({ worktree: worktreePath }),
3534
3960
  "--",
3535
3961
  fixPrompt
3536
3962
  ];
@@ -3539,7 +3965,8 @@ function attemptAutoFix(worktreePath, config, errors) {
3539
3965
  cwd: worktreePath,
3540
3966
  timeout: config.verification.timeout,
3541
3967
  stdio: "pipe",
3542
- maxBuffer: MAX_OUTPUT_BUFFER2
3968
+ maxBuffer: MAX_OUTPUT_BUFFER2,
3969
+ env: containedEnv()
3543
3970
  });
3544
3971
  }
3545
3972
  async function reportFindings(client, cardId, result, recovery) {
@@ -3872,6 +4299,7 @@ async function collectGateEvidence(registry, context) {
3872
4299
  }
3873
4300
  // src/git-diff-stat.ts
3874
4301
  init_log();
4302
+ init_run_containment();
3875
4303
  import { execFileSync as execFileSync6 } from "node:child_process";
3876
4304
  var TAG12 = "git-diff-stat";
3877
4305
  var MAX_CHANGED_FILES = 30;
@@ -3952,7 +4380,7 @@ function formatDiffSummary(diff, maxFiles = 100) {
3952
4380
  }
3953
4381
  function captureDiffStat(worktreePath, baseBranch, maxFiles = MAX_CHANGED_FILES) {
3954
4382
  try {
3955
- 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 });
3956
4384
  return parseNumstat(raw, maxFiles);
3957
4385
  } catch (err) {
3958
4386
  log.warn(TAG12, "git diff --numstat failed", {
@@ -4196,54 +4624,14 @@ async function runInSandbox(args) {
4196
4624
  return { passed: false, output };
4197
4625
  }
4198
4626
  }
4199
- // src/run-sizing.ts
4200
- init_dist();
4201
4627
 
4202
- // src/runner.ts
4203
- init_dist();
4204
- import { getConfigDir } from "@gethmy/mcp/src/config.js";
4205
- var HARMONY_CREDENTIAL_KEYS = [
4206
- "HARMONY_API_KEY",
4207
- "HARMONY_API_URL",
4208
- "HARMONY_WORKSPACE_ID",
4209
- "SUPABASE_ANON_KEY",
4210
- "SUPABASE_SERVICE_ROLE_KEY",
4211
- "SUPABASE_URL"
4212
- ];
4213
- function mayHoldCredentials(role) {
4214
- return role === "author" || role === "reviewer";
4215
- }
4216
- function credentialReadDeny() {
4217
- return `Read(/${getConfigDir()}/**)`;
4218
- }
4219
- function credentialAccessDeny() {
4220
- const dir = `/${getConfigDir()}/**`;
4221
- return [`Read(${dir})`, `Grep(${dir})`, `Glob(${dir})`];
4222
- }
4223
- function buildRoleLaunch(args) {
4224
- const role = normalizeStageRole(args.role);
4225
- const keep = mayHoldCredentials(role);
4226
- const env = {};
4227
- for (const [key, value] of Object.entries(args.parentEnv)) {
4228
- if (value === undefined)
4229
- continue;
4230
- if (!keep && HARMONY_CREDENTIAL_KEYS.includes(key))
4231
- continue;
4232
- env[key] = value;
4233
- }
4234
- return {
4235
- role,
4236
- prompt: args.prompt,
4237
- repoPath: args.repoPath,
4238
- env,
4239
- disallowedTools: keep ? [] : [credentialReadDeny()]
4240
- };
4241
- }
4242
- function envKeysDroppedByLaunch(parentEnv, launch) {
4243
- return Object.keys(parentEnv).filter((key) => parentEnv[key] !== undefined && !Object.hasOwn(launch.env, key));
4244
- }
4628
+ // src/index.ts
4629
+ init_run_containment();
4245
4630
 
4246
4631
  // src/run-sizing.ts
4632
+ init_dist();
4633
+ init_confine_to_repo();
4634
+ init_runner();
4247
4635
  var SIZING_MODEL = "haiku";
4248
4636
  var SIZING_MAX_TURNS = 25;
4249
4637
  var SIZING_MAX_BUDGET_USD = 0.75;
@@ -4417,6 +4805,10 @@ async function sizeRun(deps) {
4417
4805
  clearTimeout(timer);
4418
4806
  }
4419
4807
  }
4808
+
4809
+ // src/index.ts
4810
+ init_runner();
4811
+
4420
4812
  // src/stage-run.ts
4421
4813
  async function runStage(request, deps) {
4422
4814
  const events = [];
@@ -4446,6 +4838,7 @@ init_log();
4446
4838
  import { execFileSync as execFileSync8, execSync } from "node:child_process";
4447
4839
  import { existsSync as existsSync3, readdirSync as readdirSync2, rmSync } from "node:fs";
4448
4840
  import { resolve as resolve3 } from "node:path";
4841
+ init_run_containment();
4449
4842
  var TAG16 = "worktree";
4450
4843
 
4451
4844
  class WorktreeBaseError extends Error {
@@ -4454,7 +4847,7 @@ class WorktreeBaseError extends Error {
4454
4847
  this.name = "WorktreeBaseError";
4455
4848
  }
4456
4849
  }
4457
- 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], {
4458
4851
  cwd: root,
4459
4852
  stdio: "pipe"
4460
4853
  })) {
@@ -4480,7 +4873,41 @@ function resolveWorktreeStartRef(baseBranch, branchName, continueExisting, branc
4480
4873
  }
4481
4874
  return `origin/${baseBranch}`;
4482
4875
  }
4483
- 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", [
4876
+ function resolveContinuationTarget(branchName, continueRequested, failedBranchPrefix, approvedBranchPrefix, branchExistsOnOrigin) {
4877
+ if (branchExistsOnOrigin(branchName)) {
4878
+ return {
4879
+ branchName,
4880
+ continueExisting: true,
4881
+ existsOnOrigin: true,
4882
+ reason: continueRequested ? "requested" : "exists_on_origin"
4883
+ };
4884
+ }
4885
+ if (failedBranchPrefix && approvedBranchPrefix && branchName.startsWith(failedBranchPrefix)) {
4886
+ const sibling = approvedBranchPrefix + branchName.slice(failedBranchPrefix.length);
4887
+ if (branchExistsOnOrigin(sibling)) {
4888
+ return {
4889
+ branchName: sibling,
4890
+ continueExisting: true,
4891
+ existsOnOrigin: true,
4892
+ reason: "approved_rename"
4893
+ };
4894
+ }
4895
+ }
4896
+ return {
4897
+ branchName,
4898
+ continueExisting: continueRequested,
4899
+ existsOnOrigin: false,
4900
+ reason: "fresh"
4901
+ };
4902
+ }
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,
4484
4911
  "fetch",
4485
4912
  "origin",
4486
4913
  `+refs/heads/${branch}:refs/remotes/origin/${branch}`
@@ -4514,7 +4941,7 @@ function fetchExistingBranch(repoRoot, branchName, attempts = 3, lsRemoteImpl =
4514
4941
  }
4515
4942
  function readWorktreeHead(worktreePath) {
4516
4943
  try {
4517
- return execFileSync8("git", ["rev-parse", "HEAD"], {
4944
+ return execFileSync8("git", [...GIT_NO_HOOKS, "rev-parse", "HEAD"], {
4518
4945
  cwd: worktreePath,
4519
4946
  encoding: "utf-8"
4520
4947
  }).trim();
@@ -4523,7 +4950,7 @@ function readWorktreeHead(worktreePath) {
4523
4950
  }
4524
4951
  }
4525
4952
  function createWorktree(basePath, baseBranch, branchName, opts = {}) {
4526
- const repoRoot = execFileSync8("git", ["rev-parse", "--show-toplevel"], {
4953
+ const repoRoot = execFileSync8("git", [...GIT_NO_HOOKS, "rev-parse", "--show-toplevel"], {
4527
4954
  encoding: "utf-8"
4528
4955
  }).trim();
4529
4956
  const worktreeDir = resolve3(repoRoot, basePath, branchName);
@@ -4532,46 +4959,63 @@ function createWorktree(basePath, baseBranch, branchName, opts = {}) {
4532
4959
  cleanupWorktree(worktreeDir, branchName);
4533
4960
  }
4534
4961
  try {
4535
- execFileSync8("git", ["worktree", "prune", "--expire=now"], {
4962
+ execFileSync8("git", [...GIT_NO_HOOKS, "worktree", "prune", "--expire=now"], {
4536
4963
  cwd: repoRoot,
4537
4964
  stdio: "pipe"
4538
4965
  });
4539
4966
  } catch {}
4540
4967
  fetchBaseBranch(repoRoot, baseBranch);
4541
- const startRef = resolveWorktreeStartRef(baseBranch, branchName, opts.continueExisting ?? false, () => fetchExistingBranch(repoRoot, branchName));
4968
+ const startRef = resolveWorktreeStartRef(baseBranch, branchName, opts.continueExisting ?? false, () => opts.branchExistsOnOrigin ?? fetchExistingBranch(repoRoot, branchName));
4542
4969
  log.info(TAG16, `Creating worktree: ${worktreeDir} (branch: ${branchName}, base: ${startRef})`);
4543
4970
  try {
4544
- 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" });
4545
4980
  } catch (err) {
4546
4981
  const msg = err instanceof Error ? err.message : String(err);
4547
4982
  log.warn(TAG16, `worktree add failed, attempting forced recovery: ${msg}`);
4548
4983
  removeWorktreeHoldingBranch(repoRoot, branchName, worktreeDir);
4549
4984
  try {
4550
- execFileSync8("git", ["worktree", "remove", worktreeDir, "--force"], {
4985
+ execFileSync8("git", [...GIT_NO_HOOKS, "worktree", "remove", worktreeDir, "--force"], {
4551
4986
  cwd: repoRoot,
4552
4987
  stdio: "pipe"
4553
4988
  });
4554
4989
  } catch {}
4555
4990
  try {
4556
- execFileSync8("git", ["worktree", "prune", "--expire=now"], {
4991
+ execFileSync8("git", [...GIT_NO_HOOKS, "worktree", "prune", "--expire=now"], {
4557
4992
  cwd: repoRoot,
4558
4993
  stdio: "pipe"
4559
4994
  });
4560
4995
  } catch {}
4561
4996
  try {
4562
- execFileSync8("git", ["branch", "-D", branchName], {
4997
+ execFileSync8("git", [...GIT_NO_HOOKS, "branch", "-D", branchName], {
4563
4998
  cwd: repoRoot,
4564
4999
  stdio: "pipe"
4565
5000
  });
4566
5001
  } catch {}
4567
- 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" });
4568
5011
  }
4569
5012
  log.info(TAG16, "Installing dependencies in worktree...");
4570
5013
  try {
4571
- execSync(installCommand(), {
5014
+ execSync(installCommand(true), {
4572
5015
  cwd: worktreeDir,
4573
5016
  stdio: "pipe",
4574
- timeout: 60000
5017
+ timeout: 60000,
5018
+ env: containedEnv()
4575
5019
  });
4576
5020
  } catch {
4577
5021
  log.warn(TAG16, "Install failed (may be fine if deps are hoisted)");
@@ -4590,7 +5034,7 @@ function containsForeignWorktrees(dir) {
4590
5034
  return children.some((child) => existsSync3(resolve3(dir, child, ".git")));
4591
5035
  }
4592
5036
  function cleanupWorktree(worktreePath, branchName) {
4593
- const repoRoot = execFileSync8("git", ["rev-parse", "--show-toplevel"], {
5037
+ const repoRoot = execFileSync8("git", [...GIT_NO_HOOKS, "rev-parse", "--show-toplevel"], {
4594
5038
  encoding: "utf-8"
4595
5039
  }).trim();
4596
5040
  if (existsSync3(worktreePath)) {
@@ -4598,7 +5042,7 @@ function cleanupWorktree(worktreePath, branchName) {
4598
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).`);
4599
5043
  }
4600
5044
  try {
4601
- execFileSync8("git", ["worktree", "remove", worktreePath, "--force"], {
5045
+ execFileSync8("git", [...GIT_NO_HOOKS, "worktree", "remove", worktreePath, "--force"], {
4602
5046
  cwd: repoRoot,
4603
5047
  stdio: "pipe"
4604
5048
  });
@@ -4609,7 +5053,7 @@ function cleanupWorktree(worktreePath, branchName) {
4609
5053
  rmSync(worktreePath, { recursive: true, force: true });
4610
5054
  }
4611
5055
  try {
4612
- execFileSync8("git", ["worktree", "prune", "--expire=now"], {
5056
+ execFileSync8("git", [...GIT_NO_HOOKS, "worktree", "prune", "--expire=now"], {
4613
5057
  cwd: repoRoot,
4614
5058
  stdio: "pipe"
4615
5059
  });
@@ -4617,7 +5061,7 @@ function cleanupWorktree(worktreePath, branchName) {
4617
5061
  }
4618
5062
  } else {
4619
5063
  try {
4620
- execFileSync8("git", ["worktree", "prune", "--expire=now"], {
5064
+ execFileSync8("git", [...GIT_NO_HOOKS, "worktree", "prune", "--expire=now"], {
4621
5065
  cwd: repoRoot,
4622
5066
  stdio: "pipe"
4623
5067
  });
@@ -4625,7 +5069,7 @@ function cleanupWorktree(worktreePath, branchName) {
4625
5069
  }
4626
5070
  if (branchName) {
4627
5071
  try {
4628
- execFileSync8("git", ["branch", "-D", branchName], {
5072
+ execFileSync8("git", [...GIT_NO_HOOKS, "branch", "-D", branchName], {
4629
5073
  cwd: repoRoot,
4630
5074
  stdio: "pipe"
4631
5075
  });
@@ -4635,7 +5079,7 @@ function cleanupWorktree(worktreePath, branchName) {
4635
5079
  function removeWorktreeHoldingBranch(repoRoot, branchName, exceptDir) {
4636
5080
  let listing;
4637
5081
  try {
4638
- listing = execFileSync8("git", ["worktree", "list", "--porcelain"], {
5082
+ listing = execFileSync8("git", [...GIT_NO_HOOKS, "worktree", "list", "--porcelain"], {
4639
5083
  cwd: repoRoot,
4640
5084
  encoding: "utf-8",
4641
5085
  stdio: ["ignore", "pipe", "pipe"]
@@ -4663,7 +5107,7 @@ function removeWorktreeHoldingBranch(repoRoot, branchName, exceptDir) {
4663
5107
  if (exceptDir && resolve3(holderPath) === resolve3(exceptDir))
4664
5108
  return null;
4665
5109
  try {
4666
- execFileSync8("git", ["worktree", "remove", holderPath, "--force"], {
5110
+ execFileSync8("git", [...GIT_NO_HOOKS, "worktree", "remove", holderPath, "--force"], {
4667
5111
  cwd: repoRoot,
4668
5112
  stdio: "pipe"
4669
5113
  });
@@ -4673,7 +5117,7 @@ function removeWorktreeHoldingBranch(repoRoot, branchName, exceptDir) {
4673
5117
  return null;
4674
5118
  }
4675
5119
  try {
4676
- execFileSync8("git", ["worktree", "prune", "--expire=now"], {
5120
+ execFileSync8("git", [...GIT_NO_HOOKS, "worktree", "prune", "--expire=now"], {
4677
5121
  cwd: repoRoot,
4678
5122
  stdio: "pipe"
4679
5123
  });
@@ -4681,13 +5125,19 @@ function removeWorktreeHoldingBranch(repoRoot, branchName, exceptDir) {
4681
5125
  return holderPath;
4682
5126
  }
4683
5127
  function resolveRepoRoot() {
4684
- return execFileSync8("git", ["rev-parse", "--show-toplevel"], {
5128
+ return execFileSync8("git", [...GIT_NO_HOOKS, "rev-parse", "--show-toplevel"], {
4685
5129
  encoding: "utf-8"
4686
5130
  }).trim();
4687
5131
  }
4688
5132
  function localBranchExists(branchName, repoRoot) {
4689
5133
  try {
4690
- 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" });
4691
5141
  return true;
4692
5142
  } catch {
4693
5143
  return false;
@@ -4697,7 +5147,7 @@ function branchAheadOfItsRemote(branchName, repoRoot = resolveRepoRoot()) {
4697
5147
  if (!localBranchExists(branchName, repoRoot))
4698
5148
  return false;
4699
5149
  try {
4700
- 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();
4701
5151
  return out.length > 0;
4702
5152
  } catch {
4703
5153
  return false;
@@ -4750,11 +5200,13 @@ function makeBranchName(shortId, title, prefix = "agent-attempts/") {
4750
5200
  // src/index.ts
4751
5201
  var MOTOR_NAME = "harmony-harness";
4752
5202
  export {
5203
+ writeOnlyDenyPaths,
4753
5204
  waitForDevServer,
4754
5205
  validateGitProviderCli,
4755
5206
  upsertReviewedSha,
4756
5207
  upsertCiReviewRequestedSha,
4757
5208
  updateExistingPr,
5209
+ toolchainCacheDirectories,
4758
5210
  testCommand,
4759
5211
  terminateGroup,
4760
5212
  teardownWorktree,
@@ -4767,6 +5219,7 @@ export {
4767
5219
  sizingEventSource,
4768
5220
  sizeRun,
4769
5221
  signalGroup,
5222
+ secretEnvKeysToStrip,
4770
5223
  sanitizeCiText,
4771
5224
  sandboxRunArgs,
4772
5225
  sandboxAvailable,
@@ -4787,6 +5240,7 @@ export {
4787
5240
  resolvePrUrl,
4788
5241
  resolvePrHeadBranch,
4789
5242
  resolveOracleRunner,
5243
+ resolveContinuationTarget,
4790
5244
  rescueUnpushedBranch,
4791
5245
  rerunFailedJobs,
4792
5246
  requestIndependentReview,
@@ -4820,7 +5274,13 @@ export {
4820
5274
  isPrOnOrigin,
4821
5275
  isInsideTree,
4822
5276
  installCommand,
5277
+ implementRunToolPolicy,
5278
+ implementRunContainmentCliArgs,
5279
+ implementRunContainment,
5280
+ hostPersistencePaths,
5281
+ harmonyMcpServer,
4823
5282
  gradeOracleRed,
5283
+ gitMetadataDenyPaths,
4824
5284
  getPrStatus,
4825
5285
  getPrFailedChecks,
4826
5286
  getHeadSha,
@@ -4850,10 +5310,14 @@ export {
4850
5310
  deriveCiStatus,
4851
5311
  decidePrBranch,
4852
5312
  decideConfinedTool,
5313
+ credentialWriteToolDeny,
5314
+ credentialToolDeny,
5315
+ credentialDirectories,
4853
5316
  credentialAccessDeny,
4854
5317
  createWorktree,
4855
5318
  createPullRequest,
4856
5319
  cooldownMsFor,
5320
+ containedEnv,
4857
5321
  confineToRepo,
4858
5322
  collectSizingOutput,
4859
5323
  collectGateEvidence,
@@ -4872,6 +5336,7 @@ export {
4872
5336
  buildCommand,
4873
5337
  branchAheadOfItsRemote,
4874
5338
  attemptAutoFix,
5339
+ assertNoProjectSandboxOverride,
4875
5340
  _resetCache,
4876
5341
  __resetSandboxProbe,
4877
5342
  WorktreeBaseError,
@@ -4880,6 +5345,7 @@ export {
4880
5345
  SDK_ALLOWED_TOOLS,
4881
5346
  SANDBOX_MOUNT,
4882
5347
  ReviewPassedCollector,
5348
+ ProjectSandboxOverrideError,
4883
5349
  OracleRedCollector,
4884
5350
  OracleCollector,
4885
5351
  ORACLE_RUNNER_HINTS,
@@ -4889,8 +5355,10 @@ export {
4889
5355
  MAX_IMPLEMENT_MODEL,
4890
5356
  MAX_CHANGED_FILES,
4891
5357
  JUDGE_MODEL,
5358
+ IMPLEMENT_ALLOWED_DOMAINS,
4892
5359
  HarmonyClient,
4893
5360
  HARMONY_CREDENTIAL_KEYS,
5361
+ GIT_NO_HOOKS,
4894
5362
  GATE_CONFIG_ERROR_MARK,
4895
5363
  GATE_CONFIG_ERROR_KEY,
4896
5364
  DevServerReadinessError,