codecompass-mcp 3.2.0__tar.gz → 4.0.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.
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/PKG-INFO +36 -18
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/README.md +35 -15
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/codecompass_mcp.egg-info/PKG-INFO +36 -18
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/codecompass_mcp.egg-info/SOURCES.txt +4 -13
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/codecompass_mcp.egg-info/entry_points.txt +1 -1
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/codecompass_mcp.egg-info/requires.txt +0 -2
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/codecompass_mcp.egg-info/top_level.txt +0 -1
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/graph/code_graph_client.py +22 -85
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/graph/code_query_cli.py +23 -119
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/ingestion/code_normalizer.py +2 -8
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/ingestion/code_parser.py +2 -18
- codecompass_mcp-4.0.0/ingestion/enricher.py +351 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/ingestion/file_watcher.py +2 -4
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/ingestion/hierarchy_builder.py +3 -31
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/main.py +269 -131
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/mcp_server.py +69 -82
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/pyproject.toml +3 -5
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/tests/test_accuracy_fixes.py +0 -2
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/tests/test_batch_impact.py +4 -4
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/tests/test_blast_radius.py +2 -2
- codecompass_mcp-4.0.0/tests/test_block_hook.py +71 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/tests/test_code_parser.py +0 -2
- codecompass_mcp-4.0.0/tests/test_enricher.py +150 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/tests/test_lit_css_parser.py +0 -2
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/tests/test_mcp_server.py +3 -0
- codecompass_mcp-3.2.0/graph/cli.py +0 -13
- codecompass_mcp-3.2.0/graph/setup.py +0 -36
- codecompass_mcp-3.2.0/ingestion/chunker.py +0 -61
- codecompass_mcp-3.2.0/ingestion/description_enricher.py +0 -189
- codecompass_mcp-3.2.0/ingestion/entity_resolver.py +0 -98
- codecompass_mcp-3.2.0/ingestion/graph_writer.py +0 -16
- codecompass_mcp-3.2.0/ingestion/reader_agent.py +0 -136
- codecompass_mcp-3.2.0/models/types.py +0 -45
- codecompass_mcp-3.2.0/tests/test_description_enricher.py +0 -95
- codecompass_mcp-3.2.0/tests/test_ingestion.py +0 -93
- codecompass_mcp-3.2.0/utils/__init__.py +0 -0
- codecompass_mcp-3.2.0/utils/formatting.py +0 -24
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/LICENSE +0 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/codecompass_mcp.egg-info/dependency_links.txt +0 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/config.py +0 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/graph/__init__.py +0 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/ingestion/__init__.py +0 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/ingestion/source_context.py +0 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/models/__init__.py +0 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/models/code_types.py +0 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/setup.cfg +0 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/tests/test_php_parser.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecompass-mcp
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.0
|
|
4
4
|
Summary: Structural code context for AI coding agents — a local code knowledge graph for blast radius, impact, deps, dead code, and flow tracing
|
|
5
5
|
Author: CodeCompass contributors
|
|
6
6
|
License: MIT
|
|
@@ -20,10 +20,8 @@ License-File: LICENSE
|
|
|
20
20
|
Requires-Dist: anthropic>=0.40.0
|
|
21
21
|
Requires-Dist: fastmcp>=2.0.0
|
|
22
22
|
Requires-Dist: networkx>=3.0
|
|
23
|
-
Requires-Dist: pypdf2>=3.0.0
|
|
24
23
|
Requires-Dist: python-dotenv>=1.0.0
|
|
25
24
|
Requires-Dist: rich>=13.0.0
|
|
26
|
-
Requires-Dist: tqdm>=4.66.0
|
|
27
25
|
Requires-Dist: tree-sitter>=0.21.0
|
|
28
26
|
Requires-Dist: tree-sitter-python>=0.21.0
|
|
29
27
|
Requires-Dist: tree-sitter-javascript>=0.21.0
|
|
@@ -69,10 +67,8 @@ The graph turns navigation into a cheap, deterministic loop:
|
|
|
69
67
|
|
|
70
68
|
| You have… | Use |
|
|
71
69
|
|---|---|
|
|
72
|
-
| a
|
|
73
|
-
|
|
|
74
|
-
| keywords | `search` |
|
|
75
|
-
| a vague, nameless need | `map` (compact index to reason over) |
|
|
70
|
+
| a concept, name, or pattern | `grep` (regex over graph entities) |
|
|
71
|
+
| the full layout | `tree` |
|
|
76
72
|
|
|
77
73
|
2. **Trace** — a relationship around a known symbol/file:
|
|
78
74
|
|
|
@@ -142,8 +138,6 @@ The server speaks stdio MCP and defaults to the working directory.
|
|
|
142
138
|
```bash
|
|
143
139
|
# discover
|
|
144
140
|
codecompass query --grep "^get_" # regex over graph entities
|
|
145
|
-
codecompass query --search "session cookie" # keyword search
|
|
146
|
-
codecompass query --map # compact {file: [symbols]} index
|
|
147
141
|
|
|
148
142
|
# trace
|
|
149
143
|
codecompass query --impact "Session.send" # callers (disambiguated), with file:line
|
|
@@ -157,6 +151,21 @@ codecompass query --tree # full hierarchy
|
|
|
157
151
|
|
|
158
152
|
Add `--hops N` for traversal depth (start at 1 and follow the one path you need). Add `--rich` for tables.
|
|
159
153
|
|
|
154
|
+
### Enrichment (agent-in-the-loop)
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
codecompass enrich # stage entities for an agent swarm: one-line descriptions + missing call edges
|
|
158
|
+
codecompass enrich --apply # merge the swarm's results into the graph
|
|
159
|
+
codecompass add-entity <name> --file src/a.py --line 9 --description "Async helper"
|
|
160
|
+
codecompass add-call caller callee --line 2
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`enrich` is a bulk, user-triggered pass. `add-entity`/`add-call` are the
|
|
164
|
+
opportunistic version: as an agent reads code and spots something the parser
|
|
165
|
+
missed, it records it immediately. Everything agent-written is marked
|
|
166
|
+
`agent_inferred` and **preserved across re-ingests** — the graph gets better
|
|
167
|
+
with use. Ambiguous call targets are skipped, never guessed.
|
|
168
|
+
|
|
160
169
|
### Flow: `flow` vs `flow-summary`
|
|
161
170
|
|
|
162
171
|
- **`flow`** — lean structure only (node name/kind/file/depth, edge from/to/order/line). What an agent needs to navigate; no embedded source.
|
|
@@ -169,8 +178,6 @@ Add `--hops N` for traversal depth (start at 1 and follow the one path you need)
|
|
|
169
178
|
| Tool | Returns |
|
|
170
179
|
|---|---|
|
|
171
180
|
| `grep(pattern, field, ignore_case)` | Regex search over graph entities |
|
|
172
|
-
| `search(query, kind)` | Keyword search, ranked |
|
|
173
|
-
| `map(include_tests)` | Compact `{file: [symbols]}` index |
|
|
174
181
|
| `impact(symbol, hops)` | Callers/importers, disambiguated, with `resolved` + `line` |
|
|
175
182
|
| `blast_radius(target, hops)` | Files reachable from a file or symbol |
|
|
176
183
|
| `batch_impact(targets, hops)` | Union of blast radii for a multi-file change |
|
|
@@ -181,6 +188,9 @@ Add `--hops N` for traversal depth (start at 1 and follow the one path you need)
|
|
|
181
188
|
| `dead_code(include_entrypoints)` | Entities with no inbound caller |
|
|
182
189
|
| `styles(element)` | CSS selectors that style an element |
|
|
183
190
|
| `tree()` | Full project hierarchy |
|
|
191
|
+
| `enrich(apply, batch_size)` | Stage/merge agent-written descriptions + missing call edges |
|
|
192
|
+
| `add_entity(name, kind, file, line, description)` | Record a parser-missed entity (`agent_inferred`) |
|
|
193
|
+
| `add_call(caller, callee, line)` | Record a parser-missed CALLS edge (`agent_inferred`) |
|
|
184
194
|
| `set_repo` / `get_repo` / `init` / `ingest` | Project selection & indexing |
|
|
185
195
|
|
|
186
196
|
---
|
|
@@ -208,11 +218,14 @@ are language-agnostic.
|
|
|
208
218
|
`AGENTS.md` guides any agent through the discover→trace→read→edit loop. For
|
|
209
219
|
Claude Code and [pi](https://pi.dev), `init` also installs a `PreToolUse` hook
|
|
210
220
|
that **blocks code *search*** (`grep`/`rg`, the `Grep`/`Glob` tools) and
|
|
211
|
-
**whole-file `cat
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
is
|
|
221
|
+
**whole-file `cat` — but only inside a codecompass-registered repo** (tracked in
|
|
222
|
+
`~/.codecompass/repos`, one line per `init`'d project). Reads outside any
|
|
223
|
+
registered repo pass through: no graph exists there, so nothing is blocked.
|
|
224
|
+
**Targeted reads stay free** (the `Read` tool, `sed -n`, `head`/`tail`). The
|
|
225
|
+
point is to change the default reflex to graph-first, not to remove reads.
|
|
226
|
+
Each project's Claude hook lives under its own `.claude/hooks/` with the
|
|
227
|
+
project root baked in — edit or delete it to adjust. Block messages point the
|
|
228
|
+
agent at the right repo's graph: `codecompass query \"<repo>\" --grep …`.
|
|
216
229
|
|
|
217
230
|
---
|
|
218
231
|
|
|
@@ -224,8 +237,11 @@ Source files
|
|
|
224
237
|
▼ code_parser tree-sitter extraction (no API calls) → typed CodeTriples
|
|
225
238
|
▼ graph.json NetworkX MultiDiGraph as JSON; file+class-qualified nodes,
|
|
226
239
|
typed edges (CALLS/IMPORTS/INHERITS/STYLES/…), resolved calls
|
|
227
|
-
▼ code_query_cli traversal: grep /
|
|
240
|
+
▼ code_query_cli traversal: grep / impact / blast-radius /
|
|
228
241
|
deps / flow / dead-code / tree
|
|
242
|
+
▼ enricher agent-in-the-loop: enrich batches (descriptions +
|
|
243
|
+
missing calls) and opportunistic add_entity/add_call
|
|
244
|
+
writes — agent_inferred, preserved across re-ingest
|
|
229
245
|
```
|
|
230
246
|
|
|
231
247
|
Everything runs locally, in-process. No network, no database, no API keys.
|
|
@@ -242,7 +258,9 @@ your-project/
|
|
|
242
258
|
|
|
243
259
|
## Limitations
|
|
244
260
|
|
|
245
|
-
- **Structure,
|
|
261
|
+
- **Structure first, semantics optional** — the parser knows what calls what,
|
|
262
|
+
not what it means. `enrich` closes that gap with agent-written descriptions
|
|
263
|
+
and missed edges, marked `agent_inferred`.
|
|
246
264
|
- **Static analysis** — dynamic dispatch, reflection, and string-based invocation
|
|
247
265
|
can't be fully resolved. `impact` surfaces those flagged `resolved: false`, and
|
|
248
266
|
`dead_code` results are always candidates to verify.
|
|
@@ -31,10 +31,8 @@ The graph turns navigation into a cheap, deterministic loop:
|
|
|
31
31
|
|
|
32
32
|
| You have… | Use |
|
|
33
33
|
|---|---|
|
|
34
|
-
| a
|
|
35
|
-
|
|
|
36
|
-
| keywords | `search` |
|
|
37
|
-
| a vague, nameless need | `map` (compact index to reason over) |
|
|
34
|
+
| a concept, name, or pattern | `grep` (regex over graph entities) |
|
|
35
|
+
| the full layout | `tree` |
|
|
38
36
|
|
|
39
37
|
2. **Trace** — a relationship around a known symbol/file:
|
|
40
38
|
|
|
@@ -104,8 +102,6 @@ The server speaks stdio MCP and defaults to the working directory.
|
|
|
104
102
|
```bash
|
|
105
103
|
# discover
|
|
106
104
|
codecompass query --grep "^get_" # regex over graph entities
|
|
107
|
-
codecompass query --search "session cookie" # keyword search
|
|
108
|
-
codecompass query --map # compact {file: [symbols]} index
|
|
109
105
|
|
|
110
106
|
# trace
|
|
111
107
|
codecompass query --impact "Session.send" # callers (disambiguated), with file:line
|
|
@@ -119,6 +115,21 @@ codecompass query --tree # full hierarchy
|
|
|
119
115
|
|
|
120
116
|
Add `--hops N` for traversal depth (start at 1 and follow the one path you need). Add `--rich` for tables.
|
|
121
117
|
|
|
118
|
+
### Enrichment (agent-in-the-loop)
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
codecompass enrich # stage entities for an agent swarm: one-line descriptions + missing call edges
|
|
122
|
+
codecompass enrich --apply # merge the swarm's results into the graph
|
|
123
|
+
codecompass add-entity <name> --file src/a.py --line 9 --description "Async helper"
|
|
124
|
+
codecompass add-call caller callee --line 2
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`enrich` is a bulk, user-triggered pass. `add-entity`/`add-call` are the
|
|
128
|
+
opportunistic version: as an agent reads code and spots something the parser
|
|
129
|
+
missed, it records it immediately. Everything agent-written is marked
|
|
130
|
+
`agent_inferred` and **preserved across re-ingests** — the graph gets better
|
|
131
|
+
with use. Ambiguous call targets are skipped, never guessed.
|
|
132
|
+
|
|
122
133
|
### Flow: `flow` vs `flow-summary`
|
|
123
134
|
|
|
124
135
|
- **`flow`** — lean structure only (node name/kind/file/depth, edge from/to/order/line). What an agent needs to navigate; no embedded source.
|
|
@@ -131,8 +142,6 @@ Add `--hops N` for traversal depth (start at 1 and follow the one path you need)
|
|
|
131
142
|
| Tool | Returns |
|
|
132
143
|
|---|---|
|
|
133
144
|
| `grep(pattern, field, ignore_case)` | Regex search over graph entities |
|
|
134
|
-
| `search(query, kind)` | Keyword search, ranked |
|
|
135
|
-
| `map(include_tests)` | Compact `{file: [symbols]}` index |
|
|
136
145
|
| `impact(symbol, hops)` | Callers/importers, disambiguated, with `resolved` + `line` |
|
|
137
146
|
| `blast_radius(target, hops)` | Files reachable from a file or symbol |
|
|
138
147
|
| `batch_impact(targets, hops)` | Union of blast radii for a multi-file change |
|
|
@@ -143,6 +152,9 @@ Add `--hops N` for traversal depth (start at 1 and follow the one path you need)
|
|
|
143
152
|
| `dead_code(include_entrypoints)` | Entities with no inbound caller |
|
|
144
153
|
| `styles(element)` | CSS selectors that style an element |
|
|
145
154
|
| `tree()` | Full project hierarchy |
|
|
155
|
+
| `enrich(apply, batch_size)` | Stage/merge agent-written descriptions + missing call edges |
|
|
156
|
+
| `add_entity(name, kind, file, line, description)` | Record a parser-missed entity (`agent_inferred`) |
|
|
157
|
+
| `add_call(caller, callee, line)` | Record a parser-missed CALLS edge (`agent_inferred`) |
|
|
146
158
|
| `set_repo` / `get_repo` / `init` / `ingest` | Project selection & indexing |
|
|
147
159
|
|
|
148
160
|
---
|
|
@@ -170,11 +182,14 @@ are language-agnostic.
|
|
|
170
182
|
`AGENTS.md` guides any agent through the discover→trace→read→edit loop. For
|
|
171
183
|
Claude Code and [pi](https://pi.dev), `init` also installs a `PreToolUse` hook
|
|
172
184
|
that **blocks code *search*** (`grep`/`rg`, the `Grep`/`Glob` tools) and
|
|
173
|
-
**whole-file `cat
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
is
|
|
185
|
+
**whole-file `cat` — but only inside a codecompass-registered repo** (tracked in
|
|
186
|
+
`~/.codecompass/repos`, one line per `init`'d project). Reads outside any
|
|
187
|
+
registered repo pass through: no graph exists there, so nothing is blocked.
|
|
188
|
+
**Targeted reads stay free** (the `Read` tool, `sed -n`, `head`/`tail`). The
|
|
189
|
+
point is to change the default reflex to graph-first, not to remove reads.
|
|
190
|
+
Each project's Claude hook lives under its own `.claude/hooks/` with the
|
|
191
|
+
project root baked in — edit or delete it to adjust. Block messages point the
|
|
192
|
+
agent at the right repo's graph: `codecompass query \"<repo>\" --grep …`.
|
|
178
193
|
|
|
179
194
|
---
|
|
180
195
|
|
|
@@ -186,8 +201,11 @@ Source files
|
|
|
186
201
|
▼ code_parser tree-sitter extraction (no API calls) → typed CodeTriples
|
|
187
202
|
▼ graph.json NetworkX MultiDiGraph as JSON; file+class-qualified nodes,
|
|
188
203
|
typed edges (CALLS/IMPORTS/INHERITS/STYLES/…), resolved calls
|
|
189
|
-
▼ code_query_cli traversal: grep /
|
|
204
|
+
▼ code_query_cli traversal: grep / impact / blast-radius /
|
|
190
205
|
deps / flow / dead-code / tree
|
|
206
|
+
▼ enricher agent-in-the-loop: enrich batches (descriptions +
|
|
207
|
+
missing calls) and opportunistic add_entity/add_call
|
|
208
|
+
writes — agent_inferred, preserved across re-ingest
|
|
191
209
|
```
|
|
192
210
|
|
|
193
211
|
Everything runs locally, in-process. No network, no database, no API keys.
|
|
@@ -204,7 +222,9 @@ your-project/
|
|
|
204
222
|
|
|
205
223
|
## Limitations
|
|
206
224
|
|
|
207
|
-
- **Structure,
|
|
225
|
+
- **Structure first, semantics optional** — the parser knows what calls what,
|
|
226
|
+
not what it means. `enrich` closes that gap with agent-written descriptions
|
|
227
|
+
and missed edges, marked `agent_inferred`.
|
|
208
228
|
- **Static analysis** — dynamic dispatch, reflection, and string-based invocation
|
|
209
229
|
can't be fully resolved. `impact` surfaces those flagged `resolved: false`, and
|
|
210
230
|
`dead_code` results are always candidates to verify.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecompass-mcp
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.0
|
|
4
4
|
Summary: Structural code context for AI coding agents — a local code knowledge graph for blast radius, impact, deps, dead code, and flow tracing
|
|
5
5
|
Author: CodeCompass contributors
|
|
6
6
|
License: MIT
|
|
@@ -20,10 +20,8 @@ License-File: LICENSE
|
|
|
20
20
|
Requires-Dist: anthropic>=0.40.0
|
|
21
21
|
Requires-Dist: fastmcp>=2.0.0
|
|
22
22
|
Requires-Dist: networkx>=3.0
|
|
23
|
-
Requires-Dist: pypdf2>=3.0.0
|
|
24
23
|
Requires-Dist: python-dotenv>=1.0.0
|
|
25
24
|
Requires-Dist: rich>=13.0.0
|
|
26
|
-
Requires-Dist: tqdm>=4.66.0
|
|
27
25
|
Requires-Dist: tree-sitter>=0.21.0
|
|
28
26
|
Requires-Dist: tree-sitter-python>=0.21.0
|
|
29
27
|
Requires-Dist: tree-sitter-javascript>=0.21.0
|
|
@@ -69,10 +67,8 @@ The graph turns navigation into a cheap, deterministic loop:
|
|
|
69
67
|
|
|
70
68
|
| You have… | Use |
|
|
71
69
|
|---|---|
|
|
72
|
-
| a
|
|
73
|
-
|
|
|
74
|
-
| keywords | `search` |
|
|
75
|
-
| a vague, nameless need | `map` (compact index to reason over) |
|
|
70
|
+
| a concept, name, or pattern | `grep` (regex over graph entities) |
|
|
71
|
+
| the full layout | `tree` |
|
|
76
72
|
|
|
77
73
|
2. **Trace** — a relationship around a known symbol/file:
|
|
78
74
|
|
|
@@ -142,8 +138,6 @@ The server speaks stdio MCP and defaults to the working directory.
|
|
|
142
138
|
```bash
|
|
143
139
|
# discover
|
|
144
140
|
codecompass query --grep "^get_" # regex over graph entities
|
|
145
|
-
codecompass query --search "session cookie" # keyword search
|
|
146
|
-
codecompass query --map # compact {file: [symbols]} index
|
|
147
141
|
|
|
148
142
|
# trace
|
|
149
143
|
codecompass query --impact "Session.send" # callers (disambiguated), with file:line
|
|
@@ -157,6 +151,21 @@ codecompass query --tree # full hierarchy
|
|
|
157
151
|
|
|
158
152
|
Add `--hops N` for traversal depth (start at 1 and follow the one path you need). Add `--rich` for tables.
|
|
159
153
|
|
|
154
|
+
### Enrichment (agent-in-the-loop)
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
codecompass enrich # stage entities for an agent swarm: one-line descriptions + missing call edges
|
|
158
|
+
codecompass enrich --apply # merge the swarm's results into the graph
|
|
159
|
+
codecompass add-entity <name> --file src/a.py --line 9 --description "Async helper"
|
|
160
|
+
codecompass add-call caller callee --line 2
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`enrich` is a bulk, user-triggered pass. `add-entity`/`add-call` are the
|
|
164
|
+
opportunistic version: as an agent reads code and spots something the parser
|
|
165
|
+
missed, it records it immediately. Everything agent-written is marked
|
|
166
|
+
`agent_inferred` and **preserved across re-ingests** — the graph gets better
|
|
167
|
+
with use. Ambiguous call targets are skipped, never guessed.
|
|
168
|
+
|
|
160
169
|
### Flow: `flow` vs `flow-summary`
|
|
161
170
|
|
|
162
171
|
- **`flow`** — lean structure only (node name/kind/file/depth, edge from/to/order/line). What an agent needs to navigate; no embedded source.
|
|
@@ -169,8 +178,6 @@ Add `--hops N` for traversal depth (start at 1 and follow the one path you need)
|
|
|
169
178
|
| Tool | Returns |
|
|
170
179
|
|---|---|
|
|
171
180
|
| `grep(pattern, field, ignore_case)` | Regex search over graph entities |
|
|
172
|
-
| `search(query, kind)` | Keyword search, ranked |
|
|
173
|
-
| `map(include_tests)` | Compact `{file: [symbols]}` index |
|
|
174
181
|
| `impact(symbol, hops)` | Callers/importers, disambiguated, with `resolved` + `line` |
|
|
175
182
|
| `blast_radius(target, hops)` | Files reachable from a file or symbol |
|
|
176
183
|
| `batch_impact(targets, hops)` | Union of blast radii for a multi-file change |
|
|
@@ -181,6 +188,9 @@ Add `--hops N` for traversal depth (start at 1 and follow the one path you need)
|
|
|
181
188
|
| `dead_code(include_entrypoints)` | Entities with no inbound caller |
|
|
182
189
|
| `styles(element)` | CSS selectors that style an element |
|
|
183
190
|
| `tree()` | Full project hierarchy |
|
|
191
|
+
| `enrich(apply, batch_size)` | Stage/merge agent-written descriptions + missing call edges |
|
|
192
|
+
| `add_entity(name, kind, file, line, description)` | Record a parser-missed entity (`agent_inferred`) |
|
|
193
|
+
| `add_call(caller, callee, line)` | Record a parser-missed CALLS edge (`agent_inferred`) |
|
|
184
194
|
| `set_repo` / `get_repo` / `init` / `ingest` | Project selection & indexing |
|
|
185
195
|
|
|
186
196
|
---
|
|
@@ -208,11 +218,14 @@ are language-agnostic.
|
|
|
208
218
|
`AGENTS.md` guides any agent through the discover→trace→read→edit loop. For
|
|
209
219
|
Claude Code and [pi](https://pi.dev), `init` also installs a `PreToolUse` hook
|
|
210
220
|
that **blocks code *search*** (`grep`/`rg`, the `Grep`/`Glob` tools) and
|
|
211
|
-
**whole-file `cat
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
is
|
|
221
|
+
**whole-file `cat` — but only inside a codecompass-registered repo** (tracked in
|
|
222
|
+
`~/.codecompass/repos`, one line per `init`'d project). Reads outside any
|
|
223
|
+
registered repo pass through: no graph exists there, so nothing is blocked.
|
|
224
|
+
**Targeted reads stay free** (the `Read` tool, `sed -n`, `head`/`tail`). The
|
|
225
|
+
point is to change the default reflex to graph-first, not to remove reads.
|
|
226
|
+
Each project's Claude hook lives under its own `.claude/hooks/` with the
|
|
227
|
+
project root baked in — edit or delete it to adjust. Block messages point the
|
|
228
|
+
agent at the right repo's graph: `codecompass query \"<repo>\" --grep …`.
|
|
216
229
|
|
|
217
230
|
---
|
|
218
231
|
|
|
@@ -224,8 +237,11 @@ Source files
|
|
|
224
237
|
▼ code_parser tree-sitter extraction (no API calls) → typed CodeTriples
|
|
225
238
|
▼ graph.json NetworkX MultiDiGraph as JSON; file+class-qualified nodes,
|
|
226
239
|
typed edges (CALLS/IMPORTS/INHERITS/STYLES/…), resolved calls
|
|
227
|
-
▼ code_query_cli traversal: grep /
|
|
240
|
+
▼ code_query_cli traversal: grep / impact / blast-radius /
|
|
228
241
|
deps / flow / dead-code / tree
|
|
242
|
+
▼ enricher agent-in-the-loop: enrich batches (descriptions +
|
|
243
|
+
missing calls) and opportunistic add_entity/add_call
|
|
244
|
+
writes — agent_inferred, preserved across re-ingest
|
|
229
245
|
```
|
|
230
246
|
|
|
231
247
|
Everything runs locally, in-process. No network, no database, no API keys.
|
|
@@ -242,7 +258,9 @@ your-project/
|
|
|
242
258
|
|
|
243
259
|
## Limitations
|
|
244
260
|
|
|
245
|
-
- **Structure,
|
|
261
|
+
- **Structure first, semantics optional** — the parser knows what calls what,
|
|
262
|
+
not what it means. `enrich` closes that gap with agent-written descriptions
|
|
263
|
+
and missed edges, marked `agent_inferred`.
|
|
246
264
|
- **Static analysis** — dynamic dispatch, reflection, and string-based invocation
|
|
247
265
|
can't be fully resolved. `impact` surfaces those flagged `resolved: false`, and
|
|
248
266
|
`dead_code` results are always candidates to verify.
|
|
@@ -11,32 +11,23 @@ codecompass_mcp.egg-info/entry_points.txt
|
|
|
11
11
|
codecompass_mcp.egg-info/requires.txt
|
|
12
12
|
codecompass_mcp.egg-info/top_level.txt
|
|
13
13
|
graph/__init__.py
|
|
14
|
-
graph/cli.py
|
|
15
14
|
graph/code_graph_client.py
|
|
16
15
|
graph/code_query_cli.py
|
|
17
|
-
graph/setup.py
|
|
18
16
|
ingestion/__init__.py
|
|
19
|
-
ingestion/chunker.py
|
|
20
17
|
ingestion/code_normalizer.py
|
|
21
18
|
ingestion/code_parser.py
|
|
22
|
-
ingestion/
|
|
23
|
-
ingestion/entity_resolver.py
|
|
19
|
+
ingestion/enricher.py
|
|
24
20
|
ingestion/file_watcher.py
|
|
25
|
-
ingestion/graph_writer.py
|
|
26
21
|
ingestion/hierarchy_builder.py
|
|
27
|
-
ingestion/reader_agent.py
|
|
28
22
|
ingestion/source_context.py
|
|
29
23
|
models/__init__.py
|
|
30
24
|
models/code_types.py
|
|
31
|
-
models/types.py
|
|
32
25
|
tests/test_accuracy_fixes.py
|
|
33
26
|
tests/test_batch_impact.py
|
|
34
27
|
tests/test_blast_radius.py
|
|
28
|
+
tests/test_block_hook.py
|
|
35
29
|
tests/test_code_parser.py
|
|
36
|
-
tests/
|
|
37
|
-
tests/test_ingestion.py
|
|
30
|
+
tests/test_enricher.py
|
|
38
31
|
tests/test_lit_css_parser.py
|
|
39
32
|
tests/test_mcp_server.py
|
|
40
|
-
tests/test_php_parser.py
|
|
41
|
-
utils/__init__.py
|
|
42
|
-
utils/formatting.py
|
|
33
|
+
tests/test_php_parser.py
|
|
@@ -10,7 +10,7 @@ import os
|
|
|
10
10
|
import re
|
|
11
11
|
import networkx as nx
|
|
12
12
|
from datetime import datetime, timezone
|
|
13
|
-
from typing import Optional
|
|
13
|
+
from typing import Optional
|
|
14
14
|
|
|
15
15
|
from models.code_types import CodeTriple, FileNode, FolderNode
|
|
16
16
|
|
|
@@ -236,6 +236,11 @@ class LocalGraphClient:
|
|
|
236
236
|
"project": project,
|
|
237
237
|
"file": triple.source_file,
|
|
238
238
|
})
|
|
239
|
+
# Definition line comes only from DEFINED_IN triples — a CALLS triple's
|
|
240
|
+
# line_number is the call site, not the definition. External symbols
|
|
241
|
+
# (never defined in-project) legitimately have no line.
|
|
242
|
+
if triple.relation_type == "DEFINED_IN" and triple.line_number:
|
|
243
|
+
self.graph.nodes[from_id]["line"] = triple.line_number
|
|
239
244
|
# A symbol is public if any of its definitions is exported. Never let a
|
|
240
245
|
# later non-exported definition clear a flag an earlier one set.
|
|
241
246
|
if getattr(triple, "is_exported", False):
|
|
@@ -254,6 +259,9 @@ class LocalGraphClient:
|
|
|
254
259
|
existing["language"] = language
|
|
255
260
|
existing["kind"] = f"{triple.to_type}:{language}"
|
|
256
261
|
existing["description"] = f"{language} {triple.to_type}"
|
|
262
|
+
# A module's definition is line 1 of its file.
|
|
263
|
+
if triple.relation_type == "DEFINED_IN" and triple.to_type == "module":
|
|
264
|
+
existing.setdefault("line", 1)
|
|
257
265
|
|
|
258
266
|
self.graph.add_edge(
|
|
259
267
|
from_id,
|
|
@@ -321,6 +329,13 @@ class LocalGraphClient:
|
|
|
321
329
|
to_id = self._resolve_to_id(triple, project, defs_by_name, class_file)
|
|
322
330
|
self.write_code_triple(triple, file_id, project, from_id=from_id, to_id=to_id)
|
|
323
331
|
|
|
332
|
+
# Module nodes are the to-side of DEFINED_IN and may exist under a
|
|
333
|
+
# different resolved id — give every in-project module its line (1).
|
|
334
|
+
for _id, attr in self.graph.nodes(data=True):
|
|
335
|
+
if (attr.get("type") == "Entity" and attr.get("entity_type") == "module"
|
|
336
|
+
and attr.get("file") and "line" not in attr):
|
|
337
|
+
attr["line"] = 1
|
|
338
|
+
|
|
324
339
|
self.save()
|
|
325
340
|
return len(triples)
|
|
326
341
|
|
|
@@ -543,6 +558,7 @@ class LocalGraphClient:
|
|
|
543
558
|
"name": a.get("name"),
|
|
544
559
|
"kind": a.get("kind"),
|
|
545
560
|
"file": a.get("file"),
|
|
561
|
+
"line": a.get("line"),
|
|
546
562
|
"matched_field": f,
|
|
547
563
|
"match": m.group(0),
|
|
548
564
|
})
|
|
@@ -552,90 +568,6 @@ class LocalGraphClient:
|
|
|
552
568
|
matches.sort(key=lambda r: (r["file"] or "", r["name"] or ""))
|
|
553
569
|
return {"pattern": pattern, "matches": matches, "count": len(matches)}
|
|
554
570
|
|
|
555
|
-
def search_entities(self, query: str, project: str, limit: int = 30,
|
|
556
|
-
kind: str | None = None) -> list[dict]:
|
|
557
|
-
"""Find graph entities matching a keyword — the way in when you don't yet
|
|
558
|
-
know a symbol name.
|
|
559
|
-
|
|
560
|
-
Matches the query (case-insensitive) against each entity's name, file
|
|
561
|
-
path, and description. Any term may match (OR), and entities matching
|
|
562
|
-
more terms — and matching in the name rather than file/description — rank
|
|
563
|
-
highest, so the most on-point symbols come first.
|
|
564
|
-
Optional `kind` filters by entity_type (e.g. "function", "class").
|
|
565
|
-
Returns a lean list (name/kind/file/entity_type) to then feed into
|
|
566
|
-
impact/flow/deps.
|
|
567
|
-
"""
|
|
568
|
-
terms = [t for t in query.lower().split() if t]
|
|
569
|
-
if not terms:
|
|
570
|
-
return []
|
|
571
|
-
|
|
572
|
-
scored = []
|
|
573
|
-
for _id, a in self.graph.nodes(data=True):
|
|
574
|
-
if a.get("type") != "Entity" or a.get("project") != project:
|
|
575
|
-
continue
|
|
576
|
-
if not a.get("file"):
|
|
577
|
-
continue
|
|
578
|
-
if kind and a.get("entity_type") != kind:
|
|
579
|
-
continue
|
|
580
|
-
name = (a.get("name") or "")
|
|
581
|
-
file = (a.get("file") or "")
|
|
582
|
-
desc = (a.get("description") or "")
|
|
583
|
-
hay_name = name.lower()
|
|
584
|
-
hay_rest = f"{file} {desc}".lower()
|
|
585
|
-
# any term may match (OR); score rewards name hits and more terms matched
|
|
586
|
-
score = 0
|
|
587
|
-
for t in terms:
|
|
588
|
-
if t == hay_name:
|
|
589
|
-
score += 100
|
|
590
|
-
elif t in hay_name:
|
|
591
|
-
score += 10
|
|
592
|
-
elif t in hay_rest:
|
|
593
|
-
score += 1
|
|
594
|
-
if score == 0:
|
|
595
|
-
continue
|
|
596
|
-
scored.append((score, {
|
|
597
|
-
"name": name,
|
|
598
|
-
"entity_type": a.get("entity_type", ""),
|
|
599
|
-
"kind": a.get("kind", ""),
|
|
600
|
-
"file": file,
|
|
601
|
-
}))
|
|
602
|
-
|
|
603
|
-
scored.sort(key=lambda s: (-s[0], s[1]["file"], s[1]["name"]))
|
|
604
|
-
return [row for _s, row in scored[:limit]]
|
|
605
|
-
|
|
606
|
-
def symbol_map(self, project: str, include_tests: bool = False) -> dict:
|
|
607
|
-
"""A compact index of the codebase for an agent to reason over.
|
|
608
|
-
|
|
609
|
-
Groups every source entity under its file as `{file: [names...]}` —
|
|
610
|
-
names only, no depth/kind/description bloat — so the whole map fits in a
|
|
611
|
-
few thousand tokens. The agent reads it once and uses its own judgment to
|
|
612
|
-
pick what's relevant to a vague task ("where would caching go?"), then
|
|
613
|
-
drills in with impact/flow/deps. This is the semantic-discovery entry
|
|
614
|
-
point that keyword `search` can't be: an LLM knows caching lives near the
|
|
615
|
-
request handler and response path even when nothing is named "cache".
|
|
616
|
-
|
|
617
|
-
`include_tests=False` (default) drops test/example files so the map shows
|
|
618
|
-
the actual implementation surface.
|
|
619
|
-
"""
|
|
620
|
-
by_file: dict[str, list[str]] = {}
|
|
621
|
-
for _id, a in self.graph.nodes(data=True):
|
|
622
|
-
if a.get("type") != "Entity" or a.get("project") != project:
|
|
623
|
-
continue
|
|
624
|
-
f = a.get("file")
|
|
625
|
-
if not f:
|
|
626
|
-
continue
|
|
627
|
-
if not include_tests and ("test/" in f or "test\\" in f
|
|
628
|
-
or f.startswith("test") or "example" in f):
|
|
629
|
-
continue
|
|
630
|
-
name = a.get("name")
|
|
631
|
-
if name:
|
|
632
|
-
by_file.setdefault(f, [])
|
|
633
|
-
if name not in by_file[f]:
|
|
634
|
-
by_file[f].append(name)
|
|
635
|
-
for f in by_file:
|
|
636
|
-
by_file[f].sort()
|
|
637
|
-
return dict(sorted(by_file.items()))
|
|
638
|
-
|
|
639
571
|
def find_dependencies(self, file_path: str, project: str, max_hops: int = 3) -> list[dict]:
|
|
640
572
|
"""Return all modules imported (directly or transitively) by file_path."""
|
|
641
573
|
file_node = next((n for n, attr in self.graph.nodes(data=True)
|
|
@@ -662,6 +594,7 @@ class LocalGraphClient:
|
|
|
662
594
|
results.append({
|
|
663
595
|
"dependency": node.get("name"),
|
|
664
596
|
"dep_type": node.get("entity_type"),
|
|
597
|
+
"line": node.get("line"),
|
|
665
598
|
"depth": depth + 1
|
|
666
599
|
})
|
|
667
600
|
|
|
@@ -710,6 +643,7 @@ class LocalGraphClient:
|
|
|
710
643
|
"kind": start_attr.get("kind", ""),
|
|
711
644
|
"description": start_attr.get("description", ""),
|
|
712
645
|
"file": start_attr.get("file", ""),
|
|
646
|
+
"line": start_attr.get("line"),
|
|
713
647
|
"depth": 0,
|
|
714
648
|
}
|
|
715
649
|
|
|
@@ -743,6 +677,7 @@ class LocalGraphClient:
|
|
|
743
677
|
"kind": succ_attr.get("kind", ""),
|
|
744
678
|
"description": succ_attr.get("description", ""),
|
|
745
679
|
"file": succ_attr.get("file", ""),
|
|
680
|
+
"line": succ_attr.get("line"),
|
|
746
681
|
"depth": depth + 1,
|
|
747
682
|
}
|
|
748
683
|
queue.append((succ, depth + 1))
|
|
@@ -776,6 +711,7 @@ class LocalGraphClient:
|
|
|
776
711
|
"callee_name": node.get("name"),
|
|
777
712
|
"callee_type": node.get("entity_type"),
|
|
778
713
|
"callee_file": node.get("file"),
|
|
714
|
+
"line": node.get("line"),
|
|
779
715
|
"depth": depth + 1
|
|
780
716
|
})
|
|
781
717
|
visited.add(succ)
|
|
@@ -847,6 +783,7 @@ class LocalGraphClient:
|
|
|
847
783
|
"kind": attr.get("kind", ""),
|
|
848
784
|
"entity_type": attr.get("entity_type", ""),
|
|
849
785
|
"file": attr.get("file", ""),
|
|
786
|
+
"line": attr.get("line"),
|
|
850
787
|
}
|
|
851
788
|
# Exported symbols and name-heuristic entry points are public API:
|
|
852
789
|
# having no in-repo caller is expected, not evidence of dead code.
|