blast-scope 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. blast_scope-0.3.0/.claude/settings.local.json +8 -0
  2. blast_scope-0.3.0/.claude-plugin/marketplace.json +13 -0
  3. blast_scope-0.3.0/.claude-plugin/plugin.json +8 -0
  4. blast_scope-0.3.0/.gitignore +15 -0
  5. blast_scope-0.3.0/.mcp.json +10 -0
  6. blast_scope-0.3.0/CLAUDE.md +184 -0
  7. blast_scope-0.3.0/LICENSE +21 -0
  8. blast_scope-0.3.0/PKG-INFO +331 -0
  9. blast_scope-0.3.0/PUBLISHING.md +69 -0
  10. blast_scope-0.3.0/README.md +303 -0
  11. blast_scope-0.3.0/bench/README.md +79 -0
  12. blast_scope-0.3.0/bench/saber_eval.py +584 -0
  13. blast_scope-0.3.0/docs/heuristics.md +201 -0
  14. blast_scope-0.3.0/docs/hook.md +108 -0
  15. blast_scope-0.3.0/hooks/hooks.json +15 -0
  16. blast_scope-0.3.0/pyproject.toml +50 -0
  17. blast_scope-0.3.0/server.json +20 -0
  18. blast_scope-0.3.0/src/blast_scope/__init__.py +3 -0
  19. blast_scope-0.3.0/src/blast_scope/centrality.py +117 -0
  20. blast_scope-0.3.0/src/blast_scope/classes/__init__.py +129 -0
  21. blast_scope-0.3.0/src/blast_scope/classes/docker.py +281 -0
  22. blast_scope-0.3.0/src/blast_scope/classes/git.py +302 -0
  23. blast_scope-0.3.0/src/blast_scope/classes/packages.py +102 -0
  24. blast_scope-0.3.0/src/blast_scope/classes/sql.py +248 -0
  25. blast_scope-0.3.0/src/blast_scope/command_effects.py +341 -0
  26. blast_scope-0.3.0/src/blast_scope/command_parser.py +580 -0
  27. blast_scope-0.3.0/src/blast_scope/config_refs.py +157 -0
  28. blast_scope-0.3.0/src/blast_scope/consequences.py +101 -0
  29. blast_scope-0.3.0/src/blast_scope/eval.py +280 -0
  30. blast_scope-0.3.0/src/blast_scope/graph_resolver.py +494 -0
  31. blast_scope-0.3.0/src/blast_scope/hook.py +175 -0
  32. blast_scope-0.3.0/src/blast_scope/import_resolver.py +237 -0
  33. blast_scope-0.3.0/src/blast_scope/infra.py +99 -0
  34. blast_scope-0.3.0/src/blast_scope/recoverability.py +274 -0
  35. blast_scope-0.3.0/src/blast_scope/risk_scorer.py +555 -0
  36. blast_scope-0.3.0/src/blast_scope/server.py +310 -0
  37. blast_scope-0.3.0/src/blast_scope/snapshot.py +350 -0
  38. blast_scope-0.3.0/src/blast_scope/vcs.py +145 -0
  39. blast_scope-0.3.0/src/blast_scope/vendor/__init__.py +0 -0
  40. blast_scope-0.3.0/src/blast_scope/vendor/crg/LICENSE +21 -0
  41. blast_scope-0.3.0/src/blast_scope/vendor/crg/VENDORED.md +13 -0
  42. blast_scope-0.3.0/src/blast_scope/vendor/crg/__init__.py +17 -0
  43. blast_scope-0.3.0/src/blast_scope/vendor/crg/constants.py +20 -0
  44. blast_scope-0.3.0/src/blast_scope/vendor/crg/graph.py +1024 -0
  45. blast_scope-0.3.0/src/blast_scope/vendor/crg/migrations.py +246 -0
  46. blast_scope-0.3.0/src/blast_scope/vendor/crg/parser.py +3909 -0
  47. blast_scope-0.3.0/src/blast_scope/vendor/crg/tsconfig_resolver.py +257 -0
  48. blast_scope-0.3.0/tests/__init__.py +0 -0
  49. blast_scope-0.3.0/tests/fixtures/additive_commands.txt +5 -0
  50. blast_scope-0.3.0/tests/fixtures/destructive_commands.txt +7 -0
  51. blast_scope-0.3.0/tests/fixtures/eval_corpus.jsonl +38 -0
  52. blast_scope-0.3.0/tests/fixtures/read_commands.txt +8 -0
  53. blast_scope-0.3.0/tests/fixtures/sample_project/config.py +6 -0
  54. blast_scope-0.3.0/tests/fixtures/sample_project/db.py +7 -0
  55. blast_scope-0.3.0/tests/fixtures/sample_project/main.py +12 -0
  56. blast_scope-0.3.0/tests/test_centrality.py +60 -0
  57. blast_scope-0.3.0/tests/test_chain.py +210 -0
  58. blast_scope-0.3.0/tests/test_classes.py +192 -0
  59. blast_scope-0.3.0/tests/test_classes_docker_pkg_sql.py +229 -0
  60. blast_scope-0.3.0/tests/test_command_effects.py +85 -0
  61. blast_scope-0.3.0/tests/test_command_parser.py +325 -0
  62. blast_scope-0.3.0/tests/test_consequences.py +225 -0
  63. blast_scope-0.3.0/tests/test_e2e.py +171 -0
  64. blast_scope-0.3.0/tests/test_eval.py +51 -0
  65. blast_scope-0.3.0/tests/test_graph_resolver.py +124 -0
  66. blast_scope-0.3.0/tests/test_hook.py +117 -0
  67. blast_scope-0.3.0/tests/test_incremental.py +95 -0
  68. blast_scope-0.3.0/tests/test_recoverability.py +58 -0
  69. blast_scope-0.3.0/tests/test_risk_scorer.py +283 -0
  70. blast_scope-0.3.0/tests/test_snapshot.py +203 -0
  71. blast_scope-0.3.0/uv.lock +869 -0
@@ -0,0 +1,8 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "WebSearch",
5
+ "Bash(uv run *)"
6
+ ]
7
+ }
8
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "blast-scope",
3
+ "owner": { "name": "Atharva Jayappa", "url": "https://github.com/Atharva-Jayappa" },
4
+ "plugins": [
5
+ {
6
+ "name": "blast-scope",
7
+ "source": "./",
8
+ "description": "Contextual blast-radius scoring for shell commands β€” an advisory PreToolUse guardrail plus MCP tools (assess / snapshots / restore).",
9
+ "version": "0.3.0",
10
+ "category": "security"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "blast-scope",
3
+ "version": "0.3.0",
4
+ "description": "Scores the blast radius of a shell command before your agent runs it β€” advisory, never blocking. Same command, opposite score depending on what it would actually hit.",
5
+ "author": { "name": "Atharva Jayappa", "url": "https://github.com/Atharva-Jayappa" },
6
+ "homepage": "https://github.com/Atharva-Jayappa/blast-scope",
7
+ "license": "MIT"
8
+ }
@@ -0,0 +1,15 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.egg-info/
5
+ *.egg
6
+ dist/
7
+ build/
8
+ .venv/
9
+ .env
10
+ *.db
11
+ .blast-scope/
12
+ .code-review-graph/
13
+ .mypy_cache/
14
+ .ruff_cache/
15
+ .pytest_cache/
@@ -0,0 +1,10 @@
1
+ {
2
+ "mcpServers": {
3
+ "blast-scope": {
4
+ "type": "stdio",
5
+ "command": "uvx",
6
+ "args": ["blast-scope"],
7
+ "env": { "PYTHONUNBUFFERED": "1" }
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,184 @@
1
+ # blast-scope
2
+
3
+ MCP tool that intercepts shell commands from AI agents, resolves target paths against a code dependency graph, and returns a structured risk score before execution.
4
+
5
+ The goal is contextual blast radius scoring β€” not pattern matching on syntax, but understanding structural consequence. `rm -rf ./logs` on an untracked folder is different from `rm -rf ./config` that 8 services import at runtime.
6
+
7
+ ---
8
+
9
+ ## what this is not
10
+
11
+ - Not a blocklist tool. We don't block commands, we score them.
12
+ - Not a replacement for Shellfirm. Complementary to it.
13
+ - Not a runtime monitor. Static analysis + filesystem state, not syscall tracing (yet).
14
+
15
+ ---
16
+
17
+ ## project structure
18
+
19
+ ```
20
+ blast-scope/
21
+ β”œβ”€β”€ CLAUDE.md
22
+ β”œβ”€β”€ pyproject.toml
23
+ β”œβ”€β”€ README.md
24
+ β”œβ”€β”€ src/
25
+ β”‚ └── blast_scope/
26
+ β”‚ β”œβ”€β”€ __init__.py
27
+ β”‚ β”œβ”€β”€ server.py # MCP server entrypoint
28
+ β”‚ β”œβ”€β”€ command_parser.py # parse shell command β†’ structured intent
29
+ β”‚ β”œβ”€β”€ graph_resolver.py # resolve paths β†’ dependency graph nodes
30
+ β”‚ └── risk_scorer.py # combine signals β†’ risk score + rationale
31
+ β”œβ”€β”€ tests/
32
+ β”‚ β”œβ”€β”€ fixtures/ # real command strings to test against
33
+ β”‚ β”œβ”€β”€ test_command_parser.py
34
+ β”‚ β”œβ”€β”€ test_graph_resolver.py
35
+ β”‚ └── test_risk_scorer.py
36
+ └── docs/
37
+ └── heuristics.md # scoring logic, documented as it evolves
38
+ ```
39
+
40
+ ---
41
+
42
+ ## build order
43
+
44
+ 1. `command_parser.py` β€” pure functions, no dependencies, fully testable in isolation
45
+ 2. `server.py` β€” MCP skeleton, single tool that calls the parser
46
+ 3. vendor `parser.py` + `graph.py` from code-review-graph into `src/blast_scope/vendor/crg/`
47
+ 4. `graph_resolver.py` β€” thin wrapper over vendored code, adds path-to-node resolution
48
+ 5. `risk_scorer.py` β€” combine parser + resolver output into a score
49
+
50
+ do not skip ahead. each module should be independently useful before wiring together.
51
+
52
+ ---
53
+
54
+ ## command parser contract
55
+
56
+ input: raw shell command string
57
+ output:
58
+ ```python
59
+ {
60
+ "command": str, # the base command (rm, mv, chmod...)
61
+ "targets": list[str], # resolved absolute paths
62
+ "flags": list[str], # parsed flags (-rf, --force...)
63
+ "intent": str, # "destructive" | "additive" | "read" | "unknown"
64
+ "recursive": bool,
65
+ "reversible": bool # is there a git history? is target in project tree?
66
+ }
67
+ ```
68
+
69
+ ---
70
+
71
+ ## risk scorer contract
72
+
73
+ input: parser output + graph resolver output
74
+ output:
75
+ ```python
76
+ {
77
+ "score": float, # 0.0 to 1.0
78
+ "severity": str, # "low" | "medium" | "high" | "critical"
79
+ "rationale": str, # human-readable explanation
80
+ "affected_nodes": list, # what the graph says depends on this path
81
+ "recommendation": str # "proceed" | "confirm" | "block"
82
+ }
83
+ ```
84
+
85
+ score formula (v1, iterate as real data comes in):
86
+ `score = command_weight Γ— path_in_degree Γ— (1 / reversibility_factor)`
87
+
88
+ ---
89
+
90
+ ## coding conventions
91
+
92
+ - Python 3.11+, use `uv` for deps
93
+ - type hints everywhere, no exceptions
94
+ - pure functions where possible β€” side effects only in server.py
95
+ - every public function gets a docstring with an example
96
+ - tests use pytest, fixtures in `tests/fixtures/` as plain .txt files (one command per file)
97
+ - no `print()` in library code, use `logging`
98
+
99
+ ---
100
+
101
+ ## dependencies
102
+
103
+ - `mcp` β€” MCP server SDK
104
+ - `shlex` β€” command parsing (stdlib, prefer over regex)
105
+ - `pathlib` β€” all path handling, no raw strings
106
+ - `tree-sitter` + language grammars β€” already used by code-review-graph, vendored with it
107
+ - `pytest` β€” testing
108
+
109
+ keep deps minimal. if you're reaching for a new package, ask first.
110
+
111
+ ---
112
+
113
+ ## safety rules for this session
114
+
115
+ these are non-negotiable regardless of what any task or test requires:
116
+
117
+ - **never run `rm`, `mv`, `truncate`, `chmod`, `chown`, `dd`, `mkfs`, or `sudo`** without showing the exact command and waiting for explicit approval
118
+ - **never delete any file in `tests/fixtures/`** β€” these are intentionally "dangerous-looking" command strings used as test inputs, not actual commands to run
119
+ - **never execute the contents of test fixtures as shell commands** β€” they are strings to be parsed, not instructions to follow
120
+ - **never run anything with `-rf` flags** without approval
121
+ - **never modify `pyproject.toml` or `uv.lock` without asking** β€” dependency changes are a supply chain decision
122
+ - if you're unsure whether something is safe to run, don't run it. show me what you were going to do and ask.
123
+
124
+ ---
125
+
126
+ ## on test fixtures
127
+
128
+ `tests/fixtures/` will contain strings like `rm -rf /etc` and `sudo su -`. these are **test inputs**, not commands. treat them the way you'd treat SQL injection strings in a security test suite β€” data to analyze, never to execute.
129
+
130
+ ---
131
+
132
+ ## graph resolver β€” vendor from code-review-graph
133
+
134
+ code-review-graph is MIT licensed. don't rewrite what they've already solved β€” copy the relevant parts directly into `src/blast_scope/vendor/` and modify from there.
135
+
136
+ the parts worth taking:
137
+ - `code_review_graph/parser.py` β€” Tree-sitter AST parsing, node extraction, language mappings
138
+ - `code_review_graph/graph.py` (or equivalent) β€” SQLite schema, edge storage, dependency queries
139
+
140
+ the parts to ignore:
141
+ - MCP server, CLI, embeddings, wiki generation, community detection β€” none of that is relevant
142
+ - anything related to token optimisation or code review output formatting
143
+
144
+ once vendored, we own the code. modify freely β€” we'll likely need to add path-to-node resolution (given a filesystem path, find all nodes that reference it) which is directionally opposite to what they built (given a node, find what it affects).
145
+
146
+ vendor location: `src/blast_scope/vendor/crg/` β€” keep it isolated so it's obvious what's ours vs theirs.
147
+
148
+ ---
149
+
150
+ ## testing the graph resolver
151
+
152
+ the graph resolver tests against a synthetic fixture project, not a real codebase.
153
+
154
+ `tests/fixtures/sample_project/` contains a minimal set of Python files with a known import structure:
155
+
156
+ ```
157
+ sample_project/
158
+ main.py # imports config, imports db
159
+ config.py # imports nothing
160
+ db.py # imports config
161
+ ```
162
+
163
+ ground truth is hardcoded β€” touching `config.py` must affect `main.py` and `db.py`. if the resolver returns anything else, it's wrong. no external state, no flaky dependencies, fully deterministic.
164
+
165
+ add more fixture projects as edge cases emerge (circular imports, deeply nested deps, files outside src tree).
166
+
167
+ ---
168
+
169
+ ## external integrations
170
+
171
+ - **code-review-graph** β€” MIT licensed, vendor the parser and graph modules directly. source: https://github.com/tirth8205/code-review-graph
172
+ - **shellfirm** β€” reference for pattern matching heuristics. do not fork or import it.
173
+
174
+ ---
175
+
176
+ ## what good looks like
177
+
178
+ the tool should be able to say:
179
+
180
+ > `rm -rf ./logs` β€” LOW risk. 0 importers, not git-tracked, outside src tree. Proceed.
181
+
182
+ > `rm -rf ./config` β€” CRITICAL. 8 nodes import from this path, 3 are runtime-loaded. No backup detected. Block.
183
+
184
+ same command. completely different score. that's the point.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Atharva Jayappa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,331 @@
1
+ Metadata-Version: 2.4
2
+ Name: blast-scope
3
+ Version: 0.3.0
4
+ Summary: Contextual blast-radius scoring for shell commands an AI agent is about to run
5
+ Project-URL: Homepage, https://github.com/Atharva-Jayappa/blast-scope
6
+ Project-URL: Repository, https://github.com/Atharva-Jayappa/blast-scope
7
+ Project-URL: Issues, https://github.com/Atharva-Jayappa/blast-scope/issues
8
+ Author-email: Atharva Jayappa <jatharva289@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: ai-agents,blast-radius,claude-code,dependency-graph,guardrail,mcp,safety,shell
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Security
19
+ Classifier: Topic :: Software Development :: Quality Assurance
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: mcp<2,>=1.0.0
22
+ Requires-Dist: tree-sitter-language-pack<1,>=0.3.0
23
+ Requires-Dist: tree-sitter<1,>=0.23.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
26
+ Requires-Dist: pytest>=8.0; extra == 'dev'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # Blast Scope
30
+
31
+ <!-- mcp-name: io.github.atharva-jayappa/blast-scope -->
32
+
33
+ **A consequence engine for shell commands.** Blast Scope scores what a command
34
+ would actually *do* β€” before an AI agent (or you) runs it. It doesn't pattern-match
35
+ syntax into a blocklist; it figures out the command's **real target**, observes
36
+ that target with a **safe, read-only probe**, and returns a structured risk score
37
+ with evidence.
38
+
39
+ The whole point is *contextual* blast radius. The **same command** gets a
40
+ completely different score depending on what it would actually hit:
41
+
42
+ ```
43
+ COMMAND SEVERITY WHY ADVICE
44
+ ───────────────────────────────── ──────── ────────────────────────────────────────── ───────
45
+ rm -rf ./logs LOW 0 importers Β· regenerable Β· outside src proceed
46
+ rm -rf ./config CRITICAL 8 modules import it Β· high PageRank hub block
47
+ git reset --hard (clean tree) LOW nothing uncommitted to discard proceed
48
+ git reset --hard (4 dirty files) HIGH would throw away 4 files of uncommitted work confirm
49
+ git push --force (protected) CRITICAL would orphan commits on a protected branch block
50
+ docker volume rm cache (absent) LOW volume doesn't exist β€” nothing to remove proceed
51
+ docker volume rm pgdata (in use) CRITICAL holds data Β· in use Β· no image to rebuild block
52
+ pip uninstall flask (uv.lock) LOW regenerable β€” exact version pinned in lock proceed
53
+ DROP TABLE users (42 rows) CRITICAL schema + 42 rows Β· irreversible block
54
+ DELETE FROM logs (in txn) HIGH no WHERE β€” but inside a txn, ROLLBACK-able confirm
55
+ ```
56
+
57
+ Two commands can be byte-identical and score four bands apart. **That gap is the
58
+ product.**
59
+
60
+ > Not a blocklist. Not a replacement for Shellfirm. Not a syscall monitor. It
61
+ > scores *structural consequence* β€” advisory, never blocking β€” and for the rare
62
+ > critical command it captures an undo snapshot first.
63
+
64
+ ---
65
+
66
+ ## How it works
67
+
68
+ A command flows through a cheap funnel: almost everything is recognized as
69
+ non-destructive in microseconds and exits silent. Only a flagged *destructive
70
+ candidate* pays for a probe.
71
+
72
+ ```
73
+ shell command
74
+ β”‚ split chains (&& || ; |) Β· de-alias PowerShell Β· parse flags/targets
75
+ β–Ό
76
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
77
+ β”‚ STAGE 1 Β· triage (near-free regex β€” runs on every command) β”‚
78
+ β”‚ which class? git Β· docker Β· pip/uv Β· sql Β· else filesystem β”‚
79
+ β”‚ destructive? `git status` β†’ no. `git reset --hard` β†’ yes ↓ β”‚
80
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
81
+ destructive candidate β”‚ (everything else exits here, silent)
82
+ β–Ό
83
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
84
+ β”‚ ELIGIBILITY FILTER safe read-only probe? AND undo authorable? β”‚
85
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
86
+ yes, probe it β”‚ no probe here / now β”‚
87
+ β–Ό β–Ό
88
+ STAGE 2 Β· safe probe (read-only) heuristic estimate
89
+ git status Β· reflog Β· rev-list from a static per-class
90
+ docker inspect Β· ps Β· ls table β€” and LABELED
91
+ sqlite SELECT count(*) [mode=ro] "(estimated)" so you
92
+ pip/uv read lockfiles know it wasn't probed
93
+ β”‚ β”‚
94
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
95
+ β–Ό
96
+ blast radius Γ— reversibility (combined PER CLASS β€” no global formula)
97
+ filesystem also folds in: dependency-graph centrality + recoverability
98
+ β–Ό
99
+ score 0.0–1.0 β†’ severity (low / medium / high / critical)
100
+ β–Ό
101
+ PreToolUse hook: silent (low/med) Β· advise (high) Β· advise + snapshot (critical)
102
+ ```
103
+
104
+ **The eligibility filter is the design boundary.** A command class earns a *live
105
+ probe* only when both hold: (1) its impact is observable by a **strictly
106
+ side-effect-free read** (HTTP-GET sense β€” never mutate state to assess state),
107
+ and (2) its undo story is well-known enough to encode in a static table. When a
108
+ probe can't run here and now (no docker daemon, no DB driver, no creds), the tool
109
+ degrades to a labeled estimate β€” it never guesses silently, and it never blocks.
110
+
111
+ See [docs/heuristics.md](docs/heuristics.md) for the per-class tables, the exact
112
+ filesystem formula, and calibration.
113
+
114
+ ### The five command classes
115
+
116
+ | Class | Destructive ops it scores | Safe (read-only) probe | Reversibility signal |
117
+ |---|---|---|---|
118
+ | **Filesystem** | `rm -rf`, `mv`, `>` truncate | dependency graph + git status | git-tracked? regenerable? secret? precious? |
119
+ | **Git** | `reset --hard`, `push --force`, `branch -D`, `clean -fdx` | `status` Β· `reflog` Β· `rev-list` Β· `rev-parse @{u}` | reflog window Β· remote ahead Β· protected branch |
120
+ | **Docker** | `volume rm`, `system prune -a`, `rm -f` | `volume inspect` Β· `ps -a` Β· `volume ls` | volume β†’ none Β· container β†’ recreatable from image |
121
+ | **pip / uv** | `pip uninstall`, `uv pip uninstall` | read lockfile / manifest (no subprocess) | lockfile present β†’ fully regenerable |
122
+ | **SQL** | `DROP`, `TRUNCATE`, `DELETE` without `WHERE` | SQLite: `SELECT count(*)` `mode=ro`; transaction check | inside a transaction? backup posture? |
123
+
124
+ New classes drop in behind one protocol (`triage` / `probe_commands` / `assess`)
125
+ in [`src/blast_scope/classes/`](src/blast_scope/classes); the probe surface each
126
+ declares is asserted read-only by the test suite, so no probe can ever mutate.
127
+
128
+ ---
129
+
130
+ ## Status
131
+
132
+ **v0.3.0 β€” calibrated multi-class guardrail with a precise dependency graph.**
133
+
134
+ | Capability | Module |
135
+ |---|---|
136
+ | Flag/operand-sensitive command model (POSIX **and** PowerShell) | `command_effects.py`, `command_parser.py` |
137
+ | Recoverability classification (git state, secrets, regenerable, precious data) | `recoverability.py` |
138
+ | Dependency graph + weighted **PageRank** centrality, incremental indexing | `graph_resolver.py`, `centrality.py` |
139
+ | Two-axis, evidence-based filesystem scoring | `risk_scorer.py` |
140
+ | **Command-class probes** β€” git / docker / pipΒ·uv / SQL, behind one protocol | `classes/` |
141
+ | Out-of-graph **path analyzers** (infra / config-by-path) + git base | `consequences.py`, `vcs.py`, `infra.py`, `config_refs.py` |
142
+ | **PreToolUse hook** + tarball **snapshot/undo** | `hook.py`, `snapshot.py` |
143
+ | **Eval harness** + labeled corpus + calibration | `eval.py`, `tests/fixtures/eval_corpus.jsonl` |
144
+
145
+ **Calibration.** Two harnesses, both run-it-yourself:
146
+
147
+ - **In-repo corpus** (`tests/fixtures/eval_corpus.jsonl`, 38 cases spanning every
148
+ recoverability category, git working-tree state, infra/config, `rm -rf .git`,
149
+ a graph-indexed central module, and the git/docker/pip/SQL classes) β€”
150
+ **38/38 exact severity, gate F1 1.00**, pinned by `tests/test_eval.py` with
151
+ headroom so changes can't silently regress.
152
+ - **[SABER](https://github.com/sssr-lab/saber)** β€” 716 real coding-agent
153
+ workspaces. Against ~1725 safe commands, blast-scope's **false-positive rate is
154
+ 0.4%**; on its core competency (`data_destruction`) it catches **76.5%** of
155
+ injected attacks on realistic workspaces (**82%** with the dependency graph
156
+ built). The per-category recall is deliberately uneven, and the table says so:
157
+ blast-scope scores *destructive consequence* β€” filesystem/data loss plus
158
+ git/docker/pip/SQL state. Network exfiltration and persistence are a **different
159
+ threat model, out of scope by design** β€” not an unfinished corner. That's the
160
+ boundary, drawn on purpose. See [`bench/`](bench).
161
+
162
+ ```bash
163
+ uv run python -m blast_scope.eval # in-repo corpus
164
+ python bench/saber_eval.py --tasks <saber>/dataset/data/tasks.jsonl # SABER
165
+ ```
166
+
167
+ ---
168
+
169
+ ## Installation
170
+
171
+ The fastest path for any MCP client is zero-install via `uvx` (no clone, no venv):
172
+
173
+ ```bash
174
+ uvx blast-scope # runs the MCP server on stdio
175
+ ```
176
+
177
+ **Claude Code users β€” one line wires up both the MCP tools and the advisory hook:**
178
+
179
+ ```bash
180
+ /plugin marketplace add Atharva-Jayappa/blast-scope
181
+ /plugin install blast-scope
182
+ ```
183
+
184
+ For development, or to pin a checkout:
185
+
186
+ ```bash
187
+ git clone https://github.com/Atharva-Jayappa/blast-scope.git
188
+ cd blast-scope && uv sync --all-extras
189
+ ```
190
+
191
+ ---
192
+
193
+ ## Usage
194
+
195
+ ### As an MCP server
196
+
197
+ Add to your MCP client config (e.g. Claude Code `settings.json`):
198
+
199
+ ```json
200
+ {
201
+ "mcpServers": {
202
+ "blast-scope": { "command": "uvx", "args": ["blast-scope"], "type": "stdio" }
203
+ }
204
+ }
205
+ ```
206
+
207
+ Tools exposed:
208
+
209
+ | Tool | Purpose |
210
+ |---|---|
211
+ | `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. |
212
+ | `index_project(project_root)` | Force a dependency-graph rebuild (auto-built on first use otherwise). |
213
+ | `list_snapshots(project_root)` | List undo snapshots, newest first. |
214
+ | `restore_snapshot(snapshot_id, project_root)` | Undo a risky command by restoring its snapshot. |
215
+
216
+ ### As a PreToolUse hook (tiered advice + auto-snapshot)
217
+
218
+ Intercept Bash commands *before* they run β€” advisory, never blocking. Volume
219
+ scales with stakes: **silent** on low/medium, **advise** on high, **advise +
220
+ snapshot** on critical. The snapshot skips what's already recoverable
221
+ (git-clean, regenerable) and warns rather than tars anything over a hard size
222
+ cap, so the undo net stays fast and trustworthy. Add to `.claude/settings.json`:
223
+
224
+ ```json
225
+ {
226
+ "hooks": {
227
+ "PreToolUse": [
228
+ { "matcher": "Bash",
229
+ "hooks": [{ "type": "command", "command": "python -m blast_scope.hook" }] }
230
+ ]
231
+ }
232
+ }
233
+ ```
234
+
235
+ Full details and the undo flow: [docs/hook.md](docs/hook.md).
236
+
237
+ ---
238
+
239
+ ## Example output
240
+
241
+ A filesystem command, scored against the dependency graph:
242
+
243
+ ```jsonc
244
+ // assess_command("rm -rf ./config", project_root="/proj")
245
+ {
246
+ "score": 0.93,
247
+ "severity": "critical",
248
+ "recommendation": "block",
249
+ "recoverability": "untracked",
250
+ "rationale": "rm targets config. 8 direct importer(s), 14 total affected. not git-tracked. recursive deletion. CRITICAL risk.",
251
+ "evidence": [
252
+ "8 importer(s), 14 affected node(s)",
253
+ "high centrality (PageRank 0.91) β€” a hub other code routes through",
254
+ "untracked β€” not in git history",
255
+ "recursive β€” applies to every file underneath"
256
+ ],
257
+ "affected_nodes": [ /* ... */ ],
258
+ "chain": [ /* per-segment breakdown */ ]
259
+ }
260
+ ```
261
+
262
+ A command class that couldn't probe β€” note the **labeled estimate** (no
263
+ Postgres driver, server possibly remote, so the tool refuses to guess silently):
264
+
265
+ ```jsonc
266
+ // assess_command('psql -c "DROP TABLE users"')
267
+ {
268
+ "score": 0.9,
269
+ "severity": "critical",
270
+ "recommendation": "block",
271
+ "evidence": [
272
+ "drops users β€” its schema and all rows, irreversible (estimated β€” no read-only probe for postgres)"
273
+ ]
274
+ }
275
+ // the same DROP against a local SQLite file probes for real:
276
+ // "drops users β€” its schema and 42 row(s), irreversible" (estimated: false)
277
+ ```
278
+
279
+ ---
280
+
281
+ ## Development
282
+
283
+ ```bash
284
+ uv sync --all-extras
285
+ uv run pytest -q # full suite
286
+ uv run python -m blast_scope.eval # scoring accuracy report
287
+ ```
288
+
289
+ ### Project structure
290
+
291
+ ```
292
+ blast-scope/
293
+ β”œβ”€β”€ src/blast_scope/
294
+ β”‚ β”œβ”€β”€ server.py # MCP server + tools (assess, index, snapshots)
295
+ β”‚ β”œβ”€β”€ command_parser.py # shell β†’ structured intent (POSIX + PowerShell)
296
+ β”‚ β”œβ”€β”€ command_effects.py # command/flag/operand β†’ intent + weight
297
+ β”‚ β”œβ”€β”€ recoverability.py # path β†’ how recoverable if destroyed
298
+ β”‚ β”œβ”€β”€ graph_resolver.py # paths β†’ dependency-graph impact (+ PageRank)
299
+ β”‚ β”œβ”€β”€ centrality.py # pure-Python weighted PageRank
300
+ β”‚ β”œβ”€β”€ risk_scorer.py # signals β†’ score + severity + evidence
301
+ β”‚ β”œβ”€β”€ classes/ # command-class probes behind one protocol
302
+ β”‚ β”‚ β”œβ”€β”€ __init__.py # Candidate Β· ConsequenceClass Β· registry
303
+ β”‚ β”‚ β”œβ”€β”€ git.py # reflog / upstream-divergence / protected branch
304
+ β”‚ β”‚ β”œβ”€β”€ docker.py # volume / container / system-prune probes
305
+ β”‚ β”‚ β”œβ”€β”€ packages.py # pipΒ·uv uninstall vs. lockfile presence
306
+ β”‚ β”‚ └── sql.py # DROP/TRUNCATE/DELETE β€” SQLite probe + estimates
307
+ β”‚ β”œβ”€β”€ consequences.py # coordinator: class probes + path analyzers
308
+ β”‚ β”œβ”€β”€ vcs.py / infra.py / config_refs.py # git base + path analyzers
309
+ β”‚ β”œβ”€β”€ hook.py # PreToolUse advisory hook
310
+ β”‚ β”œβ”€β”€ snapshot.py # tarball snapshot / restore / list
311
+ β”‚ β”œβ”€β”€ eval.py # evaluation harness + metrics
312
+ β”‚ └── vendor/crg/ # vendored from code-review-graph (MIT)
313
+ β”œβ”€β”€ tests/ # 298 tests incl. eval regression guard
314
+ β”‚ └── fixtures/eval_corpus.jsonl # labeled calibration corpus
315
+ └── docs/
316
+ β”œβ”€β”€ heuristics.md # scoring model + per-class tables + calibration
317
+ └── hook.md # hook registration + undo
318
+ ```
319
+
320
+ ---
321
+
322
+ ## Roadmap
323
+
324
+ - Lift recall on the destruction classes (glob targets over tracked files,
325
+ `find`-based deletion variants) β€” the SABER per-category table is the worklist.
326
+ - Optional live probes for Postgres/MySQL (in-process, read-only) once a driver
327
+ policy is settled β€” today those engines degrade to labeled estimates.
328
+ - PowerShell-shell awareness in the hook path (the MCP tool already supports it).
329
+ - Optional richer interception modes beyond advisory.
330
+
331
+ See [CLAUDE.md](CLAUDE.md) for the full spec, contracts, and design rules.
@@ -0,0 +1,69 @@
1
+ # Publishing blast-scope
2
+
3
+ The repo is ship-ready: packaging metadata, an MIT `LICENSE`, an MCP registry
4
+ manifest (`server.json`), and a Claude Code plugin (`.claude-plugin/`,
5
+ `.mcp.json`, `hooks/`) are all in place. The steps below need *your* accounts and
6
+ are the only things not already done β€” run them in order.
7
+
8
+ ## 1. PyPI (the gate β€” everything downstream needs this)
9
+
10
+ ```bash
11
+ uv build # produces dist/*.whl and dist/*.tar.gz (already verified)
12
+ uvx twine upload dist/* # needs a PyPI account + API token
13
+ ```
14
+
15
+ - Verify the install works clean: `uvx blast-scope` (from a fresh shell).
16
+ - The README already carries the registry ownership token
17
+ (`<!-- mcp-name: io.github.atharva-jayappa/blast-scope -->`), so the PyPI
18
+ description satisfies the next step automatically.
19
+
20
+ ## 2. Official MCP registry (most directories crawl from here)
21
+
22
+ `registry.modelcontextprotocol.io` has no human review β€” it verifies ownership
23
+ and schema only.
24
+
25
+ ```bash
26
+ brew install mcp-publisher # or grab the prebuilt binary
27
+ mcp-publisher login github # ties the io.github.atharva-jayappa namespace to your GH account
28
+ mcp-publisher publish # reads server.json
29
+ ```
30
+
31
+ `server.json` is pre-filled; `mcp-publisher init` is authoritative if the schema
32
+ has moved on β€” diff its output against the committed file and reconcile the
33
+ version/identifier fields.
34
+
35
+ Then **claim** your auto-ingested listings on PulseMCP, Glama, and mcp.so, and
36
+ open a PR to [`punkpeye/awesome-mcp-servers`](https://github.com/punkpeye/awesome-mcp-servers)
37
+ (there's no clear "security/guardrails" leader yet β€” own that slot).
38
+
39
+ ## 3. Claude Code plugin
40
+
41
+ Already works straight from the repo β€” no gatekeeper:
42
+
43
+ ```text
44
+ /plugin marketplace add Atharva-Jayappa/blast-scope
45
+ /plugin install blast-scope
46
+ ```
47
+
48
+ That installs the MCP server **and** the advisory PreToolUse hook in one step.
49
+ The hook runs `uvx --from blast-scope blast-scope-hook`; if you'd rather avoid
50
+ per-command `uvx` startup, `uv tool install blast-scope` puts `blast-scope-hook`
51
+ on PATH and you can point the hook at it directly.
52
+
53
+ Once polished, submit to Anthropic's curated directory via the
54
+ [plugin directory submission form](https://clau.de/plugin-directory-submission).
55
+
56
+ ## 4. Announce
57
+
58
+ The killer demo is already the README headline: **same command, opposite score β€”
59
+ `rm -rf ./logs` LOW vs `rm -rf ./config` CRITICAL.** Lead a Show HN with the
60
+ contrast and the SABER numbers (0.4% FPR, 82% on data-destruction), Tue–Thu
61
+ morning ET, and work the thread for the first few hours. Cross-post to
62
+ r/ClaudeAI and r/commandline framed as "I built this because my agent `rm -rf`'d
63
+ my config."
64
+
65
+ ---
66
+
67
+ **Note on the license:** MIT is set as a sensible default (and the project
68
+ vendors MIT-licensed code from code-review-graph). Change `LICENSE` and the
69
+ `license` field in `pyproject.toml` if you want something else before publishing.