fix(git-master): use shared shell detection for cross-platform env prefix (fix #3207)

Use detectShellType() and buildEnvPrefix() from src/shared/shell-env.ts instead of hardcoding bash-only VAR=value syntax. PowerShell users get $env:VAR='value'; cmd users get set VAR="value" &&; unix/Git Bash users keep VAR=value. Skips injecting non-bash prefixes into bash code blocks to avoid syntax mismatch.

Reland of #3214 (ekkoitac) which had unresolvable CLA + used custom shell detection instead of the shared utility.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: ekkoitac <lobster@example.com>

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-05-18 13:31:55 +09:00
parent 5555dbfc67
commit d7caeb0178
2 changed files with 198 additions and 7 deletions
@@ -1,18 +1,60 @@
import { assertValidGitEnvPrefix, type GitMasterConfig } from "../../config/schema"
import { detectShellType, buildEnvPrefix, type ShellType } from "../../shared/shell-env"
const BASH_CODE_BLOCK_PATTERN = /```bash\r?\n([\s\S]*?)```/g
const LEADING_GIT_COMMAND_PATTERN = /^([ \t]*(?:[A-Za-z_][A-Za-z0-9_]*=[^ \t]+\s+)*)git(?=[ \t]|$)/gm
const INLINE_GIT_COMMAND_PATTERN = /([;&|()][ \t]*)git(?=[ \t]|$)/g
/**
* Parse a bash-format env prefix string ("VAR=value VAR2=value2") into a Record.
* Only handles simple KEY=VALUE pairs (no quoting needed since assertValidGitEnvPrefix
* already validates the format is shell-safe alphanumeric assignments).
*/
export function parseBashEnvPrefix(prefix: string): Record<string, string> {
const result: Record<string, string> = {}
const pairs = prefix.trim().split(/\s+/)
for (const pair of pairs) {
const eqIndex = pair.indexOf("=")
if (eqIndex === -1) continue
const key = pair.slice(0, eqIndex)
const value = pair.slice(eqIndex + 1)
result[key] = value
}
return result
}
/**
* Build the shell-aware command prefix for git commands.
* Uses the shared shell detection and env prefix builder to emit correct syntax
* for PowerShell ($env:VAR='value';), cmd (set VAR="value" &&), or unix (VAR=value).
*
* For unix shells, we use the inline VAR=value prefix style (not export) to match
* the original behavior where the env var applies only to the immediately following command.
*/
export function buildShellAwareGitPrefix(bashPrefix: string, shellType?: ShellType): string {
if (!bashPrefix) return ""
const resolvedShellType = shellType ?? detectShellType()
if (resolvedShellType === "unix" || resolvedShellType === "csh") {
return bashPrefix
}
const envRecord = parseBashEnvPrefix(bashPrefix)
return buildEnvPrefix(envRecord, resolvedShellType)
}
export function injectGitMasterConfig(template: string, config?: GitMasterConfig): string {
const commitFooter = config?.commit_footer ?? true
const includeCoAuthoredBy = config?.include_co_authored_by ?? true
const gitEnvPrefix = assertValidGitEnvPrefix(config?.git_env_prefix ?? "GIT_MASTER=1")
let result = gitEnvPrefix ? injectGitEnvPrefix(template, gitEnvPrefix) : template
const shellType = detectShellType()
const shellPrefix = gitEnvPrefix ? buildShellAwareGitPrefix(gitEnvPrefix, shellType) : ""
const codeBlockLang = shellType === "powershell" ? "pwsh" : "bash"
const skipBashBlockPrefixing = shellType === "powershell" || shellType === "cmd"
let result = gitEnvPrefix ? injectGitEnvPrefix(template, shellPrefix, codeBlockLang) : template
if (commitFooter || includeCoAuthoredBy) {
const injection = buildCommitFooterInjection(commitFooter, includeCoAuthoredBy, gitEnvPrefix)
const injection = buildCommitFooterInjection(commitFooter, includeCoAuthoredBy, shellPrefix)
const insertionPoint = result.indexOf("```\n</execution>")
result =
@@ -25,10 +67,14 @@ export function injectGitMasterConfig(template: string, config?: GitMasterConfig
: result + "\n\n" + injection
}
return gitEnvPrefix ? prefixGitCommandsInBashCodeBlocks(result, gitEnvPrefix) : result
if (gitEnvPrefix && !skipBashBlockPrefixing) {
result = prefixGitCommandsInBashCodeBlocks(result, shellPrefix)
}
return result
}
function injectGitEnvPrefix(template: string, prefix: string): string {
function injectGitEnvPrefix(template: string, prefix: string, codeBlockLang: string): string {
const envPrefixSection = [
"## GIT COMMAND PREFIX (MANDATORY)",
"",
@@ -37,7 +83,7 @@ function injectGitEnvPrefix(template: string, prefix: string): string {
"",
"This allows custom git hooks to detect when git-master skill is active.",
"",
"```bash",
`\`\`\`${codeBlockLang}`,
`${prefix} git status`,
`${prefix} git add <files>`,
`${prefix} git commit -m "message"`,