codecompass-mcp 3.2.0__tar.gz → 3.2.1__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-3.2.1}/PKG-INFO +1 -3
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/codecompass_mcp.egg-info/PKG-INFO +1 -3
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/codecompass_mcp.egg-info/SOURCES.txt +1 -11
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/codecompass_mcp.egg-info/entry_points.txt +1 -1
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/codecompass_mcp.egg-info/requires.txt +0 -2
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/codecompass_mcp.egg-info/top_level.txt +0 -1
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/graph/code_graph_client.py +1 -1
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/graph/code_query_cli.py +15 -89
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/ingestion/code_normalizer.py +2 -8
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/ingestion/code_parser.py +2 -18
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/ingestion/file_watcher.py +2 -4
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/ingestion/hierarchy_builder.py +3 -31
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/main.py +70 -75
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/mcp_server.py +20 -53
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/pyproject.toml +3 -5
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/tests/test_accuracy_fixes.py +0 -2
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/tests/test_batch_impact.py +4 -4
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/tests/test_blast_radius.py +2 -2
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/tests/test_code_parser.py +0 -2
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/tests/test_description_enricher.py +1 -1
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/tests/test_lit_css_parser.py +0 -2
- 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/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_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-3.2.1}/LICENSE +0 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/README.md +0 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/codecompass_mcp.egg-info/dependency_links.txt +0 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/config.py +0 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/graph/__init__.py +0 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/ingestion/__init__.py +0 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/ingestion/description_enricher.py +0 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/ingestion/source_context.py +0 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/models/__init__.py +0 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/models/code_types.py +0 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/setup.cfg +0 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/tests/test_mcp_server.py +0 -0
- {codecompass_mcp-3.2.0 → codecompass_mcp-3.2.1}/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.
|
|
3
|
+
Version: 3.2.1
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecompass-mcp
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.1
|
|
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
|
|
@@ -11,32 +11,22 @@ 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
19
|
ingestion/description_enricher.py
|
|
23
|
-
ingestion/entity_resolver.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
|
|
35
28
|
tests/test_code_parser.py
|
|
36
29
|
tests/test_description_enricher.py
|
|
37
|
-
tests/test_ingestion.py
|
|
38
30
|
tests/test_lit_css_parser.py
|
|
39
31
|
tests/test_mcp_server.py
|
|
40
|
-
tests/test_php_parser.py
|
|
41
|
-
utils/__init__.py
|
|
42
|
-
utils/formatting.py
|
|
32
|
+
tests/test_php_parser.py
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
python -m graph.code_query_cli --impact "login()" <repo_path>
|
|
4
4
|
python -m graph.code_query_cli --deps src/auth/login.py <repo_path>
|
|
5
5
|
python -m graph.code_query_cli --styles LoginForm <repo_path>
|
|
6
|
-
python -m graph.code_query_cli --
|
|
6
|
+
python -m graph.code_query_cli --flow "main()" <repo_path>
|
|
7
7
|
python -m graph.code_query_cli --tree <repo_path>
|
|
8
8
|
|
|
9
9
|
Output is plain text by default (agent-friendly). Pass --rich for formatted tables.
|
|
@@ -12,16 +12,12 @@ Output is plain text by default (agent-friendly). Pass --rich for formatted tabl
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
14
|
import argparse
|
|
15
|
+
from contextlib import closing
|
|
15
16
|
import json
|
|
16
17
|
import os
|
|
17
18
|
import sys
|
|
18
19
|
import xml.etree.ElementTree as ET
|
|
19
20
|
from datetime import datetime, timezone
|
|
20
|
-
from pathlib import Path
|
|
21
|
-
|
|
22
|
-
_project_root = Path(__file__).resolve().parent.parent
|
|
23
|
-
if str(_project_root) not in sys.path:
|
|
24
|
-
sys.path.insert(0, str(_project_root))
|
|
25
21
|
|
|
26
22
|
from rich.console import Console
|
|
27
23
|
from rich.table import Table
|
|
@@ -42,88 +38,64 @@ STALE_WARN_HOURS = 24
|
|
|
42
38
|
|
|
43
39
|
def fetch_impact(entity_name: str, repo_path: str, project: str, max_hops: int = DEFAULT_HOPS) -> dict:
|
|
44
40
|
"""Return callers of entity_name as structured data."""
|
|
45
|
-
|
|
46
|
-
try:
|
|
41
|
+
with closing(get_client(repo_path)) as client:
|
|
47
42
|
rows = client.find_callers(entity_name, project, max_hops)
|
|
48
43
|
updated_at = _entity_updated_at(client, entity_name)
|
|
49
|
-
finally:
|
|
50
|
-
client.close()
|
|
51
44
|
return {"entity": entity_name, "callers": rows, "updated_at": updated_at}
|
|
52
45
|
|
|
53
46
|
|
|
54
47
|
def fetch_grep(pattern: str, repo_path: str, project: str, field: str = "all",
|
|
55
48
|
ignore_case: bool = True, limit: int = 100) -> dict:
|
|
56
49
|
"""Regex-search the graph — 'grep' over indexed entities, not file lines."""
|
|
57
|
-
|
|
58
|
-
try:
|
|
50
|
+
with closing(get_client(repo_path)) as client:
|
|
59
51
|
return client.grep_graph(pattern, project, field=field,
|
|
60
52
|
ignore_case=ignore_case, limit=limit)
|
|
61
|
-
finally:
|
|
62
|
-
client.close()
|
|
63
53
|
|
|
64
54
|
|
|
65
55
|
def fetch_search(query: str, repo_path: str, project: str, limit: int = 30,
|
|
66
56
|
kind: str | None = None) -> dict:
|
|
67
57
|
"""Search the graph for entities matching a keyword — the discovery entry point."""
|
|
68
|
-
|
|
69
|
-
try:
|
|
58
|
+
with closing(get_client(repo_path)) as client:
|
|
70
59
|
rows = client.search_entities(query, project, limit=limit, kind=kind)
|
|
71
|
-
finally:
|
|
72
|
-
client.close()
|
|
73
60
|
return {"query": query, "matches": rows, "count": len(rows)}
|
|
74
61
|
|
|
75
62
|
|
|
76
63
|
def fetch_map(repo_path: str, project: str, include_tests: bool = False) -> dict:
|
|
77
64
|
"""Compact `{file: [symbols]}` index for an agent to reason over during discovery."""
|
|
78
|
-
|
|
79
|
-
try:
|
|
65
|
+
with closing(get_client(repo_path)) as client:
|
|
80
66
|
m = client.symbol_map(project, include_tests=include_tests)
|
|
81
|
-
finally:
|
|
82
|
-
client.close()
|
|
83
67
|
return {"project": project, "files": m, "file_count": len(m)}
|
|
84
68
|
|
|
85
69
|
|
|
86
70
|
def fetch_deps(file_path: str, repo_path: str, project: str, max_hops: int = DEFAULT_HOPS) -> dict:
|
|
87
71
|
"""Return what file_path imports as structured data."""
|
|
88
|
-
|
|
89
|
-
try:
|
|
72
|
+
with closing(get_client(repo_path)) as client:
|
|
90
73
|
rows = client.find_dependencies(file_path, project, max_hops)
|
|
91
74
|
updated_at = client.get_file_updated_at(file_path, project)
|
|
92
|
-
finally:
|
|
93
|
-
client.close()
|
|
94
75
|
return {"file": file_path, "dependencies": rows, "updated_at": updated_at}
|
|
95
76
|
|
|
96
77
|
|
|
97
78
|
def fetch_styles(element_name: str, repo_path: str, project: str) -> dict:
|
|
98
79
|
"""Return CSS selectors that style element_name as structured data."""
|
|
99
|
-
|
|
100
|
-
try:
|
|
80
|
+
with closing(get_client(repo_path)) as client:
|
|
101
81
|
rows = client.find_styles(element_name, project)
|
|
102
82
|
updated_at = _entity_updated_at(client, element_name)
|
|
103
|
-
finally:
|
|
104
|
-
client.close()
|
|
105
83
|
return {"element": element_name, "selectors": rows, "updated_at": updated_at}
|
|
106
84
|
|
|
107
85
|
|
|
108
86
|
def fetch_trace(start_name: str, repo_path: str, project: str, max_hops: int = DEFAULT_HOPS) -> dict:
|
|
109
87
|
"""Return forward call chain from start_name as structured data."""
|
|
110
|
-
|
|
111
|
-
try:
|
|
88
|
+
with closing(get_client(repo_path)) as client:
|
|
112
89
|
rows = client.trace_calls(start_name, project, max_hops)
|
|
113
90
|
updated_at = _entity_updated_at(client, start_name)
|
|
114
|
-
finally:
|
|
115
|
-
client.close()
|
|
116
91
|
return {"entity": start_name, "calls": rows, "updated_at": updated_at}
|
|
117
92
|
|
|
118
93
|
|
|
119
94
|
def fetch_blast_radius(target: str, repo_path: str, project: str, max_hops: int = DEFAULT_HOPS) -> dict:
|
|
120
95
|
"""Return blast radius of target as structured data."""
|
|
121
|
-
|
|
122
|
-
try:
|
|
96
|
+
with closing(get_client(repo_path)) as client:
|
|
123
97
|
rows, target_file = client.get_blast_radius(target, project, max_hops)
|
|
124
98
|
updated_at = client.get_file_updated_at(target_file, project) if target_file else None
|
|
125
|
-
finally:
|
|
126
|
-
client.close()
|
|
127
99
|
|
|
128
100
|
if target_file is None:
|
|
129
101
|
return {"target": target, "found": False, "files": [], "updated_at": None}
|
|
@@ -153,9 +125,7 @@ def fetch_batch_impact(targets: list[str], repo_path: str, project: str, max_hop
|
|
|
153
125
|
for t in targets:
|
|
154
126
|
flat_targets.extend(s.strip() for s in t.split(",") if s.strip())
|
|
155
127
|
|
|
156
|
-
|
|
157
|
-
client = get_client(repo_path)
|
|
158
|
-
try:
|
|
128
|
+
with closing(get_client(repo_path)) as client:
|
|
159
129
|
merged: dict[str, dict] = {}
|
|
160
130
|
resolved: list[str] = []
|
|
161
131
|
staleness_ts: str | None = None
|
|
@@ -181,8 +151,6 @@ def fetch_batch_impact(targets: list[str], repo_path: str, project: str, max_hop
|
|
|
181
151
|
if h < merged[f]["hops"]:
|
|
182
152
|
merged[f]["hops"] = h
|
|
183
153
|
merged[f]["via"].add(target)
|
|
184
|
-
finally:
|
|
185
|
-
client.close()
|
|
186
154
|
|
|
187
155
|
deduped = sorted(
|
|
188
156
|
[{"file": f, "hops": v["hops"], "via": sorted(v["via"])} for f, v in merged.items()],
|
|
@@ -199,11 +167,8 @@ def fetch_batch_impact(targets: list[str], repo_path: str, project: str, max_hop
|
|
|
199
167
|
|
|
200
168
|
def fetch_dead_code(repo_path: str, project: str, show_entrypoints: bool = False) -> dict:
|
|
201
169
|
"""Return dead-code candidates as structured data."""
|
|
202
|
-
|
|
203
|
-
try:
|
|
170
|
+
with closing(get_client(repo_path)) as client:
|
|
204
171
|
result = client.find_dead_code(project)
|
|
205
|
-
finally:
|
|
206
|
-
client.close()
|
|
207
172
|
return {
|
|
208
173
|
"project": project,
|
|
209
174
|
"dead": result["dead"],
|
|
@@ -214,12 +179,9 @@ def fetch_dead_code(repo_path: str, project: str, show_entrypoints: bool = False
|
|
|
214
179
|
|
|
215
180
|
def fetch_tree(repo_path: str, project: str) -> dict:
|
|
216
181
|
"""Return project hierarchy as structured data."""
|
|
217
|
-
|
|
218
|
-
try:
|
|
182
|
+
with closing(get_client(repo_path)) as client:
|
|
219
183
|
rows = client.get_project_tree(project)
|
|
220
184
|
last_ingested = client.get_project_last_ingested(project)
|
|
221
|
-
finally:
|
|
222
|
-
client.close()
|
|
223
185
|
return {"project": project, "tree": rows, "last_ingested": last_ingested}
|
|
224
186
|
|
|
225
187
|
|
|
@@ -231,11 +193,8 @@ def fetch_flow(start_name: str, repo_path: str, project: str, max_hops: int = DE
|
|
|
231
193
|
line — NO embedded source, docstrings, narration, or rendered image. Use
|
|
232
194
|
fetch_flow_summary for a human-facing walkthrough (mermaid + narration).
|
|
233
195
|
"""
|
|
234
|
-
|
|
235
|
-
try:
|
|
196
|
+
with closing(get_client(repo_path)) as client:
|
|
236
197
|
data = client.trace_flow(start_name, project, max_hops, include_external=include_external)
|
|
237
|
-
finally:
|
|
238
|
-
client.close()
|
|
239
198
|
|
|
240
199
|
nodes = data["nodes"]
|
|
241
200
|
edges = data["edges"]
|
|
@@ -272,11 +231,8 @@ def fetch_flow_summary(start_name: str, repo_path: str, project: str, max_hops:
|
|
|
272
231
|
"drawio" renders a diagram. Heavier than fetch_flow — use when a person needs
|
|
273
232
|
to read the flow, not when an agent just needs the structure.
|
|
274
233
|
"""
|
|
275
|
-
|
|
276
|
-
try:
|
|
234
|
+
with closing(get_client(repo_path)) as client:
|
|
277
235
|
data = client.trace_flow(start_name, project, max_hops, include_external=include_external)
|
|
278
|
-
finally:
|
|
279
|
-
client.close()
|
|
280
236
|
|
|
281
237
|
nodes = data["nodes"]
|
|
282
238
|
edges = data["edges"]
|
|
@@ -327,8 +283,6 @@ def main() -> None:
|
|
|
327
283
|
run_deps(args.deps, repo_path, project, max_hops=args.hops, rich=rich)
|
|
328
284
|
elif args.styles:
|
|
329
285
|
run_styles(args.styles, repo_path, project, rich=rich)
|
|
330
|
-
elif args.trace:
|
|
331
|
-
run_trace(args.trace, repo_path, project, max_hops=args.hops, rich=rich)
|
|
332
286
|
elif args.flow:
|
|
333
287
|
run_flow(args.flow, repo_path, project, max_hops=args.hops, rich=rich,
|
|
334
288
|
output=args.flow_output, include_external=args.include_external,
|
|
@@ -437,33 +391,6 @@ def run_styles(element_name: str, repo_path: str, project: str, rich: bool = Fal
|
|
|
437
391
|
print(f" {row.get('selector','')} in {row.get('source_file','')} line {row.get('line','')}")
|
|
438
392
|
|
|
439
393
|
|
|
440
|
-
def run_trace(start_name: str, repo_path: str, project: str, max_hops: int = DEFAULT_HOPS, rich: bool = False) -> None:
|
|
441
|
-
"""Trace the call chain forward from start_name."""
|
|
442
|
-
data = fetch_trace(start_name, repo_path, project, max_hops)
|
|
443
|
-
rows = data["calls"]
|
|
444
|
-
updated_at = data["updated_at"]
|
|
445
|
-
|
|
446
|
-
if not rows:
|
|
447
|
-
print(f"'{start_name}' makes no tracked calls within {max_hops} hops.")
|
|
448
|
-
return
|
|
449
|
-
|
|
450
|
-
stamp = _staleness_line(updated_at, rich_mode=rich)
|
|
451
|
-
if rich:
|
|
452
|
-
console.print(f"\n[bold blue]Call trace from:[/] {start_name}")
|
|
453
|
-
if stamp:
|
|
454
|
-
console.print(stamp)
|
|
455
|
-
table = _make_table(title=f"Call chain from '{start_name}'", columns=["Callee", "Type", "File", "Depth"])
|
|
456
|
-
for row in rows:
|
|
457
|
-
table.add_row(row.get("callee_name",""), row.get("callee_type",""), row.get("callee_file",""), str(row.get("depth","")))
|
|
458
|
-
console.print(table)
|
|
459
|
-
else:
|
|
460
|
-
if stamp:
|
|
461
|
-
print(stamp)
|
|
462
|
-
print(f"Call chain from '{start_name}':")
|
|
463
|
-
for row in rows:
|
|
464
|
-
print(f" {row.get('callee_name','')} ({row.get('callee_type','')}) in {row.get('callee_file','')} [depth {row.get('depth','')}]")
|
|
465
|
-
|
|
466
|
-
|
|
467
394
|
def run_blast_radius(target: str, repo_path: str, project: str, max_hops: int = DEFAULT_HOPS, rich: bool = False) -> None:
|
|
468
395
|
"""Show every file that DEPENDS ON the target (reverse: callers/importers/subclasses)."""
|
|
469
396
|
data = fetch_blast_radius(target, repo_path, project, max_hops)
|
|
@@ -1018,7 +945,6 @@ def _parse_args() -> argparse.Namespace:
|
|
|
1018
945
|
parser.add_argument("--impact", metavar="ENTITY")
|
|
1019
946
|
parser.add_argument("--deps", metavar="FILE")
|
|
1020
947
|
parser.add_argument("--styles", metavar="ELEMENT")
|
|
1021
|
-
parser.add_argument("--trace", metavar="ENTITY")
|
|
1022
948
|
parser.add_argument("--flow", metavar="ENTITY")
|
|
1023
949
|
parser.add_argument("--flow-summary", metavar="ENTITY",
|
|
1024
950
|
help="Flow trace + rendered narration (mermaid by default) for a human")
|
|
@@ -17,6 +17,7 @@ import json
|
|
|
17
17
|
from typing import Any
|
|
18
18
|
|
|
19
19
|
import anthropic
|
|
20
|
+
from rich.progress import track
|
|
20
21
|
|
|
21
22
|
from config import anthropic_api_key
|
|
22
23
|
from models.code_types import CodeTriple
|
|
@@ -67,14 +68,7 @@ def normalize_triples(triples: list[CodeTriple], progress: bool = False) -> list
|
|
|
67
68
|
batches = _split_into_batches(triples, BATCH_SIZE)
|
|
68
69
|
normalized: list[CodeTriple] = []
|
|
69
70
|
|
|
70
|
-
if progress
|
|
71
|
-
try:
|
|
72
|
-
from tqdm import tqdm
|
|
73
|
-
batches_iter = tqdm(batches, desc="Normalizing batches", unit="batch")
|
|
74
|
-
except ImportError:
|
|
75
|
-
batches_iter = batches
|
|
76
|
-
else:
|
|
77
|
-
batches_iter = batches
|
|
71
|
+
batches_iter = track(batches, description="Normalizing batches") if progress else batches
|
|
78
72
|
|
|
79
73
|
for batch in batches_iter:
|
|
80
74
|
corrected = _normalize_batch(client, batch)
|
|
@@ -13,6 +13,7 @@ import re
|
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
from typing import Callable
|
|
15
15
|
|
|
16
|
+
from rich.progress import track
|
|
16
17
|
from tree_sitter import Language, Parser, Node
|
|
17
18
|
|
|
18
19
|
from models.code_types import CodeTriple
|
|
@@ -316,11 +317,6 @@ def _extract_python(root: Node, source: bytes, file_path: str) -> list[CodeTripl
|
|
|
316
317
|
return triples
|
|
317
318
|
|
|
318
319
|
|
|
319
|
-
def _extract_python_callee(call_node: Node) -> str | None:
|
|
320
|
-
"""Back-compat shim: callee name only."""
|
|
321
|
-
return _extract_python_call_parts(call_node)[0]
|
|
322
|
-
|
|
323
|
-
|
|
324
320
|
def _extract_python_call_parts(call_node: Node) -> tuple[str | None, str | None, Node | None]:
|
|
325
321
|
"""Return (callee_name, receiver_text, receiver_object_node) for a `call`."""
|
|
326
322
|
if not call_node.children:
|
|
@@ -1444,11 +1440,6 @@ def _resolve_receiver_type(receiver: str | None, obj: Node | None, call_node: No
|
|
|
1444
1440
|
return None
|
|
1445
1441
|
|
|
1446
1442
|
|
|
1447
|
-
def _extract_js_callee(call_node: Node) -> str | None:
|
|
1448
|
-
"""Back-compat shim: callee name only."""
|
|
1449
|
-
return _extract_js_call(call_node)[0]
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
1443
|
def _extract_js_require_target(call_node: Node) -> str | None:
|
|
1453
1444
|
"""Module string of a `require('x')` or dynamic `import('x')` call, else None.
|
|
1454
1445
|
|
|
@@ -1785,14 +1776,7 @@ def parse_directory(
|
|
|
1785
1776
|
if Path(filename).suffix.lower() in SUPPORTED_EXTENSIONS:
|
|
1786
1777
|
source_files.append(os.path.join(dirpath, filename))
|
|
1787
1778
|
|
|
1788
|
-
if progress
|
|
1789
|
-
try:
|
|
1790
|
-
from tqdm import tqdm
|
|
1791
|
-
source_files_iter = tqdm(source_files, desc="Parsing files", unit="file")
|
|
1792
|
-
except ImportError:
|
|
1793
|
-
source_files_iter = source_files
|
|
1794
|
-
else:
|
|
1795
|
-
source_files_iter = source_files
|
|
1779
|
+
source_files_iter = track(source_files, description="Parsing files") if progress else source_files
|
|
1796
1780
|
|
|
1797
1781
|
triples: list[CodeTriple] = []
|
|
1798
1782
|
for full_path in source_files_iter:
|
|
@@ -16,8 +16,6 @@ from __future__ import annotations
|
|
|
16
16
|
import os
|
|
17
17
|
import time
|
|
18
18
|
from pathlib import Path
|
|
19
|
-
from typing import Optional
|
|
20
|
-
|
|
21
19
|
from watchdog.events import FileSystemEventHandler, FileSystemEvent
|
|
22
20
|
from watchdog.observers import Observer
|
|
23
21
|
|
|
@@ -48,7 +46,7 @@ class _CodeFileEventHandler(FileSystemEventHandler):
|
|
|
48
46
|
self._project_root = project_root
|
|
49
47
|
self._project_name = project_name
|
|
50
48
|
self._client = client
|
|
51
|
-
self._file_id_map = file_id_map # {rel_path:
|
|
49
|
+
self._file_id_map = file_id_map # {rel_path: file_node_id}
|
|
52
50
|
|
|
53
51
|
def on_modified(self, event: FileSystemEvent) -> None:
|
|
54
52
|
if event.is_directory:
|
|
@@ -81,7 +79,7 @@ class _CodeFileEventHandler(FileSystemEventHandler):
|
|
|
81
79
|
# ------------------------------------------------------------------
|
|
82
80
|
|
|
83
81
|
def _handle_change(self, abs_path: str) -> None:
|
|
84
|
-
"""Re-parse a changed file and apply only the delta to
|
|
82
|
+
"""Re-parse a changed file and apply only the delta to the local graph."""
|
|
85
83
|
ext = Path(abs_path).suffix.lower()
|
|
86
84
|
if ext not in SUPPORTED_EXTENSIONS:
|
|
87
85
|
return
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Hierarchy builder — walks a repo and writes the Project → Folder → File skeleton to
|
|
1
|
+
"""Hierarchy builder — walks a repo and writes the Project → Folder → File skeleton to the local graph.
|
|
2
2
|
|
|
3
3
|
This runs before code_parser so every file has a node to attach entities to.
|
|
4
4
|
No API calls — purely local filesystem traversal.
|
|
@@ -24,9 +24,9 @@ _SOURCE_EXTENSIONS = {".py", ".js", ".jsx", ".ts", ".tsx", ".html", ".css", ".sc
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def build_hierarchy(project_root: str, project_name: str, client) -> dict[str, str]:
|
|
27
|
-
"""Walk project_root and write Project → Folder → File nodes to
|
|
27
|
+
"""Walk project_root and write Project → Folder → File nodes to the local graph.
|
|
28
28
|
|
|
29
|
-
Returns a mapping of {relative_file_path:
|
|
29
|
+
Returns a mapping of {relative_file_path: node_id} so the caller
|
|
30
30
|
can attach entity nodes to the correct File nodes.
|
|
31
31
|
|
|
32
32
|
Args:
|
|
@@ -74,34 +74,6 @@ def build_hierarchy(project_root: str, project_name: str, client) -> dict[str, s
|
|
|
74
74
|
return file_id_map
|
|
75
75
|
|
|
76
76
|
|
|
77
|
-
def collect_file_nodes(project_root: str) -> list[FileNode]:
|
|
78
|
-
"""Return FileNode objects for every supported source file under project_root.
|
|
79
|
-
|
|
80
|
-
Useful for dry-run inspection or passing file lists to other pipeline stages.
|
|
81
|
-
"""
|
|
82
|
-
nodes: list[FileNode] = []
|
|
83
|
-
for dirpath, dirnames, filenames in os.walk(project_root):
|
|
84
|
-
dirnames[:] = [d for d in dirnames if d not in _SKIP_DIRS]
|
|
85
|
-
for filename in filenames:
|
|
86
|
-
ext = Path(filename).suffix.lower()
|
|
87
|
-
if ext in _SOURCE_EXTENSIONS:
|
|
88
|
-
full_path = os.path.join(dirpath, filename)
|
|
89
|
-
rel_path = os.path.relpath(full_path, project_root)
|
|
90
|
-
nodes.append(_make_file_node(rel_path, project_root))
|
|
91
|
-
return nodes
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def collect_folder_nodes(project_root: str) -> list[FolderNode]:
|
|
95
|
-
"""Return FolderNode objects for every non-skipped directory under project_root."""
|
|
96
|
-
nodes: list[FolderNode] = []
|
|
97
|
-
for dirpath, dirnames, _ in os.walk(project_root):
|
|
98
|
-
dirnames[:] = [d for d in dirnames if d not in _SKIP_DIRS]
|
|
99
|
-
rel_dir = os.path.relpath(dirpath, project_root)
|
|
100
|
-
if rel_dir != ".":
|
|
101
|
-
nodes.append(_make_folder_node(rel_dir, project_root))
|
|
102
|
-
return nodes
|
|
103
|
-
|
|
104
|
-
|
|
105
77
|
# ---------------------------------------------------------------------------
|
|
106
78
|
# Internal helpers
|
|
107
79
|
# ---------------------------------------------------------------------------
|