Skip to main content
Glama
git-policy.ts4.08 kB
import { resolve } from 'node:path'; export type GitInvocation = { index: number; argv: string[]; }; export type GitCommandInfo = { name: string; index: number; }; export type GitExecutionContext = { invocation: GitInvocation | null; command: GitCommandInfo | null; subcommand: string | null; workDir: string; }; export type GitPolicyEvaluation = { requiresCommitHelper: boolean; requiresExplicitConsent: boolean; isDestructive: boolean; }; const COMMIT_HELPER_SUBCOMMANDS = new Set(['add', 'commit']); const GUARDED_SUBCOMMANDS = new Set(['push', 'pull', 'merge', 'rebase', 'cherry-pick']); const DESTRUCTIVE_SUBCOMMANDS = new Set([ 'reset', 'checkout', 'clean', 'restore', 'switch', 'stash', 'branch', 'filter-branch', 'fast-import', ]); export function extractGitInvocation(commandArgs: string[]): GitInvocation | null { for (const [index, token] of commandArgs.entries()) { if (token === 'git' || token.endsWith('/git')) { return { index, argv: commandArgs.slice(index) }; } } return null; } export function findGitSubcommand(commandArgs: string[]): GitCommandInfo | null { if (commandArgs.length <= 1) { return null; } const optionsWithValue = new Set(['-C', '--git-dir', '--work-tree', '-c']); let index = 1; while (index < commandArgs.length) { const token = commandArgs[index]; if (token === '--') { const next = commandArgs[index + 1]; return next ? { name: next, index: index + 1 } : null; } if (!token.startsWith('-')) { return { name: token, index }; } if (token.includes('=')) { index += 1; continue; } if (optionsWithValue.has(token)) { index += 2; continue; } index += 1; } return null; } export function determineGitWorkdir(baseDir: string, gitArgs: string[], command: GitCommandInfo | null): string { let workDir = baseDir; const limit = command ? command.index : gitArgs.length; let index = 1; while (index < limit) { const token = gitArgs[index]; if (token === '-C') { const next = gitArgs[index + 1]; if (next) { workDir = resolve(workDir, next); } index += 2; continue; } if (token.startsWith('-C')) { const pathSegment = token.slice(2); if (pathSegment.length > 0) { workDir = resolve(workDir, pathSegment); } } index += 1; } return workDir; } export function analyzeGitExecution(commandArgs: string[], workspaceDir: string): GitExecutionContext { const invocation = extractGitInvocation(commandArgs); const command = invocation ? findGitSubcommand(invocation.argv) : null; const workDir = invocation ? determineGitWorkdir(workspaceDir, invocation.argv, command) : workspaceDir; return { invocation, command, subcommand: command?.name ?? null, workDir, }; } export function requiresCommitHelper(subcommand: string | null): boolean { if (!subcommand) { return false; } return COMMIT_HELPER_SUBCOMMANDS.has(subcommand); } export function requiresExplicitGitConsent(subcommand: string | null): boolean { if (!subcommand) { return false; } return GUARDED_SUBCOMMANDS.has(subcommand); } export function isDestructiveGitSubcommand(command: GitCommandInfo | null, gitArgv: string[]): boolean { if (!command) { return false; } const subcommand = command.name; if (DESTRUCTIVE_SUBCOMMANDS.has(subcommand)) { return true; } if (subcommand === 'bisect') { const action = gitArgv[command.index + 1] ?? ''; return action === 'reset'; } return false; } export function evaluateGitPolicies(context: GitExecutionContext): GitPolicyEvaluation { const invocationArgv = context.invocation?.argv; const normalizedArgv = Array.isArray(invocationArgv) ? invocationArgv : []; return { requiresCommitHelper: requiresCommitHelper(context.subcommand), requiresExplicitConsent: requiresExplicitGitConsent(context.subcommand), isDestructive: isDestructiveGitSubcommand(context.command, normalizedArgv), }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/steipete/Peekaboo'

If you have feedback or need assistance with the MCP directory API, please join our Discord server