codegraphcontext 0.3.5__py3-none-any.whl → 0.3.8__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/cli_helpers.py +8 -6
- codegraphcontext/cli/setup_wizard.py +6 -0
- codegraphcontext/core/database_kuzu.py +20 -10
- codegraphcontext/server.py +36 -1
- codegraphcontext/tools/code_finder.py +93 -31
- codegraphcontext/tools/graph_builder.py +88 -19
- codegraphcontext/tools/languages/cpp.py +137 -72
- codegraphcontext/tools/languages/go.py +42 -5
- codegraphcontext/utils/path_ignore.py +55 -0
- codegraphcontext/utils/tree_sitter_manager.py +8 -9
- {codegraphcontext-0.3.5.dist-info → codegraphcontext-0.3.8.dist-info}/METADATA +6 -5
- {codegraphcontext-0.3.5.dist-info → codegraphcontext-0.3.8.dist-info}/RECORD +16 -15
- {codegraphcontext-0.3.5.dist-info → codegraphcontext-0.3.8.dist-info}/WHEEL +0 -0
- {codegraphcontext-0.3.5.dist-info → codegraphcontext-0.3.8.dist-info}/entry_points.txt +0 -0
- {codegraphcontext-0.3.5.dist-info → codegraphcontext-0.3.8.dist-info}/licenses/LICENSE +0 -0
- {codegraphcontext-0.3.5.dist-info → codegraphcontext-0.3.8.dist-info}/top_level.txt +0 -0
|
@@ -483,9 +483,10 @@ def clean_helper():
|
|
|
483
483
|
console.print("[cyan]🧹 Cleaning database (removing orphaned nodes)...[/cyan]")
|
|
484
484
|
|
|
485
485
|
try:
|
|
486
|
-
# Determine
|
|
486
|
+
# Determine backend type for query compatibility
|
|
487
487
|
db_type = db_manager.__class__.__name__
|
|
488
488
|
is_falkordb = "Falkor" in db_type
|
|
489
|
+
is_kuzu = "Kuzu" in db_type
|
|
489
490
|
|
|
490
491
|
total_deleted = 0
|
|
491
492
|
batch_size = 1000
|
|
@@ -493,14 +494,15 @@ def clean_helper():
|
|
|
493
494
|
with db_manager.get_driver().session() as session:
|
|
494
495
|
# Keep deleting orphaned nodes in batches until none are found
|
|
495
496
|
while True:
|
|
496
|
-
if is_falkordb:
|
|
497
|
-
# FalkorDB-compatible query using OPTIONAL MATCH
|
|
497
|
+
if is_falkordb or is_kuzu:
|
|
498
|
+
# FalkorDB / KùzuDB-compatible query using OPTIONAL MATCH
|
|
499
|
+
# (KùzuDB does not support the Neo4j `NOT EXISTS { MATCH ... }` subquery syntax)
|
|
498
500
|
query = """
|
|
499
501
|
MATCH (n)
|
|
500
502
|
WHERE NOT (n:Repository)
|
|
501
|
-
OPTIONAL MATCH
|
|
502
|
-
WITH n,
|
|
503
|
-
WHERE
|
|
503
|
+
OPTIONAL MATCH p = (n)-[*..10]-(r:Repository)
|
|
504
|
+
WITH n, p
|
|
505
|
+
WHERE p IS NULL
|
|
504
506
|
WITH n LIMIT $batch_size
|
|
505
507
|
DETACH DELETE n
|
|
506
508
|
RETURN count(n) as deleted
|
|
@@ -296,6 +296,12 @@ def run_command(command, console, shell=False, check=True, input_text=None):
|
|
|
296
296
|
Returns the completed process object on success, None on failure.
|
|
297
297
|
"""
|
|
298
298
|
cmd_str = command if isinstance(command, str) else ' '.join(command)
|
|
299
|
+
|
|
300
|
+
# Mask passwords from being printed out
|
|
301
|
+
if "set-initial-password" in cmd_str:
|
|
302
|
+
import re
|
|
303
|
+
cmd_str = re.sub(r'(set-initial-password\s+)(\S+)', r'\g<1>********', cmd_str)
|
|
304
|
+
|
|
299
305
|
console.print(f"[cyan]$ {cmd_str}[/cyan]")
|
|
300
306
|
try:
|
|
301
307
|
process = subprocess.run(
|
|
@@ -109,14 +109,21 @@ class KuzuDBManager:
|
|
|
109
109
|
("Parameter", "uid STRING, name STRING, path STRING, function_line_number INT64, PRIMARY KEY (uid)")
|
|
110
110
|
]
|
|
111
111
|
|
|
112
|
+
# rel_tables: list of (table_name, schema, use_group)
|
|
113
|
+
# use_group=True -> CREATE REL TABLE GROUP (for multi FROM..TO bindings)
|
|
114
|
+
# use_group=False -> CREATE REL TABLE (single binding)
|
|
112
115
|
rel_tables = [
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
("
|
|
118
|
-
("
|
|
119
|
-
("
|
|
116
|
+
# Note: in KùzuDB, some labels (e.g. `Macro`, `Property`, `Union`) are treated as reserved
|
|
117
|
+
# keywords in CREATE REL TABLE statements. We must escape them with backticks
|
|
118
|
+
# or the rel table creation will fail silently, leading to runtime
|
|
119
|
+
# "Binder exception: Table CONTAINS does not exist".
|
|
120
|
+
("CONTAINS", "FROM File TO Function, FROM File TO Class, FROM File TO Variable, FROM File TO Trait, FROM File TO Interface, FROM `Macro` TO `Macro`, FROM File TO `Macro`, FROM File TO Struct, FROM File TO Enum, FROM File TO `Union`, FROM File TO Annotation, FROM File TO Record, FROM File TO `Property`, FROM Repository TO Directory, FROM Directory TO Directory, FROM Directory TO File, FROM Repository TO File, FROM Class TO Function, FROM Function TO Function", True),
|
|
121
|
+
("CALLS", "FROM Function TO Function, FROM Function TO Class, FROM File TO Function, FROM File TO Class, FROM Class TO Function, FROM Class TO Class, line_number INT64, args STRING[], full_call_name STRING", True),
|
|
122
|
+
("IMPORTS", "FROM File TO Module, alias STRING, full_import_name STRING, imported_name STRING, line_number INT64", False),
|
|
123
|
+
("INHERITS", "FROM Class TO Class, FROM Record TO Record, FROM Interface TO Interface", True),
|
|
124
|
+
("HAS_PARAMETER", "FROM Function TO Parameter", False),
|
|
125
|
+
("INCLUDES", "FROM Class TO Module", False),
|
|
126
|
+
("IMPLEMENTS", "FROM Class TO Interface, FROM Struct TO Interface, FROM Record TO Interface", True)
|
|
120
127
|
]
|
|
121
128
|
|
|
122
129
|
for table_name, schema in node_tables:
|
|
@@ -127,10 +134,13 @@ class KuzuDBManager:
|
|
|
127
134
|
warning_logger(f"Kuzu Schema Node Error ({table_name}): {e}")
|
|
128
135
|
debug_log(f"Kuzu Schema Node Error ({table_name}): {e}")
|
|
129
136
|
|
|
130
|
-
for table_name, schema in rel_tables:
|
|
137
|
+
for table_name, schema, use_group in rel_tables:
|
|
131
138
|
try:
|
|
132
|
-
|
|
133
|
-
|
|
139
|
+
if use_group:
|
|
140
|
+
# KùzuDB requires CREATE REL TABLE GROUP for multi-binding relationships
|
|
141
|
+
self._conn.execute(f"CREATE REL TABLE GROUP `{table_name}`({schema})")
|
|
142
|
+
else:
|
|
143
|
+
self._conn.execute(f"CREATE REL TABLE `{table_name}`({schema})")
|
|
134
144
|
except Exception as e:
|
|
135
145
|
if "already exists" not in str(e).lower():
|
|
136
146
|
warning_logger(f"Kuzu Schema Rel Error ({table_name}): {e}")
|
codegraphcontext/server.py
CHANGED
|
@@ -36,6 +36,40 @@ from .tools.handlers import (
|
|
|
36
36
|
DEFAULT_EDIT_DISTANCE = 2
|
|
37
37
|
DEFAULT_FUZZY_SEARCH = False
|
|
38
38
|
|
|
39
|
+
WORKSPACE_PREFIX = "/workspace/"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _is_path_key(key: str) -> bool:
|
|
43
|
+
"""Check if a dict key represents a file path field.
|
|
44
|
+
|
|
45
|
+
Matches keys like 'path', 'clone_path', 'caller_file_path', and also
|
|
46
|
+
Cypher-aliased keys like 'f.path', 'n.caller_file_path'.
|
|
47
|
+
"""
|
|
48
|
+
# Strip Cypher alias prefix (e.g. "f.path" -> "path")
|
|
49
|
+
bare = key.rsplit(".", 1)[-1] if "." in key else key
|
|
50
|
+
return bare == "path" or bare.endswith("_path")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _strip_path_value(value):
|
|
54
|
+
"""Strip /workspace/ prefix from a single string value."""
|
|
55
|
+
if isinstance(value, str) and value.startswith(WORKSPACE_PREFIX):
|
|
56
|
+
return value[len(WORKSPACE_PREFIX):]
|
|
57
|
+
return value
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _strip_workspace_prefix(obj):
|
|
61
|
+
"""Recursively strip /workspace/ prefix from path values in results."""
|
|
62
|
+
if isinstance(obj, dict):
|
|
63
|
+
return {
|
|
64
|
+
k: _strip_path_value(v) if _is_path_key(k) else _strip_workspace_prefix(v)
|
|
65
|
+
for k, v in obj.items()
|
|
66
|
+
}
|
|
67
|
+
elif isinstance(obj, list):
|
|
68
|
+
return [_strip_workspace_prefix(item) for item in obj]
|
|
69
|
+
return obj
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
39
73
|
class MCPServer:
|
|
40
74
|
"""
|
|
41
75
|
The main MCP Server class.
|
|
@@ -254,7 +288,8 @@ class MCPServer:
|
|
|
254
288
|
tool_name = params.get('name')
|
|
255
289
|
args = params.get('arguments', {})
|
|
256
290
|
result = await self.handle_tool_call(tool_name, args)
|
|
257
|
-
|
|
291
|
+
result = _strip_workspace_prefix(result)
|
|
292
|
+
|
|
258
293
|
if "error" in result:
|
|
259
294
|
response = {
|
|
260
295
|
"jsonrpc": "2.0", "id": request_id,
|
|
@@ -5,6 +5,7 @@ from typing import Any, Dict, List, Literal, Optional
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
from ..core.database import DatabaseManager
|
|
8
|
+
from ..utils.path_ignore import cypher_path_not_under_ignore_dirs
|
|
8
9
|
|
|
9
10
|
logger = logging.getLogger(__name__)
|
|
10
11
|
|
|
@@ -539,9 +540,13 @@ class CodeFinder:
|
|
|
539
540
|
|
|
540
541
|
with self.driver.session() as session:
|
|
541
542
|
repo_filter = "AND func.path STARTS WITH $repo_path" if repo_path else ""
|
|
542
|
-
|
|
543
|
+
decorator_filter = "AND ALL(decorator_name IN $exclude_decorated_with WHERE NOT decorator_name IN func.decorators)" if exclude_decorated_with else ""
|
|
544
|
+
func_ignore = cypher_path_not_under_ignore_dirs("func.path")
|
|
545
|
+
caller_ignore = cypher_path_not_under_ignore_dirs("caller.path")
|
|
546
|
+
|
|
547
|
+
query = f"""
|
|
543
548
|
MATCH (func:Function)
|
|
544
|
-
WHERE func.is_dependency = false {repo_filter}
|
|
549
|
+
WHERE func.is_dependency = false {repo_filter} {func_ignore}
|
|
545
550
|
AND NOT func.name IN ['main', 'setup', 'run']
|
|
546
551
|
AND NOT (func.name STARTS WITH '__' AND func.name ENDS WITH '__')
|
|
547
552
|
AND NOT func.name STARTS WITH '_test'
|
|
@@ -550,10 +555,10 @@ class CodeFinder:
|
|
|
550
555
|
AND NOT toLower(func.name) CONTAINS 'application'
|
|
551
556
|
AND NOT toLower(func.name) CONTAINS 'entry'
|
|
552
557
|
AND NOT toLower(func.name) CONTAINS 'entrypoint'
|
|
553
|
-
|
|
558
|
+
{decorator_filter}
|
|
554
559
|
WITH func
|
|
555
560
|
OPTIONAL MATCH (caller:Function)-[:CALLS]->(func)
|
|
556
|
-
WHERE caller.is_dependency = false
|
|
561
|
+
WHERE caller.is_dependency = false {caller_ignore}
|
|
557
562
|
WITH func, count(caller) as caller_count
|
|
558
563
|
WHERE caller_count = 0
|
|
559
564
|
OPTIONAL MATCH (file:File)-[:CONTAINS]->(func)
|
|
@@ -566,7 +571,15 @@ class CodeFinder:
|
|
|
566
571
|
file.name as file_name
|
|
567
572
|
ORDER BY func.path, func.line_number
|
|
568
573
|
LIMIT 50
|
|
569
|
-
"""
|
|
574
|
+
"""
|
|
575
|
+
|
|
576
|
+
params = {}
|
|
577
|
+
if repo_path:
|
|
578
|
+
params["repo_path"] = repo_path
|
|
579
|
+
if exclude_decorated_with:
|
|
580
|
+
params["exclude_decorated_with"] = exclude_decorated_with
|
|
581
|
+
|
|
582
|
+
result = session.run(query, **params)
|
|
570
583
|
|
|
571
584
|
return {
|
|
572
585
|
"potentially_unused_functions": result.data(),
|
|
@@ -581,8 +594,8 @@ class CodeFinder:
|
|
|
581
594
|
# KùzuDB-compatible: Use anonymous end node and filter with WHERE
|
|
582
595
|
query = f"""
|
|
583
596
|
MATCH p = (f:Function)-[:CALLS*]->()
|
|
584
|
-
WITH f, p, nodes(p) as path_nodes
|
|
585
|
-
WITH f, path_nodes
|
|
597
|
+
WITH f as f, p as p, nodes(p) as path_nodes
|
|
598
|
+
WITH f as f, path_nodes as path_nodes, path_nodes[size(path_nodes)] as target
|
|
586
599
|
WHERE target.name = $function_name AND target.path = $path {repo_filter}
|
|
587
600
|
RETURN DISTINCT f.name AS caller_name, f.path AS caller_file_path, f.line_number AS caller_line_number, f.is_dependency AS caller_is_dependency
|
|
588
601
|
ORDER BY caller_is_dependency ASC, caller_file_path, caller_line_number
|
|
@@ -593,8 +606,8 @@ class CodeFinder:
|
|
|
593
606
|
# KùzuDB-compatible: Use anonymous end node and filter with WHERE
|
|
594
607
|
query = f"""
|
|
595
608
|
MATCH p = (f:Function)-[:CALLS*]->()
|
|
596
|
-
WITH f, p, nodes(p) as path_nodes
|
|
597
|
-
WITH f, path_nodes
|
|
609
|
+
WITH f as f, p as p, nodes(p) as path_nodes
|
|
610
|
+
WITH f as f, path_nodes as path_nodes, path_nodes[size(path_nodes)] as target
|
|
598
611
|
WHERE target.name = $function_name {repo_filter}
|
|
599
612
|
RETURN DISTINCT f.name AS caller_name, f.path AS caller_file_path, f.line_number AS caller_line_number, f.is_dependency AS caller_is_dependency
|
|
600
613
|
ORDER BY caller_is_dependency ASC, caller_file_path, caller_line_number
|
|
@@ -612,8 +625,8 @@ class CodeFinder:
|
|
|
612
625
|
query = f"""
|
|
613
626
|
MATCH (caller:Function {{name: $function_name, path: $path}})
|
|
614
627
|
MATCH p = (caller)-[:CALLS*]->()
|
|
615
|
-
WITH p, nodes(p) as path_nodes
|
|
616
|
-
WITH
|
|
628
|
+
WITH p as p, nodes(p) as path_nodes
|
|
629
|
+
WITH path_nodes[size(path_nodes)] as f
|
|
617
630
|
{repo_filter}
|
|
618
631
|
RETURN DISTINCT f.name AS callee_name, f.path AS callee_file_path, f.line_number AS callee_line_number, f.is_dependency AS callee_is_dependency
|
|
619
632
|
ORDER BY callee_is_dependency ASC, callee_file_path, callee_line_number
|
|
@@ -625,8 +638,8 @@ class CodeFinder:
|
|
|
625
638
|
query = f"""
|
|
626
639
|
MATCH (caller:Function {{name: $function_name}})
|
|
627
640
|
MATCH p = (caller)-[:CALLS*]->()
|
|
628
|
-
WITH p, nodes(p) as path_nodes
|
|
629
|
-
WITH
|
|
641
|
+
WITH p as p, nodes(p) as path_nodes
|
|
642
|
+
WITH path_nodes[size(path_nodes)] as f
|
|
630
643
|
{repo_filter}
|
|
631
644
|
RETURN DISTINCT f.name AS callee_name, f.path AS callee_file_path, f.line_number AS callee_line_number, f.is_dependency AS callee_is_dependency
|
|
632
645
|
ORDER BY callee_is_dependency ASC, callee_file_path, callee_line_number
|
|
@@ -647,24 +660,12 @@ class CodeFinder:
|
|
|
647
660
|
query = f"""
|
|
648
661
|
MATCH (start:Function {start_props}), (end_target:Function {end_props})
|
|
649
662
|
{repo_filter}
|
|
650
|
-
WITH start, end_target
|
|
663
|
+
WITH start as start, end_target as end_target
|
|
651
664
|
MATCH path = (start)-[:CALLS*1..{max_depth}]->()
|
|
652
|
-
WITH path, end_target, nodes(path) as func_nodes, relationships(path) as call_rels
|
|
653
|
-
WITH path, func_nodes, call_rels,
|
|
665
|
+
WITH path as path, end_target as end_target, nodes(path) as func_nodes, relationships(path) as call_rels
|
|
666
|
+
WITH path as path, func_nodes as func_nodes, call_rels as call_rels, end_target as end_target, func_nodes[size(func_nodes)] as path_end
|
|
654
667
|
WHERE path_end.name = end_target.name AND (end_target.path IS NULL OR path_end.path = end_target.path)
|
|
655
|
-
RETURN
|
|
656
|
-
[node in func_nodes | {{
|
|
657
|
-
name: node.name,
|
|
658
|
-
path: node.path,
|
|
659
|
-
line_number: node.line_number,
|
|
660
|
-
is_dependency: node.is_dependency
|
|
661
|
-
}}] as function_chain,
|
|
662
|
-
[rel in call_rels | {{
|
|
663
|
-
call_line: rel.line_number,
|
|
664
|
-
args: rel.args,
|
|
665
|
-
full_call_name: rel.full_call_name
|
|
666
|
-
}}] as call_details,
|
|
667
|
-
length(path) as chain_length
|
|
668
|
+
RETURN func_nodes as function_nodes, call_rels as call_nodes, size(call_rels) as chain_length
|
|
668
669
|
ORDER BY chain_length ASC
|
|
669
670
|
LIMIT 20
|
|
670
671
|
"""
|
|
@@ -679,7 +680,67 @@ class CodeFinder:
|
|
|
679
680
|
}
|
|
680
681
|
|
|
681
682
|
result = session.run(query, **params)
|
|
682
|
-
|
|
683
|
+
|
|
684
|
+
# Post-process Node/Rel objects into plain dicts so CLI output stays stable
|
|
685
|
+
rows = result.data()
|
|
686
|
+
transformed: List[Dict[str, Any]] = []
|
|
687
|
+
for row in rows:
|
|
688
|
+
func_nodes = row.get("function_nodes") or []
|
|
689
|
+
rel_nodes = row.get("call_nodes") or []
|
|
690
|
+
chain_len = row.get("chain_length", 0)
|
|
691
|
+
|
|
692
|
+
function_chain = []
|
|
693
|
+
for n in func_nodes:
|
|
694
|
+
# Depending on KùzuDB + driver wrapping, list elements can arrive
|
|
695
|
+
# either as Node/Rel objects or already-materialized dicts.
|
|
696
|
+
if isinstance(n, dict):
|
|
697
|
+
props = n
|
|
698
|
+
else:
|
|
699
|
+
props = None
|
|
700
|
+
try:
|
|
701
|
+
props = n.get_properties()
|
|
702
|
+
except Exception:
|
|
703
|
+
props = getattr(n, "properties", None)
|
|
704
|
+
if props is None:
|
|
705
|
+
props = {}
|
|
706
|
+
function_chain.append(
|
|
707
|
+
{
|
|
708
|
+
"name": props.get("name"),
|
|
709
|
+
"path": props.get("path"),
|
|
710
|
+
"line_number": props.get("line_number"),
|
|
711
|
+
"is_dependency": props.get("is_dependency"),
|
|
712
|
+
}
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
call_details = []
|
|
716
|
+
for r in rel_nodes:
|
|
717
|
+
if isinstance(r, dict):
|
|
718
|
+
props = r
|
|
719
|
+
else:
|
|
720
|
+
props = None
|
|
721
|
+
try:
|
|
722
|
+
props = r.get_properties()
|
|
723
|
+
except Exception:
|
|
724
|
+
props = getattr(r, "properties", None)
|
|
725
|
+
if props is None:
|
|
726
|
+
props = {}
|
|
727
|
+
call_details.append(
|
|
728
|
+
{
|
|
729
|
+
"call_line": props.get("line_number"),
|
|
730
|
+
"args": props.get("args"),
|
|
731
|
+
"full_call_name": props.get("full_call_name"),
|
|
732
|
+
}
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
transformed.append(
|
|
736
|
+
{
|
|
737
|
+
"function_chain": function_chain,
|
|
738
|
+
"call_details": call_details,
|
|
739
|
+
"chain_length": chain_len,
|
|
740
|
+
}
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
return transformed
|
|
683
744
|
|
|
684
745
|
def find_by_type(self, element_type: str, limit: int = 50) -> List[Dict]:
|
|
685
746
|
"""Find all elements of a specific type (Function, Class, File, Module)."""
|
|
@@ -988,9 +1049,10 @@ class CodeFinder:
|
|
|
988
1049
|
"""Find the most complex functions based on cyclomatic complexity."""
|
|
989
1050
|
with self.driver.session() as session:
|
|
990
1051
|
repo_filter = "AND f.path STARTS WITH $repo_path" if repo_path else ""
|
|
1052
|
+
path_ignore = cypher_path_not_under_ignore_dirs("f.path")
|
|
991
1053
|
query = f"""
|
|
992
1054
|
MATCH (f:Function)
|
|
993
|
-
WHERE f.cyclomatic_complexity IS NOT NULL AND f.is_dependency = false {repo_filter}
|
|
1055
|
+
WHERE f.cyclomatic_complexity IS NOT NULL AND f.is_dependency = false {repo_filter} {path_ignore}
|
|
994
1056
|
RETURN f.name as function_name, f.path as path, f.cyclomatic_complexity as complexity, f.line_number as line_number
|
|
995
1057
|
ORDER BY f.cyclomatic_complexity DESC
|
|
996
1058
|
LIMIT $limit
|
|
@@ -14,9 +14,22 @@ from ..utils.debug_log import debug_log, info_logger, error_logger, warning_logg
|
|
|
14
14
|
from tree_sitter import Language, Parser
|
|
15
15
|
from ..utils.tree_sitter_manager import get_tree_sitter_manager
|
|
16
16
|
from ..cli.config_manager import get_config_value
|
|
17
|
+
from ..utils.path_ignore import file_path_has_ignore_dir_segment
|
|
17
18
|
import fnmatch
|
|
18
19
|
|
|
19
20
|
DEFAULT_IGNORE_PATTERNS = [
|
|
21
|
+
# Vendor / env dirs (gitignore-style; complements IGNORE_DIRS during indexing)
|
|
22
|
+
"node_modules/",
|
|
23
|
+
"venv/",
|
|
24
|
+
".venv/",
|
|
25
|
+
"env/",
|
|
26
|
+
".env/",
|
|
27
|
+
"dist/",
|
|
28
|
+
"build/",
|
|
29
|
+
"target/",
|
|
30
|
+
"out/",
|
|
31
|
+
".git/",
|
|
32
|
+
"__pycache__/",
|
|
20
33
|
"*.png",
|
|
21
34
|
"*.jpg",
|
|
22
35
|
"*.jpeg",
|
|
@@ -210,6 +223,12 @@ class GraphBuilder:
|
|
|
210
223
|
except Exception as e:
|
|
211
224
|
warning_logger(f"Schema creation warning: {e}")
|
|
212
225
|
|
|
226
|
+
# Neo4j RANGE indexes have an ~8 kB key-size limit. Long C++ template
|
|
227
|
+
# function names (e.g. from llama.cpp) can exceed this, causing
|
|
228
|
+
# "Property value is too large to index" errors. We cap string properties
|
|
229
|
+
# at 4096 chars, which is comfortably under the 8 kB boundary.
|
|
230
|
+
_MAX_STR_LEN = 4096
|
|
231
|
+
|
|
213
232
|
@staticmethod
|
|
214
233
|
def _sanitize_props(props: Dict) -> Dict:
|
|
215
234
|
"""Return a copy of *props* with all values coerced to database-safe types.
|
|
@@ -220,9 +239,15 @@ class GraphBuilder:
|
|
|
220
239
|
parsers (e.g. C's ``detailed_args`` or Scala's tuple ``class_context``)
|
|
221
240
|
are serialized to a JSON string so the data is preserved rather than
|
|
222
241
|
being silently dropped.
|
|
242
|
+
|
|
243
|
+
Additionally, string values are truncated to _MAX_STR_LEN characters to
|
|
244
|
+
avoid Neo4j's RANGE-index 8 kB property-size limit (triggered by very
|
|
245
|
+
long C++ template-mangled function names).
|
|
223
246
|
"""
|
|
224
247
|
import json
|
|
225
248
|
|
|
249
|
+
MAX = GraphBuilder._MAX_STR_LEN
|
|
250
|
+
|
|
226
251
|
def _is_primitive(v):
|
|
227
252
|
return isinstance(v, (str, int, float, bool)) or v is None
|
|
228
253
|
|
|
@@ -230,15 +255,21 @@ class GraphBuilder:
|
|
|
230
255
|
return isinstance(v, list) and all(_is_primitive(item) for item in v)
|
|
231
256
|
|
|
232
257
|
def _coerce(v):
|
|
258
|
+
if isinstance(v, str):
|
|
259
|
+
# Truncate long strings to stay within Neo4j RANGE index limits
|
|
260
|
+
return v[:MAX] if len(v) > MAX else v
|
|
233
261
|
if _is_primitive(v):
|
|
234
262
|
return v
|
|
235
263
|
if _is_flat_list(v):
|
|
236
|
-
|
|
264
|
+
# Truncate any long strings in lists too
|
|
265
|
+
return [s[:MAX] if isinstance(s, str) and len(s) > MAX else s for s in v]
|
|
237
266
|
# Tuples, dicts, lists-of-dicts, nested structures → JSON string
|
|
238
267
|
try:
|
|
239
|
-
|
|
268
|
+
serialized = json.dumps(v, default=str)
|
|
269
|
+
return serialized[:MAX] if len(serialized) > MAX else serialized
|
|
240
270
|
except Exception:
|
|
241
|
-
|
|
271
|
+
s = str(v)
|
|
272
|
+
return s[:MAX] if len(s) > MAX else s
|
|
242
273
|
|
|
243
274
|
return {k: _coerce(v) for k, v in props.items()}
|
|
244
275
|
|
|
@@ -446,8 +477,20 @@ class GraphBuilder:
|
|
|
446
477
|
# before writing to the database to avoid runtime errors such as
|
|
447
478
|
# "Property values can only be of primitive types or arrays of
|
|
448
479
|
# primitive types" raised by FalkorDB / KùzuDB.
|
|
480
|
+
# _sanitize_props also truncates long strings to avoid Neo4j's
|
|
481
|
+
# 8 kB RANGE-index limit (seen with long C++ template names).
|
|
449
482
|
safe_props = self._sanitize_props(item)
|
|
450
|
-
|
|
483
|
+
try:
|
|
484
|
+
session.run(query, path=file_path_str, name=item['name'], line_number=item['line_number'], props=safe_props)
|
|
485
|
+
except Exception as node_err:
|
|
486
|
+
err_str = str(node_err)
|
|
487
|
+
if "too large to index" in err_str or "property size" in err_str.lower():
|
|
488
|
+
warning_logger(
|
|
489
|
+
f"Skipping {label} '{item['name']}' in {file_path_str}: "
|
|
490
|
+
f"property value too large for index (name length={len(item['name'])})"
|
|
491
|
+
)
|
|
492
|
+
else:
|
|
493
|
+
raise # Re-raise unexpected errors
|
|
451
494
|
|
|
452
495
|
if label == 'Function':
|
|
453
496
|
for arg_name in item.get('args', []):
|
|
@@ -517,30 +560,51 @@ class GraphBuilder:
|
|
|
517
560
|
# Ensure full_import_name is available in params for SET clause
|
|
518
561
|
params = imp.copy()
|
|
519
562
|
params['path'] = file_path_str
|
|
520
|
-
params['rel_props'] = rel_props
|
|
521
563
|
params['module_name'] = imp.get('name') # Use 'name' from imp as module name
|
|
522
564
|
|
|
565
|
+
# Sanitize scalar params but keep rel_props as a dict
|
|
566
|
+
# (FalkorDB SET r += $rel_props requires a map, not a string)
|
|
567
|
+
sanitized = self._sanitize_props(params)
|
|
568
|
+
sanitized['rel_props'] = rel_props
|
|
569
|
+
|
|
523
570
|
session.run(f"""
|
|
524
571
|
MATCH (f:File {{path: $path}})
|
|
525
572
|
MERGE (m:Module {{name: $module_name}})
|
|
526
573
|
{set_clause_str}
|
|
527
574
|
MERGE (f)-[r:IMPORTS]->(m)
|
|
528
575
|
SET r += $rel_props
|
|
529
|
-
""", **
|
|
576
|
+
""", **sanitized)
|
|
530
577
|
|
|
531
578
|
|
|
532
579
|
# Handle CONTAINS relationship between class to their children like variables
|
|
533
580
|
for func in file_data.get('functions', []):
|
|
534
581
|
if func.get('class_context'):
|
|
535
|
-
|
|
582
|
+
# Try same-file match first (Python, JS, etc.)
|
|
583
|
+
if not self._safe_run_create(session, """
|
|
536
584
|
MATCH (c:Class {name: $class_name, path: $path})
|
|
537
585
|
MATCH (fn:Function {name: $func_name, path: $path, line_number: $func_line})
|
|
538
586
|
MERGE (c)-[:CONTAINS]->(fn)
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
587
|
+
RETURN count(*) as created
|
|
588
|
+
""", {
|
|
589
|
+
'class_name': func['class_context'],
|
|
590
|
+
'path': file_path_str,
|
|
591
|
+
'func_name': func['name'],
|
|
592
|
+
'func_line': func['line_number']
|
|
593
|
+
}):
|
|
594
|
+
# Cross-file match for C/C++ where class is in .h and method in .cpp.
|
|
595
|
+
# Note: matches by class name only (no path constraint), so classes
|
|
596
|
+
# with identical names in different files could get false links.
|
|
597
|
+
self._safe_run_create(session, """
|
|
598
|
+
MATCH (c:Class {name: $class_name})
|
|
599
|
+
MATCH (fn:Function {name: $func_name, path: $path, line_number: $func_line})
|
|
600
|
+
MERGE (c)-[:CONTAINS]->(fn)
|
|
601
|
+
RETURN count(*) as created
|
|
602
|
+
""", {
|
|
603
|
+
'class_name': func['class_context'],
|
|
604
|
+
'path': file_path_str,
|
|
605
|
+
'func_name': func['name'],
|
|
606
|
+
'func_line': func['line_number']
|
|
607
|
+
})
|
|
544
608
|
|
|
545
609
|
# --- NEW: Class INCLUDES Module (Ruby mixins) ---
|
|
546
610
|
for inc in file_data.get('module_inclusions', []):
|
|
@@ -693,7 +757,7 @@ class GraphBuilder:
|
|
|
693
757
|
|
|
694
758
|
# KùzuDB workaround: Try Function->Function first, then other combinations
|
|
695
759
|
# This avoids polymorphic MERGE which KùzuDB doesn't support
|
|
696
|
-
call_params = {
|
|
760
|
+
call_params = self._sanitize_props({
|
|
697
761
|
'caller_name': caller_name,
|
|
698
762
|
'caller_file_path': caller_file_path,
|
|
699
763
|
'caller_line_number': caller_line_number,
|
|
@@ -702,8 +766,9 @@ class GraphBuilder:
|
|
|
702
766
|
'line_number': call['line_number'],
|
|
703
767
|
'args': call.get('args', []),
|
|
704
768
|
'full_call_name': call.get('full_name', called_name)
|
|
705
|
-
}
|
|
706
|
-
|
|
769
|
+
})
|
|
770
|
+
|
|
771
|
+
# Try Function caller -> Function callee
|
|
707
772
|
if not self._safe_run_create(session, """
|
|
708
773
|
OPTIONAL MATCH (caller:Function {name: $caller_name, path: $caller_file_path})
|
|
709
774
|
OPTIONAL MATCH (called:Function {name: $called_name, path: $called_file_path})
|
|
@@ -782,14 +847,15 @@ class GraphBuilder:
|
|
|
782
847
|
""", call_params)
|
|
783
848
|
else:
|
|
784
849
|
# File-level calls: Try Function first, then Class
|
|
785
|
-
call_params = {
|
|
850
|
+
call_params = self._sanitize_props({
|
|
786
851
|
'caller_file_path': caller_file_path,
|
|
787
852
|
'called_name': called_name,
|
|
788
853
|
'called_file_path': resolved_path,
|
|
789
854
|
'line_number': call['line_number'],
|
|
790
855
|
'args': call.get('args', []),
|
|
791
856
|
'full_call_name': call.get('full_name', called_name)
|
|
792
|
-
}
|
|
857
|
+
})
|
|
858
|
+
|
|
793
859
|
|
|
794
860
|
if not self._safe_run_create(session, """
|
|
795
861
|
OPTIONAL MATCH (caller:File {path: $caller_file_path})
|
|
@@ -1162,13 +1228,16 @@ class GraphBuilder:
|
|
|
1162
1228
|
|
|
1163
1229
|
# Step 4: Write nodes to graph using existing add_file_to_graph()
|
|
1164
1230
|
processed = 0
|
|
1231
|
+
index_root = path.resolve()
|
|
1165
1232
|
for abs_path_str, file_data in files_data.items():
|
|
1166
|
-
|
|
1233
|
+
file_path = Path(abs_path_str)
|
|
1234
|
+
if file_path.is_file() and file_path_has_ignore_dir_segment(file_path, index_root):
|
|
1235
|
+
continue
|
|
1236
|
+
file_data["repo_path"] = str(index_root)
|
|
1167
1237
|
if job_id:
|
|
1168
1238
|
self.job_manager.update_job(job_id, current_file=abs_path_str)
|
|
1169
1239
|
|
|
1170
1240
|
# Step 5: Tree-sitter supplement — add source text, complexity, imports and bases
|
|
1171
|
-
file_path = Path(abs_path_str)
|
|
1172
1241
|
ts_parser = self.get_parser(file_path.suffix)
|
|
1173
1242
|
if file_path.exists() and ts_parser:
|
|
1174
1243
|
try:
|
|
@@ -11,6 +11,7 @@ CPP_QUERIES = {
|
|
|
11
11
|
declarator: [
|
|
12
12
|
(identifier) @name
|
|
13
13
|
(field_identifier) @name
|
|
14
|
+
(qualified_identifier) @qualified_name
|
|
14
15
|
]
|
|
15
16
|
)
|
|
16
17
|
) @function_node
|
|
@@ -35,6 +36,7 @@ CPP_QUERIES = {
|
|
|
35
36
|
(field_expression
|
|
36
37
|
field: (field_identifier) @method_name
|
|
37
38
|
)
|
|
39
|
+
(qualified_identifier) @scoped_name
|
|
38
40
|
]
|
|
39
41
|
arguments: (argument_list) @args
|
|
40
42
|
)
|
|
@@ -158,36 +160,40 @@ class CppTreeSitterParser:
|
|
|
158
160
|
for match in execute_query(self.language, query_str, root_node):
|
|
159
161
|
capture_name = match[1]
|
|
160
162
|
node = match[0]
|
|
161
|
-
if capture_name
|
|
162
|
-
#
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
# Double check to prevent crashes if AST is different (e.g. pointers)
|
|
168
|
-
if func_node.type != 'function_definition':
|
|
169
|
-
# Fallback or try finding function_definition upwards
|
|
170
|
-
curr = node
|
|
171
|
-
while curr and curr.type != 'function_definition':
|
|
172
|
-
curr = curr.parent
|
|
173
|
-
func_node = curr
|
|
174
|
-
|
|
163
|
+
if capture_name in ('name', 'qualified_name'):
|
|
164
|
+
# Find the enclosing function_definition node
|
|
165
|
+
func_node = node.parent
|
|
166
|
+
while func_node and func_node.type != 'function_definition':
|
|
167
|
+
func_node = func_node.parent
|
|
168
|
+
|
|
175
169
|
if not func_node: continue
|
|
176
170
|
|
|
177
|
-
|
|
178
|
-
|
|
171
|
+
# For qualified names like QueueElement::execute, extract
|
|
172
|
+
# both the class context and the method name
|
|
173
|
+
raw_text = self._get_node_text(node)
|
|
174
|
+
class_context = None
|
|
175
|
+
if capture_name == 'qualified_name' and '::' in raw_text:
|
|
176
|
+
parts = raw_text.rsplit('::', 1)
|
|
177
|
+
class_context = parts[0]
|
|
178
|
+
name = parts[1]
|
|
179
|
+
else:
|
|
180
|
+
name = raw_text
|
|
181
|
+
|
|
179
182
|
params = self._extract_function_params(func_node)
|
|
180
|
-
|
|
183
|
+
|
|
181
184
|
func_data = {
|
|
182
185
|
"name": name,
|
|
183
186
|
"line_number": node.start_point[0] + 1,
|
|
184
187
|
"end_line": func_node.end_point[0] + 1,
|
|
185
188
|
"args": params,
|
|
186
189
|
}
|
|
187
|
-
|
|
190
|
+
|
|
191
|
+
if class_context:
|
|
192
|
+
func_data["class_context"] = class_context
|
|
193
|
+
|
|
188
194
|
if self.index_source:
|
|
189
195
|
func_data["source"] = self._get_node_text(func_node)
|
|
190
|
-
|
|
196
|
+
|
|
191
197
|
functions.append(func_data)
|
|
192
198
|
return functions
|
|
193
199
|
|
|
@@ -238,17 +244,52 @@ class CppTreeSitterParser:
|
|
|
238
244
|
if capture_name == 'name':
|
|
239
245
|
class_node = node.parent
|
|
240
246
|
name = self._get_node_text(node)
|
|
247
|
+
bases = self._extract_base_classes(class_node)
|
|
241
248
|
class_data = {
|
|
242
249
|
"name": name,
|
|
243
250
|
"line_number": node.start_point[0] + 1,
|
|
244
251
|
"end_line": class_node.end_point[0] + 1,
|
|
245
|
-
"bases":
|
|
252
|
+
"bases": bases,
|
|
246
253
|
}
|
|
247
254
|
if self.index_source:
|
|
248
255
|
class_data["source"] = self._get_node_text(class_node)
|
|
249
256
|
classes.append(class_data)
|
|
250
257
|
return classes
|
|
251
258
|
|
|
259
|
+
def _extract_base_classes(self, class_node) -> list[str]:
|
|
260
|
+
"""Extract base class names from a class_specifier node.
|
|
261
|
+
|
|
262
|
+
Handles: class Foo : public Bar, private Baz, virtual Base
|
|
263
|
+
Tree-sitter AST: class_specifier -> base_class_clause -> (base_class_specifier)+
|
|
264
|
+
Each base_class_specifier has an optional access_specifier and a type node.
|
|
265
|
+
"""
|
|
266
|
+
bases = []
|
|
267
|
+
for child in class_node.children:
|
|
268
|
+
if child.type == 'base_class_clause':
|
|
269
|
+
for base_spec in child.children:
|
|
270
|
+
if base_spec.type in ('base_class_specifier', 'type_identifier',
|
|
271
|
+
'qualified_identifier', 'template_type'):
|
|
272
|
+
# base_class_specifier contains access specifier + type
|
|
273
|
+
if base_spec.type == 'base_class_specifier':
|
|
274
|
+
# Find the type identifier within the base specifier
|
|
275
|
+
for sub in base_spec.children:
|
|
276
|
+
if sub.type in ('type_identifier', 'qualified_identifier',
|
|
277
|
+
'template_type'):
|
|
278
|
+
base_name = self._get_node_text(sub)
|
|
279
|
+
# Strip template args for graph matching
|
|
280
|
+
if '<' in base_name:
|
|
281
|
+
base_name = base_name[:base_name.index('<')].strip()
|
|
282
|
+
bases.append(base_name)
|
|
283
|
+
break
|
|
284
|
+
else:
|
|
285
|
+
# Direct type node (no access specifier)
|
|
286
|
+
base_name = self._get_node_text(base_spec)
|
|
287
|
+
if '<' in base_name:
|
|
288
|
+
base_name = base_name[:base_name.index('<')].strip()
|
|
289
|
+
bases.append(base_name)
|
|
290
|
+
break # Only one base_class_clause per class
|
|
291
|
+
return bases
|
|
292
|
+
|
|
252
293
|
def _find_imports(self, root_node):
|
|
253
294
|
imports = []
|
|
254
295
|
query_str = CPP_QUERIES['imports']
|
|
@@ -270,6 +311,7 @@ class CppTreeSitterParser:
|
|
|
270
311
|
query_str = CPP_QUERIES['enums']
|
|
271
312
|
for node, capture_name in execute_query(self.language, query_str, root_node):
|
|
272
313
|
if capture_name == 'name':
|
|
314
|
+
name = self._get_node_text(node)
|
|
273
315
|
enum_node = node.parent
|
|
274
316
|
enum_data = {
|
|
275
317
|
"name": name,
|
|
@@ -346,17 +388,17 @@ class CppTreeSitterParser:
|
|
|
346
388
|
if capture_name == 'name':
|
|
347
389
|
assignment_node = node.parent
|
|
348
390
|
lambda_node = assignment_node.child_by_field_name('value')
|
|
349
|
-
|
|
350
|
-
|
|
391
|
+
if lambda_node is None or lambda_node.type != 'lambda_expression':
|
|
392
|
+
continue
|
|
351
393
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
394
|
+
params_node = lambda_node.child_by_field_name('declarator')
|
|
395
|
+
if params_node:
|
|
396
|
+
params_node = params_node.child_by_field_name('parameters')
|
|
355
397
|
name = self._get_node_text(node)
|
|
356
398
|
params_node = lambda_node.child_by_field_name('parameters')
|
|
357
|
-
|
|
399
|
+
|
|
358
400
|
context, context_type, _ = self._get_parent_context(assignment_node)
|
|
359
|
-
class_context, _, _ = self._get_parent_context(assignment_node, types=('
|
|
401
|
+
class_context, _, _ = self._get_parent_context(assignment_node, types=('class_specifier',))
|
|
360
402
|
|
|
361
403
|
func_data = {
|
|
362
404
|
"name": name,
|
|
@@ -402,7 +444,7 @@ class CppTreeSitterParser:
|
|
|
402
444
|
type_text = self._get_node_text(type_node) if type_node else None
|
|
403
445
|
|
|
404
446
|
context, _, _ = self._get_parent_context(node)
|
|
405
|
-
class_context, _, _ = self._get_parent_context(node, types=('
|
|
447
|
+
class_context, _, _ = self._get_parent_context(node, types=('class_specifier',))
|
|
406
448
|
|
|
407
449
|
variable_data = {
|
|
408
450
|
"name": name,
|
|
@@ -417,17 +459,24 @@ class CppTreeSitterParser:
|
|
|
417
459
|
variables.append(variable_data)
|
|
418
460
|
return variables
|
|
419
461
|
|
|
420
|
-
def _get_parent_context(self, node, types=('function_definition', '
|
|
462
|
+
def _get_parent_context(self, node, types=('function_definition', 'class_specifier')):
|
|
421
463
|
curr = node.parent
|
|
422
464
|
while curr:
|
|
423
465
|
if curr.type in types:
|
|
424
466
|
if curr.type == 'function_definition':
|
|
425
|
-
# Traverse declarator to find name
|
|
467
|
+
# Traverse declarator to find name, handling qualified names
|
|
426
468
|
decl = curr.child_by_field_name('declarator')
|
|
427
469
|
while decl:
|
|
428
470
|
if decl.type == 'identifier':
|
|
429
471
|
return self._get_node_text(decl), curr.type, decl.start_point[0] + 1
|
|
430
|
-
|
|
472
|
+
if decl.type == 'qualified_identifier':
|
|
473
|
+
# e.g. QueueElement::execute — return just the method name
|
|
474
|
+
text = self._get_node_text(decl)
|
|
475
|
+
name = text.rsplit('::', 1)[-1] if '::' in text else text
|
|
476
|
+
return name, curr.type, decl.start_point[0] + 1
|
|
477
|
+
if decl.type == 'field_identifier':
|
|
478
|
+
return self._get_node_text(decl), curr.type, decl.start_point[0] + 1
|
|
479
|
+
|
|
431
480
|
child = decl.child_by_field_name('declarator')
|
|
432
481
|
if child:
|
|
433
482
|
decl = child
|
|
@@ -435,6 +484,9 @@ class CppTreeSitterParser:
|
|
|
435
484
|
break
|
|
436
485
|
# Fallback or if not found
|
|
437
486
|
return None, curr.type, curr.start_point[0] + 1
|
|
487
|
+
elif curr.type == 'class_specifier':
|
|
488
|
+
name_node = curr.child_by_field_name('name')
|
|
489
|
+
return self._get_node_text(name_node) if name_node else None, curr.type, curr.start_point[0] + 1
|
|
438
490
|
else:
|
|
439
491
|
name_node = curr.child_by_field_name('name')
|
|
440
492
|
return self._get_node_text(name_node) if name_node else None, curr.type, curr.start_point[0] + 1
|
|
@@ -445,49 +497,43 @@ class CppTreeSitterParser:
|
|
|
445
497
|
calls = []
|
|
446
498
|
query_str = CPP_QUERIES['calls']
|
|
447
499
|
for node, capture_name in execute_query(self.language, query_str, root_node):
|
|
448
|
-
if capture_name
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
if
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
args.append({
|
|
476
|
-
"type": param_type,
|
|
477
|
-
"name": param_name
|
|
478
|
-
})
|
|
479
|
-
|
|
500
|
+
if capture_name in ("function_name", "method_name", "scoped_name"):
|
|
501
|
+
raw_text = self._get_node_text(node)
|
|
502
|
+
|
|
503
|
+
# Determine the called function name and any object/class qualifier
|
|
504
|
+
inferred_obj_type = None
|
|
505
|
+
if capture_name == "scoped_name" and '::' in raw_text:
|
|
506
|
+
# e.g. QueueElement::setStatus or std::move
|
|
507
|
+
parts = raw_text.rsplit('::', 1)
|
|
508
|
+
func_name = parts[1]
|
|
509
|
+
inferred_obj_type = parts[0]
|
|
510
|
+
elif capture_name == "method_name":
|
|
511
|
+
# e.g. obj.method() or ptr->method() — field_expression
|
|
512
|
+
func_name = raw_text
|
|
513
|
+
# Try to get the object expression for type inference
|
|
514
|
+
field_expr = node.parent # field_expression node
|
|
515
|
+
if field_expr and field_expr.type == 'field_expression':
|
|
516
|
+
obj_node = field_expr.child_by_field_name('argument')
|
|
517
|
+
if obj_node:
|
|
518
|
+
obj_text = self._get_node_text(obj_node)
|
|
519
|
+
# Treat this->method like self.method for resolution
|
|
520
|
+
if obj_text == 'this':
|
|
521
|
+
inferred_obj_type = 'this'
|
|
522
|
+
else:
|
|
523
|
+
inferred_obj_type = obj_text
|
|
524
|
+
else:
|
|
525
|
+
func_name = raw_text
|
|
480
526
|
|
|
481
|
-
# Get context info (function
|
|
527
|
+
# Get context info (which function/class contains this call)
|
|
482
528
|
context_name, context_type, context_line = self._get_parent_context(node)
|
|
483
|
-
class_context, _, _ = self._get_parent_context(node, types=("
|
|
529
|
+
class_context, _, _ = self._get_parent_context(node, types=("class_specifier",))
|
|
484
530
|
|
|
485
531
|
call_data = {
|
|
486
532
|
"name": func_name,
|
|
487
|
-
"full_name":
|
|
533
|
+
"full_name": raw_text,
|
|
488
534
|
"line_number": node.start_point[0] + 1,
|
|
489
|
-
"args":
|
|
490
|
-
"inferred_obj_type":
|
|
535
|
+
"args": [],
|
|
536
|
+
"inferred_obj_type": inferred_obj_type,
|
|
491
537
|
"context": (context_name, context_type, context_line),
|
|
492
538
|
"class_context": class_context,
|
|
493
539
|
"lang": self.language_name,
|
|
@@ -506,8 +552,12 @@ class CppTreeSitterParser:
|
|
|
506
552
|
while curr:
|
|
507
553
|
if curr.type in ("function_definition", "function_declarator"):
|
|
508
554
|
id_node = curr.child_by_field_name("declarator")
|
|
509
|
-
if id_node
|
|
510
|
-
|
|
555
|
+
if id_node:
|
|
556
|
+
if id_node.type == "identifier":
|
|
557
|
+
name_parts.insert(0, id_node.text.decode("utf8"))
|
|
558
|
+
elif id_node.type == "qualified_identifier":
|
|
559
|
+
# Already contains Class::method — use it directly
|
|
560
|
+
return id_node.text.decode("utf8")
|
|
511
561
|
elif curr.type == "class_specifier":
|
|
512
562
|
name_node = curr.child_by_field_name("name")
|
|
513
563
|
if name_node:
|
|
@@ -532,8 +582,9 @@ def pre_scan_cpp(files: list[Path], parser_wrapper) -> dict:
|
|
|
532
582
|
(class_specifier name: (type_identifier) @name)
|
|
533
583
|
(struct_specifier name: (type_identifier) @name)
|
|
534
584
|
(function_definition declarator: (function_declarator declarator: (identifier) @name))
|
|
585
|
+
(function_definition declarator: (function_declarator declarator: (qualified_identifier) @qualified_name))
|
|
535
586
|
"""
|
|
536
|
-
|
|
587
|
+
|
|
537
588
|
|
|
538
589
|
for path in files:
|
|
539
590
|
try:
|
|
@@ -542,9 +593,23 @@ def pre_scan_cpp(files: list[Path], parser_wrapper) -> dict:
|
|
|
542
593
|
tree = parser_wrapper.parser.parse(source_bytes)
|
|
543
594
|
|
|
544
595
|
for node, capture_name in execute_query(parser_wrapper.language, query_str, tree.root_node):
|
|
596
|
+
resolved_path = str(path.resolve())
|
|
545
597
|
if capture_name == "name":
|
|
546
598
|
name = node.text.decode("utf-8")
|
|
547
|
-
imports_map.setdefault(name, [])
|
|
599
|
+
paths = imports_map.setdefault(name, [])
|
|
600
|
+
if resolved_path not in paths:
|
|
601
|
+
paths.append(resolved_path)
|
|
602
|
+
elif capture_name == "qualified_name":
|
|
603
|
+
# e.g. QueueElement::execute — index both the full name and the method name
|
|
604
|
+
full_name = node.text.decode("utf-8")
|
|
605
|
+
paths = imports_map.setdefault(full_name, [])
|
|
606
|
+
if resolved_path not in paths:
|
|
607
|
+
paths.append(resolved_path)
|
|
608
|
+
if '::' in full_name:
|
|
609
|
+
method_name = full_name.rsplit('::', 1)[1]
|
|
610
|
+
paths = imports_map.setdefault(method_name, [])
|
|
611
|
+
if resolved_path not in paths:
|
|
612
|
+
paths.append(resolved_path)
|
|
548
613
|
except Exception as e:
|
|
549
614
|
warning_logger(f"Tree-sitter pre-scan failed for {path}: {e}")
|
|
550
615
|
|
|
@@ -79,6 +79,7 @@ class GoTreeSitterParser:
|
|
|
79
79
|
self.language_name = generic_parser_wrapper.language_name
|
|
80
80
|
self.language = generic_parser_wrapper.language
|
|
81
81
|
self.parser = generic_parser_wrapper.parser
|
|
82
|
+
self.index_source = False
|
|
82
83
|
|
|
83
84
|
def _get_node_text(self, node) -> str:
|
|
84
85
|
return node.text.decode('utf-8')
|
|
@@ -99,17 +100,52 @@ class GoTreeSitterParser:
|
|
|
99
100
|
return None, None, None
|
|
100
101
|
|
|
101
102
|
def _calculate_complexity(self, node):
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
"""
|
|
104
|
+
Compute a simple cyclomatic complexity score from the Go AST.
|
|
105
|
+
|
|
106
|
+
We treat each decision/control-flow construct as +1:
|
|
107
|
+
- if/for/switch/select
|
|
108
|
+
- switch cases (case_clause) and select clauses (comm_clause)
|
|
109
|
+
- logical operators (&&, ||) inside binary_expression as +1
|
|
110
|
+
"""
|
|
111
|
+
# Note: tree-sitter-go node types differ from other languages and from
|
|
112
|
+
# what we'd typically expect (e.g. it's `switch`/`case` rather than
|
|
113
|
+
# `switch_statement`/`case_clause`).
|
|
114
|
+
decision_node_types = {
|
|
115
|
+
# top-level control constructs
|
|
116
|
+
"if_statement",
|
|
117
|
+
"for_statement",
|
|
118
|
+
"switch",
|
|
119
|
+
"select_statement",
|
|
120
|
+
"select",
|
|
121
|
+
# switch variants (tree-sitter grammar)
|
|
122
|
+
"expression_switch_statement",
|
|
123
|
+
"type_switch_statement",
|
|
124
|
+
# switch case branches
|
|
125
|
+
"case",
|
|
126
|
+
"expression_case",
|
|
127
|
+
"default_case",
|
|
128
|
+
# select communication branches
|
|
129
|
+
"communication_case",
|
|
106
130
|
}
|
|
131
|
+
|
|
107
132
|
count = 1
|
|
108
133
|
|
|
109
134
|
def traverse(n):
|
|
110
135
|
nonlocal count
|
|
111
|
-
if n.type in
|
|
136
|
+
if n.type in decision_node_types:
|
|
112
137
|
count += 1
|
|
138
|
+
# Still traverse children because nested constructs also contribute.
|
|
139
|
+
elif n.type == "binary_expression":
|
|
140
|
+
# Only count logical operators, not all binary expressions/comparisons.
|
|
141
|
+
# Example patterns: "a && b", "a || b".
|
|
142
|
+
try:
|
|
143
|
+
txt = self._get_node_text(n)
|
|
144
|
+
except Exception:
|
|
145
|
+
txt = ""
|
|
146
|
+
if "&&" in txt or "||" in txt:
|
|
147
|
+
count += 1
|
|
148
|
+
|
|
113
149
|
for child in n.children:
|
|
114
150
|
traverse(child)
|
|
115
151
|
|
|
@@ -240,6 +276,7 @@ class GoTreeSitterParser:
|
|
|
240
276
|
"decorators": [],
|
|
241
277
|
"lang": self.language_name,
|
|
242
278
|
"is_dependency": False,
|
|
279
|
+
"cyclomatic_complexity": self._calculate_complexity(func_node),
|
|
243
280
|
}
|
|
244
281
|
|
|
245
282
|
if self.index_source:
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Path segment helpers aligned with IGNORE_DIRS indexing rules.
|
|
3
|
+
|
|
4
|
+
`is_dependency` on graph nodes marks separately indexed packages, not vendored
|
|
5
|
+
dirs inside a repo (e.g. node_modules). Analysis queries must also exclude
|
|
6
|
+
those paths when IGNORE_DIRS would skip them during indexing.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import List, Optional
|
|
11
|
+
|
|
12
|
+
from ..cli.config_manager import DEFAULT_CONFIG, get_config_value
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def parse_ignore_dir_names() -> List[str]:
|
|
16
|
+
"""Directory names from IGNORE_DIRS, falling back to defaults when unset or empty."""
|
|
17
|
+
raw = (get_config_value("IGNORE_DIRS") or "").strip()
|
|
18
|
+
if not raw:
|
|
19
|
+
raw = DEFAULT_CONFIG["IGNORE_DIRS"]
|
|
20
|
+
return [d.strip() for d in raw.split(",") if d.strip()]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def cypher_path_not_under_ignore_dirs(path_var: str, ignore_dir_names: Optional[List[str]] = None) -> str:
|
|
24
|
+
"""
|
|
25
|
+
Returns a Cypher fragment that keeps only paths not under any IGNORE_DIRS segment
|
|
26
|
+
(Unix `/name/` or Windows `\\name\\`).
|
|
27
|
+
"""
|
|
28
|
+
names = ignore_dir_names if ignore_dir_names is not None else parse_ignore_dir_names()
|
|
29
|
+
if not names:
|
|
30
|
+
return ""
|
|
31
|
+
parts: List[str] = []
|
|
32
|
+
for d in names:
|
|
33
|
+
esc = d.replace("\\", "\\\\").replace("'", "\\'")
|
|
34
|
+
parts.append(f"{path_var} CONTAINS '/{esc}/'")
|
|
35
|
+
parts.append(f"{path_var} CONTAINS '\\\\{esc}\\\\'")
|
|
36
|
+
return " AND NOT (" + " OR ".join(parts) + ")"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def file_path_has_ignore_dir_segment(file_path: Path, index_root: Path) -> bool:
|
|
40
|
+
"""
|
|
41
|
+
True if any directory segment of file_path relative to index_root matches
|
|
42
|
+
IGNORE_DIRS (same logic as Tree-sitter indexing).
|
|
43
|
+
"""
|
|
44
|
+
ignore_dirs_str = (get_config_value("IGNORE_DIRS") or "").strip()
|
|
45
|
+
if not ignore_dirs_str:
|
|
46
|
+
ignore_dirs_str = DEFAULT_CONFIG["IGNORE_DIRS"]
|
|
47
|
+
ignore_dirs = {d.strip().lower() for d in ignore_dirs_str.split(",") if d.strip()}
|
|
48
|
+
if not ignore_dirs:
|
|
49
|
+
return False
|
|
50
|
+
try:
|
|
51
|
+
rel = file_path.resolve().relative_to(index_root.resolve())
|
|
52
|
+
parts = {p.lower() for p in rel.parent.parts}
|
|
53
|
+
return bool(parts.intersection(ignore_dirs))
|
|
54
|
+
except ValueError:
|
|
55
|
+
return False
|
|
@@ -60,6 +60,11 @@ LANGUAGE_ALIASES = {
|
|
|
60
60
|
"exs": "elixir",
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
# Canonical names that differ from tree-sitter-language-pack names
|
|
64
|
+
LANGUAGE_PACK_NAMES = {
|
|
65
|
+
"c_sharp": "csharp",
|
|
66
|
+
}
|
|
67
|
+
|
|
63
68
|
|
|
64
69
|
class TreeSitterManager:
|
|
65
70
|
"""
|
|
@@ -128,15 +133,9 @@ class TreeSitterManager:
|
|
|
128
133
|
return self._language_cache[canonical_name]
|
|
129
134
|
|
|
130
135
|
try:
|
|
131
|
-
#
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
# tree_sitter_c_sharp.language() returns a PyCapsule, wrap it in Language
|
|
135
|
-
capsule = tree_sitter_c_sharp.language()
|
|
136
|
-
language = Language(capsule)
|
|
137
|
-
else:
|
|
138
|
-
# Load the language from tree-sitter-language-pack
|
|
139
|
-
language = get_language(canonical_name)
|
|
136
|
+
# Map canonical name to language-pack name where they differ
|
|
137
|
+
pack_name = LANGUAGE_PACK_NAMES.get(canonical_name, canonical_name)
|
|
138
|
+
language = get_language(pack_name)
|
|
140
139
|
|
|
141
140
|
self._language_cache[canonical_name] = language
|
|
142
141
|
return language
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codegraphcontext
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.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
|
|
@@ -41,7 +41,7 @@ Requires-Dist: watchdog>=3.0.0
|
|
|
41
41
|
Requires-Dist: stdlibs>=2023.11.18
|
|
42
42
|
Requires-Dist: typer[all]>=0.9.0
|
|
43
43
|
Requires-Dist: rich>=13.7.0
|
|
44
|
-
Requires-Dist: inquirerpy>=0.3.
|
|
44
|
+
Requires-Dist: inquirerpy>=0.3.4
|
|
45
45
|
Requires-Dist: python-dotenv>=1.0.0
|
|
46
46
|
Requires-Dist: tree-sitter>=0.21.0
|
|
47
47
|
Requires-Dist: tree-sitter-language-pack>=0.6.0
|
|
@@ -51,6 +51,7 @@ Requires-Dist: nbformat
|
|
|
51
51
|
Requires-Dist: nbconvert>=7.16.6
|
|
52
52
|
Requires-Dist: pathspec>=0.12.1
|
|
53
53
|
Requires-Dist: falkordb>=0.1.0
|
|
54
|
+
Requires-Dist: requests>=2.28.0
|
|
54
55
|
Requires-Dist: falkordblite>=0.1.0; sys_platform != "win32" and python_version >= "3.12"
|
|
55
56
|
Requires-Dist: fastapi>=0.100.0
|
|
56
57
|
Requires-Dist: uvicorn>=0.22.0
|
|
@@ -162,7 +163,7 @@ A powerful **MCP server** and **CLI toolkit** that indexes local code into a gra
|
|
|
162
163
|
---
|
|
163
164
|
|
|
164
165
|
## Project Details
|
|
165
|
-
- **Version:** 0.3.
|
|
166
|
+
- **Version:** 0.3.8
|
|
166
167
|
- **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
|
|
167
168
|
- **License:** MIT License (See [LICENSE](LICENSE) for details)
|
|
168
169
|
- **Website:** [CodeGraphContext](http://codegraphcontext.vercel.app/)
|
|
@@ -249,7 +250,7 @@ _If you’re using CodeGraphContext in your project, feel free to open a PR and
|
|
|
249
250
|
- `stdlibs>=2023.11.18`
|
|
250
251
|
- `typer[all]>=0.9.0`
|
|
251
252
|
- `rich>=13.7.0`
|
|
252
|
-
- `inquirerpy>=0.3.
|
|
253
|
+
- `inquirerpy>=0.3.7`
|
|
253
254
|
- `python-dotenv>=1.0.0`
|
|
254
255
|
- `tree-sitter>=0.21.0`
|
|
255
256
|
- `tree-sitter-language-pack>=0.6.0`
|
|
@@ -271,7 +272,7 @@ pip install codegraphcontext
|
|
|
271
272
|
|
|
272
273
|
### If 'cgc' command isn't found, run our one-line fix:
|
|
273
274
|
```
|
|
274
|
-
curl -sSL
|
|
275
|
+
curl -sSL https://raw.githubusercontent.com/CodeGraphContext/CodeGraphContext/main/scripts/post_install_fix.sh | bash
|
|
275
276
|
```
|
|
276
277
|
|
|
277
278
|
---
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
codegraphcontext/__init__.py,sha256=mFY5raGZpG90gG6E8NH0R85mDl3Ikak2HQHOiwCXl-I,77
|
|
2
2
|
codegraphcontext/__main__.py,sha256=21QjL_lfd6cy-clJYPBg6xKb_IuNDcvvWdaAJEvZ-HQ,99
|
|
3
3
|
codegraphcontext/prompts.py,sha256=E5P55paM0oHfBcNVfxkxpXRGZnya1kr_mKDg9i49FwM,6755
|
|
4
|
-
codegraphcontext/server.py,sha256=
|
|
4
|
+
codegraphcontext/server.py,sha256=Rz6Exa99INSaHKdeIBDQwwMoDOFJY8vhuXwEAdqs6qg,13917
|
|
5
5
|
codegraphcontext/tool_definitions.py,sha256=_0ahezQSURX_ewWydT2sFcvC6OFGdMoPA7KwgWNVcks,11482
|
|
6
6
|
codegraphcontext/cli/__init__.py,sha256=v6CMDVKM5d_sXn3S5nZNf0phXn0IdrnhLazUoen9k9w,38
|
|
7
|
-
codegraphcontext/cli/cli_helpers.py,sha256=
|
|
7
|
+
codegraphcontext/cli/cli_helpers.py,sha256=sWm9TLWy8GahX3htlonz5wO54DUV_OqkkvT9ohNVR1M,29777
|
|
8
8
|
codegraphcontext/cli/config_manager.py,sha256=MK7GMGZ4hd8-ZOShXBd_KDtNqHQ3ngdbef5KM_naPrQ,15899
|
|
9
9
|
codegraphcontext/cli/main.py,sha256=89jWaO8Gr3nRdLQtfwF434wpU77d5bJwt016f_lLnGg,86486
|
|
10
10
|
codegraphcontext/cli/registry_commands.py,sha256=30rJm4SeS0n1jax4JVuhuv4zYLzyMmlHcCSnsDDhgc8,19964
|
|
11
11
|
codegraphcontext/cli/setup_macos.py,sha256=Xjlv_9jk9qv8Gh7stpH1pvlalzC0Fg176y7jc5G1zh0,3575
|
|
12
|
-
codegraphcontext/cli/setup_wizard.py,sha256=
|
|
12
|
+
codegraphcontext/cli/setup_wizard.py,sha256=UptvbVfHPudIkoO8idWpEuomweIBsXEm9yNw21PXFvc,41598
|
|
13
13
|
codegraphcontext/cli/visualizer.py,sha256=YgqXA-o1l3qRABat_s-wT0gTM_rmHmOhpUtxS7Rhcfk,1861
|
|
14
14
|
codegraphcontext/core/__init__.py,sha256=r-dC2b8hn5e4ZBVfWnCcBYMTQVcayxG5-eA5Do6uSM4,7278
|
|
15
15
|
codegraphcontext/core/bundle_registry.py,sha256=kLvTLEl99QwfyAw8a7-4hqconLCtS64G9ECPVdfVBsM,7476
|
|
@@ -17,14 +17,14 @@ codegraphcontext/core/cgc_bundle.py,sha256=lxaImBnEJMiscrGssLTgLaeEa7tY33xztjbxO
|
|
|
17
17
|
codegraphcontext/core/database.py,sha256=2UL8AyE6vX5mVHg1zcD74TYEREmddhKzN7Bjy2THQwM,11435
|
|
18
18
|
codegraphcontext/core/database_falkordb.py,sha256=IKRcUwqdCR92ymX9rNAQSs49RWPxug_78utwePEcliQ,18842
|
|
19
19
|
codegraphcontext/core/database_falkordb_remote.py,sha256=aMiuJTB_-2rBUpbAmX8ElZiZrYZbOXK-On3ncgMCyhY,7118
|
|
20
|
-
codegraphcontext/core/database_kuzu.py,sha256=
|
|
20
|
+
codegraphcontext/core/database_kuzu.py,sha256=3SkKI-BtsMfSIbt-ZIavB113xtht1LszPGMdZk4Zpbs,23578
|
|
21
21
|
codegraphcontext/core/falkor_worker.py,sha256=bMcTOac6e8IzSPjCd-YulGVh-S7QsNtXs2c8hx6M6gk,4958
|
|
22
22
|
codegraphcontext/core/jobs.py,sha256=d6v_IERdEcDlIsz3CW3p0QMp3N-6dgQICs3FZRPgPgU,4809
|
|
23
23
|
codegraphcontext/core/watcher.py,sha256=42AWDv7LUE5v_HlY_Rvvlk8deDdlUBvM3hI-DMDWAOk,9813
|
|
24
24
|
codegraphcontext/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
25
|
codegraphcontext/tools/advanced_language_query_tool.py,sha256=APtC-KSkXXSrVLwiw3kgi6Q-xaqXl6skLWHPyuNh358,3670
|
|
26
|
-
codegraphcontext/tools/code_finder.py,sha256=
|
|
27
|
-
codegraphcontext/tools/graph_builder.py,sha256=
|
|
26
|
+
codegraphcontext/tools/code_finder.py,sha256=RwhS2uelcRmkL8TsQze0D2aXRUWEmKbBTVzhyXkq5ds,58271
|
|
27
|
+
codegraphcontext/tools/graph_builder.py,sha256=5cudzFiymG1J1SQ-JWnl-AdeMI3CvaHnl9ZaNGdtuQ4,84927
|
|
28
28
|
codegraphcontext/tools/package_resolver.py,sha256=KtbdMReTezszjdsqYniL-Xb-QUsrAJWtf1NSiyIPkLI,18704
|
|
29
29
|
codegraphcontext/tools/scip_indexer.py,sha256=gxe5-f090wonHEWYDT1CV6b3SpSTQtwng6A1SfPX_LA,19970
|
|
30
30
|
codegraphcontext/tools/scip_pb2.py,sha256=dwOMNKlu6VyLq5h8kTPZRDqxrwfVL8yw7I7ziokFG48,98229
|
|
@@ -35,11 +35,11 @@ codegraphcontext/tools/handlers/management_handlers.py,sha256=jCosISjq4QD7oBa8Za
|
|
|
35
35
|
codegraphcontext/tools/handlers/query_handlers.py,sha256=Go_qhx2XXcymlEiU8JC16PHTyrLQo59drauAPytUQZA,3508
|
|
36
36
|
codegraphcontext/tools/handlers/watcher_handlers.py,sha256=9hBkMdI8mwQyXj77WvgyUWCtNeItV3DiCLnmNI7Xjp8,3623
|
|
37
37
|
codegraphcontext/tools/languages/c.py,sha256=mrXQWEvMtzLVElATYNZ7VThNk1ZNZdw2UV8i6r2y2y4,22291
|
|
38
|
-
codegraphcontext/tools/languages/cpp.py,sha256=
|
|
38
|
+
codegraphcontext/tools/languages/cpp.py,sha256=aXBF_MX43PpRTr5U7UBcn5mZvP6fjP4Q8g21JuCho38,25761
|
|
39
39
|
codegraphcontext/tools/languages/csharp.py,sha256=0ZIpV531i8yhz4vktaKkzbHDg-TCxgSZPC69SBkmfYU,22594
|
|
40
40
|
codegraphcontext/tools/languages/dart.py,sha256=RXs4OhyGuFM7l4WH42dSfmgt1HTqwfobx0hFqnQ2rg0,15030
|
|
41
41
|
codegraphcontext/tools/languages/elixir.py,sha256=mFsMvi_5ZlCsrB5QQ7Kklj94tLo3D6aFU7ukuhGmGSI,18815
|
|
42
|
-
codegraphcontext/tools/languages/go.py,sha256=
|
|
42
|
+
codegraphcontext/tools/languages/go.py,sha256=cBfeM3Qt9W73KKb39VPhS6LlcWLdVJpvrAnnUeSiH4E,20456
|
|
43
43
|
codegraphcontext/tools/languages/haskell.py,sha256=ewXkp4HipC2yMkfhUwobjk5RXTlSnXd4tqzoja4W6ck,24008
|
|
44
44
|
codegraphcontext/tools/languages/java.py,sha256=5lu8qY8zG2Xl2sFTXLx-pZLU3a1CDbvMnwxcwTuyQNs,20707
|
|
45
45
|
codegraphcontext/tools/languages/javascript.py,sha256=W7PaY2r7KKDQ2Mw1cp20b9ZI_Y5JQB5FYL6xeYDxl1I,24291
|
|
@@ -69,12 +69,13 @@ codegraphcontext/tools/query_tool_languages/scala_toolkit.py,sha256=dbSMAUNZOBS7
|
|
|
69
69
|
codegraphcontext/tools/query_tool_languages/swift_toolkit.py,sha256=qg5VHM4-xkAiSOt7cbcYymLg9G-uAKlzgKnIrRSEgJU,207
|
|
70
70
|
codegraphcontext/tools/query_tool_languages/typescript_toolkit.py,sha256=3S4hpmOpEyaHoTP5ZdElFNkVyMZZDL_Gl4-DF0We1Mg,211
|
|
71
71
|
codegraphcontext/utils/debug_log.py,sha256=Qg7jwyeg7x2h3Ur_2S34bdMCkHdlk_ngHfPwa97A9vE,2836
|
|
72
|
-
codegraphcontext/utils/
|
|
72
|
+
codegraphcontext/utils/path_ignore.py,sha256=WPIcDmyAi6eL9692rPBQrUZunn5gHRb57nzNRHSIq28,2124
|
|
73
|
+
codegraphcontext/utils/tree_sitter_manager.py,sha256=qYFAfDcMRtJ712bf8IMEZlBKsnY3-mbreCqJEOnmiuM,8975
|
|
73
74
|
codegraphcontext/utils/visualize_graph.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
74
75
|
codegraphcontext/viz/server.py,sha256=Yu4fwMC2FsE6KUt7oYNlcE3GpCrblstkwUIVaGXTPYI,12767
|
|
75
|
-
codegraphcontext-0.3.
|
|
76
|
-
codegraphcontext-0.3.
|
|
77
|
-
codegraphcontext-0.3.
|
|
78
|
-
codegraphcontext-0.3.
|
|
79
|
-
codegraphcontext-0.3.
|
|
80
|
-
codegraphcontext-0.3.
|
|
76
|
+
codegraphcontext-0.3.8.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
|
|
77
|
+
codegraphcontext-0.3.8.dist-info/METADATA,sha256=gH5p3c4aTnpthV2hOeqrK2013T5ASgTH5eJ_WvB2v2g,21613
|
|
78
|
+
codegraphcontext-0.3.8.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
79
|
+
codegraphcontext-0.3.8.dist-info/entry_points.txt,sha256=LCxWCWMshdvYGoHBPuQZ8C-e4CiNSHCLXofrNSGHkoE,103
|
|
80
|
+
codegraphcontext-0.3.8.dist-info/top_level.txt,sha256=CBgc6LAPZIO5FS0nSYYkylDifHsZTIqw3Gf5UwDxeGI,17
|
|
81
|
+
codegraphcontext-0.3.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|