codegraphcontext 0.5.5__py3-none-any.whl → 0.5.7__py3-none-any.whl
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/cli/main.py +12 -6
- codegraphcontext/tools/advanced_language_query_tool.py +6 -4
- codegraphcontext/tools/code_finder.py +46 -15
- codegraphcontext/tools/handlers/analysis_handlers.py +4 -3
- codegraphcontext/tools/handlers/indexing_handlers.py +19 -0
- codegraphcontext/tools/handlers/management_handlers.py +3 -3
- codegraphcontext/tools/indexing/scip_pipeline.py +8 -0
- codegraphcontext/tools/languages/c.py +34 -7
- codegraphcontext/tools/languages/cpp.py +62 -0
- codegraphcontext/tools/languages/csharp.py +83 -1
- codegraphcontext/tools/languages/javascript.py +68 -4
- codegraphcontext/tools/languages/ruby.py +56 -20
- codegraphcontext/tools/package_resolver.py +9 -1
- codegraphcontext/tools/query_tool_languages/java_toolkit.py +17 -8
- codegraphcontext/tools/report_generator.py +14 -1
- codegraphcontext/utils/visualize_graph.py +23 -3
- codegraphcontext/viz/server.py +51 -19
- {codegraphcontext-0.5.5.dist-info → codegraphcontext-0.5.7.dist-info}/METADATA +3 -3
- {codegraphcontext-0.5.5.dist-info → codegraphcontext-0.5.7.dist-info}/RECORD +23 -23
- {codegraphcontext-0.5.5.dist-info → codegraphcontext-0.5.7.dist-info}/WHEEL +0 -0
- {codegraphcontext-0.5.5.dist-info → codegraphcontext-0.5.7.dist-info}/entry_points.txt +0 -0
- {codegraphcontext-0.5.5.dist-info → codegraphcontext-0.5.7.dist-info}/licenses/LICENSE +0 -0
- {codegraphcontext-0.5.5.dist-info → codegraphcontext-0.5.7.dist-info}/top_level.txt +0 -0
codegraphcontext/cli/main.py
CHANGED
|
@@ -2830,15 +2830,18 @@ def analyze_complexity(
|
|
|
2830
2830
|
|
|
2831
2831
|
@analyze_app.command("dead-code")
|
|
2832
2832
|
def analyze_dead_code(
|
|
2833
|
-
path: Optional[str] = typer.Argument(None, help="
|
|
2833
|
+
path: Optional[str] = typer.Argument(None, help="Repository path to scope the analysis to"),
|
|
2834
2834
|
exclude_decorators: Optional[str] = typer.Option(None, "--exclude", "-e", help="Comma-separated decorators to exclude"),
|
|
2835
2835
|
context: Optional[str] = typer.Option(None, "--context", "-c", help="Specific context to use"),
|
|
2836
2836
|
):
|
|
2837
2837
|
"""
|
|
2838
|
-
Find potentially unused functions
|
|
2839
|
-
|
|
2838
|
+
Find potentially unused functions.
|
|
2839
|
+
|
|
2840
|
+
Without a path the whole database is scanned, which on a shared graph
|
|
2841
|
+
reports dead code from every indexed repository. Pass a path to scope it.
|
|
2842
|
+
|
|
2840
2843
|
Example:
|
|
2841
|
-
cgc analyze dead-code
|
|
2844
|
+
cgc analyze dead-code .
|
|
2842
2845
|
cgc analyze dead-code --exclude route,task,api
|
|
2843
2846
|
"""
|
|
2844
2847
|
_load_credentials()
|
|
@@ -2849,8 +2852,11 @@ def analyze_dead_code(
|
|
|
2849
2852
|
|
|
2850
2853
|
try:
|
|
2851
2854
|
exclude_list = exclude_decorators.split(',') if exclude_decorators else []
|
|
2852
|
-
|
|
2853
|
-
|
|
2855
|
+
# `path` was accepted and then dropped, so running inside one repository
|
|
2856
|
+
# still reported dead code from every repository in the database.
|
|
2857
|
+
repo_path = Path(path).resolve().as_posix() if path else None
|
|
2858
|
+
results = code_finder.find_dead_code(exclude_list, repo_path=repo_path)
|
|
2859
|
+
|
|
2854
2860
|
unused_funcs = results.get('potentially_unused_functions', [])
|
|
2855
2861
|
|
|
2856
2862
|
if not unused_funcs:
|
|
@@ -82,10 +82,12 @@ class Advanced_language_query:
|
|
|
82
82
|
|
|
83
83
|
if language not in self.TOOLKITS:
|
|
84
84
|
raise ValueError(f"Unsupported language: {language}")
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
try:
|
|
86
|
+
self.toolkit = self.TOOLKITS[language]()
|
|
87
|
+
# Getting the language query
|
|
88
|
+
cypher_query = self.toolkit.get_cypher_query(label)
|
|
89
|
+
except NotImplementedError as exc:
|
|
90
|
+
return {"error": str(exc), "language": language, "query": query}
|
|
89
91
|
try:
|
|
90
92
|
debug_log(f"Executing Cypher query: {cypher_query}")
|
|
91
93
|
with self.db_manager.get_driver().session() as session:
|
|
@@ -14,6 +14,22 @@ logger = logging.getLogger(__name__)
|
|
|
14
14
|
|
|
15
15
|
_MAX_TRAVERSAL_DEPTH = 20
|
|
16
16
|
|
|
17
|
+
# Names that are entry points rather than dead code. Matched on the *whole*
|
|
18
|
+
# name, lowercased. This used to be a substring test (`name CONTAINS 'main'`,
|
|
19
|
+
# `toLower(name) CONTAINS 'entry'`), which silently excluded any function whose
|
|
20
|
+
# name merely contained one of them — `domain_check`, `remainder`,
|
|
21
|
+
# `maintain_index`, `entry_count` — so genuinely unused code was never
|
|
22
|
+
# reported and there was no way to tell.
|
|
23
|
+
_ENTRY_POINT_NAMES = (
|
|
24
|
+
"main",
|
|
25
|
+
"setup",
|
|
26
|
+
"run",
|
|
27
|
+
"application",
|
|
28
|
+
"entry",
|
|
29
|
+
"entrypoint",
|
|
30
|
+
)
|
|
31
|
+
_ENTRY_POINT_NAMES_CYPHER = "[" + ", ".join(f"'{n}'" for n in _ENTRY_POINT_NAMES) + "]"
|
|
32
|
+
|
|
17
33
|
|
|
18
34
|
def _sanitize_depth(depth, default: int = 3) -> int:
|
|
19
35
|
"""Coerce and clamp a traversal depth before interpolating it into Cypher.
|
|
@@ -809,19 +825,16 @@ class CodeFinder:
|
|
|
809
825
|
query = f"""
|
|
810
826
|
MATCH (func:Function)
|
|
811
827
|
WHERE func.is_dependency = false {repo_filter} {func_ignore}
|
|
812
|
-
AND NOT func.name IN
|
|
828
|
+
AND NOT toLower(func.name) IN {_ENTRY_POINT_NAMES_CYPHER}
|
|
813
829
|
AND func.name <> '<module>'
|
|
814
830
|
AND NOT (func.name STARTS WITH '__' AND func.name ENDS WITH '__')
|
|
815
831
|
AND NOT func.name STARTS WITH '_test'
|
|
816
832
|
AND NOT func.name STARTS WITH 'test_'
|
|
817
|
-
AND NOT func.name CONTAINS 'main'
|
|
818
|
-
AND NOT toLower(func.name) CONTAINS 'application'
|
|
819
|
-
AND NOT toLower(func.name) CONTAINS 'entry'
|
|
820
|
-
AND NOT toLower(func.name) CONTAINS 'entrypoint'
|
|
821
833
|
{decorator_filter}
|
|
822
834
|
WITH func
|
|
823
|
-
OPTIONAL MATCH (caller
|
|
824
|
-
WHERE caller.is_dependency = false
|
|
835
|
+
OPTIONAL MATCH (caller)-[:CALLS|HEURISTIC_CALLS]->(func)
|
|
836
|
+
WHERE (caller.is_dependency IS NULL OR caller.is_dependency = false)
|
|
837
|
+
{caller_ignore}
|
|
825
838
|
WITH func, count(caller) as caller_count
|
|
826
839
|
WHERE caller_count = 0
|
|
827
840
|
OPTIONAL MATCH (file:File)-[:CONTAINS]->(func)
|
|
@@ -1211,16 +1224,31 @@ class CodeFinder:
|
|
|
1211
1224
|
try:
|
|
1212
1225
|
if query_type == "find_callers":
|
|
1213
1226
|
results = self.who_calls_function(target, context, repo_path=repo_path)
|
|
1214
|
-
#
|
|
1215
|
-
#
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1227
|
+
# target_file_path is only constant when `context` pinned a single
|
|
1228
|
+
# definition. Without it, who_calls_function matches
|
|
1229
|
+
# Function {name: $function_name} across every file, so rows
|
|
1230
|
+
# legitimately carry different targets — hoisting the first row's
|
|
1231
|
+
# value and stripping the rest reported every caller as calling
|
|
1232
|
+
# whichever definition happened to sort first.
|
|
1233
|
+
#
|
|
1234
|
+
# Hoist only when the rows agree; otherwise leave the field on each
|
|
1235
|
+
# row so the ambiguity is visible to the caller.
|
|
1236
|
+
target_paths = {r.get("target_file_path") for r in results}
|
|
1237
|
+
target_file_path = target_paths.pop() if len(target_paths) == 1 else None
|
|
1238
|
+
if target_file_path is not None:
|
|
1239
|
+
for r in results:
|
|
1240
|
+
r.pop("target_file_path", None)
|
|
1241
|
+
envelope = {
|
|
1220
1242
|
"query_type": "find_callers", "target": target, "context": context,
|
|
1221
1243
|
"target_file_path": target_file_path, "results": results,
|
|
1222
1244
|
"summary": f"Found {len(results)} functions that call '{target}'"
|
|
1223
1245
|
}
|
|
1246
|
+
if target_file_path is None and len(target_paths) > 1:
|
|
1247
|
+
envelope["note"] = (
|
|
1248
|
+
f"'{target}' is defined in {len(target_paths)} files; each result "
|
|
1249
|
+
"carries its own target_file_path. Pass `context` to disambiguate."
|
|
1250
|
+
)
|
|
1251
|
+
return envelope
|
|
1224
1252
|
|
|
1225
1253
|
elif query_type == "find_callees":
|
|
1226
1254
|
results = self.what_does_function_call(target, context, repo_path=repo_path)
|
|
@@ -1272,7 +1300,10 @@ class CodeFinder:
|
|
|
1272
1300
|
}
|
|
1273
1301
|
|
|
1274
1302
|
elif query_type in ["dead_code", "unused", "unreachable"]:
|
|
1275
|
-
|
|
1303
|
+
# graph_name must be forwarded: find_dead_code defaults it to None and
|
|
1304
|
+
# re-assigns the shared self._active_graph, so omitting it here
|
|
1305
|
+
# silently redirected the query to the default graph.
|
|
1306
|
+
results = self.find_dead_code(repo_path=repo_path, graph_name=graph_name)
|
|
1276
1307
|
return {
|
|
1277
1308
|
"query_type": "dead_code", "results": results,
|
|
1278
1309
|
"summary": f"Found {len(results['potentially_unused_functions'])} potentially unused functions"
|
|
@@ -1281,7 +1312,7 @@ class CodeFinder:
|
|
|
1281
1312
|
elif query_type == "find_complexity":
|
|
1282
1313
|
limit_val = context if context else target
|
|
1283
1314
|
limit = int(limit_val) if limit_val and str(limit_val).isdigit() else 10
|
|
1284
|
-
results = self.find_most_complex_functions(limit, repo_path=repo_path)
|
|
1315
|
+
results = self.find_most_complex_functions(limit, repo_path=repo_path, graph_name=graph_name)
|
|
1285
1316
|
return {
|
|
1286
1317
|
"query_type": "find_complexity", "limit": limit, "results": results,
|
|
1287
1318
|
"summary": f"Found the top {len(results)} most complex functions"
|
|
@@ -236,8 +236,8 @@ def find_java_spring_beans(code_finder: CodeFinder, **args) -> Dict[str, Any]:
|
|
|
236
236
|
query = f"""
|
|
237
237
|
MATCH (c:Class)
|
|
238
238
|
WHERE {where_clause}
|
|
239
|
-
OPTIONAL MATCH ()-[:INJECTS]->(c)
|
|
240
|
-
WITH c, count(
|
|
239
|
+
OPTIONAL MATCH ()-[inj:INJECTS]->(c)
|
|
240
|
+
WITH c, count(inj) AS injection_count
|
|
241
241
|
RETURN c.name AS name, c.spring_stereotype AS stereotype,
|
|
242
242
|
c.path AS file, c.line_number AS line_number, injection_count
|
|
243
243
|
ORDER BY stereotype, name
|
|
@@ -296,7 +296,8 @@ def find_datasource_nodes(code_finder: CodeFinder, **args) -> Dict[str, Any]:
|
|
|
296
296
|
{kp_where}
|
|
297
297
|
OPTIONAL MATCH (kp:RedisKeyPattern)-[:STORED_IN]->(d)
|
|
298
298
|
RETURN d.name AS datasource,
|
|
299
|
-
collect({{pattern:
|
|
299
|
+
[x IN collect(kp) | {{pattern: x.pattern, key_type: x.key_type,
|
|
300
|
+
count: x.count}}] AS key_patterns
|
|
300
301
|
"""
|
|
301
302
|
|
|
302
303
|
try:
|
|
@@ -19,6 +19,25 @@ def add_code_to_graph(graph_builder, job_manager, loop, list_repos_func, **args)
|
|
|
19
19
|
|
|
20
20
|
if not path:
|
|
21
21
|
return {"error": "Path is a required argument (repo_path)."}
|
|
22
|
+
|
|
23
|
+
# `graph_name` is advertised in this tool's schema but indexing cannot yet
|
|
24
|
+
# honour it: GraphBuilder binds its GraphWriter to the default driver at
|
|
25
|
+
# construction, so a named graph would need a per-job writer threaded
|
|
26
|
+
# through the whole pipeline. Refuse explicitly rather than write to the
|
|
27
|
+
# default graph and report success — an agent that believed the repository
|
|
28
|
+
# landed in a named graph would find nothing there later, with no error to
|
|
29
|
+
# explain it.
|
|
30
|
+
graph_name = args.get("graph_name")
|
|
31
|
+
if graph_name:
|
|
32
|
+
return {
|
|
33
|
+
"error": (
|
|
34
|
+
f"Indexing into a named graph is not supported yet, so "
|
|
35
|
+
f"graph_name={graph_name!r} cannot be honoured. Omit it to index "
|
|
36
|
+
"into the default graph, or select the graph via the CLI context "
|
|
37
|
+
"(`cgc context create` / `cgc index --context`)."
|
|
38
|
+
),
|
|
39
|
+
"unsupported_argument": "graph_name",
|
|
40
|
+
}
|
|
22
41
|
|
|
23
42
|
try:
|
|
24
43
|
path_obj = Path(path).resolve()
|
|
@@ -354,15 +354,15 @@ def get_repository_stats(code_finder: CodeFinder, **args) -> Dict[str, Any]:
|
|
|
354
354
|
}
|
|
355
355
|
|
|
356
356
|
# 1. Files
|
|
357
|
-
file_query = "MATCH (r:Repository {path: $path})-[:CONTAINS*]->(f:File) RETURN count(f) as c"
|
|
357
|
+
file_query = "MATCH (r:Repository {path: $path})-[:CONTAINS*]->(f:File) RETURN count(DISTINCT f) as c"
|
|
358
358
|
file_count = session.run(file_query, path=repo_path_obj).single()["c"]
|
|
359
359
|
|
|
360
360
|
# 2. Functions
|
|
361
|
-
func_query = "MATCH (r:Repository {path: $path})-[:CONTAINS*]->(func:Function) RETURN count(func) as c"
|
|
361
|
+
func_query = "MATCH (r:Repository {path: $path})-[:CONTAINS*]->(func:Function) RETURN count(DISTINCT func) as c"
|
|
362
362
|
func_count = session.run(func_query, path=repo_path_obj).single()["c"]
|
|
363
363
|
|
|
364
364
|
# 3. Classes
|
|
365
|
-
class_query = "MATCH (r:Repository {path: $path})-[:CONTAINS*]->(cls:Class) RETURN count(cls) as c"
|
|
365
|
+
class_query = "MATCH (r:Repository {path: $path})-[:CONTAINS*]->(cls:Class) RETURN count(DISTINCT cls) as c"
|
|
366
366
|
class_count = session.run(class_query, path=repo_path_obj).single()["c"]
|
|
367
367
|
|
|
368
368
|
# 4. Modules (imported)
|
|
@@ -191,6 +191,14 @@ async def run_scip_index_async(
|
|
|
191
191
|
cgcignore_path=cgcignore_path,
|
|
192
192
|
supported_extensions=set(parsers_keys),
|
|
193
193
|
)
|
|
194
|
+
# Recompute total_files now that we know the full set (SCIP-covered +
|
|
195
|
+
# supplementary), so progress_percentage stays <= 100% throughout.
|
|
196
|
+
if job_id:
|
|
197
|
+
new_total = len(files_data) + sum(
|
|
198
|
+
1 for f in supplementary_files
|
|
199
|
+
if str(f.resolve()) not in scip_abs_paths
|
|
200
|
+
)
|
|
201
|
+
job_manager.update_job(job_id, total_files=new_total)
|
|
194
202
|
minimal_nodes = 0
|
|
195
203
|
for repo_file in supplementary_files:
|
|
196
204
|
abs_str = str(repo_file.resolve())
|
|
@@ -6,20 +6,37 @@ from codegraphcontext.utils.debug_log import debug_log, info_logger, error_logge
|
|
|
6
6
|
from codegraphcontext.utils.tree_sitter_manager import execute_query
|
|
7
7
|
|
|
8
8
|
C_QUERIES = {
|
|
9
|
+
# A pointer return type wraps the function declarator from the OUTSIDE:
|
|
10
|
+
# `char* f()` is
|
|
11
|
+
# function_definition > pointer_declarator > function_declarator > identifier
|
|
12
|
+
# The second alternative below used to nest these the other way round, which
|
|
13
|
+
# matches nothing, so no function returning `T*` or `T**` was extracted —
|
|
14
|
+
# and because pre_scan_c shares this query they were invisible as call
|
|
15
|
+
# targets too.
|
|
9
16
|
"functions": """
|
|
10
17
|
(function_definition
|
|
11
18
|
declarator: (function_declarator
|
|
12
19
|
declarator: (identifier) @name
|
|
13
20
|
)
|
|
14
21
|
) @function_node
|
|
15
|
-
|
|
22
|
+
|
|
16
23
|
(function_definition
|
|
17
|
-
declarator: (
|
|
18
|
-
declarator: (
|
|
24
|
+
declarator: (pointer_declarator
|
|
25
|
+
declarator: (function_declarator
|
|
19
26
|
declarator: (identifier) @name
|
|
20
27
|
)
|
|
21
28
|
)
|
|
22
29
|
) @function_node
|
|
30
|
+
|
|
31
|
+
(function_definition
|
|
32
|
+
declarator: (pointer_declarator
|
|
33
|
+
declarator: (pointer_declarator
|
|
34
|
+
declarator: (function_declarator
|
|
35
|
+
declarator: (identifier) @name
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
) @function_node
|
|
23
40
|
""",
|
|
24
41
|
"structs": """
|
|
25
42
|
(struct_specifier
|
|
@@ -762,15 +779,25 @@ def pre_scan_c(files: list[Path], parser_wrapper) -> dict:
|
|
|
762
779
|
declarator: (identifier) @name
|
|
763
780
|
)
|
|
764
781
|
)
|
|
765
|
-
|
|
782
|
+
|
|
766
783
|
(function_definition
|
|
767
|
-
declarator: (
|
|
768
|
-
declarator: (
|
|
784
|
+
declarator: (pointer_declarator
|
|
785
|
+
declarator: (function_declarator
|
|
769
786
|
declarator: (identifier) @name
|
|
770
787
|
)
|
|
771
788
|
)
|
|
772
789
|
)
|
|
773
|
-
|
|
790
|
+
|
|
791
|
+
(function_definition
|
|
792
|
+
declarator: (pointer_declarator
|
|
793
|
+
declarator: (pointer_declarator
|
|
794
|
+
declarator: (function_declarator
|
|
795
|
+
declarator: (identifier) @name
|
|
796
|
+
)
|
|
797
|
+
)
|
|
798
|
+
)
|
|
799
|
+
)
|
|
800
|
+
|
|
774
801
|
(struct_specifier
|
|
775
802
|
name: (type_identifier) @name
|
|
776
803
|
)
|
|
@@ -6,6 +6,13 @@ from codegraphcontext.utils.debug_log import debug_log, info_logger, error_logge
|
|
|
6
6
|
from codegraphcontext.utils.tree_sitter_manager import execute_query
|
|
7
7
|
|
|
8
8
|
CPP_QUERIES = {
|
|
9
|
+
# A pointer or reference return type wraps the function declarator from the
|
|
10
|
+
# OUTSIDE: `char* f()` is
|
|
11
|
+
# function_definition > pointer_declarator > function_declarator > identifier
|
|
12
|
+
# not function_declarator > pointer_declarator. Matching only the unwrapped
|
|
13
|
+
# shape meant no function returning `T*`, `T&` or `T**` was ever extracted —
|
|
14
|
+
# accessors, factories and `operator[]` are pervasive in C++, and because
|
|
15
|
+
# pre_scan_cpp shares this query they were invisible as call targets too.
|
|
9
16
|
"functions": """
|
|
10
17
|
(function_definition
|
|
11
18
|
declarator: (function_declarator
|
|
@@ -16,6 +23,44 @@ CPP_QUERIES = {
|
|
|
16
23
|
]
|
|
17
24
|
)
|
|
18
25
|
) @function_node
|
|
26
|
+
|
|
27
|
+
(function_definition
|
|
28
|
+
declarator: (pointer_declarator
|
|
29
|
+
declarator: (function_declarator
|
|
30
|
+
declarator: [
|
|
31
|
+
(identifier) @name
|
|
32
|
+
(field_identifier) @name
|
|
33
|
+
(qualified_identifier) @qualified_name
|
|
34
|
+
]
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
) @function_node
|
|
38
|
+
|
|
39
|
+
(function_definition
|
|
40
|
+
declarator: (pointer_declarator
|
|
41
|
+
declarator: (pointer_declarator
|
|
42
|
+
declarator: (function_declarator
|
|
43
|
+
declarator: [
|
|
44
|
+
(identifier) @name
|
|
45
|
+
(field_identifier) @name
|
|
46
|
+
(qualified_identifier) @qualified_name
|
|
47
|
+
]
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
) @function_node
|
|
52
|
+
|
|
53
|
+
(function_definition
|
|
54
|
+
declarator: (reference_declarator
|
|
55
|
+
(function_declarator
|
|
56
|
+
declarator: [
|
|
57
|
+
(identifier) @name
|
|
58
|
+
(field_identifier) @name
|
|
59
|
+
(qualified_identifier) @qualified_name
|
|
60
|
+
]
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
) @function_node
|
|
19
64
|
""",
|
|
20
65
|
"classes": """
|
|
21
66
|
(class_specifier
|
|
@@ -651,6 +696,23 @@ def pre_scan_cpp(files: list[Path], parser_wrapper) -> dict:
|
|
|
651
696
|
(type_definition declarator: (type_identifier) @name)
|
|
652
697
|
(function_definition declarator: (function_declarator declarator: (identifier) @name))
|
|
653
698
|
(function_definition declarator: (function_declarator declarator: (qualified_identifier) @qualified_name))
|
|
699
|
+
|
|
700
|
+
; A pointer/reference return type wraps the function declarator from the
|
|
701
|
+
; outside, so these forms need their own alternatives — without them a
|
|
702
|
+
; `T* f()` was not registered here either, and so was unresolvable as a
|
|
703
|
+
; call target even once it became a node.
|
|
704
|
+
(function_definition declarator: (pointer_declarator
|
|
705
|
+
declarator: (function_declarator declarator: (identifier) @name)))
|
|
706
|
+
(function_definition declarator: (pointer_declarator
|
|
707
|
+
declarator: (function_declarator declarator: (qualified_identifier) @qualified_name)))
|
|
708
|
+
(function_definition declarator: (pointer_declarator declarator: (pointer_declarator
|
|
709
|
+
declarator: (function_declarator declarator: (identifier) @name))))
|
|
710
|
+
(function_definition declarator: (pointer_declarator declarator: (pointer_declarator
|
|
711
|
+
declarator: (function_declarator declarator: (qualified_identifier) @qualified_name))))
|
|
712
|
+
(function_definition declarator: (reference_declarator
|
|
713
|
+
(function_declarator declarator: (identifier) @name)))
|
|
714
|
+
(function_definition declarator: (reference_declarator
|
|
715
|
+
(function_declarator declarator: (qualified_identifier) @qualified_name)))
|
|
654
716
|
"""
|
|
655
717
|
|
|
656
718
|
|
|
@@ -6,6 +6,21 @@ from codegraphcontext.utils.debug_log import debug_log, info_logger, error_logge
|
|
|
6
6
|
from codegraphcontext.utils.tree_sitter_manager import execute_query
|
|
7
7
|
|
|
8
8
|
CSHARP_QUERIES = {
|
|
9
|
+
# Fields and locals share a shape: `variable_declaration` carries the type
|
|
10
|
+
# and holds one `variable_declarator` per name. There was no `variables`
|
|
11
|
+
# query at all and the key was hardcoded to [], so C# produced no Variable
|
|
12
|
+
# nodes — and `resolution/calls.py` lost the local-variable declarations it
|
|
13
|
+
# uses to infer a receiver's type, so `var s = new UserService(); s.Save();`
|
|
14
|
+
# could not resolve Save to UserService.Save.
|
|
15
|
+
"variables": """
|
|
16
|
+
(field_declaration
|
|
17
|
+
(variable_declaration) @decl
|
|
18
|
+
) @field_node
|
|
19
|
+
|
|
20
|
+
(local_declaration_statement
|
|
21
|
+
(variable_declaration) @decl
|
|
22
|
+
) @local_node
|
|
23
|
+
""",
|
|
9
24
|
"functions": """
|
|
10
25
|
(method_declaration
|
|
11
26
|
name: (identifier) @name
|
|
@@ -123,6 +138,7 @@ class CSharpTreeSitterParser:
|
|
|
123
138
|
parsed_enums = []
|
|
124
139
|
parsed_records = []
|
|
125
140
|
parsed_properties = []
|
|
141
|
+
parsed_variables = []
|
|
126
142
|
parsed_imports = []
|
|
127
143
|
parsed_calls = []
|
|
128
144
|
|
|
@@ -143,6 +159,8 @@ class CSharpTreeSitterParser:
|
|
|
143
159
|
parsed_records = self._parse_type_declarations(captures, source_code, path, "Record")
|
|
144
160
|
elif capture_name == "properties":
|
|
145
161
|
parsed_properties = self._parse_properties(captures, source_code, path, tree.root_node)
|
|
162
|
+
elif capture_name == "variables":
|
|
163
|
+
parsed_variables = self._parse_variables(captures)
|
|
146
164
|
elif capture_name == "imports":
|
|
147
165
|
parsed_imports = self._parse_imports(captures, source_code)
|
|
148
166
|
elif capture_name == "calls":
|
|
@@ -157,7 +175,7 @@ class CSharpTreeSitterParser:
|
|
|
157
175
|
"enums": parsed_enums,
|
|
158
176
|
"records": parsed_records,
|
|
159
177
|
"properties": parsed_properties,
|
|
160
|
-
"variables":
|
|
178
|
+
"variables": parsed_variables,
|
|
161
179
|
"imports": parsed_imports,
|
|
162
180
|
"function_calls": parsed_calls,
|
|
163
181
|
"is_dependency": is_dependency,
|
|
@@ -359,6 +377,70 @@ class CSharpTreeSitterParser:
|
|
|
359
377
|
|
|
360
378
|
return imports
|
|
361
379
|
|
|
380
|
+
def _parse_variables(self, captures: list) -> list:
|
|
381
|
+
"""Fields and locals, from `variable_declaration` nodes.
|
|
382
|
+
|
|
383
|
+
Both shapes nest identically — `variable_declaration` carries the type
|
|
384
|
+
and holds one `variable_declarator` per declared name, so
|
|
385
|
+
`int a = 1, b = 2;` yields two rows sharing a type.
|
|
386
|
+
"""
|
|
387
|
+
variables = []
|
|
388
|
+
seen = set()
|
|
389
|
+
for node, capture_name in captures:
|
|
390
|
+
if capture_name != "decl":
|
|
391
|
+
continue
|
|
392
|
+
type_node = node.child_by_field_name("type")
|
|
393
|
+
var_type = self._get_node_text(type_node) if type_node is not None else None
|
|
394
|
+
|
|
395
|
+
for declarator in node.children:
|
|
396
|
+
if declarator.type != "variable_declarator":
|
|
397
|
+
continue
|
|
398
|
+
name_node = declarator.child_by_field_name("name")
|
|
399
|
+
if name_node is None:
|
|
400
|
+
name_node = next(
|
|
401
|
+
(c for c in declarator.children if c.type == "identifier"), None
|
|
402
|
+
)
|
|
403
|
+
if name_node is None:
|
|
404
|
+
continue
|
|
405
|
+
|
|
406
|
+
line_number = name_node.start_point[0] + 1
|
|
407
|
+
name = self._get_node_text(name_node)
|
|
408
|
+
if (name, line_number) in seen:
|
|
409
|
+
continue
|
|
410
|
+
seen.add((name, line_number))
|
|
411
|
+
|
|
412
|
+
# tree-sitter-c-sharp has no `equals_value_clause`: the
|
|
413
|
+
# initialiser is a direct sibling following the `=` token.
|
|
414
|
+
value = None
|
|
415
|
+
seen_equals = False
|
|
416
|
+
for child in declarator.children:
|
|
417
|
+
if child.type == "=":
|
|
418
|
+
seen_equals = True
|
|
419
|
+
continue
|
|
420
|
+
if seen_equals and child.is_named:
|
|
421
|
+
value = self._get_node_text(child)
|
|
422
|
+
break
|
|
423
|
+
|
|
424
|
+
context, _ctx_type, _ctx_line = self._get_parent_context(
|
|
425
|
+
declarator, types=("method_declaration", "function_declaration")
|
|
426
|
+
)
|
|
427
|
+
class_context, _c_type, _c_line = self._get_parent_context(
|
|
428
|
+
declarator,
|
|
429
|
+
types=("class_declaration", "struct_declaration", "record_declaration"),
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
variables.append({
|
|
433
|
+
"name": name,
|
|
434
|
+
"line_number": line_number,
|
|
435
|
+
"value": value,
|
|
436
|
+
"type": var_type,
|
|
437
|
+
"context": context,
|
|
438
|
+
"class_context": class_context,
|
|
439
|
+
"lang": self.language_name,
|
|
440
|
+
"is_dependency": False,
|
|
441
|
+
})
|
|
442
|
+
return variables
|
|
443
|
+
|
|
362
444
|
def _get_parent_context(self, node: Any, types: Tuple[str, ...] = ('class_declaration', 'struct_declaration', 'function_declaration', 'method_declaration')):
|
|
363
445
|
"""Find parent context for C# constructs."""
|
|
364
446
|
curr = node.parent
|
|
@@ -360,13 +360,77 @@ class JavascriptTreeSitterParser:
|
|
|
360
360
|
left_child = child.child_by_field_name('left')
|
|
361
361
|
if left_child and left_child.type == 'identifier':
|
|
362
362
|
params.append(self._get_node_text(left_child))
|
|
363
|
-
elif child.type
|
|
363
|
+
elif child.type in ('rest_element', 'rest_pattern'):
|
|
364
364
|
# Rest parameter: ...args
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
365
|
+
# Try named field first, then direct identifier child
|
|
366
|
+
name_node = (child.child_by_field_name('name') or
|
|
367
|
+
child.child_by_field_name('argument'))
|
|
368
|
+
if name_node is None:
|
|
369
|
+
name_node = next(
|
|
370
|
+
(c for c in child.children if c.type == 'identifier'), None
|
|
371
|
+
)
|
|
372
|
+
if name_node:
|
|
373
|
+
params.append(f"...{self._get_node_text(name_node)}")
|
|
374
|
+
elif child.type == 'object_pattern':
|
|
375
|
+
# Destructured object: {a}, {a: renamed}, {a = 1}
|
|
376
|
+
names = self._pattern_binding_names(child)
|
|
377
|
+
params.append("{" + ", ".join(names) + "}" if names else "{...}")
|
|
378
|
+
elif child.type == 'array_pattern':
|
|
379
|
+
# Destructured array: [a, b]
|
|
380
|
+
names = self._pattern_binding_names(child)
|
|
381
|
+
params.append("[" + ", ".join(names) + "]" if names else "[...]")
|
|
368
382
|
return params
|
|
369
383
|
|
|
384
|
+
def _pattern_binding_names(self, pattern_node):
|
|
385
|
+
"""Local names a destructuring pattern binds, in source order.
|
|
386
|
+
|
|
387
|
+
Recorded rather than collapsed to a placeholder because the binding is
|
|
388
|
+
what later code refers to — `function Button({label})` is the dominant
|
|
389
|
+
React idiom, and `label` is the name that appears in the body. One
|
|
390
|
+
entry is still emitted per *parameter position* so arity, which call
|
|
391
|
+
resolution matches on, is unchanged.
|
|
392
|
+
"""
|
|
393
|
+
names = []
|
|
394
|
+
|
|
395
|
+
def walk(node):
|
|
396
|
+
for child in node.named_children:
|
|
397
|
+
if child.type == 'shorthand_property_identifier_pattern':
|
|
398
|
+
# { a }
|
|
399
|
+
names.append(self._get_node_text(child))
|
|
400
|
+
elif child.type == 'pair_pattern':
|
|
401
|
+
# { a: local } — the binding is the value, not the key
|
|
402
|
+
value = child.child_by_field_name('value')
|
|
403
|
+
if value is not None and value.type == 'identifier':
|
|
404
|
+
names.append(self._get_node_text(value))
|
|
405
|
+
elif value is not None:
|
|
406
|
+
walk(value) # nested destructuring
|
|
407
|
+
elif child.type == 'object_assignment_pattern':
|
|
408
|
+
# { a = 1 }
|
|
409
|
+
target = child.child_by_field_name('left') or (
|
|
410
|
+
child.named_children[0] if child.named_children else None
|
|
411
|
+
)
|
|
412
|
+
if target is not None:
|
|
413
|
+
if target.type in (
|
|
414
|
+
'identifier', 'shorthand_property_identifier_pattern'
|
|
415
|
+
):
|
|
416
|
+
names.append(self._get_node_text(target))
|
|
417
|
+
else:
|
|
418
|
+
walk(target)
|
|
419
|
+
elif child.type == 'identifier':
|
|
420
|
+
# [ a, b ]
|
|
421
|
+
names.append(self._get_node_text(child))
|
|
422
|
+
elif child.type in ('object_pattern', 'array_pattern'):
|
|
423
|
+
walk(child)
|
|
424
|
+
elif child.type in ('rest_element', 'rest_pattern'):
|
|
425
|
+
inner = next(
|
|
426
|
+
(c for c in child.children if c.type == 'identifier'), None
|
|
427
|
+
)
|
|
428
|
+
if inner is not None:
|
|
429
|
+
names.append(f"...{self._get_node_text(inner)}")
|
|
430
|
+
|
|
431
|
+
walk(pattern_node)
|
|
432
|
+
return names
|
|
433
|
+
|
|
370
434
|
|
|
371
435
|
def _get_jsdoc_comment(self, func_node):
|
|
372
436
|
"""Extract JSDoc comment preceding the function."""
|
|
@@ -180,14 +180,47 @@ class RubyTreeSitterParser:
|
|
|
180
180
|
prev_sibling = prev_sibling.prev_sibling
|
|
181
181
|
return None
|
|
182
182
|
|
|
183
|
+
# Ruby parameter node types, and the prefix each carries in source. The
|
|
184
|
+
# bare `identifier` case is a required positional parameter.
|
|
185
|
+
_PARAM_PREFIXES = {
|
|
186
|
+
'optional_parameter': '', # b = 1
|
|
187
|
+
'keyword_parameter': '', # key:
|
|
188
|
+
'splat_parameter': '*', # *rest
|
|
189
|
+
'hash_splat_parameter': '**', # **opts
|
|
190
|
+
'block_parameter': '&', # &blk
|
|
191
|
+
}
|
|
192
|
+
|
|
183
193
|
def _parse_method_parameters(self, method_node: Any) -> list[str]:
|
|
184
|
-
"""Parse method parameters from a method node.
|
|
194
|
+
"""Parse method parameters from a method node.
|
|
195
|
+
|
|
196
|
+
Parameters live inside a `method_parameters` child, not directly on the
|
|
197
|
+
method — the only direct `identifier` child is the method's own name,
|
|
198
|
+
which this used to skip, so every Ruby method reported no parameters at
|
|
199
|
+
all and no `HAS_PARAMETER` edge was ever created.
|
|
200
|
+
"""
|
|
201
|
+
params_node = method_node.child_by_field_name('parameters')
|
|
202
|
+
if params_node is None:
|
|
203
|
+
params_node = next(
|
|
204
|
+
(c for c in method_node.children
|
|
205
|
+
if c.type in ('method_parameters', 'parameters', 'bare_parameters')),
|
|
206
|
+
None,
|
|
207
|
+
)
|
|
208
|
+
if params_node is None:
|
|
209
|
+
return []
|
|
210
|
+
|
|
185
211
|
params = []
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
212
|
+
for child in params_node.children:
|
|
213
|
+
if not child.is_named:
|
|
214
|
+
continue # punctuation: ( ) ,
|
|
215
|
+
if child.type == 'identifier':
|
|
190
216
|
params.append(self._get_node_text(child))
|
|
217
|
+
continue
|
|
218
|
+
prefix = self._PARAM_PREFIXES.get(child.type)
|
|
219
|
+
if prefix is None:
|
|
220
|
+
continue
|
|
221
|
+
name_node = next((c for c in child.children if c.type == 'identifier'), None)
|
|
222
|
+
if name_node is not None:
|
|
223
|
+
params.append(prefix + self._get_node_text(name_node))
|
|
191
224
|
return params
|
|
192
225
|
|
|
193
226
|
def parse(self, path: Path, is_dependency: bool = False, index_source: bool = False) -> Dict[str, Any]:
|
|
@@ -297,23 +330,26 @@ class RubyTreeSitterParser:
|
|
|
297
330
|
# Collect all captures first
|
|
298
331
|
all_captures = list(execute_query(self.language, query_str, root_node))
|
|
299
332
|
|
|
300
|
-
#
|
|
333
|
+
# Read each class's own `name` field rather than matching @name captures
|
|
334
|
+
# to classes by byte range. An outer class always contains an inner
|
|
335
|
+
# class's name node, so range-matching assigned the inner name to the
|
|
336
|
+
# outer class (the first container, in document order) and left the
|
|
337
|
+
# inner entry nameless — where it was then dropped. `class Client`
|
|
338
|
+
# wrapping `class TimeoutError` yielded exactly one class, named
|
|
339
|
+
# TimeoutError, carrying Client's line range.
|
|
301
340
|
captures_by_class = {}
|
|
302
341
|
for node, capture_name in all_captures:
|
|
303
|
-
if capture_name
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
node.end_byte <= class_node.end_byte):
|
|
315
|
-
captures_by_class[class_id]['name'] = self._get_node_text(node)
|
|
316
|
-
break
|
|
342
|
+
if capture_name != 'class':
|
|
343
|
+
continue
|
|
344
|
+
name_node = node.child_by_field_name('name')
|
|
345
|
+
if name_node is None:
|
|
346
|
+
# tree-sitter-ruby gives the `class` keyword token the same
|
|
347
|
+
# node type as the definition; only the definition has a name.
|
|
348
|
+
continue
|
|
349
|
+
captures_by_class[id(node)] = {
|
|
350
|
+
'node': node,
|
|
351
|
+
'name': self._get_node_text(name_node),
|
|
352
|
+
}
|
|
317
353
|
|
|
318
354
|
# Build class entries
|
|
319
355
|
for class_data in captures_by_class.values():
|
|
@@ -49,7 +49,15 @@ def _get_python_package_path(package_name: str) -> Optional[str]:
|
|
|
49
49
|
if _is_stdlib_module(package_name):
|
|
50
50
|
return str(module_file)
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
# For a flat, single-file package (e.g. six.py, typing_extensions.py),
|
|
53
|
+
# the parent is site-packages itself. Returning it would cause
|
|
54
|
+
# add_package_to_graph to index every installed dependency.
|
|
55
|
+
# Return the file instead so only the package is indexed.
|
|
56
|
+
parent = module_file.parent
|
|
57
|
+
if parent.name in ("site-packages", "dist-packages"):
|
|
58
|
+
return str(module_file)
|
|
59
|
+
|
|
60
|
+
return str(parent)
|
|
53
61
|
|
|
54
62
|
if spec.submodule_search_locations:
|
|
55
63
|
locations = list(spec.submodule_search_locations)
|
|
@@ -12,15 +12,23 @@ from typing import Any, Dict, List, Optional
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class JavaToolkit:
|
|
15
|
-
"""Spring-aware Java analysis methods backed by CodeFinder / raw Cypher.
|
|
15
|
+
"""Spring-aware Java analysis methods backed by CodeFinder / raw Cypher.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
Note: The generic ``advanced_language_query`` path calls this toolkit
|
|
18
|
+
with no arguments (consistent with all other language toolkits). The
|
|
19
|
+
Spring-specific MCP tools (``find_java_spring_endpoints`` /
|
|
20
|
+
``find_java_spring_beans``) pass a ``code_finder`` via ``_init_with``.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self) -> None:
|
|
24
|
+
self._cf: Any = None
|
|
25
|
+
self._driver: Any = None
|
|
26
|
+
|
|
27
|
+
def _init_with(self, code_finder: Any) -> "JavaToolkit":
|
|
28
|
+
"""Initialise with a live CodeFinder for Spring-specific queries."""
|
|
22
29
|
self._cf = code_finder
|
|
23
30
|
self._driver = code_finder.driver
|
|
31
|
+
return self
|
|
24
32
|
|
|
25
33
|
# ── Internal helpers ──────────────────────────────────────────────────────
|
|
26
34
|
|
|
@@ -106,8 +114,8 @@ class JavaToolkit:
|
|
|
106
114
|
f"""
|
|
107
115
|
MATCH (c:Class)
|
|
108
116
|
WHERE {where}
|
|
109
|
-
OPTIONAL MATCH ()-[:INJECTS]->(c)
|
|
110
|
-
WITH c, count(
|
|
117
|
+
OPTIONAL MATCH ()-[inj:INJECTS]->(c)
|
|
118
|
+
WITH c, count(inj) AS injection_count
|
|
111
119
|
RETURN c.name AS name, c.spring_stereotype AS stereotype,
|
|
112
120
|
c.path AS file, c.line_number AS line_number,
|
|
113
121
|
injection_count
|
|
@@ -249,5 +257,6 @@ class JavaToolkit:
|
|
|
249
257
|
# Legacy shim — keeps existing callers working
|
|
250
258
|
def get_cypher_query(self, query: str) -> str:
|
|
251
259
|
raise NotImplementedError(
|
|
260
|
+
"AdvancedLanguageQuery is not implemented yet for Java. "
|
|
252
261
|
"Use find_spring_endpoints(), find_beans_by_stereotype(), etc. instead."
|
|
253
262
|
)
|
|
@@ -220,12 +220,25 @@ def _section_cross_module_calls(driver: Any, limit: int = 20, repo_path: Optiona
|
|
|
220
220
|
|
|
221
221
|
def _section_dead_code(driver: Any, limit: int = 20, repo_path: Optional[str] = None) -> str:
|
|
222
222
|
"""Functions with no incoming CALLS edges (potential dead code)."""
|
|
223
|
+
# Share the entry-point list with `CodeFinder.find_dead_code` so the report
|
|
224
|
+
# and the CLI/MCP tool cannot disagree. They previously did, in both
|
|
225
|
+
# directions: this query had no name exclusions at all (so every dunder and
|
|
226
|
+
# `test_*` showed up as dead), while `find_dead_code` restricted callers to
|
|
227
|
+
# `(caller:Function)` and so missed the File-sourced edges that this one
|
|
228
|
+
# correctly counts.
|
|
229
|
+
from .code_finder import _ENTRY_POINT_NAMES_CYPHER
|
|
230
|
+
|
|
223
231
|
rows = _run_cypher(
|
|
224
232
|
driver,
|
|
225
|
-
"""
|
|
233
|
+
f"""
|
|
226
234
|
MATCH (fn:Function)
|
|
227
235
|
WHERE (fn.is_dependency IS NULL OR fn.is_dependency = false)
|
|
228
236
|
AND ($repo_path IS NULL OR fn.path STARTS WITH $repo_path)
|
|
237
|
+
AND NOT toLower(fn.name) IN {_ENTRY_POINT_NAMES_CYPHER}
|
|
238
|
+
AND fn.name <> '<module>'
|
|
239
|
+
AND NOT (fn.name STARTS WITH '__' AND fn.name ENDS WITH '__')
|
|
240
|
+
AND NOT fn.name STARTS WITH '_test'
|
|
241
|
+
AND NOT fn.name STARTS WITH 'test_'
|
|
229
242
|
AND NOT ()-[:CALLS|HEURISTIC_CALLS]->(fn)
|
|
230
243
|
RETURN fn.name AS name, fn.path AS path
|
|
231
244
|
ORDER BY fn.path, fn.name
|
|
@@ -12,6 +12,7 @@ import json
|
|
|
12
12
|
import os
|
|
13
13
|
import tempfile
|
|
14
14
|
import webbrowser
|
|
15
|
+
from html import escape as html_escape
|
|
15
16
|
from pathlib import Path
|
|
16
17
|
from typing import Any, Dict, List, Optional
|
|
17
18
|
|
|
@@ -55,6 +56,24 @@ def edge_color(rel_type: str) -> str:
|
|
|
55
56
|
return _EDGE_COLORS.get(rel_type, _EDGE_COLORS["default"])
|
|
56
57
|
|
|
57
58
|
|
|
59
|
+
def _json_for_script(data: Any) -> str:
|
|
60
|
+
"""Serialise ``data`` for inlining inside a ``<script>`` element.
|
|
61
|
+
|
|
62
|
+
``json.dumps`` escapes quotes and backslashes but leaves ``<``, ``>`` and
|
|
63
|
+
``&`` alone, so a node name or file path containing ``</script>`` closes
|
|
64
|
+
the element early and the rest of the document is parsed as markup. Those
|
|
65
|
+
three characters only ever appear inside string literals, so replacing
|
|
66
|
+
them with their ``\\uXXXX`` form keeps the payload valid JSON and valid
|
|
67
|
+
JavaScript while making it inert as markup.
|
|
68
|
+
"""
|
|
69
|
+
return (
|
|
70
|
+
json.dumps(data, indent=2)
|
|
71
|
+
.replace("<", "\\u003c")
|
|
72
|
+
.replace(">", "\\u003e")
|
|
73
|
+
.replace("&", "\\u0026")
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
58
77
|
def build_graph_data(
|
|
59
78
|
nodes: List[Dict[str, Any]],
|
|
60
79
|
edges: List[Dict[str, Any]],
|
|
@@ -125,14 +144,15 @@ def render_html(
|
|
|
125
144
|
str
|
|
126
145
|
Complete HTML document as a string.
|
|
127
146
|
"""
|
|
128
|
-
graph_json =
|
|
147
|
+
graph_json = _json_for_script(graph_data)
|
|
148
|
+
safe_title = html_escape(title)
|
|
129
149
|
|
|
130
150
|
html = f"""<!DOCTYPE html>
|
|
131
151
|
<html lang="en">
|
|
132
152
|
<head>
|
|
133
153
|
<meta charset="UTF-8" />
|
|
134
154
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
135
|
-
<title>{
|
|
155
|
+
<title>{safe_title}</title>
|
|
136
156
|
<style>
|
|
137
157
|
* {{ box-sizing: border-box; margin: 0; padding: 0; }}
|
|
138
158
|
body {{
|
|
@@ -198,7 +218,7 @@ def render_html(
|
|
|
198
218
|
</head>
|
|
199
219
|
<body>
|
|
200
220
|
<div id="header">
|
|
201
|
-
<h1>🏗 {
|
|
221
|
+
<h1>🏗 {safe_title}</h1>
|
|
202
222
|
<span id="stats"></span>
|
|
203
223
|
</div>
|
|
204
224
|
<div id="canvas-container">
|
codegraphcontext/viz/server.py
CHANGED
|
@@ -96,13 +96,16 @@ def _get_eid(element):
|
|
|
96
96
|
if isinstance(element, (int, str)):
|
|
97
97
|
return str(element)
|
|
98
98
|
|
|
99
|
-
# If element is a dict (like Neo4j returned items or KuzuDB node/rel
|
|
99
|
+
# If element is a dict (like Neo4j returned items or KuzuDB/Ladybug node/rel
|
|
100
|
+
# dicts). KùzuDB spells its internal id field '_id'; LadybugDB spells the
|
|
101
|
+
# same field '_ID' (uppercase) — confirmed against a live query on each
|
|
102
|
+
# embedded backend.
|
|
100
103
|
if isinstance(element, dict):
|
|
101
104
|
# KuzuDB _src / _dst are directly {'offset': X, 'table': Y}
|
|
102
105
|
if 'offset' in element and 'table' in element:
|
|
103
106
|
return f"{element.get('table')}_{element.get('offset')}"
|
|
104
|
-
|
|
105
|
-
for key in ['_id', 'id', 'element_id']:
|
|
107
|
+
|
|
108
|
+
for key in ['_id', '_ID', 'id', 'element_id']:
|
|
106
109
|
if key in element:
|
|
107
110
|
val = element[key]
|
|
108
111
|
if val is not None:
|
|
@@ -116,7 +119,7 @@ def _get_eid(element):
|
|
|
116
119
|
for attr in ['element_id', 'id', '_id']:
|
|
117
120
|
if hasattr(element, attr):
|
|
118
121
|
val = getattr(element, attr)
|
|
119
|
-
if val is not None:
|
|
122
|
+
if val is not None:
|
|
120
123
|
# KuzuDB objects if any
|
|
121
124
|
if isinstance(val, dict):
|
|
122
125
|
return f"{val.get('table', 0)}_{val.get('offset', 0)}"
|
|
@@ -124,6 +127,18 @@ def _get_eid(element):
|
|
|
124
127
|
return str(id(element))
|
|
125
128
|
|
|
126
129
|
|
|
130
|
+
def _meta(d: dict, name: str):
|
|
131
|
+
"""Read a KùzuDB/LadybugDB internal dict field regardless of its casing.
|
|
132
|
+
|
|
133
|
+
KùzuDB spells these fields lowercase (_label/_src/_dst); LadybugDB spells
|
|
134
|
+
the same fields uppercase (_LABEL/_SRC/_DST) — confirmed against a live
|
|
135
|
+
query on each embedded backend. Keying on one casing only silently drops
|
|
136
|
+
every row on the other backend.
|
|
137
|
+
"""
|
|
138
|
+
lower = f"_{name}"
|
|
139
|
+
return d[lower] if lower in d else d.get(f"_{name.upper()}")
|
|
140
|
+
|
|
141
|
+
|
|
127
142
|
@app.get("/api/graph")
|
|
128
143
|
async def get_graph(repo_path: Optional[str] = None, cypher_query: Optional[str] = None):
|
|
129
144
|
if not db_manager:
|
|
@@ -186,9 +201,10 @@ async def get_graph(repo_path: Optional[str] = None, cypher_query: Optional[str]
|
|
|
186
201
|
# Extract labels
|
|
187
202
|
labels = []
|
|
188
203
|
if isinstance(node, dict):
|
|
189
|
-
# KuzuDB node label is under '_label'
|
|
190
|
-
|
|
191
|
-
|
|
204
|
+
# KuzuDB node label is under '_label' (LadybugDB: '_LABEL')
|
|
205
|
+
meta_label = _meta(node, 'label')
|
|
206
|
+
if meta_label is not None:
|
|
207
|
+
labels = [meta_label]
|
|
192
208
|
elif 'label' in node:
|
|
193
209
|
labels = [node['label']]
|
|
194
210
|
else:
|
|
@@ -247,13 +263,17 @@ async def get_graph(repo_path: Optional[str] = None, cypher_query: Optional[str]
|
|
|
247
263
|
# FalkorDB / KuzuDB may return rels as dicts OR objects
|
|
248
264
|
if isinstance(rel, dict):
|
|
249
265
|
rid = get_eid(rel)
|
|
250
|
-
# KuzuDB uses _src
|
|
251
|
-
src = rel
|
|
252
|
-
|
|
253
|
-
|
|
266
|
+
# KuzuDB uses _src/_dst (LadybugDB: _SRC/_DST), FalkorDB uses src_node/dest_node
|
|
267
|
+
src = _meta(rel, 'src')
|
|
268
|
+
if src is None:
|
|
269
|
+
src = rel.get('src_node')
|
|
270
|
+
dst = _meta(rel, 'dst')
|
|
271
|
+
if dst is None:
|
|
272
|
+
dst = rel.get('dest_node')
|
|
273
|
+
|
|
254
274
|
source = get_eid(src) if src is not None else None
|
|
255
275
|
target = get_eid(dst) if dst is not None else None
|
|
256
|
-
rel_type = str(rel
|
|
276
|
+
rel_type = str(_meta(rel, 'label') or rel.get('relation', rel.get('type', 'RELATED'))).upper()
|
|
257
277
|
else:
|
|
258
278
|
rid = get_eid(rel)
|
|
259
279
|
start_node = None
|
|
@@ -420,8 +440,9 @@ def parse_node(node, nodes_dict):
|
|
|
420
440
|
|
|
421
441
|
labels = []
|
|
422
442
|
if isinstance(node, dict):
|
|
423
|
-
|
|
424
|
-
|
|
443
|
+
meta_label = _meta(node, 'label')
|
|
444
|
+
if meta_label is not None:
|
|
445
|
+
labels = [meta_label]
|
|
425
446
|
elif 'label' in node:
|
|
426
447
|
labels = [node['label']]
|
|
427
448
|
else:
|
|
@@ -463,11 +484,15 @@ def parse_node(node, nodes_dict):
|
|
|
463
484
|
def parse_rel(rel, edges):
|
|
464
485
|
rid = _get_eid(rel)
|
|
465
486
|
if isinstance(rel, dict):
|
|
466
|
-
src = rel
|
|
467
|
-
|
|
487
|
+
src = _meta(rel, 'src')
|
|
488
|
+
if src is None:
|
|
489
|
+
src = rel.get('src_node')
|
|
490
|
+
dst = _meta(rel, 'dst')
|
|
491
|
+
if dst is None:
|
|
492
|
+
dst = rel.get('dest_node')
|
|
468
493
|
source = _get_eid(src) if src is not None else None
|
|
469
494
|
target = _get_eid(dst) if dst is not None else None
|
|
470
|
-
rel_type = str(rel
|
|
495
|
+
rel_type = str(_meta(rel, 'label') or rel.get('relation', rel.get('type', 'RELATED'))).upper()
|
|
471
496
|
else:
|
|
472
497
|
start_node = None
|
|
473
498
|
end_node = None
|
|
@@ -510,9 +535,16 @@ def parse_element(val, nodes_dict, edges):
|
|
|
510
535
|
|
|
511
536
|
type_name = type(val).__name__
|
|
512
537
|
|
|
513
|
-
|
|
538
|
+
is_dict_rel = isinstance(val, dict) and (
|
|
539
|
+
any(k in val for k in ('_src', '_dst', '_SRC', '_DST', 'src_node', 'dest_node'))
|
|
540
|
+
)
|
|
541
|
+
is_dict_node = isinstance(val, dict) and (
|
|
542
|
+
'_label' in val or '_LABEL' in val or 'labels' in val
|
|
543
|
+
) and not is_dict_rel
|
|
544
|
+
|
|
545
|
+
if type_name in ('Node', 'KuzuNode') or is_dict_node:
|
|
514
546
|
parse_node(val, nodes_dict)
|
|
515
|
-
elif type_name in ('Relationship', 'KuzuRelationship') or
|
|
547
|
+
elif type_name in ('Relationship', 'KuzuRelationship') or is_dict_rel:
|
|
516
548
|
parse_rel(val, edges)
|
|
517
549
|
elif isinstance(val, (list, tuple)):
|
|
518
550
|
for item in val:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codegraphcontext
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.7
|
|
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
|
|
@@ -372,8 +372,8 @@ CodeGraphContext supports multiple graph database backends to suit your environm
|
|
|
372
372
|
| **Platform** | **All (Windows Native, macOS, Linux)** | **All (Windows Native, macOS, Linux)** | Unix-only (Linux/macOS/WSL) | All Platforms |
|
|
373
373
|
| **Use Case** | Desktop, IDE, Local development | Custom research projects | Specialized Unix development | Enterprise, Massive graphs |
|
|
374
374
|
| **Requirement**| `pip install kuzu` | `pip install ladybug` | `pip install falkordblite` | Neo4j Server / Docker / Nornic Cloud |
|
|
375
|
-
| **Speed** | ⚡ Extremely Fast | ⚡ Fast | 🚀 Scalable |
|
|
376
|
-
| **Persistence**| Yes (to disk) | Yes (to disk) | Yes (to disk) |
|
|
375
|
+
| **Speed** | ⚡ Extremely Fast | ⚡ Fast | 🚀 Scalable | 🌐 Network-dependent |
|
|
376
|
+
| **Persistence**| Yes (to disk) | Yes (to disk) | Yes (to disk) | Yes (server-side) |
|
|
377
377
|
|
|
378
378
|
---
|
|
379
379
|
|
|
@@ -14,7 +14,7 @@ codegraphcontext/cli/__init__.py,sha256=2vUlnf7H09M9JbS2gS_LmMUBJWAWcGGHkkl4I2Yr
|
|
|
14
14
|
codegraphcontext/cli/cli_helpers.py,sha256=w7TCwcL5T6XyRPYKzLkBdt0V6O3YEkWlPl_WTBZHWEA,54179
|
|
15
15
|
codegraphcontext/cli/config_manager.py,sha256=wjYT-aYreRfgRxd0Hy_thLSmlgsV2_JRuMVlVlZlEBo,54960
|
|
16
16
|
codegraphcontext/cli/hook_manager.py,sha256=nKr6SiuM6zA3eZv1rZSNPXUyqkha8Ja0VGQ3_145bLE,7615
|
|
17
|
-
codegraphcontext/cli/main.py,sha256=
|
|
17
|
+
codegraphcontext/cli/main.py,sha256=G5DEaVvLX4rqOrB2YemRz8YvG8Alc0Naot9B_rhwTJ0,141135
|
|
18
18
|
codegraphcontext/cli/project_config.py,sha256=Slae6fIojlcqRbFzRkAMAsglamryLCPrjUFG16e165g,7492
|
|
19
19
|
codegraphcontext/cli/registry_commands.py,sha256=KwLwxKzMWs0tAFd__nPf_gz3kmlPO5b6Zcb_9Qkguf0,18516
|
|
20
20
|
codegraphcontext/cli/setup_macos.py,sha256=0jcxRsQX7_UcfKrFUnc5Yx3m34w0C3w3x5n7KH6ty6g,3743
|
|
@@ -38,11 +38,11 @@ codegraphcontext/core/jobs.py,sha256=8D5E8K_t9jTzPGSAeNK05d-7dPq2SeIJA0igSbHit9A
|
|
|
38
38
|
codegraphcontext/core/simulator.py,sha256=Sb3PpnoMGVDWvqPVSv5dcQqiL3nTvhmLzzLzJ41krkE,24140
|
|
39
39
|
codegraphcontext/core/watcher.py,sha256=nSfkmZueMBHxx8-7zNHScKtaSGfNWrfjWdAe-T0Y2vI,18793
|
|
40
40
|
codegraphcontext/tools/__init__.py,sha256=iqQPEkDlGXO7rAaLNQrS_ZAbLSklBSK-OowpARq0mig,41
|
|
41
|
-
codegraphcontext/tools/advanced_language_query_tool.py,sha256=
|
|
42
|
-
codegraphcontext/tools/code_finder.py,sha256=
|
|
41
|
+
codegraphcontext/tools/advanced_language_query_tool.py,sha256=8pRVMg-Tbp3JrbN31McyTbum91gJPlrjHFCFVJ78coA,4169
|
|
42
|
+
codegraphcontext/tools/code_finder.py,sha256=d8RK2HQVPD4EQ_ZmhSsEhU6AY0NoYPnIjROwZpGEB78,73651
|
|
43
43
|
codegraphcontext/tools/graph_builder.py,sha256=uPHc4q8u7NQ7lSceRm4IwN3nLxgq9C0MvQtkY8HA9CE,57271
|
|
44
|
-
codegraphcontext/tools/package_resolver.py,sha256=
|
|
45
|
-
codegraphcontext/tools/report_generator.py,sha256=
|
|
44
|
+
codegraphcontext/tools/package_resolver.py,sha256=j6s4H5VnourUdJ-CkA3kCOc-Vdewnjc-PNDshAPCiaU,7802
|
|
45
|
+
codegraphcontext/tools/report_generator.py,sha256=3tH83_24nm-pWcY_oXUeLB2jYtGGZmkiBb_oyuhS4WM,16114
|
|
46
46
|
codegraphcontext/tools/scip_indexer.py,sha256=8ZHaooZeQgTtO_wRI0VwiMgIQF1QNAg7o6Xx5VGj3BM,39112
|
|
47
47
|
codegraphcontext/tools/scip_pb2.py,sha256=jSMLR7ud4eaKrIkQN-XDWNaFCyUaueafUm9WgOhjqRA,98270
|
|
48
48
|
codegraphcontext/tools/system.py,sha256=RCUraHQOT-M146CwLM3W1GcBHsAvuans1puL9SPakxs,6647
|
|
@@ -52,9 +52,9 @@ codegraphcontext/tools/datasources/__init__.py,sha256=EDY0_s7DJPRzJhfmYUz3Pxv0b6
|
|
|
52
52
|
codegraphcontext/tools/datasources/cassandra_ingester.py,sha256=sgbskSkvE98qtOprgzkbVSEIasZWe4WXOzaCiRR2c_Q,3060
|
|
53
53
|
codegraphcontext/tools/datasources/mysql_ingester.py,sha256=uWstOv7mOEws1pmT8uAsEBkoOGM1FXOWt_2VCE-tZeg,3991
|
|
54
54
|
codegraphcontext/tools/datasources/redis_ingester.py,sha256=mVUKYmr_v6g7wYaavmQscEHGi_CPZrxYLjfkldc6cWE,3788
|
|
55
|
-
codegraphcontext/tools/handlers/analysis_handlers.py,sha256=
|
|
56
|
-
codegraphcontext/tools/handlers/indexing_handlers.py,sha256=
|
|
57
|
-
codegraphcontext/tools/handlers/management_handlers.py,sha256=
|
|
55
|
+
codegraphcontext/tools/handlers/analysis_handlers.py,sha256=8pZAcNNw0CFJ4LfuYqcJ-4cnS-X7_4sKWwd6S7EUGos,14155
|
|
56
|
+
codegraphcontext/tools/handlers/indexing_handlers.py,sha256=sKtbP19EX694zs-6rsVkhwihGwSIWaKjm4N41jMXP2M,7437
|
|
57
|
+
codegraphcontext/tools/handlers/management_handlers.py,sha256=19-fCuMuY5oHXQ9O4F1qG4ZsKKsGpSGbxxA1gH4AE-g,17419
|
|
58
58
|
codegraphcontext/tools/handlers/query_handlers.py,sha256=mqI7pfuSRc92ChAS5IGtrkPyhpPeh-dg-ewVnysx5nw,4643
|
|
59
59
|
codegraphcontext/tools/handlers/watcher_handlers.py,sha256=po0NP3WnUqJAiDyZW9pz2g2V-itnazI10cXQJuc1UZ8,3809
|
|
60
60
|
codegraphcontext/tools/indexing/__init__.py,sha256=exCNeB4UYhKhNSoEBNta_EZ51Kdze4iK4e3oWH8B4X8,126
|
|
@@ -66,7 +66,7 @@ codegraphcontext/tools/indexing/pre_scan.py,sha256=St7Hc3-NFTMYF39vWneJL6iIRIc78
|
|
|
66
66
|
codegraphcontext/tools/indexing/sanitize.py,sha256=IaTP7Aztso3SsvjNZMEqJ1iktV4Tl75hXcYnCWBGpLs,1490
|
|
67
67
|
codegraphcontext/tools/indexing/schema.py,sha256=AcHaqxDi9d5nGjZdg5S6mbjhHo98UYPRfcz1ZpvNEy4,9157
|
|
68
68
|
codegraphcontext/tools/indexing/schema_contract.py,sha256=23ok37ASjvOsU8S-sqyRiKIkGhcUxQ8PSErbBmylggU,1815
|
|
69
|
-
codegraphcontext/tools/indexing/scip_pipeline.py,sha256=
|
|
69
|
+
codegraphcontext/tools/indexing/scip_pipeline.py,sha256=OLbf0rhs1XU4LTQ6B3mS1-ngbPEKIN69o59rw9Pbk_E,13128
|
|
70
70
|
codegraphcontext/tools/indexing/vector_resolver.py,sha256=0D0QClro7nx9Wx6hbbFlOsO4dqgcTrwPMqE0CsJK01o,5477
|
|
71
71
|
codegraphcontext/tools/indexing/persistence/__init__.py,sha256=hFRlNgiYS7uN9X4e4xfP3c1BvcYSxpmq0k_uaDRn2Fg,121
|
|
72
72
|
codegraphcontext/tools/indexing/persistence/utils.py,sha256=OtndHpE_R14mPz5i-Bo9mA7NsoIuYnCNER-oIwxDOYA,2028
|
|
@@ -75,9 +75,9 @@ codegraphcontext/tools/indexing/resolution/__init__.py,sha256=BR57mVydpzjOnFSbGb
|
|
|
75
75
|
codegraphcontext/tools/indexing/resolution/calls.py,sha256=Uamtm8DXJnwlnTkdzYfu6G74MOcEyQ4njasweE7Nydo,130762
|
|
76
76
|
codegraphcontext/tools/indexing/resolution/inheritance.py,sha256=xataBg0Lvgj3Sexgxx434TmwgRpvODaQs5HQYEVxPVs,21192
|
|
77
77
|
codegraphcontext/tools/indexing/resolution/post_resolution.py,sha256=OzmbbmLwIbKb5wm1RodoyvDST3d35A2e35ruDKBQDF4,10859
|
|
78
|
-
codegraphcontext/tools/languages/c.py,sha256=
|
|
79
|
-
codegraphcontext/tools/languages/cpp.py,sha256=
|
|
80
|
-
codegraphcontext/tools/languages/csharp.py,sha256=
|
|
78
|
+
codegraphcontext/tools/languages/c.py,sha256=quo2j0PCTlSOJctqqF_5zKeisERvTDyKuPtz_qvFgQA,31618
|
|
79
|
+
codegraphcontext/tools/languages/cpp.py,sha256=41kDYWbuH6hjNAZyCEH9RhZBNCUz8kQyccw3Ppd-efQ,31389
|
|
80
|
+
codegraphcontext/tools/languages/csharp.py,sha256=oiSBmEf96mIU-2u-0OVXyx3QTn1_lOv-XLPc6aujarA,27502
|
|
81
81
|
codegraphcontext/tools/languages/css.py,sha256=aN1Nm8vHmfV0J0jQ-_q5x0YDTOtLV2IW-5lp7WtGG5o,3789
|
|
82
82
|
codegraphcontext/tools/languages/dart.py,sha256=yjqVA6XwoADrZab95p6wva0jT-PAGw_x90LR-cp2BDk,27160
|
|
83
83
|
codegraphcontext/tools/languages/elisp.py,sha256=QVxJq328bR4DsQK8Vs8b9oxthx1Wm_BS1dsCSjD_P8I,22717
|
|
@@ -87,7 +87,7 @@ codegraphcontext/tools/languages/gradle.py,sha256=WA1nQTi-JMHv5vLTJ35ECW0dQCb4PE
|
|
|
87
87
|
codegraphcontext/tools/languages/haskell.py,sha256=YyslUwcanFs00IinFrj7_TJ2IY2Q97NYsG2Y-9qqolU,19888
|
|
88
88
|
codegraphcontext/tools/languages/html.py,sha256=cjzAH55-JYbObGcMiqjuYD6X0Cbs1Kj5ixcZv9BzMns,5595
|
|
89
89
|
codegraphcontext/tools/languages/java.py,sha256=dpyHBGPhL1FaYP_unh-ncRDOlmmA2nBqnkLc1tacYm0,52024
|
|
90
|
-
codegraphcontext/tools/languages/javascript.py,sha256=
|
|
90
|
+
codegraphcontext/tools/languages/javascript.py,sha256=LR0W2BKZANQaQlNiIshORmJ08IAVVf9Pv1a8-6pILDo,30197
|
|
91
91
|
codegraphcontext/tools/languages/kotlin.py,sha256=8W367uThQzuTqbFzobUnjm_VEGf_jhLox_fmo13wYsc,88644
|
|
92
92
|
codegraphcontext/tools/languages/lua.py,sha256=TxpOxFd47E4hKwkJpO7b_dMvWveYd4Eaqp1n5SnN-zM,16726
|
|
93
93
|
codegraphcontext/tools/languages/maven.py,sha256=yQjrdSRZJPy6tUopZ7x5qYp8Rtbug4PPbBjCdl9FMDY,6393
|
|
@@ -95,7 +95,7 @@ codegraphcontext/tools/languages/mybatis.py,sha256=T6mVBCg7_5xOobBIAqZg9oHtZQy7s
|
|
|
95
95
|
codegraphcontext/tools/languages/perl.py,sha256=vjz65VFoKLsql2gyaT44B3R0sieloQmxO7fHMp8aqLA,12837
|
|
96
96
|
codegraphcontext/tools/languages/php.py,sha256=WwdFmWvBxQX8rsqHFCr8tdrz_W3pWv_COp1Bw-xiGiE,29804
|
|
97
97
|
codegraphcontext/tools/languages/python.py,sha256=6Fd05Hxc-jVRTa6t0wYuk8-yZ-0MdrRW3xo1TOucxEk,32714
|
|
98
|
-
codegraphcontext/tools/languages/ruby.py,sha256=
|
|
98
|
+
codegraphcontext/tools/languages/ruby.py,sha256=vkSNS6JOmmVKxG9eDPo5rNd6fNACUScrEjGBZbHg8Fc,24961
|
|
99
99
|
codegraphcontext/tools/languages/rust.py,sha256=o_uhEL_a4REPYXqdH4tljucyXMs5ModaMfeT-sanTBc,15560
|
|
100
100
|
codegraphcontext/tools/languages/scala.py,sha256=TmH_DK7baoo7_eMYdu9e7Cqb0ddVpibrhUO5TQD4AsI,23820
|
|
101
101
|
codegraphcontext/tools/languages/sfc_common.py,sha256=BLCqNO8EoLSdxt1u-1XvrkUotgWHJkXts6w1NQXH6XU,8567
|
|
@@ -113,7 +113,7 @@ codegraphcontext/tools/query_tool_languages/dart_toolkit.py,sha256=W5ClOZ-KCRFmw
|
|
|
113
113
|
codegraphcontext/tools/query_tool_languages/elisp_toolkit.py,sha256=fH8T5HKULMSgq8m46kCUi7Ql1lglTH5taQemJLCrKi0,2591
|
|
114
114
|
codegraphcontext/tools/query_tool_languages/go_toolkit.py,sha256=wK-QMk8fBlJjvnvp9nK9hRq-GbcfsdRaY35E65uacME,273
|
|
115
115
|
codegraphcontext/tools/query_tool_languages/haskell_toolkit.py,sha256=3GMwoSAk8X1nK4Yjz8YMhc_m7bStEI7wYOu0tgTkZl4,283
|
|
116
|
-
codegraphcontext/tools/query_tool_languages/java_toolkit.py,sha256=
|
|
116
|
+
codegraphcontext/tools/query_tool_languages/java_toolkit.py,sha256=jjHs_lMgMFu8AgOcUvNrWJSdGSKTZuSYRyjzuESWFMo,9423
|
|
117
117
|
codegraphcontext/tools/query_tool_languages/javascript_toolkit.py,sha256=AZNo63prnaETy1BXy1en40brGODIpcrH6GdmSohp5co,289
|
|
118
118
|
codegraphcontext/tools/query_tool_languages/perl_toolkit.py,sha256=vkd9O5YCXIgSbq2dYVvWT4T8BvL66Qo98TRf0kD5w-c,277
|
|
119
119
|
codegraphcontext/tools/query_tool_languages/python_toolkit.py,sha256=wijYvL7g4wevJhrtbk5asLC8oxIlpZ8IxidRi4HChKM,281
|
|
@@ -133,8 +133,8 @@ codegraphcontext/utils/path_sandbox.py,sha256=AemD1DHVMA0lj9BpmRkKwJB2OaE0Sewggm
|
|
|
133
133
|
codegraphcontext/utils/repo_path.py,sha256=BxS3y6RV_WaZhg_WxgJygS4SnQa-Ryth2fsicfnYorY,1121
|
|
134
134
|
codegraphcontext/utils/tool_limits.py,sha256=3d-QnlzxBNocz58VKNhdsta7ZpWThs1AX46_gYXl6pQ,2751
|
|
135
135
|
codegraphcontext/utils/tree_sitter_manager.py,sha256=Xr26xYL1HUP8V1Hk9d7tbS4RUb774f-Stc2DtuE7oN0,13307
|
|
136
|
-
codegraphcontext/utils/visualize_graph.py,sha256=
|
|
137
|
-
codegraphcontext/viz/server.py,sha256=
|
|
136
|
+
codegraphcontext/utils/visualize_graph.py,sha256=me35VuizqLyveT86XdgavrgnB2B4lG7WqFyiMr5ea1w,14644
|
|
137
|
+
codegraphcontext/viz/server.py,sha256=l2hM6phhYeMn0vAmMz7wtRGdj7LMkg2BsNMOvS1RUfg,31833
|
|
138
138
|
codegraphcontext/viz/dist/cgcIcon.png,sha256=lik7hnqvkqLc5uo9w9Q_YJXtRx7cWPrXqSLttGtW1sg,58703
|
|
139
139
|
codegraphcontext/viz/dist/favicon.ico,sha256=AjhUxD_Eslt5XuSVHIAZ494Fk__rb5GLXR8qm0elfP4,1870
|
|
140
140
|
codegraphcontext/viz/dist/index.html,sha256=uEaFo3uHWD2KojFiyfWRJoS1_t6zrllOnf_siaB0I5U,1946
|
|
@@ -173,9 +173,9 @@ codegraphcontext/viz/dist/wasm/tree-sitter-typescript.wasm,sha256=hRVATc7tOOHthq
|
|
|
173
173
|
codegraphcontext/viz/dist/wasm/tree-sitter.wasm,sha256=CCeVuI_hXktkBD-DW01xYPv5XoAYVRIqzJUJI5te-RY,196763
|
|
174
174
|
codegraphcontext/viz/dist/wasm/web-tree-sitter.js,sha256=DIaCNqRylrT_PBVw8g4ImeSnhP9uXNe_ycOlUiVGPko,153666
|
|
175
175
|
codegraphcontext/viz/dist/wasm/web-tree-sitter.wasm,sha256=CCeVuI_hXktkBD-DW01xYPv5XoAYVRIqzJUJI5te-RY,196763
|
|
176
|
-
codegraphcontext-0.5.
|
|
177
|
-
codegraphcontext-0.5.
|
|
178
|
-
codegraphcontext-0.5.
|
|
179
|
-
codegraphcontext-0.5.
|
|
180
|
-
codegraphcontext-0.5.
|
|
181
|
-
codegraphcontext-0.5.
|
|
176
|
+
codegraphcontext-0.5.7.dist-info/licenses/LICENSE,sha256=rh8M-bJpQYJnw2vtRVgt0t7piMZXh5QzaKeNEI0vqqA,1061
|
|
177
|
+
codegraphcontext-0.5.7.dist-info/METADATA,sha256=pb9kZrMtqIxBsrCVhKXy0zX--PZTWRc6jE0Px6G7dyg,32892
|
|
178
|
+
codegraphcontext-0.5.7.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
179
|
+
codegraphcontext-0.5.7.dist-info/entry_points.txt,sha256=LCxWCWMshdvYGoHBPuQZ8C-e4CiNSHCLXofrNSGHkoE,103
|
|
180
|
+
codegraphcontext-0.5.7.dist-info/top_level.txt,sha256=CBgc6LAPZIO5FS0nSYYkylDifHsZTIqw3Gf5UwDxeGI,17
|
|
181
|
+
codegraphcontext-0.5.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|