codegraphcontext 0.1.7__tar.gz → 0.1.8__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.
- {codegraphcontext-0.1.7/src/codegraphcontext.egg-info → codegraphcontext-0.1.8}/PKG-INFO +40 -4
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/README.md +39 -3
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/pyproject.toml +1 -1
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext/server.py +17 -22
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext/tools/code_finder.py +145 -13
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext/tools/graph_builder.py +124 -21
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext/tools/import_extractor.py +13 -0
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8/src/codegraphcontext.egg-info}/PKG-INFO +40 -4
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/LICENSE +0 -0
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/setup.cfg +0 -0
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext/__init__.py +0 -0
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext/__main__.py +0 -0
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext/cli/__init__.py +0 -0
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext/cli/main.py +0 -0
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext/cli/setup_wizard.py +0 -0
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext/core/__init__.py +0 -0
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext/core/database.py +0 -0
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext/core/jobs.py +0 -0
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext/core/watcher.py +0 -0
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext/prompts.py +0 -0
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext/tools/__init__.py +0 -0
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext/tools/system.py +0 -0
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext.egg-info/SOURCES.txt +0 -0
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext.egg-info/dependency_links.txt +0 -0
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext.egg-info/entry_points.txt +0 -0
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext.egg-info/requires.txt +0 -0
- {codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codegraphcontext
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8
|
|
4
4
|
Summary: An MCP server that indexes local code into a graph database to provide context to AI assistants.
|
|
5
5
|
Author-email: Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -53,9 +53,13 @@ Dynamic: license-file
|
|
|
53
53
|
# CodeGraphContext
|
|
54
54
|
[](https://github.com/Shashankss1205/CodeGraphContext/actions/workflows/test.yml)
|
|
55
55
|
|
|
56
|
-
|
|
57
56
|
An MCP server that indexes local code into a graph database to provide context to AI assistants.
|
|
58
57
|
|
|
58
|
+
## Project Details
|
|
59
|
+
- **Version:** 0.1.8
|
|
60
|
+
- **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
|
|
61
|
+
- **License:** MIT License (See [LICENSE](LICENSE) for details)
|
|
62
|
+
|
|
59
63
|
## Features
|
|
60
64
|
|
|
61
65
|
- **Code Indexing:** Analyzes Python code and builds a knowledge graph of its components.
|
|
@@ -63,12 +67,23 @@ An MCP server that indexes local code into a graph database to provide context t
|
|
|
63
67
|
- **Live Updates:** Watches local files for changes and automatically updates the graph.
|
|
64
68
|
- **Interactive Setup:** A user-friendly command-line wizard for easy setup.
|
|
65
69
|
|
|
70
|
+
## Dependencies
|
|
71
|
+
|
|
72
|
+
- `neo4j>=5.15.0`
|
|
73
|
+
- `watchdog>=3.0.0`
|
|
74
|
+
- `requests>=2.31.0`
|
|
75
|
+
- `stdlibs>=2023.11.18`
|
|
76
|
+
- `typer[all]>=0.9.0`
|
|
77
|
+
- `rich>=13.7.0`
|
|
78
|
+
- `inquirerpy>=0.3.4`
|
|
79
|
+
- `python-dotenv>=1.0.0`
|
|
80
|
+
|
|
66
81
|
## Getting Started
|
|
67
82
|
|
|
68
83
|
1. **Install:** `pip install codegraphcontext`
|
|
69
84
|
2. **Setup:** `cgc setup`
|
|
70
85
|
3. **Start:** `cgc start`
|
|
71
|
-
4. **Index Code:** `cgc tool add-code-to-graph '{"path": "/path/to/your/project"}'`
|
|
86
|
+
4. **Index Code:** `cgc tool add-code-to-graph '{"path": "/path/to/your/project"}'` (Under active development)
|
|
72
87
|
|
|
73
88
|
## MCP Client Configuration
|
|
74
89
|
|
|
@@ -98,7 +113,11 @@ Add the following to your MCP client's configuration:
|
|
|
98
113
|
"analyze_code_relationships",
|
|
99
114
|
"watch_directory",
|
|
100
115
|
"find_dead_code",
|
|
101
|
-
"execute_cypher_query"
|
|
116
|
+
"execute_cypher_query",
|
|
117
|
+
"calculate_cyclomatic_complexity",
|
|
118
|
+
"find_most_complex_functions",
|
|
119
|
+
"list_indexed_repositories",
|
|
120
|
+
"delete_repository"
|
|
102
121
|
],
|
|
103
122
|
"disabled": false
|
|
104
123
|
},
|
|
@@ -149,5 +168,22 @@ Once the server is running, you can interact with it through your AI assistant u
|
|
|
149
168
|
- "Which files import the `requests` library?"
|
|
150
169
|
- "Find all implementations of the `render` method."
|
|
151
170
|
|
|
171
|
+
- **Advanced Call Chain and Dependency Tracking (Spanning Hundreds of Files):**
|
|
172
|
+
The CodeGraphContext excels at tracing complex execution flows and dependencies across vast codebases. Leveraging the power of graph databases, it can identify direct and indirect callers and callees, even when a function is called through multiple layers of abstraction or across numerous files. This is invaluable for:
|
|
173
|
+
- **Impact Analysis:** Understand the full ripple effect of a change to a core function.
|
|
174
|
+
- **Debugging:** Trace the path of execution from an entry point to a specific bug.
|
|
175
|
+
- **Code Comprehension:** Grasp how different parts of a large system interact.
|
|
176
|
+
|
|
177
|
+
- "Show me the full call chain from the `main` function to `process_data`."
|
|
178
|
+
- "Find all functions that directly or indirectly call `validate_input`."
|
|
179
|
+
- "What are all the functions that `initialize_system` eventually calls?"
|
|
180
|
+
- "Trace the dependencies of the `DatabaseManager` module."
|
|
181
|
+
|
|
152
182
|
- **Code Quality and Maintenance:**
|
|
153
183
|
- "Is there any dead or unused code in this project?"
|
|
184
|
+
- "Calculate the cyclomatic complexity of the `process_data` function in `src/utils.py`."
|
|
185
|
+
- "Find the 5 most complex functions in the codebase."
|
|
186
|
+
|
|
187
|
+
- **Repository Management:**
|
|
188
|
+
- "List all currently indexed repositories."
|
|
189
|
+
- "Delete the indexed repository at `/path/to/old-project`."
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# CodeGraphContext
|
|
2
2
|
[](https://github.com/Shashankss1205/CodeGraphContext/actions/workflows/test.yml)
|
|
3
3
|
|
|
4
|
-
|
|
5
4
|
An MCP server that indexes local code into a graph database to provide context to AI assistants.
|
|
6
5
|
|
|
6
|
+
## Project Details
|
|
7
|
+
- **Version:** 0.1.8
|
|
8
|
+
- **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
|
|
9
|
+
- **License:** MIT License (See [LICENSE](LICENSE) for details)
|
|
10
|
+
|
|
7
11
|
## Features
|
|
8
12
|
|
|
9
13
|
- **Code Indexing:** Analyzes Python code and builds a knowledge graph of its components.
|
|
@@ -11,12 +15,23 @@ An MCP server that indexes local code into a graph database to provide context t
|
|
|
11
15
|
- **Live Updates:** Watches local files for changes and automatically updates the graph.
|
|
12
16
|
- **Interactive Setup:** A user-friendly command-line wizard for easy setup.
|
|
13
17
|
|
|
18
|
+
## Dependencies
|
|
19
|
+
|
|
20
|
+
- `neo4j>=5.15.0`
|
|
21
|
+
- `watchdog>=3.0.0`
|
|
22
|
+
- `requests>=2.31.0`
|
|
23
|
+
- `stdlibs>=2023.11.18`
|
|
24
|
+
- `typer[all]>=0.9.0`
|
|
25
|
+
- `rich>=13.7.0`
|
|
26
|
+
- `inquirerpy>=0.3.4`
|
|
27
|
+
- `python-dotenv>=1.0.0`
|
|
28
|
+
|
|
14
29
|
## Getting Started
|
|
15
30
|
|
|
16
31
|
1. **Install:** `pip install codegraphcontext`
|
|
17
32
|
2. **Setup:** `cgc setup`
|
|
18
33
|
3. **Start:** `cgc start`
|
|
19
|
-
4. **Index Code:** `cgc tool add-code-to-graph '{"path": "/path/to/your/project"}'`
|
|
34
|
+
4. **Index Code:** `cgc tool add-code-to-graph '{"path": "/path/to/your/project"}'` (Under active development)
|
|
20
35
|
|
|
21
36
|
## MCP Client Configuration
|
|
22
37
|
|
|
@@ -46,7 +61,11 @@ Add the following to your MCP client's configuration:
|
|
|
46
61
|
"analyze_code_relationships",
|
|
47
62
|
"watch_directory",
|
|
48
63
|
"find_dead_code",
|
|
49
|
-
"execute_cypher_query"
|
|
64
|
+
"execute_cypher_query",
|
|
65
|
+
"calculate_cyclomatic_complexity",
|
|
66
|
+
"find_most_complex_functions",
|
|
67
|
+
"list_indexed_repositories",
|
|
68
|
+
"delete_repository"
|
|
50
69
|
],
|
|
51
70
|
"disabled": false
|
|
52
71
|
},
|
|
@@ -97,5 +116,22 @@ Once the server is running, you can interact with it through your AI assistant u
|
|
|
97
116
|
- "Which files import the `requests` library?"
|
|
98
117
|
- "Find all implementations of the `render` method."
|
|
99
118
|
|
|
119
|
+
- **Advanced Call Chain and Dependency Tracking (Spanning Hundreds of Files):**
|
|
120
|
+
The CodeGraphContext excels at tracing complex execution flows and dependencies across vast codebases. Leveraging the power of graph databases, it can identify direct and indirect callers and callees, even when a function is called through multiple layers of abstraction or across numerous files. This is invaluable for:
|
|
121
|
+
- **Impact Analysis:** Understand the full ripple effect of a change to a core function.
|
|
122
|
+
- **Debugging:** Trace the path of execution from an entry point to a specific bug.
|
|
123
|
+
- **Code Comprehension:** Grasp how different parts of a large system interact.
|
|
124
|
+
|
|
125
|
+
- "Show me the full call chain from the `main` function to `process_data`."
|
|
126
|
+
- "Find all functions that directly or indirectly call `validate_input`."
|
|
127
|
+
- "What are all the functions that `initialize_system` eventually calls?"
|
|
128
|
+
- "Trace the dependencies of the `DatabaseManager` module."
|
|
129
|
+
|
|
100
130
|
- **Code Quality and Maintenance:**
|
|
101
131
|
- "Is there any dead or unused code in this project?"
|
|
132
|
+
- "Calculate the cyclomatic complexity of the `process_data` function in `src/utils.py`."
|
|
133
|
+
- "Find the 5 most complex functions in the codebase."
|
|
134
|
+
|
|
135
|
+
- **Repository Management:**
|
|
136
|
+
- "List all currently indexed repositories."
|
|
137
|
+
- "Delete the indexed repository at `/path/to/old-project`."
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "codegraphcontext"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.8"
|
|
4
4
|
description = "An MCP server that indexes local code into a graph database to provide context to AI assistants."
|
|
5
5
|
authors = [{ name = "Shashank Shekhar Singh", email = "shashankshekharsingh1205@gmail.com" }]
|
|
6
6
|
readme = "README.md"
|
|
@@ -103,11 +103,11 @@ class MCPServer:
|
|
|
103
103
|
},
|
|
104
104
|
"analyze_code_relationships": {
|
|
105
105
|
"name": "analyze_code_relationships",
|
|
106
|
-
"description": "Analyze code relationships like 'who calls this function' or 'class hierarchy'.",
|
|
106
|
+
"description": "Analyze code relationships like 'who calls this function' or 'class hierarchy'. Supported query types include: find_callers, find_callees, find_all_callers, find_all_callees, find_importers, who_modifies, class_hierarchy, overrides, dead_code, call_chain, module_deps, variable_scope, find_complexity, find_functions_by_argument, find_functions_by_decorator.",
|
|
107
107
|
"inputSchema": {
|
|
108
108
|
"type": "object",
|
|
109
109
|
"properties": {
|
|
110
|
-
"query_type": {"type": "string", "description": "Type of relationship query to run."},
|
|
110
|
+
"query_type": {"type": "string", "description": "Type of relationship query to run.", "enum": ["find_callers", "find_callees", "find_all_callers", "find_all_callees", "find_importers", "who_modifies", "class_hierarchy", "overrides", "dead_code", "call_chain", "module_deps", "variable_scope", "find_complexity", "find_functions_by_argument", "find_functions_by_decorator"]},
|
|
111
111
|
"target": {"type": "string", "description": "The function, class, or module to analyze."},
|
|
112
112
|
"context": {"type": "string", "description": "Optional: specific file path for precise results."}
|
|
113
113
|
},
|
|
@@ -159,11 +159,12 @@ class MCPServer:
|
|
|
159
159
|
},
|
|
160
160
|
"find_dead_code": {
|
|
161
161
|
"name": "find_dead_code",
|
|
162
|
-
"description": "Find potentially unused functions (dead code) across the entire indexed codebase.",
|
|
162
|
+
"description": "Find potentially unused functions (dead code) across the entire indexed codebase, optionally excluding functions with specific decorators.",
|
|
163
163
|
"inputSchema": {
|
|
164
164
|
"type": "object",
|
|
165
|
-
"properties": {
|
|
166
|
-
|
|
165
|
+
"properties": {
|
|
166
|
+
"exclude_decorated_with": {"type": "array", "items": {"type": "string"}, "description": "Optional: A list of decorator names (e.g., '@app.route') to exclude from dead code detection.", "default": []}
|
|
167
|
+
}
|
|
167
168
|
}
|
|
168
169
|
},
|
|
169
170
|
"calculate_cyclomatic_complexity": {
|
|
@@ -207,7 +208,6 @@ class MCPServer:
|
|
|
207
208
|
"required": ["repo_path"]
|
|
208
209
|
}
|
|
209
210
|
}
|
|
210
|
-
# Other tools like list_imports, add_package_to_graph can be added here following the same pattern
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
def get_database_status(self) -> dict:
|
|
@@ -304,12 +304,12 @@ class MCPServer:
|
|
|
304
304
|
"details": str(e)
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
-
def find_dead_code_tool(self) -> Dict[str, Any]:
|
|
307
|
+
def find_dead_code_tool(self, **args) -> Dict[str, Any]:
|
|
308
308
|
"""Tool to find potentially dead code across the entire project."""
|
|
309
|
+
exclude_decorated_with = args.get("exclude_decorated_with", [])
|
|
309
310
|
try:
|
|
310
311
|
debug_log("Finding dead code.")
|
|
311
|
-
|
|
312
|
-
results = self.code_finder.find_dead_code()
|
|
312
|
+
results = self.code_finder.find_dead_code(exclude_decorated_with=exclude_decorated_with)
|
|
313
313
|
|
|
314
314
|
return {
|
|
315
315
|
"success": True,
|
|
@@ -446,17 +446,11 @@ class MCPServer:
|
|
|
446
446
|
else:
|
|
447
447
|
return {"error": f"Path {path} does not exist"}
|
|
448
448
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
# 'itertools', 'functools', 'operator', 'pathlib', 'urllib', 'http', 'logging',
|
|
455
|
-
# 'threading', 'multiprocessing', 'asyncio', 'typing', 'dataclasses', 'enum',
|
|
456
|
-
# 'abc', 'io', 'csv', 'sqlite3', 'pickle', 'base64', 'hashlib', 'hmac', 'secrets',
|
|
457
|
-
# 'unittest', 'doctest', 'pdb', 'profile', 'cProfile', 'timeit'
|
|
458
|
-
# }
|
|
459
|
-
all_imports = all_imports - stdlib_modules
|
|
449
|
+
# Removed standard library filtering as per user request.
|
|
450
|
+
# if language == 'python':
|
|
451
|
+
# # Get the list of stdlib modules for the current Python version
|
|
452
|
+
# stdlib_modules = set(stdlibs.module_names)
|
|
453
|
+
# all_imports = all_imports - stdlib_modules
|
|
460
454
|
|
|
461
455
|
return {
|
|
462
456
|
"imports": sorted(list(all_imports)), "language": language,
|
|
@@ -642,8 +636,9 @@ class MCPServer:
|
|
|
642
636
|
return {
|
|
643
637
|
"error": "Both 'query_type' and 'target' are required",
|
|
644
638
|
"supported_query_types": [
|
|
645
|
-
"
|
|
646
|
-
"class_hierarchy", "overrides", "dead_code"
|
|
639
|
+
"find_callers", "find_callees", "find_importers", "who_modifies",
|
|
640
|
+
"class_hierarchy", "overrides", "dead_code", "call_chain",
|
|
641
|
+
"module_deps", "variable_scope", "find_complexity"
|
|
647
642
|
]
|
|
648
643
|
}
|
|
649
644
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import logging
|
|
3
3
|
import re
|
|
4
4
|
from typing import Any, Dict, List
|
|
5
|
+
from pathlib import Path
|
|
5
6
|
|
|
6
7
|
from ..core.database import DatabaseManager
|
|
7
8
|
|
|
@@ -116,6 +117,56 @@ class CodeFinder:
|
|
|
116
117
|
|
|
117
118
|
return results
|
|
118
119
|
|
|
120
|
+
def find_functions_by_argument(self, argument_name: str, file_path: str = None) -> List[Dict]:
|
|
121
|
+
"""Find functions that take a specific argument name."""
|
|
122
|
+
with self.driver.session() as session:
|
|
123
|
+
if file_path:
|
|
124
|
+
query = """
|
|
125
|
+
MATCH (f:Function)-[:HAS_PARAMETER]->(p:Parameter)
|
|
126
|
+
WHERE p.name = $argument_name AND f.file_path = $file_path
|
|
127
|
+
RETURN f.name AS function_name, f.file_path AS file_path, f.line_number AS line_number,
|
|
128
|
+
f.docstring AS docstring, f.is_dependency AS is_dependency
|
|
129
|
+
ORDER BY f.is_dependency ASC, f.file_path, f.line_number
|
|
130
|
+
LIMIT 20
|
|
131
|
+
"""
|
|
132
|
+
result = session.run(query, argument_name=argument_name, file_path=file_path)
|
|
133
|
+
else:
|
|
134
|
+
query = """
|
|
135
|
+
MATCH (f:Function)-[:HAS_PARAMETER]->(p:Parameter)
|
|
136
|
+
WHERE p.name = $argument_name
|
|
137
|
+
RETURN f.name AS function_name, f.file_path AS file_path, f.line_number AS line_number,
|
|
138
|
+
f.docstring AS docstring, f.is_dependency AS is_dependency
|
|
139
|
+
ORDER BY f.is_dependency ASC, f.file_path, f.line_number
|
|
140
|
+
LIMIT 20
|
|
141
|
+
"""
|
|
142
|
+
result = session.run(query, argument_name=argument_name)
|
|
143
|
+
return [dict(record) for record in result]
|
|
144
|
+
|
|
145
|
+
def find_functions_by_decorator(self, decorator_name: str, file_path: str = None) -> List[Dict]:
|
|
146
|
+
"""Find functions that have a specific decorator applied to them."""
|
|
147
|
+
with self.driver.session() as session:
|
|
148
|
+
if file_path:
|
|
149
|
+
query = """
|
|
150
|
+
MATCH (f:Function)
|
|
151
|
+
WHERE f.file_path = $file_path AND $decorator_name IN f.decorators
|
|
152
|
+
RETURN f.name AS function_name, f.file_path AS file_path, f.line_number AS line_number,
|
|
153
|
+
f.docstring AS docstring, f.is_dependency AS is_dependency, f.decorators AS decorators
|
|
154
|
+
ORDER BY f.is_dependency ASC, f.file_path, f.line_number
|
|
155
|
+
LIMIT 20
|
|
156
|
+
"""
|
|
157
|
+
result = session.run(query, decorator_name=decorator_name, file_path=file_path)
|
|
158
|
+
else:
|
|
159
|
+
query = """
|
|
160
|
+
MATCH (f:Function)
|
|
161
|
+
WHERE $decorator_name IN f.decorators
|
|
162
|
+
RETURN f.name AS function_name, f.file_path AS file_path, f.line_number AS line_number,
|
|
163
|
+
f.docstring AS docstring, f.is_dependency AS is_dependency, f.decorators AS decorators
|
|
164
|
+
ORDER BY f.is_dependency ASC, f.file_path, f.line_number
|
|
165
|
+
LIMIT 20
|
|
166
|
+
"""
|
|
167
|
+
result = session.run(query, decorator_name=decorator_name)
|
|
168
|
+
return [dict(record) for record in result]
|
|
169
|
+
|
|
119
170
|
def who_calls_function(self, function_name: str, file_path: str = None) -> List[Dict]:
|
|
120
171
|
"""Find what functions call a specific function using CALLS relationships with improved matching"""
|
|
121
172
|
with self.driver.session() as session:
|
|
@@ -230,7 +281,7 @@ class CodeFinder:
|
|
|
230
281
|
with self.driver.session() as session:
|
|
231
282
|
result = session.run("""
|
|
232
283
|
MATCH (file:File)-[imp:IMPORTS]->(module:Module)
|
|
233
|
-
WHERE module.name
|
|
284
|
+
WHERE module.name = $module_name OR module.full_import_name CONTAINS $module_name
|
|
234
285
|
OPTIONAL MATCH (repo:Repository)-[:CONTAINS]->(file)
|
|
235
286
|
RETURN DISTINCT
|
|
236
287
|
file.name as file_name,
|
|
@@ -344,8 +395,11 @@ class CodeFinder:
|
|
|
344
395
|
|
|
345
396
|
return [dict(record) for record in result]
|
|
346
397
|
|
|
347
|
-
def find_dead_code(self) -> Dict[str, Any]:
|
|
348
|
-
"""Find potentially unused functions (not called by other functions in the project)"""
|
|
398
|
+
def find_dead_code(self, exclude_decorated_with: List[str] = None) -> Dict[str, Any]:
|
|
399
|
+
"""Find potentially unused functions (not called by other functions in the project), optionally excluding those with specific decorators."""
|
|
400
|
+
if exclude_decorated_with is None:
|
|
401
|
+
exclude_decorated_with = []
|
|
402
|
+
|
|
349
403
|
with self.driver.session() as session:
|
|
350
404
|
result = session.run("""
|
|
351
405
|
MATCH (func:Function)
|
|
@@ -353,6 +407,7 @@ class CodeFinder:
|
|
|
353
407
|
AND NOT func.name IN ['main', '__init__', '__main__', 'setup', 'run', '__new__', '__del__']
|
|
354
408
|
AND NOT func.name STARTS WITH '_test'
|
|
355
409
|
AND NOT func.name STARTS WITH 'test_'
|
|
410
|
+
AND ALL(decorator_name IN $exclude_decorated_with WHERE NOT decorator_name IN func.decorators)
|
|
356
411
|
WITH func
|
|
357
412
|
OPTIONAL MATCH (caller:Function)-[:CALLS]->(func)
|
|
358
413
|
WHERE caller.is_dependency = false
|
|
@@ -368,37 +423,84 @@ class CodeFinder:
|
|
|
368
423
|
file.name as file_name
|
|
369
424
|
ORDER BY func.file_path, func.line_number
|
|
370
425
|
LIMIT 50
|
|
371
|
-
""")
|
|
426
|
+
""", exclude_decorated_with=exclude_decorated_with)
|
|
372
427
|
|
|
373
428
|
return {
|
|
374
429
|
"potentially_unused_functions": [dict(record) for record in result],
|
|
375
430
|
"note": "These functions might be unused, but could be entry points, callbacks, or called dynamically"
|
|
376
431
|
}
|
|
377
432
|
|
|
433
|
+
def find_all_callers(self, function_name: str, file_path: str = None) -> List[Dict]:
|
|
434
|
+
"""Find all direct and indirect callers of a specific function."""
|
|
435
|
+
with self.driver.session() as session:
|
|
436
|
+
if file_path:
|
|
437
|
+
# Find functions within the specified file_path that call the target function
|
|
438
|
+
query = """
|
|
439
|
+
MATCH (f:Function)-[:CALLS*]->(target:Function {name: $function_name})
|
|
440
|
+
WHERE f.file_path = $file_path
|
|
441
|
+
RETURN DISTINCT f.name AS caller_name, f.file_path AS caller_file_path, f.line_number AS caller_line_number, f.is_dependency AS caller_is_dependency
|
|
442
|
+
ORDER BY f.is_dependency ASC, f.file_path, f.line_number
|
|
443
|
+
LIMIT 50
|
|
444
|
+
"""
|
|
445
|
+
result = session.run(query, function_name=function_name, file_path=file_path)
|
|
446
|
+
else:
|
|
447
|
+
# If no file_path (context) is provided, find all callers of the function by name
|
|
448
|
+
query = """
|
|
449
|
+
MATCH (f:Function)-[:CALLS*]->(target:Function {name: $function_name})
|
|
450
|
+
RETURN DISTINCT f.name AS caller_name, f.file_path AS caller_file_path, f.line_number AS caller_line_number, f.is_dependency AS caller_is_dependency
|
|
451
|
+
ORDER BY f.is_dependency ASC, f.file_path, f.line_number
|
|
452
|
+
LIMIT 50
|
|
453
|
+
"""
|
|
454
|
+
result = session.run(query, function_name=function_name)
|
|
455
|
+
return [dict(record) for record in result]
|
|
456
|
+
|
|
457
|
+
def find_all_callees(self, function_name: str, file_path: str = None) -> List[Dict]:
|
|
458
|
+
"""Find all direct and indirect callees of a specific function."""
|
|
459
|
+
with self.driver.session() as session:
|
|
460
|
+
if file_path:
|
|
461
|
+
query = """
|
|
462
|
+
MATCH (caller:Function {name: $function_name, file_path: $file_path})
|
|
463
|
+
MATCH (caller)-[:CALLS*]->(f:Function)
|
|
464
|
+
RETURN DISTINCT f.name AS callee_name, f.file_path AS callee_file_path, f.line_number AS callee_line_number, f.is_dependency AS callee_is_dependency
|
|
465
|
+
ORDER BY f.is_dependency ASC, f.file_path, f.line_number
|
|
466
|
+
LIMIT 50
|
|
467
|
+
"""
|
|
468
|
+
result = session.run(query, function_name=function_name, file_path=file_path)
|
|
469
|
+
else:
|
|
470
|
+
query = """
|
|
471
|
+
MATCH (caller:Function {name: $function_name})
|
|
472
|
+
MATCH (caller)-[:CALLS*]->(f:Function)
|
|
473
|
+
RETURN DISTINCT f.name AS callee_name, f.file_path AS callee_file_path, f.line_number AS callee_line_number, f.is_dependency AS callee_is_dependency
|
|
474
|
+
ORDER BY f.is_dependency ASC, f.file_path, f.line_number
|
|
475
|
+
LIMIT 50
|
|
476
|
+
"""
|
|
477
|
+
result = session.run(query, function_name=function_name)
|
|
478
|
+
return [dict(record) for record in result]
|
|
479
|
+
|
|
378
480
|
def find_function_call_chain(self, start_function: str, end_function: str, max_depth: int = 5) -> List[Dict]:
|
|
379
481
|
"""Find call chains between two functions"""
|
|
380
482
|
with self.driver.session() as session:
|
|
381
|
-
result = session.run("""
|
|
483
|
+
result = session.run(f"""
|
|
382
484
|
MATCH path = shortestPath(
|
|
383
|
-
(start:Function {name: $start_function})-[:CALLS*1
|
|
485
|
+
(start:Function {{name: $start_function}})-[:CALLS*1..{max_depth}]->(end:Function {{name: $end_function}})
|
|
384
486
|
)
|
|
385
487
|
WITH path, nodes(path) as func_nodes, relationships(path) as call_rels
|
|
386
488
|
RETURN
|
|
387
|
-
[node in func_nodes | {
|
|
489
|
+
[node in func_nodes | {{
|
|
388
490
|
name: node.name,
|
|
389
491
|
file_path: node.file_path,
|
|
390
492
|
line_number: node.line_number,
|
|
391
493
|
is_dependency: node.is_dependency
|
|
392
|
-
}] as function_chain,
|
|
393
|
-
[rel in call_rels | {
|
|
494
|
+
}}] as function_chain,
|
|
495
|
+
[rel in call_rels | {{
|
|
394
496
|
call_line: rel.line_number,
|
|
395
497
|
args: rel.args,
|
|
396
498
|
full_call_name: rel.full_call_name
|
|
397
|
-
}] as call_details,
|
|
499
|
+
}}] as call_details,
|
|
398
500
|
length(path) as chain_length
|
|
399
501
|
ORDER BY chain_length ASC
|
|
400
502
|
LIMIT 10
|
|
401
|
-
""", start_function=start_function, end_function=end_function
|
|
503
|
+
""", start_function=start_function, end_function=end_function)
|
|
402
504
|
|
|
403
505
|
return [dict(record) for record in result]
|
|
404
506
|
|
|
@@ -494,6 +596,20 @@ class CodeFinder:
|
|
|
494
596
|
"summary": f"Found {len(results)} files that import '{target}'"
|
|
495
597
|
}
|
|
496
598
|
|
|
599
|
+
elif query_type == "find_functions_by_argument":
|
|
600
|
+
results = self.find_functions_by_argument(target, context)
|
|
601
|
+
return {
|
|
602
|
+
"query_type": "find_functions_by_argument", "target": target, "context": context, "results": results,
|
|
603
|
+
"summary": f"Found {len(results)} functions that take '{target}' as an argument"
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
elif query_type == "find_functions_by_decorator":
|
|
607
|
+
results = self.find_functions_by_decorator(target, context)
|
|
608
|
+
return {
|
|
609
|
+
"query_type": "find_functions_by_decorator", "target": target, "context": context, "results": results,
|
|
610
|
+
"summary": f"Found {len(results)} functions decorated with '{target}'"
|
|
611
|
+
}
|
|
612
|
+
|
|
497
613
|
elif query_type in ["who_modifies", "modifies", "mutations", "changes", "variable_usage"]:
|
|
498
614
|
results = self.who_modifies_variable(target)
|
|
499
615
|
return {
|
|
@@ -530,13 +646,29 @@ class CodeFinder:
|
|
|
530
646
|
"summary": f"Found the top {len(results)} most complex functions"
|
|
531
647
|
}
|
|
532
648
|
|
|
649
|
+
elif query_type == "find_all_callers":
|
|
650
|
+
results = self.find_all_callers(target, context)
|
|
651
|
+
return {
|
|
652
|
+
"query_type": "find_all_callers", "target": target, "context": context, "results": results,
|
|
653
|
+
"summary": f"Found {len(results)} direct and indirect callers of '{target}'"
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
elif query_type == "find_all_callees":
|
|
657
|
+
results = self.find_all_callees(target, context)
|
|
658
|
+
return {
|
|
659
|
+
"query_type": "find_all_callees", "target": target, "context": context, "results": results,
|
|
660
|
+
"summary": f"Found {len(results)} direct and indirect callees of '{target}'"
|
|
661
|
+
}
|
|
662
|
+
|
|
533
663
|
elif query_type in ["call_chain", "path", "chain"]:
|
|
534
664
|
if '->' in target:
|
|
535
665
|
start_func, end_func = target.split('->', 1)
|
|
536
|
-
|
|
666
|
+
# max_depth can be passed as context, default to 5 if not provided or invalid
|
|
667
|
+
max_depth = int(context) if context and context.isdigit() else 5
|
|
668
|
+
results = self.find_function_call_chain(start_func.strip(), end_func.strip(), max_depth)
|
|
537
669
|
return {
|
|
538
670
|
"query_type": "call_chain", "target": target, "results": results,
|
|
539
|
-
"summary": f"Found {len(results)} call chains from '{start_func.strip()}' to '{end_func.strip()}'"
|
|
671
|
+
"summary": f"Found {len(results)} call chains from '{start_func.strip()}' to '{end_func.strip()}' (max depth: {max_depth})"
|
|
540
672
|
}
|
|
541
673
|
else:
|
|
542
674
|
return {
|
{codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext/tools/graph_builder.py
RENAMED
|
@@ -244,7 +244,9 @@ class CodeVisitor(ast.NodeVisitor):
|
|
|
244
244
|
"args": [arg.arg for arg in node.args.args], "source": ast.unparse(node),
|
|
245
245
|
"context": self.current_context, "class_context": self.current_class,
|
|
246
246
|
"is_dependency": self.is_dependency, "docstring": ast.get_docstring(node),
|
|
247
|
-
"decorators": [ast.unparse(d) for d in node.decorator_list]
|
|
247
|
+
"decorators": [ast.unparse(d) for d in node.decorator_list],
|
|
248
|
+
"source_code": ast.unparse(node)} # Add source_code here
|
|
249
|
+
self.functions.append(func_data)
|
|
248
250
|
self.functions.append(func_data)
|
|
249
251
|
self._push_context(node.name, "function", node.lineno)
|
|
250
252
|
# This will trigger visit_Assign and visit_Call for nodes inside the function
|
|
@@ -343,7 +345,8 @@ class CodeVisitor(ast.NodeVisitor):
|
|
|
343
345
|
"""Visit import statements"""
|
|
344
346
|
for name in node.names:
|
|
345
347
|
import_data = {
|
|
346
|
-
"name": name.name,
|
|
348
|
+
"name": name.name.split('.')[0], # Store the top-level package name
|
|
349
|
+
"full_import_name": name.name, # Store the full import name
|
|
347
350
|
"line_number": node.lineno,
|
|
348
351
|
"alias": name.asname,
|
|
349
352
|
"context": self.current_context,
|
|
@@ -361,14 +364,20 @@ class CodeVisitor(ast.NodeVisitor):
|
|
|
361
364
|
|
|
362
365
|
for alias in node.names:
|
|
363
366
|
# If node.module is None, it's an import like `from . import name`
|
|
367
|
+
# Determine the base module name for the 'name' property
|
|
364
368
|
if node.module:
|
|
365
|
-
|
|
369
|
+
# For 'from .module import name', base_module is 'module'
|
|
370
|
+
# For 'from package.module import name', base_module is 'package'
|
|
371
|
+
base_module = node.module.split('.')[0]
|
|
372
|
+
full_import_name = f"{prefix}{node.module}.{alias.name}"
|
|
366
373
|
else:
|
|
367
|
-
#
|
|
368
|
-
|
|
374
|
+
# For 'from . import name', base_module is 'name'
|
|
375
|
+
base_module = alias.name
|
|
376
|
+
full_import_name = f"{prefix}{alias.name}"
|
|
369
377
|
|
|
370
378
|
import_data = {
|
|
371
|
-
"name":
|
|
379
|
+
"name": base_module, # Store the top-level module name
|
|
380
|
+
"full_import_name": full_import_name, # Store the full import path
|
|
372
381
|
"line_number": node.lineno,
|
|
373
382
|
"alias": alias.asname,
|
|
374
383
|
"context": self.current_context,
|
|
@@ -445,29 +454,29 @@ class CodeVisitor(ast.NodeVisitor):
|
|
|
445
454
|
inferred_obj_type = None
|
|
446
455
|
if isinstance(node.func, ast.Attribute):
|
|
447
456
|
base_obj_node = node.func.value
|
|
448
|
-
|
|
457
|
+
|
|
449
458
|
if isinstance(base_obj_node, ast.Name):
|
|
450
459
|
obj_name = base_obj_node.id
|
|
451
460
|
if obj_name == 'self':
|
|
452
461
|
# If the base is 'self', find the type of the attribute on the current class
|
|
453
462
|
inferred_obj_type = self.class_symbol_table.get(node.func.attr)
|
|
454
463
|
if not inferred_obj_type: # Fallback for method calls directly on self
|
|
455
|
-
|
|
464
|
+
inferred_obj_type = self.current_class
|
|
456
465
|
else:
|
|
457
466
|
inferred_obj_type = (self.local_symbol_table.get(obj_name) or
|
|
458
|
-
|
|
459
|
-
|
|
467
|
+
self.class_symbol_table.get(obj_name) or
|
|
468
|
+
self.module_symbol_table.get(obj_name))
|
|
460
469
|
# If it's not a variable, it might be a direct call on a Class name.
|
|
461
470
|
if not inferred_obj_type and obj_name in self.imports_map:
|
|
462
471
|
inferred_obj_type = obj_name
|
|
463
472
|
|
|
464
473
|
elif isinstance(base_obj_node, ast.Call):
|
|
465
474
|
inferred_obj_type = self._resolve_type_from_call(base_obj_node)
|
|
466
|
-
|
|
475
|
+
|
|
467
476
|
elif isinstance(base_obj_node, ast.Attribute): # e.g., self.job_manager
|
|
468
477
|
# This handles nested attributes
|
|
469
478
|
# The goal is to find the type of `self.job_manager`, which is 'JobManager'
|
|
470
|
-
|
|
479
|
+
|
|
471
480
|
# Resolve the base of the chain, e.g., get 'self' from 'self.job_manager'
|
|
472
481
|
base = base_obj_node
|
|
473
482
|
while isinstance(base, ast.Attribute):
|
|
@@ -477,11 +486,66 @@ class CodeVisitor(ast.NodeVisitor):
|
|
|
477
486
|
# In self.X.Y... The attribute we care about is the first one, X
|
|
478
487
|
attr_name = base_obj_node.attr
|
|
479
488
|
inferred_obj_type = self.class_symbol_table.get(attr_name)
|
|
480
|
-
|
|
489
|
+
|
|
481
490
|
elif isinstance(node.func, ast.Name):
|
|
482
491
|
inferred_obj_type = (self.local_symbol_table.get(call_name) or
|
|
483
|
-
|
|
484
|
-
|
|
492
|
+
self.class_symbol_table.get(call_name) or
|
|
493
|
+
self.module_symbol_table.get(call_name))
|
|
494
|
+
|
|
495
|
+
# there are no CALLS relationships originating from P2pkhAddress.to_address in the graph. This is the root cause of the find_all_callees tool reporting 0
|
|
496
|
+
# results.
|
|
497
|
+
|
|
498
|
+
# The problem is not with the find_all_callees query itself, but with the GraphBuilder's ability to correctly identify and create CALLS relationships for methods like
|
|
499
|
+
# P2pkhAddress.to_address.
|
|
500
|
+
|
|
501
|
+
# Specifically, the GraphBuilder._create_function_calls method is likely not correctly processing calls made within methods of a class, especially when those calls are to:
|
|
502
|
+
# 1. self.method(): Internal method calls.
|
|
503
|
+
# 2. Functions imported from other modules (e.g., h_to_b, get_network).
|
|
504
|
+
# 3. Functions from external libraries (e.g., hashlib.sha256, b58encode).
|
|
505
|
+
|
|
506
|
+
# The GraphBuilder.CodeVisitor.visit_Call method is responsible for identifying function calls. It needs to be improved to handle these cases.
|
|
507
|
+
|
|
508
|
+
# Plan:
|
|
509
|
+
|
|
510
|
+
# 1. Enhance `CodeVisitor.visit_Call` in `src/codegraphcontext/tools/graph_builder.py`:
|
|
511
|
+
# * Internal Method Calls (`self.method()`): When node.func is an ast.Attribute and node.func.value.id is self, the call_name should be node.func.attr, and the resolved_path should
|
|
512
|
+
# be the file_path of the current class.
|
|
513
|
+
# * Imported Functions: The _create_function_calls method already has some logic for resolving imported functions using imports_map. I need to ensure this logic is robust and
|
|
514
|
+
# correctly applied within visit_Call to set inferred_obj_type or resolved_path accurately.
|
|
515
|
+
# * External Library Functions: For now, we might not be able to fully resolve calls to external library functions unless those libraries are also indexed. However, we should at
|
|
516
|
+
# least capture the full_call_name and call_name for these.
|
|
517
|
+
# inferred_obj_type = None
|
|
518
|
+
# if isinstance(node.func, ast.Attribute):
|
|
519
|
+
# base_obj_node = node.func.value
|
|
520
|
+
|
|
521
|
+
# if isinstance(base_obj_node, ast.Name):
|
|
522
|
+
# obj_name = base_obj_node.id
|
|
523
|
+
# if obj_name == 'self':
|
|
524
|
+
# # If the base is 'self', the call is to a method of the current class
|
|
525
|
+
# inferred_obj_type = self.current_class
|
|
526
|
+
# else:
|
|
527
|
+
# # Try to resolve the type of the object from symbol tables
|
|
528
|
+
# inferred_obj_type = (self.local_symbol_table.get(obj_name) or
|
|
529
|
+
# self.class_symbol_table.get(obj_name) or
|
|
530
|
+
# self.module_symbol_table.get(obj_name))
|
|
531
|
+
# # If not found in symbol tables, check if it's a class name from imports
|
|
532
|
+
# if not inferred_obj_type and obj_name in self.imports_map:
|
|
533
|
+
# inferred_obj_type = obj_name
|
|
534
|
+
|
|
535
|
+
# elif isinstance(base_obj_node, ast.Call):
|
|
536
|
+
# inferred_obj_type = self._resolve_type_from_call(base_obj_node)
|
|
537
|
+
|
|
538
|
+
# elif isinstance(base_obj_node, ast.Attribute): # e.g., self.job_manager.method()
|
|
539
|
+
# # Recursively resolve the type of the base attribute
|
|
540
|
+
# inferred_obj_type = self._resolve_attribute_base_type(base_obj_node)
|
|
541
|
+
|
|
542
|
+
# elif isinstance(node.func, ast.Name):
|
|
543
|
+
# # If it's a direct function call, try to infer its type from symbol tables or imports
|
|
544
|
+
# inferred_obj_type = (self.local_symbol_table.get(call_name) or
|
|
545
|
+
# self.class_symbol_table.get(call_name) or
|
|
546
|
+
# self.module_symbol_table.get(call_name))
|
|
547
|
+
# if not inferred_obj_type and call_name in self.imports_map:
|
|
548
|
+
# inferred_obj_type = call_name
|
|
485
549
|
|
|
486
550
|
if call_name and call_name not in __builtins__:
|
|
487
551
|
call_data = {
|
|
@@ -621,12 +685,36 @@ class GraphBuilder:
|
|
|
621
685
|
MERGE (f)-[:CONTAINS]->(n)
|
|
622
686
|
"""
|
|
623
687
|
session.run(query, file_path=file_path_str, name=item['name'], line_number=item['line_number'], props=item)
|
|
688
|
+
|
|
689
|
+
# If it's a function, create parameter nodes and relationships and calculate complexity
|
|
690
|
+
if label == 'Function':
|
|
691
|
+
# Calculate cyclomatic complexity
|
|
692
|
+
try:
|
|
693
|
+
func_tree = ast.parse(item['source_code'])
|
|
694
|
+
complexity_visitor = CyclomaticComplexityVisitor()
|
|
695
|
+
complexity_visitor.visit(func_tree)
|
|
696
|
+
item['cyclomatic_complexity'] = complexity_visitor.complexity
|
|
697
|
+
except Exception as e:
|
|
698
|
+
logger.warning(f"Could not calculate cyclomatic complexity for {item['name']} in {file_path_str}: {e}")
|
|
699
|
+
item['cyclomatic_complexity'] = 1 # Default to 1 on error
|
|
700
|
+
|
|
701
|
+
for arg_name in item.get('args', []):
|
|
702
|
+
session.run("""
|
|
703
|
+
MATCH (fn:Function {name: $func_name, file_path: $file_path, line_number: $line_number})
|
|
704
|
+
MERGE (p:Parameter {name: $arg_name, file_path: $file_path, function_line_number: $line_number})
|
|
705
|
+
MERGE (fn)-[:HAS_PARAMETER]->(p)
|
|
706
|
+
""", func_name=item['name'], file_path=file_path_str, line_number=item['line_number'], arg_name=arg_name)
|
|
624
707
|
|
|
625
708
|
for imp in file_data['imports']:
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
709
|
+
set_clauses = ["m.alias = $alias"]
|
|
710
|
+
if 'full_import_name' in imp:
|
|
711
|
+
set_clauses.append("m.full_import_name = $full_import_name")
|
|
712
|
+
set_clause_str = ", ".join(set_clauses)
|
|
713
|
+
|
|
714
|
+
session.run(f"""
|
|
715
|
+
MATCH (f:File {{path: $file_path}})
|
|
716
|
+
MERGE (m:Module {{name: $name}})
|
|
717
|
+
SET {set_clause_str}
|
|
630
718
|
MERGE (f)-[:IMPORTS]->(m)
|
|
631
719
|
""", file_path=file_path_str, **imp)
|
|
632
720
|
|
|
@@ -663,9 +751,20 @@ class GraphBuilder:
|
|
|
663
751
|
|
|
664
752
|
for var in file_data.get('variables', []):
|
|
665
753
|
context = var.get('context')
|
|
754
|
+
class_context = var.get('class_context')
|
|
666
755
|
parent_line = var.get('parent_line')
|
|
667
756
|
|
|
668
|
-
if
|
|
757
|
+
if class_context:
|
|
758
|
+
session.run("""
|
|
759
|
+
MATCH (c:Class {name: $class_name, file_path: $file_path})
|
|
760
|
+
MATCH (v:Variable {name: $var_name, file_path: $file_path, line_number: $var_line})
|
|
761
|
+
MERGE (c)-[:CONTAINS]->(v)
|
|
762
|
+
""",
|
|
763
|
+
class_name=class_context,
|
|
764
|
+
file_path=file_path,
|
|
765
|
+
var_name=var['name'],
|
|
766
|
+
var_line=var['line_number'])
|
|
767
|
+
elif context and parent_line:
|
|
669
768
|
parent_label = "Function"
|
|
670
769
|
parent_node_data = None
|
|
671
770
|
|
|
@@ -749,7 +848,11 @@ class GraphBuilder:
|
|
|
749
848
|
|
|
750
849
|
# Fallback if no path could be resolved by any of the above rules
|
|
751
850
|
if not resolved_path:
|
|
752
|
-
|
|
851
|
+
# If the called name is in the imports map, use its path
|
|
852
|
+
if called_name in imports_map and imports_map[called_name]:
|
|
853
|
+
resolved_path = imports_map[called_name][0] # Take the first path for now
|
|
854
|
+
else:
|
|
855
|
+
resolved_path = caller_file_path
|
|
753
856
|
|
|
754
857
|
caller_context = call.get('context')
|
|
755
858
|
inferred_type = call.get('inferred_obj_type')
|
{codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext/tools/import_extractor.py
RENAMED
|
@@ -6,6 +6,18 @@ from pathlib import Path
|
|
|
6
6
|
from typing import Set
|
|
7
7
|
|
|
8
8
|
import stdlibs
|
|
9
|
+
import os
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
def debug_log(message):
|
|
15
|
+
"""Write debug message to a file"""
|
|
16
|
+
debug_file = os.path.expanduser("~/mcp_debug.log")
|
|
17
|
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
18
|
+
with open(debug_file, "a") as f:
|
|
19
|
+
f.write(f"[{timestamp}] {message}\n")
|
|
20
|
+
f.flush()
|
|
9
21
|
|
|
10
22
|
logger = logging.getLogger(__name__)
|
|
11
23
|
|
|
@@ -32,6 +44,7 @@ class ImportExtractor:
|
|
|
32
44
|
imports.add(node.module.split('.')[0]) # Get top-level package
|
|
33
45
|
except Exception as e:
|
|
34
46
|
logger.warning(f"Error parsing or reading {file_path}: {e}")
|
|
47
|
+
debug_log(f"Raw imports extracted from {file_path}: {imports}") # Add this line
|
|
35
48
|
return imports
|
|
36
49
|
|
|
37
50
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codegraphcontext
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8
|
|
4
4
|
Summary: An MCP server that indexes local code into a graph database to provide context to AI assistants.
|
|
5
5
|
Author-email: Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -53,9 +53,13 @@ Dynamic: license-file
|
|
|
53
53
|
# CodeGraphContext
|
|
54
54
|
[](https://github.com/Shashankss1205/CodeGraphContext/actions/workflows/test.yml)
|
|
55
55
|
|
|
56
|
-
|
|
57
56
|
An MCP server that indexes local code into a graph database to provide context to AI assistants.
|
|
58
57
|
|
|
58
|
+
## Project Details
|
|
59
|
+
- **Version:** 0.1.8
|
|
60
|
+
- **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
|
|
61
|
+
- **License:** MIT License (See [LICENSE](LICENSE) for details)
|
|
62
|
+
|
|
59
63
|
## Features
|
|
60
64
|
|
|
61
65
|
- **Code Indexing:** Analyzes Python code and builds a knowledge graph of its components.
|
|
@@ -63,12 +67,23 @@ An MCP server that indexes local code into a graph database to provide context t
|
|
|
63
67
|
- **Live Updates:** Watches local files for changes and automatically updates the graph.
|
|
64
68
|
- **Interactive Setup:** A user-friendly command-line wizard for easy setup.
|
|
65
69
|
|
|
70
|
+
## Dependencies
|
|
71
|
+
|
|
72
|
+
- `neo4j>=5.15.0`
|
|
73
|
+
- `watchdog>=3.0.0`
|
|
74
|
+
- `requests>=2.31.0`
|
|
75
|
+
- `stdlibs>=2023.11.18`
|
|
76
|
+
- `typer[all]>=0.9.0`
|
|
77
|
+
- `rich>=13.7.0`
|
|
78
|
+
- `inquirerpy>=0.3.4`
|
|
79
|
+
- `python-dotenv>=1.0.0`
|
|
80
|
+
|
|
66
81
|
## Getting Started
|
|
67
82
|
|
|
68
83
|
1. **Install:** `pip install codegraphcontext`
|
|
69
84
|
2. **Setup:** `cgc setup`
|
|
70
85
|
3. **Start:** `cgc start`
|
|
71
|
-
4. **Index Code:** `cgc tool add-code-to-graph '{"path": "/path/to/your/project"}'`
|
|
86
|
+
4. **Index Code:** `cgc tool add-code-to-graph '{"path": "/path/to/your/project"}'` (Under active development)
|
|
72
87
|
|
|
73
88
|
## MCP Client Configuration
|
|
74
89
|
|
|
@@ -98,7 +113,11 @@ Add the following to your MCP client's configuration:
|
|
|
98
113
|
"analyze_code_relationships",
|
|
99
114
|
"watch_directory",
|
|
100
115
|
"find_dead_code",
|
|
101
|
-
"execute_cypher_query"
|
|
116
|
+
"execute_cypher_query",
|
|
117
|
+
"calculate_cyclomatic_complexity",
|
|
118
|
+
"find_most_complex_functions",
|
|
119
|
+
"list_indexed_repositories",
|
|
120
|
+
"delete_repository"
|
|
102
121
|
],
|
|
103
122
|
"disabled": false
|
|
104
123
|
},
|
|
@@ -149,5 +168,22 @@ Once the server is running, you can interact with it through your AI assistant u
|
|
|
149
168
|
- "Which files import the `requests` library?"
|
|
150
169
|
- "Find all implementations of the `render` method."
|
|
151
170
|
|
|
171
|
+
- **Advanced Call Chain and Dependency Tracking (Spanning Hundreds of Files):**
|
|
172
|
+
The CodeGraphContext excels at tracing complex execution flows and dependencies across vast codebases. Leveraging the power of graph databases, it can identify direct and indirect callers and callees, even when a function is called through multiple layers of abstraction or across numerous files. This is invaluable for:
|
|
173
|
+
- **Impact Analysis:** Understand the full ripple effect of a change to a core function.
|
|
174
|
+
- **Debugging:** Trace the path of execution from an entry point to a specific bug.
|
|
175
|
+
- **Code Comprehension:** Grasp how different parts of a large system interact.
|
|
176
|
+
|
|
177
|
+
- "Show me the full call chain from the `main` function to `process_data`."
|
|
178
|
+
- "Find all functions that directly or indirectly call `validate_input`."
|
|
179
|
+
- "What are all the functions that `initialize_system` eventually calls?"
|
|
180
|
+
- "Trace the dependencies of the `DatabaseManager` module."
|
|
181
|
+
|
|
152
182
|
- **Code Quality and Maintenance:**
|
|
153
183
|
- "Is there any dead or unused code in this project?"
|
|
184
|
+
- "Calculate the cyclomatic complexity of the `process_data` function in `src/utils.py`."
|
|
185
|
+
- "Find the 5 most complex functions in the codebase."
|
|
186
|
+
|
|
187
|
+
- **Repository Management:**
|
|
188
|
+
- "List all currently indexed repositories."
|
|
189
|
+
- "Delete the indexed repository at `/path/to/old-project`."
|
|
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
|
{codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext.egg-info/requires.txt
RENAMED
|
File without changes
|
{codegraphcontext-0.1.7 → codegraphcontext-0.1.8}/src/codegraphcontext.egg-info/top_level.txt
RENAMED
|
File without changes
|