{"owner":"Dicklesworthstone","repo":"destructive_command_guard","hasSkills":true,"totalSkillsCount":3,"totalTokensCount":22446,"categories":["anthropic-skill","root-instruction"],"hasMcp":false,"mcpConfig":null,"found":["SKILL.md","docs/agents.md","AGENTS.md"],"skills":{"SKILL.md":"---\nname: dcg\ndescription: \"Destructive Command Guard - High-performance Rust hook for Claude Code that blocks dangerous commands before execution. SIMD-accelerated, modular pack system, whitelist-first architecture. Essential safety layer for agent workflows.\"\n---\n\n# DCG — Destructive Command Guard\n\nA high-performance Claude Code hook that intercepts and blocks destructive commands before they execute. Written in Rust with SIMD-accelerated filtering for sub-millisecond latency.\n\n## Why This Exists\n\nAI coding agents are powerful but fallible. They can accidentally run destructive commands:\n\n- **\"Let me clean up the build artifacts\"** → `rm -rf ./src` (typo)\n- **\"I'll reset to the last commit\"** → `git reset --hard` (destroys uncommitted changes)\n- **\"Let me fix the merge conflict\"** → `git checkout -- .` (discards all modifications)\n- **\"I'll clean up untracked files\"** → `git clean -fd` (permanently deletes untracked files)\n\nDCG intercepts dangerous commands *before* execution and blocks them with a clear explanation, giving you a chance to stash your changes first.\n\n## Critical Design Principles\n\n### 1. Whitelist-First Architecture\n\nSafe patterns are checked *before* destructive patterns. This ensures explicitly safe commands are never accidentally blocked:\n\n```\ngit checkout -b feature    →  Matches SAFE \"checkout-new-branch\"  →  ALLOW\ngit checkout -- file.txt   →  No safe match, matches DESTRUCTIVE  →  DENY\n```\n\n### 2. Fail-Safe Defaults (Default-Allow)\n\nUnrecognized commands are **allowed by default**. This ensures:\n- The hook never breaks legitimate workflows\n- Only *known* dangerous patterns are blocked\n- New git commands work until explicitly categorized\n\n### 3. Zero False Negatives Philosophy\n\nThe pattern set prioritizes **never allowing dangerous commands** over avoiding false positives. A few extra prompts for manual confirmation are acceptable; lost work is not.\n\n## What It Blocks\n\n### Git Commands That Destroy Uncommitted Work\n\n| Command | Reason |\n|---------|--------|\n| `git reset --hard` | Destroys uncommitted changes |\n| `git reset --merge` | Destroys uncommitted changes |\n| `git checkout -- <file>` | Discards file modifications |\n| `git restore <file>` (without `--staged`) | Discards uncommitted changes |\n| `git clean -f` | Permanently deletes untracked files |\n\n### Git Commands That Destroy Remote History\n\n| Command | Reason |\n|---------|--------|\n| `git push --force` / `-f` | Overwrites remote commits |\n| `git branch -d`, `--delete`, `-D`, `-f`, `-M`, `-C` | Deletes or force-overwrites a user-owned branch ref |\n\n### Git Commands That Destroy Stashed Work\n\n| Command | Reason |\n|---------|--------|\n| `git stash drop` | Permanently deletes a stash |\n| `git stash clear` | Permanently deletes all stashes |\n\n### Filesystem Commands\n\n| Command | Reason |\n|---------|--------|\n| `rm -rf` (outside `/tmp`, `/var/tmp`, `$TMPDIR`) | Recursive deletion is dangerous |\n\n## What It ALLOWS\n\nSafe operations pass through silently:\n\n### Always Safe Git Operations\n\n`git status`, `git log`, `git diff`, `git add`, `git commit`, `git push`, `git pull`, `git fetch`, read-only branch listings, `git stash`, `git stash pop`, `git stash list`\n\n### Explicitly Safe Patterns\n\n| Pattern | Why Safe |\n|---------|----------|\n| `git checkout -b <branch>` | Creating new branches |\n| `git checkout --orphan <branch>` | Creating orphan branches |\n| `git restore --staged <file>` | Unstaging only, doesn't touch working tree |\n| `git restore -S <file>` | Short flag for staged |\n| `git clean -n` / `--dry-run` | Preview mode, no actual deletion |\n| `rm -rf /tmp/*` | Temp directories are ephemeral |\n| `rm -rf $TMPDIR/*` | Shell variable forms |\n\n### Safe Alternative: `--force-with-lease`\n\n```bash\ngit push --force-with-lease   # ALLOWED - refuses if remote has unseen commits\ngit push --force              # BLOCKED - can overwrite others' work\n```\n\n## Modular Pack System\n\nDCG uses a modular \"pack\" system to organize patterns by category:\n\n### Core Packs (Always Enabled)\n\n| Pack | Description |\n|------|-------------|\n| `core.git` | Destructive git commands |\n| `core.filesystem` | Dangerous rm -rf outside temp |\n\n### Database Packs\n\n| Pack | Description |\n|------|-------------|\n| `database.postgresql` | DROP/TRUNCATE in PostgreSQL |\n| `database.mysql` | DROP/TRUNCATE in MySQL/MariaDB |\n| `database.mongodb` | dropDatabase, drop() |\n| `database.redis` | FLUSHALL/FLUSHDB |\n| `database.sqlite` | DROP in SQLite |\n\n### Container Packs\n\n| Pack | Description |\n|------|-------------|\n| `containers.docker` | docker system prune, docker rm -f |\n| `containers.compose` | docker-compose down --volumes |\n| `containers.podman` | podman system prune |\n\n### Kubernetes Packs\n\n| Pack | Description |\n|------|-------------|\n| `kubernetes.kubectl` | kubectl delete namespace |\n| `kubernetes.helm` | helm uninstall |\n| `kubernetes.kustomize` | kustomize delete patterns |\n\n### Cloud Provider Packs\n\n| Pack | Description |\n|------|-------------|\n| `cloud.aws` | Destructive AWS CLI commands |\n| `cloud.gcp` | Destructive gcloud commands |\n| `cloud.azure` | Destructive az commands |\n\n### Infrastructure Packs\n\n| Pack | Description |\n|------|-------------|\n| `infrastructure.terraform` | terraform destroy |\n| `infrastructure.ansible` | Dangerous ansible patterns |\n| `infrastructure.pulumi` | pulumi destroy |\n\n### System Packs\n\n| Pack | Description |\n|------|-------------|\n| `system.disk` | dd, mkfs, fdisk operations |\n| `system.permissions` | Dangerous chmod/chown patterns |\n| `system.services` | systemctl stop/disable patterns |\n\n### Other Packs\n\n| Pack | Description |\n|------|-------------|\n| `strict_git` | Extra paranoid git protections |\n| `package_managers` | npm unpublish, cargo yank |\n\n### Configuring Packs\n\n```toml\n# ~/.config/dcg/config.toml\n[packs]\nenabled = [\n    \"database.postgresql\",\n    \"containers.docker\",\n    \"kubernetes\",  # Enables all kubernetes sub-packs\n]\n```\n\n## Environment Variables\n\n| Variable | Description |\n|----------|-------------|\n| `DCG_PACKS=\"containers.docker,kubernetes\"` | Enable packs (comma-separated) |\n| `DCG_DISABLE=\"kubernetes.helm\"` | Disable packs/sub-packs |\n| `DCG_VERBOSE=1` | Verbose output |\n| `DCG_COLOR=auto\\|always\\|never` | Color mode |\n| `DCG_BYPASS=1` | Bypass DCG entirely (escape hatch) |\n\n## Installation\n\n### Quick Install (Recommended)\n\n```bash\ncurl -fsSL \"https://raw.githubusercontent.com/Dicklesworthstone/destructive_command_guard/main/install.sh?$(date +%s)\" | bash\n\n# Easy mode: auto-update PATH\ncurl -fsSL \"https://raw.githubusercontent.com/Dicklesworthstone/destructive_command_guard/main/install.sh?$(date +%s)\" | bash -s -- --easy-mode\n\n# System-wide (requires sudo)\ncurl -fsSL \"https://raw.githubusercontent.com/Dicklesworthstone/destructive_command_guard/main/install.sh?$(date +%s)\" | sudo bash -s -- --system\n```\n\n### From Source (Requires Rust Nightly)\n\n```bash\ncargo +nightly install --git https://github.com/Dicklesworthstone/destructive_command_guard\n```\n\n### Prebuilt Binaries\n\nAvailable for: Linux x86_64, Linux ARM64, macOS Intel, macOS Apple Silicon, Windows\n\n## Claude Code Configuration\n\nAdd to `~/.claude/settings.json`:\n\n```json\n{\n  \"hooks\": {\n    \"PreToolUse\": [\n      {\n        \"matcher\": \"Bash|PowerShell|Monitor\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"/absolute/path/to/dcg\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\nReplace `/absolute/path/to/dcg` with the exact resolved binary path. Do not use\nbare `dcg`: non-interactive hook shells may not inherit the interactive\n`PATH`. On native Windows, use `install.ps1` so the hook receives the\nPowerShell-safe absolute command and explicit shell selection.\n\n**Important:** Restart Claude Code after adding the hook.\n\n## How It Works\n\n### Processing Pipeline\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│                        Claude Code                               │\n│  Agent executes `rm -rf ./build`                                │\n└─────────────────────┬───────────────────────────────────────────┘\n                      │\n                      ▼ PreToolUse hook (stdin: JSON)\n┌─────────────────────────────────────────────────────────────────┐\n│                          dcg                                     │\n│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐       │\n│  │    Parse     │───▶│  Normalize   │───▶│ Quick Reject │       │\n│  │    JSON      │    │   Command    │    │   Filter     │       │\n│  └──────────────┘    └──────────────┘    └──────┬───────┘       │\n│                                                  │               │\n│                      ┌───────────────────────────┘               │\n│                      ▼                                           │\n│  ┌──────────────────────────────────────────────────────────┐   │\n│  │                   Pattern Matching                        │   │\n│  │   1. Check SAFE_PATTERNS (whitelist) ──▶ Allow if match  │   │\n│  │   2. Check DESTRUCTIVE_PATTERNS ──────▶ Deny if match    │   │\n│  │   3. No match ────────────────────────▶ Allow (default)  │   │\n│  └──────────────────────────────────────────────────────────┘   │\n└─────────────────────┬───────────────────────────────────────────┘\n                      │\n                      ▼ stdout: JSON (deny) or empty (allow)\n```\n\n### Stage 1: JSON Parsing\n- Reads hook input from stdin\n- Validates Claude Code's `PreToolUse` format\n- Non-Bash tools immediately allowed\n\n### Stage 2: Command Normalization\n- Strips absolute paths: `/usr/bin/git status` → `git status`\n- Preserves argument paths\n\n### Stage 3: Quick Rejection Filter\n- SIMD-accelerated substring search for \"git\" or \"rm\"\n- Commands without these bypass regex entirely (99%+ of commands)\n\n### Stage 4: Pattern Matching\n- Safe patterns checked first (short-circuit on match → allow)\n- Destructive patterns checked second (match → deny)\n- No match → default allow\n\n## Exit Codes\n\n| Code | Meaning |\n|------|---------|\n| `0` | Command is safe, proceed |\n| `2` | Command is blocked, do not execute |\n\n## CLI Usage\n\nTest commands manually:\n\n```bash\n# Show version with build metadata\ndcg --version\n\n# Test a command\necho '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git reset --hard\"}}' | dcg\n```\n\n## Example Block Message\n\n```\n════════════════════════════════════════════════════════════════════════\nBLOCKED  dcg\n────────────────────────────────────────────────────────────────────────\nReason:  git reset --hard destroys uncommitted changes. Use 'git stash' first.\n\nCommand:  git reset --hard HEAD~1\n\nTip: If you need to run this command, execute it manually in a terminal.\n     Consider using 'git stash' first to save your changes.\n════════════════════════════════════════════════════════════════════════\n```\n\n### Contextual Suggestions\n\n| Command Type | Suggestion |\n|-------------|------------|\n| `git reset`, `git checkout --` | \"Consider using 'git stash' first\" |\n| `git clean` | \"Use 'git clean -n' first to preview\" |\n| `git push --force` | \"Consider using '--force-with-lease'\" |\n| `rm -rf` | \"Verify the path carefully before running manually\" |\n\n## Edge Cases Handled\n\n### Path Normalization\n\n```bash\n/usr/bin/git reset --hard          # Blocked\n/usr/local/bin/git checkout -- .   # Blocked\n/bin/rm -rf /home/user             # Blocked\n```\n\n### Flag Ordering Variants\n\n```bash\nrm -rf /path          # Combined flags\nrm -fr /path          # Reversed order\nrm -r -f /path        # Separate flags\nrm --recursive --force /path    # Long flags\n```\n\nAll variants are handled.\n\n### Shell Variable Expansion\n\n```bash\nrm -rf $TMPDIR/build           # Allowed (temp)\nrm -rf ${TMPDIR}/build         # Allowed\nrm -rf \"$TMPDIR/build\"         # Allowed\nrm -rf \"${TMPDIR:-/tmp}/build\" # Allowed\n```\n\n### Staged vs Worktree Restore\n\n```bash\ngit restore --staged file.txt    # Allowed (unstaging only)\ngit restore -S file.txt          # Allowed (short flag)\ngit restore file.txt             # BLOCKED (discards changes)\ngit restore --worktree file.txt  # BLOCKED (explicit worktree)\ngit restore -S -W file.txt       # BLOCKED (includes worktree)\n```\n\n## Performance Optimizations\n\nDCG is designed for zero perceived latency:\n\n| Optimization | Technique |\n|--------------|-----------|\n| **Lazy Static** | Regex patterns compiled once via `LazyLock` |\n| **SIMD Quick Reject** | `memchr` crate for CPU vector instructions |\n| **Early Exit** | Safe match returns immediately |\n| **Zero-Copy JSON** | `serde_json` operates on input buffer |\n| **Zero-Allocation** | `Cow<str>` for path normalization |\n| **Release Profile** | `opt-level=\"z\"`, LTO, single codegen unit |\n\n**Result:** Sub-millisecond execution for typical commands.\n\n## Pattern Counts\n\n| Type | Count |\n|------|-------|\n| Safe patterns (whitelist) | 34 |\n| Destructive patterns (blacklist) | 16 |\n\n## Security Considerations\n\n### What DCG Protects Against\n\n- Accidental data loss from `git checkout --` or `git reset --hard`\n- Remote history destruction from force pushes\n- Stash loss from `git stash drop/clear`\n- Filesystem accidents from `rm -rf` outside temp directories\n\n### What DCG Does NOT Protect Against\n\n- Malicious actors (can bypass the hook)\n- Non-Bash commands (Python/JavaScript file writes, API calls)\n- Committed but unpushed work\n- Commands inside scripts (`./deploy.sh` contents not inspected)\n\n### Threat Model\n\nDCG assumes the AI agent is **well-intentioned but fallible**. It catches honest mistakes, not adversarial attacks.\n\n## Troubleshooting\n\n### Hook not blocking commands\n\n1. Verify `~/.claude/settings.json` has hook configuration\n2. Restart Claude Code\n3. Test manually: `echo '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git reset --hard\"}}' | dcg`\n\n### Hook blocking safe commands\n\n1. Check if there's an edge case not covered\n2. File a GitHub issue\n3. Temporary bypass: `DCG_BYPASS=1` or run command manually\n\n## FAQ\n\n**Q: Why block both `git branch -d` and `git branch -D`?**\n\nLowercase `-d` checks merge state, but still removes the branch name, upstream-tracking configuration, and convenient reflog reference. Those are user-owned state, so every delete form requires explicit approval. Review first with `git branch -vv`, `git branch --merged`, and `git branch --no-merged`. The merge-checked form is its own rule, `core.git:branch-delete`, so `[policy.rules] \"core.git:branch-delete\" = \"ask\"` turns it into an approval prompt while `-D` (`core.git:branch-force-delete`) stays denied.\n\n**Q: Why is `git push --force-with-lease` allowed?**\n\nForce-with-lease refuses to push if the remote has commits you haven't seen, preventing accidental overwrites.\n\n**Q: Why block all `rm -rf` outside temp directories?**\n\nRecursive forced deletion is extremely dangerous. A typo or wrong variable can delete critical files. Temp directories are designed to be ephemeral.\n\n**Q: What if I really need to run a blocked command?**\n\nDCG instructs the agent to ask for permission. Run the command manually in a separate terminal after making a conscious decision.\n\n## Integration with Flywheel\n\n| Tool | Integration |\n|------|-------------|\n| **Claude Code** | Native PreToolUse hook |\n| **Agent Mail** | Agents can report blocked commands to coordinator |\n| **BV** | Flag tasks that repeatedly trigger DCG |\n| **CASS** | Search DCG block patterns across sessions |\n| **RU** | DCG protects agent-sweep from destructive commits |\n","docs/agents.md":"# Agent-Specific Profiles\n\ndcg can detect which AI coding agent is invoking it and apply agent-specific\ntrust levels and configuration overrides. This allows you to grant higher\ntrust to well-behaved agents while maintaining strict controls for unknown ones.\n\n## Supported Agents\n\n| Agent | Detection Method | Environment Variable |\n|-------|------------------|---------------------|\n| Claude Code | Environment | `CLAUDE_CODE=1` or `CLAUDE_SESSION_ID` |\n| Augment Code | Environment | `AUGMENT_AGENT=1` or `AUGMENT_CONVERSATION_ID` |\n| Aider | Environment | `AIDER_SESSION=1` |\n| Continue | Environment | `CONTINUE_SESSION_ID` |\n| Codex CLI | Environment | `CODEX_CLI=1` |\n| Gemini CLI | Environment | `GEMINI_CLI=1` |\n| GitHub Copilot CLI | Environment | `COPILOT_CLI=1` or `COPILOT_AGENT_START_TIME_SEC` |\n| VS Code Copilot Chat | Hook payload | `tool_name` is `runTerminalCommand`, `run_in_terminal`, or `runInTerminal`; the Agent Host's batched `toolCalls` envelope is also recognized, with every shell entry evaluated independently |\n| Cursor IDE | Environment | `CURSOR_IDE=1` (set by dcg's hook script) |\n| Hermes Agent | Environment | `HERMES_AGENT=1` or `HERMES_SESSION_ID` |\n| Grok (xAI) | Environment | `GROK_SESSION_ID`, `GROK_HOOK_EVENT`, or `GROK_WORKSPACE_ROOT` |\n| Oh My Pi (`omp`) | Explicit bridge / process | Generated extension passes `--agent omp`; exact `omp` and `oh-my-pi` process names are fallback matches |\n| Crush | Environment | `CRUSH=1` (set by Crush for hook subprocesses and `bash`-tool commands; the generic `AGENT`/`AI_AGENT` markers are not consulted). Hook payloads are recognized by their `event: \"PreToolUse\"` + `tool_input` envelope — see [crush-integration.md](crush-integration.md) |\n| Muse Code | Explicit flag only | The hook written by `dcg install --muse` passes `--agent muse` (Muse clears the hook environment). Its payload looks like Codex's (`turn_id`), so without the flag `[agents.codex-cli]` applies; the identity is never taken from the payload or a process name — see [muse-integration.md](muse-integration.md) |\n| Amp | Explicit bridge | The plugin written by `dcg install --amp` passes `--agent amp`; no environment marker or process name is consulted — see [amp-integration.md](amp-integration.md) |\n| Pi | Environment | `PI_CODING_AGENT=true` |\n| Posit Assistant | Environment | `PA_PROJECT_DIR` (set in hook subprocesses; checked last among environment markers so agents with their own markers win — environment detection still precedes parent-process detection) |\n\n## Detection Priority\n\nAgent detection follows this priority order:\n\n1. **Explicit `--agent` flag**: Manual override via CLI\n2. **Environment variables**: Most agents set identifying env vars\n3. **Parent process inspection**: Fallback check of process tree\n4. **Unknown**: Default when no agent is detected\n\n### Oh My Pi (`omp`)\n\nInstall dcg's native OMP ExtensionAPI module with:\n\n```bash\ndcg install --omp\n```\n\nThe default user path is `~/.omp/agent/extensions/dcg-guard.ts`. OMP named\nprofiles use `~/.omp/profiles/<name>/agent/extensions/dcg-guard.ts`; dcg follows\nOMP's `OMP_PROFILE`-before-`PI_PROFILE` precedence and honors\n`PI_CONFIG_DIR` for a config directory name relative to the user's home plus\n`PI_CODING_AGENT_DIR` for the default profile. Drive-qualified\n`PI_CONFIG_DIR` values are rejected on Windows because Rust would otherwise\nresolve them differently from OMP's home-relative `path.join` behavior. Use\n`dcg install --omp --project` to install `<cwd>/.omp/extensions/dcg-guard.ts`\ninstead. OMP checks only the current working directory for native project\nextensions; it neither requires Git nor walks to an ancestor, so launch OMP\nfrom that same directory.\n\nThe extension uses OMP's pre-execution `tool_call` event and sends each `bash`\ncommand to `dcg --robot test --stdin --agent omp --format json` with the dialect\nselected by OMP's execution route. Pinning the private bridge format prevents\nambient `DCG_FORMAT` from redirecting or invalidating its compact protocol\nwithout removing that variable from supported environment-conditioned policy.\nThe embedded install-time absolute dcg pathname is authoritative: ambient\n`DCG_BIN` cannot redirect the marker-owned guard. This binds a pathname, not a\nhash, inode/file ID, signature, or immutable executable object. Bun resolves\nthat pathname again for each tool call, so moving or removing the file produces\na visible infrastructure failure, while replacing bytes at the same pathname\nchanges what a later callback executes. `dcg doctor` compares the marker-owned\nextension with source generated for the doctor process's pathname at inspection\ntime; it does not attest binary contents or an extension already loaded by a\nrunning OMP session. To rebind deliberately, run\n`/desired/path/dcg install --omp --force` (add `--project` for project scope)\nand restart OMP. Protect the binary, extension, and their parent directories\nfrom writers not trusted to control OMP execution; a writer that can replace the\nextension can already run code inside OMP. Ordinary and managed-async calls use\nOMP's embedded Brush shell and pass `--dialect posix`, including on native\nWindows. An eligible local `pty: true` call instead maps OMP's configured\nexternal shell to `--dialect posix`, `cmd`, or `ps`; `PI_NO_PTY=1` disables that\nPTY route. A dcg deny, ask, or indeterminate result returns\n`{ block: true, reason }` to OMP.\nBecause the bridge supplies `--agent omp` explicitly, OMP remains distinct from\nlegacy Pi even when OMP exposes Pi-family compatibility variables. The\ncanonical profile key is `agents.omp`; `oh-my-pi` is accepted as an alias.\nWhen `[history] enabled = true`, these robot-boundary evaluations are persisted\nwith the canonical `agent_type = \"omp\"`; ordinary human `dcg test` diagnostics\nremain outside command history.\n\nThe bridge spawns dcg directly, without a shell, and gives Bun a 30-second\nparent-side timeout with a hard kill signal. Immediately after a successful\nspawn it records one monotonic absolute deadline 30.5 seconds away and arms one\nobservation watchdog. A direct child can exit while a descendant still holds\nits stdout or stderr descriptor, so exit settlement rearms that sole timer for\nthe lesser of a 250-millisecond pipe-drain grace and the remaining absolute\nbudget. If the remaining hard budget wins, expiry retains hard-deadline\nprovenance. Exit-observation rejection attempts one direct-child `SIGKILL` and\nuses the same clamped drain; neither rearm kills again. Every active watchdog is\ncleared when observation finishes, and late exit settlement or rejection\nremains consumed. There is no retry, replacement process, or second\nconcurrently active bridge watchdog.\n\nThis is the finite child-observation lineage:\n\n| Current state + event | Process action | Pipe/clock action | Result invariant |\n|---|---|---|---|\n| `Bun.spawn` throws | No child exists | No watchdog is armed | One visible spawn diagnostic; fail open |\n| Spawn succeeds | Bun owns the 30-second direct-child timeout | Record and arm the monotonic 30.5-second absolute observation deadline; read both capped pipes and exit concurrently | No classification before the bounded observation settles |\n| Complete deny/ask/indeterminate frame or stdout overflow arrives | None | Retain the frame or capped overflow evidence while observation continues | Blocking evidence is absorbing |\n| Exit resolves before the hard deadline | None; the direct child is already gone | Rearm for `min(250 ms, remaining hard budget)` | Bytes already read remain eligible evidence; the absolute deadline cannot move |\n| Exit rejects before the hard deadline | Attempt one direct-child `SIGKILL` | Rearm for `min(250 ms, remaining hard budget)` without another kill | The exit fault and any kill fault remain visible; kill request alone does not prove a signal |\n| Drain grace expires | The direct child has exited or already received the rejection-path kill | Cancel both local readers | Retained block/overflow still blocks; missing evidence follows visible infrastructure fail-open |\n| Hard observation deadline expires | Attempt one direct-child `SIGKILL` | Cancel both readers and the local exit wrapper | Retained block/overflow still blocks; deadline remains visible |\n| All three observations settle first | None | Clear the active watchdog | Read signal provenance, classify once, and emit each owned diagnostic once |\n\nIf a blocking frame and a deadline become runnable together, JavaScript's event\nloop imposes a total order: bytes delivered before reader cancellation are\nretained and absorbing; bytes delivered after cancellation are outside the\nobservation boundary. The stdout and stderr caps bound retained memory, and the\nouter/deferred deadlines bound a standards-compliant asynchronous observation.\nIf an underlying WHATWG cancellation algorithm rejects, the stream is still\nclosed and pending reads settle; the bridge consumes the cancellation promise's\nrejection while retaining prior deny/ask/indeterminate and overflow evidence.\nThe backstops sit above dcg's ordinary 1-second evaluator default and the broad\nWindows preset's 3-second default, not in place of those configurable budgets.\nAn explicit evaluator budget above 30 seconds is nevertheless capped on this\nOMP bridge; direct hook and diagnostic invocations retain their configured\nbudget.\n\nTwo seams remain fundamentally outside this in-process boundary. A synchronous\nstall inside `Bun.spawn` (or any JavaScript/event-loop stall) prevents a timer\nfrom starting or firing. Bun's `proc.kill()` targets the direct child, not a\nprocess group, so it cannot prove that descendants were terminated or release\ntheir unrelated resources; local reader cancellation bounds the callback even\nwhen an inherited pipe holder survives. A non-standard stream whose synchronous\ncancel path throws without settling a pending read is likewise outside the Web\nStreams contract and cannot be bounded by another JavaScript promise. After\nobservation, the bridge reads Bun's `signalCode` and reports an exact signal\nonly when Bun actually exposes one; a successful `proc.kill(\"SIGKILL\")` request\ndoes not itself forge SIGKILL provenance. This keeps an ordinary numeric exit\n137 distinct from an observed timeout or other observed `SIGKILL`.\nA complete deny, ask, or indeterminate verdict already written to stdout\nremains authoritative regardless of signal, cancellation, stream/exit fault,\nor deadline; stdout overflow and dcg's independent blocking exit 1 are likewise\nabsorbing. Other abnormal statuses and infrastructure faults remain visible\neven when blocking evidence controls the action.\n\nOMP's public ExtensionAPI does not expose its ACP client-terminal capability or\nselected backend, and both ACP and JSON-RPC surface as `mode: \"rpc\"`. The bridge\ntherefore does not infer a Windows dialect from mode: non-PTY RPC calls remain\nPOSIX-scoped so ordinary JSON-RPC execution does not gain Cmd/PowerShell false\npositives. As a result, an ACP client-terminal call does not yet receive exact\nCmd/PowerShell-specific coverage; closing that residual requires OMP to expose\nthe actual BashTool route before the `tool_call` handler runs.\n\n## Trust Levels\n\nThree trust levels label how much you trust a given agent:\n\n| Level | Description |\n|-------|-------------|\n| `high` | Agent has proven reliable; typically paired with a broader allowlist and fewer packs |\n| `medium` | Default; standard configuration |\n| `low` | Extra caution; typically paired with more packs and a restricted allowlist |\n\n### How trust levels work\n\nThe `trust_level` field is an **advisory label**. It is recorded in JSON output\nand shown in verbose/debug logs so you (and downstream tooling) can see what\ntrust tier was in effect for a given evaluation. It does **not**, by itself,\nchange which rules fire or how confidence scores are computed.\n\nAll behavioral differences between agents come from the other profile options\nthat you configure alongside the trust level:\n\n| Option | What it does | Typical usage |\n|--------|-------------|---------------|\n| `disabled_packs` | Removes packs (and their sub-packs) from evaluation | High-trust agents that don't need certain rule sets |\n| `extra_packs` | Adds packs to evaluation | Low-trust agents that should be checked against more rules |\n| `additional_allowlist` | Adds command patterns that bypass deny rules | High-trust agents with known-safe build commands |\n| `disabled_allowlist` | When `true`, ignores *all* allowlist entries (base + additional) | Low-trust agents that should never get a free pass |\n\nIn other words: setting `trust_level = \"high\"` alone does not relax any rules.\nYou must also adjust `disabled_packs`, `extra_packs`, `additional_allowlist`,\nor `disabled_allowlist` to change evaluation behavior.\n\n### Why the separation?\n\nThis design is intentional. Trust is not a magic knob -- different environments\nneed different trade-offs. A \"high trust\" agent in one project might need strict\ndatabase rules but relaxed filesystem rules, while in another project the\nopposite applies. By keeping the label separate from the behavioral knobs, dcg\ngives you full control without hidden side effects.\n\n### Practical examples\n\n**High-trust agent** -- a well-tested agent that runs routine build/test\ncommands. You widen the allowlist and disable packs that produce false positives\nfor its workflow:\n\n```toml\n[agents.claude-code]\ntrust_level = \"high\"\nadditional_allowlist = [\"npm run build\", \"cargo test\", \"make lint\"]\ndisabled_packs = [\"kubernetes\"]\n```\n\n**Medium-trust agent (default)** -- standard rules, no overrides:\n\n```toml\n[agents.default]\ntrust_level = \"medium\"\n```\n\n**Low-trust agent** -- an unknown or new agent. You add extra packs and disable\nthe allowlist so every command is evaluated against the full rule set:\n\n```toml\n[agents.unknown]\ntrust_level = \"low\"\ndisabled_allowlist = true\n# Real pack / category IDs (see `dcg packs` / docs/packs/README.md). A category\n# ID like \"database\" expands to every database.* sub-pack. \"paranoid\" is a\n# graduation mode, not a pack — use the real `strict_git` pack for stricter git\n# rules, and `core.filesystem` (not \"filesystem\") for the filesystem pack.\nextra_packs = [\"strict_git\", \"database\", \"system\"]\n```\n\n## Configuration\n\nConfigure agent profiles in your `config.toml`:\n\n```toml\n# Trust Claude Code more (it sets CLAUDE_CODE=1)\n[agents.claude-code]\ntrust_level = \"high\"\nadditional_allowlist = [\"npm run build\", \"cargo test\"]\n\n[agents.omp]\ntrust_level = \"medium\"\nextra_packs = [\"strict_git\"]\n\n# Restrict unknown agents\n[agents.unknown]\ntrust_level = \"low\"\ndisabled_allowlist = true\nextra_packs = [\"strict_git\", \"database\"]\n\n# Default profile for unspecified agents\n[agents.default]\ntrust_level = \"medium\"\n```\n\n### Profile Options\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `trust_level` | string | `\"medium\"` | Advisory label: `\"high\"`, `\"medium\"`, or `\"low\"`. Included in JSON/verbose output but does not change rule evaluation by itself. |\n| `disabled_packs` | array | `[]` | Pack or category IDs to remove from evaluation for this agent (a category ID drops every matching sub-pack). |\n| `extra_packs` | array | `[]` | Additional pack or category IDs to enable for this agent (a category ID expands to all its sub-packs). |\n| `additional_allowlist` | array | `[]` | Command patterns to allowlist for this agent (added on top of the base allowlist). |\n| `disabled_allowlist` | bool | `false` | If `true`, ignore all allowlist entries for this agent (more restrictive). |\n\n### Example: Agent Profiles for a Trusted Config\n\n```toml\n# In ~/.config/dcg/config.toml, or in a reviewed file selected with DCG_CONFIG\n[agents.unknown]\ntrust_level = \"low\"\ndisabled_allowlist = true\nextra_packs = [\"strict_git\", \"database\", \"system\"]\n\n[agents.claude-code]\ntrust_level = \"medium\"\nadditional_allowlist = [\"npm test\", \"npm run lint\"]\n```\n\nAutomatically discovered repository `.dcg.toml` files cannot define agent\nprofiles. Profiles can disable packs or add allowlist entries, so accepting\nthem from a newly cloned repository would cross dcg's security boundary. To\nuse a reviewed repository-owned profile deliberately, invoke dcg with\n`DCG_CONFIG=.dcg.toml` (or copy the profile into the user config).\n\n## Custom Agents\n\nDefine profiles for custom agents by setting an environment variable:\n\n```bash\n# Set a custom agent identifier\nexport MY_BUILD_BOT=1\n```\n\nThen configure in `config.toml`:\n\n```toml\n[agents.my-build-bot]\ntrust_level = \"high\"\nadditional_allowlist = [\"make deploy\"]\n```\n\n## Profile Resolution\n\nWhen resolving which profile to use:\n\n1. Look for exact match: `agents.<agent-config-key>`\n2. Fall back to `agents.unknown` if agent is unrecognized\n3. Fall back to `agents.default` if no specific profile exists\n\n## Verbose Output\n\nUse `--verbose` or `-v` to see agent detection info:\n\n```bash\n$ dcg test \"git push --force\" --verbose\nCommand: git push --force\n...\nElapsed: 21.14ms\nAgent: Claude Code\nTrust level: medium\nSeverity: critical\n```\n\nUse `-vv` for detailed debug output:\n\n```bash\n$ dcg test \"git push --force\" -vv\n...\nAgent detection:\n  Detected: Claude Code (claude-code)\n  Method: environment_variable\n  Matched: CLAUDE_CODE\n  Profile: agents.claude-code\n  Trust level: medium\n```\n\n## JSON Output\n\nThe `--format json` output includes agent information:\n\n```json\n{\n  \"command\": \"git push --force\",\n  \"decision\": \"deny\",\n  \"agent\": {\n    \"detected\": \"claude-code\",\n    \"trust_level\": \"medium\",\n    \"detection_method\": \"environment_variable\"\n  }\n}\n```\n\n## Robot Mode\n\nRobot mode provides a unified, machine-friendly interface for AI agents. When\nenabled, dcg optimizes its output for programmatic consumption.\n\n### Enabling Robot Mode\n\n```bash\n# Via flag\ndcg --robot test \"rm -rf /\"\n\n# Via environment variable\nDCG_ROBOT=1 dcg test \"rm -rf /\"\n```\n\n### Robot Mode Behavior\n\n| Aspect | Normal Mode | Robot Mode |\n|--------|-------------|------------|\n| stdout | JSON or pretty | Always JSON |\n| stderr | Rich colored output | Silent |\n| Exit codes | Varies | Standardized |\n| ANSI codes | If TTY | Never |\n| Progress | Shown | Hidden |\n| Suggestions | Shown | In JSON only |\n\n### Standardized Exit Codes\n\nIn robot mode, dcg uses consistent exit codes across all commands:\n\n| Code | Constant | Meaning |\n|------|----------|---------|\n| 0 | `EXIT_SUCCESS` | Success / Allow |\n| 1 | `EXIT_DENIED` | Command denied/blocked |\n| 2 | `EXIT_WARNING` | Warning (with --fail-on warn) |\n| 3 | `EXIT_CONFIG_ERROR` | Configuration error |\n| 4 | `EXIT_PARSE_ERROR` | Parse/input error |\n| 5 | `EXIT_IO_ERROR` | IO error |\n| 141 | `EXIT_BROKEN_PIPE` | stdout/stderr reader went away (`EPIPE`); a clean exit, never a signal death |\n\n### Robot Mode JSON Output\n\nAll robot-mode responses are pure JSON on stdout:\n\n```json\n{\n  \"command\": \"rm -rf /\",\n  \"decision\": \"deny\",\n  \"rule_id\": \"core.filesystem:rm-rf-root\",\n  \"pack_id\": \"core.filesystem\",\n  \"severity\": \"critical\",\n  \"reason\": \"rm -rf / would delete the entire filesystem\",\n  \"agent\": {\n    \"detected\": \"claude-code\",\n    \"trust_level\": \"medium\",\n    \"detection_method\": \"environment_variable\"\n  }\n}\n```\n\n### Hook Mode vs Robot Mode\n\n**Hook mode** (default when no subcommand) follows the active hook protocol:\n- Claude Code, Gemini CLI, Copilot CLI, VS Code Copilot Chat, and compatible JSON-hook protocols emit\n  JSON on stdout for denials and empty stdout for allows.\n- Codex CLI uses strict hook parsing, so dcg emits a minimal\n  `hookSpecificOutput` denial on stdout and exits 0.\n- Rich output always goes to stderr for human visibility.\n\nHook mode carries the verdict in stdout JSON and exits 0 whether the command\nwas allowed, warned about, sent for review, or denied. The exit status changes\nonly when a blocking verdict could not be delivered:\n\n| Code | Constant | Meaning |\n|------|----------|---------|\n| 0 | `EXIT_SUCCESS` | Verdict delivered on stdout (an allow is empty stdout). Also an allow or a warning whose stdout reader went away: nothing was lost. |\n| 2 | `EXIT_HOOK_BLOCK` | A deny, ask, or indeterminate verdict could not be written to stdout (`EPIPE`: the host closed the pipe early). Exit 0 with nothing on stdout would read as \"proceed\", so the block travels in the exit status instead, with the reason on stderr. |\n\nHow hosts read exit 2 with nothing on stdout:\n\n| Protocol | Effect |\n|----------|--------|\n| Claude Code and Claude-compatible hosts (Posit Assistant, Augment) | Blocks; stderr is fed back to the model as the reason |\n| Gemini CLI | Blocks (exit 2 is its blocking error) |\n| Copilot CLI | Blocks (`preToolUse` hooks that exit 2 deny the call) |\n| Crush | Blocks; stderr is the reason |\n| Grok | Blocks (exit 2 is a documented explicit deny) |\n| Codex CLI | Blocks in current releases (exit 2 with the reason on stderr is documented); some earlier builds logged it as a hook failure and failed open |\n| Hermes | Blocks in current releases (a `pre_tool_call` hook exiting 2 blocks even with no JSON on stdout); earlier builds only warned |\n| Reasonix | Blocks — exit 2 is its only blocking channel, so dcg always uses it there |\n| Muse Code | Not documented: a non-zero exit is a hook failure, which Muse hands to the handler's `onFailure` fallback when one is declared |\n| Antigravity (`agy`) | Logged as a hook failure, then fails open — the same outcome as exit 0 with no JSON, but visible |\n\nHook mode never exits 2 for any other reason, and never exits 141: every\nhook-mode write tolerates a closed pipe. `EXIT_BROKEN_PIPE` belongs to the\nCLI surface below.\n\n**Robot mode** with subcommands uses standardized exit codes:\n- Exit 1 for denials (allows scripting with `$?`)\n- Pure JSON on stdout\n- Silent stderr\n\n## Rich Output and Agent Compatibility\n\ndcg keeps agent-facing output and human-facing output on separate streams. This\nis the compatibility contract for rich terminal formatting.\n\n| Stream | Purpose | Hook-mode content | Robot-mode content |\n|--------|---------|-------------------|--------------------|\n| stdout | Agent and script parsing | Protocol JSON for denials, empty for allows | JSON only |\n| stderr | Human-visible diagnostics | Rich or plain text warning boxes | Silent |\n\nRich output is display-only. It must never be parsed by agents and must never be\nwritten to stdout. When dcg prints Unicode boxes, colors, highlighted commands,\nor suggestion panels, that output belongs on stderr.\n\n### Rich Output Selection\n\ndcg uses rich terminal formatting only when the runtime is suitable. It falls\nback to plain output when any of these controls are active:\n\n| Control | Effect |\n|---------|--------|\n| `DCG_NO_RICH=1` | Disable rich formatting while keeping normal command behavior |\n| `--legacy-output` or `DCG_LEGACY_OUTPUT=1` | Force legacy/plain rendering paths |\n| `NO_COLOR=1` or `DCG_NO_COLOR=1` | Disable colorized output |\n| `TERM=dumb` | Use dumb-terminal-safe output |\n| `CI=1` | Suppress rich interactive formatting in CI |\n| non-TTY stdout | Prefer plain output for pipeline-friendly behavior |\n| `--robot` or `DCG_ROBOT=1` | Emit machine-readable stdout and keep stderr silent |\n\n### Wrapper Guidance\n\nAgent wrappers should choose the interface that matches their parser:\n\n```bash\n# Hook integration: preserve both streams.\ndcg < hook-input.json >hook-stdout.json 2>human-warning.txt\n\n# Scripting integration: use robot mode and parse stdout only.\ndcg --robot test \"rm -rf /\" >decision.json 2>/dev/null\n```\n\nFor Codex and Claude-compatible hook integrations, parse stdout when it is\nnon-empty and treat empty stdout with exit 0 as allow. Codex's denial payload is\nminimal and intentionally omits dcg-only metadata.\n\n### Example: Agent Integration\n\n```bash\n#!/bin/bash\n# Script for AI agent to check commands before execution\n\ncheck_command() {\n    local cmd=\"$1\"\n    local result\n\n    # Use robot mode for predictable output\n    result=$(dcg --robot test \"$cmd\" 2>/dev/null)\n    local exit_code=$?\n\n    if [ $exit_code -eq 0 ]; then\n        echo \"Command allowed: $cmd\"\n        return 0\n    elif [ $exit_code -eq 1 ]; then\n        echo \"Command BLOCKED: $cmd\"\n        echo \"Reason: $(echo \"$result\" | jq -r '.reason')\"\n        return 1\n    else\n        echo \"Error checking command (exit code: $exit_code)\"\n        return $exit_code\n    fi\n}\n\n# Usage\ncheck_command \"git status\"      # Allowed\ncheck_command \"rm -rf /\"        # Blocked\n```\n\n### Unified Output Format\n\nRobot mode uses the unified `OutputFormat` enum:\n\n```bash\n# These are equivalent in robot mode\ndcg --robot test \"cmd\"\ndcg --robot --format json test \"cmd\"\n```\n\nAvailable formats:\n- `pretty` / `text` / `human` - Human-readable (default without --robot)\n- `json` / `sarif` / `structured` - JSON output (default with --robot)\n- `jsonl` - JSON Lines (one object per line, for streaming)\n- `compact` - Compact single-line output\n\n## Best Practices\n\n1. **Start with defaults**: The default `medium` trust level is safe for most\n   use cases.\n\n2. **Grant trust incrementally**: Only increase trust for agents after\n   observing their behavior.\n\n3. **Version reviewed profiles carefully**: A repository can keep a reviewed\n   profile in `.dcg.toml`, but it only takes effect when explicitly selected\n   with `DCG_CONFIG=.dcg.toml`; automatic discovery ignores agent profiles.\n\n4. **Restrict unknown agents**: Always configure `agents.unknown` with lower\n   trust in production environments.\n\n5. **Review the JSON output**: Use `--format json` in CI to audit which agents\n   are accessing your codebase.\n\n6. **Use robot mode for scripting**: When integrating dcg into automated\n   workflows, use `--robot` for consistent, parseable output.\n\n7. **Check exit codes**: In robot mode, use exit codes to make decisions\n   without parsing JSON for simple allow/deny checks.\n","AGENTS.md":"# AGENTS.md — dcg (Destructive Command Guard)\n\n> Guidelines for AI coding agents working in this Rust codebase.\n\n---\n\n## RULE 0 - THE FUNDAMENTAL OVERRIDE PREROGATIVE\n\nIf I tell you to do something, even if it goes against what follows below, YOU MUST LISTEN TO ME. I AM IN CHARGE, NOT YOU.\n\n---\n\n## RULE 0.5 - SUITE-WIDE RULES LIVE IN /data/projects/AGENTS.md\n\nThe suite-wide rules in **`/data/projects/AGENTS.md`** bind you here too. Read it. Two sections\nare load-bearing for perf work and are NOT duplicated below, so they cannot drift out of sync:\n\n- **`## Named Reward-Hacking Patterns (ALL FORBIDDEN)`** — 12 named patterns, several already\n  observed in this suite: gate self-weakening (and the exact price of a legitimate gate fix),\n  proof-class inflation, golden regeneration reflex, commit-stream pumping, tautological tests,\n  easy-lever cherry-picking, close-pump abuse, scope-splitting, spec-editing as progress,\n  conformance metastasis, dependency smuggling, bench-path hardcoding.\n- **`### Work-Graph Discipline`** — JSONL is truth and `beads.db` is disposable, `br sync\n  --import-only` after every pull, single-writer on graph structure, closure on cited evidence\n  with blocker beads gated on their named probe, `br dep cycles` stays empty.\n\nThe three that most often decide whether a number here is real: a **self-speedup is\nMAINTENANCE, not a win** — a win needs the incumbent live in the SAME invocation; **never\nweaken a gate to land a change**, and if a gate is genuinely defective, meet the evidence\nstandard and publish the win/lose split of what the fix admits; and **reporting a loss is a\nsuccess** — one line, revert, next lever, no retraction narrative.\n\n---\n\n## RULE NUMBER 1: NO FILE DELETION\n\n**YOU ARE NEVER ALLOWED TO DELETE A FILE WITHOUT EXPRESS PERMISSION.** Even a new file that you yourself created, such as a test code file. You have a horrible track record of deleting critically important files or otherwise throwing away tons of expensive work. As a result, you have permanently lost any and all rights to determine that a file or folder should be deleted.\n\n**YOU MUST ALWAYS ASK AND RECEIVE CLEAR, WRITTEN PERMISSION BEFORE EVER DELETING A FILE OR FOLDER OF ANY KIND.**\n\n---\n\n## Irreversible Git & Filesystem Actions — DO NOT EVER BREAK GLASS\n\n> **Note:** This project exists specifically to block these dangerous commands for AI agents. Practice what we preach.\n\n1. **Absolutely forbidden commands:** `git reset --hard`, `git clean -fd`, `rm -rf`, or any command that can delete or overwrite code/data must never be run unless the user explicitly provides the exact command and states, in the same message, that they understand and want the irreversible consequences.\n2. **No guessing:** If there is any uncertainty about what a command might delete or overwrite, stop immediately and ask the user for specific approval. \"I think it's safe\" is never acceptable.\n3. **Safer alternatives first:** When cleanup or rollbacks are needed, request permission to use non-destructive options (`git status`, `git diff`, `git stash`, copying to backups) before ever considering a destructive command.\n4. **Mandatory explicit plan:** Even after explicit user authorization, restate the command verbatim, list exactly what will be affected, and wait for a confirmation that your understanding is correct. Only then may you execute it—if anything remains ambiguous, refuse and escalate.\n5. **Document the confirmation:** When running any approved destructive command, record (in the session notes / final response) the exact user text that authorized it, the command actually run, and the execution time. If that record is absent, the operation did not happen.\n\n---\n\n## Git Branch: ONLY Use `main`, NEVER `master`\n\n**The default branch is `main`. The `master` branch exists only for legacy URL compatibility.**\n\n- **All work happens on `main`** — commits, PRs, feature branches all merge to `main`\n- **Never reference `master` in code or docs** — if you see `master` anywhere, it's a bug that needs fixing\n- **The `master` branch must stay synchronized with `main`** — after pushing to `main`, also push to `master`:\n  ```bash\n  git push origin main:master\n  ```\n\n**Why this matters:** The `dcg update` command and install URLs historically referenced `master`. If `master` falls behind `main`, users get stale code. We had a bug where `master` was **497 commits behind**, causing users to see old installer behavior.\n\n**If you see `master` referenced anywhere:**\n1. Update it to `main`\n2. Ensure `master` is synchronized: `git push origin main:master`\n\n---\n\n## Toolchain: Rust & Cargo\n\nWe only use **Cargo** in this project, NEVER any other package manager.\n\n- **Edition:** Rust 2024 (nightly required — see `rust-toolchain.toml`)\n- **Dependency versions:** Explicit versions for stability\n- **Configuration:** Cargo.toml only (single crate, not a workspace)\n- **Unsafe code:** Forbidden (`#![forbid(unsafe_code)]`)\n\n### Key Dependencies\n\n| Crate | Purpose |\n|-------|---------|\n| `serde` + `serde_json` | JSON parsing for Claude Code hook protocol |\n| `serde_yaml` | External pack YAML parsing |\n| `toml` + `toml_edit` | TOML config parsing with formatting preservation |\n| `fancy-regex` | Advanced regex with lookahead/lookbehind |\n| `regex` | `RegexSet` for heredoc detection |\n| `memchr` | SIMD-accelerated substring search |\n| `aho-corasick` | Multi-pattern string matching for keyword quick-reject |\n| `colored` | Terminal colors with TTY detection |\n| `clap` + `clap_complete` | CLI argument parsing with shell completions |\n| `chrono` | RFC 3339 timestamps |\n| `ast-grep-core` + `ast-grep-language` | AST-based pattern matching for heredoc/inline-script content |\n| `rusqlite` | Bundled upstream SQLite for best-effort telemetry history |\n| `rust-mcp-sdk` | MCP server integration (stdio transport) |\n| `tokio` | Async runtime for MCP server mode |\n| `ratatui` + `comfy-table` + `indicatif` + `console` | TUI/CLI visual polish |\n| `self_update` | Binary self-update from GitHub Releases |\n| `vergen-gix` | Build metadata embedding (build.rs) |\n| `tracing` + `tracing-subscriber` | Structured logging and diagnostics |\n| `sha2` + `hmac` | Hashing and HMAC for allow-once short codes |\n| `flate2` | Gzip compression for history export |\n\n### Release Profile\n\nThe release build optimizes for binary size:\n\n```toml\n[profile.release]\nopt-level = \"z\"     # Optimize for size (lean binary for distribution)\nlto = true          # Link-time optimization\ncodegen-units = 1   # Single codegen unit for better optimization\npanic = \"abort\"     # Smaller binary, no unwinding overhead\nstrip = true        # Remove debug symbols\n```\n\n### Feature Flags\n\n```toml\n[features]\nrayon = [\"dep:rayon\"]           # Rayon data parallelism (optional)\nrich-output = [\"dep:rich_rust\"] # Enable rich_rust for premium terminal output\nlegacy-output = []              # Keep old rendering (placeholder for gradual migration)\n```\n\n---\n\n## Code Editing Discipline\n\n### No Script-Based Changes\n\n**NEVER** run a script that processes/changes code files in this repo. Brittle regex-based transformations create far more problems than they solve.\n\n- **Always make code changes manually**, even when there are many instances\n- For many simple changes: use parallel subagents\n- For subtle/complex changes: do them methodically yourself\n\n### No File Proliferation\n\nIf you want to change something or add a feature, **revise existing code files in place**.\n\n**NEVER** create variations like:\n- `mainV2.rs`\n- `main_improved.rs`\n- `main_enhanced.rs`\n\nNew files are reserved for **genuinely new functionality** that makes zero sense to include in any existing file. The bar for creating new files is **incredibly high**.\n\n---\n\n## Backwards Compatibility\n\nWe do not care about backwards compatibility—we're in early development with no users. We want to do things the **RIGHT** way with **NO TECH DEBT**.\n\n- Never create \"compatibility shims\"\n- Never create wrapper functions for deprecated APIs\n- Just fix the code directly\n\n---\n\n## Compiler Checks (CRITICAL)\n\n**After any substantive code changes, you MUST verify no errors were introduced:**\n\n```bash\n# Check for compiler errors and warnings\ncargo check --all-targets\n\n# Check for clippy lints (pedantic + nursery are enabled)\ncargo clippy --all-targets -- -D warnings\n\n# Verify formatting\ncargo fmt --check\n```\n\nIf you see errors, **carefully understand and resolve each issue**. Read sufficient context to fix them the RIGHT way.\n\n---\n\n## Windows Support (native, `x86_64-pc-windows-msvc`)\n\ndcg ships a **native Windows** binary (built/tested on `windows-latest` with the\nnightly toolchain) and a `check (windows)` CI job. When touching anything\nplatform-sensitive, follow these conventions:\n\n- **Separate command-pattern DATA from dcg's own paths.** Destructive-command\n  patterns (`rm -rf /`, `normalize.rs` stripping `/usr/bin/git`, `/etc`, `/tmp`)\n  are DATA about Unix commands and must STAY — Windows users still run git-bash.\n  Only dcg's *own* config/state paths get Windows-ified (resolve via the `dirs`\n  crate; the system layer is `%ProgramData%\\dcg`, helper `config::system_config_dir()`).\n- **`.exe` suffix.** When constructing a path to the dcg binary, use\n  `env!(\"CARGO_BIN_EXE_dcg\")` / `assert_cmd::cargo::cargo_bin(\"dcg\")` in tests, or\n  `std::env::consts::EXE_SUFFIX` in `src`. **Never** a bare `push(\"dcg\")` — the\n  Windows CI job greps for it and fails. Resolve per-user paths only through\n  `config::home_dir()` / `user_config_dir()` / `user_data_dir()` /\n  `user_data_local_dir()` / `user_cache_dir()` — never `dirs::*` (clippy's\n  `disallowed-methods` rejects it) and never raw `HOME` (unset on Windows).\n  `dirs` resolves the Windows profile via the known-folder API and ignores\n  `USERPROFILE`, so a sandboxed Windows test run used to rewrite the operator's\n  real `~\\.claude\\settings.json` and allow-once store (bd-b2b1). Set\n  `USERPROFILE`/`TEMP`/`TMP` alongside `HOME` in test isolation; `USERPROFILE`\n  alone now relocates `%APPDATA%`/`%LOCALAPPDATA%` too when those are unset.\n- **Verify Windows branches from Linux** without a Windows box: `mingw` + the\n  `x86_64-pc-windows-gnu` target are installed, so\n  `cargo check --target x86_64-pc-windows-gnu --lib` (or `--bin dcg` / `--tests`)\n  compile-checks every `#[cfg(windows)]` path. When `pwsh` is installed, use it\n  to run the PowerShell installer/test scripts; otherwise run those gates on the\n  native Windows release host.\n- **Windows packs.** `src/packs/windows/` holds the native-Windows packs\n  (`windows.filesystem`/`windows.system` default-ON on Windows, `windows.misc`/\n  `windows.powershell` opt-in). Patterns use inline `(?i)`; keyword arrays\n  may retain conventional casing variants for readability, but keyword\n  quick-rejection is ASCII case-insensitive so mixed-case Windows spellings\n  cannot skip the regex stage (see `src/packs/windows/mod.rs`). See\n  [`docs/windows.md`](docs/windows.md).\n- **The `careful_company_running_windows` preset.**\n  `src/packs/careful_company_running_windows/` holds six opt-in sub-packs\n  covering **outbound communication and data egress** (email, chat/webhooks,\n  HTTP upload, file transfer, tunnels) plus **tampering with the controls that\n  supervise the agent** (Defender/firewall/EDR, audit logs, and dcg's own\n  bypass/uninstall). It is the only pack ID with *curated transitive\n  membership*: enabling it also enables the pinned\n  `CAREFUL_COMPANY_PRESET_MEMBERS` list in `src/packs/mod.rs`\n  (`windows.*`, `database.*`, `storage.*`, `remote.*`, `backup.*`, `secrets.*`,\n  `cloud.*`). That list is deliberately explicit — do **not** convert it to\n  prefix matching, or future packs will join a security posture silently.\n  Two invariants to preserve when touching this area:\n  - **Tier order.** `windows.*` is tier 11 and the preset is tier 12, so\n    Windows packs keep claiming attribution for commands both match. Rule ids\n    (`pack_id:pattern_name`) are what allowlists key on, so reordering these\n    silently invalidates existing `windows.*` allowlist entries.\n  - **The `hfdt` trust boundary.** While any preset sub-pack is enabled,\n    `src/evaluator.rs` allows a command whose executable is `hfdt` **before any\n    pack runs** — including `core.*`. It is structural (whole-segment\n    executable match, no chains/redirection/substitution), but it does mean\n    enabling the preset *reduces* coverage for that one executable. Keep it\n    documented in `README.md` and `docs/careful-company-windows.md`.\n\n---\n\n## Testing\n\n### Testing Policy\n\nEvery module includes inline `#[cfg(test)]` unit tests alongside the implementation. Tests must cover:\n- Happy path\n- Edge cases (empty input, max values, boundary conditions)\n- Error conditions\n\nEnd-to-end tests live in `scripts/e2e_test.sh`.\n\n### Unit Tests\n\nThe test suite includes 80+ tests covering all functionality:\n\n```bash\n# Run all tests\ncargo test\n\n# Run with output\ncargo test -- --nocapture\n\n# Run specific test module\ncargo test normalize_command_tests\ncargo test safe_pattern_tests\ncargo test destructive_pattern_tests\n```\n\n**Prefer `cargo nextest run` when touching anything that mutates the\nenvironment.** CI runs `cargo nextest run --profile ci`, and nextest gives each\ntest its own process — verified by observation, not assumption: sampling a\n`--lib` run showed up to 128 concurrent per-test `--exact` invocations. That\nprocess isolation is what makes the `unsafe { env::set_var }` sites in test code\nsound, because it removes the concurrent reader.\n\nUnder plain `cargo test` every test shares one process and runs on threads, and\nthe crate has ~109 `env::var` call sites against ~22 `EnvVarGuard` uses, none of\nthe readers taking the lock — plus native code (bundled SQLite reading `TMPDIR`)\nthat cannot take a Rust lock even in principle. So `cargo test` is the\nconfiguration the single crate-wide `ENV_LOCK` cannot actually make safe (#445).\nNo failure has been observed from this; the race is available, not active.\n\n`cargo test` stays documented above because filter and `--ignored` syntax differ\nbetween the two runners (nextest wants `--run-ignored`), and several workflows in\nthis file depend on the `cargo test` spelling.\n\n### The Three Release-Blocking E2E Suites (read before touching perf or protocols)\n\n`cargo test` cannot catch the failure modes that have actually broken users.\nThree real-binary, no-mock suites exist specifically to close those gaps. All\nthree must be green before any release.\n\n| Suite | Catches | Why unit tests can't |\n|-------|---------|----------------------|\n| `scripts/e2e_harness_matrix.sh` | Wire/bridge breakage for **every** agent (Claude Code, Codex, Gemini, Copilot, Hermes, Grok, agy, Crush, OMP) | Unit tests call Rust functions; harnesses parse **bytes**. Asserts decision field + exit code + stdout/stderr separation per protocol against the real binary. |\n| `scripts/perf_baseline.py --assert-budget-ms` | **#245**: evaluator cost silently eating the fixed hook deadline | The perf job is a *relative* ratchet — a uniform slowdown just gets re-baselined. This gate asserts paired `full_eval − DCG_BYPASS` p95 against the **shipped** `HOOK_EVALUATION_BUDGET_MS` with a hermetic HOME and scrubbed `DCG_*`; raw process latency remains separate evidence. |\n| `scripts/e2e_fleet_install.sh` | Published artifact missing/unrunnable per platform; installer picking the wrong triple; checksum/signature verification silently skipped; hook config non-idempotent | Nothing in-tree proves the **public download path** works on real Linux/macOS/Windows hardware. |\n\n```bash\n# Protocol/bridge conformance for all 8 harnesses (needs a release binary + jq)\n./scripts/e2e_harness_matrix.sh --binary target/release/dcg\n\n# Absolute evaluator-cost gate — the #245 guard. Budget MUST come from src/perf.rs.\nBUDGET_MS=$(sed -nE 's/^pub const HOOK_EVALUATION_BUDGET_MS: u64 = ([0-9_]+);$/\\1/p' src/perf.rs | tr -d '_')\npython3 scripts/perf_baseline.py --bin target/release/dcg --skip-trace \\\n  --assert-budget-ms \"$BUDGET_MS\" --assert-margin-pct 50\n\n# Real installs from the PUBLIC release on every DSR host\n./scripts/e2e_fleet_install.sh --version vX.Y.Z          # whole fleet\n./scripts/e2e_fleet_install.sh --version vX.Y.Z --local-only\n```\n\nRules:\n- **Scrub ambient `DCG_*` before measuring anything.** Operators bitten by #245\n  export `DCG_HOOK_TIMEOUT_MS=5000` (an agent `settings.json` `env` block puts\n  it in every child process), so an un-scrubbed suite measures the *workaround*\n  and passes on exactly the machines that need protecting. `env -i` covers the\n  hook calls; the installer cannot use it (it needs the host PATH for\n  `curl`/`tar`/`xz`/`minisign`), so the probes also `unset` every `DCG_*` up\n  front. Assert `general.hook_timeout_source` too — a bare `>= 1000` check\n  cannot tell the shipped default from an inherited 5000.\n- **Set `DCG_SELF_HEAL_HOOK=0` before the installer runs, not after.** dcg\n  repairs a missing/stale hook entry whenever it runs in hook mode, and\n  released binaries up to v0.14.4 resolve the Windows settings path via the\n  Win32 known-folder API, which `USERPROFILE` cannot redirect — so a late\n  disable can rewrite a real machine's agent config. Current source honors\n  `USERPROFILE` (bd-b2b1), but the installer may still fetch an older release.\n- **Never hard-code the budget in `.github/workflows/ci.yml`.** It is grepped\n  out of `HOOK_EVALUATION_BUDGET_MS`; `perf::tests::ci_enforces_absolute_latency_gate_against_shipped_budget`\n  fails if that wiring is removed or the margin is loosened past 60%.\n- **Treat the JSON as the certificate, not stderr.** Gate mode records its\n  supplied/shipped/effective budgets, margin, derived limit, every per-case\n  verdict, 95/95 binomial tail-tolerance result, violations, and overall\n  PASS/FAIL in `latency_gate`; CI retains that artifact even when the gate\n  fails. Gate mode requires at least 59 samples; CI uses 100 and permits at\n  most one over-limit sample per case. When using `--output` in gate mode,\n  place it outside the repository; the harness rejects in-tree output so its\n  own certificate cannot dirty the source snapshot it claims to measure.\n- **Bind the binary to the checkout.** Gate mode requires a clean checkout and\n  exact equality between the binary's embedded `git describe --tags --dirty`\n  value and the repository's value. CI uses a full tag history so a shallow\n  clone cannot turn this proof into an unknown result.\n- Measure dcg's own cost as `full_eval − DCG_BYPASS`, never raw wall-clock:\n  process spawn (≈940ms under Windows PowerShell) sits **outside** the\n  evaluation deadline and would otherwise produce false alarms. For host\n  safety this certificate sets `DCG_SELF_HEAL_HOOK=0`, records that exclusion,\n  and therefore does not claim to measure self-healing work. Capture and\n  validate every timed child's actual wire decision after stopping its timer;\n  before/after semantic controls alone cannot catch intermittent fail-open\n  behavior inside the sample window.\n- The fleet suite installs into a scratch prefix with an isolated `HOME` and\n  `--no-configure`; it never touches a host's real agent hook config.\n- A probe that dies partway must FAIL, not pass: every probe emits\n  `probe_complete` and the runner asserts the full expected case set.\n\n### End-to-End Testing\n\n```bash\n# Run the E2E test script (needs bash >= 4; macOS /bin/bash 3.2 breaks the summary)\n./scripts/e2e_test.sh\n\n# Or test manually\necho '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git reset --hard\"}}' | cargo run --release\n# Should output JSON denial\n\necho '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git status\"}}' | cargo run --release\n# Should output nothing (allowed)\n```\n\n### Test Categories\n\n| Module | Tests | Purpose |\n|--------|-------|---------|\n| `normalize_command_tests` | 8 | Path stripping for git/rm binaries |\n| `quick_reject_tests` | 5 | Fast-path filtering for non-git/rm commands |\n| `safe_pattern_tests` | 16 | Whitelist accuracy |\n| `destructive_pattern_tests` | 20 | Blacklist coverage |\n| `input_parsing_tests` | 8 | JSON parsing robustness |\n| `deny_output_tests` | 2 | Output format validation |\n| `integration_tests` | 4 | End-to-end pipeline |\n| `optimization_tests` | 9 | Performance paths |\n| `edge_case_tests` | 24 | Real-world edge cases |\n\n---\n\n## Third-Party Library Usage\n\nIf you aren't 100% sure how to use a third-party library, **SEARCH ONLINE** to find the latest documentation and current best practices.\n\n---\n\n## dcg (Destructive Command Guard) — This Project\n\n**This is the project you're working on.** dcg is a high-performance Claude Code hook that blocks destructive commands before they execute. It protects against dangerous git commands, filesystem operations, database queries, container commands, and more through a modular pack system.\n\n### What It Does\n\nGuards AI coding agents from executing destructive commands by intercepting Claude Code's `PreToolUse` hook protocol, evaluating commands against safe/destructive pattern lists, and denying dangerous operations with structured JSON output including remediation suggestions.\n\n### Architecture\n\n```\nJSON Input → Parse → Quick Reject (memchr) → Normalize → Safe Patterns → Destructive Patterns → Default Allow\n```\n\n### Key Files\n\n| File | Purpose |\n|------|---------|\n| `src/main.rs` | Entry point, hook I/O, CLI dispatch |\n| `src/evaluator.rs` | Pattern matching engine (safe + destructive evaluation) |\n| `src/hook.rs` | Claude Code PreToolUse hook protocol handling |\n| `src/normalize.rs` | Command normalization (path stripping, alias expansion) |\n| `src/heredoc.rs` | Heredoc and inline script extraction |\n| `src/ast_matcher.rs` | AST-based pattern matching for embedded code |\n| `src/config.rs` | Configuration loading (TOML, allowlists, pack enable/disable) |\n| `src/allowlist.rs` | Allowlist management (project, user, system scopes) |\n| `src/cli.rs` | CLI commands (explain, scan, packs, allowlist, etc.) |\n| `src/scan.rs` | Codebase scanning for destructive patterns |\n| `src/context.rs` | Contextual analysis for pattern matching |\n| `src/confidence.rs` | Match confidence scoring |\n| `src/error_codes.rs` | Standardized DCG-XXXX error codes |\n| `src/exit_codes.rs` | Process exit code definitions |\n| `src/packs/` | Modular pattern pack system (core + extensions) |\n| `src/output/` | Output formatting (JSON, colorful stderr) |\n| `src/highlight.rs` | Syntax highlighting for command display |\n| `src/logging.rs` | Tracing/logging configuration |\n| `src/perf.rs` | Performance budgets and benchmarks |\n| `src/simulate.rs` | Command simulation and dry-run support |\n| `src/mcp.rs` | MCP server integration |\n| `src/agent.rs` | Agent detection and identification |\n| `src/interactive.rs` | Interactive mode |\n| `src/git.rs` | Git-specific command analysis |\n| `src/history/` | Decision history and telemetry |\n| `src/sarif.rs` | SARIF output format for scan results |\n| `src/pending_exceptions.rs` | Pending exception management |\n| `src/lib.rs` | Library re-exports |\n| `Cargo.toml` | Dependencies and release optimizations |\n| `build.rs` | Build script for version metadata (vergen) |\n| `rust-toolchain.toml` | Nightly toolchain requirement |\n| `scripts/e2e_test.sh` | End-to-end test script (hundreds of command scenarios) |\n\n### Output Style\n\nThis tool has two output modes:\n\n- **JSON to stdout:** For Claude Code hook protocol (`hookSpecificOutput` with `permissionDecision: \"deny\"`)\n- **Colorful warning to stderr:** For human visibility when commands are blocked\n\nOutput behavior:\n- **Deny:** Colorful warning to stderr + JSON to stdout\n- **Allow:** No output (silent exit)\n- **--version/-V:** Version info with build metadata to stderr\n- **--help/-h:** Usage information to stderr\n\nColors are automatically disabled when stderr is not a TTY (e.g., piped to file).\n\n### Pattern System\n\n- **34 safe patterns** (whitelist, checked first)\n- **16 destructive patterns** (blacklist, checked second)\n- **Default allow** for unmatched commands\n\n### Adding New Patterns\n\n1. Identify the command to block/allow\n2. Write a regex using `fancy-regex` syntax (supports lookahead/lookbehind)\n3. Add to `SAFE_PATTERNS` or `DESTRUCTIVE_PATTERNS` using the macros:\n\n```rust\n// Safe pattern (whitelist)\npattern!(\"pattern-name\", r\"regex-here\")\n\n// Destructive pattern (blacklist)\ndestructive!(\n    r\"regex-here\",\n    \"Human-readable reason for blocking\"\n)\n```\n\n4. Add tests for all variants\n5. Run `cargo test` and `./scripts/e2e_test.sh`\n\n### Performance Requirements\n\nEvery Bash command passes through this hook. Performance is critical:\n\n- Quick rejection filter eliminates 99%+ of commands before regex\n- Lazy-initialized static regex patterns (compiled once, reused)\n- Sub-millisecond execution for typical commands\n- Zero allocations on the hot path for safe commands\n\n### Heredoc Detection Notes\n\n- **Rule IDs**: Heredoc patterns use stable IDs like `heredoc.python.shutil_rmtree` for allowlisting.\n- **Bounded failure**: Heredoc parse/AST failures use the configured bounded\n  fallback; disabling fallback blocks. Absolute hook-deadline exhaustion and\n  incomplete nested evaluation return `Indeterminate`, never `Allow`.\n- **Tests**: Prefer targeted tests in `src/ast_matcher.rs` and `src/heredoc.rs`.\n  - `cargo test ast_matcher`\n  - `cargo test heredoc`\n  - Add positive and negative fixtures for each new pattern.\n\n---\n\n<!-- dcg-machine-readable-v1 -->\n\n## DCG Hook Protocol (Machine-Readable Reference)\n\n> This section provides structured documentation for AI agents integrating with dcg.\n\n### JSON Input Format\n\ndcg reads from stdin in Claude Code's `PreToolUse` hook format:\n\n```json\n{\n  \"tool_name\": \"Bash\",\n  \"tool_input\": {\n    \"command\": \"git reset --hard HEAD~5\"\n  }\n}\n```\n\n**Required fields:**\n- `tool_name`: Must be `\"Bash\"` for dcg to process (other tools are ignored)\n- `tool_input.command`: The shell command string to evaluate\n\n### JSON Output Format (Denial)\n\nWhen a command is blocked, dcg outputs JSON to stdout:\n\n```json\n{\n  \"hookSpecificOutput\": {\n    \"hookEventName\": \"PreToolUse\",\n    \"permissionDecision\": \"deny\",\n    \"permissionDecisionReason\": \"BLOCKED by dcg\\n\\nTip: dcg explain \\\"git reset --hard HEAD~5\\\"\\n\\nReason: git reset --hard destroys uncommitted changes\\n\\nExplanation: Rewrites history and discards uncommitted changes.\\n\\nRule: core.git:reset-hard\\n\\nIf this operation is truly needed, ask the user for explicit permission and have them run the command manually.\",\n    \"ruleId\": \"core.git:reset-hard\",\n    \"packId\": \"core.git\",\n    \"severity\": \"critical\",\n    \"confidence\": 1.0,\n    \"allowOnceCode\": \"a1b2c3\",\n    \"allowOnceFullHash\": \"sha256:abc123...\",\n    \"remediation\": {\n      \"safeAlternative\": \"git stash\",\n      \"explanation\": \"Use git stash to save your changes first.\",\n      \"allowOnceCommand\": \"dcg allow-once a1b2c3\"\n    }\n  }\n}\n```\n\n**Key fields for agent parsing:**\n| Field | Type | Description |\n|-------|------|-------------|\n| `permissionDecision` | `\"allow\"` \\| `\"deny\"` | The decision |\n| `ruleId` | `string` | Stable pattern ID (e.g., `\"core.git:reset-hard\"`) for allowlisting |\n| `packId` | `string` | Pack that matched (e.g., `\"core.git\"`) |\n| `severity` | `string` | `\"critical\"`, `\"high\"`, `\"medium\"`, or `\"low\"` |\n| `confidence` | `number?` | Match confidence 0.0–1.0 (two decimals) from `crate::confidence::compute_match_confidence`, the score the `[confidence]` warn threshold reads: 1.0 when the matched span is executed (command word, wrapper-invoked command, redirection target), lower when it sits in quoted data, a comment, or an inert command's arguments. Omitted when the match has no source span (e.g. inside `$(…)`, structural verdicts). |\n| `allowOnceCode` | `string` | Short code for `dcg allow-once` |\n| `remediation.safeAlternative` | `string?` | Suggested safe command |\n\n### JSON Output Format (Allow)\n\nWhen a command is allowed: **no output** (silent exit 0).\n\n---\n\n## Exit Codes Reference\n\n| Code | Meaning | Agent Action |\n|------|---------|--------------|\n| `0` | Command allowed OR protocol JSON denial was emitted | Parse stdout; if empty, command was allowed |\n| `1` | Parse error or invalid input | Retry with corrected input |\n| `2` | Configuration error | Check config syntax and stderr diagnostics |\n\n**Detection logic for agents:**\n```bash\noutput=$(echo \"$hook_input\" | dcg 2>/dev/null)\nif [ -z \"$output\" ]; then\n  echo \"ALLOWED\"\nelse\n  echo \"DENIED: $output\"\nfi\n```\n\nCodex CLI uses a stricter hook parser: blocked commands return a minimal\n`hookSpecificOutput` denial on stdout with exit code 0. See\n[`docs/codex-integration.md`](docs/codex-integration.md) for the Codex-specific\nprotocol notes.\n\n---\n\n## Error Codes Reference\n\nDCG uses standardized error codes in the format `DCG-XXXX` for machine-parseable error handling.\n\n### Error Categories\n\n| Range | Category | Description |\n|-------|----------|-------------|\n| DCG-1xxx | `pattern_match` | Pattern matching and evaluation errors |\n| DCG-2xxx | `configuration` | Configuration loading and parsing errors |\n| DCG-3xxx | `runtime` | Runtime and execution errors |\n| DCG-4xxx | `external` | External integration errors |\n\n### Common Error Codes\n\n| Code | Description | Typical Cause |\n|------|-------------|---------------|\n| `DCG-1001` | Pattern compilation failed | Invalid regex syntax in pattern |\n| `DCG-1002` | Pattern match timeout | Complex pattern taking too long |\n| `DCG-2001` | Config file not found | Missing configuration file |\n| `DCG-2002` | Config parse error | Invalid TOML/JSON syntax |\n| `DCG-2004` | Allowlist load error | Invalid allowlist file |\n| `DCG-3001` | JSON parse error | Malformed JSON input |\n| `DCG-3002` | IO error | File read/write failure |\n| `DCG-4001` | External pack load failed | Invalid external pack YAML |\n\n### Error JSON Structure\n\nWhen errors are returned in JSON format, they follow this structure:\n\n```json\n{\n  \"error\": {\n    \"code\": \"DCG-3001\",\n    \"category\": \"runtime\",\n    \"message\": \"JSON parse error: unexpected token at position 15\",\n    \"context\": {\n      \"position\": 15,\n      \"input_preview\": \"{ \\\"tool_name\\\": ...\"\n    }\n  }\n}\n```\n\n**Fields:**\n- `code`: Stable error code for programmatic handling\n- `category`: Error category (`pattern_match`, `configuration`, `runtime`, `external`)\n- `message`: Human-readable error description\n- `context`: Additional details (optional, varies by error type)\n\n---\n\n## Allowlist & Bypass Instructions\n\n### Temporary Bypass (24-hour allow-once)\n\nWhen a command is blocked, the output includes an `allowOnceCode`. Use it:\n\n```bash\ndcg allow-once <code>\n```\n\nThis allows the specific command for 24 hours in the current directory scope.\n\n### Permanent Allowlist (by rule ID)\n\nAdd a rule to the project allowlist:\n\n```bash\ndcg allowlist add <ruleId> --project\n# Example: dcg allowlist add core.git:reset-hard --project\n```\n\nAllowlist files (in priority order):\n1. `.dcg/allowlist.toml` (project)\n2. `~/.config/dcg/allowlist.toml` (user)\n3. `/etc/dcg/allowlist.toml` (system)\n\n### Bypass Environment Variable\n\nFor emergency bypass (use sparingly):\n\n```bash\nDCG_BYPASS=1 <command>\n```\n\n**Warning:** This disables all protection. Log and justify any usage.\n\n---\n\n## Pattern Quick Reference\n\n### Core Git Patterns (Always Enabled)\n\n| Pattern ID | Blocks | Severity |\n|------------|--------|----------|\n| `core.git:reset-hard` | `git reset --hard` | Critical |\n| `core.git:reset-merge` | `git reset --merge` | High |\n| `core.git:checkout-discard` | `git checkout -- <file>` | High |\n| `core.git:restore-discard` | `git restore <file>` (without `--staged`) | High |\n| `core.git:clean-force` | `git clean -f`, `git clean -fd` | High |\n| `core.git:force-push` | `git push --force`, `git push -f` | High |\n| `core.git:branch-delete` | `git branch -d`, `--delete` (merge-checked, no force) | High |\n| `core.git:branch-force-delete` | `git branch -D`, `--delete --force`, `-f`, `-M`, `-C` (a policy/allowlist entry for it also covers `branch-delete` unless that rule has its own) | High |\n| `core.git:stash-drop` | `git stash drop` — Medium, so the default policy **warns and lets it run** (the dropped stash stays recoverable via `git fsck` until gc) | Medium |\n| `core.git:stash-clear` | `git stash clear` | Critical |\n\n### Core Filesystem Patterns (Always Enabled)\n\n| Pattern ID | Blocks | Severity |\n|------------|--------|----------|\n| `core.filesystem:rm-rf-root` | `rm -rf /`, `rm -rf ~` | Critical |\n| `core.filesystem:rm-rf-general` | `rm -rf` outside temp dirs | High |\n\n### Safe Patterns (Whitelist - Always Allowed)\n\n| Pattern | Command | Why Safe |\n|---------|---------|----------|\n| `git-checkout-branch` | `git checkout -b <branch>` | Creates new branch |\n| `git-checkout-orphan` | `git checkout --orphan <branch>` | Creates orphan branch |\n| `git-restore-staged` | `git restore --staged <file>` | Only unstages, doesn't discard |\n| `git-clean-dry-run` | `git clean -n`, `git clean --dry-run` | Preview only |\n| `rm-tmp` | `rm -rf /tmp/*`, `/var/tmp/*` | Temp directory cleanup |\n\n### Pack Enable/Disable Examples\n\n```toml\n# ~/.config/dcg/config.toml\n[packs]\nenabled = [\n    \"database.postgresql\",    # Blocks DROP TABLE, TRUNCATE\n    \"kubernetes.kubectl\",     # Blocks kubectl delete namespace\n    \"cloud.aws\",              # Blocks aws ec2 terminate-instances\n]\n\ndisabled = [\n    \"containers.docker\",      # Disable Docker protection\n]\n```\n\nList all packs: `dcg packs --verbose`\n\n---\n\n## CLI Quick Reference for Agents\n\n| Command | Purpose |\n|---------|---------|\n| `dcg explain \"<command>\"` | Detailed trace of why command is blocked/allowed |\n| `dcg allow-once <code>` | Allow a blocked command for 24 hours |\n| `dcg allowlist add <ruleId> --project` | Permanently allow a rule |\n| `dcg packs` | List enabled packs |\n| `dcg packs --verbose` | List all packs with pattern counts |\n| `dcg scan .` | Scan codebase for destructive patterns |\n| `dcg --version` | Show version and build info |\n\n---\n\n## Agent Integration Checklist\n\nWhen integrating with dcg, ensure your agent:\n\n- [ ] Parses stdout for JSON denial responses\n- [ ] Handles empty stdout as \"command allowed\"\n- [ ] Uses `ruleId` for stable allowlisting (not pattern text)\n- [ ] Displays `remediation.safeAlternative` to users when available\n- [ ] Respects `severity` for prioritization (critical > high > medium > low)\n- [ ] Uses `dcg explain` before asking users to bypass\n\n---\n\n## JSON Schema Reference\n\nFormal JSON Schema definitions (Draft 2020-12) for all dcg output formats are available in `docs/json-schema/`:\n\n| Schema | Purpose |\n|--------|---------|\n| [`hook-output.json`](docs/json-schema/hook-output.json) | PreToolUse hook denial response format |\n| [`scan-results.json`](docs/json-schema/scan-results.json) | `dcg scan` command output format |\n| [`stats-output.json`](docs/json-schema/stats-output.json) | `dcg stats` command output format |\n| [`error.json`](docs/json-schema/error.json) | Error response formats for various commands |\n\nUse these schemas for:\n- Validating dcg output in automated pipelines\n- Generating type-safe client code\n- Understanding the complete output contract\n\n<!-- end-dcg-machine-readable -->\n\n---\n\n## CI/CD Pipeline\n\n### Jobs Overview\n\n| Job | Trigger | Purpose | Blocking |\n|-----|---------|---------|----------|\n| `check` | PR, push | Format, clippy, UBS, tests | Yes |\n| `coverage` | PR, push | Coverage thresholds | Yes |\n| `memory-tests` | PR, push | Memory leak detection | Yes |\n| `benchmarks` | push to main | Performance budgets | Warn only |\n| `e2e` | PR, push | End-to-end shell tests | Yes |\n| `scan-regression` | PR, push | Scan output stability | Yes |\n| `perf-regression` | PR, push | Process-per-invocation perf | Yes |\n\n### Check Job\n\nRuns format, clippy, UBS static analysis, and unit tests. Includes:\n- `cargo fmt --check` - Code formatting\n- `cargo clippy --all-targets -- -D warnings` - Lints (pedantic + nursery enabled)\n- UBS analysis on changed Rust files (warning-only, non-blocking)\n- `cargo nextest run` - Full test suite with JUnit XML report\n\n### Coverage Job\n\nRuns `cargo llvm-cov` and enforces the thresholds configured in\n`.github/workflows/ci.yml` (`OVERALL_MIN`, `EVALUATOR_MIN`, `HOOK_MIN`).\nThese are enforced gates, not aspirational targets:\n- **Overall:** >= 70%\n- **src/evaluator.rs:** >= 65%\n- **src/hook.rs:** >= 70%\n\nIf CI thresholds change, update this section in the same change. The\n`coverage_threshold_docs` test checks that these documented values stay in sync\nwith the workflow.\n\nCoverage is uploaded to Codecov for trend tracking. Dashboard: https://codecov.io/gh/Dicklesworthstone/destructive_command_guard\n\n### Memory Tests Job\n\nRuns dedicated memory leak tests with:\n- `--test-threads=1` for accurate measurements\n- Release mode for realistic performance\n- 1-2MB growth budgets per test\n\nTests include: hook input parsing, pattern evaluation, heredoc extraction, file extractors, full pipeline, and a self-test that verifies the framework catches leaks.\n\n### Benchmarks Job\n\nRuns on push to main only (benchmarks are noisy on PRs). Checks performance budgets from `src/perf.rs`:\n- Quick reject: < 50us panic\n- Fast path: < 500us panic\n- Pattern match: < 1ms panic\n- Heredoc extract: < 2ms panic\n- Full heredoc pipeline: < 20ms panic\n- Hook evaluation deadline: 1000ms (exhaustion is indeterminate, never a silent allow)\n\n### UBS Static Analysis\n\nUltimate Bug Scanner runs on changed Rust files. Currently warning-only (non-blocking) to tune for false positives. Configuration in `.ubsignore` excludes test/bench/fuzz directories.\n\n### Dependabot\n\nAutomated dependency updates configured in `.github/dependabot.yml`:\n- **Cargo dependencies:** Weekly (Monday 9am EST), 5 PR limit\n- **GitHub Actions:** Weekly (Monday 9am EST), 3 PR limit\n- **Grouping:** Minor/patch updates grouped; serde updates separate (more careful review)\n\n### Debugging CI Failures\n\n#### Coverage Threshold Failure\n1. Check which file(s) dropped below threshold in CI output\n2. Run `cargo llvm-cov --html` locally to see uncovered lines\n3. Add tests for uncovered code paths\n4. Download `coverage-report` artifact for full details\n\n#### Memory Test Failure\n1. Download `memory-test-output` artifact\n2. Check which test failed and growth amount\n3. Run locally: `cargo test --test memory_tests --release -- --nocapture --test-threads=1`\n4. Profile with valgrind if needed\n\n#### UBS Warnings\n1. Check ubs-output.log in CI summary\n2. Review flagged issues - may be false positives\n3. If valid issues, fix them; if false positives, add to `.ubsignore`\n\n#### E2E Test Failure\n1. Download `e2e-artifacts` artifact\n2. Check `e2e_output.json` for failing test details\n3. Run locally: `./scripts/e2e_test.sh --verbose`\n4. The step summary shows the first failure with output\n\n#### Benchmark Regression\n1. Download `benchmark-results` artifact\n2. Compare against budgets in `src/perf.rs`\n3. Profile locally with `cargo bench --bench heredoc_perf`\n4. Check for algorithmic regressions in hot path\n\n---\n\n## Release Process\n\nWhen fixes are ready for release, follow this process:\n\nThe steps below describe the normal GitHub Actions path. If Actions cannot run\nor a native Windows artifact must be built locally, the more detailed\n**Local/DSR Release and Windows Deployment Runbook** near the end of this file\nis authoritative.\n\n### 1. Verify CI Passes Locally\n\n```bash\ncargo fmt --check\ncargo check --all-targets\ncargo clippy --all-targets -- -D warnings\ncargo test\n```\n\n### 2. Commit Changes\n\n```bash\ngit add -A\ngit commit -m \"fix: description of fixes\n\n- List specific fixes\n- Include any breaking changes\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\"\n```\n\n### 3. Bump Version (if needed)\n\nThe version in `Cargo.toml` determines the release tag. A version may be reused\nafter a failed *local, pre-tag* attempt. Once its tag has been pushed or a\nrelease has been published, treat that version as immutable and bump to a new\npatch version instead of moving the tag or replacing signed assets.\n\n- **Patch** (0.2.10 -> 0.2.11): Bug fixes, no new features\n- **Minor** (0.2.x -> 0.3.0): New features, backward compatible\n- **Major** (0.x -> 1.0): Breaking changes\n\n### 4. Push and Trigger Release\n\n```bash\ngit push origin main\ngit push origin main:master  # Keep master in sync\n```\n\nThe `release-automation.yml` workflow will:\n1. Detect version change in `Cargo.toml`\n2. Create an annotated git tag (e.g., `v0.2.13`)\n3. Push the tag, which triggers `dist.yml`\n\nThe `dist.yml` workflow will:\n1. Run tests and clippy\n2. Build binaries for all platforms (Linux x86/ARM, macOS Intel/Apple Silicon, Windows)\n3. Create `.tar.xz` archives with SHA256 checksums\n4. Sign artifacts with Sigstore (cosign) - creates `.sigstore.json` bundles\n5. Upload everything to GitHub Releases\n\n### 5. Verify Release\n\n```bash\ngh release list --limit 5\ngh release view v0.2.13  # Check assets were uploaded\n```\n\nExpected assets per release:\n- `dcg-{target}.tar.xz` (Unix) or `.zip` (Windows) - Binary archive\n- `<archive>.sha256` - Mandatory per-artifact checksum\n- `<archive>.sigstore.json` - Sigstore signature bundle\n- `install.sh`, `install.ps1`, and their checksum/Sigstore sidecars\n- Manual DSR releases additionally include minisign signatures, SLSA\n  provenance, a build manifest, and the pinned public verification keys\n\n### Troubleshooting Failed Releases\n\nIf CI fails:\n1. Check workflow run: `gh run list --workflow=dist.yml --limit=5`\n2. View failed job: `gh run view <run-id>`\n3. Fix issues locally, commit, and push again\n4. If no public tag or release exists yet, retry the same version; otherwise\n   create a new patch release. Never force-move a public release tag.\n\nCommon failures:\n- **Clippy errors**: Fix lints, ensure `cargo clippy -- -D warnings` passes\n- **Test failures**: Run `cargo test` to reproduce\n- **Format errors**: Run `cargo fmt` to fix\n\n---\n\n## MCP Agent Mail — Multi-Agent Coordination\n\nA mail-like layer that lets coding agents coordinate asynchronously via MCP tools and resources. Provides identities, inbox/outbox, searchable threads, and advisory file reservations with human-auditable artifacts in Git.\n\n### Why It's Useful\n\n- **Prevents conflicts:** Explicit file reservations (leases) for files/globs\n- **Token-efficient:** Messages stored in per-project archive, not in context\n- **Quick reads:** `resource://inbox/...`, `resource://thread/...`\n\n### Same Repository Workflow\n\n1. **Register identity:**\n   ```\n   ensure_project(project_key=<abs-path>)\n   register_agent(project_key, program, model)\n   ```\n\n2. **Reserve files before editing:**\n   ```\n   file_reservation_paths(project_key, agent_name, [\"src/**\"], ttl_seconds=3600, exclusive=true)\n   ```\n\n3. **Communicate with threads:**\n   ```\n   send_message(..., thread_id=\"FEAT-123\")\n   fetch_inbox(project_key, agent_name)\n   acknowledge_message(project_key, agent_name, message_id)\n   ```\n\n4. **Quick reads:**\n   ```\n   resource://inbox/{Agent}?project=<abs-path>&limit=20\n   resource://thread/{id}?project=<abs-path>&include_bodies=true\n   ```\n\n### Macros vs Granular Tools\n\n- **Prefer macros for speed:** `macro_start_session`, `macro_prepare_thread`, `macro_file_reservation_cycle`, `macro_contact_handshake`\n- **Use granular tools for control:** `register_agent`, `file_reservation_paths`, `send_message`, `fetch_inbox`, `acknowledge_message`\n\n### Common Pitfalls\n\n- `\"from_agent not registered\"`: Always `register_agent` in the correct `project_key` first\n- `\"FILE_RESERVATION_CONFLICT\"`: Adjust patterns, wait for expiry, or use non-exclusive reservation\n- **Auth errors:** If JWT+JWKS enabled, include bearer token with matching `kid`\n\n---\n\n## Beads (br) — Dependency-Aware Issue Tracking\n\nBeads provides a lightweight, dependency-aware issue database and CLI (`br` - beads_rust) for selecting \"ready work,\" setting priorities, and tracking status. It complements MCP Agent Mail's messaging and file reservations.\n\n**Important:** `br` is non-invasive—it NEVER runs git commands automatically. You must manually commit changes after `br sync --flush-only`.\n\n### Conventions\n\n- **Single source of truth:** Beads for task status/priority/dependencies; Agent Mail for conversation and audit\n- **Shared identifiers:** Use Beads issue ID (e.g., `br-123`) as Mail `thread_id` and prefix subjects with `[br-123]`\n- **Reservations:** When starting a task, call `file_reservation_paths()` with the issue ID in `reason`\n\n### Typical Agent Flow\n\n1. **Pick ready work (Beads):**\n   ```bash\n   br ready --json  # Choose highest priority, no blockers\n   ```\n\n2. **Reserve edit surface (Mail):**\n   ```\n   file_reservation_paths(project_key, agent_name, [\"src/**\"], ttl_seconds=3600, exclusive=true, reason=\"br-123\")\n   ```\n\n3. **Announce start (Mail):**\n   ```\n   send_message(..., thread_id=\"br-123\", subject=\"[br-123] Start: <title>\", ack_required=true)\n   ```\n\n4. **Work and update:** Reply in-thread with progress\n\n5. **Complete and release:**\n   ```bash\n   br close 123 --reason \"Completed\"\n   br sync --flush-only  # Export to JSONL (no git operations)\n   ```\n   ```\n   release_file_reservations(project_key, agent_name, paths=[\"src/**\"])\n   ```\n   Final Mail reply: `[br-123] Completed` with summary\n\n### Mapping Cheat Sheet\n\n| Concept | Value |\n|---------|-------|\n| Mail `thread_id` | `br-###` |\n| Mail subject | `[br-###] ...` |\n| File reservation `reason` | `br-###` |\n| Commit messages | Include `br-###` for traceability |\n\n---\n\n## bv — Graph-Aware Triage Engine\n\nbv is a graph-aware triage engine for Beads projects (`.beads/beads.jsonl`). It computes PageRank, betweenness, critical path, cycles, HITS, eigenvector, and k-core metrics deterministically.\n\n**Scope boundary:** bv handles *what to work on* (triage, priority, planning). For agent-to-agent coordination (messaging, work claiming, file reservations), use MCP Agent Mail.\n\n**CRITICAL: Use ONLY `--robot-*` flags. Bare `bv` launches an interactive TUI that blocks your session.**\n\n### The Workflow: Start With Triage\n\n**`bv --robot-triage` is your single entry point.** It returns:\n- `quick_ref`: at-a-glance counts + top 3 picks\n- `recommendations`: ranked actionable items with scores, reasons, unblock info\n- `quick_wins`: low-effort high-impact items\n- `blockers_to_clear`: items that unblock the most downstream work\n- `project_health`: status/type/priority distributions, graph metrics\n- `commands`: copy-paste shell commands for next steps\n\n```bash\nbv --robot-triage        # THE MEGA-COMMAND: start here\nbv --robot-next          # Minimal: just the single top pick + claim command\n```\n\n### Command Reference\n\n**Planning:**\n| Command | Returns |\n|---------|---------|\n| `--robot-plan` | Parallel execution tracks with `unblocks` lists |\n| `--robot-priority` | Priority misalignment detection with confidence |\n\n**Graph Analysis:**\n| Command | Returns |\n|---------|---------|\n| `--robot-insights` | Full metrics: PageRank, betweenness, HITS, eigenvector, critical path, cycles, k-core, articulation points, slack |\n| `--robot-label-health` | Per-label health: `health_level`, `velocity_score`, `staleness`, `blocked_count` |\n| `--robot-label-flow` | Cross-label dependency: `flow_matrix`, `dependencies`, `bottleneck_labels` |\n| `--robot-label-attention [--attention-limit=N]` | Attention-ranked labels |\n\n**History & Change Tracking:**\n| Command | Returns |\n|---------|---------|\n| `--robot-history` | Bead-to-commit correlations |\n| `--robot-diff --diff-since <ref>` | Changes since ref: new/closed/modified issues, cycles |\n\n**Other:**\n| Command | Returns |\n|---------|---------|\n| `--robot-burndown <sprint>` | Sprint burndown, scope changes, at-risk items |\n| `--robot-forecast <id\\|all>` | ETA predictions with dependency-aware scheduling |\n| `--robot-alerts` | Stale issues, blocking cascades, priority mismatches |\n| `--robot-suggest` | Hygiene: duplicates, missing deps, label suggestions |\n| `--robot-graph [--graph-format=json\\|dot\\|mermaid]` | Dependency graph export |\n| `--export-graph <file.html>` | Interactive HTML visualization |\n\n### Scoping & Filtering\n\n```bash\nbv --robot-plan --label backend              # Scope to label's subgraph\nbv --robot-insights --as-of HEAD~30          # Historical point-in-time\nbv --recipe actionable --robot-plan          # Pre-filter: ready to work\nbv --recipe high-impact --robot-triage       # Pre-filter: top PageRank\nbv --robot-triage --robot-triage-by-track    # Group by parallel work streams\nbv --robot-triage --robot-triage-by-label    # Group by domain\n```\n\n### Understanding Robot Output\n\n**All robot JSON includes:**\n- `data_hash` — Fingerprint of source beads.jsonl\n- `status` — Per-metric state: `computed|approx|timeout|skipped` + elapsed ms\n- `as_of` / `as_of_commit` — Present when using `--as-of`\n\n**Two-phase analysis:**\n- **Phase 1 (instant):** degree, topo sort, density\n- **Phase 2 (async, 500ms timeout):** PageRank, betweenness, HITS, eigenvector, cycles\n\n### jq Quick Reference\n\n```bash\nbv --robot-triage | jq '.quick_ref'                        # At-a-glance summary\nbv --robot-triage | jq '.recommendations[0]'               # Top recommendation\nbv --robot-plan | jq '.plan.summary.highest_impact'        # Best unblock target\nbv --robot-insights | jq '.status'                         # Check metric readiness\nbv --robot-insights | jq '.Cycles'                         # Circular deps (must fix!)\n```\n\n---\n\n## UBS — Ultimate Bug Scanner\n\n**Golden Rule:** `ubs <changed-files>` before every commit. Exit 0 = safe. Exit >0 = fix & re-run.\n\n### Commands\n\n```bash\nubs file.rs file2.rs                    # Specific files (< 1s) — USE THIS\nubs $(git diff --name-only --cached)    # Staged files — before commit\nubs --only=rust,toml src/               # Language filter (3-5x faster)\nubs --ci --fail-on-warning .            # CI mode — before PR\nubs .                                   # Whole project (ignores target/, Cargo.lock)\n```\n\n### Output Format\n\n```\nWarning  Category (N errors)\n    file.rs:42:5 - Issue description\n    Suggested fix\nExit code: 1\n```\n\nParse: `file:line:col` -> location | fix hint -> how to fix | Exit 0/1 -> pass/fail\n\n### Fix Workflow\n\n1. Read finding "},"files":{"SKILL.md":"---\nname: dcg\ndescription: \"Destructive Command Guard - High-performance Rust hook for Claude Code that blocks dangerous commands before execution. SIMD-accelerated, modular pack system, whitelist-first architecture. Essential safety layer for agent workflows.\"\n---\n\n# DCG — Destructive Command Guard\n\nA high-performance Claude Code hook that intercepts and blocks destructive commands before they execute. Written in Rust with SIMD-accelerated filtering for sub-millisecond latency.\n\n## Why This Exists\n\nAI coding agents are powerful but fallible. They can accidentally run destructive commands:\n\n- **\"Let me clean up the build artifacts\"** → `rm -rf ./src` (typo)\n- **\"I'll reset to the last commit\"** → `git reset --hard` (destroys uncommitted changes)\n- **\"Let me fix the merge conflict\"** → `git checkout -- .` (discards all modifications)\n- **\"I'll clean up untracked files\"** → `git clean -fd` (permanently deletes untracked files)\n\nDCG intercepts dangerous commands *before* execution and blocks them with a clear explanation, giving you a chance to stash your changes first.\n\n## Critical Design Principles\n\n### 1. Whitelist-First Architecture\n\nSafe patterns are checked *before* destructive patterns. This ensures explicitly safe commands are never accidentally blocked:\n\n```\ngit checkout -b feature    →  Matches SAFE \"checkout-new-branch\"  →  ALLOW\ngit checkout -- file.txt   →  No safe match, matches DESTRUCTIVE  →  DENY\n```\n\n### 2. Fail-Safe Defaults (Default-Allow)\n\nUnrecognized commands are **allowed by default**. This ensures:\n- The hook never breaks legitimate workflows\n- Only *known* dangerous patterns are blocked\n- New git commands work until explicitly categorized\n\n### 3. Zero False Negatives Philosophy\n\nThe pattern set prioritizes **never allowing dangerous commands** over avoiding false positives. A few extra prompts for manual confirmation are acceptable; lost work is not.\n\n## What It Blocks\n\n### Git Commands That Destroy Uncommitted Work\n\n| Command | Reason |\n|---------|--------|\n| `git reset --hard` | Destroys uncommitted changes |\n| `git reset --merge` | Destroys uncommitted changes |\n| `git checkout -- <file>` | Discards file modifications |\n| `git restore <file>` (without `--staged`) | Discards uncommitted changes |\n| `git clean -f` | Permanently deletes untracked files |\n\n### Git Commands That Destroy Remote History\n\n| Command | Reason |\n|---------|--------|\n| `git push --force` / `-f` | Overwrites remote commits |\n| `git branch -d`, `--delete`, `-D`, `-f`, `-M`, `-C` | Deletes or force-overwrites a user-owned branch ref |\n\n### Git Commands That Destroy Stashed Work\n\n| Command | Reason |\n|---------|--------|\n| `git stash drop` | Permanently deletes a stash |\n| `git stash clear` | Permanently deletes all stashes |\n\n### Filesystem Commands\n\n| Command | Reason |\n|---------|--------|\n| `rm -rf` (outside `/tmp`, `/var/tmp`, `$TMPDIR`) | Recursive deletion is dangerous |\n\n## What It ALLOWS\n\nSafe operations pass through silently:\n\n### Always Safe Git Operations\n\n`git status`, `git log`, `git diff`, `git add`, `git commit`, `git push`, `git pull`, `git fetch`, read-only branch listings, `git stash`, `git stash pop`, `git stash list`\n\n### Explicitly Safe Patterns\n\n| Pattern | Why Safe |\n|---------|----------|\n| `git checkout -b <branch>` | Creating new branches |\n| `git checkout --orphan <branch>` | Creating orphan branches |\n| `git restore --staged <file>` | Unstaging only, doesn't touch working tree |\n| `git restore -S <file>` | Short flag for staged |\n| `git clean -n` / `--dry-run` | Preview mode, no actual deletion |\n| `rm -rf /tmp/*` | Temp directories are ephemeral |\n| `rm -rf $TMPDIR/*` | Shell variable forms |\n\n### Safe Alternative: `--force-with-lease`\n\n```bash\ngit push --force-with-lease   # ALLOWED - refuses if remote has unseen commits\ngit push --force              # BLOCKED - can overwrite others' work\n```\n\n## Modular Pack System\n\nDCG uses a modular \"pack\" system to organize patterns by category:\n\n### Core Packs (Always Enabled)\n\n| Pack | Description |\n|------|-------------|\n| `core.git` | Destructive git commands |\n| `core.filesystem` | Dangerous rm -rf outside temp |\n\n### Database Packs\n\n| Pack | Description |\n|------|-------------|\n| `database.postgresql` | DROP/TRUNCATE in PostgreSQL |\n| `database.mysql` | DROP/TRUNCATE in MySQL/MariaDB |\n| `database.mongodb` | dropDatabase, drop() |\n| `database.redis` | FLUSHALL/FLUSHDB |\n| `database.sqlite` | DROP in SQLite |\n\n### Container Packs\n\n| Pack | Description |\n|------|-------------|\n| `containers.docker` | docker system prune, docker rm -f |\n| `containers.compose` | docker-compose down --volumes |\n| `containers.podman` | podman system prune |\n\n### Kubernetes Packs\n\n| Pack | Description |\n|------|-------------|\n| `kubernetes.kubectl` | kubectl delete namespace |\n| `kubernetes.helm` | helm uninstall |\n| `kubernetes.kustomize` | kustomize delete patterns |\n\n### Cloud Provider Packs\n\n| Pack | Description |\n|------|-------------|\n| `cloud.aws` | Destructive AWS CLI commands |\n| `cloud.gcp` | Destructive gcloud commands |\n| `cloud.azure` | Destructive az commands |\n\n### Infrastructure Packs\n\n| Pack | Description |\n|------|-------------|\n| `infrastructure.terraform` | terraform destroy |\n| `infrastructure.ansible` | Dangerous ansible patterns |\n| `infrastructure.pulumi` | pulumi destroy |\n\n### System Packs\n\n| Pack | Description |\n|------|-------------|\n| `system.disk` | dd, mkfs, fdisk operations |\n| `system.permissions` | Dangerous chmod/chown patterns |\n| `system.services` | systemctl stop/disable patterns |\n\n### Other Packs\n\n| Pack | Description |\n|------|-------------|\n| `strict_git` | Extra paranoid git protections |\n| `package_managers` | npm unpublish, cargo yank |\n\n### Configuring Packs\n\n```toml\n# ~/.config/dcg/config.toml\n[packs]\nenabled = [\n    \"database.postgresql\",\n    \"containers.docker\",\n    \"kubernetes\",  # Enables all kubernetes sub-packs\n]\n```\n\n## Environment Variables\n\n| Variable | Description |\n|----------|-------------|\n| `DCG_PACKS=\"containers.docker,kubernetes\"` | Enable packs (comma-separated) |\n| `DCG_DISABLE=\"kubernetes.helm\"` | Disable packs/sub-packs |\n| `DCG_VERBOSE=1` | Verbose output |\n| `DCG_COLOR=auto\\|always\\|never` | Color mode |\n| `DCG_BYPASS=1` | Bypass DCG entirely (escape hatch) |\n\n## Installation\n\n### Quick Install (Recommended)\n\n```bash\ncurl -fsSL \"https://raw.githubusercontent.com/Dicklesworthstone/destructive_command_guard/main/install.sh?$(date +%s)\" | bash\n\n# Easy mode: auto-update PATH\ncurl -fsSL \"https://raw.githubusercontent.com/Dicklesworthstone/destructive_command_guard/main/install.sh?$(date +%s)\" | bash -s -- --easy-mode\n\n# System-wide (requires sudo)\ncurl -fsSL \"https://raw.githubusercontent.com/Dicklesworthstone/destructive_command_guard/main/install.sh?$(date +%s)\" | sudo bash -s -- --system\n```\n\n### From Source (Requires Rust Nightly)\n\n```bash\ncargo +nightly install --git https://github.com/Dicklesworthstone/destructive_command_guard\n```\n\n### Prebuilt Binaries\n\nAvailable for: Linux x86_64, Linux ARM64, macOS Intel, macOS Apple Silicon, Windows\n\n## Claude Code Configuration\n\nAdd to `~/.claude/settings.json`:\n\n```json\n{\n  \"hooks\": {\n    \"PreToolUse\": [\n      {\n        \"matcher\": \"Bash|PowerShell|Monitor\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"/absolute/path/to/dcg\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\nReplace `/absolute/path/to/dcg` with the exact resolved binary path. Do not use\nbare `dcg`: non-interactive hook shells may not inherit the interactive\n`PATH`. On native Windows, use `install.ps1` so the hook receives the\nPowerShell-safe absolute command and explicit shell selection.\n\n**Important:** Restart Claude Code after adding the hook.\n\n## How It Works\n\n### Processing Pipeline\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│                        Claude Code                               │\n│  Agent executes `rm -rf ./build`                                │\n└─────────────────────┬───────────────────────────────────────────┘\n                      │\n                      ▼ PreToolUse hook (stdin: JSON)\n┌─────────────────────────────────────────────────────────────────┐\n│                          dcg                                     │\n│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐       │\n│  │    Parse     │───▶│  Normalize   │───▶│ Quick Reject │       │\n│  │    JSON      │    │   Command    │    │   Filter     │       │\n│  └──────────────┘    └──────────────┘    └──────┬───────┘       │\n│                                                  │               │\n│                      ┌───────────────────────────┘               │\n│                      ▼                                           │\n│  ┌──────────────────────────────────────────────────────────┐   │\n│  │                   Pattern Matching                        │   │\n│  │   1. Check SAFE_PATTERNS (whitelist) ──▶ Allow if match  │   │\n│  │   2. Check DESTRUCTIVE_PATTERNS ──────▶ Deny if match    │   │\n│  │   3. No match ────────────────────────▶ Allow (default)  │   │\n│  └──────────────────────────────────────────────────────────┘   │\n└─────────────────────┬───────────────────────────────────────────┘\n                      │\n                      ▼ stdout: JSON (deny) or empty (allow)\n```\n\n### Stage 1: JSON Parsing\n- Reads hook input from stdin\n- Validates Claude Code's `PreToolUse` format\n- Non-Bash tools immediately allowed\n\n### Stage 2: Command Normalization\n- Strips absolute paths: `/usr/bin/git status` → `git status`\n- Preserves argument paths\n\n### Stage 3: Quick Rejection Filter\n- SIMD-accelerated substring search for \"git\" or \"rm\"\n- Commands without these bypass regex entirely (99%+ of commands)\n\n### Stage 4: Pattern Matching\n- Safe patterns checked first (short-circuit on match → allow)\n- Destructive patterns checked second (match → deny)\n- No match → default allow\n\n## Exit Codes\n\n| Code | Meaning |\n|------|---------|\n| `0` | Command is safe, proceed |\n| `2` | Command is blocked, do not execute |\n\n## CLI Usage\n\nTest commands manually:\n\n```bash\n# Show version with build metadata\ndcg --version\n\n# Test a command\necho '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git reset --hard\"}}' | dcg\n```\n\n## Example Block Message\n\n```\n════════════════════════════════════════════════════════════════════════\nBLOCKED  dcg\n────────────────────────────────────────────────────────────────────────\nReason:  git reset --hard destroys uncommitted changes. Use 'git stash' first.\n\nCommand:  git reset --hard HEAD~1\n\nTip: If you need to run this command, execute it manually in a terminal.\n     Consider using 'git stash' first to save your changes.\n════════════════════════════════════════════════════════════════════════\n```\n\n### Contextual Suggestions\n\n| Command Type | Suggestion |\n|-------------|------------|\n| `git reset`, `git checkout --` | \"Consider using 'git stash' first\" |\n| `git clean` | \"Use 'git clean -n' first to preview\" |\n| `git push --force` | \"Consider using '--force-with-lease'\" |\n| `rm -rf` | \"Verify the path carefully before running manually\" |\n\n## Edge Cases Handled\n\n### Path Normalization\n\n```bash\n/usr/bin/git reset --hard          # Blocked\n/usr/local/bin/git checkout -- .   # Blocked\n/bin/rm -rf /home/user             # Blocked\n```\n\n### Flag Ordering Variants\n\n```bash\nrm -rf /path          # Combined flags\nrm -fr /path          # Reversed order\nrm -r -f /path        # Separate flags\nrm --recursive --force /path    # Long flags\n```\n\nAll variants are handled.\n\n### Shell Variable Expansion\n\n```bash\nrm -rf $TMPDIR/build           # Allowed (temp)\nrm -rf ${TMPDIR}/build         # Allowed\nrm -rf \"$TMPDIR/build\"         # Allowed\nrm -rf \"${TMPDIR:-/tmp}/build\" # Allowed\n```\n\n### Staged vs Worktree Restore\n\n```bash\ngit restore --staged file.txt    # Allowed (unstaging only)\ngit restore -S file.txt          # Allowed (short flag)\ngit restore file.txt             # BLOCKED (discards changes)\ngit restore --worktree file.txt  # BLOCKED (explicit worktree)\ngit restore -S -W file.txt       # BLOCKED (includes worktree)\n```\n\n## Performance Optimizations\n\nDCG is designed for zero perceived latency:\n\n| Optimization | Technique |\n|--------------|-----------|\n| **Lazy Static** | Regex patterns compiled once via `LazyLock` |\n| **SIMD Quick Reject** | `memchr` crate for CPU vector instructions |\n| **Early Exit** | Safe match returns immediately |\n| **Zero-Copy JSON** | `serde_json` operates on input buffer |\n| **Zero-Allocation** | `Cow<str>` for path normalization |\n| **Release Profile** | `opt-level=\"z\"`, LTO, single codegen unit |\n\n**Result:** Sub-millisecond execution for typical commands.\n\n## Pattern Counts\n\n| Type | Count |\n|------|-------|\n| Safe patterns (whitelist) | 34 |\n| Destructive patterns (blacklist) | 16 |\n\n## Security Considerations\n\n### What DCG Protects Against\n\n- Accidental data loss from `git checkout --` or `git reset --hard`\n- Remote history destruction from force pushes\n- Stash loss from `git stash drop/clear`\n- Filesystem accidents from `rm -rf` outside temp directories\n\n### What DCG Does NOT Protect Against\n\n- Malicious actors (can bypass the hook)\n- Non-Bash commands (Python/JavaScript file writes, API calls)\n- Committed but unpushed work\n- Commands inside scripts (`./deploy.sh` contents not inspected)\n\n### Threat Model\n\nDCG assumes the AI agent is **well-intentioned but fallible**. It catches honest mistakes, not adversarial attacks.\n\n## Troubleshooting\n\n### Hook not blocking commands\n\n1. Verify `~/.claude/settings.json` has hook configuration\n2. Restart Claude Code\n3. Test manually: `echo '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git reset --hard\"}}' | dcg`\n\n### Hook blocking safe commands\n\n1. Check if there's an edge case not covered\n2. File a GitHub issue\n3. Temporary bypass: `DCG_BYPASS=1` or run command manually\n\n## FAQ\n\n**Q: Why block both `git branch -d` and `git branch -D`?**\n\nLowercase `-d` checks merge state, but still removes the branch name, upstream-tracking configuration, and convenient reflog reference. Those are user-owned state, so every delete form requires explicit approval. Review first with `git branch -vv`, `git branch --merged`, and `git branch --no-merged`. The merge-checked form is its own rule, `core.git:branch-delete`, so `[policy.rules] \"core.git:branch-delete\" = \"ask\"` turns it into an approval prompt while `-D` (`core.git:branch-force-delete`) stays denied.\n\n**Q: Why is `git push --force-with-lease` allowed?**\n\nForce-with-lease refuses to push if the remote has commits you haven't seen, preventing accidental overwrites.\n\n**Q: Why block all `rm -rf` outside temp directories?**\n\nRecursive forced deletion is extremely dangerous. A typo or wrong variable can delete critical files. Temp directories are designed to be ephemeral.\n\n**Q: What if I really need to run a blocked command?**\n\nDCG instructs the agent to ask for permission. Run the command manually in a separate terminal after making a conscious decision.\n\n## Integration with Flywheel\n\n| Tool | Integration |\n|------|-------------|\n| **Claude Code** | Native PreToolUse hook |\n| **Agent Mail** | Agents can report blocked commands to coordinator |\n| **BV** | Flag tasks that repeatedly trigger DCG |\n| **CASS** | Search DCG block patterns across sessions |\n| **RU** | DCG protects agent-sweep from destructive commits |\n","docs/agents.md":"# Agent-Specific Profiles\n\ndcg can detect which AI coding agent is invoking it and apply agent-specific\ntrust levels and configuration overrides. This allows you to grant higher\ntrust to well-behaved agents while maintaining strict controls for unknown ones.\n\n## Supported Agents\n\n| Agent | Detection Method | Environment Variable |\n|-------|------------------|---------------------|\n| Claude Code | Environment | `CLAUDE_CODE=1` or `CLAUDE_SESSION_ID` |\n| Augment Code | Environment | `AUGMENT_AGENT=1` or `AUGMENT_CONVERSATION_ID` |\n| Aider | Environment | `AIDER_SESSION=1` |\n| Continue | Environment | `CONTINUE_SESSION_ID` |\n| Codex CLI | Environment | `CODEX_CLI=1` |\n| Gemini CLI | Environment | `GEMINI_CLI=1` |\n| GitHub Copilot CLI | Environment | `COPILOT_CLI=1` or `COPILOT_AGENT_START_TIME_SEC` |\n| VS Code Copilot Chat | Hook payload | `tool_name` is `runTerminalCommand`, `run_in_terminal`, or `runInTerminal`; the Agent Host's batched `toolCalls` envelope is also recognized, with every shell entry evaluated independently |\n| Cursor IDE | Environment | `CURSOR_IDE=1` (set by dcg's hook script) |\n| Hermes Agent | Environment | `HERMES_AGENT=1` or `HERMES_SESSION_ID` |\n| Grok (xAI) | Environment | `GROK_SESSION_ID`, `GROK_HOOK_EVENT`, or `GROK_WORKSPACE_ROOT` |\n| Oh My Pi (`omp`) | Explicit bridge / process | Generated extension passes `--agent omp`; exact `omp` and `oh-my-pi` process names are fallback matches |\n| Crush | Environment | `CRUSH=1` (set by Crush for hook subprocesses and `bash`-tool commands; the generic `AGENT`/`AI_AGENT` markers are not consulted). Hook payloads are recognized by their `event: \"PreToolUse\"` + `tool_input` envelope — see [crush-integration.md](crush-integration.md) |\n| Muse Code | Explicit flag only | The hook written by `dcg install --muse` passes `--agent muse` (Muse clears the hook environment). Its payload looks like Codex's (`turn_id`), so without the flag `[agents.codex-cli]` applies; the identity is never taken from the payload or a process name — see [muse-integration.md](muse-integration.md) |\n| Amp | Explicit bridge | The plugin written by `dcg install --amp` passes `--agent amp`; no environment marker or process name is consulted — see [amp-integration.md](amp-integration.md) |\n| Pi | Environment | `PI_CODING_AGENT=true` |\n| Posit Assistant | Environment | `PA_PROJECT_DIR` (set in hook subprocesses; checked last among environment markers so agents with their own markers win — environment detection still precedes parent-process detection) |\n\n## Detection Priority\n\nAgent detection follows this priority order:\n\n1. **Explicit `--agent` flag**: Manual override via CLI\n2. **Environment variables**: Most agents set identifying env vars\n3. **Parent process inspection**: Fallback check of process tree\n4. **Unknown**: Default when no agent is detected\n\n### Oh My Pi (`omp`)\n\nInstall dcg's native OMP ExtensionAPI module with:\n\n```bash\ndcg install --omp\n```\n\nThe default user path is `~/.omp/agent/extensions/dcg-guard.ts`. OMP named\nprofiles use `~/.omp/profiles/<name>/agent/extensions/dcg-guard.ts`; dcg follows\nOMP's `OMP_PROFILE`-before-`PI_PROFILE` precedence and honors\n`PI_CONFIG_DIR` for a config directory name relative to the user's home plus\n`PI_CODING_AGENT_DIR` for the default profile. Drive-qualified\n`PI_CONFIG_DIR` values are rejected on Windows because Rust would otherwise\nresolve them differently from OMP's home-relative `path.join` behavior. Use\n`dcg install --omp --project` to install `<cwd>/.omp/extensions/dcg-guard.ts`\ninstead. OMP checks only the current working directory for native project\nextensions; it neither requires Git nor walks to an ancestor, so launch OMP\nfrom that same directory.\n\nThe extension uses OMP's pre-execution `tool_call` event and sends each `bash`\ncommand to `dcg --robot test --stdin --agent omp --format json` with the dialect\nselected by OMP's execution route. Pinning the private bridge format prevents\nambient `DCG_FORMAT` from redirecting or invalidating its compact protocol\nwithout removing that variable from supported environment-conditioned policy.\nThe embedded install-time absolute dcg pathname is authoritative: ambient\n`DCG_BIN` cannot redirect the marker-owned guard. This binds a pathname, not a\nhash, inode/file ID, signature, or immutable executable object. Bun resolves\nthat pathname again for each tool call, so moving or removing the file produces\na visible infrastructure failure, while replacing bytes at the same pathname\nchanges what a later callback executes. `dcg doctor` compares the marker-owned\nextension with source generated for the doctor process's pathname at inspection\ntime; it does not attest binary contents or an extension already loaded by a\nrunning OMP session. To rebind deliberately, run\n`/desired/path/dcg install --omp --force` (add `--project` for project scope)\nand restart OMP. Protect the binary, extension, and their parent directories\nfrom writers not trusted to control OMP execution; a writer that can replace the\nextension can already run code inside OMP. Ordinary and managed-async calls use\nOMP's embedded Brush shell and pass `--dialect posix`, including on native\nWindows. An eligible local `pty: true` call instead maps OMP's configured\nexternal shell to `--dialect posix`, `cmd`, or `ps`; `PI_NO_PTY=1` disables that\nPTY route. A dcg deny, ask, or indeterminate result returns\n`{ block: true, reason }` to OMP.\nBecause the bridge supplies `--agent omp` explicitly, OMP remains distinct from\nlegacy Pi even when OMP exposes Pi-family compatibility variables. The\ncanonical profile key is `agents.omp`; `oh-my-pi` is accepted as an alias.\nWhen `[history] enabled = true`, these robot-boundary evaluations are persisted\nwith the canonical `agent_type = \"omp\"`; ordinary human `dcg test` diagnostics\nremain outside command history.\n\nThe bridge spawns dcg directly, without a shell, and gives Bun a 30-second\nparent-side timeout with a hard kill signal. Immediately after a successful\nspawn it records one monotonic absolute deadline 30.5 seconds away and arms one\nobservation watchdog. A direct child can exit while a descendant still holds\nits stdout or stderr descriptor, so exit settlement rearms that sole timer for\nthe lesser of a 250-millisecond pipe-drain grace and the remaining absolute\nbudget. If the remaining hard budget wins, expiry retains hard-deadline\nprovenance. Exit-observation rejection attempts one direct-child `SIGKILL` and\nuses the same clamped drain; neither rearm kills again. Every active watchdog is\ncleared when observation finishes, and late exit settlement or rejection\nremains consumed. There is no retry, replacement process, or second\nconcurrently active bridge watchdog.\n\nThis is the finite child-observation lineage:\n\n| Current state + event | Process action | Pipe/clock action | Result invariant |\n|---|---|---|---|\n| `Bun.spawn` throws | No child exists | No watchdog is armed | One visible spawn diagnostic; fail open |\n| Spawn succeeds | Bun owns the 30-second direct-child timeout | Record and arm the monotonic 30.5-second absolute observation deadline; read both capped pipes and exit concurrently | No classification before the bounded observation settles |\n| Complete deny/ask/indeterminate frame or stdout overflow arrives | None | Retain the frame or capped overflow evidence while observation continues | Blocking evidence is absorbing |\n| Exit resolves before the hard deadline | None; the direct child is already gone | Rearm for `min(250 ms, remaining hard budget)` | Bytes already read remain eligible evidence; the absolute deadline cannot move |\n| Exit rejects before the hard deadline | Attempt one direct-child `SIGKILL` | Rearm for `min(250 ms, remaining hard budget)` without another kill | The exit fault and any kill fault remain visible; kill request alone does not prove a signal |\n| Drain grace expires | The direct child has exited or already received the rejection-path kill | Cancel both local readers | Retained block/overflow still blocks; missing evidence follows visible infrastructure fail-open |\n| Hard observation deadline expires | Attempt one direct-child `SIGKILL` | Cancel both readers and the local exit wrapper | Retained block/overflow still blocks; deadline remains visible |\n| All three observations settle first | None | Clear the active watchdog | Read signal provenance, classify once, and emit each owned diagnostic once |\n\nIf a blocking frame and a deadline become runnable together, JavaScript's event\nloop imposes a total order: bytes delivered before reader cancellation are\nretained and absorbing; bytes delivered after cancellation are outside the\nobservation boundary. The stdout and stderr caps bound retained memory, and the\nouter/deferred deadlines bound a standards-compliant asynchronous observation.\nIf an underlying WHATWG cancellation algorithm rejects, the stream is still\nclosed and pending reads settle; the bridge consumes the cancellation promise's\nrejection while retaining prior deny/ask/indeterminate and overflow evidence.\nThe backstops sit above dcg's ordinary 1-second evaluator default and the broad\nWindows preset's 3-second default, not in place of those configurable budgets.\nAn explicit evaluator budget above 30 seconds is nevertheless capped on this\nOMP bridge; direct hook and diagnostic invocations retain their configured\nbudget.\n\nTwo seams remain fundamentally outside this in-process boundary. A synchronous\nstall inside `Bun.spawn` (or any JavaScript/event-loop stall) prevents a timer\nfrom starting or firing. Bun's `proc.kill()` targets the direct child, not a\nprocess group, so it cannot prove that descendants were terminated or release\ntheir unrelated resources; local reader cancellation bounds the callback even\nwhen an inherited pipe holder survives. A non-standard stream whose synchronous\ncancel path throws without settling a pending read is likewise outside the Web\nStreams contract and cannot be bounded by another JavaScript promise. After\nobservation, the bridge reads Bun's `signalCode` and reports an exact signal\nonly when Bun actually exposes one; a successful `proc.kill(\"SIGKILL\")` request\ndoes not itself forge SIGKILL provenance. This keeps an ordinary numeric exit\n137 distinct from an observed timeout or other observed `SIGKILL`.\nA complete deny, ask, or indeterminate verdict already written to stdout\nremains authoritative regardless of signal, cancellation, stream/exit fault,\nor deadline; stdout overflow and dcg's independent blocking exit 1 are likewise\nabsorbing. Other abnormal statuses and infrastructure faults remain visible\neven when blocking evidence controls the action.\n\nOMP's public ExtensionAPI does not expose its ACP client-terminal capability or\nselected backend, and both ACP and JSON-RPC surface as `mode: \"rpc\"`. The bridge\ntherefore does not infer a Windows dialect from mode: non-PTY RPC calls remain\nPOSIX-scoped so ordinary JSON-RPC execution does not gain Cmd/PowerShell false\npositives. As a result, an ACP client-terminal call does not yet receive exact\nCmd/PowerShell-specific coverage; closing that residual requires OMP to expose\nthe actual BashTool route before the `tool_call` handler runs.\n\n## Trust Levels\n\nThree trust levels label how much you trust a given agent:\n\n| Level | Description |\n|-------|-------------|\n| `high` | Agent has proven reliable; typically paired with a broader allowlist and fewer packs |\n| `medium` | Default; standard configuration |\n| `low` | Extra caution; typically paired with more packs and a restricted allowlist |\n\n### How trust levels work\n\nThe `trust_level` field is an **advisory label**. It is recorded in JSON output\nand shown in verbose/debug logs so you (and downstream tooling) can see what\ntrust tier was in effect for a given evaluation. It does **not**, by itself,\nchange which rules fire or how confidence scores are computed.\n\nAll behavioral differences between agents come from the other profile options\nthat you configure alongside the trust level:\n\n| Option | What it does | Typical usage |\n|--------|-------------|---------------|\n| `disabled_packs` | Removes packs (and their sub-packs) from evaluation | High-trust agents that don't need certain rule sets |\n| `extra_packs` | Adds packs to evaluation | Low-trust agents that should be checked against more rules |\n| `additional_allowlist` | Adds command patterns that bypass deny rules | High-trust agents with known-safe build commands |\n| `disabled_allowlist` | When `true`, ignores *all* allowlist entries (base + additional) | Low-trust agents that should never get a free pass |\n\nIn other words: setting `trust_level = \"high\"` alone does not relax any rules.\nYou must also adjust `disabled_packs`, `extra_packs`, `additional_allowlist`,\nor `disabled_allowlist` to change evaluation behavior.\n\n### Why the separation?\n\nThis design is intentional. Trust is not a magic knob -- different environments\nneed different trade-offs. A \"high trust\" agent in one project might need strict\ndatabase rules but relaxed filesystem rules, while in another project the\nopposite applies. By keeping the label separate from the behavioral knobs, dcg\ngives you full control without hidden side effects.\n\n### Practical examples\n\n**High-trust agent** -- a well-tested agent that runs routine build/test\ncommands. You widen the allowlist and disable packs that produce false positives\nfor its workflow:\n\n```toml\n[agents.claude-code]\ntrust_level = \"high\"\nadditional_allowlist = [\"npm run build\", \"cargo test\", \"make lint\"]\ndisabled_packs = [\"kubernetes\"]\n```\n\n**Medium-trust agent (default)** -- standard rules, no overrides:\n\n```toml\n[agents.default]\ntrust_level = \"medium\"\n```\n\n**Low-trust agent** -- an unknown or new agent. You add extra packs and disable\nthe allowlist so every command is evaluated against the full rule set:\n\n```toml\n[agents.unknown]\ntrust_level = \"low\"\ndisabled_allowlist = true\n# Real pack / category IDs (see `dcg packs` / docs/packs/README.md). A category\n# ID like \"database\" expands to every database.* sub-pack. \"paranoid\" is a\n# graduation mode, not a pack — use the real `strict_git` pack for stricter git\n# rules, and `core.filesystem` (not \"filesystem\") for the filesystem pack.\nextra_packs = [\"strict_git\", \"database\", \"system\"]\n```\n\n## Configuration\n\nConfigure agent profiles in your `config.toml`:\n\n```toml\n# Trust Claude Code more (it sets CLAUDE_CODE=1)\n[agents.claude-code]\ntrust_level = \"high\"\nadditional_allowlist = [\"npm run build\", \"cargo test\"]\n\n[agents.omp]\ntrust_level = \"medium\"\nextra_packs = [\"strict_git\"]\n\n# Restrict unknown agents\n[agents.unknown]\ntrust_level = \"low\"\ndisabled_allowlist = true\nextra_packs = [\"strict_git\", \"database\"]\n\n# Default profile for unspecified agents\n[agents.default]\ntrust_level = \"medium\"\n```\n\n### Profile Options\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `trust_level` | string | `\"medium\"` | Advisory label: `\"high\"`, `\"medium\"`, or `\"low\"`. Included in JSON/verbose output but does not change rule evaluation by itself. |\n| `disabled_packs` | array | `[]` | Pack or category IDs to remove from evaluation for this agent (a category ID drops every matching sub-pack). |\n| `extra_packs` | array | `[]` | Additional pack or category IDs to enable for this agent (a category ID expands to all its sub-packs). |\n| `additional_allowlist` | array | `[]` | Command patterns to allowlist for this agent (added on top of the base allowlist). |\n| `disabled_allowlist` | bool | `false` | If `true`, ignore all allowlist entries for this agent (more restrictive). |\n\n### Example: Agent Profiles for a Trusted Config\n\n```toml\n# In ~/.config/dcg/config.toml, or in a reviewed file selected with DCG_CONFIG\n[agents.unknown]\ntrust_level = \"low\"\ndisabled_allowlist = true\nextra_packs = [\"strict_git\", \"database\", \"system\"]\n\n[agents.claude-code]\ntrust_level = \"medium\"\nadditional_allowlist = [\"npm test\", \"npm run lint\"]\n```\n\nAutomatically discovered repository `.dcg.toml` files cannot define agent\nprofiles. Profiles can disable packs or add allowlist entries, so accepting\nthem from a newly cloned repository would cross dcg's security boundary. To\nuse a reviewed repository-owned profile deliberately, invoke dcg with\n`DCG_CONFIG=.dcg.toml` (or copy the profile into the user config).\n\n## Custom Agents\n\nDefine profiles for custom agents by setting an environment variable:\n\n```bash\n# Set a custom agent identifier\nexport MY_BUILD_BOT=1\n```\n\nThen configure in `config.toml`:\n\n```toml\n[agents.my-build-bot]\ntrust_level = \"high\"\nadditional_allowlist = [\"make deploy\"]\n```\n\n## Profile Resolution\n\nWhen resolving which profile to use:\n\n1. Look for exact match: `agents.<agent-config-key>`\n2. Fall back to `agents.unknown` if agent is unrecognized\n3. Fall back to `agents.default` if no specific profile exists\n\n## Verbose Output\n\nUse `--verbose` or `-v` to see agent detection info:\n\n```bash\n$ dcg test \"git push --force\" --verbose\nCommand: git push --force\n...\nElapsed: 21.14ms\nAgent: Claude Code\nTrust level: medium\nSeverity: critical\n```\n\nUse `-vv` for detailed debug output:\n\n```bash\n$ dcg test \"git push --force\" -vv\n...\nAgent detection:\n  Detected: Claude Code (claude-code)\n  Method: environment_variable\n  Matched: CLAUDE_CODE\n  Profile: agents.claude-code\n  Trust level: medium\n```\n\n## JSON Output\n\nThe `--format json` output includes agent information:\n\n```json\n{\n  \"command\": \"git push --force\",\n  \"decision\": \"deny\",\n  \"agent\": {\n    \"detected\": \"claude-code\",\n    \"trust_level\": \"medium\",\n    \"detection_method\": \"environment_variable\"\n  }\n}\n```\n\n## Robot Mode\n\nRobot mode provides a unified, machine-friendly interface for AI agents. When\nenabled, dcg optimizes its output for programmatic consumption.\n\n### Enabling Robot Mode\n\n```bash\n# Via flag\ndcg --robot test \"rm -rf /\"\n\n# Via environment variable\nDCG_ROBOT=1 dcg test \"rm -rf /\"\n```\n\n### Robot Mode Behavior\n\n| Aspect | Normal Mode | Robot Mode |\n|--------|-------------|------------|\n| stdout | JSON or pretty | Always JSON |\n| stderr | Rich colored output | Silent |\n| Exit codes | Varies | Standardized |\n| ANSI codes | If TTY | Never |\n| Progress | Shown | Hidden |\n| Suggestions | Shown | In JSON only |\n\n### Standardized Exit Codes\n\nIn robot mode, dcg uses consistent exit codes across all commands:\n\n| Code | Constant | Meaning |\n|------|----------|---------|\n| 0 | `EXIT_SUCCESS` | Success / Allow |\n| 1 | `EXIT_DENIED` | Command denied/blocked |\n| 2 | `EXIT_WARNING` | Warning (with --fail-on warn) |\n| 3 | `EXIT_CONFIG_ERROR` | Configuration error |\n| 4 | `EXIT_PARSE_ERROR` | Parse/input error |\n| 5 | `EXIT_IO_ERROR` | IO error |\n| 141 | `EXIT_BROKEN_PIPE` | stdout/stderr reader went away (`EPIPE`); a clean exit, never a signal death |\n\n### Robot Mode JSON Output\n\nAll robot-mode responses are pure JSON on stdout:\n\n```json\n{\n  \"command\": \"rm -rf /\",\n  \"decision\": \"deny\",\n  \"rule_id\": \"core.filesystem:rm-rf-root\",\n  \"pack_id\": \"core.filesystem\",\n  \"severity\": \"critical\",\n  \"reason\": \"rm -rf / would delete the entire filesystem\",\n  \"agent\": {\n    \"detected\": \"claude-code\",\n    \"trust_level\": \"medium\",\n    \"detection_method\": \"environment_variable\"\n  }\n}\n```\n\n### Hook Mode vs Robot Mode\n\n**Hook mode** (default when no subcommand) follows the active hook protocol:\n- Claude Code, Gemini CLI, Copilot CLI, VS Code Copilot Chat, and compatible JSON-hook protocols emit\n  JSON on stdout for denials and empty stdout for allows.\n- Codex CLI uses strict hook parsing, so dcg emits a minimal\n  `hookSpecificOutput` denial on stdout and exits 0.\n- Rich output always goes to stderr for human visibility.\n\nHook mode carries the verdict in stdout JSON and exits 0 whether the command\nwas allowed, warned about, sent for review, or denied. The exit status changes\nonly when a blocking verdict could not be delivered:\n\n| Code | Constant | Meaning |\n|------|----------|---------|\n| 0 | `EXIT_SUCCESS` | Verdict delivered on stdout (an allow is empty stdout). Also an allow or a warning whose stdout reader went away: nothing was lost. |\n| 2 | `EXIT_HOOK_BLOCK` | A deny, ask, or indeterminate verdict could not be written to stdout (`EPIPE`: the host closed the pipe early). Exit 0 with nothing on stdout would read as \"proceed\", so the block travels in the exit status instead, with the reason on stderr. |\n\nHow hosts read exit 2 with nothing on stdout:\n\n| Protocol | Effect |\n|----------|--------|\n| Claude Code and Claude-compatible hosts (Posit Assistant, Augment) | Blocks; stderr is fed back to the model as the reason |\n| Gemini CLI | Blocks (exit 2 is its blocking error) |\n| Copilot CLI | Blocks (`preToolUse` hooks that exit 2 deny the call) |\n| Crush | Blocks; stderr is the reason |\n| Grok | Blocks (exit 2 is a documented explicit deny) |\n| Codex CLI | Blocks in current releases (exit 2 with the reason on stderr is documented); some earlier builds logged it as a hook failure and failed open |\n| Hermes | Blocks in current releases (a `pre_tool_call` hook exiting 2 blocks even with no JSON on stdout); earlier builds only warned |\n| Reasonix | Blocks — exit 2 is its only blocking channel, so dcg always uses it there |\n| Muse Code | Not documented: a non-zero exit is a hook failure, which Muse hands to the handler's `onFailure` fallback when one is declared |\n| Antigravity (`agy`) | Logged as a hook failure, then fails open — the same outcome as exit 0 with no JSON, but visible |\n\nHook mode never exits 2 for any other reason, and never exits 141: every\nhook-mode write tolerates a closed pipe. `EXIT_BROKEN_PIPE` belongs to the\nCLI surface below.\n\n**Robot mode** with subcommands uses standardized exit codes:\n- Exit 1 for denials (allows scripting with `$?`)\n- Pure JSON on stdout\n- Silent stderr\n\n## Rich Output and Agent Compatibility\n\ndcg keeps agent-facing output and human-facing output on separate streams. This\nis the compatibility contract for rich terminal formatting.\n\n| Stream | Purpose | Hook-mode content | Robot-mode content |\n|--------|---------|-------------------|--------------------|\n| stdout | Agent and script parsing | Protocol JSON for denials, empty for allows | JSON only |\n| stderr | Human-visible diagnostics | Rich or plain text warning boxes | Silent |\n\nRich output is display-only. It must never be parsed by agents and must never be\nwritten to stdout. When dcg prints Unicode boxes, colors, highlighted commands,\nor suggestion panels, that output belongs on stderr.\n\n### Rich Output Selection\n\ndcg uses rich terminal formatting only when the runtime is suitable. It falls\nback to plain output when any of these controls are active:\n\n| Control | Effect |\n|---------|--------|\n| `DCG_NO_RICH=1` | Disable rich formatting while keeping normal command behavior |\n| `--legacy-output` or `DCG_LEGACY_OUTPUT=1` | Force legacy/plain rendering paths |\n| `NO_COLOR=1` or `DCG_NO_COLOR=1` | Disable colorized output |\n| `TERM=dumb` | Use dumb-terminal-safe output |\n| `CI=1` | Suppress rich interactive formatting in CI |\n| non-TTY stdout | Prefer plain output for pipeline-friendly behavior |\n| `--robot` or `DCG_ROBOT=1` | Emit machine-readable stdout and keep stderr silent |\n\n### Wrapper Guidance\n\nAgent wrappers should choose the interface that matches their parser:\n\n```bash\n# Hook integration: preserve both streams.\ndcg < hook-input.json >hook-stdout.json 2>human-warning.txt\n\n# Scripting integration: use robot mode and parse stdout only.\ndcg --robot test \"rm -rf /\" >decision.json 2>/dev/null\n```\n\nFor Codex and Claude-compatible hook integrations, parse stdout when it is\nnon-empty and treat empty stdout with exit 0 as allow. Codex's denial payload is\nminimal and intentionally omits dcg-only metadata.\n\n### Example: Agent Integration\n\n```bash\n#!/bin/bash\n# Script for AI agent to check commands before execution\n\ncheck_command() {\n    local cmd=\"$1\"\n    local result\n\n    # Use robot mode for predictable output\n    result=$(dcg --robot test \"$cmd\" 2>/dev/null)\n    local exit_code=$?\n\n    if [ $exit_code -eq 0 ]; then\n        echo \"Command allowed: $cmd\"\n        return 0\n    elif [ $exit_code -eq 1 ]; then\n        echo \"Command BLOCKED: $cmd\"\n        echo \"Reason: $(echo \"$result\" | jq -r '.reason')\"\n        return 1\n    else\n        echo \"Error checking command (exit code: $exit_code)\"\n        return $exit_code\n    fi\n}\n\n# Usage\ncheck_command \"git status\"      # Allowed\ncheck_command \"rm -rf /\"        # Blocked\n```\n\n### Unified Output Format\n\nRobot mode uses the unified `OutputFormat` enum:\n\n```bash\n# These are equivalent in robot mode\ndcg --robot test \"cmd\"\ndcg --robot --format json test \"cmd\"\n```\n\nAvailable formats:\n- `pretty` / `text` / `human` - Human-readable (default without --robot)\n- `json` / `sarif` / `structured` - JSON output (default with --robot)\n- `jsonl` - JSON Lines (one object per line, for streaming)\n- `compact` - Compact single-line output\n\n## Best Practices\n\n1. **Start with defaults**: The default `medium` trust level is safe for most\n   use cases.\n\n2. **Grant trust incrementally**: Only increase trust for agents after\n   observing their behavior.\n\n3. **Version reviewed profiles carefully**: A repository can keep a reviewed\n   profile in `.dcg.toml`, but it only takes effect when explicitly selected\n   with `DCG_CONFIG=.dcg.toml`; automatic discovery ignores agent profiles.\n\n4. **Restrict unknown agents**: Always configure `agents.unknown` with lower\n   trust in production environments.\n\n5. **Review the JSON output**: Use `--format json` in CI to audit which agents\n   are accessing your codebase.\n\n6. **Use robot mode for scripting**: When integrating dcg into automated\n   workflows, use `--robot` for consistent, parseable output.\n\n7. **Check exit codes**: In robot mode, use exit codes to make decisions\n   without parsing JSON for simple allow/deny checks.\n","AGENTS.md":"# AGENTS.md — dcg (Destructive Command Guard)\n\n> Guidelines for AI coding agents working in this Rust codebase.\n\n---\n\n## RULE 0 - THE FUNDAMENTAL OVERRIDE PREROGATIVE\n\nIf I tell you to do something, even if it goes against what follows below, YOU MUST LISTEN TO ME. I AM IN CHARGE, NOT YOU.\n\n---\n\n## RULE 0.5 - SUITE-WIDE RULES LIVE IN /data/projects/AGENTS.md\n\nThe suite-wide rules in **`/data/projects/AGENTS.md`** bind you here too. Read it. Two sections\nare load-bearing for perf work and are NOT duplicated below, so they cannot drift out of sync:\n\n- **`## Named Reward-Hacking Patterns (ALL FORBIDDEN)`** — 12 named patterns, several already\n  observed in this suite: gate self-weakening (and the exact price of a legitimate gate fix),\n  proof-class inflation, golden regeneration reflex, commit-stream pumping, tautological tests,\n  easy-lever cherry-picking, close-pump abuse, scope-splitting, spec-editing as progress,\n  conformance metastasis, dependency smuggling, bench-path hardcoding.\n- **`### Work-Graph Discipline`** — JSONL is truth and `beads.db` is disposable, `br sync\n  --import-only` after every pull, single-writer on graph structure, closure on cited evidence\n  with blocker beads gated on their named probe, `br dep cycles` stays empty.\n\nThe three that most often decide whether a number here is real: a **self-speedup is\nMAINTENANCE, not a win** — a win needs the incumbent live in the SAME invocation; **never\nweaken a gate to land a change**, and if a gate is genuinely defective, meet the evidence\nstandard and publish the win/lose split of what the fix admits; and **reporting a loss is a\nsuccess** — one line, revert, next lever, no retraction narrative.\n\n---\n\n## RULE NUMBER 1: NO FILE DELETION\n\n**YOU ARE NEVER ALLOWED TO DELETE A FILE WITHOUT EXPRESS PERMISSION.** Even a new file that you yourself created, such as a test code file. You have a horrible track record of deleting critically important files or otherwise throwing away tons of expensive work. As a result, you have permanently lost any and all rights to determine that a file or folder should be deleted.\n\n**YOU MUST ALWAYS ASK AND RECEIVE CLEAR, WRITTEN PERMISSION BEFORE EVER DELETING A FILE OR FOLDER OF ANY KIND.**\n\n---\n\n## Irreversible Git & Filesystem Actions — DO NOT EVER BREAK GLASS\n\n> **Note:** This project exists specifically to block these dangerous commands for AI agents. Practice what we preach.\n\n1. **Absolutely forbidden commands:** `git reset --hard`, `git clean -fd`, `rm -rf`, or any command that can delete or overwrite code/data must never be run unless the user explicitly provides the exact command and states, in the same message, that they understand and want the irreversible consequences.\n2. **No guessing:** If there is any uncertainty about what a command might delete or overwrite, stop immediately and ask the user for specific approval. \"I think it's safe\" is never acceptable.\n3. **Safer alternatives first:** When cleanup or rollbacks are needed, request permission to use non-destructive options (`git status`, `git diff`, `git stash`, copying to backups) before ever considering a destructive command.\n4. **Mandatory explicit plan:** Even after explicit user authorization, restate the command verbatim, list exactly what will be affected, and wait for a confirmation that your understanding is correct. Only then may you execute it—if anything remains ambiguous, refuse and escalate.\n5. **Document the confirmation:** When running any approved destructive command, record (in the session notes / final response) the exact user text that authorized it, the command actually run, and the execution time. If that record is absent, the operation did not happen.\n\n---\n\n## Git Branch: ONLY Use `main`, NEVER `master`\n\n**The default branch is `main`. The `master` branch exists only for legacy URL compatibility.**\n\n- **All work happens on `main`** — commits, PRs, feature branches all merge to `main`\n- **Never reference `master` in code or docs** — if you see `master` anywhere, it's a bug that needs fixing\n- **The `master` branch must stay synchronized with `main`** — after pushing to `main`, also push to `master`:\n  ```bash\n  git push origin main:master\n  ```\n\n**Why this matters:** The `dcg update` command and install URLs historically referenced `master`. If `master` falls behind `main`, users get stale code. We had a bug where `master` was **497 commits behind**, causing users to see old installer behavior.\n\n**If you see `master` referenced anywhere:**\n1. Update it to `main`\n2. Ensure `master` is synchronized: `git push origin main:master`\n\n---\n\n## Toolchain: Rust & Cargo\n\nWe only use **Cargo** in this project, NEVER any other package manager.\n\n- **Edition:** Rust 2024 (nightly required — see `rust-toolchain.toml`)\n- **Dependency versions:** Explicit versions for stability\n- **Configuration:** Cargo.toml only (single crate, not a workspace)\n- **Unsafe code:** Forbidden (`#![forbid(unsafe_code)]`)\n\n### Key Dependencies\n\n| Crate | Purpose |\n|-------|---------|\n| `serde` + `serde_json` | JSON parsing for Claude Code hook protocol |\n| `serde_yaml` | External pack YAML parsing |\n| `toml` + `toml_edit` | TOML config parsing with formatting preservation |\n| `fancy-regex` | Advanced regex with lookahead/lookbehind |\n| `regex` | `RegexSet` for heredoc detection |\n| `memchr` | SIMD-accelerated substring search |\n| `aho-corasick` | Multi-pattern string matching for keyword quick-reject |\n| `colored` | Terminal colors with TTY detection |\n| `clap` + `clap_complete` | CLI argument parsing with shell completions |\n| `chrono` | RFC 3339 timestamps |\n| `ast-grep-core` + `ast-grep-language` | AST-based pattern matching for heredoc/inline-script content |\n| `rusqlite` | Bundled upstream SQLite for best-effort telemetry history |\n| `rust-mcp-sdk` | MCP server integration (stdio transport) |\n| `tokio` | Async runtime for MCP server mode |\n| `ratatui` + `comfy-table` + `indicatif` + `console` | TUI/CLI visual polish |\n| `self_update` | Binary self-update from GitHub Releases |\n| `vergen-gix` | Build metadata embedding (build.rs) |\n| `tracing` + `tracing-subscriber` | Structured logging and diagnostics |\n| `sha2` + `hmac` | Hashing and HMAC for allow-once short codes |\n| `flate2` | Gzip compression for history export |\n\n### Release Profile\n\nThe release build optimizes for binary size:\n\n```toml\n[profile.release]\nopt-level = \"z\"     # Optimize for size (lean binary for distribution)\nlto = true          # Link-time optimization\ncodegen-units = 1   # Single codegen unit for better optimization\npanic = \"abort\"     # Smaller binary, no unwinding overhead\nstrip = true        # Remove debug symbols\n```\n\n### Feature Flags\n\n```toml\n[features]\nrayon = [\"dep:rayon\"]           # Rayon data parallelism (optional)\nrich-output = [\"dep:rich_rust\"] # Enable rich_rust for premium terminal output\nlegacy-output = []              # Keep old rendering (placeholder for gradual migration)\n```\n\n---\n\n## Code Editing Discipline\n\n### No Script-Based Changes\n\n**NEVER** run a script that processes/changes code files in this repo. Brittle regex-based transformations create far more problems than they solve.\n\n- **Always make code changes manually**, even when there are many instances\n- For many simple changes: use parallel subagents\n- For subtle/complex changes: do them methodically yourself\n\n### No File Proliferation\n\nIf you want to change something or add a feature, **revise existing code files in place**.\n\n**NEVER** create variations like:\n- `mainV2.rs`\n- `main_improved.rs`\n- `main_enhanced.rs`\n\nNew files are reserved for **genuinely new functionality** that makes zero sense to include in any existing file. The bar for creating new files is **incredibly high**.\n\n---\n\n## Backwards Compatibility\n\nWe do not care about backwards compatibility—we're in early development with no users. We want to do things the **RIGHT** way with **NO TECH DEBT**.\n\n- Never create \"compatibility shims\"\n- Never create wrapper functions for deprecated APIs\n- Just fix the code directly\n\n---\n\n## Compiler Checks (CRITICAL)\n\n**After any substantive code changes, you MUST verify no errors were introduced:**\n\n```bash\n# Check for compiler errors and warnings\ncargo check --all-targets\n\n# Check for clippy lints (pedantic + nursery are enabled)\ncargo clippy --all-targets -- -D warnings\n\n# Verify formatting\ncargo fmt --check\n```\n\nIf you see errors, **carefully understand and resolve each issue**. Read sufficient context to fix them the RIGHT way.\n\n---\n\n## Windows Support (native, `x86_64-pc-windows-msvc`)\n\ndcg ships a **native Windows** binary (built/tested on `windows-latest` with the\nnightly toolchain) and a `check (windows)` CI job. When touching anything\nplatform-sensitive, follow these conventions:\n\n- **Separate command-pattern DATA from dcg's own paths.** Destructive-command\n  patterns (`rm -rf /`, `normalize.rs` stripping `/usr/bin/git`, `/etc`, `/tmp`)\n  are DATA about Unix commands and must STAY — Windows users still run git-bash.\n  Only dcg's *own* config/state paths get Windows-ified (resolve via the `dirs`\n  crate; the system layer is `%ProgramData%\\dcg`, helper `config::system_config_dir()`).\n- **`.exe` suffix.** When constructing a path to the dcg binary, use\n  `env!(\"CARGO_BIN_EXE_dcg\")` / `assert_cmd::cargo::cargo_bin(\"dcg\")` in tests, or\n  `std::env::consts::EXE_SUFFIX` in `src`. **Never** a bare `push(\"dcg\")` — the\n  Windows CI job greps for it and fails. Resolve per-user paths only through\n  `config::home_dir()` / `user_config_dir()` / `user_data_dir()` /\n  `user_data_local_dir()` / `user_cache_dir()` — never `dirs::*` (clippy's\n  `disallowed-methods` rejects it) and never raw `HOME` (unset on Windows).\n  `dirs` resolves the Windows profile via the known-folder API and ignores\n  `USERPROFILE`, so a sandboxed Windows test run used to rewrite the operator's\n  real `~\\.claude\\settings.json` and allow-once store (bd-b2b1). Set\n  `USERPROFILE`/`TEMP`/`TMP` alongside `HOME` in test isolation; `USERPROFILE`\n  alone now relocates `%APPDATA%`/`%LOCALAPPDATA%` too when those are unset.\n- **Verify Windows branches from Linux** without a Windows box: `mingw` + the\n  `x86_64-pc-windows-gnu` target are installed, so\n  `cargo check --target x86_64-pc-windows-gnu --lib` (or `--bin dcg` / `--tests`)\n  compile-checks every `#[cfg(windows)]` path. When `pwsh` is installed, use it\n  to run the PowerShell installer/test scripts; otherwise run those gates on the\n  native Windows release host.\n- **Windows packs.** `src/packs/windows/` holds the native-Windows packs\n  (`windows.filesystem`/`windows.system` default-ON on Windows, `windows.misc`/\n  `windows.powershell` opt-in). Patterns use inline `(?i)`; keyword arrays\n  may retain conventional casing variants for readability, but keyword\n  quick-rejection is ASCII case-insensitive so mixed-case Windows spellings\n  cannot skip the regex stage (see `src/packs/windows/mod.rs`). See\n  [`docs/windows.md`](docs/windows.md).\n- **The `careful_company_running_windows` preset.**\n  `src/packs/careful_company_running_windows/` holds six opt-in sub-packs\n  covering **outbound communication and data egress** (email, chat/webhooks,\n  HTTP upload, file transfer, tunnels) plus **tampering with the controls that\n  supervise the agent** (Defender/firewall/EDR, audit logs, and dcg's own\n  bypass/uninstall). It is the only pack ID with *curated transitive\n  membership*: enabling it also enables the pinned\n  `CAREFUL_COMPANY_PRESET_MEMBERS` list in `src/packs/mod.rs`\n  (`windows.*`, `database.*`, `storage.*`, `remote.*`, `backup.*`, `secrets.*`,\n  `cloud.*`). That list is deliberately explicit — do **not** convert it to\n  prefix matching, or future packs will join a security posture silently.\n  Two invariants to preserve when touching this area:\n  - **Tier order.** `windows.*` is tier 11 and the preset is tier 12, so\n    Windows packs keep claiming attribution for commands both match. Rule ids\n    (`pack_id:pattern_name`) are what allowlists key on, so reordering these\n    silently invalidates existing `windows.*` allowlist entries.\n  - **The `hfdt` trust boundary.** While any preset sub-pack is enabled,\n    `src/evaluator.rs` allows a command whose executable is `hfdt` **before any\n    pack runs** — including `core.*`. It is structural (whole-segment\n    executable match, no chains/redirection/substitution), but it does mean\n    enabling the preset *reduces* coverage for that one executable. Keep it\n    documented in `README.md` and `docs/careful-company-windows.md`.\n\n---\n\n## Testing\n\n### Testing Policy\n\nEvery module includes inline `#[cfg(test)]` unit tests alongside the implementation. Tests must cover:\n- Happy path\n- Edge cases (empty input, max values, boundary conditions)\n- Error conditions\n\nEnd-to-end tests live in `scripts/e2e_test.sh`.\n\n### Unit Tests\n\nThe test suite includes 80+ tests covering all functionality:\n\n```bash\n# Run all tests\ncargo test\n\n# Run with output\ncargo test -- --nocapture\n\n# Run specific test module\ncargo test normalize_command_tests\ncargo test safe_pattern_tests\ncargo test destructive_pattern_tests\n```\n\n**Prefer `cargo nextest run` when touching anything that mutates the\nenvironment.** CI runs `cargo nextest run --profile ci`, and nextest gives each\ntest its own process — verified by observation, not assumption: sampling a\n`--lib` run showed up to 128 concurrent per-test `--exact` invocations. That\nprocess isolation is what makes the `unsafe { env::set_var }` sites in test code\nsound, because it removes the concurrent reader.\n\nUnder plain `cargo test` every test shares one process and runs on threads, and\nthe crate has ~109 `env::var` call sites against ~22 `EnvVarGuard` uses, none of\nthe readers taking the lock — plus native code (bundled SQLite reading `TMPDIR`)\nthat cannot take a Rust lock even in principle. So `cargo test` is the\nconfiguration the single crate-wide `ENV_LOCK` cannot actually make safe (#445).\nNo failure has been observed from this; the race is available, not active.\n\n`cargo test` stays documented above because filter and `--ignored` syntax differ\nbetween the two runners (nextest wants `--run-ignored`), and several workflows in\nthis file depend on the `cargo test` spelling.\n\n### The Three Release-Blocking E2E Suites (read before touching perf or protocols)\n\n`cargo test` cannot catch the failure modes that have actually broken users.\nThree real-binary, no-mock suites exist specifically to close those gaps. All\nthree must be green before any release.\n\n| Suite | Catches | Why unit tests can't |\n|-------|---------|----------------------|\n| `scripts/e2e_harness_matrix.sh` | Wire/bridge breakage for **every** agent (Claude Code, Codex, Gemini, Copilot, Hermes, Grok, agy, Crush, OMP) | Unit tests call Rust functions; harnesses parse **bytes**. Asserts decision field + exit code + stdout/stderr separation per protocol against the real binary. |\n| `scripts/perf_baseline.py --assert-budget-ms` | **#245**: evaluator cost silently eating the fixed hook deadline | The perf job is a *relative* ratchet — a uniform slowdown just gets re-baselined. This gate asserts paired `full_eval − DCG_BYPASS` p95 against the **shipped** `HOOK_EVALUATION_BUDGET_MS` with a hermetic HOME and scrubbed `DCG_*`; raw process latency remains separate evidence. |\n| `scripts/e2e_fleet_install.sh` | Published artifact missing/unrunnable per platform; installer picking the wrong triple; checksum/signature verification silently skipped; hook config non-idempotent | Nothing in-tree proves the **public download path** works on real Linux/macOS/Windows hardware. |\n\n```bash\n# Protocol/bridge conformance for all 8 harnesses (needs a release binary + jq)\n./scripts/e2e_harness_matrix.sh --binary target/release/dcg\n\n# Absolute evaluator-cost gate — the #245 guard. Budget MUST come from src/perf.rs.\nBUDGET_MS=$(sed -nE 's/^pub const HOOK_EVALUATION_BUDGET_MS: u64 = ([0-9_]+);$/\\1/p' src/perf.rs | tr -d '_')\npython3 scripts/perf_baseline.py --bin target/release/dcg --skip-trace \\\n  --assert-budget-ms \"$BUDGET_MS\" --assert-margin-pct 50\n\n# Real installs from the PUBLIC release on every DSR host\n./scripts/e2e_fleet_install.sh --version vX.Y.Z          # whole fleet\n./scripts/e2e_fleet_install.sh --version vX.Y.Z --local-only\n```\n\nRules:\n- **Scrub ambient `DCG_*` before measuring anything.** Operators bitten by #245\n  export `DCG_HOOK_TIMEOUT_MS=5000` (an agent `settings.json` `env` block puts\n  it in every child process), so an un-scrubbed suite measures the *workaround*\n  and passes on exactly the machines that need protecting. `env -i` covers the\n  hook calls; the installer cannot use it (it needs the host PATH for\n  `curl`/`tar`/`xz`/`minisign`), so the probes also `unset` every `DCG_*` up\n  front. Assert `general.hook_timeout_source` too — a bare `>= 1000` check\n  cannot tell the shipped default from an inherited 5000.\n- **Set `DCG_SELF_HEAL_HOOK=0` before the installer runs, not after.** dcg\n  repairs a missing/stale hook entry whenever it runs in hook mode, and\n  released binaries up to v0.14.4 resolve the Windows settings path via the\n  Win32 known-folder API, which `USERPROFILE` cannot redirect — so a late\n  disable can rewrite a real machine's agent config. Current source honors\n  `USERPROFILE` (bd-b2b1), but the installer may still fetch an older release.\n- **Never hard-code the budget in `.github/workflows/ci.yml`.** It is grepped\n  out of `HOOK_EVALUATION_BUDGET_MS`; `perf::tests::ci_enforces_absolute_latency_gate_against_shipped_budget`\n  fails if that wiring is removed or the margin is loosened past 60%.\n- **Treat the JSON as the certificate, not stderr.** Gate mode records its\n  supplied/shipped/effective budgets, margin, derived limit, every per-case\n  verdict, 95/95 binomial tail-tolerance result, violations, and overall\n  PASS/FAIL in `latency_gate`; CI retains that artifact even when the gate\n  fails. Gate mode requires at least 59 samples; CI uses 100 and permits at\n  most one over-limit sample per case. When using `--output` in gate mode,\n  place it outside the repository; the harness rejects in-tree output so its\n  own certificate cannot dirty the source snapshot it claims to measure.\n- **Bind the binary to the checkout.** Gate mode requires a clean checkout and\n  exact equality between the binary's embedded `git describe --tags --dirty`\n  value and the repository's value. CI uses a full tag history so a shallow\n  clone cannot turn this proof into an unknown result.\n- Measure dcg's own cost as `full_eval − DCG_BYPASS`, never raw wall-clock:\n  process spawn (≈940ms under Windows PowerShell) sits **outside** the\n  evaluation deadline and would otherwise produce false alarms. For host\n  safety this certificate sets `DCG_SELF_HEAL_HOOK=0`, records that exclusion,\n  and therefore does not claim to measure self-healing work. Capture and\n  validate every timed child's actual wire decision after stopping its timer;\n  before/after semantic controls alone cannot catch intermittent fail-open\n  behavior inside the sample window.\n- The fleet suite installs into a scratch prefix with an isolated `HOME` and\n  `--no-configure`; it never touches a host's real agent hook config.\n- A probe that dies partway must FAIL, not pass: every probe emits\n  `probe_complete` and the runner asserts the full expected case set.\n\n### End-to-End Testing\n\n```bash\n# Run the E2E test script (needs bash >= 4; macOS /bin/bash 3.2 breaks the summary)\n./scripts/e2e_test.sh\n\n# Or test manually\necho '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git reset --hard\"}}' | cargo run --release\n# Should output JSON denial\n\necho '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git status\"}}' | cargo run --release\n# Should output nothing (allowed)\n```\n\n### Test Categories\n\n| Module | Tests | Purpose |\n|--------|-------|---------|\n| `normalize_command_tests` | 8 | Path stripping for git/rm binaries |\n| `quick_reject_tests` | 5 | Fast-path filtering for non-git/rm commands |\n| `safe_pattern_tests` | 16 | Whitelist accuracy |\n| `destructive_pattern_tests` | 20 | Blacklist coverage |\n| `input_parsing_tests` | 8 | JSON parsing robustness |\n| `deny_output_tests` | 2 | Output format validation |\n| `integration_tests` | 4 | End-to-end pipeline |\n| `optimization_tests` | 9 | Performance paths |\n| `edge_case_tests` | 24 | Real-world edge cases |\n\n---\n\n## Third-Party Library Usage\n\nIf you aren't 100% sure how to use a third-party library, **SEARCH ONLINE** to find the latest documentation and current best practices.\n\n---\n\n## dcg (Destructive Command Guard) — This Project\n\n**This is the project you're working on.** dcg is a high-performance Claude Code hook that blocks destructive commands before they execute. It protects against dangerous git commands, filesystem operations, database queries, container commands, and more through a modular pack system.\n\n### What It Does\n\nGuards AI coding agents from executing destructive commands by intercepting Claude Code's `PreToolUse` hook protocol, evaluating commands against safe/destructive pattern lists, and denying dangerous operations with structured JSON output including remediation suggestions.\n\n### Architecture\n\n```\nJSON Input → Parse → Quick Reject (memchr) → Normalize → Safe Patterns → Destructive Patterns → Default Allow\n```\n\n### Key Files\n\n| File | Purpose |\n|------|---------|\n| `src/main.rs` | Entry point, hook I/O, CLI dispatch |\n| `src/evaluator.rs` | Pattern matching engine (safe + destructive evaluation) |\n| `src/hook.rs` | Claude Code PreToolUse hook protocol handling |\n| `src/normalize.rs` | Command normalization (path stripping, alias expansion) |\n| `src/heredoc.rs` | Heredoc and inline script extraction |\n| `src/ast_matcher.rs` | AST-based pattern matching for embedded code |\n| `src/config.rs` | Configuration loading (TOML, allowlists, pack enable/disable) |\n| `src/allowlist.rs` | Allowlist management (project, user, system scopes) |\n| `src/cli.rs` | CLI commands (explain, scan, packs, allowlist, etc.) |\n| `src/scan.rs` | Codebase scanning for destructive patterns |\n| `src/context.rs` | Contextual analysis for pattern matching |\n| `src/confidence.rs` | Match confidence scoring |\n| `src/error_codes.rs` | Standardized DCG-XXXX error codes |\n| `src/exit_codes.rs` | Process exit code definitions |\n| `src/packs/` | Modular pattern pack system (core + extensions) |\n| `src/output/` | Output formatting (JSON, colorful stderr) |\n| `src/highlight.rs` | Syntax highlighting for command display |\n| `src/logging.rs` | Tracing/logging configuration |\n| `src/perf.rs` | Performance budgets and benchmarks |\n| `src/simulate.rs` | Command simulation and dry-run support |\n| `src/mcp.rs` | MCP server integration |\n| `src/agent.rs` | Agent detection and identification |\n| `src/interactive.rs` | Interactive mode |\n| `src/git.rs` | Git-specific command analysis |\n| `src/history/` | Decision history and telemetry |\n| `src/sarif.rs` | SARIF output format for scan results |\n| `src/pending_exceptions.rs` | Pending exception management |\n| `src/lib.rs` | Library re-exports |\n| `Cargo.toml` | Dependencies and release optimizations |\n| `build.rs` | Build script for version metadata (vergen) |\n| `rust-toolchain.toml` | Nightly toolchain requirement |\n| `scripts/e2e_test.sh` | End-to-end test script (hundreds of command scenarios) |\n\n### Output Style\n\nThis tool has two output modes:\n\n- **JSON to stdout:** For Claude Code hook protocol (`hookSpecificOutput` with `permissionDecision: \"deny\"`)\n- **Colorful warning to stderr:** For human visibility when commands are blocked\n\nOutput behavior:\n- **Deny:** Colorful warning to stderr + JSON to stdout\n- **Allow:** No output (silent exit)\n- **--version/-V:** Version info with build metadata to stderr\n- **--help/-h:** Usage information to stderr\n\nColors are automatically disabled when stderr is not a TTY (e.g., piped to file).\n\n### Pattern System\n\n- **34 safe patterns** (whitelist, checked first)\n- **16 destructive patterns** (blacklist, checked second)\n- **Default allow** for unmatched commands\n\n### Adding New Patterns\n\n1. Identify the command to block/allow\n2. Write a regex using `fancy-regex` syntax (supports lookahead/lookbehind)\n3. Add to `SAFE_PATTERNS` or `DESTRUCTIVE_PATTERNS` using the macros:\n\n```rust\n// Safe pattern (whitelist)\npattern!(\"pattern-name\", r\"regex-here\")\n\n// Destructive pattern (blacklist)\ndestructive!(\n    r\"regex-here\",\n    \"Human-readable reason for blocking\"\n)\n```\n\n4. Add tests for all variants\n5. Run `cargo test` and `./scripts/e2e_test.sh`\n\n### Performance Requirements\n\nEvery Bash command passes through this hook. Performance is critical:\n\n- Quick rejection filter eliminates 99%+ of commands before regex\n- Lazy-initialized static regex patterns (compiled once, reused)\n- Sub-millisecond execution for typical commands\n- Zero allocations on the hot path for safe commands\n\n### Heredoc Detection Notes\n\n- **Rule IDs**: Heredoc patterns use stable IDs like `heredoc.python.shutil_rmtree` for allowlisting.\n- **Bounded failure**: Heredoc parse/AST failures use the configured bounded\n  fallback; disabling fallback blocks. Absolute hook-deadline exhaustion and\n  incomplete nested evaluation return `Indeterminate`, never `Allow`.\n- **Tests**: Prefer targeted tests in `src/ast_matcher.rs` and `src/heredoc.rs`.\n  - `cargo test ast_matcher`\n  - `cargo test heredoc`\n  - Add positive and negative fixtures for each new pattern.\n\n---\n\n<!-- dcg-machine-readable-v1 -->\n\n## DCG Hook Protocol (Machine-Readable Reference)\n\n> This section provides structured documentation for AI agents integrating with dcg.\n\n### JSON Input Format\n\ndcg reads from stdin in Claude Code's `PreToolUse` hook format:\n\n```json\n{\n  \"tool_name\": \"Bash\",\n  \"tool_input\": {\n    \"command\": \"git reset --hard HEAD~5\"\n  }\n}\n```\n\n**Required fields:**\n- `tool_name`: Must be `\"Bash\"` for dcg to process (other tools are ignored)\n- `tool_input.command`: The shell command string to evaluate\n\n### JSON Output Format (Denial)\n\nWhen a command is blocked, dcg outputs JSON to stdout:\n\n```json\n{\n  \"hookSpecificOutput\": {\n    \"hookEventName\": \"PreToolUse\",\n    \"permissionDecision\": \"deny\",\n    \"permissionDecisionReason\": \"BLOCKED by dcg\\n\\nTip: dcg explain \\\"git reset --hard HEAD~5\\\"\\n\\nReason: git reset --hard destroys uncommitted changes\\n\\nExplanation: Rewrites history and discards uncommitted changes.\\n\\nRule: core.git:reset-hard\\n\\nIf this operation is truly needed, ask the user for explicit permission and have them run the command manually.\",\n    \"ruleId\": \"core.git:reset-hard\",\n    \"packId\": \"core.git\",\n    \"severity\": \"critical\",\n    \"confidence\": 1.0,\n    \"allowOnceCode\": \"a1b2c3\",\n    \"allowOnceFullHash\": \"sha256:abc123...\",\n    \"remediation\": {\n      \"safeAlternative\": \"git stash\",\n      \"explanation\": \"Use git stash to save your changes first.\",\n      \"allowOnceCommand\": \"dcg allow-once a1b2c3\"\n    }\n  }\n}\n```\n\n**Key fields for agent parsing:**\n| Field | Type | Description |\n|-------|------|-------------|\n| `permissionDecision` | `\"allow\"` \\| `\"deny\"` | The decision |\n| `ruleId` | `string` | Stable pattern ID (e.g., `\"core.git:reset-hard\"`) for allowlisting |\n| `packId` | `string` | Pack that matched (e.g., `\"core.git\"`) |\n| `severity` | `string` | `\"critical\"`, `\"high\"`, `\"medium\"`, or `\"low\"` |\n| `confidence` | `number?` | Match confidence 0.0–1.0 (two decimals) from `crate::confidence::compute_match_confidence`, the score the `[confidence]` warn threshold reads: 1.0 when the matched span is executed (command word, wrapper-invoked command, redirection target), lower when it sits in quoted data, a comment, or an inert command's arguments. Omitted when the match has no source span (e.g. inside `$(…)`, structural verdicts). |\n| `allowOnceCode` | `string` | Short code for `dcg allow-once` |\n| `remediation.safeAlternative` | `string?` | Suggested safe command |\n\n### JSON Output Format (Allow)\n\nWhen a command is allowed: **no output** (silent exit 0).\n\n---\n\n## Exit Codes Reference\n\n| Code | Meaning | Agent Action |\n|------|---------|--------------|\n| `0` | Command allowed OR protocol JSON denial was emitted | Parse stdout; if empty, command was allowed |\n| `1` | Parse error or invalid input | Retry with corrected input |\n| `2` | Configuration error | Check config syntax and stderr diagnostics |\n\n**Detection logic for agents:**\n```bash\noutput=$(echo \"$hook_input\" | dcg 2>/dev/null)\nif [ -z \"$output\" ]; then\n  echo \"ALLOWED\"\nelse\n  echo \"DENIED: $output\"\nfi\n```\n\nCodex CLI uses a stricter hook parser: blocked commands return a minimal\n`hookSpecificOutput` denial on stdout with exit code 0. See\n[`docs/codex-integration.md`](docs/codex-integration.md) for the Codex-specific\nprotocol notes.\n\n---\n\n## Error Codes Reference\n\nDCG uses standardized error codes in the format `DCG-XXXX` for machine-parseable error handling.\n\n### Error Categories\n\n| Range | Category | Description |\n|-------|----------|-------------|\n| DCG-1xxx | `pattern_match` | Pattern matching and evaluation errors |\n| DCG-2xxx | `configuration` | Configuration loading and parsing errors |\n| DCG-3xxx | `runtime` | Runtime and execution errors |\n| DCG-4xxx | `external` | External integration errors |\n\n### Common Error Codes\n\n| Code | Description | Typical Cause |\n|------|-------------|---------------|\n| `DCG-1001` | Pattern compilation failed | Invalid regex syntax in pattern |\n| `DCG-1002` | Pattern match timeout | Complex pattern taking too long |\n| `DCG-2001` | Config file not found | Missing configuration file |\n| `DCG-2002` | Config parse error | Invalid TOML/JSON syntax |\n| `DCG-2004` | Allowlist load error | Invalid allowlist file |\n| `DCG-3001` | JSON parse error | Malformed JSON input |\n| `DCG-3002` | IO error | File read/write failure |\n| `DCG-4001` | External pack load failed | Invalid external pack YAML |\n\n### Error JSON Structure\n\nWhen errors are returned in JSON format, they follow this structure:\n\n```json\n{\n  \"error\": {\n    \"code\": \"DCG-3001\",\n    \"category\": \"runtime\",\n    \"message\": \"JSON parse error: unexpected token at position 15\",\n    \"context\": {\n      \"position\": 15,\n      \"input_preview\": \"{ \\\"tool_name\\\": ...\"\n    }\n  }\n}\n```\n\n**Fields:**\n- `code`: Stable error code for programmatic handling\n- `category`: Error category (`pattern_match`, `configuration`, `runtime`, `external`)\n- `message`: Human-readable error description\n- `context`: Additional details (optional, varies by error type)\n\n---\n\n## Allowlist & Bypass Instructions\n\n### Temporary Bypass (24-hour allow-once)\n\nWhen a command is blocked, the output includes an `allowOnceCode`. Use it:\n\n```bash\ndcg allow-once <code>\n```\n\nThis allows the specific command for 24 hours in the current directory scope.\n\n### Permanent Allowlist (by rule ID)\n\nAdd a rule to the project allowlist:\n\n```bash\ndcg allowlist add <ruleId> --project\n# Example: dcg allowlist add core.git:reset-hard --project\n```\n\nAllowlist files (in priority order):\n1. `.dcg/allowlist.toml` (project)\n2. `~/.config/dcg/allowlist.toml` (user)\n3. `/etc/dcg/allowlist.toml` (system)\n\n### Bypass Environment Variable\n\nFor emergency bypass (use sparingly):\n\n```bash\nDCG_BYPASS=1 <command>\n```\n\n**Warning:** This disables all protection. Log and justify any usage.\n\n---\n\n## Pattern Quick Reference\n\n### Core Git Patterns (Always Enabled)\n\n| Pattern ID | Blocks | Severity |\n|------------|--------|----------|\n| `core.git:reset-hard` | `git reset --hard` | Critical |\n| `core.git:reset-merge` | `git reset --merge` | High |\n| `core.git:checkout-discard` | `git checkout -- <file>` | High |\n| `core.git:restore-discard` | `git restore <file>` (without `--staged`) | High |\n| `core.git:clean-force` | `git clean -f`, `git clean -fd` | High |\n| `core.git:force-push` | `git push --force`, `git push -f` | High |\n| `core.git:branch-delete` | `git branch -d`, `--delete` (merge-checked, no force) | High |\n| `core.git:branch-force-delete` | `git branch -D`, `--delete --force`, `-f`, `-M`, `-C` (a policy/allowlist entry for it also covers `branch-delete` unless that rule has its own) | High |\n| `core.git:stash-drop` | `git stash drop` — Medium, so the default policy **warns and lets it run** (the dropped stash stays recoverable via `git fsck` until gc) | Medium |\n| `core.git:stash-clear` | `git stash clear` | Critical |\n\n### Core Filesystem Patterns (Always Enabled)\n\n| Pattern ID | Blocks | Severity |\n|------------|--------|----------|\n| `core.filesystem:rm-rf-root` | `rm -rf /`, `rm -rf ~` | Critical |\n| `core.filesystem:rm-rf-general` | `rm -rf` outside temp dirs | High |\n\n### Safe Patterns (Whitelist - Always Allowed)\n\n| Pattern | Command | Why Safe |\n|---------|---------|----------|\n| `git-checkout-branch` | `git checkout -b <branch>` | Creates new branch |\n| `git-checkout-orphan` | `git checkout --orphan <branch>` | Creates orphan branch |\n| `git-restore-staged` | `git restore --staged <file>` | Only unstages, doesn't discard |\n| `git-clean-dry-run` | `git clean -n`, `git clean --dry-run` | Preview only |\n| `rm-tmp` | `rm -rf /tmp/*`, `/var/tmp/*` | Temp directory cleanup |\n\n### Pack Enable/Disable Examples\n\n```toml\n# ~/.config/dcg/config.toml\n[packs]\nenabled = [\n    \"database.postgresql\",    # Blocks DROP TABLE, TRUNCATE\n    \"kubernetes.kubectl\",     # Blocks kubectl delete namespace\n    \"cloud.aws\",              # Blocks aws ec2 terminate-instances\n]\n\ndisabled = [\n    \"containers.docker\",      # Disable Docker protection\n]\n```\n\nList all packs: `dcg packs --verbose`\n\n---\n\n## CLI Quick Reference for Agents\n\n| Command | Purpose |\n|---------|---------|\n| `dcg explain \"<command>\"` | Detailed trace of why command is blocked/allowed |\n| `dcg allow-once <code>` | Allow a blocked command for 24 hours |\n| `dcg allowlist add <ruleId> --project` | Permanently allow a rule |\n| `dcg packs` | List enabled packs |\n| `dcg packs --verbose` | List all packs with pattern counts |\n| `dcg scan .` | Scan codebase for destructive patterns |\n| `dcg --version` | Show version and build info |\n\n---\n\n## Agent Integration Checklist\n\nWhen integrating with dcg, ensure your agent:\n\n- [ ] Parses stdout for JSON denial responses\n- [ ] Handles empty stdout as \"command allowed\"\n- [ ] Uses `ruleId` for stable allowlisting (not pattern text)\n- [ ] Displays `remediation.safeAlternative` to users when available\n- [ ] Respects `severity` for prioritization (critical > high > medium > low)\n- [ ] Uses `dcg explain` before asking users to bypass\n\n---\n\n## JSON Schema Reference\n\nFormal JSON Schema definitions (Draft 2020-12) for all dcg output formats are available in `docs/json-schema/`:\n\n| Schema | Purpose |\n|--------|---------|\n| [`hook-output.json`](docs/json-schema/hook-output.json) | PreToolUse hook denial response format |\n| [`scan-results.json`](docs/json-schema/scan-results.json) | `dcg scan` command output format |\n| [`stats-output.json`](docs/json-schema/stats-output.json) | `dcg stats` command output format |\n| [`error.json`](docs/json-schema/error.json) | Error response formats for various commands |\n\nUse these schemas for:\n- Validating dcg output in automated pipelines\n- Generating type-safe client code\n- Understanding the complete output contract\n\n<!-- end-dcg-machine-readable -->\n\n---\n\n## CI/CD Pipeline\n\n### Jobs Overview\n\n| Job | Trigger | Purpose | Blocking |\n|-----|---------|---------|----------|\n| `check` | PR, push | Format, clippy, UBS, tests | Yes |\n| `coverage` | PR, push | Coverage thresholds | Yes |\n| `memory-tests` | PR, push | Memory leak detection | Yes |\n| `benchmarks` | push to main | Performance budgets | Warn only |\n| `e2e` | PR, push | End-to-end shell tests | Yes |\n| `scan-regression` | PR, push | Scan output stability | Yes |\n| `perf-regression` | PR, push | Process-per-invocation perf | Yes |\n\n### Check Job\n\nRuns format, clippy, UBS static analysis, and unit tests. Includes:\n- `cargo fmt --check` - Code formatting\n- `cargo clippy --all-targets -- -D warnings` - Lints (pedantic + nursery enabled)\n- UBS analysis on changed Rust files (warning-only, non-blocking)\n- `cargo nextest run` - Full test suite with JUnit XML report\n\n### Coverage Job\n\nRuns `cargo llvm-cov` and enforces the thresholds configured in\n`.github/workflows/ci.yml` (`OVERALL_MIN`, `EVALUATOR_MIN`, `HOOK_MIN`).\nThese are enforced gates, not aspirational targets:\n- **Overall:** >= 70%\n- **src/evaluator.rs:** >= 65%\n- **src/hook.rs:** >= 70%\n\nIf CI thresholds change, update this section in the same change. The\n`coverage_threshold_docs` test checks that these documented values stay in sync\nwith the workflow.\n\nCoverage is uploaded to Codecov for trend tracking. Dashboard: https://codecov.io/gh/Dicklesworthstone/destructive_command_guard\n\n### Memory Tests Job\n\nRuns dedicated memory leak tests with:\n- `--test-threads=1` for accurate measurements\n- Release mode for realistic performance\n- 1-2MB growth budgets per test\n\nTests include: hook input parsing, pattern evaluation, heredoc extraction, file extractors, full pipeline, and a self-test that verifies the framework catches leaks.\n\n### Benchmarks Job\n\nRuns on push to main only (benchmarks are noisy on PRs). Checks performance budgets from `src/perf.rs`:\n- Quick reject: < 50us panic\n- Fast path: < 500us panic\n- Pattern match: < 1ms panic\n- Heredoc extract: < 2ms panic\n- Full heredoc pipeline: < 20ms panic\n- Hook evaluation deadline: 1000ms (exhaustion is indeterminate, never a silent allow)\n\n### UBS Static Analysis\n\nUltimate Bug Scanner runs on changed Rust files. Currently warning-only (non-blocking) to tune for false positives. Configuration in `.ubsignore` excludes test/bench/fuzz directories.\n\n### Dependabot\n\nAutomated dependency updates configured in `.github/dependabot.yml`:\n- **Cargo dependencies:** Weekly (Monday 9am EST), 5 PR limit\n- **GitHub Actions:** Weekly (Monday 9am EST), 3 PR limit\n- **Grouping:** Minor/patch updates grouped; serde updates separate (more careful review)\n\n### Debugging CI Failures\n\n#### Coverage Threshold Failure\n1. Check which file(s) dropped below threshold in CI output\n2. Run `cargo llvm-cov --html` locally to see uncovered lines\n3. Add tests for uncovered code paths\n4. Download `coverage-report` artifact for full details\n\n#### Memory Test Failure\n1. Download `memory-test-output` artifact\n2. Check which test failed and growth amount\n3. Run locally: `cargo test --test memory_tests --release -- --nocapture --test-threads=1`\n4. Profile with valgrind if needed\n\n#### UBS Warnings\n1. Check ubs-output.log in CI summary\n2. Review flagged issues - may be false positives\n3. If valid issues, fix them; if false positives, add to `.ubsignore`\n\n#### E2E Test Failure\n1. Download `e2e-artifacts` artifact\n2. Check `e2e_output.json` for failing test details\n3. Run locally: `./scripts/e2e_test.sh --verbose`\n4. The step summary shows the first failure with output\n\n#### Benchmark Regression\n1. Download `benchmark-results` artifact\n2. Compare against budgets in `src/perf.rs`\n3. Profile locally with `cargo bench --bench heredoc_perf`\n4. Check for algorithmic regressions in hot path\n\n---\n\n## Release Process\n\nWhen fixes are ready for release, follow this process:\n\nThe steps below describe the normal GitHub Actions path. If Actions cannot run\nor a native Windows artifact must be built locally, the more detailed\n**Local/DSR Release and Windows Deployment Runbook** near the end of this file\nis authoritative.\n\n### 1. Verify CI Passes Locally\n\n```bash\ncargo fmt --check\ncargo check --all-targets\ncargo clippy --all-targets -- -D warnings\ncargo test\n```\n\n### 2. Commit Changes\n\n```bash\ngit add -A\ngit commit -m \"fix: description of fixes\n\n- List specific fixes\n- Include any breaking changes\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\"\n```\n\n### 3. Bump Version (if needed)\n\nThe version in `Cargo.toml` determines the release tag. A version may be reused\nafter a failed *local, pre-tag* attempt. Once its tag has been pushed or a\nrelease has been published, treat that version as immutable and bump to a new\npatch version instead of moving the tag or replacing signed assets.\n\n- **Patch** (0.2.10 -> 0.2.11): Bug fixes, no new features\n- **Minor** (0.2.x -> 0.3.0): New features, backward compatible\n- **Major** (0.x -> 1.0): Breaking changes\n\n### 4. Push and Trigger Release\n\n```bash\ngit push origin main\ngit push origin main:master  # Keep master in sync\n```\n\nThe `release-automation.yml` workflow will:\n1. Detect version change in `Cargo.toml`\n2. Create an annotated git tag (e.g., `v0.2.13`)\n3. Push the tag, which triggers `dist.yml`\n\nThe `dist.yml` workflow will:\n1. Run tests and clippy\n2. Build binaries for all platforms (Linux x86/ARM, macOS Intel/Apple Silicon, Windows)\n3. Create `.tar.xz` archives with SHA256 checksums\n4. Sign artifacts with Sigstore (cosign) - creates `.sigstore.json` bundles\n5. Upload everything to GitHub Releases\n\n### 5. Verify Release\n\n```bash\ngh release list --limit 5\ngh release view v0.2.13  # Check assets were uploaded\n```\n\nExpected assets per release:\n- `dcg-{target}.tar.xz` (Unix) or `.zip` (Windows) - Binary archive\n- `<archive>.sha256` - Mandatory per-artifact checksum\n- `<archive>.sigstore.json` - Sigstore signature bundle\n- `install.sh`, `install.ps1`, and their checksum/Sigstore sidecars\n- Manual DSR releases additionally include minisign signatures, SLSA\n  provenance, a build manifest, and the pinned public verification keys\n\n### Troubleshooting Failed Releases\n\nIf CI fails:\n1. Check workflow run: `gh run list --workflow=dist.yml --limit=5`\n2. View failed job: `gh run view <run-id>`\n3. Fix issues locally, commit, and push again\n4. If no public tag or release exists yet, retry the same version; otherwise\n   create a new patch release. Never force-move a public release tag.\n\nCommon failures:\n- **Clippy errors**: Fix lints, ensure `cargo clippy -- -D warnings` passes\n- **Test failures**: Run `cargo test` to reproduce\n- **Format errors**: Run `cargo fmt` to fix\n\n---\n\n## MCP Agent Mail — Multi-Agent Coordination\n\nA mail-like layer that lets coding agents coordinate asynchronously via MCP tools and resources. Provides identities, inbox/outbox, searchable threads, and advisory file reservations with human-auditable artifacts in Git.\n\n### Why It's Useful\n\n- **Prevents conflicts:** Explicit file reservations (leases) for files/globs\n- **Token-efficient:** Messages stored in per-project archive, not in context\n- **Quick reads:** `resource://inbox/...`, `resource://thread/...`\n\n### Same Repository Workflow\n\n1. **Register identity:**\n   ```\n   ensure_project(project_key=<abs-path>)\n   register_agent(project_key, program, model)\n   ```\n\n2. **Reserve files before editing:**\n   ```\n   file_reservation_paths(project_key, agent_name, [\"src/**\"], ttl_seconds=3600, exclusive=true)\n   ```\n\n3. **Communicate with threads:**\n   ```\n   send_message(..., thread_id=\"FEAT-123\")\n   fetch_inbox(project_key, agent_name)\n   acknowledge_message(project_key, agent_name, message_id)\n   ```\n\n4. **Quick reads:**\n   ```\n   resource://inbox/{Agent}?project=<abs-path>&limit=20\n   resource://thread/{id}?project=<abs-path>&include_bodies=true\n   ```\n\n### Macros vs Granular Tools\n\n- **Prefer macros for speed:** `macro_start_session`, `macro_prepare_thread`, `macro_file_reservation_cycle`, `macro_contact_handshake`\n- **Use granular tools for control:** `register_agent`, `file_reservation_paths`, `send_message`, `fetch_inbox`, `acknowledge_message`\n\n### Common Pitfalls\n\n- `\"from_agent not registered\"`: Always `register_agent` in the correct `project_key` first\n- `\"FILE_RESERVATION_CONFLICT\"`: Adjust patterns, wait for expiry, or use non-exclusive reservation\n- **Auth errors:** If JWT+JWKS enabled, include bearer token with matching `kid`\n\n---\n\n## Beads (br) — Dependency-Aware Issue Tracking\n\nBeads provides a lightweight, dependency-aware issue database and CLI (`br` - beads_rust) for selecting \"ready work,\" setting priorities, and tracking status. It complements MCP Agent Mail's messaging and file reservations.\n\n**Important:** `br` is non-invasive—it NEVER runs git commands automatically. You must manually commit changes after `br sync --flush-only`.\n\n### Conventions\n\n- **Single source of truth:** Beads for task status/priority/dependencies; Agent Mail for conversation and audit\n- **Shared identifiers:** Use Beads issue ID (e.g., `br-123`) as Mail `thread_id` and prefix subjects with `[br-123]`\n- **Reservations:** When starting a task, call `file_reservation_paths()` with the issue ID in `reason`\n\n### Typical Agent Flow\n\n1. **Pick ready work (Beads):**\n   ```bash\n   br ready --json  # Choose highest priority, no blockers\n   ```\n\n2. **Reserve edit surface (Mail):**\n   ```\n   file_reservation_paths(project_key, agent_name, [\"src/**\"], ttl_seconds=3600, exclusive=true, reason=\"br-123\")\n   ```\n\n3. **Announce start (Mail):**\n   ```\n   send_message(..., thread_id=\"br-123\", subject=\"[br-123] Start: <title>\", ack_required=true)\n   ```\n\n4. **Work and update:** Reply in-thread with progress\n\n5. **Complete and release:**\n   ```bash\n   br close 123 --reason \"Completed\"\n   br sync --flush-only  # Export to JSONL (no git operations)\n   ```\n   ```\n   release_file_reservations(project_key, agent_name, paths=[\"src/**\"])\n   ```\n   Final Mail reply: `[br-123] Completed` with summary\n\n### Mapping Cheat Sheet\n\n| Concept | Value |\n|---------|-------|\n| Mail `thread_id` | `br-###` |\n| Mail subject | `[br-###] ...` |\n| File reservation `reason` | `br-###` |\n| Commit messages | Include `br-###` for traceability |\n\n---\n\n## bv — Graph-Aware Triage Engine\n\nbv is a graph-aware triage engine for Beads projects (`.beads/beads.jsonl`). It computes PageRank, betweenness, critical path, cycles, HITS, eigenvector, and k-core metrics deterministically.\n\n**Scope boundary:** bv handles *what to work on* (triage, priority, planning). For agent-to-agent coordination (messaging, work claiming, file reservations), use MCP Agent Mail.\n\n**CRITICAL: Use ONLY `--robot-*` flags. Bare `bv` launches an interactive TUI that blocks your session.**\n\n### The Workflow: Start With Triage\n\n**`bv --robot-triage` is your single entry point.** It returns:\n- `quick_ref`: at-a-glance counts + top 3 picks\n- `recommendations`: ranked actionable items with scores, reasons, unblock info\n- `quick_wins`: low-effort high-impact items\n- `blockers_to_clear`: items that unblock the most downstream work\n- `project_health`: status/type/priority distributions, graph metrics\n- `commands`: copy-paste shell commands for next steps\n\n```bash\nbv --robot-triage        # THE MEGA-COMMAND: start here\nbv --robot-next          # Minimal: just the single top pick + claim command\n```\n\n### Command Reference\n\n**Planning:**\n| Command | Returns |\n|---------|---------|\n| `--robot-plan` | Parallel execution tracks with `unblocks` lists |\n| `--robot-priority` | Priority misalignment detection with confidence |\n\n**Graph Analysis:**\n| Command | Returns |\n|---------|---------|\n| `--robot-insights` | Full metrics: PageRank, betweenness, HITS, eigenvector, critical path, cycles, k-core, articulation points, slack |\n| `--robot-label-health` | Per-label health: `health_level`, `velocity_score`, `staleness`, `blocked_count` |\n| `--robot-label-flow` | Cross-label dependency: `flow_matrix`, `dependencies`, `bottleneck_labels` |\n| `--robot-label-attention [--attention-limit=N]` | Attention-ranked labels |\n\n**History & Change Tracking:**\n| Command | Returns |\n|---------|---------|\n| `--robot-history` | Bead-to-commit correlations |\n| `--robot-diff --diff-since <ref>` | Changes since ref: new/closed/modified issues, cycles |\n\n**Other:**\n| Command | Returns |\n|---------|---------|\n| `--robot-burndown <sprint>` | Sprint burndown, scope changes, at-risk items |\n| `--robot-forecast <id\\|all>` | ETA predictions with dependency-aware scheduling |\n| `--robot-alerts` | Stale issues, blocking cascades, priority mismatches |\n| `--robot-suggest` | Hygiene: duplicates, missing deps, label suggestions |\n| `--robot-graph [--graph-format=json\\|dot\\|mermaid]` | Dependency graph export |\n| `--export-graph <file.html>` | Interactive HTML visualization |\n\n### Scoping & Filtering\n\n```bash\nbv --robot-plan --label backend              # Scope to label's subgraph\nbv --robot-insights --as-of HEAD~30          # Historical point-in-time\nbv --recipe actionable --robot-plan          # Pre-filter: ready to work\nbv --recipe high-impact --robot-triage       # Pre-filter: top PageRank\nbv --robot-triage --robot-triage-by-track    # Group by parallel work streams\nbv --robot-triage --robot-triage-by-label    # Group by domain\n```\n\n### Understanding Robot Output\n\n**All robot JSON includes:**\n- `data_hash` — Fingerprint of source beads.jsonl\n- `status` — Per-metric state: `computed|approx|timeout|skipped` + elapsed ms\n- `as_of` / `as_of_commit` — Present when using `--as-of`\n\n**Two-phase analysis:**\n- **Phase 1 (instant):** degree, topo sort, density\n- **Phase 2 (async, 500ms timeout):** PageRank, betweenness, HITS, eigenvector, cycles\n\n### jq Quick Reference\n\n```bash\nbv --robot-triage | jq '.quick_ref'                        # At-a-glance summary\nbv --robot-triage | jq '.recommendations[0]'               # Top recommendation\nbv --robot-plan | jq '.plan.summary.highest_impact'        # Best unblock target\nbv --robot-insights | jq '.status'                         # Check metric readiness\nbv --robot-insights | jq '.Cycles'                         # Circular deps (must fix!)\n```\n\n---\n\n## UBS — Ultimate Bug Scanner\n\n**Golden Rule:** `ubs <changed-files>` before every commit. Exit 0 = safe. Exit >0 = fix & re-run.\n\n### Commands\n\n```bash\nubs file.rs file2.rs                    # Specific files (< 1s) — USE THIS\nubs $(git diff --name-only --cached)    # Staged files — before commit\nubs --only=rust,toml src/               # Language filter (3-5x faster)\nubs --ci --fail-on-warning .            # CI mode — before PR\nubs .                                   # Whole project (ignores target/, Cargo.lock)\n```\n\n### Output Format\n\n```\nWarning  Category (N errors)\n    file.rs:42:5 - Issue description\n    Suggested fix\nExit code: 1\n```\n\nParse: `file:line:col` -> location | fix hint -> how to fix | Exit 0/1 -> pass/fail\n\n### Fix Workflow\n\n1. Read finding "},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/Dicklesworthstone/destructive_command_guard/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md — dcg (Destructive Command Guard)\n\n> Guidelines for AI coding agents working in this Rust codebase.\n\n---\n\n## RULE 0 - THE FUNDAMENTAL OVERRIDE PREROGATIVE\n\nIf I tell you to do something, even if it goes against what follows below, YOU MUST LISTEN TO ME. I AM IN CHARGE, NOT YOU.\n\n---\n\n## RULE 0.5 - SUITE-WIDE RULES LIVE IN /data/projects/AGENTS.md\n\nThe suite-wide rules in **`/data/projects/AGENTS.md`** bind you here too. Read it. Two sections\nare load-bearing for perf work and are NOT duplicated below, so they cannot drift out of sync:\n\n- **`## Named Reward-Hacking Patterns (ALL FORBIDDEN)`** — 12 named patterns, several already\n  observed in this suite: gate self-weakening (and the exact price of a legitimate gate fix),\n  proof-class inflation, golden regeneration reflex, commit-stream pumping, tautological tests,\n  easy-lever cherry-picking, close-pump abuse, scope-splitting, spec-editing as progress,\n  conformance metastasis, dependency smuggling, bench-path hardcoding.\n- **`### Work-Graph Discipline`** — JSONL is truth and `beads.db` is disposable, `br sync\n  --import-only` after every pull, single-writer on graph structure, closure on cited evidence\n  with blocker beads gated on their named probe, `br dep cycles` stays empty.\n\nThe three that most often decide whether a number here is real: a **self-speedup is\nMAINTENANCE, not a win** — a win needs the incumbent live in the SAME invocation; **never\nweaken a gate to land a change**, and if a gate is genuinely defective, meet the evidence\nstandard and publish the win/lose split of what the fix admits; and **reporting a loss is a\nsuccess** — one line, revert, next lever, no retraction narrative.\n\n---\n\n## RULE NUMBER 1: NO FILE DELETION\n\n**YOU ARE NEVER ALLOWED TO DELETE A FILE WITHOUT EXPRESS PERMISSION.** Even a new file that you yourself created, such as a test code file. You have a horrible track record of deleting critically important files or otherwise throwing away tons of expensive work. As a result, you have permanently lost any and all rights to determine that a file or folder should be deleted.\n\n**YOU MUST ALWAYS ASK AND RECEIVE CLEAR, WRITTEN PERMISSION BEFORE EVER DELETING A FILE OR FOLDER OF ANY KIND.**\n\n---\n\n## Irreversible Git & Filesystem Actions — DO NOT EVER BREAK GLASS\n\n> **Note:** This project exists specifically to block these dangerous commands for AI agents. Practice what we preach.\n\n1. **Absolutely forbidden commands:** `git reset --hard`, `git clean -fd`, `rm -rf`, or any command that can delete or overwrite code/data must never be run unless the user explicitly provides the exact command and states, in the same message, that they understand and want the irreversible consequences.\n2. **No guessing:** If there is any uncertainty about what a command might delete or overwrite, stop immediately and ask the user for specific approval. \"I think it's safe\" is never acceptable.\n3. **Safer alternatives first:** When cleanup or rollbacks are needed, request permission to use non-destructive options (`git status`, `git diff`, `git stash`, copying to backups) before ever considering a destructive command.\n4. **Mandatory explicit plan:** Even after explicit user authorization, restate the command verbatim, list exactly what will be affected, and wait for a confirmation that your understanding is correct. Only then may you execute it—if anything remains ambiguous, refuse and escalate.\n5. **Document the confirmation:** When running any approved destructive command, record (in the session notes / final response) the exact user text that authorized it, the command actually run, and the execution time. If that record is absent, the operation did not happen.\n\n---\n\n## Git Branch: ONLY Use `main`, NEVER `master`\n\n**The default branch is `main`. The `master` branch exists only for legacy URL compatibility.**\n\n- **All work happens on `main`** — commits, PRs, feature branches all merge to `main`\n- **Never reference `master` in code or docs** — if you see `master` anywhere, it's a bug that needs fixing\n- **The `master` branch must stay synchronized with `main`** — after pushing to `main`, also push to `master`:\n  ```bash\n  git push origin main:master\n  ```\n\n**Why this matters:** The `dcg update` command and install URLs historically referenced `master`. If `master` falls behind `main`, users get stale code. We had a bug where `master` was **497 commits behind**, causing users to see old installer behavior.\n\n**If you see `master` referenced anywhere:**\n1. Update it to `main`\n2. Ensure `master` is synchronized: `git push origin main:master`\n\n---\n\n## Toolchain: Rust & Cargo\n\nWe only use **Cargo** in this project, NEVER any other package manager.\n\n- **Edition:** Rust 2024 (nightly required — see `rust-toolchain.toml`)\n- **Dependency versions:** Explicit versions for stability\n- **Configuration:** Cargo.toml only (single crate, not a workspace)\n- **Unsafe code:** Forbidden (`#![forbid(unsafe_code)]`)\n\n### Key Dependencies\n\n| Crate | Purpose |\n|-------|---------|\n| `serde` + `serde_json` | JSON parsing for Claude Code hook protocol |\n| `serde_yaml` | External pack YAML parsing |\n| `toml` + `toml_edit` | TOML config parsing with formatting preservation |\n| `fancy-regex` | Advanced regex with lookahead/lookbehind |\n| `regex` | `RegexSet` for heredoc detection |\n| `memchr` | SIMD-accelerated substring search |\n| `aho-corasick` | Multi-pattern string matching for keyword quick-reject |\n| `colored` | Terminal colors with TTY detection |\n| `clap` + `clap_complete` | CLI argument parsing with shell completions |\n| `chrono` | RFC 3339 timestamps |\n| `ast-grep-core` + `ast-grep-language` | AST-based pattern matching for heredoc/inline-script content |\n| `rusqlite` | Bundled upstream SQLite for best-effort telemetry history |\n| `rust-mcp-sdk` | MCP server integration (stdio transport) |\n| `tokio` | Async runtime for MCP server mode |\n| `ratatui` + `comfy-table` + `indicatif` + `console` | TUI/CLI visual polish |\n| `self_update` | Binary self-update from GitHub Releases |\n| `vergen-gix` | Build metadata embedding (build.rs) |\n| `tracing` + `tracing-subscriber` | Structured logging and diagnostics |\n| `sha2` + `hmac` | Hashing and HMAC for allow-once short codes |\n| `flate2` | Gzip compression for history export |\n\n### Release Profile\n\nThe release build optimizes for binary size:\n\n```toml\n[profile.release]\nopt-level = \"z\"     # Optimize for size (lean binary for distribution)\nlto = true          # Link-time optimization\ncodegen-units = 1   # Single codegen unit for better optimization\npanic = \"abort\"     # Smaller binary, no unwinding overhead\nstrip = true        # Remove debug symbols\n```\n\n### Feature Flags\n\n```toml\n[features]\nrayon = [\"dep:rayon\"]           # Rayon data parallelism (optional)\nrich-output = [\"dep:rich_rust\"] # Enable rich_rust for premium terminal output\nlegacy-output = []              # Keep old rendering (placeholder for gradual migration)\n```\n\n---\n\n## Code Editing Discipline\n\n### No Script-Based Changes\n\n**NEVER** run a script that processes/changes code files in this repo. Brittle regex-based transformations create far more problems than they solve.\n\n- **Always make code changes manually**, even when there are many instances\n- For many simple changes: use parallel subagents\n- For subtle/complex changes: do them methodically yourself\n\n### No File Proliferation\n\nIf you want to change something or add a feature, **revise existing code files in place**.\n\n**NEVER** create variations like:\n- `mainV2.rs`\n- `main_improved.rs`\n- `main_enhanced.rs`\n\nNew files are reserved for **genuinely new functionality** that makes zero sense to include in any existing file. The bar for creating new files is **incredibly high**.\n\n---\n\n## Backwards Compatibility\n\nWe do not care about backwards compatibility—we're in early development with no users. We want to do things the **RIGHT** way with **NO TECH DEBT**.\n\n- Never create \"compatibility shims\"\n- Never create wrapper functions for deprecated APIs\n- Just fix the code directly\n\n---\n\n## Compiler Checks (CRITICAL)\n\n**After any substantive code changes, you MUST verify no errors were introduced:**\n\n```bash\n# Check for compiler errors and warnings\ncargo check --all-targets\n\n# Check for clippy lints (pedantic + nursery are enabled)\ncargo clippy --all-targets -- -D warnings\n\n# Verify formatting\ncargo fmt --check\n```\n\nIf you see errors, **carefully understand and resolve each issue**. Read sufficient context to fix them the RIGHT way.\n\n---\n\n## Windows Support (native, `x86_64-pc-windows-msvc`)\n\ndcg ships a **native Windows** binary (built/tested on `windows-latest` with the\nnightly toolchain) and a `check (windows)` CI job. When touching anything\nplatform-sensitive, follow these conventions:\n\n- **Separate command-pattern DATA from dcg's own paths.** Destructive-command\n  patterns (`rm -rf /`, `normalize.rs` stripping `/usr/bin/git`, `/etc`, `/tmp`)\n  are DATA about Unix commands and must STAY — Windows users still run git-bash.\n  Only dcg's *own* config/state paths get Windows-ified (resolve via the `dirs`\n  crate; the system layer is `%ProgramData%\\dcg`, helper `config::system_config_dir()`).\n- **`.exe` suffix.** When constructing a path to the dcg binary, use\n  `env!(\"CARGO_BIN_EXE_dcg\")` / `assert_cmd::cargo::cargo_bin(\"dcg\")` in tests, or\n  `std::env::consts::EXE_SUFFIX` in `src`. **Never** a bare `push(\"dcg\")` — the\n  Windows CI job greps for it and fails. Resolve per-user paths only through\n  `config::home_dir()` / `user_config_dir()` / `user_data_dir()` /\n  `user_data_local_dir()` / `user_cache_dir()` — never `dirs::*` (clippy's\n  `disallowed-methods` rejects it) and never raw `HOME` (unset on Windows).\n  `dirs` resolves the Windows profile via the known-folder API and ignores\n  `USERPROFILE`, so a sandboxed Windows test run used to rewrite the operator's\n  real `~\\.claude\\settings.json` and allow-once store (bd-b2b1). Set\n  `USERPROFILE`/`TEMP`/`TMP` alongside `HOME` in test isolation; `USERPROFILE`\n  alone now relocates `%APPDATA%`/`%LOCALAPPDATA%` too when those are unset.\n- **Verify Windows branches from Linux** without a Windows box: `mingw` + the\n  `x86_64-pc-windows-gnu` target are installed, so\n  `cargo check --target x86_64-pc-windows-gnu --lib` (or `--bin dcg` / `--tests`)\n  compile-checks every `#[cfg(windows)]` path. When `pwsh` is installed, use it\n  to run the PowerShell installer/test scripts; otherwise run those gates on the\n  native Windows release host.\n- **Windows packs.** `src/packs/windows/` holds the native-Windows packs\n  (`windows.filesystem`/`windows.system` default-ON on Windows, `windows.misc`/\n  `windows.powershell` opt-in). Patterns use inline `(?i)`; keyword arrays\n  may retain conventional casing variants for readability, but keyword\n  quick-rejection is ASCII case-insensitive so mixed-case Windows spellings\n  cannot skip the regex stage (see `src/packs/windows/mod.rs`). See\n  [`docs/windows.md`](docs/windows.md).\n- **The `careful_company_running_windows` preset.**\n  `src/packs/careful_company_running_windows/` holds six opt-in sub-packs\n  covering **outbound communication and data egress** (email, chat/webhooks,\n  HTTP upload, file transfer, tunnels) plus **tampering with the controls that\n  supervise the agent** (Defender/firewall/EDR, audit logs, and dcg's own\n  bypass/uninstall). It is the only pack ID with *curated transitive\n  membership*: enabling it also enables the pinned\n  `CAREFUL_COMPANY_PRESET_MEMBERS` list in `src/packs/mod.rs`\n  (`windows.*`, `database.*`, `storage.*`, `remote.*`, `backup.*`, `secrets.*`,\n  `cloud.*`). That list is deliberately explicit — do **not** convert it to\n  prefix matching, or future packs will join a security posture silently.\n  Two invariants to preserve when touching this area:\n  - **Tier order.** `windows.*` is tier 11 and the preset is tier 12, so\n    Windows packs keep claiming attribution for commands both match. Rule ids\n    (`pack_id:pattern_name`) are what allowlists key on, so reordering these\n    silently invalidates existing `windows.*` allowlist entries.\n  - **The `hfdt` trust boundary.** While any preset sub-pack is enabled,\n    `src/evaluator.rs` allows a command whose executable is `hfdt` **before any\n    pack runs** — including `core.*`. It is structural (whole-segment\n    executable match, no chains/redirection/substitution), but it does mean\n    enabling the preset *reduces* coverage for that one executable. Keep it\n    documented in `README.md` and `docs/careful-company-windows.md`.\n\n---\n\n## Testing\n\n### Testing Policy\n\nEvery module includes inline `#[cfg(test)]` unit tests alongside the implementation. Tests must cover:\n- Happy path\n- Edge cases (empty input, max values, boundary conditions)\n- Error conditions\n\nEnd-to-end tests live in `scripts/e2e_test.sh`.\n\n### Unit Tests\n\nThe test suite includes 80+ tests covering all functionality:\n\n```bash\n# Run all tests\ncargo test\n\n# Run with output\ncargo test -- --nocapture\n\n# Run specific test module\ncargo test normalize_command_tests\ncargo test safe_pattern_tests\ncargo test destructive_pattern_tests\n```\n\n**Prefer `cargo nextest run` when touching anything that mutates the\nenvironment.** CI runs `cargo nextest run --profile ci`, and nextest gives each\ntest its own process — verified by observation, not assumption: sampling a\n`--lib` run showed up to 128 concurrent per-test `--exact` invocations. That\nprocess isolation is what makes the `unsafe { env::set_var }` sites in test code\nsound, because it removes the concurrent reader.\n\nUnder plain `cargo test` every test shares one process and runs on threads, and\nthe crate has ~109 `env::var` call sites against ~22 `EnvVarGuard` uses, none of\nthe readers taking the lock — plus native code (bundled SQLite reading `TMPDIR`)\nthat cannot take a Rust lock even in principle. So `cargo test` is the\nconfiguration the single crate-wide `ENV_LOCK` cannot actually make safe (#445).\nNo failure has been observed from this; the race is available, not active.\n\n`cargo test` stays documented above because filter and `--ignored` syntax differ\nbetween the two runners (nextest wants `--run-ignored`), and several workflows in\nthis file depend on the `cargo test` spelling.\n\n### The Three Release-Blocking E2E Suites (read before touching perf or protocols)\n\n`cargo test` cannot catch the failure modes that have actually broken users.\nThree real-binary, no-mock suites exist specifically to close those gaps. All\nthree must be green before any release.\n\n| Suite | Catches | Why unit tests can't |\n|-------|---------|----------------------|\n| `scripts/e2e_harness_matrix.sh` | Wire/bridge breakage for **every** agent (Claude Code, Codex, Gemini, Copilot, Hermes, Grok, agy, Crush, OMP) | Unit tests call Rust functions; harnesses parse **bytes**. Asserts decision field + exit code + stdout/stderr separation per protocol against the real binary. |\n| `scripts/perf_baseline.py --assert-budget-ms` | **#245**: evaluator cost silently eating the fixed hook deadline | The perf job is a *relative* ratchet — a uniform slowdown just gets re-baselined. This gate asserts paired `full_eval − DCG_BYPASS` p95 against the **shipped** `HOOK_EVALUATION_BUDGET_MS` with a hermetic HOME and scrubbed `DCG_*`; raw process latency remains separate evidence. |\n| `scripts/e2e_fleet_install.sh` | Published artifact missing/unrunnable per platform; installer picking the wrong triple; checksum/signature verification silently skipped; hook config non-idempotent | Nothing in-tree proves the **public download path** works on real Linux/macOS/Windows hardware. |\n\n```bash\n# Protocol/bridge conformance for all 8 harnesses (needs a release binary + jq)\n./scripts/e2e_harness_matrix.sh --binary target/release/dcg\n\n# Absolute evaluator-cost gate — the #245 guard. Budget MUST come from src/perf.rs.\nBUDGET_MS=$(sed -nE 's/^pub const HOOK_EVALUATION_BUDGET_MS: u64 = ([0-9_]+);$/\\1/p' src/perf.rs | tr -d '_')\npython3 scripts/perf_baseline.py --bin target/release/dcg --skip-trace \\\n  --assert-budget-ms \"$BUDGET_MS\" --assert-margin-pct 50\n\n# Real installs from the PUBLIC release on every DSR host\n./scripts/e2e_fleet_install.sh --version vX.Y.Z          # whole fleet\n./scripts/e2e_fleet_install.sh --version vX.Y.Z --local-only\n```\n\nRules:\n- **Scrub ambient `DCG_*` before measuring anything.** Operators bitten by #245\n  export `DCG_HOOK_TIMEOUT_MS=5000` (an agent `settings.json` `env` block puts\n  it in every child process), so an un-scrubbed suite measures the *workaround*\n  and passes on exactly the machines that need protecting. `env -i` covers the\n  hook calls; the installer cannot use it (it needs the host PATH for\n  `curl`/`tar`/`xz`/`minisign`), so the probes also `unset` every `DCG_*` up\n  front. Assert `general.hook_timeout_source` too — a bare `>= 1000` check\n  cannot tell the shipped default from an inherited 5000.\n- **Set `DCG_SELF_HEAL_HOOK=0` before the installer runs, not after.** dcg\n  repairs a missing/stale hook entry whenever it runs in hook mode, and\n  released binaries up to v0.14.4 resolve the Windows settings path via the\n  Win32 known-folder API, which `USERPROFILE` cannot redirect — so a late\n  disable can rewrite a real machine's agent config. Current source honors\n  `USERPROFILE` (bd-b2b1), but the installer may still fetch an older release.\n- **Never hard-code the budget in `.github/workflows/ci.yml`.** It is grepped\n  out of `HOOK_EVALUATION_BUDGET_MS`; `perf::tests::ci_enforces_absolute_latency_gate_against_shipped_budget`\n  fails if that wiring is removed or the margin is loosened past 60%.\n- **Treat the JSON as the certificate, not stderr.** Gate mode records its\n  supplied/shipped/effective budgets, margin, derived limit, every per-case\n  verdict, 95/95 binomial tail-tolerance result, violations, and overall\n  PASS/FAIL in `latency_gate`; CI retains that artifact even when the gate\n  fails. Gate mode requires at least 59 samples; CI uses 100 and permits at\n  most one over-limit sample per case. When using `--output` in gate mode,\n  place it outside the repository; the harness rejects in-tree output so its\n  own certificate cannot dirty the source snapshot it claims to measure.\n- **Bind the binary to the checkout.** Gate mode requires a clean checkout and\n  exact equality between the binary's embedded `git describe --tags --dirty`\n  value and the repository's value. CI uses a full tag history so a shallow\n  clone cannot turn this proof into an unknown result.\n- Measure dcg's own cost as `full_eval − DCG_BYPASS`, never raw wall-clock:\n  process spawn (≈940ms under Windows PowerShell) sits **outside** the\n  evaluation deadline and would otherwise produce false alarms. For host\n  safety this certificate sets `DCG_SELF_HEAL_HOOK=0`, records that exclusion,\n  and therefore does not claim to measure self-healing work. Capture and\n  validate every timed child's actual wire decision after stopping its timer;\n  before/after semantic controls alone cannot catch intermittent fail-open\n  behavior inside the sample window.\n- The fleet suite installs into a scratch prefix with an isolated `HOME` and\n  `--no-configure`; it never touches a host's real agent hook config.\n- A probe that dies partway must FAIL, not pass: every probe emits\n  `probe_complete` and the runner asserts the full expected case set.\n\n### End-to-End Testing\n\n```bash\n# Run the E2E test script (needs bash >= 4; macOS /bin/bash 3.2 breaks the summary)\n./scripts/e2e_test.sh\n\n# Or test manually\necho '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git reset --hard\"}}' | cargo run --release\n# Should output JSON denial\n\necho '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git status\"}}' | cargo run --release\n# Should output nothing (allowed)\n```\n\n### Test Categories\n\n| Module | Tests | Purpose |\n|--------|-------|---------|\n| `normalize_command_tests` | 8 | Path stripping for git/rm binaries |\n| `quick_reject_tests` | 5 | Fast-path filtering for non-git/rm commands |\n| `safe_pattern_tests` | 16 | Whitelist accuracy |\n| `destructive_pattern_tests` | 20 | Blacklist coverage |\n| `input_parsing_tests` | 8 | JSON parsing robustness |\n| `deny_output_tests` | 2 | Output format validation |\n| `integration_tests` | 4 | End-to-end pipeline |\n| `optimization_tests` | 9 | Performance paths |\n| `edge_case_tests` | 24 | Real-world edge cases |\n\n---\n\n## Third-Party Library Usage\n\nIf you aren't 100% sure how to use a third-party library, **SEARCH ONLINE** to find the latest documentation and current best practices.\n\n---\n\n## dcg (Destructive Command Guard) — This Project\n\n**This is the project you're working on.** dcg is a high-performance Claude Code hook that blocks destructive commands before they execute. It protects against dangerous git commands, filesystem operations, database queries, container commands, and more through a modular pack system.\n\n### What It Does\n\nGuards AI coding agents from executing destructive commands by intercepting Claude Code's `PreToolUse` hook protocol, evaluating commands against safe/destructive pattern lists, and denying dangerous operations with structured JSON output including remediation suggestions.\n\n### Architecture\n\n```\nJSON Input → Parse → Quick Reject (memchr) → Normalize → Safe Patterns → Destructive Patterns → Default Allow\n```\n\n### Key Files\n\n| File | Purpose |\n|------|---------|\n| `src/main.rs` | Entry point, hook I/O, CLI dispatch |\n| `src/evaluator.rs` | Pattern matching engine (safe + destructive evaluation) |\n| `src/hook.rs` | Claude Code PreToolUse hook protocol handling |\n| `src/normalize.rs` | Command normalization (path stripping, alias expansion) |\n| `src/heredoc.rs` | Heredoc and inline script extraction |\n| `src/ast_matcher.rs` | AST-based pattern matching for embedded code |\n| `src/config.rs` | Configuration loading (TOML, allowlists, pack enable/disable) |\n| `src/allowlist.rs` | Allowlist management (project, user, system scopes) |\n| `src/cli.rs` | CLI commands (explain, scan, packs, allowlist, etc.) |\n| `src/scan.rs` | Codebase scanning for destructive patterns |\n| `src/context.rs` | Contextual analysis for pattern matching |\n| `src/confidence.rs` | Match confidence scoring |\n| `src/error_codes.rs` | Standardized DCG-XXXX error codes |\n| `src/exit_codes.rs` | Process exit code definitions |\n| `src/packs/` | Modular pattern pack system (core + extensions) |\n| `src/output/` | Output formatting (JSON, colorful stderr) |\n| `src/highlight.rs` | Syntax highlighting for command display |\n| `src/logging.rs` | Tracing/logging configuration |\n| `src/perf.rs` | Performance budgets and benchmarks |\n| `src/simulate.rs` | Command simulation and dry-run support |\n| `src/mcp.rs` | MCP server integration |\n| `src/agent.rs` | Agent detection and identification |\n| `src/interactive.rs` | Interactive mode |\n| `src/git.rs` | Git-specific command analysis |\n| `src/history/` | Decision history and telemetry |\n| `src/sarif.rs` | SARIF output format for scan results |\n| `src/pending_exceptions.rs` | Pending exception management |\n| `src/lib.rs` | Library re-exports |\n| `Cargo.toml` | Dependencies and release optimizations |\n| `build.rs` | Build script for version metadata (vergen) |\n| `rust-toolchain.toml` | Nightly toolchain requirement |\n| `scripts/e2e_test.sh` | End-to-end test script (hundreds of command scenarios) |\n\n### Output Style\n\nThis tool has two output modes:\n\n- **JSON to stdout:** For Claude Code hook protocol (`hookSpecificOutput` with `permissionDecision: \"deny\"`)\n- **Colorful warning to stderr:** For human visibility when commands are blocked\n\nOutput behavior:\n- **Deny:** Colorful warning to stderr + JSON to stdout\n- **Allow:** No output (silent exit)\n- **--version/-V:** Version info with build metadata to stderr\n- **--help/-h:** Usage information to stderr\n\nColors are automatically disabled when stderr is not a TTY (e.g., piped to file).\n\n### Pattern System\n\n- **34 safe patterns** (whitelist, checked first)\n- **16 destructive patterns** (blacklist, checked second)\n- **Default allow** for unmatched commands\n\n### Adding New Patterns\n\n1. Identify the command to block/allow\n2. Write a regex using `fancy-regex` syntax (supports lookahead/lookbehind)\n3. Add to `SAFE_PATTERNS` or `DESTRUCTIVE_PATTERNS` using the macros:\n\n```rust\n// Safe pattern (whitelist)\npattern!(\"pattern-name\", r\"regex-here\")\n\n// Destructive pattern (blacklist)\ndestructive!(\n    r\"regex-here\",\n    \"Human-readable reason for blocking\"\n)\n```\n\n4. Add tests for all variants\n5. Run `cargo test` and `./scripts/e2e_test.sh`\n\n### Performance Requirements\n\nEvery Bash command passes through this hook. Performance is critical:\n\n- Quick rejection filter eliminates 99%+ of commands before regex\n- Lazy-initialized static regex patterns (compiled once, reused)\n- Sub-millisecond execution for typical commands\n- Zero allocations on the hot path for safe commands\n\n### Heredoc Detection Notes\n\n- **Rule IDs**: Heredoc patterns use stable IDs like `heredoc.python.shutil_rmtree` for allowlisting.\n- **Bounded failure**: Heredoc parse/AST failures use the configured bounded\n  fallback; disabling fallback blocks. Absolute hook-deadline exhaustion and\n  incomplete nested evaluation return `Indeterminate`, never `Allow`.\n- **Tests**: Prefer targeted tests in `src/ast_matcher.rs` and `src/heredoc.rs`.\n  - `cargo test ast_matcher`\n  - `cargo test heredoc`\n  - Add positive and negative fixtures for each new pattern.\n\n---\n\n<!-- dcg-machine-readable-v1 -->\n\n## DCG Hook Protocol (Machine-Readable Reference)\n\n> This section provides structured documentation for AI agents integrating with dcg.\n\n### JSON Input Format\n\ndcg reads from stdin in Claude Code's `PreToolUse` hook format:\n\n```json\n{\n  \"tool_name\": \"Bash\",\n  \"tool_input\": {\n    \"command\": \"git reset --hard HEAD~5\"\n  }\n}\n```\n\n**Required fields:**\n- `tool_name`: Must be `\"Bash\"` for dcg to process (other tools are ignored)\n- `tool_input.command`: The shell command string to evaluate\n\n### JSON Output Format (Denial)\n\nWhen a command is blocked, dcg outputs JSON to stdout:\n\n```json\n{\n  \"hookSpecificOutput\": {\n    \"hookEventName\": \"PreToolUse\",\n    \"permissionDecision\": \"deny\",\n    \"permissionDecisionReason\": \"BLOCKED by dcg\\n\\nTip: dcg explain \\\"git reset --hard HEAD~5\\\"\\n\\nReason: git reset --hard destroys uncommitted changes\\n\\nExplanation: Rewrites history and discards uncommitted changes.\\n\\nRule: core.git:reset-hard\\n\\nIf this operation is truly needed, ask the user for explicit permission and have them run the command manually.\",\n    \"ruleId\": \"core.git:reset-hard\",\n    \"packId\": \"core.git\",\n    \"severity\": \"critical\",\n    \"confidence\": 1.0,\n    \"allowOnceCode\": \"a1b2c3\",\n    \"allowOnceFullHash\": \"sha256:abc123...\",\n    \"remediation\": {\n      \"safeAlternative\": \"git stash\",\n      \"explanation\": \"Use git stash to save your changes first.\",\n      \"allowOnceCommand\": \"dcg allow-once a1b2c3\"\n    }\n  }\n}\n```\n\n**Key fields for agent parsing:**\n| Field | Type | Description |\n|-------|------|-------------|\n| `permissionDecision` | `\"allow\"` \\| `\"deny\"` | The decision |\n| `ruleId` | `string` | Stable pattern ID (e.g., `\"core.git:reset-hard\"`) for allowlisting |\n| `packId` | `string` | Pack that matched (e.g., `\"core.git\"`) |\n| `severity` | `string` | `\"critical\"`, `\"high\"`, `\"medium\"`, or `\"low\"` |\n| `confidence` | `number?` | Match confidence 0.0–1.0 (two decimals) from `crate::confidence::compute_match_confidence`, the score the `[confidence]` warn threshold reads: 1.0 when the matched span is executed (command word, wrapper-invoked command, redirection target), lower when it sits in quoted data, a comment, or an inert command's arguments. Omitted when the match has no source span (e.g. inside `$(…)`, structural verdicts). |\n| `allowOnceCode` | `string` | Short code for `dcg allow-once` |\n| `remediation.safeAlternative` | `string?` | Suggested safe command |\n\n### JSON Output Format (Allow)\n\nWhen a command is allowed: **no output** (silent exit 0).\n\n---\n\n## Exit Codes Reference\n\n| Code | Meaning | Agent Action |\n|------|---------|--------------|\n| `0` | Command allowed OR protocol JSON denial was emitted | Parse stdout; if empty, command was allowed |\n| `1` | Parse error or invalid input | Retry with corrected input |\n| `2` | Configuration error | Check config syntax and stderr diagnostics |\n\n**Detection logic for agents:**\n```bash\noutput=$(echo \"$hook_input\" | dcg 2>/dev/null)\nif [ -z \"$output\" ]; then\n  echo \"ALLOWED\"\nelse\n  echo \"DENIED: $output\"\nfi\n```\n\nCodex CLI uses a stricter hook parser: blocked commands return a minimal\n`hookSpecificOutput` denial on stdout with exit code 0. See\n[`docs/codex-integration.md`](docs/codex-integration.md) for the Codex-specific\nprotocol notes.\n\n---\n\n## Error Codes Reference\n\nDCG uses standardized error codes in the format `DCG-XXXX` for machine-parseable error handling.\n\n### Error Categories\n\n| Range | Category | Description |\n|-------|----------|-------------|\n| DCG-1xxx | `pattern_match` | Pattern matching and evaluation errors |\n| DCG-2xxx | `configuration` | Configuration loading and parsing errors |\n| DCG-3xxx | `runtime` | Runtime and execution errors |\n| DCG-4xxx | `external` | External integration errors |\n\n### Common Error Codes\n\n| Code | Description | Typical Cause |\n|------|-------------|---------------|\n| `DCG-1001` | Pattern compilation failed | Invalid regex syntax in pattern |\n| `DCG-1002` | Pattern match timeout | Complex pattern taking too long |\n| `DCG-2001` | Config file not found | Missing configuration file |\n| `DCG-2002` | Config parse error | Invalid TOML/JSON syntax |\n| `DCG-2004` | Allowlist load error | Invalid allowlist file |\n| `DCG-3001` | JSON parse error | Malformed JSON input |\n| `DCG-3002` | IO error | File read/write failure |\n| `DCG-4001` | External pack load failed | Invalid external pack YAML |\n\n### Error JSON Structure\n\nWhen errors are returned in JSON format, they follow this structure:\n\n```json\n{\n  \"error\": {\n    \"code\": \"DCG-3001\",\n    \"category\": \"runtime\",\n    \"message\": \"JSON parse error: unexpected token at position 15\",\n    \"context\": {\n      \"position\": 15,\n      \"input_preview\": \"{ \\\"tool_name\\\": ...\"\n    }\n  }\n}\n```\n\n**Fields:**\n- `code`: Stable error code for programmatic handling\n- `category`: Error category (`pattern_match`, `configuration`, `runtime`, `external`)\n- `message`: Human-readable error description\n- `context`: Additional details (optional, varies by error type)\n\n---\n\n## Allowlist & Bypass Instructions\n\n### Temporary Bypass (24-hour allow-once)\n\nWhen a command is blocked, the output includes an `allowOnceCode`. Use it:\n\n```bash\ndcg allow-once <code>\n```\n\nThis allows the specific command for 24 hours in the current directory scope.\n\n### Permanent Allowlist (by rule ID)\n\nAdd a rule to the project allowlist:\n\n```bash\ndcg allowlist add <ruleId> --project\n# Example: dcg allowlist add core.git:reset-hard --project\n```\n\nAllowlist files (in priority order):\n1. `.dcg/allowlist.toml` (project)\n2. `~/.config/dcg/allowlist.toml` (user)\n3. `/etc/dcg/allowlist.toml` (system)\n\n### Bypass Environment Variable\n\nFor emergency bypass (use sparingly):\n\n```bash\nDCG_BYPASS=1 <command>\n```\n\n**Warning:** This disables all protection. Log and justify any usage.\n\n---\n\n## Pattern Quick Reference\n\n### Core Git Patterns (Always Enabled)\n\n| Pattern ID | Blocks | Severity |\n|------------|--------|----------|\n| `core.git:reset-hard` | `git reset --hard` | Critical |\n| `core.git:reset-merge` | `git reset --merge` | High |\n| `core.git:checkout-discard` | `git checkout -- <file>` | High |\n| `core.git:restore-discard` | `git restore <file>` (without `--staged`) | High |\n| `core.git:clean-force` | `git clean -f`, `git clean -fd` | High |\n| `core.git:force-push` | `git push --force`, `git push -f` | High |\n| `core.git:branch-delete` | `git branch -d`, `--delete` (merge-checked, no force) | High |\n| `core.git:branch-force-delete` | `git branch -D`, `--delete --force`, `-f`, `-M`, `-C` (a policy/allowlist entry for it also covers `branch-delete` unless that rule has its own) | High |\n| `core.git:stash-drop` | `git stash drop` — Medium, so the default policy **warns and lets it run** (the dropped stash stays recoverable via `git fsck` until gc) | Medium |\n| `core.git:stash-clear` | `git stash clear` | Critical |\n\n### Core Filesystem Patterns (Always Enabled)\n\n| Pattern ID | Blocks | Severity |\n|------------|--------|----------|\n| `core.filesystem:rm-rf-root` | `rm -rf /`, `rm -rf ~` | Critical |\n| `core.filesystem:rm-rf-general` | `rm -rf` outside temp dirs | High |\n\n### Safe Patterns (Whitelist - Always Allowed)\n\n| Pattern | Command | Why Safe |\n|---------|---------|----------|\n| `git-checkout-branch` | `git checkout -b <branch>` | Creates new branch |\n| `git-checkout-orphan` | `git checkout --orphan <branch>` | Creates orphan branch |\n| `git-restore-staged` | `git restore --staged <file>` | Only unstages, doesn't discard |\n| `git-clean-dry-run` | `git clean -n`, `git clean --dry-run` | Preview only |\n| `rm-tmp` | `rm -rf /tmp/*`, `/var/tmp/*` | Temp directory cleanup |\n\n### Pack Enable/Disable Examples\n\n```toml\n# ~/.config/dcg/config.toml\n[packs]\nenabled = [\n    \"database.postgresql\",    # Blocks DROP TABLE, TRUNCATE\n    \"kubernetes.kubectl\",     # Blocks kubectl delete namespace\n    \"cloud.aws\",              # Blocks aws ec2 terminate-instances\n]\n\ndisabled = [\n    \"containers.docker\",      # Disable Docker protection\n]\n```\n\nList all packs: `dcg packs --verbose`\n\n---\n\n## CLI Quick Reference for Agents\n\n| Command | Purpose |\n|---------|---------|\n| `dcg explain \"<command>\"` | Detailed trace of why command is blocked/allowed |\n| `dcg allow-once <code>` | Allow a blocked command for 24 hours |\n| `dcg allowlist add <ruleId> --project` | Permanently allow a rule |\n| `dcg packs` | List enabled packs |\n| `dcg packs --verbose` | List all packs with pattern counts |\n| `dcg scan .` | Scan codebase for destructive patterns |\n| `dcg --version` | Show version and build info |\n\n---\n\n## Agent Integration Checklist\n\nWhen integrating with dcg, ensure your agent:\n\n- [ ] Parses stdout for JSON denial responses\n- [ ] Handles empty stdout as \"command allowed\"\n- [ ] Uses `ruleId` for stable allowlisting (not pattern text)\n- [ ] Displays `remediation.safeAlternative` to users when available\n- [ ] Respects `severity` for prioritization (critical > high > medium > low)\n- [ ] Uses `dcg explain` before asking users to bypass\n\n---\n\n## JSON Schema Reference\n\nFormal JSON Schema definitions (Draft 2020-12) for all dcg output formats are available in `docs/json-schema/`:\n\n| Schema | Purpose |\n|--------|---------|\n| [`hook-output.json`](docs/json-schema/hook-output.json) | PreToolUse hook denial response format |\n| [`scan-results.json`](docs/json-schema/scan-results.json) | `dcg scan` command output format |\n| [`stats-output.json`](docs/json-schema/stats-output.json) | `dcg stats` command output format |\n| [`error.json`](docs/json-schema/error.json) | Error response formats for various commands |\n\nUse these schemas for:\n- Validating dcg output in automated pipelines\n- Generating type-safe client code\n- Understanding the complete output contract\n\n<!-- end-dcg-machine-readable -->\n\n---\n\n## CI/CD Pipeline\n\n### Jobs Overview\n\n| Job | Trigger | Purpose | Blocking |\n|-----|---------|---------|----------|\n| `check` | PR, push | Format, clippy, UBS, tests | Yes |\n| `coverage` | PR, push | Coverage thresholds | Yes |\n| `memory-tests` | PR, push | Memory leak detection | Yes |\n| `benchmarks` | push to main | Performance budgets | Warn only |\n| `e2e` | PR, push | End-to-end shell tests | Yes |\n| `scan-regression` | PR, push | Scan output stability | Yes |\n| `perf-regression` | PR, push | Process-per-invocation perf | Yes |\n\n### Check Job\n\nRuns format, clippy, UBS static analysis, and unit tests. Includes:\n- `cargo fmt --check` - Code formatting\n- `cargo clippy --all-targets -- -D warnings` - Lints (pedantic + nursery enabled)\n- UBS analysis on changed Rust files (warning-only, non-blocking)\n- `cargo nextest run` - Full test suite with JUnit XML report\n\n### Coverage Job\n\nRuns `cargo llvm-cov` and enforces the thresholds configured in\n`.github/workflows/ci.yml` (`OVERALL_MIN`, `EVALUATOR_MIN`, `HOOK_MIN`).\nThese are enforced gates, not aspirational targets:\n- **Overall:** >= 70%\n- **src/evaluator.rs:** >= 65%\n- **src/hook.rs:** >= 70%\n\nIf CI thresholds change, update this section in the same change. The\n`coverage_threshold_docs` test checks that these documented values stay in sync\nwith the workflow.\n\nCoverage is uploaded to Codecov for trend tracking. Dashboard: https://codecov.io/gh/Dicklesworthstone/destructive_command_guard\n\n### Memory Tests Job\n\nRuns dedicated memory leak tests with:\n- `--test-threads=1` for accurate measurements\n- Release mode for realistic performance\n- 1-2MB growth budgets per test\n\nTests include: hook input parsing, pattern evaluation, heredoc extraction, file extractors, full pipeline, and a self-test that verifies the framework catches leaks.\n\n### Benchmarks Job\n\nRuns on push to main only (benchmarks are noisy on PRs). Checks performance budgets from `src/perf.rs`:\n- Quick reject: < 50us panic\n- Fast path: < 500us panic\n- Pattern match: < 1ms panic\n- Heredoc extract: < 2ms panic\n- Full heredoc pipeline: < 20ms panic\n- Hook evaluation deadline: 1000ms (exhaustion is indeterminate, never a silent allow)\n\n### UBS Static Analysis\n\nUltimate Bug Scanner runs on changed Rust files. Currently warning-only (non-blocking) to tune for false positives. Configuration in `.ubsignore` excludes test/bench/fuzz directories.\n\n### Dependabot\n\nAutomated dependency updates configured in `.github/dependabot.yml`:\n- **Cargo dependencies:** Weekly (Monday 9am EST), 5 PR limit\n- **GitHub Actions:** Weekly (Monday 9am EST), 3 PR limit\n- **Grouping:** Minor/patch updates grouped; serde updates separate (more careful review)\n\n### Debugging CI Failures\n\n#### Coverage Threshold Failure\n1. Check which file(s) dropped below threshold in CI output\n2. Run `cargo llvm-cov --html` locally to see uncovered lines\n3. Add tests for uncovered code paths\n4. Download `coverage-report` artifact for full details\n\n#### Memory Test Failure\n1. Download `memory-test-output` artifact\n2. Check which test failed and growth amount\n3. Run locally: `cargo test --test memory_tests --release -- --nocapture --test-threads=1`\n4. Profile with valgrind if needed\n\n#### UBS Warnings\n1. Check ubs-output.log in CI summary\n2. Review flagged issues - may be false positives\n3. If valid issues, fix them; if false positives, add to `.ubsignore`\n\n#### E2E Test Failure\n1. Download `e2e-artifacts` artifact\n2. Check `e2e_output.json` for failing test details\n3. Run locally: `./scripts/e2e_test.sh --verbose`\n4. The step summary shows the first failure with output\n\n#### Benchmark Regression\n1. Download `benchmark-results` artifact\n2. Compare against budgets in `src/perf.rs`\n3. Profile locally with `cargo bench --bench heredoc_perf`\n4. Check for algorithmic regressions in hot path\n\n---\n\n## Release Process\n\nWhen fixes are ready for release, follow this process:\n\nThe steps below describe the normal GitHub Actions path. If Actions cannot run\nor a native Windows artifact must be built locally, the more detailed\n**Local/DSR Release and Windows Deployment Runbook** near the end of this file\nis authoritative.\n\n### 1. Verify CI Passes Locally\n\n```bash\ncargo fmt --check\ncargo check --all-targets\ncargo clippy --all-targets -- -D warnings\ncargo test\n```\n\n### 2. Commit Changes\n\n```bash\ngit add -A\ngit commit -m \"fix: description of fixes\n\n- List specific fixes\n- Include any breaking changes\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\"\n```\n\n### 3. Bump Version (if needed)\n\nThe version in `Cargo.toml` determines the release tag. A version may be reused\nafter a failed *local, pre-tag* attempt. Once its tag has been pushed or a\nrelease has been published, treat that version as immutable and bump to a new\npatch version instead of moving the tag or replacing signed assets.\n\n- **Patch** (0.2.10 -> 0.2.11): Bug fixes, no new features\n- **Minor** (0.2.x -> 0.3.0): New features, backward compatible\n- **Major** (0.x -> 1.0): Breaking changes\n\n### 4. Push and Trigger Release\n\n```bash\ngit push origin main\ngit push origin main:master  # Keep master in sync\n```\n\nThe `release-automation.yml` workflow will:\n1. Detect version change in `Cargo.toml`\n2. Create an annotated git tag (e.g., `v0.2.13`)\n3. Push the tag, which triggers `dist.yml`\n\nThe `dist.yml` workflow will:\n1. Run tests and clippy\n2. Build binaries for all platforms (Linux x86/ARM, macOS Intel/Apple Silicon, Windows)\n3. Create `.tar.xz` archives with SHA256 checksums\n4. Sign artifacts with Sigstore (cosign) - creates `.sigstore.json` bundles\n5. Upload everything to GitHub Releases\n\n### 5. Verify Release\n\n```bash\ngh release list --limit 5\ngh release view v0.2.13  # Check assets were uploaded\n```\n\nExpected assets per release:\n- `dcg-{target}.tar.xz` (Unix) or `.zip` (Windows) - Binary archive\n- `<archive>.sha256` - Mandatory per-artifact checksum\n- `<archive>.sigstore.json` - Sigstore signature bundle\n- `install.sh`, `install.ps1`, and their checksum/Sigstore sidecars\n- Manual DSR releases additionally include minisign signatures, SLSA\n  provenance, a build manifest, and the pinned public verification keys\n\n### Troubleshooting Failed Releases\n\nIf CI fails:\n1. Check workflow run: `gh run list --workflow=dist.yml --limit=5`\n2. View failed job: `gh run view <run-id>`\n3. Fix issues locally, commit, and push again\n4. If no public tag or release exists yet, retry the same version; otherwise\n   create a new patch release. Never force-move a public release tag.\n\nCommon failures:\n- **Clippy errors**: Fix lints, ensure `cargo clippy -- -D warnings` passes\n- **Test failures**: Run `cargo test` to reproduce\n- **Format errors**: Run `cargo fmt` to fix\n\n---\n\n## MCP Agent Mail — Multi-Agent Coordination\n\nA mail-like layer that lets coding agents coordinate asynchronously via MCP tools and resources. Provides identities, inbox/outbox, searchable threads, and advisory file reservations with human-auditable artifacts in Git.\n\n### Why It's Useful\n\n- **Prevents conflicts:** Explicit file reservations (leases) for files/globs\n- **Token-efficient:** Messages stored in per-project archive, not in context\n- **Quick reads:** `resource://inbox/...`, `resource://thread/...`\n\n### Same Repository Workflow\n\n1. **Register identity:**\n   ```\n   ensure_project(project_key=<abs-path>)\n   register_agent(project_key, program, model)\n   ```\n\n2. **Reserve files before editing:**\n   ```\n   file_reservation_paths(project_key, agent_name, [\"src/**\"], ttl_seconds=3600, exclusive=true)\n   ```\n\n3. **Communicate with threads:**\n   ```\n   send_message(..., thread_id=\"FEAT-123\")\n   fetch_inbox(project_key, agent_name)\n   acknowledge_message(project_key, agent_name, message_id)\n   ```\n\n4. **Quick reads:**\n   ```\n   resource://inbox/{Agent}?project=<abs-path>&limit=20\n   resource://thread/{id}?project=<abs-path>&include_bodies=true\n   ```\n\n### Macros vs Granular Tools\n\n- **Prefer macros for speed:** `macro_start_session`, `macro_prepare_thread`, `macro_file_reservation_cycle`, `macro_contact_handshake`\n- **Use granular tools for control:** `register_agent`, `file_reservation_paths`, `send_message`, `fetch_inbox`, `acknowledge_message`\n\n### Common Pitfalls\n\n- `\"from_agent not registered\"`: Always `register_agent` in the correct `project_key` first\n- `\"FILE_RESERVATION_CONFLICT\"`: Adjust patterns, wait for expiry, or use non-exclusive reservation\n- **Auth errors:** If JWT+JWKS enabled, include bearer token with matching `kid`\n\n---\n\n## Beads (br) — Dependency-Aware Issue Tracking\n\nBeads provides a lightweight, dependency-aware issue database and CLI (`br` - beads_rust) for selecting \"ready work,\" setting priorities, and tracking status. It complements MCP Agent Mail's messaging and file reservations.\n\n**Important:** `br` is non-invasive—it NEVER runs git commands automatically. You must manually commit changes after `br sync --flush-only`.\n\n### Conventions\n\n- **Single source of truth:** Beads for task status/priority/dependencies; Agent Mail for conversation and audit\n- **Shared identifiers:** Use Beads issue ID (e.g., `br-123`) as Mail `thread_id` and prefix subjects with `[br-123]`\n- **Reservations:** When starting a task, call `file_reservation_paths()` with the issue ID in `reason`\n\n### Typical Agent Flow\n\n1. **Pick ready work (Beads):**\n   ```bash\n   br ready --json  # Choose highest priority, no blockers\n   ```\n\n2. **Reserve edit surface (Mail):**\n   ```\n   file_reservation_paths(project_key, agent_name, [\"src/**\"], ttl_seconds=3600, exclusive=true, reason=\"br-123\")\n   ```\n\n3. **Announce start (Mail):**\n   ```\n   send_message(..., thread_id=\"br-123\", subject=\"[br-123] Start: <title>\", ack_required=true)\n   ```\n\n4. **Work and update:** Reply in-thread with progress\n\n5. **Complete and release:**\n   ```bash\n   br close 123 --reason \"Completed\"\n   br sync --flush-only  # Export to JSONL (no git operations)\n   ```\n   ```\n   release_file_reservations(project_key, agent_name, paths=[\"src/**\"])\n   ```\n   Final Mail reply: `[br-123] Completed` with summary\n\n### Mapping Cheat Sheet\n\n| Concept | Value |\n|---------|-------|\n| Mail `thread_id` | `br-###` |\n| Mail subject | `[br-###] ...` |\n| File reservation `reason` | `br-###` |\n| Commit messages | Include `br-###` for traceability |\n\n---\n\n## bv — Graph-Aware Triage Engine\n\nbv is a graph-aware triage engine for Beads projects (`.beads/beads.jsonl`). It computes PageRank, betweenness, critical path, cycles, HITS, eigenvector, and k-core metrics deterministically.\n\n**Scope boundary:** bv handles *what to work on* (triage, priority, planning). For agent-to-agent coordination (messaging, work claiming, file reservations), use MCP Agent Mail.\n\n**CRITICAL: Use ONLY `--robot-*` flags. Bare `bv` launches an interactive TUI that blocks your session.**\n\n### The Workflow: Start With Triage\n\n**`bv --robot-triage` is your single entry point.** It returns:\n- `quick_ref`: at-a-glance counts + top 3 picks\n- `recommendations`: ranked actionable items with scores, reasons, unblock info\n- `quick_wins`: low-effort high-impact items\n- `blockers_to_clear`: items that unblock the most downstream work\n- `project_health`: status/type/priority distributions, graph metrics\n- `commands`: copy-paste shell commands for next steps\n\n```bash\nbv --robot-triage        # THE MEGA-COMMAND: start here\nbv --robot-next          # Minimal: just the single top pick + claim command\n```\n\n### Command Reference\n\n**Planning:**\n| Command | Returns |\n|---------|---------|\n| `--robot-plan` | Parallel execution tracks with `unblocks` lists |\n| `--robot-priority` | Priority misalignment detection with confidence |\n\n**Graph Analysis:**\n| Command | Returns |\n|---------|---------|\n| `--robot-insights` | Full metrics: PageRank, betweenness, HITS, eigenvector, critical path, cycles, k-core, articulation points, slack |\n| `--robot-label-health` | Per-label health: `health_level`, `velocity_score`, `staleness`, `blocked_count` |\n| `--robot-label-flow` | Cross-label dependency: `flow_matrix`, `dependencies`, `bottleneck_labels` |\n| `--robot-label-attention [--attention-limit=N]` | Attention-ranked labels |\n\n**History & Change Tracking:**\n| Command | Returns |\n|---------|---------|\n| `--robot-history` | Bead-to-commit correlations |\n| `--robot-diff --diff-since <ref>` | Changes since ref: new/closed/modified issues, cycles |\n\n**Other:**\n| Command | Returns |\n|---------|---------|\n| `--robot-burndown <sprint>` | Sprint burndown, scope changes, at-risk items |\n| `--robot-forecast <id\\|all>` | ETA predictions with dependency-aware scheduling |\n| `--robot-alerts` | Stale issues, blocking cascades, priority mismatches |\n| `--robot-suggest` | Hygiene: duplicates, missing deps, label suggestions |\n| `--robot-graph [--graph-format=json\\|dot\\|mermaid]` | Dependency graph export |\n| `--export-graph <file.html>` | Interactive HTML visualization |\n\n### Scoping & Filtering\n\n```bash\nbv --robot-plan --label backend              # Scope to label's subgraph\nbv --robot-insights --as-of HEAD~30          # Historical point-in-time\nbv --recipe actionable --robot-plan          # Pre-filter: ready to work\nbv --recipe high-impact --robot-triage       # Pre-filter: top PageRank\nbv --robot-triage --robot-triage-by-track    # Group by parallel work streams\nbv --robot-triage --robot-triage-by-label    # Group by domain\n```\n\n### Understanding Robot Output\n\n**All robot JSON includes:**\n- `data_hash` — Fingerprint of source beads.jsonl\n- `status` — Per-metric state: `computed|approx|timeout|skipped` + elapsed ms\n- `as_of` / `as_of_commit` — Present when using `--as-of`\n\n**Two-phase analysis:**\n- **Phase 1 (instant):** degree, topo sort, density\n- **Phase 2 (async, 500ms timeout):** PageRank, betweenness, HITS, eigenvector, cycles\n\n### jq Quick Reference\n\n```bash\nbv --robot-triage | jq '.quick_ref'                        # At-a-glance summary\nbv --robot-triage | jq '.recommendations[0]'               # Top recommendation\nbv --robot-plan | jq '.plan.summary.highest_impact'        # Best unblock target\nbv --robot-insights | jq '.status'                         # Check metric readiness\nbv --robot-insights | jq '.Cycles'                         # Circular deps (must fix!)\n```\n\n---\n\n## UBS — Ultimate Bug Scanner\n\n**Golden Rule:** `ubs <changed-files>` before every commit. Exit 0 = safe. Exit >0 = fix & re-run.\n\n### Commands\n\n```bash\nubs file.rs file2.rs                    # Specific files (< 1s) — USE THIS\nubs $(git diff --name-only --cached)    # Staged files — before commit\nubs --only=rust,toml src/               # Language filter (3-5x faster)\nubs --ci --fail-on-warning .            # CI mode — before PR\nubs .                                   # Whole project (ignores target/, Cargo.lock)\n```\n\n### Output Format\n\n```\nWarning  Category (N errors)\n    file.rs:42:5 - Issue description\n    Suggested fix\nExit code: 1\n```\n\nParse: `file:line:col` -> location | fix hint -> how to fix | Exit 0/1 -> pass/fail\n\n### Fix Workflow\n\n1. Read finding ","isInternal":false,"tokens":12604,"sizeBytes":50094},{"name":"agents.md","path":"docs/agents.md","rawUrl":"https://raw.githubusercontent.com/Dicklesworthstone/destructive_command_guard/HEAD/docs/agents.md","title":"docs - AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# Agent-Specific Profiles\n\ndcg can detect which AI coding agent is invoking it and apply agent-specific\ntrust levels and configuration overrides. This allows you to grant higher\ntrust to well-behaved agents while maintaining strict controls for unknown ones.\n\n## Supported Agents\n\n| Agent | Detection Method | Environment Variable |\n|-------|------------------|---------------------|\n| Claude Code | Environment | `CLAUDE_CODE=1` or `CLAUDE_SESSION_ID` |\n| Augment Code | Environment | `AUGMENT_AGENT=1` or `AUGMENT_CONVERSATION_ID` |\n| Aider | Environment | `AIDER_SESSION=1` |\n| Continue | Environment | `CONTINUE_SESSION_ID` |\n| Codex CLI | Environment | `CODEX_CLI=1` |\n| Gemini CLI | Environment | `GEMINI_CLI=1` |\n| GitHub Copilot CLI | Environment | `COPILOT_CLI=1` or `COPILOT_AGENT_START_TIME_SEC` |\n| VS Code Copilot Chat | Hook payload | `tool_name` is `runTerminalCommand`, `run_in_terminal`, or `runInTerminal`; the Agent Host's batched `toolCalls` envelope is also recognized, with every shell entry evaluated independently |\n| Cursor IDE | Environment | `CURSOR_IDE=1` (set by dcg's hook script) |\n| Hermes Agent | Environment | `HERMES_AGENT=1` or `HERMES_SESSION_ID` |\n| Grok (xAI) | Environment | `GROK_SESSION_ID`, `GROK_HOOK_EVENT`, or `GROK_WORKSPACE_ROOT` |\n| Oh My Pi (`omp`) | Explicit bridge / process | Generated extension passes `--agent omp`; exact `omp` and `oh-my-pi` process names are fallback matches |\n| Crush | Environment | `CRUSH=1` (set by Crush for hook subprocesses and `bash`-tool commands; the generic `AGENT`/`AI_AGENT` markers are not consulted). Hook payloads are recognized by their `event: \"PreToolUse\"` + `tool_input` envelope — see [crush-integration.md](crush-integration.md) |\n| Muse Code | Explicit flag only | The hook written by `dcg install --muse` passes `--agent muse` (Muse clears the hook environment). Its payload looks like Codex's (`turn_id`), so without the flag `[agents.codex-cli]` applies; the identity is never taken from the payload or a process name — see [muse-integration.md](muse-integration.md) |\n| Amp | Explicit bridge | The plugin written by `dcg install --amp` passes `--agent amp`; no environment marker or process name is consulted — see [amp-integration.md](amp-integration.md) |\n| Pi | Environment | `PI_CODING_AGENT=true` |\n| Posit Assistant | Environment | `PA_PROJECT_DIR` (set in hook subprocesses; checked last among environment markers so agents with their own markers win — environment detection still precedes parent-process detection) |\n\n## Detection Priority\n\nAgent detection follows this priority order:\n\n1. **Explicit `--agent` flag**: Manual override via CLI\n2. **Environment variables**: Most agents set identifying env vars\n3. **Parent process inspection**: Fallback check of process tree\n4. **Unknown**: Default when no agent is detected\n\n### Oh My Pi (`omp`)\n\nInstall dcg's native OMP ExtensionAPI module with:\n\n```bash\ndcg install --omp\n```\n\nThe default user path is `~/.omp/agent/extensions/dcg-guard.ts`. OMP named\nprofiles use `~/.omp/profiles/<name>/agent/extensions/dcg-guard.ts`; dcg follows\nOMP's `OMP_PROFILE`-before-`PI_PROFILE` precedence and honors\n`PI_CONFIG_DIR` for a config directory name relative to the user's home plus\n`PI_CODING_AGENT_DIR` for the default profile. Drive-qualified\n`PI_CONFIG_DIR` values are rejected on Windows because Rust would otherwise\nresolve them differently from OMP's home-relative `path.join` behavior. Use\n`dcg install --omp --project` to install `<cwd>/.omp/extensions/dcg-guard.ts`\ninstead. OMP checks only the current working directory for native project\nextensions; it neither requires Git nor walks to an ancestor, so launch OMP\nfrom that same directory.\n\nThe extension uses OMP's pre-execution `tool_call` event and sends each `bash`\ncommand to `dcg --robot test --stdin --agent omp --format json` with the dialect\nselected by OMP's execution route. Pinning the private bridge format prevents\nambient `DCG_FORMAT` from redirecting or invalidating its compact protocol\nwithout removing that variable from supported environment-conditioned policy.\nThe embedded install-time absolute dcg pathname is authoritative: ambient\n`DCG_BIN` cannot redirect the marker-owned guard. This binds a pathname, not a\nhash, inode/file ID, signature, or immutable executable object. Bun resolves\nthat pathname again for each tool call, so moving or removing the file produces\na visible infrastructure failure, while replacing bytes at the same pathname\nchanges what a later callback executes. `dcg doctor` compares the marker-owned\nextension with source generated for the doctor process's pathname at inspection\ntime; it does not attest binary contents or an extension already loaded by a\nrunning OMP session. To rebind deliberately, run\n`/desired/path/dcg install --omp --force` (add `--project` for project scope)\nand restart OMP. Protect the binary, extension, and their parent directories\nfrom writers not trusted to control OMP execution; a writer that can replace the\nextension can already run code inside OMP. Ordinary and managed-async calls use\nOMP's embedded Brush shell and pass `--dialect posix`, including on native\nWindows. An eligible local `pty: true` call instead maps OMP's configured\nexternal shell to `--dialect posix`, `cmd`, or `ps`; `PI_NO_PTY=1` disables that\nPTY route. A dcg deny, ask, or indeterminate result returns\n`{ block: true, reason }` to OMP.\nBecause the bridge supplies `--agent omp` explicitly, OMP remains distinct from\nlegacy Pi even when OMP exposes Pi-family compatibility variables. The\ncanonical profile key is `agents.omp`; `oh-my-pi` is accepted as an alias.\nWhen `[history] enabled = true`, these robot-boundary evaluations are persisted\nwith the canonical `agent_type = \"omp\"`; ordinary human `dcg test` diagnostics\nremain outside command history.\n\nThe bridge spawns dcg directly, without a shell, and gives Bun a 30-second\nparent-side timeout with a hard kill signal. Immediately after a successful\nspawn it records one monotonic absolute deadline 30.5 seconds away and arms one\nobservation watchdog. A direct child can exit while a descendant still holds\nits stdout or stderr descriptor, so exit settlement rearms that sole timer for\nthe lesser of a 250-millisecond pipe-drain grace and the remaining absolute\nbudget. If the remaining hard budget wins, expiry retains hard-deadline\nprovenance. Exit-observation rejection attempts one direct-child `SIGKILL` and\nuses the same clamped drain; neither rearm kills again. Every active watchdog is\ncleared when observation finishes, and late exit settlement or rejection\nremains consumed. There is no retry, replacement process, or second\nconcurrently active bridge watchdog.\n\nThis is the finite child-observation lineage:\n\n| Current state + event | Process action | Pipe/clock action | Result invariant |\n|---|---|---|---|\n| `Bun.spawn` throws | No child exists | No watchdog is armed | One visible spawn diagnostic; fail open |\n| Spawn succeeds | Bun owns the 30-second direct-child timeout | Record and arm the monotonic 30.5-second absolute observation deadline; read both capped pipes and exit concurrently | No classification before the bounded observation settles |\n| Complete deny/ask/indeterminate frame or stdout overflow arrives | None | Retain the frame or capped overflow evidence while observation continues | Blocking evidence is absorbing |\n| Exit resolves before the hard deadline | None; the direct child is already gone | Rearm for `min(250 ms, remaining hard budget)` | Bytes already read remain eligible evidence; the absolute deadline cannot move |\n| Exit rejects before the hard deadline | Attempt one direct-child `SIGKILL` | Rearm for `min(250 ms, remaining hard budget)` without another kill | The exit fault and any kill fault remain visible; kill request alone does not prove a signal |\n| Drain grace expires | The direct child has exited or already received the rejection-path kill | Cancel both local readers | Retained block/overflow still blocks; missing evidence follows visible infrastructure fail-open |\n| Hard observation deadline expires | Attempt one direct-child `SIGKILL` | Cancel both readers and the local exit wrapper | Retained block/overflow still blocks; deadline remains visible |\n| All three observations settle first | None | Clear the active watchdog | Read signal provenance, classify once, and emit each owned diagnostic once |\n\nIf a blocking frame and a deadline become runnable together, JavaScript's event\nloop imposes a total order: bytes delivered before reader cancellation are\nretained and absorbing; bytes delivered after cancellation are outside the\nobservation boundary. The stdout and stderr caps bound retained memory, and the\nouter/deferred deadlines bound a standards-compliant asynchronous observation.\nIf an underlying WHATWG cancellation algorithm rejects, the stream is still\nclosed and pending reads settle; the bridge consumes the cancellation promise's\nrejection while retaining prior deny/ask/indeterminate and overflow evidence.\nThe backstops sit above dcg's ordinary 1-second evaluator default and the broad\nWindows preset's 3-second default, not in place of those configurable budgets.\nAn explicit evaluator budget above 30 seconds is nevertheless capped on this\nOMP bridge; direct hook and diagnostic invocations retain their configured\nbudget.\n\nTwo seams remain fundamentally outside this in-process boundary. A synchronous\nstall inside `Bun.spawn` (or any JavaScript/event-loop stall) prevents a timer\nfrom starting or firing. Bun's `proc.kill()` targets the direct child, not a\nprocess group, so it cannot prove that descendants were terminated or release\ntheir unrelated resources; local reader cancellation bounds the callback even\nwhen an inherited pipe holder survives. A non-standard stream whose synchronous\ncancel path throws without settling a pending read is likewise outside the Web\nStreams contract and cannot be bounded by another JavaScript promise. After\nobservation, the bridge reads Bun's `signalCode` and reports an exact signal\nonly when Bun actually exposes one; a successful `proc.kill(\"SIGKILL\")` request\ndoes not itself forge SIGKILL provenance. This keeps an ordinary numeric exit\n137 distinct from an observed timeout or other observed `SIGKILL`.\nA complete deny, ask, or indeterminate verdict already written to stdout\nremains authoritative regardless of signal, cancellation, stream/exit fault,\nor deadline; stdout overflow and dcg's independent blocking exit 1 are likewise\nabsorbing. Other abnormal statuses and infrastructure faults remain visible\neven when blocking evidence controls the action.\n\nOMP's public ExtensionAPI does not expose its ACP client-terminal capability or\nselected backend, and both ACP and JSON-RPC surface as `mode: \"rpc\"`. The bridge\ntherefore does not infer a Windows dialect from mode: non-PTY RPC calls remain\nPOSIX-scoped so ordinary JSON-RPC execution does not gain Cmd/PowerShell false\npositives. As a result, an ACP client-terminal call does not yet receive exact\nCmd/PowerShell-specific coverage; closing that residual requires OMP to expose\nthe actual BashTool route before the `tool_call` handler runs.\n\n## Trust Levels\n\nThree trust levels label how much you trust a given agent:\n\n| Level | Description |\n|-------|-------------|\n| `high` | Agent has proven reliable; typically paired with a broader allowlist and fewer packs |\n| `medium` | Default; standard configuration |\n| `low` | Extra caution; typically paired with more packs and a restricted allowlist |\n\n### How trust levels work\n\nThe `trust_level` field is an **advisory label**. It is recorded in JSON output\nand shown in verbose/debug logs so you (and downstream tooling) can see what\ntrust tier was in effect for a given evaluation. It does **not**, by itself,\nchange which rules fire or how confidence scores are computed.\n\nAll behavioral differences between agents come from the other profile options\nthat you configure alongside the trust level:\n\n| Option | What it does | Typical usage |\n|--------|-------------|---------------|\n| `disabled_packs` | Removes packs (and their sub-packs) from evaluation | High-trust agents that don't need certain rule sets |\n| `extra_packs` | Adds packs to evaluation | Low-trust agents that should be checked against more rules |\n| `additional_allowlist` | Adds command patterns that bypass deny rules | High-trust agents with known-safe build commands |\n| `disabled_allowlist` | When `true`, ignores *all* allowlist entries (base + additional) | Low-trust agents that should never get a free pass |\n\nIn other words: setting `trust_level = \"high\"` alone does not relax any rules.\nYou must also adjust `disabled_packs`, `extra_packs`, `additional_allowlist`,\nor `disabled_allowlist` to change evaluation behavior.\n\n### Why the separation?\n\nThis design is intentional. Trust is not a magic knob -- different environments\nneed different trade-offs. A \"high trust\" agent in one project might need strict\ndatabase rules but relaxed filesystem rules, while in another project the\nopposite applies. By keeping the label separate from the behavioral knobs, dcg\ngives you full control without hidden side effects.\n\n### Practical examples\n\n**High-trust agent** -- a well-tested agent that runs routine build/test\ncommands. You widen the allowlist and disable packs that produce false positives\nfor its workflow:\n\n```toml\n[agents.claude-code]\ntrust_level = \"high\"\nadditional_allowlist = [\"npm run build\", \"cargo test\", \"make lint\"]\ndisabled_packs = [\"kubernetes\"]\n```\n\n**Medium-trust agent (default)** -- standard rules, no overrides:\n\n```toml\n[agents.default]\ntrust_level = \"medium\"\n```\n\n**Low-trust agent** -- an unknown or new agent. You add extra packs and disable\nthe allowlist so every command is evaluated against the full rule set:\n\n```toml\n[agents.unknown]\ntrust_level = \"low\"\ndisabled_allowlist = true\n# Real pack / category IDs (see `dcg packs` / docs/packs/README.md). A category\n# ID like \"database\" expands to every database.* sub-pack. \"paranoid\" is a\n# graduation mode, not a pack — use the real `strict_git` pack for stricter git\n# rules, and `core.filesystem` (not \"filesystem\") for the filesystem pack.\nextra_packs = [\"strict_git\", \"database\", \"system\"]\n```\n\n## Configuration\n\nConfigure agent profiles in your `config.toml`:\n\n```toml\n# Trust Claude Code more (it sets CLAUDE_CODE=1)\n[agents.claude-code]\ntrust_level = \"high\"\nadditional_allowlist = [\"npm run build\", \"cargo test\"]\n\n[agents.omp]\ntrust_level = \"medium\"\nextra_packs = [\"strict_git\"]\n\n# Restrict unknown agents\n[agents.unknown]\ntrust_level = \"low\"\ndisabled_allowlist = true\nextra_packs = [\"strict_git\", \"database\"]\n\n# Default profile for unspecified agents\n[agents.default]\ntrust_level = \"medium\"\n```\n\n### Profile Options\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `trust_level` | string | `\"medium\"` | Advisory label: `\"high\"`, `\"medium\"`, or `\"low\"`. Included in JSON/verbose output but does not change rule evaluation by itself. |\n| `disabled_packs` | array | `[]` | Pack or category IDs to remove from evaluation for this agent (a category ID drops every matching sub-pack). |\n| `extra_packs` | array | `[]` | Additional pack or category IDs to enable for this agent (a category ID expands to all its sub-packs). |\n| `additional_allowlist` | array | `[]` | Command patterns to allowlist for this agent (added on top of the base allowlist). |\n| `disabled_allowlist` | bool | `false` | If `true`, ignore all allowlist entries for this agent (more restrictive). |\n\n### Example: Agent Profiles for a Trusted Config\n\n```toml\n# In ~/.config/dcg/config.toml, or in a reviewed file selected with DCG_CONFIG\n[agents.unknown]\ntrust_level = \"low\"\ndisabled_allowlist = true\nextra_packs = [\"strict_git\", \"database\", \"system\"]\n\n[agents.claude-code]\ntrust_level = \"medium\"\nadditional_allowlist = [\"npm test\", \"npm run lint\"]\n```\n\nAutomatically discovered repository `.dcg.toml` files cannot define agent\nprofiles. Profiles can disable packs or add allowlist entries, so accepting\nthem from a newly cloned repository would cross dcg's security boundary. To\nuse a reviewed repository-owned profile deliberately, invoke dcg with\n`DCG_CONFIG=.dcg.toml` (or copy the profile into the user config).\n\n## Custom Agents\n\nDefine profiles for custom agents by setting an environment variable:\n\n```bash\n# Set a custom agent identifier\nexport MY_BUILD_BOT=1\n```\n\nThen configure in `config.toml`:\n\n```toml\n[agents.my-build-bot]\ntrust_level = \"high\"\nadditional_allowlist = [\"make deploy\"]\n```\n\n## Profile Resolution\n\nWhen resolving which profile to use:\n\n1. Look for exact match: `agents.<agent-config-key>`\n2. Fall back to `agents.unknown` if agent is unrecognized\n3. Fall back to `agents.default` if no specific profile exists\n\n## Verbose Output\n\nUse `--verbose` or `-v` to see agent detection info:\n\n```bash\n$ dcg test \"git push --force\" --verbose\nCommand: git push --force\n...\nElapsed: 21.14ms\nAgent: Claude Code\nTrust level: medium\nSeverity: critical\n```\n\nUse `-vv` for detailed debug output:\n\n```bash\n$ dcg test \"git push --force\" -vv\n...\nAgent detection:\n  Detected: Claude Code (claude-code)\n  Method: environment_variable\n  Matched: CLAUDE_CODE\n  Profile: agents.claude-code\n  Trust level: medium\n```\n\n## JSON Output\n\nThe `--format json` output includes agent information:\n\n```json\n{\n  \"command\": \"git push --force\",\n  \"decision\": \"deny\",\n  \"agent\": {\n    \"detected\": \"claude-code\",\n    \"trust_level\": \"medium\",\n    \"detection_method\": \"environment_variable\"\n  }\n}\n```\n\n## Robot Mode\n\nRobot mode provides a unified, machine-friendly interface for AI agents. When\nenabled, dcg optimizes its output for programmatic consumption.\n\n### Enabling Robot Mode\n\n```bash\n# Via flag\ndcg --robot test \"rm -rf /\"\n\n# Via environment variable\nDCG_ROBOT=1 dcg test \"rm -rf /\"\n```\n\n### Robot Mode Behavior\n\n| Aspect | Normal Mode | Robot Mode |\n|--------|-------------|------------|\n| stdout | JSON or pretty | Always JSON |\n| stderr | Rich colored output | Silent |\n| Exit codes | Varies | Standardized |\n| ANSI codes | If TTY | Never |\n| Progress | Shown | Hidden |\n| Suggestions | Shown | In JSON only |\n\n### Standardized Exit Codes\n\nIn robot mode, dcg uses consistent exit codes across all commands:\n\n| Code | Constant | Meaning |\n|------|----------|---------|\n| 0 | `EXIT_SUCCESS` | Success / Allow |\n| 1 | `EXIT_DENIED` | Command denied/blocked |\n| 2 | `EXIT_WARNING` | Warning (with --fail-on warn) |\n| 3 | `EXIT_CONFIG_ERROR` | Configuration error |\n| 4 | `EXIT_PARSE_ERROR` | Parse/input error |\n| 5 | `EXIT_IO_ERROR` | IO error |\n| 141 | `EXIT_BROKEN_PIPE` | stdout/stderr reader went away (`EPIPE`); a clean exit, never a signal death |\n\n### Robot Mode JSON Output\n\nAll robot-mode responses are pure JSON on stdout:\n\n```json\n{\n  \"command\": \"rm -rf /\",\n  \"decision\": \"deny\",\n  \"rule_id\": \"core.filesystem:rm-rf-root\",\n  \"pack_id\": \"core.filesystem\",\n  \"severity\": \"critical\",\n  \"reason\": \"rm -rf / would delete the entire filesystem\",\n  \"agent\": {\n    \"detected\": \"claude-code\",\n    \"trust_level\": \"medium\",\n    \"detection_method\": \"environment_variable\"\n  }\n}\n```\n\n### Hook Mode vs Robot Mode\n\n**Hook mode** (default when no subcommand) follows the active hook protocol:\n- Claude Code, Gemini CLI, Copilot CLI, VS Code Copilot Chat, and compatible JSON-hook protocols emit\n  JSON on stdout for denials and empty stdout for allows.\n- Codex CLI uses strict hook parsing, so dcg emits a minimal\n  `hookSpecificOutput` denial on stdout and exits 0.\n- Rich output always goes to stderr for human visibility.\n\nHook mode carries the verdict in stdout JSON and exits 0 whether the command\nwas allowed, warned about, sent for review, or denied. The exit status changes\nonly when a blocking verdict could not be delivered:\n\n| Code | Constant | Meaning |\n|------|----------|---------|\n| 0 | `EXIT_SUCCESS` | Verdict delivered on stdout (an allow is empty stdout). Also an allow or a warning whose stdout reader went away: nothing was lost. |\n| 2 | `EXIT_HOOK_BLOCK` | A deny, ask, or indeterminate verdict could not be written to stdout (`EPIPE`: the host closed the pipe early). Exit 0 with nothing on stdout would read as \"proceed\", so the block travels in the exit status instead, with the reason on stderr. |\n\nHow hosts read exit 2 with nothing on stdout:\n\n| Protocol | Effect |\n|----------|--------|\n| Claude Code and Claude-compatible hosts (Posit Assistant, Augment) | Blocks; stderr is fed back to the model as the reason |\n| Gemini CLI | Blocks (exit 2 is its blocking error) |\n| Copilot CLI | Blocks (`preToolUse` hooks that exit 2 deny the call) |\n| Crush | Blocks; stderr is the reason |\n| Grok | Blocks (exit 2 is a documented explicit deny) |\n| Codex CLI | Blocks in current releases (exit 2 with the reason on stderr is documented); some earlier builds logged it as a hook failure and failed open |\n| Hermes | Blocks in current releases (a `pre_tool_call` hook exiting 2 blocks even with no JSON on stdout); earlier builds only warned |\n| Reasonix | Blocks — exit 2 is its only blocking channel, so dcg always uses it there |\n| Muse Code | Not documented: a non-zero exit is a hook failure, which Muse hands to the handler's `onFailure` fallback when one is declared |\n| Antigravity (`agy`) | Logged as a hook failure, then fails open — the same outcome as exit 0 with no JSON, but visible |\n\nHook mode never exits 2 for any other reason, and never exits 141: every\nhook-mode write tolerates a closed pipe. `EXIT_BROKEN_PIPE` belongs to the\nCLI surface below.\n\n**Robot mode** with subcommands uses standardized exit codes:\n- Exit 1 for denials (allows scripting with `$?`)\n- Pure JSON on stdout\n- Silent stderr\n\n## Rich Output and Agent Compatibility\n\ndcg keeps agent-facing output and human-facing output on separate streams. This\nis the compatibility contract for rich terminal formatting.\n\n| Stream | Purpose | Hook-mode content | Robot-mode content |\n|--------|---------|-------------------|--------------------|\n| stdout | Agent and script parsing | Protocol JSON for denials, empty for allows | JSON only |\n| stderr | Human-visible diagnostics | Rich or plain text warning boxes | Silent |\n\nRich output is display-only. It must never be parsed by agents and must never be\nwritten to stdout. When dcg prints Unicode boxes, colors, highlighted commands,\nor suggestion panels, that output belongs on stderr.\n\n### Rich Output Selection\n\ndcg uses rich terminal formatting only when the runtime is suitable. It falls\nback to plain output when any of these controls are active:\n\n| Control | Effect |\n|---------|--------|\n| `DCG_NO_RICH=1` | Disable rich formatting while keeping normal command behavior |\n| `--legacy-output` or `DCG_LEGACY_OUTPUT=1` | Force legacy/plain rendering paths |\n| `NO_COLOR=1` or `DCG_NO_COLOR=1` | Disable colorized output |\n| `TERM=dumb` | Use dumb-terminal-safe output |\n| `CI=1` | Suppress rich interactive formatting in CI |\n| non-TTY stdout | Prefer plain output for pipeline-friendly behavior |\n| `--robot` or `DCG_ROBOT=1` | Emit machine-readable stdout and keep stderr silent |\n\n### Wrapper Guidance\n\nAgent wrappers should choose the interface that matches their parser:\n\n```bash\n# Hook integration: preserve both streams.\ndcg < hook-input.json >hook-stdout.json 2>human-warning.txt\n\n# Scripting integration: use robot mode and parse stdout only.\ndcg --robot test \"rm -rf /\" >decision.json 2>/dev/null\n```\n\nFor Codex and Claude-compatible hook integrations, parse stdout when it is\nnon-empty and treat empty stdout with exit 0 as allow. Codex's denial payload is\nminimal and intentionally omits dcg-only metadata.\n\n### Example: Agent Integration\n\n```bash\n#!/bin/bash\n# Script for AI agent to check commands before execution\n\ncheck_command() {\n    local cmd=\"$1\"\n    local result\n\n    # Use robot mode for predictable output\n    result=$(dcg --robot test \"$cmd\" 2>/dev/null)\n    local exit_code=$?\n\n    if [ $exit_code -eq 0 ]; then\n        echo \"Command allowed: $cmd\"\n        return 0\n    elif [ $exit_code -eq 1 ]; then\n        echo \"Command BLOCKED: $cmd\"\n        echo \"Reason: $(echo \"$result\" | jq -r '.reason')\"\n        return 1\n    else\n        echo \"Error checking command (exit code: $exit_code)\"\n        return $exit_code\n    fi\n}\n\n# Usage\ncheck_command \"git status\"      # Allowed\ncheck_command \"rm -rf /\"        # Blocked\n```\n\n### Unified Output Format\n\nRobot mode uses the unified `OutputFormat` enum:\n\n```bash\n# These are equivalent in robot mode\ndcg --robot test \"cmd\"\ndcg --robot --format json test \"cmd\"\n```\n\nAvailable formats:\n- `pretty` / `text` / `human` - Human-readable (default without --robot)\n- `json` / `sarif` / `structured` - JSON output (default with --robot)\n- `jsonl` - JSON Lines (one object per line, for streaming)\n- `compact` - Compact single-line output\n\n## Best Practices\n\n1. **Start with defaults**: The default `medium` trust level is safe for most\n   use cases.\n\n2. **Grant trust incrementally**: Only increase trust for agents after\n   observing their behavior.\n\n3. **Version reviewed profiles carefully**: A repository can keep a reviewed\n   profile in `.dcg.toml`, but it only takes effect when explicitly selected\n   with `DCG_CONFIG=.dcg.toml`; automatic discovery ignores agent profiles.\n\n4. **Restrict unknown agents**: Always configure `agents.unknown` with lower\n   trust in production environments.\n\n5. **Review the JSON output**: Use `--format json` in CI to audit which agents\n   are accessing your codebase.\n\n6. **Use robot mode for scripting**: When integrating dcg into automated\n   workflows, use `--robot` for consistent, parseable output.\n\n7. **Check exit codes**: In robot mode, use exit codes to make decisions\n   without parsing JSON for simple allow/deny checks.\n","isInternal":false,"tokens":6012,"sizeBytes":25737},{"name":"SKILL.md","path":"SKILL.md","rawUrl":"https://raw.githubusercontent.com/Dicklesworthstone/destructive_command_guard/HEAD/SKILL.md","title":"dcg","category":"anthropic-skill","format":"markdown","content":"---\nname: dcg\ndescription: \"Destructive Command Guard - High-performance Rust hook for Claude Code that blocks dangerous commands before execution. SIMD-accelerated, modular pack system, whitelist-first architecture. Essential safety layer for agent workflows.\"\n---\n\n# DCG — Destructive Command Guard\n\nA high-performance Claude Code hook that intercepts and blocks destructive commands before they execute. Written in Rust with SIMD-accelerated filtering for sub-millisecond latency.\n\n## Why This Exists\n\nAI coding agents are powerful but fallible. They can accidentally run destructive commands:\n\n- **\"Let me clean up the build artifacts\"** → `rm -rf ./src` (typo)\n- **\"I'll reset to the last commit\"** → `git reset --hard` (destroys uncommitted changes)\n- **\"Let me fix the merge conflict\"** → `git checkout -- .` (discards all modifications)\n- **\"I'll clean up untracked files\"** → `git clean -fd` (permanently deletes untracked files)\n\nDCG intercepts dangerous commands *before* execution and blocks them with a clear explanation, giving you a chance to stash your changes first.\n\n## Critical Design Principles\n\n### 1. Whitelist-First Architecture\n\nSafe patterns are checked *before* destructive patterns. This ensures explicitly safe commands are never accidentally blocked:\n\n```\ngit checkout -b feature    →  Matches SAFE \"checkout-new-branch\"  →  ALLOW\ngit checkout -- file.txt   →  No safe match, matches DESTRUCTIVE  →  DENY\n```\n\n### 2. Fail-Safe Defaults (Default-Allow)\n\nUnrecognized commands are **allowed by default**. This ensures:\n- The hook never breaks legitimate workflows\n- Only *known* dangerous patterns are blocked\n- New git commands work until explicitly categorized\n\n### 3. Zero False Negatives Philosophy\n\nThe pattern set prioritizes **never allowing dangerous commands** over avoiding false positives. A few extra prompts for manual confirmation are acceptable; lost work is not.\n\n## What It Blocks\n\n### Git Commands That Destroy Uncommitted Work\n\n| Command | Reason |\n|---------|--------|\n| `git reset --hard` | Destroys uncommitted changes |\n| `git reset --merge` | Destroys uncommitted changes |\n| `git checkout -- <file>` | Discards file modifications |\n| `git restore <file>` (without `--staged`) | Discards uncommitted changes |\n| `git clean -f` | Permanently deletes untracked files |\n\n### Git Commands That Destroy Remote History\n\n| Command | Reason |\n|---------|--------|\n| `git push --force` / `-f` | Overwrites remote commits |\n| `git branch -d`, `--delete`, `-D`, `-f`, `-M`, `-C` | Deletes or force-overwrites a user-owned branch ref |\n\n### Git Commands That Destroy Stashed Work\n\n| Command | Reason |\n|---------|--------|\n| `git stash drop` | Permanently deletes a stash |\n| `git stash clear` | Permanently deletes all stashes |\n\n### Filesystem Commands\n\n| Command | Reason |\n|---------|--------|\n| `rm -rf` (outside `/tmp`, `/var/tmp`, `$TMPDIR`) | Recursive deletion is dangerous |\n\n## What It ALLOWS\n\nSafe operations pass through silently:\n\n### Always Safe Git Operations\n\n`git status`, `git log`, `git diff`, `git add`, `git commit`, `git push`, `git pull`, `git fetch`, read-only branch listings, `git stash`, `git stash pop`, `git stash list`\n\n### Explicitly Safe Patterns\n\n| Pattern | Why Safe |\n|---------|----------|\n| `git checkout -b <branch>` | Creating new branches |\n| `git checkout --orphan <branch>` | Creating orphan branches |\n| `git restore --staged <file>` | Unstaging only, doesn't touch working tree |\n| `git restore -S <file>` | Short flag for staged |\n| `git clean -n` / `--dry-run` | Preview mode, no actual deletion |\n| `rm -rf /tmp/*` | Temp directories are ephemeral |\n| `rm -rf $TMPDIR/*` | Shell variable forms |\n\n### Safe Alternative: `--force-with-lease`\n\n```bash\ngit push --force-with-lease   # ALLOWED - refuses if remote has unseen commits\ngit push --force              # BLOCKED - can overwrite others' work\n```\n\n## Modular Pack System\n\nDCG uses a modular \"pack\" system to organize patterns by category:\n\n### Core Packs (Always Enabled)\n\n| Pack | Description |\n|------|-------------|\n| `core.git` | Destructive git commands |\n| `core.filesystem` | Dangerous rm -rf outside temp |\n\n### Database Packs\n\n| Pack | Description |\n|------|-------------|\n| `database.postgresql` | DROP/TRUNCATE in PostgreSQL |\n| `database.mysql` | DROP/TRUNCATE in MySQL/MariaDB |\n| `database.mongodb` | dropDatabase, drop() |\n| `database.redis` | FLUSHALL/FLUSHDB |\n| `database.sqlite` | DROP in SQLite |\n\n### Container Packs\n\n| Pack | Description |\n|------|-------------|\n| `containers.docker` | docker system prune, docker rm -f |\n| `containers.compose` | docker-compose down --volumes |\n| `containers.podman` | podman system prune |\n\n### Kubernetes Packs\n\n| Pack | Description |\n|------|-------------|\n| `kubernetes.kubectl` | kubectl delete namespace |\n| `kubernetes.helm` | helm uninstall |\n| `kubernetes.kustomize` | kustomize delete patterns |\n\n### Cloud Provider Packs\n\n| Pack | Description |\n|------|-------------|\n| `cloud.aws` | Destructive AWS CLI commands |\n| `cloud.gcp` | Destructive gcloud commands |\n| `cloud.azure` | Destructive az commands |\n\n### Infrastructure Packs\n\n| Pack | Description |\n|------|-------------|\n| `infrastructure.terraform` | terraform destroy |\n| `infrastructure.ansible` | Dangerous ansible patterns |\n| `infrastructure.pulumi` | pulumi destroy |\n\n### System Packs\n\n| Pack | Description |\n|------|-------------|\n| `system.disk` | dd, mkfs, fdisk operations |\n| `system.permissions` | Dangerous chmod/chown patterns |\n| `system.services` | systemctl stop/disable patterns |\n\n### Other Packs\n\n| Pack | Description |\n|------|-------------|\n| `strict_git` | Extra paranoid git protections |\n| `package_managers` | npm unpublish, cargo yank |\n\n### Configuring Packs\n\n```toml\n# ~/.config/dcg/config.toml\n[packs]\nenabled = [\n    \"database.postgresql\",\n    \"containers.docker\",\n    \"kubernetes\",  # Enables all kubernetes sub-packs\n]\n```\n\n## Environment Variables\n\n| Variable | Description |\n|----------|-------------|\n| `DCG_PACKS=\"containers.docker,kubernetes\"` | Enable packs (comma-separated) |\n| `DCG_DISABLE=\"kubernetes.helm\"` | Disable packs/sub-packs |\n| `DCG_VERBOSE=1` | Verbose output |\n| `DCG_COLOR=auto\\|always\\|never` | Color mode |\n| `DCG_BYPASS=1` | Bypass DCG entirely (escape hatch) |\n\n## Installation\n\n### Quick Install (Recommended)\n\n```bash\ncurl -fsSL \"https://raw.githubusercontent.com/Dicklesworthstone/destructive_command_guard/main/install.sh?$(date +%s)\" | bash\n\n# Easy mode: auto-update PATH\ncurl -fsSL \"https://raw.githubusercontent.com/Dicklesworthstone/destructive_command_guard/main/install.sh?$(date +%s)\" | bash -s -- --easy-mode\n\n# System-wide (requires sudo)\ncurl -fsSL \"https://raw.githubusercontent.com/Dicklesworthstone/destructive_command_guard/main/install.sh?$(date +%s)\" | sudo bash -s -- --system\n```\n\n### From Source (Requires Rust Nightly)\n\n```bash\ncargo +nightly install --git https://github.com/Dicklesworthstone/destructive_command_guard\n```\n\n### Prebuilt Binaries\n\nAvailable for: Linux x86_64, Linux ARM64, macOS Intel, macOS Apple Silicon, Windows\n\n## Claude Code Configuration\n\nAdd to `~/.claude/settings.json`:\n\n```json\n{\n  \"hooks\": {\n    \"PreToolUse\": [\n      {\n        \"matcher\": \"Bash|PowerShell|Monitor\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"/absolute/path/to/dcg\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\nReplace `/absolute/path/to/dcg` with the exact resolved binary path. Do not use\nbare `dcg`: non-interactive hook shells may not inherit the interactive\n`PATH`. On native Windows, use `install.ps1` so the hook receives the\nPowerShell-safe absolute command and explicit shell selection.\n\n**Important:** Restart Claude Code after adding the hook.\n\n## How It Works\n\n### Processing Pipeline\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│                        Claude Code                               │\n│  Agent executes `rm -rf ./build`                                │\n└─────────────────────┬───────────────────────────────────────────┘\n                      │\n                      ▼ PreToolUse hook (stdin: JSON)\n┌─────────────────────────────────────────────────────────────────┐\n│                          dcg                                     │\n│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐       │\n│  │    Parse     │───▶│  Normalize   │───▶│ Quick Reject │       │\n│  │    JSON      │    │   Command    │    │   Filter     │       │\n│  └──────────────┘    └──────────────┘    └──────┬───────┘       │\n│                                                  │               │\n│                      ┌───────────────────────────┘               │\n│                      ▼                                           │\n│  ┌──────────────────────────────────────────────────────────┐   │\n│  │                   Pattern Matching                        │   │\n│  │   1. Check SAFE_PATTERNS (whitelist) ──▶ Allow if match  │   │\n│  │   2. Check DESTRUCTIVE_PATTERNS ──────▶ Deny if match    │   │\n│  │   3. No match ────────────────────────▶ Allow (default)  │   │\n│  └──────────────────────────────────────────────────────────┘   │\n└─────────────────────┬───────────────────────────────────────────┘\n                      │\n                      ▼ stdout: JSON (deny) or empty (allow)\n```\n\n### Stage 1: JSON Parsing\n- Reads hook input from stdin\n- Validates Claude Code's `PreToolUse` format\n- Non-Bash tools immediately allowed\n\n### Stage 2: Command Normalization\n- Strips absolute paths: `/usr/bin/git status` → `git status`\n- Preserves argument paths\n\n### Stage 3: Quick Rejection Filter\n- SIMD-accelerated substring search for \"git\" or \"rm\"\n- Commands without these bypass regex entirely (99%+ of commands)\n\n### Stage 4: Pattern Matching\n- Safe patterns checked first (short-circuit on match → allow)\n- Destructive patterns checked second (match → deny)\n- No match → default allow\n\n## Exit Codes\n\n| Code | Meaning |\n|------|---------|\n| `0` | Command is safe, proceed |\n| `2` | Command is blocked, do not execute |\n\n## CLI Usage\n\nTest commands manually:\n\n```bash\n# Show version with build metadata\ndcg --version\n\n# Test a command\necho '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git reset --hard\"}}' | dcg\n```\n\n## Example Block Message\n\n```\n════════════════════════════════════════════════════════════════════════\nBLOCKED  dcg\n────────────────────────────────────────────────────────────────────────\nReason:  git reset --hard destroys uncommitted changes. Use 'git stash' first.\n\nCommand:  git reset --hard HEAD~1\n\nTip: If you need to run this command, execute it manually in a terminal.\n     Consider using 'git stash' first to save your changes.\n════════════════════════════════════════════════════════════════════════\n```\n\n### Contextual Suggestions\n\n| Command Type | Suggestion |\n|-------------|------------|\n| `git reset`, `git checkout --` | \"Consider using 'git stash' first\" |\n| `git clean` | \"Use 'git clean -n' first to preview\" |\n| `git push --force` | \"Consider using '--force-with-lease'\" |\n| `rm -rf` | \"Verify the path carefully before running manually\" |\n\n## Edge Cases Handled\n\n### Path Normalization\n\n```bash\n/usr/bin/git reset --hard          # Blocked\n/usr/local/bin/git checkout -- .   # Blocked\n/bin/rm -rf /home/user             # Blocked\n```\n\n### Flag Ordering Variants\n\n```bash\nrm -rf /path          # Combined flags\nrm -fr /path          # Reversed order\nrm -r -f /path        # Separate flags\nrm --recursive --force /path    # Long flags\n```\n\nAll variants are handled.\n\n### Shell Variable Expansion\n\n```bash\nrm -rf $TMPDIR/build           # Allowed (temp)\nrm -rf ${TMPDIR}/build         # Allowed\nrm -rf \"$TMPDIR/build\"         # Allowed\nrm -rf \"${TMPDIR:-/tmp}/build\" # Allowed\n```\n\n### Staged vs Worktree Restore\n\n```bash\ngit restore --staged file.txt    # Allowed (unstaging only)\ngit restore -S file.txt          # Allowed (short flag)\ngit restore file.txt             # BLOCKED (discards changes)\ngit restore --worktree file.txt  # BLOCKED (explicit worktree)\ngit restore -S -W file.txt       # BLOCKED (includes worktree)\n```\n\n## Performance Optimizations\n\nDCG is designed for zero perceived latency:\n\n| Optimization | Technique |\n|--------------|-----------|\n| **Lazy Static** | Regex patterns compiled once via `LazyLock` |\n| **SIMD Quick Reject** | `memchr` crate for CPU vector instructions |\n| **Early Exit** | Safe match returns immediately |\n| **Zero-Copy JSON** | `serde_json` operates on input buffer |\n| **Zero-Allocation** | `Cow<str>` for path normalization |\n| **Release Profile** | `opt-level=\"z\"`, LTO, single codegen unit |\n\n**Result:** Sub-millisecond execution for typical commands.\n\n## Pattern Counts\n\n| Type | Count |\n|------|-------|\n| Safe patterns (whitelist) | 34 |\n| Destructive patterns (blacklist) | 16 |\n\n## Security Considerations\n\n### What DCG Protects Against\n\n- Accidental data loss from `git checkout --` or `git reset --hard`\n- Remote history destruction from force pushes\n- Stash loss from `git stash drop/clear`\n- Filesystem accidents from `rm -rf` outside temp directories\n\n### What DCG Does NOT Protect Against\n\n- Malicious actors (can bypass the hook)\n- Non-Bash commands (Python/JavaScript file writes, API calls)\n- Committed but unpushed work\n- Commands inside scripts (`./deploy.sh` contents not inspected)\n\n### Threat Model\n\nDCG assumes the AI agent is **well-intentioned but fallible**. It catches honest mistakes, not adversarial attacks.\n\n## Troubleshooting\n\n### Hook not blocking commands\n\n1. Verify `~/.claude/settings.json` has hook configuration\n2. Restart Claude Code\n3. Test manually: `echo '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git reset --hard\"}}' | dcg`\n\n### Hook blocking safe commands\n\n1. Check if there's an edge case not covered\n2. File a GitHub issue\n3. Temporary bypass: `DCG_BYPASS=1` or run command manually\n\n## FAQ\n\n**Q: Why block both `git branch -d` and `git branch -D`?**\n\nLowercase `-d` checks merge state, but still removes the branch name, upstream-tracking configuration, and convenient reflog reference. Those are user-owned state, so every delete form requires explicit approval. Review first with `git branch -vv`, `git branch --merged`, and `git branch --no-merged`. The merge-checked form is its own rule, `core.git:branch-delete`, so `[policy.rules] \"core.git:branch-delete\" = \"ask\"` turns it into an approval prompt while `-D` (`core.git:branch-force-delete`) stays denied.\n\n**Q: Why is `git push --force-with-lease` allowed?**\n\nForce-with-lease refuses to push if the remote has commits you haven't seen, preventing accidental overwrites.\n\n**Q: Why block all `rm -rf` outside temp directories?**\n\nRecursive forced deletion is extremely dangerous. A typo or wrong variable can delete critical files. Temp directories are designed to be ephemeral.\n\n**Q: What if I really need to run a blocked command?**\n\nDCG instructs the agent to ask for permission. Run the command manually in a separate terminal after making a conscious decision.\n\n## Integration with Flywheel\n\n| Tool | Integration |\n|------|-------------|\n| **Claude Code** | Native PreToolUse hook |\n| **Agent Mail** | Agents can report blocked commands to coordinator |\n| **BV** | Flag tasks that repeatedly trigger DCG |\n| **CASS** | Search DCG block patterns across sessions |\n| **RU** | DCG protects agent-sweep from destructive commits |\n","frontmatter":{"name":"dcg","description":"Destructive Command Guard - High-performance Rust hook for Claude Code that blocks dangerous commands before execution. SIMD-accelerated, modular pack system, whitelist-first architecture. Essential safety layer for agent workflows."},"isInternal":false,"tokens":3830,"sizeBytes":17048}],"systemPromptSnippet":"<yakaai_skills repo=\"Dicklesworthstone/destructive_command_guard\">\n<!-- File: AGENTS.md (Tokens: ~12604 | Category: root-instruction) -->\n# AGENTS.md — dcg (Destructive Command Guard)\n\n> Guidelines for AI coding agents working in this Rust codebase.\n\n---\n\n## RULE 0 - THE FUNDAMENTAL OVERRIDE PREROGATIVE\n\nIf I tell you to do something, even if it goes against what follows below, YOU MUST LISTEN TO ME. I AM IN CHARGE, NOT YOU.\n\n---\n\n## RULE 0.5 - SUITE-WIDE RULES LIVE IN /data/projects/AGENTS.md\n\nThe suite-wide rules in **`/data/projects/AGENTS.md`** bind you here too. Read it. Two sections\nare load-bearing for perf work and are NOT duplicated below, so they cannot drift out of sync:\n\n- **`## Named Reward-Hacking Patterns (ALL FORBIDDEN)`** — 12 named patterns, several already\n  observed in this suite: gate self-weakening (and the exact price of a legitimate gate fix),\n  proof-class inflation, golden regeneration reflex, commit-stream pumping, tautological tests,\n  easy-lever cherry-picking, close-pump abuse, scope-splitting, spec-editing as progress,\n  conformance metastasis, dependency smuggling, bench-path hardcoding.\n- **`### Work-Graph Discipline`** — JSONL is truth and `beads.db` is disposable, `br sync\n  --import-only` after every pull, single-writer on graph structure, closure on cited evidence\n  with blocker beads gated on their named probe, `br dep cycles` stays empty.\n\nThe three that most often decide whether a number here is real: a **self-speedup is\nMAINTENANCE, not a win** — a win needs the incumbent live in the SAME invocation; **never\nweaken a gate to land a change**, and if a gate is genuinely defective, meet the evidence\nstandard and publish the win/lose split of what the fix admits; and **reporting a loss is a\nsuccess** — one line, revert, next lever, no retraction narrative.\n\n---\n\n## RULE NUMBER 1: NO FILE DELETION\n\n**YOU ARE NEVER ALLOWED TO DELETE A FILE WITHOUT EXPRESS PERMISSION.** Even a new file that you yourself created, such as a test code file. You have a horrible track record of deleting critically important files or otherwise throwing away tons of expensive work. As a result, you have permanently lost any and all rights to determine that a file or folder should be deleted.\n\n**YOU MUST ALWAYS ASK AND RECEIVE CLEAR, WRITTEN PERMISSION BEFORE EVER DELETING A FILE OR FOLDER OF ANY KIND.**\n\n---\n\n## Irreversible Git & Filesystem Actions — DO NOT EVER BREAK GLASS\n\n> **Note:** This project exists specifically to block these dangerous commands for AI agents. Practice what we preach.\n\n1. **Absolutely forbidden commands:** `git reset --hard`, `git clean -fd`, `rm -rf`, or any command that can delete or overwrite code/data must never be run unless the user explicitly provides the exact command and states, in the same message, that they understand and want the irreversible consequences.\n2. **No guessing:** If there is any uncertainty about what a command might delete or overwrite, stop immediately and ask the user for specific approval. \"I think it's safe\" is never acceptable.\n3. **Safer alternatives first:** When cleanup or rollbacks are needed, request permission to use non-destructive options (`git status`, `git diff`, `git stash`, copying to backups) before ever considering a destructive command.\n4. **Mandatory explicit plan:** Even after explicit user authorization, restate the command verbatim, list exactly what will be affected, and wait for a confirmation that your understanding is correct. Only then may you execute it—if anything remains ambiguous, refuse and escalate.\n5. **Document the confirmation:** When running any approved destructive command, record (in the session notes / final response) the exact user text that authorized it, the command actually run, and the execution time. If that record is absent, the operation did not happen.\n\n---\n\n## Git Branch: ONLY Use `main`, NEVER `master`\n\n**The default branch is `main`. The `master` branch exists only for legacy URL compatibility.**\n\n- **All work happens on `main`** — commits, PRs, feature branches all merge to `main`\n- **Never reference `master` in code or docs** — if you see `master` anywhere, it's a bug that needs fixing\n- **The `master` branch must stay synchronized with `main`** — after pushing to `main`, also push to `master`:\n  ```bash\n  git push origin main:master\n  ```\n\n**Why this matters:** The `dcg update` command and install URLs historically referenced `master`. If `master` falls behind `main`, users get stale code. We had a bug where `master` was **497 commits behind**, causing users to see old installer behavior.\n\n**If you see `master` referenced anywhere:**\n1. Update it to `main`\n2. Ensure `master` is synchronized: `git push origin main:master`\n\n---\n\n## Toolchain: Rust & Cargo\n\nWe only use **Cargo** in this project, NEVER any other package manager.\n\n- **Edition:** Rust 2024 (nightly required — see `rust-toolchain.toml`)\n- **Dependency versions:** Explicit versions for stability\n- **Configuration:** Cargo.toml only (single crate, not a workspace)\n- **Unsafe code:** Forbidden (`#![forbid(unsafe_code)]`)\n\n### Key Dependencies\n\n| Crate | Purpose |\n|-------|---------|\n| `serde` + `serde_json` | JSON parsing for Claude Code hook protocol |\n| `serde_yaml` | External pack YAML parsing |\n| `toml` + `toml_edit` | TOML config parsing with formatting preservation |\n| `fancy-regex` | Advanced regex with lookahead/lookbehind |\n| `regex` | `RegexSet` for heredoc detection |\n| `memchr` | SIMD-accelerated substring search |\n| `aho-corasick` | Multi-pattern string matching for keyword quick-reject |\n| `colored` | Terminal colors with TTY detection |\n| `clap` + `clap_complete` | CLI argument parsing with shell completions |\n| `chrono` | RFC 3339 timestamps |\n| `ast-grep-core` + `ast-grep-language` | AST-based pattern matching for heredoc/inline-script content |\n| `rusqlite` | Bundled upstream SQLite for best-effort telemetry history |\n| `rust-mcp-sdk` | MCP server integration (stdio transport) |\n| `tokio` | Async runtime for MCP server mode |\n| `ratatui` + `comfy-table` + `indicatif` + `console` | TUI/CLI visual polish |\n| `self_update` | Binary self-update from GitHub Releases |\n| `vergen-gix` | Build metadata embedding (build.rs) |\n| `tracing` + `tracing-subscriber` | Structured logging and diagnostics |\n| `sha2` + `hmac` | Hashing and HMAC for allow-once short codes |\n| `flate2` | Gzip compression for history export |\n\n### Release Profile\n\nThe release build optimizes for binary size:\n\n```toml\n[profile.release]\nopt-level = \"z\"     # Optimize for size (lean binary for distribution)\nlto = true          # Link-time optimization\ncodegen-units = 1   # Single codegen unit for better optimization\npanic = \"abort\"     # Smaller binary, no unwinding overhead\nstrip = true        # Remove debug symbols\n```\n\n### Feature Flags\n\n```toml\n[features]\nrayon = [\"dep:rayon\"]           # Rayon data parallelism (optional)\nrich-output = [\"dep:rich_rust\"] # Enable rich_rust for premium terminal output\nlegacy-output = []              # Keep old rendering (placeholder for gradual migration)\n```\n\n---\n\n## Code Editing Discipline\n\n### No Script-Based Changes\n\n**NEVER** run a script that processes/changes code files in this repo. Brittle regex-based transformations create far more problems than they solve.\n\n- **Always make code changes manually**, even when there are many instances\n- For many simple changes: use parallel subagents\n- For subtle/complex changes: do them methodically yourself\n\n### No File Proliferation\n\nIf you want to change something or add a feature, **revise existing code files in place**.\n\n**NEVER** create variations like:\n- `mainV2.rs`\n- `main_improved.rs`\n- `main_enhanced.rs`\n\nNew files are reserved for **genuinely new functionality** that makes zero sense to include in any existing file. The bar for creating new files is **incredibly high**.\n\n---\n\n## Backwards Compatibility\n\nWe do not care about backwards compatibility—we're in early development with no users. We want to do things the **RIGHT** way with **NO TECH DEBT**.\n\n- Never create \"compatibility shims\"\n- Never create wrapper functions for deprecated APIs\n- Just fix the code directly\n\n---\n\n## Compiler Checks (CRITICAL)\n\n**After any substantive code changes, you MUST verify no errors were introduced:**\n\n```bash\n# Check for compiler errors and warnings\ncargo check --all-targets\n\n# Check for clippy lints (pedantic + nursery are enabled)\ncargo clippy --all-targets -- -D warnings\n\n# Verify formatting\ncargo fmt --check\n```\n\nIf you see errors, **carefully understand and resolve each issue**. Read sufficient context to fix them the RIGHT way.\n\n---\n\n## Windows Support (native, `x86_64-pc-windows-msvc`)\n\ndcg ships a **native Windows** binary (built/tested on `windows-latest` with the\nnightly toolchain) and a `check (windows)` CI job. When touching anything\nplatform-sensitive, follow these conventions:\n\n- **Separate command-pattern DATA from dcg's own paths.** Destructive-command\n  patterns (`rm -rf /`, `normalize.rs` stripping `/usr/bin/git`, `/etc`, `/tmp`)\n  are DATA about Unix commands and must STAY — Windows users still run git-bash.\n  Only dcg's *own* config/state paths get Windows-ified (resolve via the `dirs`\n  crate; the system layer is `%ProgramData%\\dcg`, helper `config::system_config_dir()`).\n- **`.exe` suffix.** When constructing a path to the dcg binary, use\n  `env!(\"CARGO_BIN_EXE_dcg\")` / `assert_cmd::cargo::cargo_bin(\"dcg\")` in tests, or\n  `std::env::consts::EXE_SUFFIX` in `src`. **Never** a bare `push(\"dcg\")` — the\n  Windows CI job greps for it and fails. Resolve per-user paths only through\n  `config::home_dir()` / `user_config_dir()` / `user_data_dir()` /\n  `user_data_local_dir()` / `user_cache_dir()` — never `dirs::*` (clippy's\n  `disallowed-methods` rejects it) and never raw `HOME` (unset on Windows).\n  `dirs` resolves the Windows profile via the known-folder API and ignores\n  `USERPROFILE`, so a sandboxed Windows test run used to rewrite the operator's\n  real `~\\.claude\\settings.json` and allow-once store (bd-b2b1). Set\n  `USERPROFILE`/`TEMP`/`TMP` alongside `HOME` in test isolation; `USERPROFILE`\n  alone now relocates `%APPDATA%`/`%LOCALAPPDATA%` too when those are unset.\n- **Verify Windows branches from Linux** without a Windows box: `mingw` + the\n  `x86_64-pc-windows-gnu` target are installed, so\n  `cargo check --target x86_64-pc-windows-gnu --lib` (or `--bin dcg` / `--tests`)\n  compile-checks every `#[cfg(windows)]` path. When `pwsh` is installed, use it\n  to run the PowerShell installer/test scripts; otherwise run those gates on the\n  native Windows release host.\n- **Windows packs.** `src/packs/windows/` holds the native-Windows packs\n  (`windows.filesystem`/`windows.system` default-ON on Windows, `windows.misc`/\n  `windows.powershell` opt-in). Patterns use inline `(?i)`; keyword arrays\n  may retain conventional casing variants for readability, but keyword\n  quick-rejection is ASCII case-insensitive so mixed-case Windows spellings\n  cannot skip the regex stage (see `src/packs/windows/mod.rs`). See\n  [`docs/windows.md`](docs/windows.md).\n- **The `careful_company_running_windows` preset.**\n  `src/packs/careful_company_running_windows/` holds six opt-in sub-packs\n  covering **outbound communication and data egress** (email, chat/webhooks,\n  HTTP upload, file transfer, tunnels) plus **tampering with the controls that\n  supervise the agent** (Defender/firewall/EDR, audit logs, and dcg's own\n  bypass/uninstall). It is the only pack ID with *curated transitive\n  membership*: enabling it also enables the pinned\n  `CAREFUL_COMPANY_PRESET_MEMBERS` list in `src/packs/mod.rs`\n  (`windows.*`, `database.*`, `storage.*`, `remote.*`, `backup.*`, `secrets.*`,\n  `cloud.*`). That list is deliberately explicit — do **not** convert it to\n  prefix matching, or future packs will join a security posture silently.\n  Two invariants to preserve when touching this area:\n  - **Tier order.** `windows.*` is tier 11 and the preset is tier 12, so\n    Windows packs keep claiming attribution for commands both match. Rule ids\n    (`pack_id:pattern_name`) are what allowlists key on, so reordering these\n    silently invalidates existing `windows.*` allowlist entries.\n  - **The `hfdt` trust boundary.** While any preset sub-pack is enabled,\n    `src/evaluator.rs` allows a command whose executable is `hfdt` **before any\n    pack runs** — including `core.*`. It is structural (whole-segment\n    executable match, no chains/redirection/substitution), but it does mean\n    enabling the preset *reduces* coverage for that one executable. Keep it\n    documented in `README.md` and `docs/careful-company-windows.md`.\n\n---\n\n## Testing\n\n### Testing Policy\n\nEvery module includes inline `#[cfg(test)]` unit tests alongside the implementation. Tests must cover:\n- Happy path\n- Edge cases (empty input, max values, boundary conditions)\n- Error conditions\n\nEnd-to-end tests live in `scripts/e2e_test.sh`.\n\n### Unit Tests\n\nThe test suite includes 80+ tests covering all functionality:\n\n```bash\n# Run all tests\ncargo test\n\n# Run with output\ncargo test -- --nocapture\n\n# Run specific test module\ncargo test normalize_command_tests\ncargo test safe_pattern_tests\ncargo test destructive_pattern_tests\n```\n\n**Prefer `cargo nextest run` when touching anything that mutates the\nenvironment.** CI runs `cargo nextest run --profile ci`, and nextest gives each\ntest its own process — verified by observation, not assumption: sampling a\n`--lib` run showed up to 128 concurrent per-test `--exact` invocations. That\nprocess isolation is what makes the `unsafe { env::set_var }` sites in test code\nsound, because it removes the concurrent reader.\n\nUnder plain `cargo test` every test shares one process and runs on threads, and\nthe crate has ~109 `env::var` call sites against ~22 `EnvVarGuard` uses, none of\nthe readers taking the lock — plus native code (bundled SQLite reading `TMPDIR`)\nthat cannot take a Rust lock even in principle. So `cargo test` is the\nconfiguration the single crate-wide `ENV_LOCK` cannot actually make safe (#445).\nNo failure has been observed from this; the race is available, not active.\n\n`cargo test` stays documented above because filter and `--ignored` syntax differ\nbetween the two runners (nextest wants `--run-ignored`), and several workflows in\nthis file depend on the `cargo test` spelling.\n\n### The Three Release-Blocking E2E Suites (read before touching perf or protocols)\n\n`cargo test` cannot catch the failure modes that have actually broken users.\nThree real-binary, no-mock suites exist specifically to close those gaps. All\nthree must be green before any release.\n\n| Suite | Catches | Why unit tests can't |\n|-------|---------|----------------------|\n| `scripts/e2e_harness_matrix.sh` | Wire/bridge breakage for **every** agent (Claude Code, Codex, Gemini, Copilot, Hermes, Grok, agy, Crush, OMP) | Unit tests call Rust functions; harnesses parse **bytes**. Asserts decision field + exit code + stdout/stderr separation per protocol against the real binary. |\n| `scripts/perf_baseline.py --assert-budget-ms` | **#245**: evaluator cost silently eating the fixed hook deadline | The perf job is a *relative* ratchet — a uniform slowdown just gets re-baselined. This gate asserts paired `full_eval − DCG_BYPASS` p95 against the **shipped** `HOOK_EVALUATION_BUDGET_MS` with a hermetic HOME and scrubbed `DCG_*`; raw process latency remains separate evidence. |\n| `scripts/e2e_fleet_install.sh` | Published artifact missing/unrunnable per platform; installer picking the wrong triple; checksum/signature verification silently skipped; hook config non-idempotent | Nothing in-tree proves the **public download path** works on real Linux/macOS/Windows hardware. |\n\n```bash\n# Protocol/bridge conformance for all 8 harnesses (needs a release binary + jq)\n./scripts/e2e_harness_matrix.sh --binary target/release/dcg\n\n# Absolute evaluator-cost gate — the #245 guard. Budget MUST come from src/perf.rs.\nBUDGET_MS=$(sed -nE 's/^pub const HOOK_EVALUATION_BUDGET_MS: u64 = ([0-9_]+);$/\\1/p' src/perf.rs | tr -d '_')\npython3 scripts/perf_baseline.py --bin target/release/dcg --skip-trace \\\n  --assert-budget-ms \"$BUDGET_MS\" --assert-margin-pct 50\n\n# Real installs from the PUBLIC release on every DSR host\n./scripts/e2e_fleet_install.sh --version vX.Y.Z          # whole fleet\n./scripts/e2e_fleet_install.sh --version vX.Y.Z --local-only\n```\n\nRules:\n- **Scrub ambient `DCG_*` before measuring anything.** Operators bitten by #245\n  export `DCG_HOOK_TIMEOUT_MS=5000` (an agent `settings.json` `env` block puts\n  it in every child process), so an un-scrubbed suite measures the *workaround*\n  and passes on exactly the machines that need protecting. `env -i` covers the\n  hook calls; the installer cannot use it (it needs the host PATH for\n  `curl`/`tar`/`xz`/`minisign`), so the probes also `unset` every `DCG_*` up\n  front. Assert `general.hook_timeout_source` too — a bare `>= 1000` check\n  cannot tell the shipped default from an inherited 5000.\n- **Set `DCG_SELF_HEAL_HOOK=0` before the installer runs, not after.** dcg\n  repairs a missing/stale hook entry whenever it runs in hook mode, and\n  released binaries up to v0.14.4 resolve the Windows settings path via the\n  Win32 known-folder API, which `USERPROFILE` cannot redirect — so a late\n  disable can rewrite a real machine's agent config. Current source honors\n  `USERPROFILE` (bd-b2b1), but the installer may still fetch an older release.\n- **Never hard-code the budget in `.github/workflows/ci.yml`.** It is grepped\n  out of `HOOK_EVALUATION_BUDGET_MS`; `perf::tests::ci_enforces_absolute_latency_gate_against_shipped_budget`\n  fails if that wiring is removed or the margin is loosened past 60%.\n- **Treat the JSON as the certificate, not stderr.** Gate mode records its\n  supplied/shipped/effective budgets, margin, derived limit, every per-case\n  verdict, 95/95 binomial tail-tolerance result, violations, and overall\n  PASS/FAIL in `latency_gate`; CI retains that artifact even when the gate\n  fails. Gate mode requires at least 59 samples; CI uses 100 and permits at\n  most one over-limit sample per case. When using `--output` in gate mode,\n  place it outside the repository; the harness rejects in-tree output so its\n  own certificate cannot dirty the source snapshot it claims to measure.\n- **Bind the binary to the checkout.** Gate mode requires a clean checkout and\n  exact equality between the binary's embedded `git describe --tags --dirty`\n  value and the repository's value. CI uses a full tag history so a shallow\n  clone cannot turn this proof into an unknown result.\n- Measure dcg's own cost as `full_eval − DCG_BYPASS`, never raw wall-clock:\n  process spawn (≈940ms under Windows PowerShell) sits **outside** the\n  evaluation deadline and would otherwise produce false alarms. For host\n  safety this certificate sets `DCG_SELF_HEAL_HOOK=0`, records that exclusion,\n  and therefore does not claim to measure self-healing work. Capture and\n  validate every timed child's actual wire decision after stopping its timer;\n  before/after semantic controls alone cannot catch intermittent fail-open\n  behavior inside the sample window.\n- The fleet suite installs into a scratch prefix with an isolated `HOME` and\n  `--no-configure`; it never touches a host's real agent hook config.\n- A probe that dies partway must FAIL, not pass: every probe emits\n  `probe_complete` and the runner asserts the full expected case set.\n\n### End-to-End Testing\n\n```bash\n# Run the E2E test script (needs bash >= 4; macOS /bin/bash 3.2 breaks the summary)\n./scripts/e2e_test.sh\n\n# Or test manually\necho '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git reset --hard\"}}' | cargo run --release\n# Should output JSON denial\n\necho '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git status\"}}' | cargo run --release\n# Should output nothing (allowed)\n```\n\n### Test Categories\n\n| Module | Tests | Purpose |\n|--------|-------|---------|\n| `normalize_command_tests` | 8 | Path stripping for git/rm binaries |\n| `quick_reject_tests` | 5 | Fast-path filtering for non-git/rm commands |\n| `safe_pattern_tests` | 16 | Whitelist accuracy |\n| `destructive_pattern_tests` | 20 | Blacklist coverage |\n| `input_parsing_tests` | 8 | JSON parsing robustness |\n| `deny_output_tests` | 2 | Output format validation |\n| `integration_tests` | 4 | End-to-end pipeline |\n| `optimization_tests` | 9 | Performance paths |\n| `edge_case_tests` | 24 | Real-world edge cases |\n\n---\n\n## Third-Party Library Usage\n\nIf you aren't 100% sure how to use a third-party library, **SEARCH ONLINE** to find the latest documentation and current best practices.\n\n---\n\n## dcg (Destructive Command Guard) — This Project\n\n**This is the project you're working on.** dcg is a high-performance Claude Code hook that blocks destructive commands before they execute. It protects against dangerous git commands, filesystem operations, database queries, container commands, and more through a modular pack system.\n\n### What It Does\n\nGuards AI coding agents from executing destructive commands by intercepting Claude Code's `PreToolUse` hook protocol, evaluating commands against safe/destructive pattern lists, and denying dangerous operations with structured JSON output including remediation suggestions.\n\n### Architecture\n\n```\nJSON Input → Parse → Quick Reject (memchr) → Normalize → Safe Patterns → Destructive Patterns → Default Allow\n```\n\n### Key Files\n\n| File | Purpose |\n|------|---------|\n| `src/main.rs` | Entry point, hook I/O, CLI dispatch |\n| `src/evaluator.rs` | Pattern matching engine (safe + destructive evaluation) |\n| `src/hook.rs` | Claude Code PreToolUse hook protocol handling |\n| `src/normalize.rs` | Command normalization (path stripping, alias expansion) |\n| `src/heredoc.rs` | Heredoc and inline script extraction |\n| `src/ast_matcher.rs` | AST-based pattern matching for embedded code |\n| `src/config.rs` | Configuration loading (TOML, allowlists, pack enable/disable) |\n| `src/allowlist.rs` | Allowlist management (project, user, system scopes) |\n| `src/cli.rs` | CLI commands (explain, scan, packs, allowlist, etc.) |\n| `src/scan.rs` | Codebase scanning for destructive patterns |\n| `src/context.rs` | Contextual analysis for pattern matching |\n| `src/confidence.rs` | Match confidence scoring |\n| `src/error_codes.rs` | Standardized DCG-XXXX error codes |\n| `src/exit_codes.rs` | Process exit code definitions |\n| `src/packs/` | Modular pattern pack system (core + extensions) |\n| `src/output/` | Output formatting (JSON, colorful stderr) |\n| `src/highlight.rs` | Syntax highlighting for command display |\n| `src/logging.rs` | Tracing/logging configuration |\n| `src/perf.rs` | Performance budgets and benchmarks |\n| `src/simulate.rs` | Command simulation and dry-run support |\n| `src/mcp.rs` | MCP server integration |\n| `src/agent.rs` | Agent detection and identification |\n| `src/interactive.rs` | Interactive mode |\n| `src/git.rs` | Git-specific command analysis |\n| `src/history/` | Decision history and telemetry |\n| `src/sarif.rs` | SARIF output format for scan results |\n| `src/pending_exceptions.rs` | Pending exception management |\n| `src/lib.rs` | Library re-exports |\n| `Cargo.toml` | Dependencies and release optimizations |\n| `build.rs` | Build script for version metadata (vergen) |\n| `rust-toolchain.toml` | Nightly toolchain requirement |\n| `scripts/e2e_test.sh` | End-to-end test script (hundreds of command scenarios) |\n\n### Output Style\n\nThis tool has two output modes:\n\n- **JSON to stdout:** For Claude Code hook protocol (`hookSpecificOutput` with `permissionDecision: \"deny\"`)\n- **Colorful warning to stderr:** For human visibility when commands are blocked\n\nOutput behavior:\n- **Deny:** Colorful warning to stderr + JSON to stdout\n- **Allow:** No output (silent exit)\n- **--version/-V:** Version info with build metadata to stderr\n- **--help/-h:** Usage information to stderr\n\nColors are automatically disabled when stderr is not a TTY (e.g., piped to file).\n\n### Pattern System\n\n- **34 safe patterns** (whitelist, checked first)\n- **16 destructive patterns** (blacklist, checked second)\n- **Default allow** for unmatched commands\n\n### Adding New Patterns\n\n1. Identify the command to block/allow\n2. Write a regex using `fancy-regex` syntax (supports lookahead/lookbehind)\n3. Add to `SAFE_PATTERNS` or `DESTRUCTIVE_PATTERNS` using the macros:\n\n```rust\n// Safe pattern (whitelist)\npattern!(\"pattern-name\", r\"regex-here\")\n\n// Destructive pattern (blacklist)\ndestructive!(\n    r\"regex-here\",\n    \"Human-readable reason for blocking\"\n)\n```\n\n4. Add tests for all variants\n5. Run `cargo test` and `./scripts/e2e_test.sh`\n\n### Performance Requirements\n\nEvery Bash command passes through this hook. Performance is critical:\n\n- Quick rejection filter eliminates 99%+ of commands before regex\n- Lazy-initialized static regex patterns (compiled once, reused)\n- Sub-millisecond execution for typical commands\n- Zero allocations on the hot path for safe commands\n\n### Heredoc Detection Notes\n\n- **Rule IDs**: Heredoc patterns use stable IDs like `heredoc.python.shutil_rmtree` for allowlisting.\n- **Bounded failure**: Heredoc parse/AST failures use the configured bounded\n  fallback; disabling fallback blocks. Absolute hook-deadline exhaustion and\n  incomplete nested evaluation return `Indeterminate`, never `Allow`.\n- **Tests**: Prefer targeted tests in `src/ast_matcher.rs` and `src/heredoc.rs`.\n  - `cargo test ast_matcher`\n  - `cargo test heredoc`\n  - Add positive and negative fixtures for each new pattern.\n\n---\n\n<!-- dcg-machine-readable-v1 -->\n\n## DCG Hook Protocol (Machine-Readable Reference)\n\n> This section provides structured documentation for AI agents integrating with dcg.\n\n### JSON Input Format\n\ndcg reads from stdin in Claude Code's `PreToolUse` hook format:\n\n```json\n{\n  \"tool_name\": \"Bash\",\n  \"tool_input\": {\n    \"command\": \"git reset --hard HEAD~5\"\n  }\n}\n```\n\n**Required fields:**\n- `tool_name`: Must be `\"Bash\"` for dcg to process (other tools are ignored)\n- `tool_input.command`: The shell command string to evaluate\n\n### JSON Output Format (Denial)\n\nWhen a command is blocked, dcg outputs JSON to stdout:\n\n```json\n{\n  \"hookSpecificOutput\": {\n    \"hookEventName\": \"PreToolUse\",\n    \"permissionDecision\": \"deny\",\n    \"permissionDecisionReason\": \"BLOCKED by dcg\\n\\nTip: dcg explain \\\"git reset --hard HEAD~5\\\"\\n\\nReason: git reset --hard destroys uncommitted changes\\n\\nExplanation: Rewrites history and discards uncommitted changes.\\n\\nRule: core.git:reset-hard\\n\\nIf this operation is truly needed, ask the user for explicit permission and have them run the command manually.\",\n    \"ruleId\": \"core.git:reset-hard\",\n    \"packId\": \"core.git\",\n    \"severity\": \"critical\",\n    \"confidence\": 1.0,\n    \"allowOnceCode\": \"a1b2c3\",\n    \"allowOnceFullHash\": \"sha256:abc123...\",\n    \"remediation\": {\n      \"safeAlternative\": \"git stash\",\n      \"explanation\": \"Use git stash to save your changes first.\",\n      \"allowOnceCommand\": \"dcg allow-once a1b2c3\"\n    }\n  }\n}\n```\n\n**Key fields for agent parsing:**\n| Field | Type | Description |\n|-------|------|-------------|\n| `permissionDecision` | `\"allow\"` \\| `\"deny\"` | The decision |\n| `ruleId` | `string` | Stable pattern ID (e.g., `\"core.git:reset-hard\"`) for allowlisting |\n| `packId` | `string` | Pack that matched (e.g., `\"core.git\"`) |\n| `severity` | `string` | `\"critical\"`, `\"high\"`, `\"medium\"`, or `\"low\"` |\n| `confidence` | `number?` | Match confidence 0.0–1.0 (two decimals) from `crate::confidence::compute_match_confidence`, the score the `[confidence]` warn threshold reads: 1.0 when the matched span is executed (command word, wrapper-invoked command, redirection target), lower when it sits in quoted data, a comment, or an inert command's arguments. Omitted when the match has no source span (e.g. inside `$(…)`, structural verdicts). |\n| `allowOnceCode` | `string` | Short code for `dcg allow-once` |\n| `remediation.safeAlternative` | `string?` | Suggested safe command |\n\n### JSON Output Format (Allow)\n\nWhen a command is allowed: **no output** (silent exit 0).\n\n---\n\n## Exit Codes Reference\n\n| Code | Meaning | Agent Action |\n|------|---------|--------------|\n| `0` | Command allowed OR protocol JSON denial was emitted | Parse stdout; if empty, command was allowed |\n| `1` | Parse error or invalid input | Retry with corrected input |\n| `2` | Configuration error | Check config syntax and stderr diagnostics |\n\n**Detection logic for agents:**\n```bash\noutput=$(echo \"$hook_input\" | dcg 2>/dev/null)\nif [ -z \"$output\" ]; then\n  echo \"ALLOWED\"\nelse\n  echo \"DENIED: $output\"\nfi\n```\n\nCodex CLI uses a stricter hook parser: blocked commands return a minimal\n`hookSpecificOutput` denial on stdout with exit code 0. See\n[`docs/codex-integration.md`](docs/codex-integration.md) for the Codex-specific\nprotocol notes.\n\n---\n\n## Error Codes Reference\n\nDCG uses standardized error codes in the format `DCG-XXXX` for machine-parseable error handling.\n\n### Error Categories\n\n| Range | Category | Description |\n|-------|----------|-------------|\n| DCG-1xxx | `pattern_match` | Pattern matching and evaluation errors |\n| DCG-2xxx | `configuration` | Configuration loading and parsing errors |\n| DCG-3xxx | `runtime` | Runtime and execution errors |\n| DCG-4xxx | `external` | External integration errors |\n\n### Common Error Codes\n\n| Code | Description | Typical Cause |\n|------|-------------|---------------|\n| `DCG-1001` | Pattern compilation failed | Invalid regex syntax in pattern |\n| `DCG-1002` | Pattern match timeout | Complex pattern taking too long |\n| `DCG-2001` | Config file not found | Missing configuration file |\n| `DCG-2002` | Config parse error | Invalid TOML/JSON syntax |\n| `DCG-2004` | Allowlist load error | Invalid allowlist file |\n| `DCG-3001` | JSON parse error | Malformed JSON input |\n| `DCG-3002` | IO error | File read/write failure |\n| `DCG-4001` | External pack load failed | Invalid external pack YAML |\n\n### Error JSON Structure\n\nWhen errors are returned in JSON format, they follow this structure:\n\n```json\n{\n  \"error\": {\n    \"code\": \"DCG-3001\",\n    \"category\": \"runtime\",\n    \"message\": \"JSON parse error: unexpected token at position 15\",\n    \"context\": {\n      \"position\": 15,\n      \"input_preview\": \"{ \\\"tool_name\\\": ...\"\n    }\n  }\n}\n```\n\n**Fields:**\n- `code`: Stable error code for programmatic handling\n- `category`: Error category (`pattern_match`, `configuration`, `runtime`, `external`)\n- `message`: Human-readable error description\n- `context`: Additional details (optional, varies by error type)\n\n---\n\n## Allowlist & Bypass Instructions\n\n### Temporary Bypass (24-hour allow-once)\n\nWhen a command is blocked, the output includes an `allowOnceCode`. Use it:\n\n```bash\ndcg allow-once <code>\n```\n\nThis allows the specific command for 24 hours in the current directory scope.\n\n### Permanent Allowlist (by rule ID)\n\nAdd a rule to the project allowlist:\n\n```bash\ndcg allowlist add <ruleId> --project\n# Example: dcg allowlist add core.git:reset-hard --project\n```\n\nAllowlist files (in priority order):\n1. `.dcg/allowlist.toml` (project)\n2. `~/.config/dcg/allowlist.toml` (user)\n3. `/etc/dcg/allowlist.toml` (system)\n\n### Bypass Environment Variable\n\nFor emergency bypass (use sparingly):\n\n```bash\nDCG_BYPASS=1 <command>\n```\n\n**Warning:** This disables all protection. Log and justify any usage.\n\n---\n\n## Pattern Quick Reference\n\n### Core Git Patterns (Always Enabled)\n\n| Pattern ID | Blocks | Severity |\n|------------|--------|----------|\n| `core.git:reset-hard` | `git reset --hard` | Critical |\n| `core.git:reset-merge` | `git reset --merge` | High |\n| `core.git:checkout-discard` | `git checkout -- <file>` | High |\n| `core.git:restore-discard` | `git restore <file>` (without `--staged`) | High |\n| `core.git:clean-force` | `git clean -f`, `git clean -fd` | High |\n| `core.git:force-push` | `git push --force`, `git push -f` | High |\n| `core.git:branch-delete` | `git branch -d`, `--delete` (merge-checked, no force) | High |\n| `core.git:branch-force-delete` | `git branch -D`, `--delete --force`, `-f`, `-M`, `-C` (a policy/allowlist entry for it also covers `branch-delete` unless that rule has its own) | High |\n| `core.git:stash-drop` | `git stash drop` — Medium, so the default policy **warns and lets it run** (the dropped stash stays recoverable via `git fsck` until gc) | Medium |\n| `core.git:stash-clear` | `git stash clear` | Critical |\n\n### Core Filesystem Patterns (Always Enabled)\n\n| Pattern ID | Blocks | Severity |\n|------------|--------|----------|\n| `core.filesystem:rm-rf-root` | `rm -rf /`, `rm -rf ~` | Critical |\n| `core.filesystem:rm-rf-general` | `rm -rf` outside temp dirs | High |\n\n### Safe Patterns (Whitelist - Always Allowed)\n\n| Pattern | Command | Why Safe |\n|---------|---------|----------|\n| `git-checkout-branch` | `git checkout -b <branch>` | Creates new branch |\n| `git-checkout-orphan` | `git checkout --orphan <branch>` | Creates orphan branch |\n| `git-restore-staged` | `git restore --staged <file>` | Only unstages, doesn't discard |\n| `git-clean-dry-run` | `git clean -n`, `git clean --dry-run` | Preview only |\n| `rm-tmp` | `rm -rf /tmp/*`, `/var/tmp/*` | Temp directory cleanup |\n\n### Pack Enable/Disable Examples\n\n```toml\n# ~/.config/dcg/config.toml\n[packs]\nenabled = [\n    \"database.postgresql\",    # Blocks DROP TABLE, TRUNCATE\n    \"kubernetes.kubectl\",     # Blocks kubectl delete namespace\n    \"cloud.aws\",              # Blocks aws ec2 terminate-instances\n]\n\ndisabled = [\n    \"containers.docker\",      # Disable Docker protection\n]\n```\n\nList all packs: `dcg packs --verbose`\n\n---\n\n## CLI Quick Reference for Agents\n\n| Command | Purpose |\n|---------|---------|\n| `dcg explain \"<command>\"` | Detailed trace of why command is blocked/allowed |\n| `dcg allow-once <code>` | Allow a blocked command for 24 hours |\n| `dcg allowlist add <ruleId> --project` | Permanently allow a rule |\n| `dcg packs` | List enabled packs |\n| `dcg packs --verbose` | List all packs with pattern counts |\n| `dcg scan .` | Scan codebase for destructive patterns |\n| `dcg --version` | Show version and build info |\n\n---\n\n## Agent Integration Checklist\n\nWhen integrating with dcg, ensure your agent:\n\n- [ ] Parses stdout for JSON denial responses\n- [ ] Handles empty stdout as \"command allowed\"\n- [ ] Uses `ruleId` for stable allowlisting (not pattern text)\n- [ ] Displays `remediation.safeAlternative` to users when available\n- [ ] Respects `severity` for prioritization (critical > high > medium > low)\n- [ ] Uses `dcg explain` before asking users to bypass\n\n---\n\n## JSON Schema Reference\n\nFormal JSON Schema definitions (Draft 2020-12) for all dcg output formats are available in `docs/json-schema/`:\n\n| Schema | Purpose |\n|--------|---------|\n| [`hook-output.json`](docs/json-schema/hook-output.json) | PreToolUse hook denial response format |\n| [`scan-results.json`](docs/json-schema/scan-results.json) | `dcg scan` command output format |\n| [`stats-output.json`](docs/json-schema/stats-output.json) | `dcg stats` command output format |\n| [`error.json`](docs/json-schema/error.json) | Error response formats for various commands |\n\nUse these schemas for:\n- Validating dcg output in automated pipelines\n- Generating type-safe client code\n- Understanding the complete output contract\n\n<!-- end-dcg-machine-readable -->\n\n---\n\n## CI/CD Pipeline\n\n### Jobs Overview\n\n| Job | Trigger | Purpose | Blocking |\n|-----|---------|---------|----------|\n| `check` | PR, push | Format, clippy, UBS, tests | Yes |\n| `coverage` | PR, push | Coverage thresholds | Yes |\n| `memory-tests` | PR, push | Memory leak detection | Yes |\n| `benchmarks` | push to main | Performance budgets | Warn only |\n| `e2e` | PR, push | End-to-end shell tests | Yes |\n| `scan-regression` | PR, push | Scan output stability | Yes |\n| `perf-regression` | PR, push | Process-per-invocation perf | Yes |\n\n### Check Job\n\nRuns format, clippy, UBS static analysis, and unit tests. Includes:\n- `cargo fmt --check` - Code formatting\n- `cargo clippy --all-targets -- -D warnings` - Lints (pedantic + nursery enabled)\n- UBS analysis on changed Rust files (warning-only, non-blocking)\n- `cargo nextest run` - Full test suite with JUnit XML report\n\n### Coverage Job\n\nRuns `cargo llvm-cov` and enforces the thresholds configured in\n`.github/workflows/ci.yml` (`OVERALL_MIN`, `EVALUATOR_MIN`, `HOOK_MIN`).\nThese are enforced gates, not aspirational targets:\n- **Overall:** >= 70%\n- **src/evaluator.rs:** >= 65%\n- **src/hook.rs:** >= 70%\n\nIf CI thresholds change, update this section in the same change. The\n`coverage_threshold_docs` test checks that these documented values stay in sync\nwith the workflow.\n\nCoverage is uploaded to Codecov for trend tracking. Dashboard: https://codecov.io/gh/Dicklesworthstone/destructive_command_guard\n\n### Memory Tests Job\n\nRuns dedicated memory leak tests with:\n- `--test-threads=1` for accurate measurements\n- Release mode for realistic performance\n- 1-2MB growth budgets per test\n\nTests include: hook input parsing, pattern evaluation, heredoc extraction, file extractors, full pipeline, and a self-test that verifies the framework catches leaks.\n\n### Benchmarks Job\n\nRuns on push to main only (benchmarks are noisy on PRs). Checks performance budgets from `src/perf.rs`:\n- Quick reject: < 50us panic\n- Fast path: < 500us panic\n- Pattern match: < 1ms panic\n- Heredoc extract: < 2ms panic\n- Full heredoc pipeline: < 20ms panic\n- Hook evaluation deadline: 1000ms (exhaustion is indeterminate, never a silent allow)\n\n### UBS Static Analysis\n\nUltimate Bug Scanner runs on changed Rust files. Currently warning-only (non-blocking) to tune for false positives. Configuration in `.ubsignore` excludes test/bench/fuzz directories.\n\n### Dependabot\n\nAutomated dependency updates configured in `.github/dependabot.yml`:\n- **Cargo dependencies:** Weekly (Monday 9am EST), 5 PR limit\n- **GitHub Actions:** Weekly (Monday 9am EST), 3 PR limit\n- **Grouping:** Minor/patch updates grouped; serde updates separate (more careful review)\n\n### Debugging CI Failures\n\n#### Coverage Threshold Failure\n1. Check which file(s) dropped below threshold in CI output\n2. Run `cargo llvm-cov --html` locally to see uncovered lines\n3. Add tests for uncovered code paths\n4. Download `coverage-report` artifact for full details\n\n#### Memory Test Failure\n1. Download `memory-test-output` artifact\n2. Check which test failed and growth amount\n3. Run locally: `cargo test --test memory_tests --release -- --nocapture --test-threads=1`\n4. Profile with valgrind if needed\n\n#### UBS Warnings\n1. Check ubs-output.log in CI summary\n2. Review flagged issues - may be false positives\n3. If valid issues, fix them; if false positives, add to `.ubsignore`\n\n#### E2E Test Failure\n1. Download `e2e-artifacts` artifact\n2. Check `e2e_output.json` for failing test details\n3. Run locally: `./scripts/e2e_test.sh --verbose`\n4. The step summary shows the first failure with output\n\n#### Benchmark Regression\n1. Download `benchmark-results` artifact\n2. Compare against budgets in `src/perf.rs`\n3. Profile locally with `cargo bench --bench heredoc_perf`\n4. Check for algorithmic regressions in hot path\n\n---\n\n## Release Process\n\nWhen fixes are ready for release, follow this process:\n\nThe steps below describe the normal GitHub Actions path. If Actions cannot run\nor a native Windows artifact must be built locally, the more detailed\n**Local/DSR Release and Windows Deployment Runbook** near the end of this file\nis authoritative.\n\n### 1. Verify CI Passes Locally\n\n```bash\ncargo fmt --check\ncargo check --all-targets\ncargo clippy --all-targets -- -D warnings\ncargo test\n```\n\n### 2. Commit Changes\n\n```bash\ngit add -A\ngit commit -m \"fix: description of fixes\n\n- List specific fixes\n- Include any breaking changes\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\"\n```\n\n### 3. Bump Version (if needed)\n\nThe version in `Cargo.toml` determines the release tag. A version may be reused\nafter a failed *local, pre-tag* attempt. Once its tag has been pushed or a\nrelease has been published, treat that version as immutable and bump to a new\npatch version instead of moving the tag or replacing signed assets.\n\n- **Patch** (0.2.10 -> 0.2.11): Bug fixes, no new features\n- **Minor** (0.2.x -> 0.3.0): New features, backward compatible\n- **Major** (0.x -> 1.0): Breaking changes\n\n### 4. Push and Trigger Release\n\n```bash\ngit push origin main\ngit push origin main:master  # Keep master in sync\n```\n\nThe `release-automation.yml` workflow will:\n1. Detect version change in `Cargo.toml`\n2. Create an annotated git tag (e.g., `v0.2.13`)\n3. Push the tag, which triggers `dist.yml`\n\nThe `dist.yml` workflow will:\n1. Run tests and clippy\n2. Build binaries for all platforms (Linux x86/ARM, macOS Intel/Apple Silicon, Windows)\n3. Create `.tar.xz` archives with SHA256 checksums\n4. Sign artifacts with Sigstore (cosign) - creates `.sigstore.json` bundles\n5. Upload everything to GitHub Releases\n\n### 5. Verify Release\n\n```bash\ngh release list --limit 5\ngh release view v0.2.13  # Check assets were uploaded\n```\n\nExpected assets per release:\n- `dcg-{target}.tar.xz` (Unix) or `.zip` (Windows) - Binary archive\n- `<archive>.sha256` - Mandatory per-artifact checksum\n- `<archive>.sigstore.json` - Sigstore signature bundle\n- `install.sh`, `install.ps1`, and their checksum/Sigstore sidecars\n- Manual DSR releases additionally include minisign signatures, SLSA\n  provenance, a build manifest, and the pinned public verification keys\n\n### Troubleshooting Failed Releases\n\nIf CI fails:\n1. Check workflow run: `gh run list --workflow=dist.yml --limit=5`\n2. View failed job: `gh run view <run-id>`\n3. Fix issues locally, commit, and push again\n4. If no public tag or release exists yet, retry the same version; otherwise\n   create a new patch release. Never force-move a public release tag.\n\nCommon failures:\n- **Clippy errors**: Fix lints, ensure `cargo clippy -- -D warnings` passes\n- **Test failures**: Run `cargo test` to reproduce\n- **Format errors**: Run `cargo fmt` to fix\n\n---\n\n## MCP Agent Mail — Multi-Agent Coordination\n\nA mail-like layer that lets coding agents coordinate asynchronously via MCP tools and resources. Provides identities, inbox/outbox, searchable threads, and advisory file reservations with human-auditable artifacts in Git.\n\n### Why It's Useful\n\n- **Prevents conflicts:** Explicit file reservations (leases) for files/globs\n- **Token-efficient:** Messages stored in per-project archive, not in context\n- **Quick reads:** `resource://inbox/...`, `resource://thread/...`\n\n### Same Repository Workflow\n\n1. **Register identity:**\n   ```\n   ensure_project(project_key=<abs-path>)\n   register_agent(project_key, program, model)\n   ```\n\n2. **Reserve files before editing:**\n   ```\n   file_reservation_paths(project_key, agent_name, [\"src/**\"], ttl_seconds=3600, exclusive=true)\n   ```\n\n3. **Communicate with threads:**\n   ```\n   send_message(..., thread_id=\"FEAT-123\")\n   fetch_inbox(project_key, agent_name)\n   acknowledge_message(project_key, agent_name, message_id)\n   ```\n\n4. **Quick reads:**\n   ```\n   resource://inbox/{Agent}?project=<abs-path>&limit=20\n   resource://thread/{id}?project=<abs-path>&include_bodies=true\n   ```\n\n### Macros vs Granular Tools\n\n- **Prefer macros for speed:** `macro_start_session`, `macro_prepare_thread`, `macro_file_reservation_cycle`, `macro_contact_handshake`\n- **Use granular tools for control:** `register_agent`, `file_reservation_paths`, `send_message`, `fetch_inbox`, `acknowledge_message`\n\n### Common Pitfalls\n\n- `\"from_agent not registered\"`: Always `register_agent` in the correct `project_key` first\n- `\"FILE_RESERVATION_CONFLICT\"`: Adjust patterns, wait for expiry, or use non-exclusive reservation\n- **Auth errors:** If JWT+JWKS enabled, include bearer token with matching `kid`\n\n---\n\n## Beads (br) — Dependency-Aware Issue Tracking\n\nBeads provides a lightweight, dependency-aware issue database and CLI (`br` - beads_rust) for selecting \"ready work,\" setting priorities, and tracking status. It complements MCP Agent Mail's messaging and file reservations.\n\n**Important:** `br` is non-invasive—it NEVER runs git commands automatically. You must manually commit changes after `br sync --flush-only`.\n\n### Conventions\n\n- **Single source of truth:** Beads for task status/priority/dependencies; Agent Mail for conversation and audit\n- **Shared identifiers:** Use Beads issue ID (e.g., `br-123`) as Mail `thread_id` and prefix subjects with `[br-123]`\n- **Reservations:** When starting a task, call `file_reservation_paths()` with the issue ID in `reason`\n\n### Typical Agent Flow\n\n1. **Pick ready work (Beads):**\n   ```bash\n   br ready --json  # Choose highest priority, no blockers\n   ```\n\n2. **Reserve edit surface (Mail):**\n   ```\n   file_reservation_paths(project_key, agent_name, [\"src/**\"], ttl_seconds=3600, exclusive=true, reason=\"br-123\")\n   ```\n\n3. **Announce start (Mail):**\n   ```\n   send_message(..., thread_id=\"br-123\", subject=\"[br-123] Start: <title>\", ack_required=true)\n   ```\n\n4. **Work and update:** Reply in-thread with progress\n\n5. **Complete and release:**\n   ```bash\n   br close 123 --reason \"Completed\"\n   br sync --flush-only  # Export to JSONL (no git operations)\n   ```\n   ```\n   release_file_reservations(project_key, agent_name, paths=[\"src/**\"])\n   ```\n   Final Mail reply: `[br-123] Completed` with summary\n\n### Mapping Cheat Sheet\n\n| Concept | Value |\n|---------|-------|\n| Mail `thread_id` | `br-###` |\n| Mail subject | `[br-###] ...` |\n| File reservation `reason` | `br-###` |\n| Commit messages | Include `br-###` for traceability |\n\n---\n\n## bv — Graph-Aware Triage Engine\n\nbv is a graph-aware triage engine for Beads projects (`.beads/beads.jsonl`). It computes PageRank, betweenness, critical path, cycles, HITS, eigenvector, and k-core metrics deterministically.\n\n**Scope boundary:** bv handles *what to work on* (triage, priority, planning). For agent-to-agent coordination (messaging, work claiming, file reservations), use MCP Agent Mail.\n\n**CRITICAL: Use ONLY `--robot-*` flags. Bare `bv` launches an interactive TUI that blocks your session.**\n\n### The Workflow: Start With Triage\n\n**`bv --robot-triage` is your single entry point.** It returns:\n- `quick_ref`: at-a-glance counts + top 3 picks\n- `recommendations`: ranked actionable items with scores, reasons, unblock info\n- `quick_wins`: low-effort high-impact items\n- `blockers_to_clear`: items that unblock the most downstream work\n- `project_health`: status/type/priority distributions, graph metrics\n- `commands`: copy-paste shell commands for next steps\n\n```bash\nbv --robot-triage        # THE MEGA-COMMAND: start here\nbv --robot-next          # Minimal: just the single top pick + claim command\n```\n\n### Command Reference\n\n**Planning:**\n| Command | Returns |\n|---------|---------|\n| `--robot-plan` | Parallel execution tracks with `unblocks` lists |\n| `--robot-priority` | Priority misalignment detection with confidence |\n\n**Graph Analysis:**\n| Command | Returns |\n|---------|---------|\n| `--robot-insights` | Full metrics: PageRank, betweenness, HITS, eigenvector, critical path, cycles, k-core, articulation points, slack |\n| `--robot-label-health` | Per-label health: `health_level`, `velocity_score`, `staleness`, `blocked_count` |\n| `--robot-label-flow` | Cross-label dependency: `flow_matrix`, `dependencies`, `bottleneck_labels` |\n| `--robot-label-attention [--attention-limit=N]` | Attention-ranked labels |\n\n**History & Change Tracking:**\n| Command | Returns |\n|---------|---------|\n| `--robot-history` | Bead-to-commit correlations |\n| `--robot-diff --diff-since <ref>` | Changes since ref: new/closed/modified issues, cycles |\n\n**Other:**\n| Command | Returns |\n|---------|---------|\n| `--robot-burndown <sprint>` | Sprint burndown, scope changes, at-risk items |\n| `--robot-forecast <id\\|all>` | ETA predictions with dependency-aware scheduling |\n| `--robot-alerts` | Stale issues, blocking cascades, priority mismatches |\n| `--robot-suggest` | Hygiene: duplicates, missing deps, label suggestions |\n| `--robot-graph [--graph-format=json\\|dot\\|mermaid]` | Dependency graph export |\n| `--export-graph <file.html>` | Interactive HTML visualization |\n\n### Scoping & Filtering\n\n```bash\nbv --robot-plan --label backend              # Scope to label's subgraph\nbv --robot-insights --as-of HEAD~30          # Historical point-in-time\nbv --recipe actionable --robot-plan          # Pre-filter: ready to work\nbv --recipe high-impact --robot-triage       # Pre-filter: top PageRank\nbv --robot-triage --robot-triage-by-track    # Group by parallel work streams\nbv --robot-triage --robot-triage-by-label    # Group by domain\n```\n\n### Understanding Robot Output\n\n**All robot JSON includes:**\n- `data_hash` — Fingerprint of source beads.jsonl\n- `status` — Per-metric state: `computed|approx|timeout|skipped` + elapsed ms\n- `as_of` / `as_of_commit` — Present when using `--as-of`\n\n**Two-phase analysis:**\n- **Phase 1 (instant):** degree, topo sort, density\n- **Phase 2 (async, 500ms timeout):** PageRank, betweenness, HITS, eigenvector, cycles\n\n### jq Quick Reference\n\n```bash\nbv --robot-triage | jq '.quick_ref'                        # At-a-glance summary\nbv --robot-triage | jq '.recommendations[0]'               # Top recommendation\nbv --robot-plan | jq '.plan.summary.highest_impact'        # Best unblock target\nbv --robot-insights | jq '.status'                         # Check metric readiness\nbv --robot-insights | jq '.Cycles'                         # Circular deps (must fix!)\n```\n\n---\n\n## UBS — Ultimate Bug Scanner\n\n**Golden Rule:** `ubs <changed-files>` before every commit. Exit 0 = safe. Exit >0 = fix & re-run.\n\n### Commands\n\n```bash\nubs file.rs file2.rs                    # Specific files (< 1s) — USE THIS\nubs $(git diff --name-only --cached)    # Staged files — before commit\nubs --only=rust,toml src/               # Language filter (3-5x faster)\nubs --ci --fail-on-warning .            # CI mode — before PR\nubs .                                   # Whole project (ignores target/, Cargo.lock)\n```\n\n### Output Format\n\n```\nWarning  Category (N errors)\n    file.rs:42:5 - Issue description\n    Suggested fix\nExit code: 1\n```\n\nParse: `file:line:col` -> location | fix hint -> how to fix | Exit 0/1 -> pass/fail\n\n### Fix Workflow\n\n1. Read finding\n\n<!-- File: docs/agents.md (Tokens: ~6012 | Category: root-instruction) -->\n# Agent-Specific Profiles\n\ndcg can detect which AI coding agent is invoking it and apply agent-specific\ntrust levels and configuration overrides. This allows you to grant higher\ntrust to well-behaved agents while maintaining strict controls for unknown ones.\n\n## Supported Agents\n\n| Agent | Detection Method | Environment Variable |\n|-------|------------------|---------------------|\n| Claude Code | Environment | `CLAUDE_CODE=1` or `CLAUDE_SESSION_ID` |\n| Augment Code | Environment | `AUGMENT_AGENT=1` or `AUGMENT_CONVERSATION_ID` |\n| Aider | Environment | `AIDER_SESSION=1` |\n| Continue | Environment | `CONTINUE_SESSION_ID` |\n| Codex CLI | Environment | `CODEX_CLI=1` |\n| Gemini CLI | Environment | `GEMINI_CLI=1` |\n| GitHub Copilot CLI | Environment | `COPILOT_CLI=1` or `COPILOT_AGENT_START_TIME_SEC` |\n| VS Code Copilot Chat | Hook payload | `tool_name` is `runTerminalCommand`, `run_in_terminal`, or `runInTerminal`; the Agent Host's batched `toolCalls` envelope is also recognized, with every shell entry evaluated independently |\n| Cursor IDE | Environment | `CURSOR_IDE=1` (set by dcg's hook script) |\n| Hermes Agent | Environment | `HERMES_AGENT=1` or `HERMES_SESSION_ID` |\n| Grok (xAI) | Environment | `GROK_SESSION_ID`, `GROK_HOOK_EVENT`, or `GROK_WORKSPACE_ROOT` |\n| Oh My Pi (`omp`) | Explicit bridge / process | Generated extension passes `--agent omp`; exact `omp` and `oh-my-pi` process names are fallback matches |\n| Crush | Environment | `CRUSH=1` (set by Crush for hook subprocesses and `bash`-tool commands; the generic `AGENT`/`AI_AGENT` markers are not consulted). Hook payloads are recognized by their `event: \"PreToolUse\"` + `tool_input` envelope — see [crush-integration.md](crush-integration.md) |\n| Muse Code | Explicit flag only | The hook written by `dcg install --muse` passes `--agent muse` (Muse clears the hook environment). Its payload looks like Codex's (`turn_id`), so without the flag `[agents.codex-cli]` applies; the identity is never taken from the payload or a process name — see [muse-integration.md](muse-integration.md) |\n| Amp | Explicit bridge | The plugin written by `dcg install --amp` passes `--agent amp`; no environment marker or process name is consulted — see [amp-integration.md](amp-integration.md) |\n| Pi | Environment | `PI_CODING_AGENT=true` |\n| Posit Assistant | Environment | `PA_PROJECT_DIR` (set in hook subprocesses; checked last among environment markers so agents with their own markers win — environment detection still precedes parent-process detection) |\n\n## Detection Priority\n\nAgent detection follows this priority order:\n\n1. **Explicit `--agent` flag**: Manual override via CLI\n2. **Environment variables**: Most agents set identifying env vars\n3. **Parent process inspection**: Fallback check of process tree\n4. **Unknown**: Default when no agent is detected\n\n### Oh My Pi (`omp`)\n\nInstall dcg's native OMP ExtensionAPI module with:\n\n```bash\ndcg install --omp\n```\n\nThe default user path is `~/.omp/agent/extensions/dcg-guard.ts`. OMP named\nprofiles use `~/.omp/profiles/<name>/agent/extensions/dcg-guard.ts`; dcg follows\nOMP's `OMP_PROFILE`-before-`PI_PROFILE` precedence and honors\n`PI_CONFIG_DIR` for a config directory name relative to the user's home plus\n`PI_CODING_AGENT_DIR` for the default profile. Drive-qualified\n`PI_CONFIG_DIR` values are rejected on Windows because Rust would otherwise\nresolve them differently from OMP's home-relative `path.join` behavior. Use\n`dcg install --omp --project` to install `<cwd>/.omp/extensions/dcg-guard.ts`\ninstead. OMP checks only the current working directory for native project\nextensions; it neither requires Git nor walks to an ancestor, so launch OMP\nfrom that same directory.\n\nThe extension uses OMP's pre-execution `tool_call` event and sends each `bash`\ncommand to `dcg --robot test --stdin --agent omp --format json` with the dialect\nselected by OMP's execution route. Pinning the private bridge format prevents\nambient `DCG_FORMAT` from redirecting or invalidating its compact protocol\nwithout removing that variable from supported environment-conditioned policy.\nThe embedded install-time absolute dcg pathname is authoritative: ambient\n`DCG_BIN` cannot redirect the marker-owned guard. This binds a pathname, not a\nhash, inode/file ID, signature, or immutable executable object. Bun resolves\nthat pathname again for each tool call, so moving or removing the file produces\na visible infrastructure failure, while replacing bytes at the same pathname\nchanges what a later callback executes. `dcg doctor` compares the marker-owned\nextension with source generated for the doctor process's pathname at inspection\ntime; it does not attest binary contents or an extension already loaded by a\nrunning OMP session. To rebind deliberately, run\n`/desired/path/dcg install --omp --force` (add `--project` for project scope)\nand restart OMP. Protect the binary, extension, and their parent directories\nfrom writers not trusted to control OMP execution; a writer that can replace the\nextension can already run code inside OMP. Ordinary and managed-async calls use\nOMP's embedded Brush shell and pass `--dialect posix`, including on native\nWindows. An eligible local `pty: true` call instead maps OMP's configured\nexternal shell to `--dialect posix`, `cmd`, or `ps`; `PI_NO_PTY=1` disables that\nPTY route. A dcg deny, ask, or indeterminate result returns\n`{ block: true, reason }` to OMP.\nBecause the bridge supplies `--agent omp` explicitly, OMP remains distinct from\nlegacy Pi even when OMP exposes Pi-family compatibility variables. The\ncanonical profile key is `agents.omp`; `oh-my-pi` is accepted as an alias.\nWhen `[history] enabled = true`, these robot-boundary evaluations are persisted\nwith the canonical `agent_type = \"omp\"`; ordinary human `dcg test` diagnostics\nremain outside command history.\n\nThe bridge spawns dcg directly, without a shell, and gives Bun a 30-second\nparent-side timeout with a hard kill signal. Immediately after a successful\nspawn it records one monotonic absolute deadline 30.5 seconds away and arms one\nobservation watchdog. A direct child can exit while a descendant still holds\nits stdout or stderr descriptor, so exit settlement rearms that sole timer for\nthe lesser of a 250-millisecond pipe-drain grace and the remaining absolute\nbudget. If the remaining hard budget wins, expiry retains hard-deadline\nprovenance. Exit-observation rejection attempts one direct-child `SIGKILL` and\nuses the same clamped drain; neither rearm kills again. Every active watchdog is\ncleared when observation finishes, and late exit settlement or rejection\nremains consumed. There is no retry, replacement process, or second\nconcurrently active bridge watchdog.\n\nThis is the finite child-observation lineage:\n\n| Current state + event | Process action | Pipe/clock action | Result invariant |\n|---|---|---|---|\n| `Bun.spawn` throws | No child exists | No watchdog is armed | One visible spawn diagnostic; fail open |\n| Spawn succeeds | Bun owns the 30-second direct-child timeout | Record and arm the monotonic 30.5-second absolute observation deadline; read both capped pipes and exit concurrently | No classification before the bounded observation settles |\n| Complete deny/ask/indeterminate frame or stdout overflow arrives | None | Retain the frame or capped overflow evidence while observation continues | Blocking evidence is absorbing |\n| Exit resolves before the hard deadline | None; the direct child is already gone | Rearm for `min(250 ms, remaining hard budget)` | Bytes already read remain eligible evidence; the absolute deadline cannot move |\n| Exit rejects before the hard deadline | Attempt one direct-child `SIGKILL` | Rearm for `min(250 ms, remaining hard budget)` without another kill | The exit fault and any kill fault remain visible; kill request alone does not prove a signal |\n| Drain grace expires | The direct child has exited or already received the rejection-path kill | Cancel both local readers | Retained block/overflow still blocks; missing evidence follows visible infrastructure fail-open |\n| Hard observation deadline expires | Attempt one direct-child `SIGKILL` | Cancel both readers and the local exit wrapper | Retained block/overflow still blocks; deadline remains visible |\n| All three observations settle first | None | Clear the active watchdog | Read signal provenance, classify once, and emit each owned diagnostic once |\n\nIf a blocking frame and a deadline become runnable together, JavaScript's event\nloop imposes a total order: bytes delivered before reader cancellation are\nretained and absorbing; bytes delivered after cancellation are outside the\nobservation boundary. The stdout and stderr caps bound retained memory, and the\nouter/deferred deadlines bound a standards-compliant asynchronous observation.\nIf an underlying WHATWG cancellation algorithm rejects, the stream is still\nclosed and pending reads settle; the bridge consumes the cancellation promise's\nrejection while retaining prior deny/ask/indeterminate and overflow evidence.\nThe backstops sit above dcg's ordinary 1-second evaluator default and the broad\nWindows preset's 3-second default, not in place of those configurable budgets.\nAn explicit evaluator budget above 30 seconds is nevertheless capped on this\nOMP bridge; direct hook and diagnostic invocations retain their configured\nbudget.\n\nTwo seams remain fundamentally outside this in-process boundary. A synchronous\nstall inside `Bun.spawn` (or any JavaScript/event-loop stall) prevents a timer\nfrom starting or firing. Bun's `proc.kill()` targets the direct child, not a\nprocess group, so it cannot prove that descendants were terminated or release\ntheir unrelated resources; local reader cancellation bounds the callback even\nwhen an inherited pipe holder survives. A non-standard stream whose synchronous\ncancel path throws without settling a pending read is likewise outside the Web\nStreams contract and cannot be bounded by another JavaScript promise. After\nobservation, the bridge reads Bun's `signalCode` and reports an exact signal\nonly when Bun actually exposes one; a successful `proc.kill(\"SIGKILL\")` request\ndoes not itself forge SIGKILL provenance. This keeps an ordinary numeric exit\n137 distinct from an observed timeout or other observed `SIGKILL`.\nA complete deny, ask, or indeterminate verdict already written to stdout\nremains authoritative regardless of signal, cancellation, stream/exit fault,\nor deadline; stdout overflow and dcg's independent blocking exit 1 are likewise\nabsorbing. Other abnormal statuses and infrastructure faults remain visible\neven when blocking evidence controls the action.\n\nOMP's public ExtensionAPI does not expose its ACP client-terminal capability or\nselected backend, and both ACP and JSON-RPC surface as `mode: \"rpc\"`. The bridge\ntherefore does not infer a Windows dialect from mode: non-PTY RPC calls remain\nPOSIX-scoped so ordinary JSON-RPC execution does not gain Cmd/PowerShell false\npositives. As a result, an ACP client-terminal call does not yet receive exact\nCmd/PowerShell-specific coverage; closing that residual requires OMP to expose\nthe actual BashTool route before the `tool_call` handler runs.\n\n## Trust Levels\n\nThree trust levels label how much you trust a given agent:\n\n| Level | Description |\n|-------|-------------|\n| `high` | Agent has proven reliable; typically paired with a broader allowlist and fewer packs |\n| `medium` | Default; standard configuration |\n| `low` | Extra caution; typically paired with more packs and a restricted allowlist |\n\n### How trust levels work\n\nThe `trust_level` field is an **advisory label**. It is recorded in JSON output\nand shown in verbose/debug logs so you (and downstream tooling) can see what\ntrust tier was in effect for a given evaluation. It does **not**, by itself,\nchange which rules fire or how confidence scores are computed.\n\nAll behavioral differences between agents come from the other profile options\nthat you configure alongside the trust level:\n\n| Option | What it does | Typical usage |\n|--------|-------------|---------------|\n| `disabled_packs` | Removes packs (and their sub-packs) from evaluation | High-trust agents that don't need certain rule sets |\n| `extra_packs` | Adds packs to evaluation | Low-trust agents that should be checked against more rules |\n| `additional_allowlist` | Adds command patterns that bypass deny rules | High-trust agents with known-safe build commands |\n| `disabled_allowlist` | When `true`, ignores *all* allowlist entries (base + additional) | Low-trust agents that should never get a free pass |\n\nIn other words: setting `trust_level = \"high\"` alone does not relax any rules.\nYou must also adjust `disabled_packs`, `extra_packs`, `additional_allowlist`,\nor `disabled_allowlist` to change evaluation behavior.\n\n### Why the separation?\n\nThis design is intentional. Trust is not a magic knob -- different environments\nneed different trade-offs. A \"high trust\" agent in one project might need strict\ndatabase rules but relaxed filesystem rules, while in another project the\nopposite applies. By keeping the label separate from the behavioral knobs, dcg\ngives you full control without hidden side effects.\n\n### Practical examples\n\n**High-trust agent** -- a well-tested agent that runs routine build/test\ncommands. You widen the allowlist and disable packs that produce false positives\nfor its workflow:\n\n```toml\n[agents.claude-code]\ntrust_level = \"high\"\nadditional_allowlist = [\"npm run build\", \"cargo test\", \"make lint\"]\ndisabled_packs = [\"kubernetes\"]\n```\n\n**Medium-trust agent (default)** -- standard rules, no overrides:\n\n```toml\n[agents.default]\ntrust_level = \"medium\"\n```\n\n**Low-trust agent** -- an unknown or new agent. You add extra packs and disable\nthe allowlist so every command is evaluated against the full rule set:\n\n```toml\n[agents.unknown]\ntrust_level = \"low\"\ndisabled_allowlist = true\n# Real pack / category IDs (see `dcg packs` / docs/packs/README.md). A category\n# ID like \"database\" expands to every database.* sub-pack. \"paranoid\" is a\n# graduation mode, not a pack — use the real `strict_git` pack for stricter git\n# rules, and `core.filesystem` (not \"filesystem\") for the filesystem pack.\nextra_packs = [\"strict_git\", \"database\", \"system\"]\n```\n\n## Configuration\n\nConfigure agent profiles in your `config.toml`:\n\n```toml\n# Trust Claude Code more (it sets CLAUDE_CODE=1)\n[agents.claude-code]\ntrust_level = \"high\"\nadditional_allowlist = [\"npm run build\", \"cargo test\"]\n\n[agents.omp]\ntrust_level = \"medium\"\nextra_packs = [\"strict_git\"]\n\n# Restrict unknown agents\n[agents.unknown]\ntrust_level = \"low\"\ndisabled_allowlist = true\nextra_packs = [\"strict_git\", \"database\"]\n\n# Default profile for unspecified agents\n[agents.default]\ntrust_level = \"medium\"\n```\n\n### Profile Options\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `trust_level` | string | `\"medium\"` | Advisory label: `\"high\"`, `\"medium\"`, or `\"low\"`. Included in JSON/verbose output but does not change rule evaluation by itself. |\n| `disabled_packs` | array | `[]` | Pack or category IDs to remove from evaluation for this agent (a category ID drops every matching sub-pack). |\n| `extra_packs` | array | `[]` | Additional pack or category IDs to enable for this agent (a category ID expands to all its sub-packs). |\n| `additional_allowlist` | array | `[]` | Command patterns to allowlist for this agent (added on top of the base allowlist). |\n| `disabled_allowlist` | bool | `false` | If `true`, ignore all allowlist entries for this agent (more restrictive). |\n\n### Example: Agent Profiles for a Trusted Config\n\n```toml\n# In ~/.config/dcg/config.toml, or in a reviewed file selected with DCG_CONFIG\n[agents.unknown]\ntrust_level = \"low\"\ndisabled_allowlist = true\nextra_packs = [\"strict_git\", \"database\", \"system\"]\n\n[agents.claude-code]\ntrust_level = \"medium\"\nadditional_allowlist = [\"npm test\", \"npm run lint\"]\n```\n\nAutomatically discovered repository `.dcg.toml` files cannot define agent\nprofiles. Profiles can disable packs or add allowlist entries, so accepting\nthem from a newly cloned repository would cross dcg's security boundary. To\nuse a reviewed repository-owned profile deliberately, invoke dcg with\n`DCG_CONFIG=.dcg.toml` (or copy the profile into the user config).\n\n## Custom Agents\n\nDefine profiles for custom agents by setting an environment variable:\n\n```bash\n# Set a custom agent identifier\nexport MY_BUILD_BOT=1\n```\n\nThen configure in `config.toml`:\n\n```toml\n[agents.my-build-bot]\ntrust_level = \"high\"\nadditional_allowlist = [\"make deploy\"]\n```\n\n## Profile Resolution\n\nWhen resolving which profile to use:\n\n1. Look for exact match: `agents.<agent-config-key>`\n2. Fall back to `agents.unknown` if agent is unrecognized\n3. Fall back to `agents.default` if no specific profile exists\n\n## Verbose Output\n\nUse `--verbose` or `-v` to see agent detection info:\n\n```bash\n$ dcg test \"git push --force\" --verbose\nCommand: git push --force\n...\nElapsed: 21.14ms\nAgent: Claude Code\nTrust level: medium\nSeverity: critical\n```\n\nUse `-vv` for detailed debug output:\n\n```bash\n$ dcg test \"git push --force\" -vv\n...\nAgent detection:\n  Detected: Claude Code (claude-code)\n  Method: environment_variable\n  Matched: CLAUDE_CODE\n  Profile: agents.claude-code\n  Trust level: medium\n```\n\n## JSON Output\n\nThe `--format json` output includes agent information:\n\n```json\n{\n  \"command\": \"git push --force\",\n  \"decision\": \"deny\",\n  \"agent\": {\n    \"detected\": \"claude-code\",\n    \"trust_level\": \"medium\",\n    \"detection_method\": \"environment_variable\"\n  }\n}\n```\n\n## Robot Mode\n\nRobot mode provides a unified, machine-friendly interface for AI agents. When\nenabled, dcg optimizes its output for programmatic consumption.\n\n### Enabling Robot Mode\n\n```bash\n# Via flag\ndcg --robot test \"rm -rf /\"\n\n# Via environment variable\nDCG_ROBOT=1 dcg test \"rm -rf /\"\n```\n\n### Robot Mode Behavior\n\n| Aspect | Normal Mode | Robot Mode |\n|--------|-------------|------------|\n| stdout | JSON or pretty | Always JSON |\n| stderr | Rich colored output | Silent |\n| Exit codes | Varies | Standardized |\n| ANSI codes | If TTY | Never |\n| Progress | Shown | Hidden |\n| Suggestions | Shown | In JSON only |\n\n### Standardized Exit Codes\n\nIn robot mode, dcg uses consistent exit codes across all commands:\n\n| Code | Constant | Meaning |\n|------|----------|---------|\n| 0 | `EXIT_SUCCESS` | Success / Allow |\n| 1 | `EXIT_DENIED` | Command denied/blocked |\n| 2 | `EXIT_WARNING` | Warning (with --fail-on warn) |\n| 3 | `EXIT_CONFIG_ERROR` | Configuration error |\n| 4 | `EXIT_PARSE_ERROR` | Parse/input error |\n| 5 | `EXIT_IO_ERROR` | IO error |\n| 141 | `EXIT_BROKEN_PIPE` | stdout/stderr reader went away (`EPIPE`); a clean exit, never a signal death |\n\n### Robot Mode JSON Output\n\nAll robot-mode responses are pure JSON on stdout:\n\n```json\n{\n  \"command\": \"rm -rf /\",\n  \"decision\": \"deny\",\n  \"rule_id\": \"core.filesystem:rm-rf-root\",\n  \"pack_id\": \"core.filesystem\",\n  \"severity\": \"critical\",\n  \"reason\": \"rm -rf / would delete the entire filesystem\",\n  \"agent\": {\n    \"detected\": \"claude-code\",\n    \"trust_level\": \"medium\",\n    \"detection_method\": \"environment_variable\"\n  }\n}\n```\n\n### Hook Mode vs Robot Mode\n\n**Hook mode** (default when no subcommand) follows the active hook protocol:\n- Claude Code, Gemini CLI, Copilot CLI, VS Code Copilot Chat, and compatible JSON-hook protocols emit\n  JSON on stdout for denials and empty stdout for allows.\n- Codex CLI uses strict hook parsing, so dcg emits a minimal\n  `hookSpecificOutput` denial on stdout and exits 0.\n- Rich output always goes to stderr for human visibility.\n\nHook mode carries the verdict in stdout JSON and exits 0 whether the command\nwas allowed, warned about, sent for review, or denied. The exit status changes\nonly when a blocking verdict could not be delivered:\n\n| Code | Constant | Meaning |\n|------|----------|---------|\n| 0 | `EXIT_SUCCESS` | Verdict delivered on stdout (an allow is empty stdout). Also an allow or a warning whose stdout reader went away: nothing was lost. |\n| 2 | `EXIT_HOOK_BLOCK` | A deny, ask, or indeterminate verdict could not be written to stdout (`EPIPE`: the host closed the pipe early). Exit 0 with nothing on stdout would read as \"proceed\", so the block travels in the exit status instead, with the reason on stderr. |\n\nHow hosts read exit 2 with nothing on stdout:\n\n| Protocol | Effect |\n|----------|--------|\n| Claude Code and Claude-compatible hosts (Posit Assistant, Augment) | Blocks; stderr is fed back to the model as the reason |\n| Gemini CLI | Blocks (exit 2 is its blocking error) |\n| Copilot CLI | Blocks (`preToolUse` hooks that exit 2 deny the call) |\n| Crush | Blocks; stderr is the reason |\n| Grok | Blocks (exit 2 is a documented explicit deny) |\n| Codex CLI | Blocks in current releases (exit 2 with the reason on stderr is documented); some earlier builds logged it as a hook failure and failed open |\n| Hermes | Blocks in current releases (a `pre_tool_call` hook exiting 2 blocks even with no JSON on stdout); earlier builds only warned |\n| Reasonix | Blocks — exit 2 is its only blocking channel, so dcg always uses it there |\n| Muse Code | Not documented: a non-zero exit is a hook failure, which Muse hands to the handler's `onFailure` fallback when one is declared |\n| Antigravity (`agy`) | Logged as a hook failure, then fails open — the same outcome as exit 0 with no JSON, but visible |\n\nHook mode never exits 2 for any other reason, and never exits 141: every\nhook-mode write tolerates a closed pipe. `EXIT_BROKEN_PIPE` belongs to the\nCLI surface below.\n\n**Robot mode** with subcommands uses standardized exit codes:\n- Exit 1 for denials (allows scripting with `$?`)\n- Pure JSON on stdout\n- Silent stderr\n\n## Rich Output and Agent Compatibility\n\ndcg keeps agent-facing output and human-facing output on separate streams. This\nis the compatibility contract for rich terminal formatting.\n\n| Stream | Purpose | Hook-mode content | Robot-mode content |\n|--------|---------|-------------------|--------------------|\n| stdout | Agent and script parsing | Protocol JSON for denials, empty for allows | JSON only |\n| stderr | Human-visible diagnostics | Rich or plain text warning boxes | Silent |\n\nRich output is display-only. It must never be parsed by agents and must never be\nwritten to stdout. When dcg prints Unicode boxes, colors, highlighted commands,\nor suggestion panels, that output belongs on stderr.\n\n### Rich Output Selection\n\ndcg uses rich terminal formatting only when the runtime is suitable. It falls\nback to plain output when any of these controls are active:\n\n| Control | Effect |\n|---------|--------|\n| `DCG_NO_RICH=1` | Disable rich formatting while keeping normal command behavior |\n| `--legacy-output` or `DCG_LEGACY_OUTPUT=1` | Force legacy/plain rendering paths |\n| `NO_COLOR=1` or `DCG_NO_COLOR=1` | Disable colorized output |\n| `TERM=dumb` | Use dumb-terminal-safe output |\n| `CI=1` | Suppress rich interactive formatting in CI |\n| non-TTY stdout | Prefer plain output for pipeline-friendly behavior |\n| `--robot` or `DCG_ROBOT=1` | Emit machine-readable stdout and keep stderr silent |\n\n### Wrapper Guidance\n\nAgent wrappers should choose the interface that matches their parser:\n\n```bash\n# Hook integration: preserve both streams.\ndcg < hook-input.json >hook-stdout.json 2>human-warning.txt\n\n# Scripting integration: use robot mode and parse stdout only.\ndcg --robot test \"rm -rf /\" >decision.json 2>/dev/null\n```\n\nFor Codex and Claude-compatible hook integrations, parse stdout when it is\nnon-empty and treat empty stdout with exit 0 as allow. Codex's denial payload is\nminimal and intentionally omits dcg-only metadata.\n\n### Example: Agent Integration\n\n```bash\n#!/bin/bash\n# Script for AI agent to check commands before execution\n\ncheck_command() {\n    local cmd=\"$1\"\n    local result\n\n    # Use robot mode for predictable output\n    result=$(dcg --robot test \"$cmd\" 2>/dev/null)\n    local exit_code=$?\n\n    if [ $exit_code -eq 0 ]; then\n        echo \"Command allowed: $cmd\"\n        return 0\n    elif [ $exit_code -eq 1 ]; then\n        echo \"Command BLOCKED: $cmd\"\n        echo \"Reason: $(echo \"$result\" | jq -r '.reason')\"\n        return 1\n    else\n        echo \"Error checking command (exit code: $exit_code)\"\n        return $exit_code\n    fi\n}\n\n# Usage\ncheck_command \"git status\"      # Allowed\ncheck_command \"rm -rf /\"        # Blocked\n```\n\n### Unified Output Format\n\nRobot mode uses the unified `OutputFormat` enum:\n\n```bash\n# These are equivalent in robot mode\ndcg --robot test \"cmd\"\ndcg --robot --format json test \"cmd\"\n```\n\nAvailable formats:\n- `pretty` / `text` / `human` - Human-readable (default without --robot)\n- `json` / `sarif` / `structured` - JSON output (default with --robot)\n- `jsonl` - JSON Lines (one object per line, for streaming)\n- `compact` - Compact single-line output\n\n## Best Practices\n\n1. **Start with defaults**: The default `medium` trust level is safe for most\n   use cases.\n\n2. **Grant trust incrementally**: Only increase trust for agents after\n   observing their behavior.\n\n3. **Version reviewed profiles carefully**: A repository can keep a reviewed\n   profile in `.dcg.toml`, but it only takes effect when explicitly selected\n   with `DCG_CONFIG=.dcg.toml`; automatic discovery ignores agent profiles.\n\n4. **Restrict unknown agents**: Always configure `agents.unknown` with lower\n   trust in production environments.\n\n5. **Review the JSON output**: Use `--format json` in CI to audit which agents\n   are accessing your codebase.\n\n6. **Use robot mode for scripting**: When integrating dcg into automated\n   workflows, use `--robot` for consistent, parseable output.\n\n7. **Check exit codes**: In robot mode, use exit codes to make decisions\n   without parsing JSON for simple allow/deny checks.\n\n<!-- File: SKILL.md (Tokens: ~3830 | Category: anthropic-skill) -->\n---\nname: dcg\ndescription: \"Destructive Command Guard - High-performance Rust hook for Claude Code that blocks dangerous commands before execution. SIMD-accelerated, modular pack system, whitelist-first architecture. Essential safety layer for agent workflows.\"\n---\n\n# DCG — Destructive Command Guard\n\nA high-performance Claude Code hook that intercepts and blocks destructive commands before they execute. Written in Rust with SIMD-accelerated filtering for sub-millisecond latency.\n\n## Why This Exists\n\nAI coding agents are powerful but fallible. They can accidentally run destructive commands:\n\n- **\"Let me clean up the build artifacts\"** → `rm -rf ./src` (typo)\n- **\"I'll reset to the last commit\"** → `git reset --hard` (destroys uncommitted changes)\n- **\"Let me fix the merge conflict\"** → `git checkout -- .` (discards all modifications)\n- **\"I'll clean up untracked files\"** → `git clean -fd` (permanently deletes untracked files)\n\nDCG intercepts dangerous commands *before* execution and blocks them with a clear explanation, giving you a chance to stash your changes first.\n\n## Critical Design Principles\n\n### 1. Whitelist-First Architecture\n\nSafe patterns are checked *before* destructive patterns. This ensures explicitly safe commands are never accidentally blocked:\n\n```\ngit checkout -b feature    →  Matches SAFE \"checkout-new-branch\"  →  ALLOW\ngit checkout -- file.txt   →  No safe match, matches DESTRUCTIVE  →  DENY\n```\n\n### 2. Fail-Safe Defaults (Default-Allow)\n\nUnrecognized commands are **allowed by default**. This ensures:\n- The hook never breaks legitimate workflows\n- Only *known* dangerous patterns are blocked\n- New git commands work until explicitly categorized\n\n### 3. Zero False Negatives Philosophy\n\nThe pattern set prioritizes **never allowing dangerous commands** over avoiding false positives. A few extra prompts for manual confirmation are acceptable; lost work is not.\n\n## What It Blocks\n\n### Git Commands That Destroy Uncommitted Work\n\n| Command | Reason |\n|---------|--------|\n| `git reset --hard` | Destroys uncommitted changes |\n| `git reset --merge` | Destroys uncommitted changes |\n| `git checkout -- <file>` | Discards file modifications |\n| `git restore <file>` (without `--staged`) | Discards uncommitted changes |\n| `git clean -f` | Permanently deletes untracked files |\n\n### Git Commands That Destroy Remote History\n\n| Command | Reason |\n|---------|--------|\n| `git push --force` / `-f` | Overwrites remote commits |\n| `git branch -d`, `--delete`, `-D`, `-f`, `-M`, `-C` | Deletes or force-overwrites a user-owned branch ref |\n\n### Git Commands That Destroy Stashed Work\n\n| Command | Reason |\n|---------|--------|\n| `git stash drop` | Permanently deletes a stash |\n| `git stash clear` | Permanently deletes all stashes |\n\n### Filesystem Commands\n\n| Command | Reason |\n|---------|--------|\n| `rm -rf` (outside `/tmp`, `/var/tmp`, `$TMPDIR`) | Recursive deletion is dangerous |\n\n## What It ALLOWS\n\nSafe operations pass through silently:\n\n### Always Safe Git Operations\n\n`git status`, `git log`, `git diff`, `git add`, `git commit`, `git push`, `git pull`, `git fetch`, read-only branch listings, `git stash`, `git stash pop`, `git stash list`\n\n### Explicitly Safe Patterns\n\n| Pattern | Why Safe |\n|---------|----------|\n| `git checkout -b <branch>` | Creating new branches |\n| `git checkout --orphan <branch>` | Creating orphan branches |\n| `git restore --staged <file>` | Unstaging only, doesn't touch working tree |\n| `git restore -S <file>` | Short flag for staged |\n| `git clean -n` / `--dry-run` | Preview mode, no actual deletion |\n| `rm -rf /tmp/*` | Temp directories are ephemeral |\n| `rm -rf $TMPDIR/*` | Shell variable forms |\n\n### Safe Alternative: `--force-with-lease`\n\n```bash\ngit push --force-with-lease   # ALLOWED - refuses if remote has unseen commits\ngit push --force              # BLOCKED - can overwrite others' work\n```\n\n## Modular Pack System\n\nDCG uses a modular \"pack\" system to organize patterns by category:\n\n### Core Packs (Always Enabled)\n\n| Pack | Description |\n|------|-------------|\n| `core.git` | Destructive git commands |\n| `core.filesystem` | Dangerous rm -rf outside temp |\n\n### Database Packs\n\n| Pack | Description |\n|------|-------------|\n| `database.postgresql` | DROP/TRUNCATE in PostgreSQL |\n| `database.mysql` | DROP/TRUNCATE in MySQL/MariaDB |\n| `database.mongodb` | dropDatabase, drop() |\n| `database.redis` | FLUSHALL/FLUSHDB |\n| `database.sqlite` | DROP in SQLite |\n\n### Container Packs\n\n| Pack | Description |\n|------|-------------|\n| `containers.docker` | docker system prune, docker rm -f |\n| `containers.compose` | docker-compose down --volumes |\n| `containers.podman` | podman system prune |\n\n### Kubernetes Packs\n\n| Pack | Description |\n|------|-------------|\n| `kubernetes.kubectl` | kubectl delete namespace |\n| `kubernetes.helm` | helm uninstall |\n| `kubernetes.kustomize` | kustomize delete patterns |\n\n### Cloud Provider Packs\n\n| Pack | Description |\n|------|-------------|\n| `cloud.aws` | Destructive AWS CLI commands |\n| `cloud.gcp` | Destructive gcloud commands |\n| `cloud.azure` | Destructive az commands |\n\n### Infrastructure Packs\n\n| Pack | Description |\n|------|-------------|\n| `infrastructure.terraform` | terraform destroy |\n| `infrastructure.ansible` | Dangerous ansible patterns |\n| `infrastructure.pulumi` | pulumi destroy |\n\n### System Packs\n\n| Pack | Description |\n|------|-------------|\n| `system.disk` | dd, mkfs, fdisk operations |\n| `system.permissions` | Dangerous chmod/chown patterns |\n| `system.services` | systemctl stop/disable patterns |\n\n### Other Packs\n\n| Pack | Description |\n|------|-------------|\n| `strict_git` | Extra paranoid git protections |\n| `package_managers` | npm unpublish, cargo yank |\n\n### Configuring Packs\n\n```toml\n# ~/.config/dcg/config.toml\n[packs]\nenabled = [\n    \"database.postgresql\",\n    \"containers.docker\",\n    \"kubernetes\",  # Enables all kubernetes sub-packs\n]\n```\n\n## Environment Variables\n\n| Variable | Description |\n|----------|-------------|\n| `DCG_PACKS=\"containers.docker,kubernetes\"` | Enable packs (comma-separated) |\n| `DCG_DISABLE=\"kubernetes.helm\"` | Disable packs/sub-packs |\n| `DCG_VERBOSE=1` | Verbose output |\n| `DCG_COLOR=auto\\|always\\|never` | Color mode |\n| `DCG_BYPASS=1` | Bypass DCG entirely (escape hatch) |\n\n## Installation\n\n### Quick Install (Recommended)\n\n```bash\ncurl -fsSL \"https://raw.githubusercontent.com/Dicklesworthstone/destructive_command_guard/main/install.sh?$(date +%s)\" | bash\n\n# Easy mode: auto-update PATH\ncurl -fsSL \"https://raw.githubusercontent.com/Dicklesworthstone/destructive_command_guard/main/install.sh?$(date +%s)\" | bash -s -- --easy-mode\n\n# System-wide (requires sudo)\ncurl -fsSL \"https://raw.githubusercontent.com/Dicklesworthstone/destructive_command_guard/main/install.sh?$(date +%s)\" | sudo bash -s -- --system\n```\n\n### From Source (Requires Rust Nightly)\n\n```bash\ncargo +nightly install --git https://github.com/Dicklesworthstone/destructive_command_guard\n```\n\n### Prebuilt Binaries\n\nAvailable for: Linux x86_64, Linux ARM64, macOS Intel, macOS Apple Silicon, Windows\n\n## Claude Code Configuration\n\nAdd to `~/.claude/settings.json`:\n\n```json\n{\n  \"hooks\": {\n    \"PreToolUse\": [\n      {\n        \"matcher\": \"Bash|PowerShell|Monitor\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"/absolute/path/to/dcg\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\nReplace `/absolute/path/to/dcg` with the exact resolved binary path. Do not use\nbare `dcg`: non-interactive hook shells may not inherit the interactive\n`PATH`. On native Windows, use `install.ps1` so the hook receives the\nPowerShell-safe absolute command and explicit shell selection.\n\n**Important:** Restart Claude Code after adding the hook.\n\n## How It Works\n\n### Processing Pipeline\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│                        Claude Code                               │\n│  Agent executes `rm -rf ./build`                                │\n└─────────────────────┬───────────────────────────────────────────┘\n                      │\n                      ▼ PreToolUse hook (stdin: JSON)\n┌─────────────────────────────────────────────────────────────────┐\n│                          dcg                                     │\n│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐       │\n│  │    Parse     │───▶│  Normalize   │───▶│ Quick Reject │       │\n│  │    JSON      │    │   Command    │    │   Filter     │       │\n│  └──────────────┘    └──────────────┘    └──────┬───────┘       │\n│                                                  │               │\n│                      ┌───────────────────────────┘               │\n│                      ▼                                           │\n│  ┌──────────────────────────────────────────────────────────┐   │\n│  │                   Pattern Matching                        │   │\n│  │   1. Check SAFE_PATTERNS (whitelist) ──▶ Allow if match  │   │\n│  │   2. Check DESTRUCTIVE_PATTERNS ──────▶ Deny if match    │   │\n│  │   3. No match ────────────────────────▶ Allow (default)  │   │\n│  └──────────────────────────────────────────────────────────┘   │\n└─────────────────────┬───────────────────────────────────────────┘\n                      │\n                      ▼ stdout: JSON (deny) or empty (allow)\n```\n\n### Stage 1: JSON Parsing\n- Reads hook input from stdin\n- Validates Claude Code's `PreToolUse` format\n- Non-Bash tools immediately allowed\n\n### Stage 2: Command Normalization\n- Strips absolute paths: `/usr/bin/git status` → `git status`\n- Preserves argument paths\n\n### Stage 3: Quick Rejection Filter\n- SIMD-accelerated substring search for \"git\" or \"rm\"\n- Commands without these bypass regex entirely (99%+ of commands)\n\n### Stage 4: Pattern Matching\n- Safe patterns checked first (short-circuit on match → allow)\n- Destructive patterns checked second (match → deny)\n- No match → default allow\n\n## Exit Codes\n\n| Code | Meaning |\n|------|---------|\n| `0` | Command is safe, proceed |\n| `2` | Command is blocked, do not execute |\n\n## CLI Usage\n\nTest commands manually:\n\n```bash\n# Show version with build metadata\ndcg --version\n\n# Test a command\necho '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git reset --hard\"}}' | dcg\n```\n\n## Example Block Message\n\n```\n════════════════════════════════════════════════════════════════════════\nBLOCKED  dcg\n────────────────────────────────────────────────────────────────────────\nReason:  git reset --hard destroys uncommitted changes. Use 'git stash' first.\n\nCommand:  git reset --hard HEAD~1\n\nTip: If you need to run this command, execute it manually in a terminal.\n     Consider using 'git stash' first to save your changes.\n════════════════════════════════════════════════════════════════════════\n```\n\n### Contextual Suggestions\n\n| Command Type | Suggestion |\n|-------------|------------|\n| `git reset`, `git checkout --` | \"Consider using 'git stash' first\" |\n| `git clean` | \"Use 'git clean -n' first to preview\" |\n| `git push --force` | \"Consider using '--force-with-lease'\" |\n| `rm -rf` | \"Verify the path carefully before running manually\" |\n\n## Edge Cases Handled\n\n### Path Normalization\n\n```bash\n/usr/bin/git reset --hard          # Blocked\n/usr/local/bin/git checkout -- .   # Blocked\n/bin/rm -rf /home/user             # Blocked\n```\n\n### Flag Ordering Variants\n\n```bash\nrm -rf /path          # Combined flags\nrm -fr /path          # Reversed order\nrm -r -f /path        # Separate flags\nrm --recursive --force /path    # Long flags\n```\n\nAll variants are handled.\n\n### Shell Variable Expansion\n\n```bash\nrm -rf $TMPDIR/build           # Allowed (temp)\nrm -rf ${TMPDIR}/build         # Allowed\nrm -rf \"$TMPDIR/build\"         # Allowed\nrm -rf \"${TMPDIR:-/tmp}/build\" # Allowed\n```\n\n### Staged vs Worktree Restore\n\n```bash\ngit restore --staged file.txt    # Allowed (unstaging only)\ngit restore -S file.txt          # Allowed (short flag)\ngit restore file.txt             # BLOCKED (discards changes)\ngit restore --worktree file.txt  # BLOCKED (explicit worktree)\ngit restore -S -W file.txt       # BLOCKED (includes worktree)\n```\n\n## Performance Optimizations\n\nDCG is designed for zero perceived latency:\n\n| Optimization | Technique |\n|--------------|-----------|\n| **Lazy Static** | Regex patterns compiled once via `LazyLock` |\n| **SIMD Quick Reject** | `memchr` crate for CPU vector instructions |\n| **Early Exit** | Safe match returns immediately |\n| **Zero-Copy JSON** | `serde_json` operates on input buffer |\n| **Zero-Allocation** | `Cow<str>` for path normalization |\n| **Release Profile** | `opt-level=\"z\"`, LTO, single codegen unit |\n\n**Result:** Sub-millisecond execution for typical commands.\n\n## Pattern Counts\n\n| Type | Count |\n|------|-------|\n| Safe patterns (whitelist) | 34 |\n| Destructive patterns (blacklist) | 16 |\n\n## Security Considerations\n\n### What DCG Protects Against\n\n- Accidental data loss from `git checkout --` or `git reset --hard`\n- Remote history destruction from force pushes\n- Stash loss from `git stash drop/clear`\n- Filesystem accidents from `rm -rf` outside temp directories\n\n### What DCG Does NOT Protect Against\n\n- Malicious actors (can bypass the hook)\n- Non-Bash commands (Python/JavaScript file writes, API calls)\n- Committed but unpushed work\n- Commands inside scripts (`./deploy.sh` contents not inspected)\n\n### Threat Model\n\nDCG assumes the AI agent is **well-intentioned but fallible**. It catches honest mistakes, not adversarial attacks.\n\n## Troubleshooting\n\n### Hook not blocking commands\n\n1. Verify `~/.claude/settings.json` has hook configuration\n2. Restart Claude Code\n3. Test manually: `echo '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git reset --hard\"}}' | dcg`\n\n### Hook blocking safe commands\n\n1. Check if there's an edge case not covered\n2. File a GitHub issue\n3. Temporary bypass: `DCG_BYPASS=1` or run command manually\n\n## FAQ\n\n**Q: Why block both `git branch -d` and `git branch -D`?**\n\nLowercase `-d` checks merge state, but still removes the branch name, upstream-tracking configuration, and convenient reflog reference. Those are user-owned state, so every delete form requires explicit approval. Review first with `git branch -vv`, `git branch --merged`, and `git branch --no-merged`. The merge-checked form is its own rule, `core.git:branch-delete`, so `[policy.rules] \"core.git:branch-delete\" = \"ask\"` turns it into an approval prompt while `-D` (`core.git:branch-force-delete`) stays denied.\n\n**Q: Why is `git push --force-with-lease` allowed?**\n\nForce-with-lease refuses to push if the remote has commits you haven't seen, preventing accidental overwrites.\n\n**Q: Why block all `rm -rf` outside temp directories?**\n\nRecursive forced deletion is extremely dangerous. A typo or wrong variable can delete critical files. Temp directories are designed to be ephemeral.\n\n**Q: What if I really need to run a blocked command?**\n\nDCG instructs the agent to ask for permission. Run the command manually in a separate terminal after making a conscious decision.\n\n## Integration with Flywheel\n\n| Tool | Integration |\n|------|-------------|\n| **Claude Code** | Native PreToolUse hook |\n| **Agent Mail** | Agents can report blocked commands to coordinator |\n| **BV** | Flag tasks that repeatedly trigger DCG |\n| **CASS** | Search DCG block patterns across sessions |\n| **RU** | DCG protects agent-sweep from destructive commits |\n</yakaai_skills>"}