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.
Files changed (47) hide show
  1. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/PKG-INFO +36 -18
  2. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/README.md +35 -15
  3. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/codecompass_mcp.egg-info/PKG-INFO +36 -18
  4. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/codecompass_mcp.egg-info/SOURCES.txt +4 -13
  5. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/codecompass_mcp.egg-info/entry_points.txt +1 -1
  6. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/codecompass_mcp.egg-info/requires.txt +0 -2
  7. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/codecompass_mcp.egg-info/top_level.txt +0 -1
  8. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/graph/code_graph_client.py +22 -85
  9. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/graph/code_query_cli.py +23 -119
  10. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/ingestion/code_normalizer.py +2 -8
  11. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/ingestion/code_parser.py +2 -18
  12. codecompass_mcp-4.0.0/ingestion/enricher.py +351 -0
  13. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/ingestion/file_watcher.py +2 -4
  14. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/ingestion/hierarchy_builder.py +3 -31
  15. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/main.py +269 -131
  16. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/mcp_server.py +69 -82
  17. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/pyproject.toml +3 -5
  18. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/tests/test_accuracy_fixes.py +0 -2
  19. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/tests/test_batch_impact.py +4 -4
  20. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/tests/test_blast_radius.py +2 -2
  21. codecompass_mcp-4.0.0/tests/test_block_hook.py +71 -0
  22. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/tests/test_code_parser.py +0 -2
  23. codecompass_mcp-4.0.0/tests/test_enricher.py +150 -0
  24. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/tests/test_lit_css_parser.py +0 -2
  25. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/tests/test_mcp_server.py +3 -0
  26. codecompass_mcp-3.2.0/graph/cli.py +0 -13
  27. codecompass_mcp-3.2.0/graph/setup.py +0 -36
  28. codecompass_mcp-3.2.0/ingestion/chunker.py +0 -61
  29. codecompass_mcp-3.2.0/ingestion/description_enricher.py +0 -189
  30. codecompass_mcp-3.2.0/ingestion/entity_resolver.py +0 -98
  31. codecompass_mcp-3.2.0/ingestion/graph_writer.py +0 -16
  32. codecompass_mcp-3.2.0/ingestion/reader_agent.py +0 -136
  33. codecompass_mcp-3.2.0/models/types.py +0 -45
  34. codecompass_mcp-3.2.0/tests/test_description_enricher.py +0 -95
  35. codecompass_mcp-3.2.0/tests/test_ingestion.py +0 -93
  36. codecompass_mcp-3.2.0/utils/__init__.py +0 -0
  37. codecompass_mcp-3.2.0/utils/formatting.py +0 -24
  38. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/LICENSE +0 -0
  39. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/codecompass_mcp.egg-info/dependency_links.txt +0 -0
  40. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/config.py +0 -0
  41. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/graph/__init__.py +0 -0
  42. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/ingestion/__init__.py +0 -0
  43. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/ingestion/source_context.py +0 -0
  44. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/models/__init__.py +0 -0
  45. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/models/code_types.py +0 -0
  46. {codecompass_mcp-3.2.0 → codecompass_mcp-4.0.0}/setup.cfg +0 -0
  47. {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.2.0
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 feature request that names a concept ("session timeout") | `grep` the concept |
73
- | a regex / name pattern | `grep` |
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`**, routing discovery through the graph while leaving
212
- **targeted reads free** (the `Read` tool, `sed -n`, `head`/`tail`). The point is
213
- to change the default reflex to graph-first, not to remove reads. Both pick the
214
- files up after a one-time trust prompt (they execute shell commands). The hook
215
- is a plain file under `.claude/hooks/` edit or delete it to adjust.
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 / search / map / impact / blast-radius /
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, not semantics** — the graph knows what calls what, not what it means.
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 feature request that names a concept ("session timeout") | `grep` the concept |
35
- | a regex / name pattern | `grep` |
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`**, routing discovery through the graph while leaving
174
- **targeted reads free** (the `Read` tool, `sed -n`, `head`/`tail`). The point is
175
- to change the default reflex to graph-first, not to remove reads. Both pick the
176
- files up after a one-time trust prompt (they execute shell commands). The hook
177
- is a plain file under `.claude/hooks/` edit or delete it to adjust.
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 / search / map / impact / blast-radius /
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, not semantics** — the graph knows what calls what, not what it means.
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.2.0
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 feature request that names a concept ("session timeout") | `grep` the concept |
73
- | a regex / name pattern | `grep` |
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`**, routing discovery through the graph while leaving
212
- **targeted reads free** (the `Read` tool, `sed -n`, `head`/`tail`). The point is
213
- to change the default reflex to graph-first, not to remove reads. Both pick the
214
- files up after a one-time trust prompt (they execute shell commands). The hook
215
- is a plain file under `.claude/hooks/` edit or delete it to adjust.
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 / search / map / impact / blast-radius /
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, not semantics** — the graph knows what calls what, not what it means.
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/description_enricher.py
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/test_description_enricher.py
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
@@ -1,3 +1,3 @@
1
1
  [console_scripts]
2
- codecompass = graph.cli:main
2
+ codecompass = main:main
3
3
  codecompass-mcp = mcp_server:main
@@ -1,10 +1,8 @@
1
1
  anthropic>=0.40.0
2
2
  fastmcp>=2.0.0
3
3
  networkx>=3.0
4
- pypdf2>=3.0.0
5
4
  python-dotenv>=1.0.0
6
5
  rich>=13.0.0
7
- tqdm>=4.66.0
8
6
  tree-sitter>=0.21.0
9
7
  tree-sitter-python>=0.21.0
10
8
  tree-sitter-javascript>=0.21.0
@@ -4,4 +4,3 @@ ingestion
4
4
  main
5
5
  mcp_server
6
6
  models
7
- utils
@@ -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, Any
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.