jarvis-ai-assistant 0.1.222__py3-none-any.whl → 0.7.0__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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +1143 -245
- jarvis/jarvis_agent/agent_manager.py +97 -0
- jarvis/jarvis_agent/builtin_input_handler.py +12 -10
- jarvis/jarvis_agent/config_editor.py +57 -0
- jarvis/jarvis_agent/edit_file_handler.py +392 -99
- jarvis/jarvis_agent/event_bus.py +48 -0
- jarvis/jarvis_agent/events.py +157 -0
- jarvis/jarvis_agent/file_context_handler.py +79 -0
- jarvis/jarvis_agent/file_methodology_manager.py +117 -0
- jarvis/jarvis_agent/jarvis.py +1117 -147
- jarvis/jarvis_agent/main.py +78 -34
- jarvis/jarvis_agent/memory_manager.py +195 -0
- jarvis/jarvis_agent/methodology_share_manager.py +174 -0
- jarvis/jarvis_agent/prompt_manager.py +82 -0
- jarvis/jarvis_agent/prompts.py +46 -9
- jarvis/jarvis_agent/protocols.py +4 -1
- jarvis/jarvis_agent/rewrite_file_handler.py +141 -0
- jarvis/jarvis_agent/run_loop.py +146 -0
- jarvis/jarvis_agent/session_manager.py +9 -9
- jarvis/jarvis_agent/share_manager.py +228 -0
- jarvis/jarvis_agent/shell_input_handler.py +23 -3
- jarvis/jarvis_agent/stdio_redirect.py +295 -0
- jarvis/jarvis_agent/task_analyzer.py +212 -0
- jarvis/jarvis_agent/task_manager.py +154 -0
- jarvis/jarvis_agent/task_planner.py +496 -0
- jarvis/jarvis_agent/tool_executor.py +8 -4
- jarvis/jarvis_agent/tool_share_manager.py +139 -0
- jarvis/jarvis_agent/user_interaction.py +42 -0
- jarvis/jarvis_agent/utils.py +54 -0
- jarvis/jarvis_agent/web_bridge.py +189 -0
- jarvis/jarvis_agent/web_output_sink.py +53 -0
- jarvis/jarvis_agent/web_server.py +751 -0
- jarvis/jarvis_c2rust/__init__.py +26 -0
- jarvis/jarvis_c2rust/cli.py +613 -0
- jarvis/jarvis_c2rust/collector.py +258 -0
- jarvis/jarvis_c2rust/library_replacer.py +1122 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +1300 -0
- jarvis/jarvis_c2rust/optimizer.py +960 -0
- jarvis/jarvis_c2rust/scanner.py +1681 -0
- jarvis/jarvis_c2rust/transpiler.py +2325 -0
- jarvis/jarvis_code_agent/build_validation_config.py +133 -0
- jarvis/jarvis_code_agent/code_agent.py +1605 -178
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +62 -0
- jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +102 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +59 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +69 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +38 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +38 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +50 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +93 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +129 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +54 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +154 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +363 -0
- jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
- jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
- jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
- jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
- jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +89 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +31 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +231 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +183 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +219 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +209 -0
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +451 -0
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +77 -0
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +48 -0
- jarvis/jarvis_code_agent/lint.py +275 -13
- jarvis/jarvis_code_agent/utils.py +142 -0
- jarvis/jarvis_code_analysis/checklists/loader.py +20 -6
- jarvis/jarvis_code_analysis/code_review.py +583 -548
- jarvis/jarvis_data/config_schema.json +339 -28
- jarvis/jarvis_git_squash/main.py +22 -13
- jarvis/jarvis_git_utils/git_commiter.py +171 -55
- jarvis/jarvis_mcp/sse_mcp_client.py +22 -15
- jarvis/jarvis_mcp/stdio_mcp_client.py +4 -4
- jarvis/jarvis_mcp/streamable_mcp_client.py +36 -16
- jarvis/jarvis_memory_organizer/memory_organizer.py +753 -0
- jarvis/jarvis_methodology/main.py +48 -63
- jarvis/jarvis_multi_agent/__init__.py +302 -43
- jarvis/jarvis_multi_agent/main.py +70 -24
- jarvis/jarvis_platform/ai8.py +40 -23
- jarvis/jarvis_platform/base.py +210 -49
- jarvis/jarvis_platform/human.py +11 -1
- jarvis/jarvis_platform/kimi.py +82 -76
- jarvis/jarvis_platform/openai.py +73 -1
- jarvis/jarvis_platform/registry.py +8 -15
- jarvis/jarvis_platform/tongyi.py +115 -101
- jarvis/jarvis_platform/yuanbao.py +89 -63
- jarvis/jarvis_platform_manager/main.py +194 -132
- jarvis/jarvis_platform_manager/service.py +122 -86
- jarvis/jarvis_rag/cli.py +156 -53
- jarvis/jarvis_rag/embedding_manager.py +155 -12
- jarvis/jarvis_rag/llm_interface.py +10 -13
- jarvis/jarvis_rag/query_rewriter.py +63 -12
- jarvis/jarvis_rag/rag_pipeline.py +222 -40
- jarvis/jarvis_rag/reranker.py +26 -3
- jarvis/jarvis_rag/retriever.py +270 -14
- jarvis/jarvis_sec/__init__.py +3605 -0
- jarvis/jarvis_sec/checkers/__init__.py +32 -0
- jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
- jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
- jarvis/jarvis_sec/cli.py +116 -0
- jarvis/jarvis_sec/report.py +257 -0
- jarvis/jarvis_sec/status.py +264 -0
- jarvis/jarvis_sec/types.py +20 -0
- jarvis/jarvis_sec/workflow.py +219 -0
- jarvis/jarvis_smart_shell/main.py +405 -137
- jarvis/jarvis_stats/__init__.py +13 -0
- jarvis/jarvis_stats/cli.py +387 -0
- jarvis/jarvis_stats/stats.py +711 -0
- jarvis/jarvis_stats/storage.py +612 -0
- jarvis/jarvis_stats/visualizer.py +282 -0
- jarvis/jarvis_tools/ask_user.py +1 -0
- jarvis/jarvis_tools/base.py +18 -2
- jarvis/jarvis_tools/clear_memory.py +239 -0
- jarvis/jarvis_tools/cli/main.py +220 -144
- jarvis/jarvis_tools/execute_script.py +52 -12
- jarvis/jarvis_tools/file_analyzer.py +17 -12
- jarvis/jarvis_tools/generate_new_tool.py +46 -24
- jarvis/jarvis_tools/read_code.py +277 -18
- jarvis/jarvis_tools/read_symbols.py +141 -0
- jarvis/jarvis_tools/read_webpage.py +86 -13
- jarvis/jarvis_tools/registry.py +294 -90
- jarvis/jarvis_tools/retrieve_memory.py +227 -0
- jarvis/jarvis_tools/save_memory.py +194 -0
- jarvis/jarvis_tools/search_web.py +62 -28
- jarvis/jarvis_tools/sub_agent.py +205 -0
- jarvis/jarvis_tools/sub_code_agent.py +217 -0
- jarvis/jarvis_tools/virtual_tty.py +330 -62
- jarvis/jarvis_utils/builtin_replace_map.py +4 -5
- jarvis/jarvis_utils/clipboard.py +90 -0
- jarvis/jarvis_utils/config.py +607 -50
- jarvis/jarvis_utils/embedding.py +3 -0
- jarvis/jarvis_utils/fzf.py +57 -0
- jarvis/jarvis_utils/git_utils.py +251 -29
- jarvis/jarvis_utils/globals.py +174 -17
- jarvis/jarvis_utils/http.py +58 -79
- jarvis/jarvis_utils/input.py +899 -153
- jarvis/jarvis_utils/methodology.py +210 -83
- jarvis/jarvis_utils/output.py +220 -137
- jarvis/jarvis_utils/utils.py +1906 -135
- jarvis_ai_assistant-0.7.0.dist-info/METADATA +465 -0
- jarvis_ai_assistant-0.7.0.dist-info/RECORD +192 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/entry_points.txt +8 -2
- jarvis/jarvis_git_details/main.py +0 -265
- jarvis/jarvis_platform/oyi.py +0 -357
- jarvis/jarvis_tools/edit_file.py +0 -255
- jarvis/jarvis_tools/rewrite_file.py +0 -195
- jarvis_ai_assistant-0.1.222.dist-info/METADATA +0 -767
- jarvis_ai_assistant-0.1.222.dist-info/RECORD +0 -110
- /jarvis/{jarvis_git_details → jarvis_memory_organizer}/__init__.py +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import List, Optional, Set
|
|
5
|
+
|
|
6
|
+
from .dependency_analyzer import DependencyGraph
|
|
7
|
+
from .file_ignore import filter_walk_dirs
|
|
8
|
+
from .symbol_extractor import Symbol, SymbolTable
|
|
9
|
+
from .language_support import detect_language, get_symbol_extractor, get_dependency_analyzer
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class EditContext:
|
|
14
|
+
"""Provides contextual information for a specific code location."""
|
|
15
|
+
file_path: str
|
|
16
|
+
line_start: int
|
|
17
|
+
line_end: int
|
|
18
|
+
current_scope: Optional[Symbol] = None
|
|
19
|
+
used_symbols: List[Symbol] = field(default_factory=list)
|
|
20
|
+
imported_symbols: List[Symbol] = field(default_factory=list)
|
|
21
|
+
relevant_files: List[str] = field(default_factory=list)
|
|
22
|
+
context_summary: str = ""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class Reference:
|
|
27
|
+
"""Represents a reference to a symbol."""
|
|
28
|
+
symbol: Symbol
|
|
29
|
+
file_path: str
|
|
30
|
+
line: int
|
|
31
|
+
column: Optional[int] = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ContextManager:
|
|
35
|
+
"""Manages the symbol table and dependency graph to provide code context."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, project_root: str):
|
|
38
|
+
self.project_root = project_root
|
|
39
|
+
self.symbol_table = SymbolTable()
|
|
40
|
+
self.dependency_graph = DependencyGraph()
|
|
41
|
+
self._file_cache: dict[str, str] = {} # Cache file contents
|
|
42
|
+
|
|
43
|
+
def get_edit_context(self, file_path: str, line_start: int, line_end: int) -> EditContext:
|
|
44
|
+
"""
|
|
45
|
+
Gets contextual information for a given edit location.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
EditContext with information about the current scope, used symbols,
|
|
49
|
+
imported symbols, and relevant files.
|
|
50
|
+
"""
|
|
51
|
+
# Get file content
|
|
52
|
+
content = self._get_file_content(file_path)
|
|
53
|
+
if not content:
|
|
54
|
+
return EditContext(file_path=file_path, line_start=line_start, line_end=line_end)
|
|
55
|
+
|
|
56
|
+
# Find current scope (function or class)
|
|
57
|
+
current_scope = self._find_current_scope(file_path, line_start)
|
|
58
|
+
|
|
59
|
+
# Find symbols used in the edit region
|
|
60
|
+
used_symbols = self._find_used_symbols(file_path, content, line_start, line_end)
|
|
61
|
+
|
|
62
|
+
# Find imported symbols
|
|
63
|
+
imported_symbols = self._find_imported_symbols(file_path)
|
|
64
|
+
|
|
65
|
+
# Find relevant files (dependencies and dependents)
|
|
66
|
+
relevant_files = self._find_relevant_files(file_path)
|
|
67
|
+
|
|
68
|
+
# Build context summary
|
|
69
|
+
context_summary = self._build_context_summary(
|
|
70
|
+
current_scope, used_symbols, imported_symbols, relevant_files
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return EditContext(
|
|
74
|
+
file_path=file_path,
|
|
75
|
+
line_start=line_start,
|
|
76
|
+
line_end=line_end,
|
|
77
|
+
current_scope=current_scope,
|
|
78
|
+
used_symbols=used_symbols,
|
|
79
|
+
imported_symbols=imported_symbols,
|
|
80
|
+
relevant_files=relevant_files,
|
|
81
|
+
context_summary=context_summary,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def find_references(self, symbol_name: str, file_path: Optional[str] = None) -> List[Reference]:
|
|
85
|
+
"""
|
|
86
|
+
Finds all references to a symbol.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
symbol_name: Name of the symbol to find references for
|
|
90
|
+
file_path: Optional file path to limit search scope
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
List of Reference objects pointing to where the symbol is used
|
|
94
|
+
"""
|
|
95
|
+
references: List[Reference] = []
|
|
96
|
+
|
|
97
|
+
# Find symbol definitions
|
|
98
|
+
symbols = self.symbol_table.find_symbol(symbol_name, file_path)
|
|
99
|
+
if not symbols:
|
|
100
|
+
return references
|
|
101
|
+
|
|
102
|
+
# Search in files that might reference this symbol
|
|
103
|
+
search_files: Set[str] = set()
|
|
104
|
+
|
|
105
|
+
# Add files that depend on the symbol's file
|
|
106
|
+
for symbol in symbols:
|
|
107
|
+
search_files.add(symbol.file_path)
|
|
108
|
+
dependents = self.dependency_graph.get_dependents(symbol.file_path)
|
|
109
|
+
search_files.update(dependents)
|
|
110
|
+
|
|
111
|
+
# If file_path is specified, limit search to that file
|
|
112
|
+
if file_path:
|
|
113
|
+
search_files = {f for f in search_files if f == file_path}
|
|
114
|
+
|
|
115
|
+
# Search for references in each file
|
|
116
|
+
for file_path_to_search in search_files:
|
|
117
|
+
content = self._get_file_content(file_path_to_search)
|
|
118
|
+
if not content:
|
|
119
|
+
continue
|
|
120
|
+
|
|
121
|
+
# Simple pattern matching for symbol references
|
|
122
|
+
# This is a basic implementation; could be enhanced with AST analysis
|
|
123
|
+
pattern = r'\b' + re.escape(symbol_name) + r'\b'
|
|
124
|
+
for match in re.finditer(pattern, content):
|
|
125
|
+
line_num = content[:match.start()].count('\n') + 1
|
|
126
|
+
col_num = match.start() - content.rfind('\n', 0, match.start()) - 1
|
|
127
|
+
|
|
128
|
+
# Check if this is not a definition (basic check)
|
|
129
|
+
line_start = content.rfind('\n', 0, match.start()) + 1
|
|
130
|
+
line_end = content.find('\n', match.end())
|
|
131
|
+
if line_end == -1:
|
|
132
|
+
line_end = len(content)
|
|
133
|
+
line_content = content[line_start:line_end]
|
|
134
|
+
|
|
135
|
+
# Skip if it's a definition (contains 'def', 'class', etc.)
|
|
136
|
+
if any(keyword in line_content for keyword in ['def ', 'class ', 'import ', 'from ']):
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
# Use the first matching symbol definition
|
|
140
|
+
if symbols:
|
|
141
|
+
references.append(Reference(
|
|
142
|
+
symbol=symbols[0],
|
|
143
|
+
file_path=file_path_to_search,
|
|
144
|
+
line=line_num,
|
|
145
|
+
column=col_num,
|
|
146
|
+
))
|
|
147
|
+
|
|
148
|
+
return references
|
|
149
|
+
|
|
150
|
+
def find_definition(self, symbol_name: str, file_path: Optional[str] = None) -> Optional[Symbol]:
|
|
151
|
+
"""
|
|
152
|
+
Finds the definition of a symbol.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
symbol_name: Name of the symbol to find
|
|
156
|
+
file_path: Optional file path to limit search scope
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Symbol object if found, None otherwise
|
|
160
|
+
"""
|
|
161
|
+
symbols = self.symbol_table.find_symbol(symbol_name, file_path)
|
|
162
|
+
if symbols:
|
|
163
|
+
# Return the first definition (could be enhanced to find the most relevant one)
|
|
164
|
+
return symbols[0]
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
def update_context_for_file(self, file_path: str, content: str):
|
|
168
|
+
"""
|
|
169
|
+
Updates the symbol table and dependency graph for a single file.
|
|
170
|
+
"""
|
|
171
|
+
# 1. Clear old data for the file
|
|
172
|
+
self.symbol_table.clear_file_symbols(file_path)
|
|
173
|
+
self.dependency_graph.clear_file_dependencies(file_path)
|
|
174
|
+
|
|
175
|
+
# 2. Update file cache
|
|
176
|
+
self._file_cache[file_path] = content
|
|
177
|
+
|
|
178
|
+
# 3. Detect language and get the appropriate extractor
|
|
179
|
+
language = detect_language(file_path)
|
|
180
|
+
if not language:
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
# 4. Extract symbols
|
|
184
|
+
extractor = get_symbol_extractor(language)
|
|
185
|
+
if extractor:
|
|
186
|
+
symbols = extractor.extract_symbols(file_path, content)
|
|
187
|
+
for symbol in symbols:
|
|
188
|
+
self.symbol_table.add_symbol(symbol)
|
|
189
|
+
|
|
190
|
+
# 5. Analyze dependencies
|
|
191
|
+
analyzer = get_dependency_analyzer(language)
|
|
192
|
+
if analyzer:
|
|
193
|
+
dependencies = analyzer.analyze_imports(file_path, content)
|
|
194
|
+
for dep in dependencies:
|
|
195
|
+
# Resolve dependency path
|
|
196
|
+
dep_path = self._resolve_dependency_path(file_path, dep.from_module)
|
|
197
|
+
if dep_path:
|
|
198
|
+
self.dependency_graph.add_dependency(file_path, dep_path)
|
|
199
|
+
|
|
200
|
+
def _get_file_content(self, file_path: str) -> Optional[str]:
|
|
201
|
+
"""Get file content, using cache if available."""
|
|
202
|
+
if file_path in self._file_cache:
|
|
203
|
+
return self._file_cache[file_path]
|
|
204
|
+
|
|
205
|
+
if not os.path.exists(file_path):
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
|
|
210
|
+
content = f.read()
|
|
211
|
+
self._file_cache[file_path] = content
|
|
212
|
+
return content
|
|
213
|
+
except Exception:
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
def _find_current_scope(self, file_path: str, line_num: int) -> Optional[Symbol]:
|
|
217
|
+
"""Find the function or class that contains the given line."""
|
|
218
|
+
symbols = self.symbol_table.get_file_symbols(file_path)
|
|
219
|
+
|
|
220
|
+
# Find the innermost scope containing the line
|
|
221
|
+
current_scope = None
|
|
222
|
+
for symbol in symbols:
|
|
223
|
+
if symbol.kind in ('function', 'class', 'method'):
|
|
224
|
+
if symbol.line_start <= line_num <= symbol.line_end:
|
|
225
|
+
# Choose the most nested scope
|
|
226
|
+
if current_scope is None or (
|
|
227
|
+
symbol.line_start >= current_scope.line_start and
|
|
228
|
+
symbol.line_end <= current_scope.line_end
|
|
229
|
+
):
|
|
230
|
+
current_scope = symbol
|
|
231
|
+
|
|
232
|
+
return current_scope
|
|
233
|
+
|
|
234
|
+
def _find_used_symbols(self, file_path: str, content: str, line_start: int, line_end: int) -> List[Symbol]:
|
|
235
|
+
"""Find symbols used in the specified line range."""
|
|
236
|
+
# Extract the code in the range
|
|
237
|
+
lines = content.split('\n')
|
|
238
|
+
region_content = '\n'.join(lines[line_start-1:line_end])
|
|
239
|
+
|
|
240
|
+
used_symbols: List[Symbol] = []
|
|
241
|
+
all_symbols = self.symbol_table.get_file_symbols(file_path)
|
|
242
|
+
|
|
243
|
+
# Simple pattern matching to find symbol usage
|
|
244
|
+
for symbol in all_symbols:
|
|
245
|
+
if symbol.kind == 'import':
|
|
246
|
+
continue
|
|
247
|
+
|
|
248
|
+
pattern = r'\b' + re.escape(symbol.name) + r'\b'
|
|
249
|
+
if re.search(pattern, region_content):
|
|
250
|
+
used_symbols.append(symbol)
|
|
251
|
+
|
|
252
|
+
return used_symbols
|
|
253
|
+
|
|
254
|
+
def _find_imported_symbols(self, file_path: str) -> List[Symbol]:
|
|
255
|
+
"""Find all imported symbols in a file."""
|
|
256
|
+
symbols = self.symbol_table.get_file_symbols(file_path)
|
|
257
|
+
return [s for s in symbols if s.kind == 'import']
|
|
258
|
+
|
|
259
|
+
def _find_relevant_files(self, file_path: str) -> List[str]:
|
|
260
|
+
"""Find relevant files (dependencies and dependents)."""
|
|
261
|
+
relevant = set()
|
|
262
|
+
|
|
263
|
+
# Add dependencies
|
|
264
|
+
deps = self.dependency_graph.get_dependencies(file_path)
|
|
265
|
+
relevant.update(deps)
|
|
266
|
+
|
|
267
|
+
# Add dependents
|
|
268
|
+
dependents = self.dependency_graph.get_dependents(file_path)
|
|
269
|
+
relevant.update(dependents)
|
|
270
|
+
|
|
271
|
+
return list(relevant)
|
|
272
|
+
|
|
273
|
+
def _resolve_dependency_path(self, file_path: str, module_name: str) -> Optional[str]:
|
|
274
|
+
"""Resolve a module name to a file path."""
|
|
275
|
+
# Handle relative imports
|
|
276
|
+
if module_name.startswith('.'):
|
|
277
|
+
# Relative import
|
|
278
|
+
base_dir = os.path.dirname(file_path)
|
|
279
|
+
parts = module_name.split('.')
|
|
280
|
+
depth = len([p for p in parts if p == ''])
|
|
281
|
+
module_parts = [p for p in parts if p]
|
|
282
|
+
|
|
283
|
+
# Navigate up directories
|
|
284
|
+
current_dir = base_dir
|
|
285
|
+
for _ in range(depth - 1):
|
|
286
|
+
current_dir = os.path.dirname(current_dir)
|
|
287
|
+
|
|
288
|
+
# Try to find the module file
|
|
289
|
+
if module_parts:
|
|
290
|
+
module_path = os.path.join(current_dir, *module_parts)
|
|
291
|
+
else:
|
|
292
|
+
module_path = current_dir
|
|
293
|
+
|
|
294
|
+
# Try common extensions
|
|
295
|
+
for ext in ['.py', '.rs', '.go', '.js', '.ts']:
|
|
296
|
+
full_path = module_path + ext
|
|
297
|
+
if os.path.exists(full_path):
|
|
298
|
+
return full_path
|
|
299
|
+
|
|
300
|
+
# Try __init__.py for Python packages
|
|
301
|
+
if ext == '.py':
|
|
302
|
+
init_path = os.path.join(module_path, '__init__.py')
|
|
303
|
+
if os.path.exists(init_path):
|
|
304
|
+
return init_path
|
|
305
|
+
else:
|
|
306
|
+
# Absolute import - search in project
|
|
307
|
+
parts = module_name.split('.')
|
|
308
|
+
for root, dirs, files in os.walk(self.project_root):
|
|
309
|
+
# Skip hidden directories and common ignore patterns
|
|
310
|
+
dirs[:] = filter_walk_dirs(dirs)
|
|
311
|
+
|
|
312
|
+
if parts[0] in dirs or f"{parts[0]}.py" in files:
|
|
313
|
+
module_path = os.path.join(root, *parts)
|
|
314
|
+
|
|
315
|
+
# Try common extensions
|
|
316
|
+
for ext in ['.py', '.rs', '.go', '.js', '.ts']:
|
|
317
|
+
full_path = module_path + ext
|
|
318
|
+
if os.path.exists(full_path):
|
|
319
|
+
return full_path
|
|
320
|
+
|
|
321
|
+
# Try __init__.py for Python packages
|
|
322
|
+
if ext == '.py':
|
|
323
|
+
init_path = os.path.join(module_path, '__init__.py')
|
|
324
|
+
if os.path.exists(init_path):
|
|
325
|
+
return init_path
|
|
326
|
+
|
|
327
|
+
return None
|
|
328
|
+
|
|
329
|
+
def _build_context_summary(
|
|
330
|
+
self,
|
|
331
|
+
current_scope: Optional[Symbol],
|
|
332
|
+
used_symbols: List[Symbol],
|
|
333
|
+
imported_symbols: List[Symbol],
|
|
334
|
+
relevant_files: List[str],
|
|
335
|
+
) -> str:
|
|
336
|
+
"""Build a human-readable context summary."""
|
|
337
|
+
parts = []
|
|
338
|
+
|
|
339
|
+
if current_scope:
|
|
340
|
+
parts.append(f"Current scope: {current_scope.kind} {current_scope.name}")
|
|
341
|
+
if current_scope.signature:
|
|
342
|
+
parts.append(f" Signature: {current_scope.signature}")
|
|
343
|
+
|
|
344
|
+
if used_symbols:
|
|
345
|
+
symbol_names = [s.name for s in used_symbols[:5]]
|
|
346
|
+
parts.append(f"Used symbols: {', '.join(symbol_names)}")
|
|
347
|
+
if len(used_symbols) > 5:
|
|
348
|
+
parts.append(f" ... and {len(used_symbols) - 5} more")
|
|
349
|
+
|
|
350
|
+
if imported_symbols:
|
|
351
|
+
import_names = [s.name for s in imported_symbols[:5]]
|
|
352
|
+
parts.append(f"Imported symbols: {', '.join(import_names)}")
|
|
353
|
+
if len(imported_symbols) > 5:
|
|
354
|
+
parts.append(f" ... and {len(imported_symbols) - 5} more")
|
|
355
|
+
|
|
356
|
+
if relevant_files:
|
|
357
|
+
parts.append(f"Relevant files: {len(relevant_files)} files")
|
|
358
|
+
for f in relevant_files[:3]:
|
|
359
|
+
parts.append(f" - {os.path.relpath(f, self.project_root)}")
|
|
360
|
+
if len(relevant_files) > 3:
|
|
361
|
+
parts.append(f" ... and {len(relevant_files) - 3} more")
|
|
362
|
+
|
|
363
|
+
return '\n'.join(parts) if parts else "No context available"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""上下文推荐数据结构和基础工具。
|
|
2
|
+
|
|
3
|
+
保留推荐结果的数据结构和格式化方法。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import List
|
|
8
|
+
|
|
9
|
+
from .symbol_extractor import Symbol
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class ContextRecommendation:
|
|
14
|
+
"""上下文推荐结果
|
|
15
|
+
|
|
16
|
+
推荐符号在文件中的位置信息。
|
|
17
|
+
"""
|
|
18
|
+
recommended_symbols: List[Symbol] # 推荐的符号列表(包含文件路径和行号)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""依赖分析器基础模块。
|
|
2
|
+
|
|
3
|
+
提供依赖分析的基础类和接口,具体语言的实现应在各自的语言支持模块中。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Dict, List, Set, Optional
|
|
9
|
+
|
|
10
|
+
from .file_ignore import filter_walk_dirs
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class Dependency:
|
|
14
|
+
"""Represents an import dependency."""
|
|
15
|
+
from_module: str
|
|
16
|
+
imported_symbol: Optional[str] = None # None means import entire module
|
|
17
|
+
file_path: str = ""
|
|
18
|
+
line: int = 0
|
|
19
|
+
|
|
20
|
+
class DependencyGraph:
|
|
21
|
+
"""Represents the dependency graph of a project."""
|
|
22
|
+
|
|
23
|
+
def __init__(self):
|
|
24
|
+
# file_path -> set of file_paths it depends on
|
|
25
|
+
self.dependencies: Dict[str, Set[str]] = {}
|
|
26
|
+
# file_path -> set of file_paths that depend on it
|
|
27
|
+
self.dependents: Dict[str, Set[str]] = {}
|
|
28
|
+
|
|
29
|
+
def add_dependency(self, file_path: str, dependency_path: str):
|
|
30
|
+
"""Adds a dependency from file_path to dependency_path."""
|
|
31
|
+
if file_path not in self.dependencies:
|
|
32
|
+
self.dependencies[file_path] = set()
|
|
33
|
+
self.dependencies[file_path].add(dependency_path)
|
|
34
|
+
|
|
35
|
+
if dependency_path not in self.dependents:
|
|
36
|
+
self.dependents[dependency_path] = set()
|
|
37
|
+
self.dependents[dependency_path].add(file_path)
|
|
38
|
+
|
|
39
|
+
def get_dependencies(self, file_path: str) -> List[str]:
|
|
40
|
+
"""Gets the list of files that file_path depends on."""
|
|
41
|
+
return list(self.dependencies.get(file_path, set()))
|
|
42
|
+
|
|
43
|
+
def get_dependents(self, file_path: str) -> List[str]:
|
|
44
|
+
"""Gets the list of files that depend on file_path."""
|
|
45
|
+
return list(self.dependents.get(file_path, set()))
|
|
46
|
+
|
|
47
|
+
def clear_file_dependencies(self, file_path: str):
|
|
48
|
+
"""Removes all dependency information for a specific file."""
|
|
49
|
+
if file_path in self.dependencies:
|
|
50
|
+
# Remove this file's dependencies on others
|
|
51
|
+
deps_to_remove = self.dependencies.pop(file_path)
|
|
52
|
+
for dep in deps_to_remove:
|
|
53
|
+
if dep in self.dependents and file_path in self.dependents[dep]:
|
|
54
|
+
self.dependents[dep].remove(file_path)
|
|
55
|
+
if not self.dependents[dep]:
|
|
56
|
+
del self.dependents[dep]
|
|
57
|
+
|
|
58
|
+
# Remove other files' dependencies on this file
|
|
59
|
+
if file_path in self.dependents:
|
|
60
|
+
dependents_to_remove = self.dependents.pop(file_path)
|
|
61
|
+
for dep_file in dependents_to_remove:
|
|
62
|
+
if dep_file in self.dependencies and file_path in self.dependencies[dep_file]:
|
|
63
|
+
self.dependencies[dep_file].remove(file_path)
|
|
64
|
+
if not self.dependencies[dep_file]:
|
|
65
|
+
del self.dependencies[dep_file]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class DependencyAnalyzer:
|
|
69
|
+
"""Analyzes import dependencies in a source code file.
|
|
70
|
+
|
|
71
|
+
这是依赖分析器的基类,具体语言的实现应该在各自的语言支持模块中。
|
|
72
|
+
例如:PythonDependencyAnalyzer 在 languages/python_language.py 中。
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def analyze_imports(self, file_path: str, content: str) -> List[Dependency]:
|
|
76
|
+
"""
|
|
77
|
+
Analyzes the import statements in the code.
|
|
78
|
+
This method should be implemented by language-specific subclasses.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
file_path: 文件路径
|
|
82
|
+
content: 文件内容
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
依赖列表
|
|
86
|
+
"""
|
|
87
|
+
raise NotImplementedError("Subclasses must implement this method.")
|
|
88
|
+
|
|
89
|
+
def build_dependency_graph(self, project_root: str) -> DependencyGraph:
|
|
90
|
+
"""
|
|
91
|
+
Builds a dependency graph for the entire project.
|
|
92
|
+
This would involve iterating over all source files.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
project_root: 项目根目录
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
依赖图
|
|
99
|
+
"""
|
|
100
|
+
graph = DependencyGraph()
|
|
101
|
+
|
|
102
|
+
# Walk through all source files in the project
|
|
103
|
+
for root, dirs, files in os.walk(project_root):
|
|
104
|
+
# Skip hidden directories and common ignore patterns
|
|
105
|
+
dirs[:] = filter_walk_dirs(dirs)
|
|
106
|
+
|
|
107
|
+
for file in files:
|
|
108
|
+
file_path = os.path.join(root, file)
|
|
109
|
+
if not self._is_source_file(file_path):
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
|
|
114
|
+
content = f.read()
|
|
115
|
+
|
|
116
|
+
dependencies = self.analyze_imports(file_path, content)
|
|
117
|
+
for dep in dependencies:
|
|
118
|
+
# Resolve dependency path (this should be done by language-specific analyzer)
|
|
119
|
+
# For now, we'll just store the module name
|
|
120
|
+
pass
|
|
121
|
+
except Exception:
|
|
122
|
+
# Skip files that can't be read or parsed
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
return graph
|
|
126
|
+
|
|
127
|
+
def _is_source_file(self, file_path: str) -> bool:
|
|
128
|
+
"""Check if a file is a source file that should be analyzed.
|
|
129
|
+
|
|
130
|
+
This should be overridden by subclasses.
|
|
131
|
+
"""
|
|
132
|
+
return False
|