codecompass-mcp 2.2.0__tar.gz → 2.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.
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/PKG-INFO +3 -3
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/README.md +2 -2
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/codecompass_mcp.egg-info/PKG-INFO +3 -3
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/ingestion/chunker.py +0 -9
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/ingestion/code_parser.py +90 -8
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/ingestion/entity_resolver.py +13 -15
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/ingestion/reader_agent.py +4 -3
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/main.py +20 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/pyproject.toml +1 -1
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/LICENSE +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/codecompass_mcp.egg-info/SOURCES.txt +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/codecompass_mcp.egg-info/dependency_links.txt +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/codecompass_mcp.egg-info/entry_points.txt +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/codecompass_mcp.egg-info/requires.txt +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/codecompass_mcp.egg-info/top_level.txt +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/config.py +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/graph/__init__.py +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/graph/cli.py +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/graph/code_graph_client.py +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/graph/code_query_cli.py +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/graph/setup.py +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/ingestion/__init__.py +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/ingestion/code_normalizer.py +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/ingestion/file_watcher.py +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/ingestion/graph_writer.py +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/ingestion/hierarchy_builder.py +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/ingestion/source_context.py +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/models/__init__.py +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/models/code_types.py +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/models/types.py +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/setup.cfg +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/tests/test_batch_impact.py +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/tests/test_blast_radius.py +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/tests/test_code_parser.py +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/tests/test_ingestion.py +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/tests/test_lit_css_parser.py +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/utils/__init__.py +0 -0
- {codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/utils/formatting.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecompass-mcp
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.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
|
|
@@ -129,10 +129,10 @@ All commands default to the current directory.
|
|
|
129
129
|
### Install
|
|
130
130
|
|
|
131
131
|
```bash
|
|
132
|
-
|
|
133
|
-
pip install -e .
|
|
132
|
+
pip install codecompass-mcp
|
|
134
133
|
```
|
|
135
134
|
|
|
135
|
+
|
|
136
136
|
### Index a project
|
|
137
137
|
|
|
138
138
|
```bash
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecompass-mcp
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.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
|
|
@@ -129,10 +129,10 @@ All commands default to the current directory.
|
|
|
129
129
|
### Install
|
|
130
130
|
|
|
131
131
|
```bash
|
|
132
|
-
|
|
133
|
-
pip install -e .
|
|
132
|
+
pip install codecompass-mcp
|
|
134
133
|
```
|
|
135
134
|
|
|
135
|
+
|
|
136
136
|
### Index a project
|
|
137
137
|
|
|
138
138
|
```bash
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import re
|
|
2
|
-
import PyPDF2
|
|
3
2
|
|
|
4
3
|
# Rough approximation: 1 token ≈ 4 characters (standard for English prose/code).
|
|
5
4
|
# Used when a tokeniser library is unavailable.
|
|
@@ -10,14 +9,6 @@ def _estimate_tokens(text: str) -> int:
|
|
|
10
9
|
return max(1, len(text) // _CHARS_PER_TOKEN)
|
|
11
10
|
|
|
12
11
|
|
|
13
|
-
def chunk_pdf(filepath: str, tokens_per_chunk: int = 500, overlap_tokens: int = 50) -> list[str]:
|
|
14
|
-
"""Split PDF into overlapping token-sized chunks."""
|
|
15
|
-
with open(filepath, "rb") as f:
|
|
16
|
-
reader = PyPDF2.PdfReader(f)
|
|
17
|
-
full_text = " ".join(page.extract_text() or "" for page in reader.pages)
|
|
18
|
-
return chunk_text(full_text, tokens_per_chunk=tokens_per_chunk, overlap_tokens=overlap_tokens)
|
|
19
|
-
|
|
20
|
-
|
|
21
12
|
def chunk_text(text: str, tokens_per_chunk: int = 500, overlap_tokens: int = 50) -> list[str]:
|
|
22
13
|
"""
|
|
23
14
|
Split text into chunks of approximately `tokens_per_chunk` tokens with
|
|
@@ -21,7 +21,7 @@ from models.code_types import CodeTriple
|
|
|
21
21
|
# Constants
|
|
22
22
|
# ---------------------------------------------------------------------------
|
|
23
23
|
|
|
24
|
-
SUPPORTED_EXTENSIONS = {".py", ".js", ".ts", ".tsx", ".html", ".css", ".scss"}
|
|
24
|
+
SUPPORTED_EXTENSIONS = {".py", ".js", ".ts", ".tsx", ".html", ".css", ".scss", ".php"}
|
|
25
25
|
|
|
26
26
|
# Relation types
|
|
27
27
|
DEFINED_IN = "DEFINED_IN"
|
|
@@ -101,6 +101,11 @@ def _load_html_parser() -> tuple[Parser, Language]:
|
|
|
101
101
|
return _make_parser(tshtml.language)
|
|
102
102
|
|
|
103
103
|
|
|
104
|
+
def _load_php_parser() -> tuple[Parser, Language]:
|
|
105
|
+
import tree_sitter_php as tsphp
|
|
106
|
+
return _make_parser(tsphp.language)
|
|
107
|
+
|
|
108
|
+
|
|
104
109
|
def _load_css_parser() -> tuple[Parser, Language]:
|
|
105
110
|
import tree_sitter_css as tscss
|
|
106
111
|
return _make_parser(tscss.language)
|
|
@@ -114,6 +119,7 @@ _PARSER_LOADERS: dict[str, Callable[[], tuple[Parser, Language]]] = {
|
|
|
114
119
|
".html": _load_html_parser,
|
|
115
120
|
".css": _load_css_parser,
|
|
116
121
|
".scss": _load_css_parser,
|
|
122
|
+
".php": _load_php_parser,
|
|
117
123
|
}
|
|
118
124
|
|
|
119
125
|
|
|
@@ -147,6 +153,15 @@ def _child_of_type(node: Node, type_name: str) -> Node | None:
|
|
|
147
153
|
return None
|
|
148
154
|
|
|
149
155
|
|
|
156
|
+
def _get_node_name(node: Node, types: list[str]) -> str | None:
|
|
157
|
+
"""Try multiple type names to find an identifier/name node."""
|
|
158
|
+
for t in types:
|
|
159
|
+
found = _child_of_type(node, t)
|
|
160
|
+
if found:
|
|
161
|
+
return _text(found)
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
|
|
150
165
|
def _children_of_type(node: Node, type_name: str) -> list[Node]:
|
|
151
166
|
return [c for c in node.children if c.type == type_name]
|
|
152
167
|
|
|
@@ -174,23 +189,23 @@ def _extract_python(root: Node, source: bytes, file_path: str) -> list[CodeTripl
|
|
|
174
189
|
for node in _walk(root):
|
|
175
190
|
match node.type:
|
|
176
191
|
case "function_definition":
|
|
177
|
-
|
|
178
|
-
if
|
|
192
|
+
name = _get_node_name(node, ["identifier"])
|
|
193
|
+
if name:
|
|
179
194
|
triples.append(CodeTriple(
|
|
180
|
-
from_entity=
|
|
195
|
+
from_entity=name,
|
|
181
196
|
from_type=TYPE_FUNCTION,
|
|
182
197
|
relation_type=DEFINED_IN,
|
|
183
198
|
to_entity=module_name,
|
|
184
199
|
to_type=TYPE_MODULE,
|
|
185
200
|
source_file=file_path,
|
|
186
|
-
line_number=_line(
|
|
201
|
+
line_number=_line(node),
|
|
187
202
|
))
|
|
188
203
|
|
|
189
204
|
case "class_definition":
|
|
190
|
-
|
|
191
|
-
if not
|
|
205
|
+
name = _get_node_name(node, ["identifier"])
|
|
206
|
+
if not name:
|
|
192
207
|
continue
|
|
193
|
-
class_name =
|
|
208
|
+
class_name = name
|
|
194
209
|
|
|
195
210
|
# Extract base classes from argument_list
|
|
196
211
|
arg_list = _child_of_type(node, "argument_list")
|
|
@@ -281,6 +296,72 @@ def _extract_python_callee(call_node: Node) -> str | None:
|
|
|
281
296
|
return None
|
|
282
297
|
|
|
283
298
|
|
|
299
|
+
# ---------------------------------------------------------------------------
|
|
300
|
+
# PHP extraction
|
|
301
|
+
# ---------------------------------------------------------------------------
|
|
302
|
+
|
|
303
|
+
def _extract_php(root: Node, source: bytes, file_path: str) -> list[CodeTriple]:
|
|
304
|
+
module_name = _module_name_from_path(file_path)
|
|
305
|
+
triples: list[CodeTriple] = []
|
|
306
|
+
|
|
307
|
+
for node in _walk(root):
|
|
308
|
+
match node.type:
|
|
309
|
+
case "function_definition":
|
|
310
|
+
name = _get_node_name(node, ["function_name", "identifier"])
|
|
311
|
+
if name:
|
|
312
|
+
triples.append(CodeTriple(
|
|
313
|
+
from_entity=name,
|
|
314
|
+
from_type=TYPE_FUNCTION,
|
|
315
|
+
relation_type=DEFINED_IN,
|
|
316
|
+
to_entity=module_name,
|
|
317
|
+
to_type=TYPE_MODULE,
|
|
318
|
+
source_file=file_path,
|
|
319
|
+
line_number=_line(node),
|
|
320
|
+
))
|
|
321
|
+
|
|
322
|
+
case "class_definition":
|
|
323
|
+
name = _get_node_name(node, ["class_name", "identifier"])
|
|
324
|
+
if not name:
|
|
325
|
+
continue
|
|
326
|
+
class_name = name
|
|
327
|
+
|
|
328
|
+
# Extract base classes (extends)
|
|
329
|
+
extends_node = _child_of_type(node, "extends_clause")
|
|
330
|
+
if extends_node:
|
|
331
|
+
base_node = _child_of_type(extends_node, "type_name") or _child_of_type(extends_node, "identifier")
|
|
332
|
+
if base_node:
|
|
333
|
+
triples.append(CodeTriple(
|
|
334
|
+
from_entity=class_name,
|
|
335
|
+
from_type=TYPE_CLASS,
|
|
336
|
+
relation_type=INHERITS,
|
|
337
|
+
to_entity=_text(base_node),
|
|
338
|
+
to_type=TYPE_CLASS,
|
|
339
|
+
source_file=file_path,
|
|
340
|
+
line_number=_line(base_node),
|
|
341
|
+
))
|
|
342
|
+
|
|
343
|
+
case "function_call":
|
|
344
|
+
callee = _extract_php_callee(node)
|
|
345
|
+
if callee and _is_meaningful_callee(callee):
|
|
346
|
+
scope = _enclosing_scope(node) # Reuse python-style scope walker for now
|
|
347
|
+
caller = scope or module_name
|
|
348
|
+
caller_type = TYPE_FUNCTION if scope else TYPE_MODULE
|
|
349
|
+
triples.append(CodeTriple(
|
|
350
|
+
from_entity=caller,
|
|
351
|
+
from_type=caller_type,
|
|
352
|
+
relation_type=CALLS,
|
|
353
|
+
to_entity=callee,
|
|
354
|
+
to_type=TYPE_FUNCTION,
|
|
355
|
+
source_file=file_path,
|
|
356
|
+
line_number=_line(node),
|
|
357
|
+
))
|
|
358
|
+
|
|
359
|
+
return triples
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _extract_php_callee(call_node: Node) -> str | None:
|
|
363
|
+
return _get_node_name(call_node, ["function_name", "identifier"])
|
|
364
|
+
|
|
284
365
|
# ---------------------------------------------------------------------------
|
|
285
366
|
# JavaScript / TypeScript extraction
|
|
286
367
|
# ---------------------------------------------------------------------------
|
|
@@ -647,6 +728,7 @@ _EXTRACTORS: dict[str, Callable] = {
|
|
|
647
728
|
".html": _extract_html,
|
|
648
729
|
".css": _extract_css,
|
|
649
730
|
".scss": _extract_css,
|
|
731
|
+
".php": _extract_php,
|
|
650
732
|
}
|
|
651
733
|
|
|
652
734
|
|
|
@@ -23,7 +23,7 @@ Return ONLY valid JSON, no other text:
|
|
|
23
23
|
If no clear duplicates exist, return {"groups": []}"""
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
def resolve_entities(client
|
|
26
|
+
def resolve_entities(client) -> int:
|
|
27
27
|
"""Identify and merge duplicate entity nodes in the local graph."""
|
|
28
28
|
all_nodes = [
|
|
29
29
|
{"id": n, "name": attr.get("name", n), "type": attr.get("entity_type", "")}
|
|
@@ -79,20 +79,18 @@ def resolve_entities(client, dry_run: bool = False) -> int:
|
|
|
79
79
|
dup_node = name_to_node.get(dup_name)
|
|
80
80
|
if not dup_node or dup_node["id"] == canonical_node["id"]:
|
|
81
81
|
continue
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
client.graph.remove_node(dup_id)
|
|
95
|
-
merged_count += 1
|
|
82
|
+
print(f" '{dup_name}' → '{canonical_name}'")
|
|
83
|
+
# Remap all edges from duplicate to canonical
|
|
84
|
+
dup_id = dup_node["id"]
|
|
85
|
+
canon_id = canonical_node["id"]
|
|
86
|
+
for pred in list(client.graph.predecessors(dup_id)):
|
|
87
|
+
for key, data in list(client.graph.get_edge_data(pred, dup_id).items()):
|
|
88
|
+
client.graph.add_edge(pred, canon_id, **data)
|
|
89
|
+
for succ in list(client.graph.successors(dup_id)):
|
|
90
|
+
for key, data in list(client.graph.get_edge_data(dup_id, succ).items()):
|
|
91
|
+
client.graph.add_edge(canon_id, succ, **data)
|
|
92
|
+
client.graph.remove_node(dup_id)
|
|
93
|
+
merged_count += 1
|
|
96
94
|
|
|
97
95
|
if merged_count:
|
|
98
96
|
client.save()
|
|
@@ -58,9 +58,10 @@ def _extract_triples_sync(chunk: str) -> list[Triple]:
|
|
|
58
58
|
)
|
|
59
59
|
|
|
60
60
|
try:
|
|
61
|
-
raw = response.content[0].text
|
|
62
|
-
raw
|
|
63
|
-
|
|
61
|
+
raw = response.content[0].text.strip()
|
|
62
|
+
if raw.startswith("```"):
|
|
63
|
+
raw = re.sub(r"^```[a-z]*\n?", "", raw)
|
|
64
|
+
raw = re.sub(r"```$", "", raw)
|
|
64
65
|
data = json.loads(raw.strip())
|
|
65
66
|
except (json.JSONDecodeError, IndexError) as e:
|
|
66
67
|
print(f"[reader_agent] JSON parse failed: {e} | raw: {raw[:200]!r}")
|
|
@@ -23,6 +23,10 @@ console = Console()
|
|
|
23
23
|
|
|
24
24
|
_CODECOMPASS_START = "<!-- codecompass-code-graph-start -->"
|
|
25
25
|
_CODECOMPASS_END = "<!-- codecompass-code-graph-end -->"
|
|
26
|
+
_CODECOMPASS_READ_INSTRUCTION = (
|
|
27
|
+
"Read `.codecompass` before making any changes or before reading any file. "
|
|
28
|
+
"If you think codecompass will help in any way, use it."
|
|
29
|
+
)
|
|
26
30
|
|
|
27
31
|
|
|
28
32
|
def init_project(repo_path: str) -> None:
|
|
@@ -69,6 +73,20 @@ def init_project(repo_path: str) -> None:
|
|
|
69
73
|
with open(file_path, "w") as f:
|
|
70
74
|
f.write(content)
|
|
71
75
|
|
|
76
|
+
# ponytail: minimal way to create claude.md in repo root
|
|
77
|
+
claude_md_path = os.path.join(repo_path, "claude.md")
|
|
78
|
+
if not os.path.exists(claude_md_path):
|
|
79
|
+
with open(claude_md_path, "w") as f:
|
|
80
|
+
f.write(f"AGENTS.md\n\n{_CODECOMPASS_READ_INSTRUCTION}\n")
|
|
81
|
+
else:
|
|
82
|
+
with open(claude_md_path) as f:
|
|
83
|
+
claude_md_content = f.read()
|
|
84
|
+
if _CODECOMPASS_READ_INSTRUCTION not in claude_md_content:
|
|
85
|
+
with open(claude_md_path, "a") as f:
|
|
86
|
+
if claude_md_content and not claude_md_content.endswith("\n"):
|
|
87
|
+
f.write("\n")
|
|
88
|
+
f.write(f"\n{_CODECOMPASS_READ_INSTRUCTION}\n")
|
|
89
|
+
|
|
72
90
|
_ensure_gitignore(repo_path)
|
|
73
91
|
console.print(f"[bold green]Initialized CodeCompass in:[/] {compass_dir}")
|
|
74
92
|
_register_project_agents_md(repo_path)
|
|
@@ -173,6 +191,8 @@ def _register_project_agents_md(repo_path: str) -> None:
|
|
|
173
191
|
block = f"""{_CODECOMPASS_START}
|
|
174
192
|
## Code graph
|
|
175
193
|
|
|
194
|
+
**{_CODECOMPASS_READ_INSTRUCTION}**
|
|
195
|
+
|
|
176
196
|
This project has a CodeCompass code knowledge graph at `.codecompass/graph.json`.
|
|
177
197
|
Every node carries `kind` (e.g. `function:python`, `class:typescript`) and a
|
|
178
198
|
human-readable `description`. Use it as your primary navigation tool.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "codecompass-mcp"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.3.0"
|
|
4
4
|
description = "Structural code context for AI coding agents — a local code knowledge graph for blast radius, impact, deps, dead code, and flow tracing"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = {text = "MIT"}
|
|
File without changes
|
|
File without changes
|
{codecompass_mcp-2.2.0 → codecompass_mcp-2.3.0}/codecompass_mcp.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|