destructive_command_guard Agent Skills & AI Rules

GitHub

The Destructive Command Guard (dcg) is for blocking dangerous git and shell commands from being executed by agents.

5,559 stars Rust Rust 1 file Β· ~2,561 tokens #ai-agents#cli#developer-tools#git#rust#safety
RAW Rules
3 Discovered Rules β€’ ~22,446 Estimated Tokens
Quick:
Loading skill content...

AI Agent Protocol & Instructions

AGENTS.md root-instruction ~12,604 tokens
Raw
# AGENTS.md β€” dcg (Destructive Command Guard)

> Guidelines for AI coding agents working in this Rust codebase.

---

## RULE 0 - THE FUNDAMENTAL OVERRIDE PREROGATIVE

If 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.

---

## RULE 0.5 - SUITE-WIDE RULES LIVE IN /data/projects/AGENTS.md

The suite-wide rules in **`/data/projects/AGENTS.md`** bind you here too. Read it. Two sections
are load-bearing for perf work and are NOT duplicated below, so they cannot drift out of sync:

- **`## Named Reward-Hacking Patterns (ALL FORBIDDEN)`** β€” 12 named patterns, several already
  observed in this suite: gate self-weakening (and the exact price of a legitimate gate fix),
  proof-class inflation, golden regeneration reflex, commit-stream pumping, tautological tests,
  easy-lever cherry-picking, close-pump abuse, scope-splitting, spec-editing as progress,
  conformance metastasis, dependency smuggling, bench-path hardcoding.
- **`### Work-Graph Discipline`** β€” JSONL is truth and `beads.db` is disposable, `br sync
  --import-only` after every pull, single-writer on graph structure, closure on cited evidence
  with blocker beads gated on their named probe, `br dep cycles` stays empty.

The three that most often decide whether a number here is real: a **self-speedup is
MAINTENANCE, not a win** β€” a win needs the incumbent live in the SAME invocation; **never
weaken a gate to land a change**, and if a gate is genuinely defective, meet the evidence
standard and publish the win/lose split of what the fix admits; and **reporting a loss is a
success** β€” one line, revert, next lever, no retraction narrative.

---

## RULE NUMBER 1: NO FILE DELETION

**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.

**YOU MUST ALWAYS ASK AND RECEIVE CLEAR, WRITTEN PERMISSION BEFORE EVER DELETING A FILE OR FOLDER OF ANY KIND.**

---

## Irreversible Git & Filesystem Actions β€” DO NOT EVER BREAK GLASS

> **Note:** This project exists specifically to block these dangerous commands for AI agents. Practice what we preach.

1. **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.
2. **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.
3. **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.
4. **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.
5. **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.

---

## Git Branch: ONLY Use `main`, NEVER `master`

**The default branch is `main`. The `master` branch exists only for legacy URL compatibility.**

- **All work happens on `main`** β€” commits, PRs, feature branches all merge to `main`
- **Never reference `master` in code or docs** β€” if you see `master` anywhere, it's a bug that needs fixing
- **The `master` branch must stay synchronized with `main`** β€” after pushing to `main`, also push to `master`:
  ```bash
  git push origin main:master
  ```

**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.

**If you see `master` referenced anywhere:**
1. Update it to `main`
2. Ensure `master` is synchronized: `git push origin main:master`

---

## Toolchain: Rust & Cargo

We only use **Cargo** in this project, NEVER any other package manager.

- **Edition:** Rust 2024 (nightly required β€” see `rust-toolchain.toml`)
- **Dependency versions:** Explicit versions for stability
- **Configuration:** Cargo.toml only (single crate, not a workspace)
- **Unsafe code:** Forbidden (`#![forbid(unsafe_code)]`)

### Key Dependencies

| Crate | Purpose |
|-------|---------|
| `serde` + `serde_json` | JSON parsing for Claude Code hook protocol |
| `serde_yaml` | External pack YAML parsing |
| `toml` + `toml_edit` | TOML config parsing with formatting preservation |
| `fancy-regex` | Advanced regex with lookahead/lookbehind |
| `regex` | `RegexSet` for heredoc detection |
| `memchr` | SIMD-accelerated substring search |
| `aho-corasick` | Multi-pattern string matching for keyword quick-reject |
| `colored` | Terminal colors with TTY detection |
| `clap` + `clap_complete` | CLI argument parsing with shell completions |
| `chrono` | RFC 3339 timestamps |
| `ast-grep-core` + `ast-grep-language` | AST-based pattern matching for heredoc/inline-script content |
| `rusqlite` | Bundled upstream SQLite for best-effort telemetry history |
| `rust-mcp-sdk` | MCP server integration (stdio transport) |
| `tokio` | Async runtime for MCP server mode |
| `ratatui` + `comfy-table` + `indicatif` + `console` | TUI/CLI visual polish |
| `self_update` | Binary self-update from GitHub Releases |
| `vergen-gix` | Build metadata embedding (build.rs) |
| `tracing` + `tracing-subscriber` | Structured logging and diagnostics |
| `sha2` + `hmac` | Hashing and HMAC for allow-once short codes |
| `flate2` | Gzip compression for history export |

### Release Profile

The release build optimizes for binary size:

```toml
[profile.release]
opt-level = "z"     # Optimize for size (lean binary for distribution)
lto = true          # Link-time optimization
codegen-units = 1   # Single codegen unit for better optimization
panic = "abort"     # Smaller binary, no unwinding overhead
strip = true        # Remove debug symbols
```

### Feature Flags

```toml
[features]
rayon = ["dep:rayon"]           # Rayon data parallelism (optional)
rich-output = ["dep:rich_rust"] # Enable rich_rust for premium terminal output
legacy-output = []              # Keep old rendering (placeholder for gradual migration)
```

---

## Code Editing Discipline

### No Script-Based Changes

**NEVER** run a script that processes/changes code files in this repo. Brittle regex-based transformations create far more problems than they solve.

- **Always make code changes manually**, even when there are many instances
- For many simple changes: use parallel subagents
- For subtle/complex changes: do them methodically yourself

### No File Proliferation

If you want to change something or add a feature, **revise existing code files in place**.

**NEVER** create variations like:
- `mainV2.rs`
- `main_improved.rs`
- `main_enhanced.rs`

New 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**.

---

## Backwards Compatibility

We 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**.

- Never create "compatibility shims"
- Never create wrapper functions for deprecated APIs
- Just fix the code directly

---

## Compiler Checks (CRITICAL)

**After any substantive code changes, you MUST verify no errors were introduced:**

```bash
# Check for compiler errors and warnings
cargo check --all-targets

# Check for clippy lints (pedantic + nursery are enabled)
cargo clippy --all-targets -- -D warnings

# Verify formatting
cargo fmt --check
```

If you see errors, **carefully understand and resolve each issue**. Read sufficient context to fix them the RIGHT way.

---

## Windows Support (native, `x86_64-pc-windows-msvc`)

dcg ships a **native Windows** binary (built/tested on `windows-latest` with the
nightly toolchain) and a `check (windows)` CI job. When touching anything
platform-sensitive, follow these conventions:

- **Separate command-pattern DATA from dcg's own paths.** Destructive-command
  patterns (`rm -rf /`, `normalize.rs` stripping `/usr/bin/git`, `/etc`, `/tmp`)
  are DATA about Unix commands and must STAY β€” Windows users still run git-bash.
  Only dcg's *own* config/state paths get Windows-ified (resolve via the `dirs`
  crate; the system layer is `%ProgramData%\dcg`, helper `config::system_config_dir()`).
- **`.exe` suffix.** When constructing a path to the dcg binary, use
  `env!("CARGO_BIN_EXE_dcg")` / `assert_cmd::cargo::cargo_bin("dcg")` in tests, or
  `std::env::consts::EXE_SUFFIX` in `src`. **Never** a bare `push("dcg")` β€” the
  Windows CI job greps for it and fails. Resolve per-user paths only through
  `config::home_dir()` / `user_config_dir()` / `user_data_dir()` /
  `user_data_local_dir()` / `user_cache_dir()` β€” never `dirs::*` (clippy's
  `disallowed-methods` rejects it) and never raw `HOME` (unset on Windows).
  `dirs` resolves the Windows profile via the known-folder API and ignores
  `USERPROFILE`, so a sandboxed Windows test run used to rewrite the operator's
  real `~\.claude\settings.json` and allow-once store (bd-b2b1). Set
  `USERPROFILE`/`TEMP`/`TMP` alongside `HOME` in test isolation; `USERPROFILE`
  alone now relocates `%APPDATA%`/`%LOCALAPPDATA%` too when those are unset.
- **Verify Windows branches from Linux** without a Windows box: `mingw` + the
  `x86_64-pc-windows-gnu` target are installed, so
  `cargo check --target x86_64-pc-windows-gnu --lib` (or `--bin dcg` / `--tests`)
  compile-checks every `#[cfg(windows)]` path. When `pwsh` is installed, use it
  to run the PowerShell installer/test scripts; otherwise run those gates on the
  native Windows release host.
- **Windows packs.** `src/packs/windows/` holds the native-Windows packs
  (`windows.filesystem`/`windows.system` default-ON on Windows, `windows.misc`/
  `windows.powershell` opt-in). Patterns use inline `(?i)`; keyword arrays
  may retain conventional casing variants for readability, but keyword
  quick-rejection is ASCII case-insensitive so mixed-case Windows spellings
  cannot skip the regex stage (see `src/packs/windows/mod.rs`). See
  [`docs/windows.md`](docs/windows.md).
- **The `careful_company_running_windows` preset.**
  `src/packs/careful_company_running_windows/` holds six opt-in sub-packs
  covering **outbound communication and data egress** (email, chat/webhooks,
  HTTP upload, file transfer, tunnels) plus **tampering with the controls that
  supervise the agent** (Defender/firewall/EDR, audit logs, and dcg's own
  bypass/uninstall). It is the only pack ID with *curated transitive
  membership*: enabling it also enables the pinned
  `CAREFUL_COMPANY_PRESET_MEMBERS` list in `src/packs/mod.rs`
  (`windows.*`, `database.*`, `storage.*`, `remote.*`, `backup.*`, `secrets.*`,
  `cloud.*`). That list is deliberately explicit β€” do **not** convert it to
  prefix matching, or future packs will join a security posture silently.
  Two invariants to preserve when touching this area:
  - **Tier order.** `windows.*` is tier 11 and the preset is tier 12, so
    Windows packs keep claiming attribution for commands both match. Rule ids
    (`pack_id:pattern_name`) are what allowlists key on, so reordering these
    silently invalidates existing `windows.*` allowlist entries.
  - **The `hfdt` trust boundary.** While any preset sub-pack is enabled,
    `src/evaluator.rs` allows a command whose executable is `hfdt` **before any
    pack runs** β€” including `core.*`. It is structural (whole-segment
    executable match, no chains/redirection/substitution), but it does mean
    enabling the preset *reduces* coverage for that one executable. Keep it
    documented in `README.md` and `docs/careful-company-windows.md`.

---

## Testing

### Testing Policy

Every module includes inline `#[cfg(test)]` unit tests alongside the implementation. Tests must cover:
- Happy path
- Edge cases (empty input, max values, boundary conditions)
- Error conditions

End-to-end tests live in `scripts/e2e_test.sh`.

### Unit Tests

The test suite includes 80+ tests covering all functionality:

```bash
# Run all tests
cargo test

# Run with output
cargo test -- --nocapture

# Run specific test module
cargo test normalize_command_tests
cargo test safe_pattern_tests
cargo test destructive_pattern_tests
```

**Prefer `cargo nextest run` when touching anything that mutates the
environment.** CI runs `cargo nextest run --profile ci`, and nextest gives each
test its own process β€” verified by observation, not assumption: sampling a
`--lib` run showed up to 128 concurrent per-test `--exact` invocations. That
process isolation is what makes the `unsafe { env::set_var }` sites in test code
sound, because it removes the concurrent reader.

Under plain `cargo test` every test shares one process and runs on threads, and
the crate has ~109 `env::var` call sites against ~22 `EnvVarGuard` uses, none of
the readers taking the lock β€” plus native code (bundled SQLite reading `TMPDIR`)
that cannot take a Rust lock even in principle. So `cargo test` is the
configuration the single crate-wide `ENV_LOCK` cannot actually make safe (#445).
No failure has been observed from this; the race is available, not active.

`cargo test` stays documented above because filter and `--ignored` syntax differ
between the two runners (nextest wants `--run-ignored`), and several workflows in
this file depend on the `cargo test` spelling.

### The Three Release-Blocking E2E Suites (read before touching perf or protocols)

`cargo test` cannot catch the failure modes that have actually broken users.
Three real-binary, no-mock suites exist specifically to close those gaps. All
three must be green before any release.

| Suite | Catches | Why unit tests can't |
|-------|---------|----------------------|
| `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. |
| `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. |
| `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. |

```bash
# Protocol/bridge conformance for all 8 harnesses (needs a release binary + jq)
./scripts/e2e_harness_matrix.sh --binary target/release/dcg

# Absolute evaluator-cost gate β€” the #245 guard. Budget MUST come from src/perf.rs.
BUDGET_MS=$(sed -nE 's/^pub const HOOK_EVALUATION_BUDGET_MS: u64 = ([0-9_]+);$/\1/p' src/perf.rs | tr -d '_')
python3 scripts/perf_baseline.py --bin target/release/dcg --skip-trace \
  --assert-budget-ms "$BUDGET_MS" --assert-margin-pct 50

# Real installs from the PUBLIC release on every DSR host
./scripts/e2e_fleet_install.sh --version vX.Y.Z          # whole fleet
./scripts/e2e_fleet_install.sh --version vX.Y.Z --local-only
```

Rules:
- **Scrub ambient `DCG_*` before measuring anything.** Operators bitten by #245
  export `DCG_HOOK_TIMEOUT_MS=5000` (an agent `settings.json` `env` block puts
  it in every child process), so an un-scrubbed suite measures the *workaround*
  and passes on exactly the machines that need protecting. `env -i` covers the
  hook calls; the installer cannot use it (it needs the host PATH for
  `curl`/`tar`/`xz`/`minisign`), so the probes also `unset` every `DCG_*` up
  front. Assert `general.hook_timeout_source` too β€” a bare `>= 1000` check
  cannot tell the shipped default from an inherited 5000.
- **Set `DCG_SELF_HEAL_HOOK=0` before the installer runs, not after.** dcg
  repairs a missing/stale hook entry whenever it runs in hook mode, and
  released binaries up to v0.14.4 resolve the Windows settings path via the
  Win32 known-folder API, which `USERPROFILE` cannot redirect β€” so a late
  disable can rewrite a real machine's agent config. Current source honors
  `USERPROFILE` (bd-b2b1), but the installer may still fetch an older release.
- **Never hard-code the budget in `.github/workflows/ci.yml`.** It is grepped
  out of `HOOK_EVALUATION_BUDGET_MS`; `perf::tests::ci_enforces_absolute_latency_gate_against_shipped_budget`
  fails if that wiring is removed or the margin is loosened past 60%.
- **Treat the JSON as the certificate, not stderr.** Gate mode records its
  supplied/shipped/effective budgets, margin, derived limit, every per-case
  verdict, 95/95 binomial tail-tolerance result, violations, and overall
  PASS/FAIL in `latency_gate`; CI retains that artifact even when the gate
  fails. Gate mode requires at least 59 samples; CI uses 100 and permits at
  most one over-limit sample per case. When using `--output` in gate mode,
  place it outside the repository; the harness rejects in-tree output so its
  own certificate cannot dirty the source snapshot it claims to measure.
- **Bind the binary to the checkout.** Gate mode requires a clean checkout and
  exact equality between the binary's embedded `git describe --tags --dirty`
  value and the repository's value. CI uses a full tag history so a shallow
  clone cannot turn this proof into an unknown result.
- Measure dcg's own cost as `full_eval βˆ’ DCG_BYPASS`, never raw wall-clock:
  process spawn (β‰ˆ940ms under Windows PowerShell) sits **outside** the
  evaluation deadline and would otherwise produce false alarms. For host
  safety this certificate sets `DCG_SELF_HEAL_HOOK=0`, records that exclusion,
  and therefore does not claim to measure self-healing work. Capture and
  validate every timed child's actual wire decision after stopping its timer;
  before/after semantic controls alone cannot catch intermittent fail-open
  behavior inside the sample window.
- The fleet suite installs into a scratch prefix with an isolated `HOME` and
  `--no-configure`; it never touches a host's real agent hook config.
- A probe that dies partway must FAIL, not pass: every probe emits
  `probe_complete` and the runner asserts the full expected case set.

### End-to-End Testing

```bash
# Run the E2E test script (needs bash >= 4; macOS /bin/bash 3.2 breaks the summary)
./scripts/e2e_test.sh

# Or test manually
echo '{"tool_name":"Bash","tool_input":{"command":"git reset --hard"}}' | cargo run --release
# Should output JSON denial

echo '{"tool_name":"Bash","tool_input":{"command":"git status"}}' | cargo run --release
# Should output nothing (allowed)
```

### Test Categories

| Module | Tests | Purpose |
|--------|-------|---------|
| `normalize_command_tests` | 8 | Path stripping for git/rm binaries |
| `quick_reject_tests` | 5 | Fast-path filtering for non-git/rm commands |
| `safe_pattern_tests` | 16 | Whitelist accuracy |
| `destructive_pattern_tests` | 20 | Blacklist coverage |
| `input_parsing_tests` | 8 | JSON parsing robustness |
| `deny_output_tests` | 2 | Output format validation |
| `integration_tests` | 4 | End-to-end pipeline |
| `optimization_tests` | 9 | Performance paths |
| `edge_case_tests` | 24 | Real-world edge cases |

---

## Third-Party Library Usage

If you aren't 100% sure how to use a third-party library, **SEARCH ONLINE** to find the latest documentation and current best practices.

---

## dcg (Destructive Command Guard) β€” This Project

**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.

### What It Does

Guards 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.

### Architecture

```
JSON Input β†’ Parse β†’ Quick Reject (memchr) β†’ Normalize β†’ Safe Patterns β†’ Destructive Patterns β†’ Default Allow
```

### Key Files

| File | Purpose |
|------|---------|
| `src/main.rs` | Entry point, hook I/O, CLI dispatch |
| `src/evaluator.rs` | Pattern matching engine (safe + destructive evaluation) |
| `src/hook.rs` | Claude Code PreToolUse hook protocol handling |
| `src/normalize.rs` | Command normalization (path stripping, alias expansion) |
| `src/heredoc.rs` | Heredoc and inline script extraction |
| `src/ast_matcher.rs` | AST-based pattern matching for embedded code |
| `src/config.rs` | Configuration loading (TOML, allowlists, pack enable/disable) |
| `src/allowlist.rs` | Allowlist management (project, user, system scopes) |
| `src/cli.rs` | CLI commands (explain, scan, packs, allowlist, etc.) |
| `src/scan.rs` | Codebase scanning for destructive patterns |
| `src/context.rs` | Contextual analysis for pattern matching |
| `src/confidence.rs` | Match confidence scoring |
| `src/error_codes.rs` | Standardized DCG-XXXX error codes |
| `src/exit_codes.rs` | Process exit code definitions |
| `src/packs/` | Modular pattern pack system (core + extensions) |
| `src/output/` | Output formatting (JSON, colorful stderr) |
| `src/highlight.rs` | Syntax highlighting for command display |
| `src/logging.rs` | Tracing/logging configuration |
| `src/perf.rs` | Performance budgets and benchmarks |
| `src/simulate.rs` | Command simulation and dry-run support |
| `src/mcp.rs` | MCP server integration |
| `src/agent.rs` | Agent detection and identification |
| `src/interactive.rs` | Interactive mode |
| `src/git.rs` | Git-specific command analysis |
| `src/history/` | Decision history and telemetry |
| `src/sarif.rs` | SARIF output format for scan results |
| `src/pending_exceptions.rs` | Pending exception management |
| `src/lib.rs` | Library re-exports |
| `Cargo.toml` | Dependencies and release optimizations |
| `build.rs` | Build script for version metadata (vergen) |
| `rust-toolchain.toml` | Nightly toolchain requirement |
| `scripts/e2e_test.sh` | End-to-end test script (hundreds of command scenarios) |

### Output Style

This tool has two output modes:

- **JSON to stdout:** For Claude Code hook protocol (`hookSpecificOutput` with `permissionDecision: "deny"`)
- **Colorful warning to stderr:** For human visibility when commands are blocked

Output behavior:
- **Deny:** Colorful warning to stderr + JSON to stdout
- **Allow:** No output (silent exit)
- **--version/-V:** Version info with build metadata to stderr
- **--help/-h:** Usage information to stderr

Colors are automatically disabled when stderr is not a TTY (e.g., piped to file).

### Pattern System

- **34 safe patterns** (whitelist, checked first)
- **16 destructive patterns** (blacklist, checked second)
- **Default allow** for unmatched commands

### Adding New Patterns

1. Identify the command to block/allow
2. Write a regex using `fancy-regex` syntax (supports lookahead/lookbehind)
3. Add to `SAFE_PATTERNS` or `DESTRUCTIVE_PATTERNS` using the macros:

```rust
// Safe pattern (whitelist)
pattern!("pattern-name", r"regex-here")

// Destructive pattern (blacklist)
destructive!(
    r"regex-here",
    "Human-readable reason for blocking"
)
```

4. Add tests for all variants
5. Run `cargo test` and `./scripts/e2e_test.sh`

### Performance Requirements

Every Bash command passes through this hook. Performance is critical:

- Quick rejection filter eliminates 99%+ of commands before regex
- Lazy-initialized static regex patterns (compiled once, reused)
- Sub-millisecond execution for typical commands
- Zero allocations on the hot path for safe commands

### Heredoc Detection Notes

- **Rule IDs**: Heredoc patterns use stable IDs like `heredoc.python.shutil_rmtree` for allowlisting.
- **Bounded failure**: Heredoc parse/AST failures use the configured bounded
  fallback; disabling fallback blocks. Absolute hook-deadline exhaustion and
  incomplete nested evaluation return `Indeterminate`, never `Allow`.
- **Tests**: Prefer targeted tests in `src/ast_matcher.rs` and `src/heredoc.rs`.
  - `cargo test ast_matcher`
  - `cargo test heredoc`
  - Add positive and negative fixtures for each new pattern.

---

<!-- dcg-machine-readable-v1 -->

## DCG Hook Protocol (Machine-Readable Reference)

> This section provides structured documentation for AI agents integrating with dcg.

### JSON Input Format

dcg reads from stdin in Claude Code's `PreToolUse` hook format:

```json
{
  "tool_name": "Bash",
  "tool_input": {
    "command": "git reset --hard HEAD~5"
  }
}
```

**Required fields:**
- `tool_name`: Must be `"Bash"` for dcg to process (other tools are ignored)
- `tool_input.command`: The shell command string to evaluate

### JSON Output Format (Denial)

When a command is blocked, dcg outputs JSON to stdout:

```json
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "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.",
    "ruleId": "core.git:reset-hard",
    "packId": "core.git",
    "severity": "critical",
    "confidence": 1.0,
    "allowOnceCode": "a1b2c3",
    "allowOnceFullHash": "sha256:abc123...",
    "remediation": {
      "safeAlternative": "git stash",
      "explanation": "Use git stash to save your changes first.",
      "allowOnceCommand": "dcg allow-once a1b2c3"
    }
  }
}
```

**Key fields for agent parsing:**
| Field | Type | Description |
|-------|------|-------------|
| `permissionDecision` | `"allow"` \| `"deny"` | The decision |
| `ruleId` | `string` | Stable pattern ID (e.g., `"core.git:reset-hard"`) for allowlisting |
| `packId` | `string` | Pack that matched (e.g., `"core.git"`) |
| `severity` | `string` | `"critical"`, `"high"`, `"medium"`, or `"low"` |
| `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). |
| `allowOnceCode` | `string` | Short code for `dcg allow-once` |
| `remediation.safeAlternative` | `string?` | Suggested safe command |

### JSON Output Format (Allow)

When a command is allowed: **no output** (silent exit 0).

---

## Exit Codes Reference

| Code | Meaning | Agent Action |
|------|---------|--------------|
| `0` | Command allowed OR protocol JSON denial was emitted | Parse stdout; if empty, command was allowed |
| `1` | Parse error or invalid input | Retry with corrected input |
| `2` | Configuration error | Check config syntax and stderr diagnostics |

**Detection logic for agents:**
```bash
output=$(echo "$hook_input" | dcg 2>/dev/null)
if [ -z "$output" ]; then
  echo "ALLOWED"
else
  echo "DENIED: $output"
fi
```

Codex CLI uses a stricter hook parser: blocked commands return a minimal
`hookSpecificOutput` denial on stdout with exit code 0. See
[`docs/codex-integration.md`](docs/codex-integration.md) for the Codex-specific
protocol notes.

---

## Error Codes Reference

DCG uses standardized error codes in the format `DCG-XXXX` for machine-parseable error handling.

### Error Categories

| Range | Category | Description |
|-------|----------|-------------|
| DCG-1xxx | `pattern_match` | Pattern matching and evaluation errors |
| DCG-2xxx | `configuration` | Configuration loading and parsing errors |
| DCG-3xxx | `runtime` | Runtime and execution errors |
| DCG-4xxx | `external` | External integration errors |

### Common Error Codes

| Code | Description | Typical Cause |
|------|-------------|---------------|
| `DCG-1001` | Pattern compilation failed | Invalid regex syntax in pattern |
| `DCG-1002` | Pattern match timeout | Complex pattern taking too long |
| `DCG-2001` | Config file not found | Missing configuration file |
| `DCG-2002` | Config parse error | Invalid TOML/JSON syntax |
| `DCG-2004` | Allowlist load error | Invalid allowlist file |
| `DCG-3001` | JSON parse error | Malformed JSON input |
| `DCG-3002` | IO error | File read/write failure |
| `DCG-4001` | External pack load failed | Invalid external pack YAML |

### Error JSON Structure

When errors are returned in JSON format, they follow this structure:

```json
{
  "error": {
    "code": "DCG-3001",
    "category": "runtime",
    "message": "JSON parse error: unexpected token at position 15",
    "context": {
      "position": 15,
      "input_preview": "{ \"tool_name\": ..."
    }
  }
}
```

**Fields:**
- `code`: Stable error code for programmatic handling
- `category`: Error category (`pattern_match`, `configuration`, `runtime`, `external`)
- `message`: Human-readable error description
- `context`: Additional details (optional, varies by error type)

---

## Allowlist & Bypass Instructions

### Temporary Bypass (24-hour allow-once)

When a command is blocked, the output includes an `allowOnceCode`. Use it:

```bash
dcg allow-once <code>
```

This allows the specific command for 24 hours in the current directory scope.

### Permanent Allowlist (by rule ID)

Add a rule to the project allowlist:

```bash
dcg allowlist add <ruleId> --project
# Example: dcg allowlist add core.git:reset-hard --project
```

Allowlist files (in priority order):
1. `.dcg/allowlist.toml` (project)
2. `~/.config/dcg/allowlist.toml` (user)
3. `/etc/dcg/allowlist.toml` (system)

### Bypass Environment Variable

For emergency bypass (use sparingly):

```bash
DCG_BYPASS=1 <command>
```

**Warning:** This disables all protection. Log and justify any usage.

---

## Pattern Quick Reference

### Core Git Patterns (Always Enabled)

| Pattern ID | Blocks | Severity |
|------------|--------|----------|
| `core.git:reset-hard` | `git reset --hard` | Critical |
| `core.git:reset-merge` | `git reset --merge` | High |
| `core.git:checkout-discard` | `git checkout -- <file>` | High |
| `core.git:restore-discard` | `git restore <file>` (without `--staged`) | High |
| `core.git:clean-force` | `git clean -f`, `git clean -fd` | High |
| `core.git:force-push` | `git push --force`, `git push -f` | High |
| `core.git:branch-delete` | `git branch -d`, `--delete` (merge-checked, no force) | High |
| `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 |
| `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 |
| `core.git:stash-clear` | `git stash clear` | Critical |

### Core Filesystem Patterns (Always Enabled)

| Pattern ID | Blocks | Severity |
|------------|--------|----------|
| `core.filesystem:rm-rf-root` | `rm -rf /`, `rm -rf ~` | Critical |
| `core.filesystem:rm-rf-general` | `rm -rf` outside temp dirs | High |

### Safe Patterns (Whitelist - Always Allowed)

| Pattern | Command | Why Safe |
|---------|---------|----------|
| `git-checkout-branch` | `git checkout -b <branch>` | Creates new branch |
| `git-checkout-orphan` | `git checkout --orphan <branch>` | Creates orphan branch |
| `git-restore-staged` | `git restore --staged <file>` | Only unstages, doesn't discard |
| `git-clean-dry-run` | `git clean -n`, `git clean --dry-run` | Preview only |
| `rm-tmp` | `rm -rf /tmp/*`, `/var/tmp/*` | Temp directory cleanup |

### Pack Enable/Disable Examples

```toml
# ~/.config/dcg/config.toml
[packs]
enabled = [
    "database.postgresql",    # Blocks DROP TABLE, TRUNCATE
    "kubernetes.kubectl",     # Blocks kubectl delete namespace
    "cloud.aws",              # Blocks aws ec2 terminate-instances
]

disabled = [
    "containers.docker",      # Disable Docker protection
]
```

List all packs: `dcg packs --verbose`

---

## CLI Quick Reference for Agents

| Command | Purpose |
|---------|---------|
| `dcg explain "<command>"` | Detailed trace of why command is blocked/allowed |
| `dcg allow-once <code>` | Allow a blocked command for 24 hours |
| `dcg allowlist add <ruleId> --project` | Permanently allow a rule |
| `dcg packs` | List enabled packs |
| `dcg packs --verbose` | List all packs with pattern counts |
| `dcg scan .` | Scan codebase for destructive patterns |
| `dcg --version` | Show version and build info |

---

## Agent Integration Checklist

When integrating with dcg, ensure your agent:

- [ ] Parses stdout for JSON denial responses
- [ ] Handles empty stdout as "command allowed"
- [ ] Uses `ruleId` for stable allowlisting (not pattern text)
- [ ] Displays `remediation.safeAlternative` to users when available
- [ ] Respects `severity` for prioritization (critical > high > medium > low)
- [ ] Uses `dcg explain` before asking users to bypass

---

## JSON Schema Reference

Formal JSON Schema definitions (Draft 2020-12) for all dcg output formats are available in `docs/json-schema/`:

| Schema | Purpose |
|--------|---------|
| [`hook-output.json`](docs/json-schema/hook-output.json) | PreToolUse hook denial response format |
| [`scan-results.json`](docs/json-schema/scan-results.json) | `dcg scan` command output format |
| [`stats-output.json`](docs/json-schema/stats-output.json) | `dcg stats` command output format |
| [`error.json`](docs/json-schema/error.json) | Error response formats for various commands |

Use these schemas for:
- Validating dcg output in automated pipelines
- Generating type-safe client code
- Understanding the complete output contract

<!-- end-dcg-machine-readable -->

---

## CI/CD Pipeline

### Jobs Overview

| Job | Trigger | Purpose | Blocking |
|-----|---------|---------|----------|
| `check` | PR, push | Format, clippy, UBS, tests | Yes |
| `coverage` | PR, push | Coverage thresholds | Yes |
| `memory-tests` | PR, push | Memory leak detection | Yes |
| `benchmarks` | push to main | Performance budgets | Warn only |
| `e2e` | PR, push | End-to-end shell tests | Yes |
| `scan-regression` | PR, push | Scan output stability | Yes |
| `perf-regression` | PR, push | Process-per-invocation perf | Yes |

### Check Job

Runs format, clippy, UBS static analysis, and unit tests. Includes:
- `cargo fmt --check` - Code formatting
- `cargo clippy --all-targets -- -D warnings` - Lints (pedantic + nursery enabled)
- UBS analysis on changed Rust files (warning-only, non-blocking)
- `cargo nextest run` - Full test suite with JUnit XML report

### Coverage Job

Runs `cargo llvm-cov` and enforces the thresholds configured in
`.github/workflows/ci.yml` (`OVERALL_MIN`, `EVALUATOR_MIN`, `HOOK_MIN`).
These are enforced gates, not aspirational targets:
- **Overall:** >= 70%
- **src/evaluator.rs:** >= 65%
- **src/hook.rs:** >= 70%

If CI thresholds change, update this section in the same change. The
`coverage_threshold_docs` test checks that these documented values stay in sync
with the workflow.

Coverage is uploaded to Codecov for trend tracking. Dashboard: https://codecov.io/gh/Dicklesworthstone/destructive_command_guard

### Memory Tests Job

Runs dedicated memory leak tests with:
- `--test-threads=1` for accurate measurements
- Release mode for realistic performance
- 1-2MB growth budgets per test

Tests include: hook input parsing, pattern evaluation, heredoc extraction, file extractors, full pipeline, and a self-test that verifies the framework catches leaks.

### Benchmarks Job

Runs on push to main only (benchmarks are noisy on PRs). Checks performance budgets from `src/perf.rs`:
- Quick reject: < 50us panic
- Fast path: < 500us panic
- Pattern match: < 1ms panic
- Heredoc extract: < 2ms panic
- Full heredoc pipeline: < 20ms panic
- Hook evaluation deadline: 1000ms (exhaustion is indeterminate, never a silent allow)

### UBS Static Analysis

Ultimate 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.

### Dependabot

Automated dependency updates configured in `.github/dependabot.yml`:
- **Cargo dependencies:** Weekly (Monday 9am EST), 5 PR limit
- **GitHub Actions:** Weekly (Monday 9am EST), 3 PR limit
- **Grouping:** Minor/patch updates grouped; serde updates separate (more careful review)

### Debugging CI Failures

#### Coverage Threshold Failure
1. Check which file(s) dropped below threshold in CI output
2. Run `cargo llvm-cov --html` locally to see uncovered lines
3. Add tests for uncovered code paths
4. Download `coverage-report` artifact for full details

#### Memory Test Failure
1. Download `memory-test-output` artifact
2. Check which test failed and growth amount
3. Run locally: `cargo test --test memory_tests --release -- --nocapture --test-threads=1`
4. Profile with valgrind if needed

#### UBS Warnings
1. Check ubs-output.log in CI summary
2. Review flagged issues - may be false positives
3. If valid issues, fix them; if false positives, add to `.ubsignore`

#### E2E Test Failure
1. Download `e2e-artifacts` artifact
2. Check `e2e_output.json` for failing test details
3. Run locally: `./scripts/e2e_test.sh --verbose`
4. The step summary shows the first failure with output

#### Benchmark Regression
1. Download `benchmark-results` artifact
2. Compare against budgets in `src/perf.rs`
3. Profile locally with `cargo bench --bench heredoc_perf`
4. Check for algorithmic regressions in hot path

---

## Release Process

When fixes are ready for release, follow this process:

The steps below describe the normal GitHub Actions path. If Actions cannot run
or a native Windows artifact must be built locally, the more detailed
**Local/DSR Release and Windows Deployment Runbook** near the end of this file
is authoritative.

### 1. Verify CI Passes Locally

```bash
cargo fmt --check
cargo check --all-targets
cargo clippy --all-targets -- -D warnings
cargo test
```

### 2. Commit Changes

```bash
git add -A
git commit -m "fix: description of fixes

- List specific fixes
- Include any breaking changes

Co-Authored-By: Claude Opus 4.5 <[email protected]>"
```

### 3. Bump Version (if needed)

The version in `Cargo.toml` determines the release tag. A version may be reused
after a failed *local, pre-tag* attempt. Once its tag has been pushed or a
release has been published, treat that version as immutable and bump to a new
patch version instead of moving the tag or replacing signed assets.

- **Patch** (0.2.10 -> 0.2.11): Bug fixes, no new features
- **Minor** (0.2.x -> 0.3.0): New features, backward compatible
- **Major** (0.x -> 1.0): Breaking changes

### 4. Push and Trigger Release

```bash
git push origin main
git push origin main:master  # Keep master in sync
```

The `release-automation.yml` workflow will:
1. Detect version change in `Cargo.toml`
2. Create an annotated git tag (e.g., `v0.2.13`)
3. Push the tag, which triggers `dist.yml`

The `dist.yml` workflow will:
1. Run tests and clippy
2. Build binaries for all platforms (Linux x86/ARM, macOS Intel/Apple Silicon, Windows)
3. Create `.tar.xz` archives with SHA256 checksums
4. Sign artifacts with Sigstore (cosign) - creates `.sigstore.json` bundles
5. Upload everything to GitHub Releases

### 5. Verify Release

```bash
gh release list --limit 5
gh release view v0.2.13  # Check assets were uploaded
```

Expected assets per release:
- `dcg-{target}.tar.xz` (Unix) or `.zip` (Windows) - Binary archive
- `<archive>.sha256` - Mandatory per-artifact checksum
- `<archive>.sigstore.json` - Sigstore signature bundle
- `install.sh`, `install.ps1`, and their checksum/Sigstore sidecars
- Manual DSR releases additionally include minisign signatures, SLSA
  provenance, a build manifest, and the pinned public verification keys

### Troubleshooting Failed Releases

If CI fails:
1. Check workflow run: `gh run list --workflow=dist.yml --limit=5`
2. View failed job: `gh run view <run-id>`
3. Fix issues locally, commit, and push again
4. If no public tag or release exists yet, retry the same version; otherwise
   create a new patch release. Never force-move a public release tag.

Common failures:
- **Clippy errors**: Fix lints, ensure `cargo clippy -- -D warnings` passes
- **Test failures**: Run `cargo test` to reproduce
- **Format errors**: Run `cargo fmt` to fix

---

## MCP Agent Mail β€” Multi-Agent Coordination

A 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.

### Why It's Useful

- **Prevents conflicts:** Explicit file reservations (leases) for files/globs
- **Token-efficient:** Messages stored in per-project archive, not in context
- **Quick reads:** `resource://inbox/...`, `resource://thread/...`

### Same Repository Workflow

1. **Register identity:**
   ```
   ensure_project(project_key=<abs-path>)
   register_agent(project_key, program, model)
   ```

2. **Reserve files before editing:**
   ```
   file_reservation_paths(project_key, agent_name, ["src/**"], ttl_seconds=3600, exclusive=true)
   ```

3. **Communicate with threads:**
   ```
   send_message(..., thread_id="FEAT-123")
   fetch_inbox(project_key, agent_name)
   acknowledge_message(project_key, agent_name, message_id)
   ```

4. **Quick reads:**
   ```
   resource://inbox/{Agent}?project=<abs-path>&limit=20
   resource://thread/{id}?project=<abs-path>&include_bodies=true
   ```

### Macros vs Granular Tools

- **Prefer macros for speed:** `macro_start_session`, `macro_prepare_thread`, `macro_file_reservation_cycle`, `macro_contact_handshake`
- **Use granular tools for control:** `register_agent`, `file_reservation_paths`, `send_message`, `fetch_inbox`, `acknowledge_message`

### Common Pitfalls

- `"from_agent not registered"`: Always `register_agent` in the correct `project_key` first
- `"FILE_RESERVATION_CONFLICT"`: Adjust patterns, wait for expiry, or use non-exclusive reservation
- **Auth errors:** If JWT+JWKS enabled, include bearer token with matching `kid`

---

## Beads (br) β€” Dependency-Aware Issue Tracking

Beads 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.

**Important:** `br` is non-invasiveβ€”it NEVER runs git commands automatically. You must manually commit changes after `br sync --flush-only`.

### Conventions

- **Single source of truth:** Beads for task status/priority/dependencies; Agent Mail for conversation and audit
- **Shared identifiers:** Use Beads issue ID (e.g., `br-123`) as Mail `thread_id` and prefix subjects with `[br-123]`
- **Reservations:** When starting a task, call `file_reservation_paths()` with the issue ID in `reason`

### Typical Agent Flow

1. **Pick ready work (Beads):**
   ```bash
   br ready --json  # Choose highest priority, no blockers
   ```

2. **Reserve edit surface (Mail):**
   ```
   file_reservation_paths(project_key, agent_name, ["src/**"], ttl_seconds=3600, exclusive=true, reason="br-123")
   ```

3. **Announce start (Mail):**
   ```
   send_message(..., thread_id="br-123", subject="[br-123] Start: <title>", ack_required=true)
   ```

4. **Work and update:** Reply in-thread with progress

5. **Complete and release:**
   ```bash
   br close 123 --reason "Completed"
   br sync --flush-only  # Export to JSONL (no git operations)
   ```
   ```
   release_file_reservations(project_key, agent_name, paths=["src/**"])
   ```
   Final Mail reply: `[br-123] Completed` with summary

### Mapping Cheat Sheet

| Concept | Value |
|---------|-------|
| Mail `thread_id` | `br-###` |
| Mail subject | `[br-###] ...` |
| File reservation `reason` | `br-###` |
| Commit messages | Include `br-###` for traceability |

---

## bv β€” Graph-Aware Triage Engine

bv 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.

**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.

**CRITICAL: Use ONLY `--robot-*` flags. Bare `bv` launches an interactive TUI that blocks your session.**

### The Workflow: Start With Triage

**`bv --robot-triage` is your single entry point.** It returns:
- `quick_ref`: at-a-glance counts + top 3 picks
- `recommendations`: ranked actionable items with scores, reasons, unblock info
- `quick_wins`: low-effort high-impact items
- `blockers_to_clear`: items that unblock the most downstream work
- `project_health`: status/type/priority distributions, graph metrics
- `commands`: copy-paste shell commands for next steps

```bash
bv --robot-triage        # THE MEGA-COMMAND: start here
bv --robot-next          # Minimal: just the single top pick + claim command
```

### Command Reference

**Planning:**
| Command | Returns |
|---------|---------|
| `--robot-plan` | Parallel execution tracks with `unblocks` lists |
| `--robot-priority` | Priority misalignment detection with confidence |

**Graph Analysis:**
| Command | Returns |
|---------|---------|
| `--robot-insights` | Full metrics: PageRank, betweenness, HITS, eigenvector, critical path, cycles, k-core, articulation points, slack |
| `--robot-label-health` | Per-label health: `health_level`, `velocity_score`, `staleness`, `blocked_count` |
| `--robot-label-flow` | Cross-label dependency: `flow_matrix`, `dependencies`, `bottleneck_labels` |
| `--robot-label-attention [--attention-limit=N]` | Attention-ranked labels |

**History & Change Tracking:**
| Command | Returns |
|---------|---------|
| `--robot-history` | Bead-to-commit correlations |
| `--robot-diff --diff-since <ref>` | Changes since ref: new/closed/modified issues, cycles |

**Other:**
| Command | Returns |
|---------|---------|
| `--robot-burndown <sprint>` | Sprint burndown, scope changes, at-risk items |
| `--robot-forecast <id\|all>` | ETA predictions with dependency-aware scheduling |
| `--robot-alerts` | Stale issues, blocking cascades, priority mismatches |
| `--robot-suggest` | Hygiene: duplicates, missing deps, label suggestions |
| `--robot-graph [--graph-format=json\|dot\|mermaid]` | Dependency graph export |
| `--export-graph <file.html>` | Interactive HTML visualization |

### Scoping & Filtering

```bash
bv --robot-plan --label backend              # Scope to label's subgraph
bv --robot-insights --as-of HEAD~30          # Historical point-in-time
bv --recipe actionable --robot-plan          # Pre-filter: ready to work
bv --recipe high-impact --robot-triage       # Pre-filter: top PageRank
bv --robot-triage --robot-triage-by-track    # Group by parallel work streams
bv --robot-triage --robot-triage-by-label    # Group by domain
```

### Understanding Robot Output

**All robot JSON includes:**
- `data_hash` β€” Fingerprint of source beads.jsonl
- `status` β€” Per-metric state: `computed|approx|timeout|skipped` + elapsed ms
- `as_of` / `as_of_commit` β€” Present when using `--as-of`

**Two-phase analysis:**
- **Phase 1 (instant):** degree, topo sort, density
- **Phase 2 (async, 500ms timeout):** PageRank, betweenness, HITS, eigenvector, cycles

### jq Quick Reference

```bash
bv --robot-triage | jq '.quick_ref'                        # At-a-glance summary
bv --robot-triage | jq '.recommendations[0]'               # Top recommendation
bv --robot-plan | jq '.plan.summary.highest_impact'        # Best unblock target
bv --robot-insights | jq '.status'                         # Check metric readiness
bv --robot-insights | jq '.Cycles'                         # Circular deps (must fix!)
```

---

## UBS β€” Ultimate Bug Scanner

**Golden Rule:** `ubs <changed-files>` before every commit. Exit 0 = safe. Exit >0 = fix & re-run.

### Commands

```bash
ubs file.rs file2.rs                    # Specific files (< 1s) β€” USE THIS
ubs $(git diff --name-only --cached)    # Staged files β€” before commit
ubs --only=rust,toml src/               # Language filter (3-5x faster)
ubs --ci --fail-on-warning .            # CI mode β€” before PR
ubs .                                   # Whole project (ignores target/, Cargo.lock)
```

### Output Format

```
Warning  Category (N errors)
    file.rs:42:5 - Issue description
    Suggested fix
Exit code: 1
```

Parse: `file:line:col` -> location | fix hint -> how to fix | Exit 0/1 -> pass/fail

### Fix Workflow

1. Read finding