{
  "markdown": "# Blast Scope\n\n<!-- mcp-name: io.github.Atharva-Jayappa/blast-scope -->\n\n**A consequence engine for shell commands.** Blast Scope scores what a command\nwould actually *do* — before an AI agent (or you) runs it. It doesn't pattern-match\nsyntax into a blocklist; it figures out the command's **real target**, observes\nthat target with a **safe, read-only probe**, and returns a structured risk score\nwith evidence.\n\nThe whole point is *contextual* blast radius. The **same command** gets a\ncompletely different score depending on what it would actually hit:\n\n```\nCOMMAND                            SEVERITY   WHY                                          ADVICE\n─────────────────────────────────  ────────   ──────────────────────────────────────────  ───────\nrm -rf ./logs                      LOW        0 importers · regenerable · outside src      proceed\nrm -rf ./config                    CRITICAL   8 modules import it · high PageRank hub      block\ngit reset --hard   (clean tree)    LOW        nothing uncommitted to discard               proceed\ngit reset --hard   (4 dirty files) HIGH       would throw away 4 files of uncommitted work confirm\ngit push --force   (protected)     CRITICAL   would orphan commits on a protected branch   block\ndocker volume rm cache  (absent)   LOW        volume doesn't exist — nothing to remove     proceed\ndocker volume rm pgdata (in use)   CRITICAL   holds data · in use · no image to rebuild    block\npip uninstall flask     (uv.lock)  LOW        regenerable — exact version pinned in lock   proceed\nDROP TABLE users        (42 rows)  CRITICAL   schema + 42 rows · irreversible              block\nDELETE FROM logs        (in txn)   HIGH       no WHERE — but inside a txn, ROLLBACK-able    confirm\n```\n\nTwo commands can be byte-identical and score four bands apart. **That gap is the\nproduct.**\n\n> Not a blocklist. Not a replacement for Shellfirm. Not a syscall monitor. It\n> scores *structural consequence* — advisory, never blocking — and for the rare\n> critical command it captures an undo snapshot first.\n\n---\n\n## How it works\n\nA command flows through a cheap funnel: almost everything is recognized as\nnon-destructive in microseconds and exits silent. Only a flagged *destructive\ncandidate* pays for a probe.\n\n```\n  shell command\n      │   split chains (&& || ; |) · de-alias PowerShell · parse flags/targets\n      ▼\n  ┌──────────────────────────────────────────────────────────────────────┐\n  │  STAGE 1 · triage  (near-free regex — runs on every command)          │\n  │     which class?   git · docker · pip/uv · sql · else filesystem       │\n  │     destructive?   `git status` → no.   `git reset --hard` → yes ↓     │\n  └───────────────────────────────┬──────────────────────────────────────┘\n                    destructive candidate │   (everything else exits here, silent)\n                                          ▼\n  ┌──────────────────────────────────────────────────────────────────────┐\n  │  ELIGIBILITY FILTER   safe read-only probe?   AND   undo authorable?   │\n  └──────────────┬──────────────────────────────────────┬─────────────────┘\n         yes, probe it │                       no probe here / now │\n                       ▼                                           ▼\n   STAGE 2 · safe probe (read-only)                    heuristic estimate\n     git  status · reflog · rev-list                   from a static per-class\n     docker  inspect · ps · ls                          table — and LABELED\n     sqlite  SELECT count(*)  [mode=ro]                  \"(estimated)\" so you\n     pip/uv  read lockfiles                              know it wasn't probed\n                       │                                           │\n                       └─────────────────────┬─────────────────────┘\n                                              ▼\n        blast radius  ×  reversibility   (combined PER CLASS — no global formula)\n        filesystem also folds in: dependency-graph centrality + recoverability\n                                              ▼\n              score 0.0–1.0  →  severity (low / medium / high / critical)\n                                              ▼\n        PreToolUse hook:  silent (low/med) · advise (high) · advise + snapshot (critical)\n```\n\n**The eligibility filter is the design boundary.** A command class earns a *live\nprobe* only when both hold: (1) its impact is observable by a **strictly\nside-effect-free read** (HTTP-GET sense — never mutate state to assess state),\nand (2) its undo story is well-known enough to encode in a static table. When a\nprobe can't run here and now (no docker daemon, no DB driver, no creds), the tool\ndegrades to a labeled estimate — it never guesses silently, and it never blocks.\n\nSee [docs/heuristics.md](docs/heuristics.md) for the per-class tables, the exact\nfilesystem formula, and calibration.\n\n### The five command classes\n\n| Class | Destructive ops it scores | Safe (read-only) probe | Reversibility signal |\n|---|---|---|---|\n| **Filesystem** | `rm -rf`, `mv`, `>` truncate | dependency graph + git status | git-tracked? regenerable? secret? precious? |\n| **Git** | `reset --hard`, `push --force`, `branch -D`, `clean -fdx` | `status` · `reflog` · `rev-list` · `rev-parse @{u}` | reflog window · remote ahead · protected branch |\n| **Docker** | `volume rm`, `system prune -a`, `rm -f` | `volume inspect` · `ps -a` · `volume ls` | volume → none · container → recreatable from image |\n| **pip / uv** | `pip uninstall`, `uv pip uninstall` | read lockfile / manifest (no subprocess) | lockfile present → fully regenerable |\n| **SQL** | `DROP`, `TRUNCATE`, `DELETE` without `WHERE` | SQLite: `SELECT count(*)` `mode=ro`; transaction check | inside a transaction? backup posture? |\n\nNew classes drop in behind one protocol (`triage` / `assess`)\nin [`src/blast_scope/classes/`](src/blast_scope/classes); each class confines\n`assess` to strictly side-effect-free reads.\n\n---\n\n## Status\n\n**Calibrated multi-class guardrail with command resolution and a precise dependency graph.**\n\n| Capability | Module |\n|---|---|\n| Flag/operand-sensitive command model (POSIX **and** PowerShell) | `command_effects.py`, `command_parser.py` |\n| **Command resolution** — env/tilde/brace/glob expansion, unset-var hazards, script transparency (`sh -c`, `npm run` + pre/post hooks, script files, Makefile targets), read-only `$(...)` substitution | `resolution.py` |\n| **Dry-run oracles** — `git clean -n` exact lists, reset divergence, checkout clobber preview, `find -delete`→`-print` rewrite, sqlite scoped-DELETE counts, rsync `--dry-run`; oracle targets feed the undo snapshot | `classes/git.py`, `classes/find.py`, `classes/rsync.py`, `classes/sql.py` |\n| Recoverability classification (git state, secrets, regenerable, precious data) | `recoverability.py` |\n| Dependency graph + weighted **PageRank** centrality, incremental indexing | `graph_resolver.py`, `centrality.py` |\n| Two-axis, evidence-based filesystem scoring | `risk_scorer.py` |\n| **Command-class probes** — git / docker / pip·uv / SQL, behind one protocol | `classes/` |\n| Out-of-graph **path analyzers** (infra / config-by-path) + git base | `consequences.py`, `vcs.py`, `infra.py`, `config_refs.py` |\n| **PreToolUse hook** + tarball **snapshot/undo** | `hook.py`, `snapshot.py` |\n| **Eval harness** + labeled corpus + calibration | `eval.py`, `tests/fixtures/eval_corpus.jsonl` |\n\n**Calibration.** Two harnesses, both run-it-yourself:\n\n- **In-repo corpus** (`tests/fixtures/eval_corpus.jsonl`, 58 cases spanning every\n  recoverability category, git working-tree state, infra/config, `rm -rf .git`,\n  a graph-indexed central module, the git/docker/pip/SQL classes, and the\n  resolution layer — unset-var collapses, glob/env-var targets, `sh -c`\n  payloads, npm pre-hooks, opaque wrappers, mass destruction of tracked\n  source) — **58/58 exact severity, gate F1 1.00**, pinned by\n  `tests/test_eval.py` with headroom so changes can't silently regress.\n- **[SABER](https://github.com/sssr-lab/saber)** — 716 real coding-agent\n  workspaces. Against ~1725 safe commands, blast-scope's **false-positive rate is\n  0.58%**; on its core competency (`data_destruction`) it catches **82.4%** of\n  injected attacks on realistic workspaces — on the fast hook path, no graph\n  required, thanks to command resolution (env/glob binding + script\n  transparency). Wrapper transparency also lifts `code_tampering` from ~0% to\n  **50%**. The per-category recall is deliberately uneven, and the table says so:\n  blast-scope scores *destructive consequence* — filesystem/data loss plus\n  git/docker/pip/SQL state. Network exfiltration and persistence are a **different\n  threat model, out of scope by design** — not an unfinished corner. That's the\n  boundary, drawn on purpose. See [`bench/`](bench).\n\n```bash\nuv run python -m blast_scope.eval                 # in-repo corpus\npython bench/saber_eval.py --tasks <saber>/dataset/data/tasks.jsonl   # SABER\n```\n\n---\n\n## Installation\n\nThe fastest path for any MCP client is zero-install via `uvx` (no clone, no venv):\n\n```bash\nuvx blast-scope        # runs the MCP server on stdio\n```\n\n**Claude Code users — one line wires up both the MCP tools and the advisory hook:**\n\n```bash\n/plugin marketplace add Atharva-Jayappa/blast-scope\n/plugin install blast-scope\n```\n\nFor development, or to pin a checkout:\n\n```bash\ngit clone https://github.com/Atharva-Jayappa/blast-scope.git\ncd blast-scope && uv sync --all-extras\n```\n\n---\n\n## Usage\n\n### As an MCP server\n\nAdd to your MCP client config (e.g. Claude Code `settings.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"blast-scope\": { \"command\": \"uvx\", \"args\": [\"blast-scope\"], \"type\": \"stdio\" }\n  }\n}\n```\n\nTools exposed:\n\n| Tool | Purpose |\n|---|---|\n| `assess_command(command, cwd?, project_root?)` | Score a (possibly chained) command. Returns score, severity, rationale, evidence, recoverability, affected nodes, and a per-segment `chain` breakdown. |\n| `index_project(project_root)` | Force a dependency-graph rebuild (auto-built on first use otherwise). |\n| `list_snapshots(project_root)` | List undo snapshots, newest first. |\n| `restore_snapshot(snapshot_id, project_root)` | Undo a risky command by restoring its snapshot. |\n\n### As a hook (tiered advice + auto-snapshot)\n\nIntercept Bash commands *before* they run — advisory, never blocking. Volume\nscales with stakes: **silent** on low/medium, **advise** on high, **advise +\nsnapshot** on critical. The snapshot skips what's already recoverable\n(git-clean, regenerable) and warns rather than tars anything over a hard size\ncap, so the undo net stays fast and trustworthy.\n\nThe hooks also keep the dependency graph alive on their own — no MCP call\nneeded: `SessionStart` cold-builds it in a detached background process, and\nevery `PreToolUse` refreshes it incrementally before scoring (a ~20 ms stat\nsweep when nothing changed), so verdicts track the current tree even after a\nburst of agent edits. Add to `.claude/settings.json`:\n\n```json\n{\n  \"hooks\": {\n    \"SessionStart\": [\n      { \"hooks\": [{ \"type\": \"command\", \"command\": \"python -m blast_scope.hook\" }] }\n    ],\n    \"PreToolUse\": [\n      { \"matcher\": \"Bash\",\n        \"hooks\": [{ \"type\": \"command\", \"command\": \"python -m blast_scope.hook\" }] }\n    ]\n  }\n}\n```\n\nFull details and the undo flow: [docs/hook.md](docs/hook.md).\n\n---\n\n## Example output\n\nA filesystem command, scored against the dependency graph:\n\n```jsonc\n// assess_command(\"rm -rf ./config\", project_root=\"/proj\")\n{\n  \"score\": 0.93,\n  \"severity\": \"critical\",\n  \"recommendation\": \"block\",\n  \"recoverability\": \"untracked\",\n  \"rationale\": \"rm targets config. 8 direct importer(s), 14 total affected. not git-tracked. recursive deletion. CRITICAL risk.\",\n  \"evidence\": [\n    \"8 importer(s), 14 affected node(s)\",\n    \"high centrality (PageRank 0.91) — a hub other code routes through\",\n    \"untracked — not in git history\",\n    \"recursive — applies to every file underneath\"\n  ],\n  \"affected_nodes\": [ /* ... */ ],\n  \"chain\": [ /* per-segment breakdown */ ]\n}\n```\n\nA command class that couldn't probe — note the **labeled estimate** (no\nPostgres driver, server possibly remote, so the tool refuses to guess silently):\n\n```jsonc\n// assess_command('psql -c \"DROP TABLE users\"')\n{\n  \"score\": 0.9,\n  \"severity\": \"critical\",\n  \"recommendation\": \"block\",\n  \"evidence\": [\n    \"drops users — its schema and all rows, irreversible (estimated — no read-only probe for postgres)\"\n  ]\n}\n// the same DROP against a local SQLite file probes for real:\n//   \"drops users — its schema and 42 row(s), irreversible\"   (estimated: false)\n```\n\n---\n\n## Development\n\n```bash\nuv sync --all-extras\nuv run pytest -q              # full suite\nuv run python -m blast_scope.eval   # scoring accuracy report\n```\n\n### Project structure\n\n```\nblast-scope/\n├── src/blast_scope/\n│   ├── server.py            # MCP server + tools (assess, index, snapshots)\n│   ├── command_parser.py    # shell → structured intent (POSIX + PowerShell)\n│   ├── command_effects.py   # command/flag/operand → intent + weight\n│   ├── recoverability.py    # path → how recoverable if destroyed\n│   ├── graph_resolver.py    # paths → dependency-graph impact (+ PageRank)\n│   ├── centrality.py        # pure-Python weighted PageRank\n│   ├── risk_scorer.py       # signals → score + severity + evidence\n│   ├── classes/             # command-class probes behind one protocol\n│   │   ├── __init__.py      #   Candidate · ConsequenceClass · registry\n│   │   ├── git.py           #   reflog / upstream-divergence / protected branch\n│   │   ├── docker.py        #   volume / container / system-prune probes\n│   │   ├── packages.py      #   pip·uv uninstall vs. lockfile presence\n│   │   └── sql.py           #   DROP/TRUNCATE/DELETE — SQLite probe + estimates\n│   ├── consequences.py      # coordinator: class probes + path analyzers\n│   ├── vcs.py / infra.py / config_refs.py   # git base + path analyzers\n│   ├── hook.py              # PreToolUse advisory hook\n│   ├── snapshot.py          # tarball snapshot / restore / list\n│   ├── eval.py              # evaluation harness + metrics\n│   └── vendor/crg/          # vendored from code-review-graph (MIT)\n├── tests/                   # 298 tests incl. eval regression guard\n│   └── fixtures/eval_corpus.jsonl   # labeled calibration corpus\n└── docs/\n    ├── heuristics.md        # scoring model + per-class tables + calibration\n    └── hook.md              # hook registration + undo\n```\n\n---\n\n## Roadmap\n\n- Lift recall on the destruction classes (glob targets over tracked files,\n  `find`-based deletion variants) — the SABER per-category table is the worklist.\n- Optional live probes for Postgres/MySQL (in-process, read-only) once a driver\n  policy is settled — today those engines degrade to labeled estimates.\n- PowerShell-shell awareness in the hook path (the MCP tool already supports it).\n- Optional richer interception modes beyond advisory.\n\nSee [CLAUDE.md](CLAUDE.md) for the full spec, contracts, and design rules.\n\n---\n\n## License\n\n[Apache 2.0](LICENSE) (versions ≤ 0.3.1 were MIT). The vendored\n[code-review-graph](https://github.com/tirth8205/code-review-graph) sources\nremain MIT under their [upstream notice](src/blast_scope/vendor/crg/LICENSE) —\nsee [NOTICE](NOTICE).\n",
  "bytes": 15192,
  "sha": "07d98b93a167c2a52f282e00268c67e04076088e7496b3dabcbfd1f8f6cf1352",
  "repo_slug": "atharva-jayappa/blast-scope",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_atharva_jayappa_blast_scope_4794fcec/readme"
}