{"_id":"@animakit/git-guardrails","name":"@animakit/git-guardrails","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@animakit/git-guardrails","version":"0.1.0","description":"Human-in-the-loop confirmation for AI agents with git/shell access — <1ms decision layer, 0 runtime deps. Extracted from 53 production sprints.","license":"Apache-2.0","author":{"name":"Justine Serna"},"repository":{"type":"git","url":"git+https://github.com/animakit-ai/anima.git","directory":"packages/git-guardrails"},"keywords":["llm","agents","git","shell","guardrails","safety","human-in-the-loop","agentic","security","ai-safety"],"type":"module","main":"./dist/index.cjs","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"sideEffects":false,"engines":{"node":">=20"},"scripts":{"build":"tsup src/index.ts --format esm,cjs --dts --clean","test":"vitest run --coverage","typecheck":"tsc --noEmit","bench":"node --import tsx benchmarks/latency.ts","bench:stats":"node --import tsx benchmarks/production-stats.ts"},"devDependencies":{"@types/node":">=20"},"publishConfig":{"access":"public"},"gitHead":"58e3830a019128fd550b85d70ca4c71724f9f2f4","_id":"@animakit/git-guardrails@0.1.0","bugs":{"url":"https://github.com/animakit-ai/anima/issues"},"homepage":"https://github.com/animakit-ai/anima#readme","_nodeVersion":"24.14.0","_npmVersion":"11.11.1","dist":{"integrity":"sha512-oliN7SABX/CvgodenTBY3O1DA9EAZuQJrPjatWFkC7P2h1XgOJXoHF2nswkkv9s9Uvi7xos66Qy2wkHdrdSHuA==","shasum":"e70c82ee98605b300ddab417138cff86edf4362e","tarball":"https://registry.npmjs.org/@animakit/git-guardrails/-/git-guardrails-0.1.0.tgz","fileCount":9,"unpackedSize":71040,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDicHtEr6mPn1+Oudwhc0SoOlX2oOvgytufv43m2xPKcwIhAJFhQkh2WnS+AEcAL33PVIjK6mxVCowQQfidFRtZr6be"}]},"_npmUser":{"name":"andrrewcorp","email":"andrew.corpdesing@gmail.com"},"directories":{},"maintainers":[{"name":"andrrewcorp","email":"andrew.corpdesing@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/git-guardrails_0.1.0_1782878173770_0.751689793445619"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-01T03:56:13.544Z","0.1.0":"2026-07-01T03:56:13.916Z","modified":"2026-07-01T03:56:14.232Z"},"maintainers":[{"name":"andrrewcorp","email":"andrew.corpdesing@gmail.com"}],"description":"Human-in-the-loop confirmation for AI agents with git/shell access — <1ms decision layer, 0 runtime deps. Extracted from 53 production sprints.","homepage":"https://github.com/animakit-ai/anima#readme","keywords":["llm","agents","git","shell","guardrails","safety","human-in-the-loop","agentic","security","ai-safety"],"repository":{"type":"git","url":"git+https://github.com/animakit-ai/anima.git","directory":"packages/git-guardrails"},"author":{"name":"Justine Serna"},"bugs":{"url":"https://github.com/animakit-ai/anima/issues"},"license":"Apache-2.0","readme":"# @animakit/git-guardrails\n\n> Agents that can `git push` need a human in the loop. This is that loop — a <1ms decision layer for shell + git safety, 0 deps.\n\n[![npm version](https://img.shields.io/npm/v/@animakit/git-guardrails)](https://www.npmjs.com/package/@animakit/git-guardrails)\n[![bundle size](https://img.shields.io/bundlephobia/minzip/@animakit/git-guardrails)](https://bundlephobia.com/package/@animakit/git-guardrails)\n[![zero deps](https://img.shields.io/badge/deps-0-brightgreen)](https://www.npmjs.com/package/@animakit/git-guardrails?activeTab=dependencies)\n[![license](https://img.shields.io/badge/license-Apache--2.0-blue)](./LICENSE)\n\n---\n\n## Why this exists\n\n> *Extracted from 53 sprints of running an AI agent in production.*\n\nAn AI agent with shell and git access is, by definition, dangerous. Most agent frameworks give \"all or nothing\" access — either the agent can't touch the system, or it can do whatever it wants.\n\n`git-guardrails` is the decision layer that sits between \"the agent wants to do X\" and \"X executes\":\n\n- **Shell safety** — allowlist of directories + blocklist of dangerous patterns (`sudo`, `rm -rf /`, `curl | bash`, `chmod 777`, etc.) — decision in <1ms, before touching `child_process`.\n- **Git write operations** — `commit`, `push`, `pr` **always** return `requiresConfirmation: true` with structured details for your confirmation prompt. No exception.\n- **Sensitive files** — before a commit, scans `git status` against patterns (`.env`, `.pem`, `.key`, `credentials`, `token`...) and **aborts** if found.\n- **Agent instruction files** — if a commit touches `CLAUDE.md`, `AGENTS.md`, `.cursorrules`, `.claude/settings.json`, or other files governing the agent's own behavior, `confirmationDetails` marks it explicitly. This is the \"agent modifying itself\" case — a self-modification vector specific to the agentic era.\n\n**Production stat (Table 2):** *(see [SPEC.md §6](./SPEC.md) — numbers to be populated from real production log)*\n\n| Metric | Value |\n|---|---|\n| Sprints analyzed | 53 |\n| Git commit confirmation rate | 100% by design |\n| Sensitive file aborts | TODO (from production log) |\n| Shell commands blocked | TODO (from production log) |\n\n---\n\n## Quickstart\n\n```bash\nnpm install @animakit/git-guardrails\n# or\npnpm add @animakit/git-guardrails\n```\n\n```typescript\nimport {\n  isCommandSafe,\n  runShell,\n  executeGitAction,\n  formatConfirmationPrompt,\n  executeConfirmedGitAction,\n  presets,\n} from '@animakit/git-guardrails';\n\n// ── Shell safety (pure, <1ms) ─────────────────────────────────────────────\n\nconst check = isCommandSafe('npm run build', '/home/user/repo', {\n  allowedDirs: ['/home/user/repos', '/tmp'],\n});\nif (!check.safe) throw new Error(`Blocked: ${check.reason}`);\n\nconst result = await runShell('npm run build', { cwd: '/home/user/repo' });\n\n// ── Git guardrails (human-in-the-loop) ───────────────────────────────────\n\nconst action = await executeGitAction({\n  action: 'commit',\n  repoPath: '/home/user/repo',\n  message: 'feat: add payment processing',\n});\n\nif (action.requiresConfirmation) {\n  // Build the confirmation prompt for Telegram / Slack / CLI\n  const prompt = formatConfirmationPrompt(action.confirmationDetails!, {\n    language: 'en',\n    format: 'markdown',\n  });\n  // → Send prompt to human, wait for OK\n  // → On confirmation:\n  const output = await executeConfirmedGitAction(action.confirmationPayload!);\n  console.log(output); // \"Commit created: [main abc1234] feat: add payment processing\"\n}\n```\n\n---\n\n## Confirmation flow\n\n```\nAgent wants to commit/push/pr\n         │\n         ▼\n  executeGitAction()\n         │\n         ├── detectSensitiveFiles() → found → throw Error (abort)\n         │\n         ├── detectAgentInstructionFiles() → found → mark in confirmationDetails\n         │                                            (does NOT abort)\n         │\n         └── requiresConfirmation: true\n                    │\n                    ▼\n          formatConfirmationPrompt()\n                    │\n                    ▼\n          Human receives prompt (Telegram/Slack/CLI)\n                    │\n                    ▼\n          Human replies OK\n                    │\n                    ▼\n        executeConfirmedGitAction()\n                    │\n                    ▼\n              ✅ Done\n```\n\n---\n\n## API Reference\n\n### Layer 1 — Shell safety (pure, <1ms)\n\n#### `isCommandSafe(command, cwd, config?)`\n\nChecks a shell command against blocked patterns and optionally validates the working directory.\n\n```typescript\nisCommandSafe('sudo apt install vim', '/home/user')\n// → { safe: false, reason: 'Blocked pattern: \\\\bsudo\\\\b' }\n\nisCommandSafe('git status', '/home/user/repo', {\n  allowedDirs: ['/home/user'],\n  blockedPatterns: { extend: [/docker rm/] },\n})\n// → { safe: true }\n```\n\n**Config options:**\n- `blockedPatterns.extend` — adds to the default set\n- `blockedPatterns.replace` — replaces the default set entirely\n- `allowedDirs` — if provided, `cwd` must start with one of these\n\n#### `isPathAllowed(path, allowedDirs)`\n\nPure check — returns `true` if `path` starts with any of `allowedDirs`.\n\n---\n\n### Layer 2 — Shell execution\n\n#### `runShell(command, options?)`\n\nExecutes via `/bin/sh -c` (POSIX) or `cmd /c` (Windows). Creates `cwd` if it doesn't exist.\n\nIntentionally does NOT call `isCommandSafe()` — the caller decides when to apply the guardrail, keeping it explicit.\n\n```typescript\nconst result = await runShell('git log --oneline -5', {\n  cwd: '/home/user/repo',\n  timeoutMs: 30_000,\n  env: { HOME: '/home/myuser' },  // injectable — no hardcoded paths\n});\n// → { stdout: '...', stderr: '...', exitCode: 0, durationMs: 47 }\n```\n\n---\n\n### Layer 3 — Git guardrails\n\n#### `executeGitAction(params, config?)`\n\nThe main decision function.\n\n| Action | Behavior |\n|---|---|\n| `clone`, `pull`, `status`, `log` | Executes immediately, `requiresConfirmation: false` |\n| `commit`, `push`, `pr` | Always `requiresConfirmation: true` |\n| `commit` + sensitive files | Throws `Error` — **never** reaches `requiresConfirmation` |\n| `commit` + agent instruction files | Sets `confirmationDetails.agentInstructionFilesChanged` — does not abort |\n\n```typescript\nconst result = await executeGitAction(\n  { action: 'commit', repoPath: '/abs/path/to/repo', message: 'feat: ...' },\n  {\n    gitTimeoutMs: 30_000,\n    shell: myMockShell,  // injectable for testing\n    sensitiveFiles: { patterns: { extend: [/my-secrets/] } },\n  }\n);\n```\n\n#### `executeConfirmedGitAction(payload, config?)`\n\nExecutes after human confirmation. Requires `gh` CLI in PATH for `'pr'`.\n\n#### `detectSensitiveFiles(gitStatusOutput, config?)`\n\nScans `git status --short` output. Returns matched file paths.\n\n#### `detectAgentInstructionFiles(gitStatusOutput, config?)`\n\nScans `git status --short` for agent instruction/config files. Returns matched paths. Does NOT abort.\n\n#### `shellEscape(s)`\n\nEscapes a string for safe use as a single-quoted shell argument.\n\n---\n\n### Presentation — `formatConfirmationPrompt(details, options?)`\n\nConverts `confirmationDetails` to human-readable text. Optional — you can build your own prompt from the raw `confirmationDetails` object.\n\n```typescript\nconst prompt = formatConfirmationPrompt(action.confirmationDetails!, {\n  language: 'es',      // 'en' | 'es', default: 'en'\n  format: 'markdown',  // 'markdown' | 'plain', default: 'markdown'\n});\n```\n\nWhen `agentInstructionFilesChanged` is non-empty, renders a visible `⚠️ WARNING` before the normal details.\n\n---\n\n### Presets\n\n```typescript\nimport { presets } from '@animakit/git-guardrails';\n\n// Production-exact patterns from Anima Body (53 sprints):\npresets.blockedShellPatterns        // sudo, rm -rf /, curl|bash, etc.\npresets.sensitiveFilePatterns       // .env, .pem, .key, .pfx, .p12, etc.\npresets.agentInstructionFilePatterns // CLAUDE.md, AGENTS.md, .cursorrules, etc.\npresets.animaProductionAllowedDirs  // Reference paths — replace with your own\n\n// Use as base + extend:\nisCommandSafe(cmd, cwd, {\n  blockedPatterns: { extend: presets.blockedShellPatterns },\n  allowedDirs: ['/my/repos', '/tmp'],\n});\n```\n\n**Agent instruction patterns covered:**\n`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`, `.cursorrules`, `.cursor/rules/**`, `.windsurfrules`, `.clinerules`, `.github/copilot-instructions.md`, `mcp.json`, `.mcp.json`, `.claude/settings.json`, `.claude/settings.local.json`\n\n---\n\n## vs Alternatives\n\nResearched before launch (June 2026) to avoid claiming \"nothing else exists.\"\n\n| Tool | What it is | Why it's different |\n|---|---|---|\n| **`agentpreflight`** (npm) | In-process, zero-dep function called before an agent tool call; has git rules (blocks `push --force`) and secret detection | Does I/O (not pure <1ms); blocks/warns rather than returning structured `confirmationDetails` for the caller to build their own human-in-the-loop flow; detects secrets by content pattern, not explicit file-name list; no directory allowlist for shell. **The closest competitor — cited by name.** |\n| **Boucle framework** (`bash-guard`+`git-safe`+`file-guard`) | Same 3 conceptual pillars | Bundle of bash/PowerShell scripts installed via curl into `~/.claude/hooks/`, tied exclusively to Claude Code's PreToolUse protocol — not an npm library, not framework-agnostic |\n| **ggshield** (GitGuardian) | Now has agent-aware hooks for Cursor/Claude Code/Codex | External CLI process invoked via per-tool hooks — not an in-process importable function |\n| Claude Code / Cursor / Windsurf / Aider / Open Interpreter / LangGraph / AutoGPT | Each has own permission/confirmation system | Locked into the product's config or requires running inside their SDK — not extractable to a custom agent |\n| Microsoft Agent 365, Permit.io, CalypsoAI, Lakera | Fleet governance in production | SaaS/platform with account and backend — not an inline function with zero setup |\n| Guardrails AI | LLM output validation (JSON, toxicity, PII) | Different layer — doesn't touch shell commands or git |\n| git-secrets / gitleaks / talisman | Secret scanning | Git hooks for humans, not importable functions inside an agent loop |\n\n**The gap `git-guardrails` fills:** a framework-agnostic, zero-dep, in-process library that returns *structured data* (not just allow/block) so your agent can route to a human-in-the-loop confirmation channel of your choice — Telegram, Slack, CLI, Discord, email — without any coupling to a specific channel or agent runtime.\n\n---\n\n## Design decisions\n\n**Why `repoPath` instead of `repo` + `REPOS_DIR`?**\nThe original Anima Body GitWorker resolved `join(config.REPOS_DIR, params.repo)` — coupling it to Anima's specific directory structure. The package receives a caller-supplied absolute `repoPath`, making it usable in any project layout.\n\n**Why `runShell()` doesn't call `isCommandSafe()` automatically?**\nKeeping them separate makes the safety boundary explicit in calling code — aligned with the philosophy \"this is the human-in-the-loop, make it visible.\" You can see exactly where the guardrail is applied.\n\n**Why structured `confirmationDetails` instead of a pre-built prompt string?**\nThe original GitWorker built a Spanish Telegram-markdown string inline in each `case`. The package returns raw data (`message`, `diffPreview`, `pendingCommits`, `agentInstructionFilesChanged`) so you can render it for any channel and language. `formatConfirmationPrompt()` is optional.\n\n---\n\n## License\n\nApache-2.0 — see [LICENSE](./LICENSE).\n\n> **Note for monorepo maintainers:** The sibling packages (`@animakit/homeostasis`, `@animakit/complexity-scorer`) are MIT-licensed. `@animakit/git-guardrails` ships under Apache-2.0 per the original SPEC. Please confirm whether this should be harmonized to MIT before publishing.\n","readmeFilename":"README.md","_rev":"1-198f736e43c727d2274719f2e1dece0e"}