diff --git a/tests/Bifrost.Web.Tests/BridgeCliTests.cs b/tests/Bifrost.Web.Tests/BridgeCliTests.cs index ee31fd8..30d8714 100644 --- a/tests/Bifrost.Web.Tests/BridgeCliTests.cs +++ b/tests/Bifrost.Web.Tests/BridgeCliTests.cs @@ -443,7 +443,6 @@ exit /b 0 ["BIFROST_ENFORCE_GITHUB_GATE"] = "true", ["BIFROST_GIT_EXECUTABLE"] = Path.Combine(gatePath, "git.cmd"), ["BIFROST_GH_EXECUTABLE"] = Path.Combine(gatePath, "gh.cmd"), - ["BIFROST_REAL_GIT"] = ResolveRealGitExecutable(), ["BIFROST_NODE_EXECUTABLE"] = ResolveExecutable("node"), ["GIT_CONFIG_COUNT"] = "1", ["GIT_CONFIG_KEY_0"] = "core.hooksPath", @@ -455,8 +454,6 @@ exit /b 0 private static Dictionary BuildDispatchedGitGateEnv() => BuildDispatchedGitHookEnv(); - private static string ResolveRealGitExecutable() => ResolveExecutable("git"); - private static string ResolveExecutable(string command) { using var process = new Process diff --git a/tools/dispatch-agent-requests.mjs b/tools/dispatch-agent-requests.mjs index 1387b19..ccbd53a 100644 --- a/tools/dispatch-agent-requests.mjs +++ b/tools/dispatch-agent-requests.mjs @@ -569,8 +569,6 @@ function buildBridgeContextEnv(request) { BIFROST_ENFORCE_GITHUB_GATE: "true", BIFROST_GIT_EXECUTABLE: resolve(gitGatePath, "git.cmd"), BIFROST_GH_EXECUTABLE: resolve(gitGatePath, "gh.cmd"), - BIFROST_REAL_GIT: resolveRealGitExecutable(), - ...buildRealGitHubCliEnv(), BIFROST_NODE_EXECUTABLE: process.execPath, ...buildGitHooksConfigEnv(hooksPath), ...buildPathPrependEnv(gitGatePath), @@ -603,41 +601,6 @@ function buildPathPrependEnv(directory) { }; } -function buildRealGitHubCliEnv() { - const realGh = tryResolveExecutable("gh"); - return realGh ? { BIFROST_REAL_GH: realGh } : {}; -} - -function resolveRealGitExecutable() { - const resolved = tryResolveExecutable("git"); - if (!resolved) { - throw new Error("Could not resolve the real git executable for dispatched turn gating."); - } - - return resolved; -} - -function tryResolveExecutable(command) { - const locator = process.platform === "win32" ? "where.exe" : "which"; - const result = spawnSync(locator, [command], { - cwd: bifrostRoot, - encoding: "utf8", - stdio: ["ignore", "pipe", "pipe"], - windowsHide: true, - }); - - if (result.status !== 0) { - return ""; - } - - const matches = `${result.stdout ?? ""}` - .split(/\r?\n/) - .map((line) => line.trim()) - .filter(Boolean); - - return matches[0] ?? ""; -} - class CodexAppServerClient { constructor({ command, env, logPath }) { this.command = command; diff --git a/tools/git-gate/gh-proxy.mjs b/tools/git-gate/gh-proxy.mjs index fb63cdd..288ca88 100644 --- a/tools/git-gate/gh-proxy.mjs +++ b/tools/git-gate/gh-proxy.mjs @@ -1,8 +1,11 @@ #!/usr/bin/env node import { spawnSync } from "node:child_process"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; const args = process.argv.slice(2); -const realGh = process.env.BIFROST_REAL_GH; +const wrapperDir = dirname(fileURLToPath(import.meta.url)); +const realGh = process.env.BIFROST_REAL_GH || resolveExecutable("gh", wrapperDir); const realGhArgs = splitCommandArgs(process.env.BIFROST_REAL_GH_ARGS ?? ""); const mutatingCommands = new Set([ "gist create", @@ -73,6 +76,34 @@ if (result.error) { process.exit(result.status ?? 1); +function resolveExecutable(command, excludedDir) { + const locator = process.platform === "win32" ? "where.exe" : "which"; + const result = spawnSync(locator, [command], { + cwd: excludedDir, + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + windowsHide: true, + }); + + if (result.status !== 0) { + return ""; + } + + const matches = `${result.stdout ?? ""}` + .split(/\r?\n/) + .map((line) => line.trim()) + .filter(Boolean) + .filter((line) => !isWithinExcludedDir(line, excludedDir)); + + return matches[0] ?? ""; +} + +function isWithinExcludedDir(candidate, excludedDir) { + const normalizedCandidate = resolve(candidate).toLowerCase(); + const normalizedExcludedDir = `${resolve(excludedDir).toLowerCase()}\\`; + return normalizedCandidate.startsWith(normalizedExcludedDir); +} + function isWindowsCommandScript(command) { return /\.cmd$/i.test(command) || /\.bat$/i.test(command); } diff --git a/tools/git-gate/gh.cmd b/tools/git-gate/gh.cmd index 9da53f9..0c52fa3 100644 --- a/tools/git-gate/gh.cmd +++ b/tools/git-gate/gh.cmd @@ -1,12 +1,6 @@ @echo off setlocal -set "REAL_GH=%BIFROST_REAL_GH%" -if not defined REAL_GH ( - echo Bifrost GitHub gate has no real GitHub CLI executable configured.>&2 - exit /b 1 -) - set "NODE_EXE=%BIFROST_NODE_EXECUTABLE%" if not defined NODE_EXE ( echo Bifrost GitHub gate has no Node executable configured.>&2 diff --git a/tools/git-gate/git-proxy.mjs b/tools/git-gate/git-proxy.mjs index 08e8b13..71b6ef2 100644 --- a/tools/git-gate/git-proxy.mjs +++ b/tools/git-gate/git-proxy.mjs @@ -1,8 +1,11 @@ #!/usr/bin/env node import { spawnSync } from "node:child_process"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; const args = process.argv.slice(2); -const realGit = process.env.BIFROST_REAL_GIT; +const wrapperDir = dirname(fileURLToPath(import.meta.url)); +const realGit = process.env.BIFROST_REAL_GIT || resolveExecutable("git", wrapperDir); if (!realGit) { process.stderr.write("Bifrost Git gate has no real git executable configured.\n"); @@ -33,6 +36,34 @@ if (result.error) { process.exit(result.status ?? 1); +function resolveExecutable(command, excludedDir) { + const locator = process.platform === "win32" ? "where.exe" : "which"; + const result = spawnSync(locator, [command], { + cwd: excludedDir, + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + windowsHide: true, + }); + + if (result.status !== 0) { + return ""; + } + + const matches = `${result.stdout ?? ""}` + .split(/\r?\n/) + .map((line) => line.trim()) + .filter(Boolean) + .filter((line) => !isWithinExcludedDir(line, excludedDir)); + + return matches[0] ?? ""; +} + +function isWithinExcludedDir(candidate, excludedDir) { + const normalizedCandidate = resolve(candidate).toLowerCase(); + const normalizedExcludedDir = `${resolve(excludedDir).toLowerCase()}\\`; + return normalizedCandidate.startsWith(normalizedExcludedDir); +} + function findGitSubcommand(input) { const optionsWithSeparateValues = new Set([ "-C", diff --git a/tools/git-gate/git.cmd b/tools/git-gate/git.cmd index c76c759..d1ad8c4 100644 --- a/tools/git-gate/git.cmd +++ b/tools/git-gate/git.cmd @@ -1,12 +1,6 @@ @echo off setlocal -set "REAL_GIT=%BIFROST_REAL_GIT%" -if not defined REAL_GIT ( - echo Bifrost Git gate has no real git executable configured.>&2 - exit /b 1 -) - set "NODE_EXE=%BIFROST_NODE_EXECUTABLE%" if not defined NODE_EXE ( echo Bifrost Git gate has no Node executable configured.>&2