codegraphcontext-rust 0.5.5__cp310-cp310-win_amd64.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/__init__.py +2 -0
- codegraphcontext/__main__.py +5 -0
- codegraphcontext/_cgc_rust.cp310-win_amd64.pyd +0 -0
- codegraphcontext/cli/__init__.py +1 -0
- codegraphcontext/cli/cli_helpers.py +774 -0
- codegraphcontext/cli/config_manager.py +1051 -0
- codegraphcontext/cli/main.py +2386 -0
- codegraphcontext/cli/registry_commands.py +404 -0
- codegraphcontext/cli/setup_macos.py +93 -0
- codegraphcontext/cli/setup_wizard.py +991 -0
- codegraphcontext/cli/visualizer.py +50 -0
- codegraphcontext/core/__init__.py +165 -0
- codegraphcontext/core/bundle_registry.py +181 -0
- codegraphcontext/core/cgc_bundle.py +858 -0
- codegraphcontext/core/cgcignore.py +118 -0
- codegraphcontext/core/database.py +273 -0
- codegraphcontext/core/database_falkordb.py +480 -0
- codegraphcontext/core/database_falkordb_remote.py +199 -0
- codegraphcontext/core/database_kuzu.py +636 -0
- codegraphcontext/core/falkor_worker.py +132 -0
- codegraphcontext/core/jobs.py +132 -0
- codegraphcontext/core/watcher.py +260 -0
- codegraphcontext/prompts.py +125 -0
- codegraphcontext/server.py +471 -0
- codegraphcontext/tool_definitions.py +220 -0
- codegraphcontext/tools/__init__.py +0 -0
- codegraphcontext/tools/advanced_language_query_tool.py +103 -0
- codegraphcontext/tools/code_finder.py +1160 -0
- codegraphcontext/tools/graph_builder.py +337 -0
- codegraphcontext/tools/handlers/analysis_handlers.py +114 -0
- codegraphcontext/tools/handlers/indexing_handlers.py +116 -0
- codegraphcontext/tools/handlers/management_handlers.py +339 -0
- codegraphcontext/tools/handlers/query_handlers.py +83 -0
- codegraphcontext/tools/handlers/watcher_handlers.py +83 -0
- codegraphcontext/tools/indexing/__init__.py +1 -0
- codegraphcontext/tools/indexing/constants.py +25 -0
- codegraphcontext/tools/indexing/discovery.py +64 -0
- codegraphcontext/tools/indexing/engine.py +109 -0
- codegraphcontext/tools/indexing/persistence/__init__.py +3 -0
- codegraphcontext/tools/indexing/persistence/parquet_loader.py +643 -0
- codegraphcontext/tools/indexing/persistence/writer.py +1114 -0
- codegraphcontext/tools/indexing/pipeline.py +171 -0
- codegraphcontext/tools/indexing/pre_scan.py +105 -0
- codegraphcontext/tools/indexing/resolution/__init__.py +8 -0
- codegraphcontext/tools/indexing/resolution/calls.py +253 -0
- codegraphcontext/tools/indexing/resolution/inheritance.py +91 -0
- codegraphcontext/tools/indexing/sanitize.py +41 -0
- codegraphcontext/tools/indexing/schema.py +79 -0
- codegraphcontext/tools/indexing/schema_contract.py +44 -0
- codegraphcontext/tools/indexing/scip_pipeline.py +140 -0
- codegraphcontext/tools/languages/c.py +563 -0
- codegraphcontext/tools/languages/cpp.py +616 -0
- codegraphcontext/tools/languages/csharp.py +551 -0
- codegraphcontext/tools/languages/dart.py +378 -0
- codegraphcontext/tools/languages/elixir.py +460 -0
- codegraphcontext/tools/languages/go.py +499 -0
- codegraphcontext/tools/languages/haskell.py +426 -0
- codegraphcontext/tools/languages/java.py +500 -0
- codegraphcontext/tools/languages/javascript.py +589 -0
- codegraphcontext/tools/languages/kotlin.py +640 -0
- codegraphcontext/tools/languages/perl.py +260 -0
- codegraphcontext/tools/languages/php.py +519 -0
- codegraphcontext/tools/languages/python.py +576 -0
- codegraphcontext/tools/languages/ruby.py +537 -0
- codegraphcontext/tools/languages/rust.py +295 -0
- codegraphcontext/tools/languages/scala.py +519 -0
- codegraphcontext/tools/languages/swift.py +491 -0
- codegraphcontext/tools/languages/typescript.py +575 -0
- codegraphcontext/tools/languages/typescriptjsx.py +151 -0
- codegraphcontext/tools/package_resolver.py +472 -0
- codegraphcontext/tools/query_tool_languages/c_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/cpp_toolkit.py +87 -0
- codegraphcontext/tools/query_tool_languages/csharp_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/dart_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/go_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/haskell_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/java_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/javascript_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/perl_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/python_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/ruby_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/rust_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/scala_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/swift_toolkit.py +5 -0
- codegraphcontext/tools/query_tool_languages/typescript_toolkit.py +5 -0
- codegraphcontext/tools/scip_indexer.py +467 -0
- codegraphcontext/tools/scip_pb2.py +2455 -0
- codegraphcontext/tools/system.py +133 -0
- codegraphcontext/tools/tree_sitter_parser.py +103 -0
- codegraphcontext/utils/debug_log.py +90 -0
- codegraphcontext/utils/path_ignore.py +55 -0
- codegraphcontext/utils/repo_path.py +27 -0
- codegraphcontext/utils/tree_sitter_manager.py +285 -0
- codegraphcontext/utils/visualize_graph.py +0 -0
- codegraphcontext/viz/server.py +282 -0
- codegraphcontext_rust-0.5.5.dist-info/METADATA +537 -0
- codegraphcontext_rust-0.5.5.dist-info/RECORD +101 -0
- codegraphcontext_rust-0.5.5.dist-info/WHEEL +4 -0
- codegraphcontext_rust-0.5.5.dist-info/entry_points.txt +3 -0
- codegraphcontext_rust-0.5.5.dist-info/licenses/LICENSE +21 -0
- codegraphcontext_rust-0.5.5.dist-info/sboms/cgc-python.cyclonedx.json +2333 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# src/codegraphcontext/tools/system.py
|
|
2
|
+
import logging
|
|
3
|
+
from dataclasses import asdict
|
|
4
|
+
from typing import Any, Dict
|
|
5
|
+
from datetime import datetime, timedelta
|
|
6
|
+
|
|
7
|
+
from neo4j.exceptions import CypherSyntaxError
|
|
8
|
+
|
|
9
|
+
from ..core.database import DatabaseManager
|
|
10
|
+
from ..core.jobs import JobManager, JobStatus
|
|
11
|
+
from ..utils.debug_log import debug_log
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SystemTools:
|
|
17
|
+
"""Handles system-level tools like job management and direct DB queries."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, db_manager: DatabaseManager, job_manager: JobManager):
|
|
20
|
+
self.db_manager = db_manager
|
|
21
|
+
self.job_manager = job_manager
|
|
22
|
+
|
|
23
|
+
def check_job_status_tool(self, job_id: str) -> Dict[str, Any]:
|
|
24
|
+
"""Tool to check job status"""
|
|
25
|
+
try:
|
|
26
|
+
job = self.job_manager.get_job(job_id)
|
|
27
|
+
if not job:
|
|
28
|
+
return {"error": f"Job {job_id} not found"}
|
|
29
|
+
|
|
30
|
+
job_dict = asdict(job)
|
|
31
|
+
|
|
32
|
+
if job.status == JobStatus.RUNNING:
|
|
33
|
+
if job.estimated_time_remaining:
|
|
34
|
+
remaining = job.estimated_time_remaining
|
|
35
|
+
job_dict["estimated_time_remaining_human"] = (
|
|
36
|
+
f"{int(remaining // 60)}m {int(remaining % 60)}s"
|
|
37
|
+
if remaining >= 60 else f"{int(remaining)}s"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if job.start_time:
|
|
41
|
+
elapsed = (datetime.now() - job.start_time).total_seconds()
|
|
42
|
+
job_dict["elapsed_time_human"] = (
|
|
43
|
+
f"{int(elapsed // 60)}m {int(elapsed % 60)}s"
|
|
44
|
+
if elapsed >= 60 else f"{int(elapsed)}s"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
elif job.status == JobStatus.COMPLETED and job.start_time and job.end_time:
|
|
48
|
+
duration = (job.end_time - job.start_time).total_seconds()
|
|
49
|
+
job_dict["actual_duration_human"] = (
|
|
50
|
+
f"{int(duration // 60)}m {int(duration % 60)}s"
|
|
51
|
+
if duration >= 60 else f"{int(duration)}s"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
job_dict["start_time"] = job.start_time.strftime("%Y-%m-%d %H:%M:%S")
|
|
55
|
+
if job.end_time:
|
|
56
|
+
job_dict["end_time"] = job.end_time.strftime("%Y-%m-%d %H:%M:%S")
|
|
57
|
+
|
|
58
|
+
job_dict["status"] = job.status.value
|
|
59
|
+
return {"success": True, "job": job_dict}
|
|
60
|
+
except Exception as e:
|
|
61
|
+
return {"error": f"Failed to check job status: {str(e)}"}
|
|
62
|
+
|
|
63
|
+
def list_jobs_tool(self) -> Dict[str, Any]:
|
|
64
|
+
"""Tool to list all jobs"""
|
|
65
|
+
try:
|
|
66
|
+
jobs = self.job_manager.list_jobs()
|
|
67
|
+
jobs_data = []
|
|
68
|
+
for job in sorted(jobs, key=lambda j: j.start_time, reverse=True):
|
|
69
|
+
job_dict = asdict(job)
|
|
70
|
+
job_dict["status"] = job.status.value
|
|
71
|
+
job_dict["start_time"] = job.start_time.isoformat()
|
|
72
|
+
if job.end_time:
|
|
73
|
+
job_dict["end_time"] = job.end_time.isoformat()
|
|
74
|
+
jobs_data.append(job_dict)
|
|
75
|
+
return {"success": True, "jobs": jobs_data, "total_jobs": len(jobs_data)}
|
|
76
|
+
except Exception as e:
|
|
77
|
+
return {"error": f"Failed to list jobs: {str(e)}"}
|
|
78
|
+
|
|
79
|
+
def execute_cypher_query_tool(self, cypher_query: str) -> Dict[str, Any]:
|
|
80
|
+
"""Tool to execute a read-only Cypher query."""
|
|
81
|
+
if not cypher_query:
|
|
82
|
+
return {"error": "Cypher query cannot be empty."}
|
|
83
|
+
|
|
84
|
+
import re as _re
|
|
85
|
+
forbidden_keywords = ['CREATE', 'MERGE', 'DELETE', 'SET', 'REMOVE', 'DROP', 'CALL apoc']
|
|
86
|
+
string_literal_pattern = r'"(?:\\.|[^"\\])*"|\'(?:\\.|[^\'\\])*\''
|
|
87
|
+
query_without_strings = _re.sub(string_literal_pattern, '', cypher_query)
|
|
88
|
+
for keyword in forbidden_keywords:
|
|
89
|
+
if _re.search(r'\b' + keyword + r'\b', query_without_strings, _re.IGNORECASE):
|
|
90
|
+
return {"error": "This tool only supports read-only queries. Prohibited keywords like CREATE, MERGE, DELETE, SET, etc., are not allowed."}
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
with self.db_manager.get_driver().session() as session:
|
|
94
|
+
result = session.run(cypher_query)
|
|
95
|
+
records = [record.data() for record in result]
|
|
96
|
+
return {
|
|
97
|
+
"success": True,
|
|
98
|
+
"query": cypher_query,
|
|
99
|
+
"record_count": len(records),
|
|
100
|
+
"results": records
|
|
101
|
+
}
|
|
102
|
+
except CypherSyntaxError as e:
|
|
103
|
+
return {"error": "Cypher syntax error.", "details": str(e)}
|
|
104
|
+
except Exception as e:
|
|
105
|
+
return {"error": "An unexpected error occurred.", "details": str(e)}
|
|
106
|
+
|
|
107
|
+
def find_dead_code_tool(self) -> Dict[str, Any]:
|
|
108
|
+
"""Finds potentially unused functions (dead code)."""
|
|
109
|
+
# This logic was moved from CodeFinder to be a system diagnostic tool
|
|
110
|
+
try:
|
|
111
|
+
with self.db_manager.get_driver().session() as session:
|
|
112
|
+
result = session.run("""
|
|
113
|
+
MATCH (func:Function)
|
|
114
|
+
WHERE func.is_dependency = false
|
|
115
|
+
AND NOT func.name STARTS WITH '_'
|
|
116
|
+
AND NOT func.name IN ['main', 'setup', 'run']
|
|
117
|
+
OPTIONAL MATCH (caller:Function)-[:CALLS]->(func)
|
|
118
|
+
WHERE caller.is_dependency = false
|
|
119
|
+
WITH func, count(caller) as caller_count
|
|
120
|
+
WHERE caller_count = 0
|
|
121
|
+
RETURN func.name as function_name, func.path as path, func.line_number as line_number
|
|
122
|
+
ORDER BY func.path, func.line_number
|
|
123
|
+
LIMIT 50
|
|
124
|
+
""")
|
|
125
|
+
return {
|
|
126
|
+
"success": True,
|
|
127
|
+
"results": {
|
|
128
|
+
"potentially_unused_functions": [dict(record) for record in result],
|
|
129
|
+
"note": "These functions might be entry points or called dynamically."
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
except Exception as e:
|
|
133
|
+
return {"error": f"Failed to find dead code: {str(e)}"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Tree-sitter parser dispatch by language name."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Dict, Optional
|
|
5
|
+
|
|
6
|
+
from tree_sitter import Language, Parser
|
|
7
|
+
|
|
8
|
+
from ..utils.tree_sitter_manager import get_tree_sitter_manager
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TreeSitterParser:
|
|
12
|
+
"""A generic parser wrapper for a specific language using tree-sitter."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, language_name: str):
|
|
15
|
+
self.language_name = language_name
|
|
16
|
+
self.ts_manager = get_tree_sitter_manager()
|
|
17
|
+
|
|
18
|
+
self.language: Language = self.ts_manager.get_language_safe(language_name)
|
|
19
|
+
self.parser = Parser(self.language)
|
|
20
|
+
|
|
21
|
+
self.language_specific_parser = None
|
|
22
|
+
if self.language_name == "python":
|
|
23
|
+
from .languages.python import PythonTreeSitterParser
|
|
24
|
+
|
|
25
|
+
self.language_specific_parser = PythonTreeSitterParser(self)
|
|
26
|
+
elif self.language_name == "javascript":
|
|
27
|
+
from .languages.javascript import JavascriptTreeSitterParser
|
|
28
|
+
|
|
29
|
+
self.language_specific_parser = JavascriptTreeSitterParser(self)
|
|
30
|
+
elif self.language_name == "go":
|
|
31
|
+
from .languages.go import GoTreeSitterParser
|
|
32
|
+
|
|
33
|
+
self.language_specific_parser = GoTreeSitterParser(self)
|
|
34
|
+
elif self.language_name == "typescript":
|
|
35
|
+
from .languages.typescript import TypescriptTreeSitterParser
|
|
36
|
+
|
|
37
|
+
self.language_specific_parser = TypescriptTreeSitterParser(self)
|
|
38
|
+
elif self.language_name == "tsx":
|
|
39
|
+
from .languages.typescriptjsx import TypescriptJSXTreeSitterParser
|
|
40
|
+
|
|
41
|
+
self.language_specific_parser = TypescriptJSXTreeSitterParser(self)
|
|
42
|
+
elif self.language_name == "cpp":
|
|
43
|
+
from .languages.cpp import CppTreeSitterParser
|
|
44
|
+
|
|
45
|
+
self.language_specific_parser = CppTreeSitterParser(self)
|
|
46
|
+
elif self.language_name == "rust":
|
|
47
|
+
from .languages.rust import RustTreeSitterParser
|
|
48
|
+
|
|
49
|
+
self.language_specific_parser = RustTreeSitterParser(self)
|
|
50
|
+
elif self.language_name == "c":
|
|
51
|
+
from .languages.c import CTreeSitterParser
|
|
52
|
+
|
|
53
|
+
self.language_specific_parser = CTreeSitterParser(self)
|
|
54
|
+
elif self.language_name == "java":
|
|
55
|
+
from .languages.java import JavaTreeSitterParser
|
|
56
|
+
|
|
57
|
+
self.language_specific_parser = JavaTreeSitterParser(self)
|
|
58
|
+
elif self.language_name == "ruby":
|
|
59
|
+
from .languages.ruby import RubyTreeSitterParser
|
|
60
|
+
|
|
61
|
+
self.language_specific_parser = RubyTreeSitterParser(self)
|
|
62
|
+
elif self.language_name == "c_sharp":
|
|
63
|
+
from .languages.csharp import CSharpTreeSitterParser
|
|
64
|
+
|
|
65
|
+
self.language_specific_parser = CSharpTreeSitterParser(self)
|
|
66
|
+
elif self.language_name == "php":
|
|
67
|
+
from .languages.php import PhpTreeSitterParser
|
|
68
|
+
|
|
69
|
+
self.language_specific_parser = PhpTreeSitterParser(self)
|
|
70
|
+
elif self.language_name == "kotlin":
|
|
71
|
+
from .languages.kotlin import KotlinTreeSitterParser
|
|
72
|
+
|
|
73
|
+
self.language_specific_parser = KotlinTreeSitterParser(self)
|
|
74
|
+
elif self.language_name == "scala":
|
|
75
|
+
from .languages.scala import ScalaTreeSitterParser
|
|
76
|
+
|
|
77
|
+
self.language_specific_parser = ScalaTreeSitterParser(self)
|
|
78
|
+
elif self.language_name == "swift":
|
|
79
|
+
from .languages.swift import SwiftTreeSitterParser
|
|
80
|
+
|
|
81
|
+
self.language_specific_parser = SwiftTreeSitterParser(self)
|
|
82
|
+
elif self.language_name == "haskell":
|
|
83
|
+
from .languages.haskell import HaskellTreeSitterParser
|
|
84
|
+
|
|
85
|
+
self.language_specific_parser = HaskellTreeSitterParser(self)
|
|
86
|
+
elif self.language_name == "dart":
|
|
87
|
+
from .languages.dart import DartTreeSitterParser
|
|
88
|
+
|
|
89
|
+
self.language_specific_parser = DartTreeSitterParser(self)
|
|
90
|
+
elif self.language_name == "perl":
|
|
91
|
+
from .languages.perl import PerlTreeSitterParser
|
|
92
|
+
|
|
93
|
+
self.language_specific_parser = PerlTreeSitterParser(self)
|
|
94
|
+
elif self.language_name == "elixir":
|
|
95
|
+
from .languages.elixir import ElixirTreeSitterParser
|
|
96
|
+
|
|
97
|
+
self.language_specific_parser = ElixirTreeSitterParser(self)
|
|
98
|
+
|
|
99
|
+
def parse(self, path: Path, is_dependency: bool = False, **kwargs) -> Dict:
|
|
100
|
+
"""Dispatches parsing to the language-specific parser."""
|
|
101
|
+
if self.language_specific_parser:
|
|
102
|
+
return self.language_specific_parser.parse(path, is_dependency, **kwargs)
|
|
103
|
+
raise NotImplementedError(f"No language-specific parser implemented for {self.language_name}")
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
# Log level mapping
|
|
8
|
+
LOG_LEVELS = {
|
|
9
|
+
'DEBUG': logging.DEBUG,
|
|
10
|
+
'INFO': logging.INFO,
|
|
11
|
+
'WARNING': logging.WARNING,
|
|
12
|
+
'ERROR': logging.ERROR,
|
|
13
|
+
'CRITICAL': logging.CRITICAL,
|
|
14
|
+
'DISABLED': logging.CRITICAL + 10 # Higher than CRITICAL to disable all
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
def _get_config_value(key, default):
|
|
18
|
+
"""Helper to get config value with fallback"""
|
|
19
|
+
try:
|
|
20
|
+
from codegraphcontext.cli.config_manager import get_config_value
|
|
21
|
+
value = get_config_value(key)
|
|
22
|
+
if value is None:
|
|
23
|
+
return default
|
|
24
|
+
# Convert string boolean to actual boolean
|
|
25
|
+
if isinstance(value, str):
|
|
26
|
+
if value.lower() in ('true', 'false'):
|
|
27
|
+
return value.lower() == 'true'
|
|
28
|
+
return value
|
|
29
|
+
except Exception:
|
|
30
|
+
return default
|
|
31
|
+
|
|
32
|
+
def _should_log(level_name):
|
|
33
|
+
"""Check if a message at the given level should be logged"""
|
|
34
|
+
configured_level = _get_config_value('ENABLE_APP_LOGS', 'INFO')
|
|
35
|
+
|
|
36
|
+
# Handle legacy boolean values
|
|
37
|
+
if isinstance(configured_level, bool):
|
|
38
|
+
return configured_level
|
|
39
|
+
|
|
40
|
+
# Convert to uppercase for comparison
|
|
41
|
+
configured_level = str(configured_level).upper()
|
|
42
|
+
|
|
43
|
+
# If disabled, don't log anything
|
|
44
|
+
if configured_level == 'DISABLED':
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
# Get numeric levels
|
|
48
|
+
configured_numeric = LOG_LEVELS.get(configured_level, logging.INFO)
|
|
49
|
+
message_numeric = LOG_LEVELS.get(level_name.upper(), logging.INFO)
|
|
50
|
+
|
|
51
|
+
# Log if message level >= configured level
|
|
52
|
+
return message_numeric >= configured_numeric
|
|
53
|
+
|
|
54
|
+
def debug_log(message):
|
|
55
|
+
"""Write debug message to a file if DEBUG_LOGS is enabled"""
|
|
56
|
+
# Check if debug logging is enabled via config
|
|
57
|
+
debug_mode = _get_config_value('DEBUG_LOGS', False)
|
|
58
|
+
if not debug_mode:
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
# Get debug log path from config
|
|
62
|
+
debug_file = _get_config_value('DEBUG_LOG_PATH', os.path.expanduser("~/mcp_debug.log"))
|
|
63
|
+
|
|
64
|
+
# Ensure parent directory exists
|
|
65
|
+
Path(debug_file).parent.mkdir(parents=True, exist_ok=True)
|
|
66
|
+
|
|
67
|
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
68
|
+
with open(debug_file, "a") as f:
|
|
69
|
+
f.write(f"[{timestamp}] {message}\n")
|
|
70
|
+
f.flush()
|
|
71
|
+
|
|
72
|
+
def info_logger(msg):
|
|
73
|
+
"""Log info message if log level allows"""
|
|
74
|
+
if _should_log('INFO'):
|
|
75
|
+
return logger.info(msg)
|
|
76
|
+
|
|
77
|
+
def error_logger(msg):
|
|
78
|
+
"""Log error message if log level allows"""
|
|
79
|
+
if _should_log('ERROR'):
|
|
80
|
+
return logger.error(msg)
|
|
81
|
+
|
|
82
|
+
def warning_logger(msg):
|
|
83
|
+
"""Log warning message if log level allows"""
|
|
84
|
+
if _should_log('WARNING'):
|
|
85
|
+
return logger.warning(msg)
|
|
86
|
+
|
|
87
|
+
def debug_logger(msg):
|
|
88
|
+
"""Log debug message if log level allows"""
|
|
89
|
+
if _should_log('DEBUG'):
|
|
90
|
+
return logger.debug(msg)
|
|
@@ -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
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Helpers for comparing indexed repository records to filesystem paths.
|
|
2
|
+
|
|
3
|
+
Rows with *no* path are skipped so the CLI never raises on ``Path(None)``. That is
|
|
4
|
+
intentional: corrupted graph data should not block indexing. When the graph is
|
|
5
|
+
queried, ``CodeFinder.list_indexed_repositories`` emits a ``logging`` warning if
|
|
6
|
+
any ``Repository`` row has a missing path, so the issue is visible in normal CLI
|
|
7
|
+
runs (see application logging configuration in ``cli.main``).
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Dict, Iterable
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def repo_record_matches_path(repo: Dict[str, Any], path_obj: Path) -> bool:
|
|
16
|
+
"""True if *repo* has a resolvable path equal to *path_obj* (resolved)."""
|
|
17
|
+
raw = repo.get("path")
|
|
18
|
+
if raw is None or raw == "":
|
|
19
|
+
return False
|
|
20
|
+
try:
|
|
21
|
+
return Path(raw).resolve() == path_obj
|
|
22
|
+
except (TypeError, OSError, ValueError):
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def any_repo_matches_path(repos: Iterable[Dict[str, Any]], path_obj: Path) -> bool:
|
|
27
|
+
return any(repo_record_matches_path(r, path_obj) for r in repos)
|