jarvis-ai-assistant 0.3.30__py3-none-any.whl → 0.7.6__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 +458 -152
- jarvis/jarvis_agent/agent_manager.py +17 -13
- jarvis/jarvis_agent/builtin_input_handler.py +2 -6
- jarvis/jarvis_agent/config_editor.py +2 -7
- jarvis/jarvis_agent/event_bus.py +82 -12
- jarvis/jarvis_agent/file_context_handler.py +329 -0
- jarvis/jarvis_agent/file_methodology_manager.py +3 -4
- jarvis/jarvis_agent/jarvis.py +628 -55
- jarvis/jarvis_agent/language_extractors/__init__.py +57 -0
- jarvis/jarvis_agent/language_extractors/c_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/cpp_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/go_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/java_extractor.py +84 -0
- jarvis/jarvis_agent/language_extractors/javascript_extractor.py +79 -0
- jarvis/jarvis_agent/language_extractors/python_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/rust_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/typescript_extractor.py +84 -0
- jarvis/jarvis_agent/language_support_info.py +486 -0
- jarvis/jarvis_agent/main.py +34 -10
- jarvis/jarvis_agent/memory_manager.py +7 -16
- jarvis/jarvis_agent/methodology_share_manager.py +10 -16
- jarvis/jarvis_agent/prompt_manager.py +1 -1
- jarvis/jarvis_agent/prompts.py +193 -171
- jarvis/jarvis_agent/protocols.py +8 -12
- jarvis/jarvis_agent/run_loop.py +105 -9
- jarvis/jarvis_agent/session_manager.py +2 -3
- jarvis/jarvis_agent/share_manager.py +20 -22
- jarvis/jarvis_agent/shell_input_handler.py +1 -2
- jarvis/jarvis_agent/stdio_redirect.py +295 -0
- jarvis/jarvis_agent/task_analyzer.py +31 -6
- jarvis/jarvis_agent/task_manager.py +11 -27
- jarvis/jarvis_agent/tool_executor.py +2 -3
- jarvis/jarvis_agent/tool_share_manager.py +12 -24
- jarvis/jarvis_agent/utils.py +5 -1
- jarvis/jarvis_agent/web_bridge.py +189 -0
- jarvis/jarvis_agent/web_output_sink.py +53 -0
- jarvis/jarvis_agent/web_server.py +786 -0
- jarvis/jarvis_c2rust/__init__.py +26 -0
- jarvis/jarvis_c2rust/cli.py +575 -0
- jarvis/jarvis_c2rust/collector.py +250 -0
- jarvis/jarvis_c2rust/constants.py +26 -0
- jarvis/jarvis_c2rust/library_replacer.py +1254 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +1272 -0
- jarvis/jarvis_c2rust/loaders.py +207 -0
- jarvis/jarvis_c2rust/models.py +28 -0
- jarvis/jarvis_c2rust/optimizer.py +2157 -0
- jarvis/jarvis_c2rust/scanner.py +1681 -0
- jarvis/jarvis_c2rust/transpiler.py +2983 -0
- jarvis/jarvis_c2rust/utils.py +385 -0
- jarvis/jarvis_code_agent/build_validation_config.py +132 -0
- jarvis/jarvis_code_agent/code_agent.py +1371 -220
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +65 -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 +106 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +74 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +72 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +70 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +53 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +47 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +61 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +110 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +154 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +110 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +153 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +648 -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 +110 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +49 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +299 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +215 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +212 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +254 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +269 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +281 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +280 -0
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +605 -0
- jarvis/jarvis_code_agent/code_analyzer/structured_code.py +556 -0
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +252 -0
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +58 -0
- jarvis/jarvis_code_agent/lint.py +501 -8
- jarvis/jarvis_code_agent/utils.py +141 -0
- jarvis/jarvis_code_analysis/code_review.py +493 -584
- jarvis/jarvis_data/config_schema.json +128 -12
- jarvis/jarvis_git_squash/main.py +4 -5
- jarvis/jarvis_git_utils/git_commiter.py +82 -75
- jarvis/jarvis_mcp/sse_mcp_client.py +22 -29
- jarvis/jarvis_mcp/stdio_mcp_client.py +12 -13
- jarvis/jarvis_mcp/streamable_mcp_client.py +15 -14
- jarvis/jarvis_memory_organizer/memory_organizer.py +55 -74
- jarvis/jarvis_methodology/main.py +32 -48
- jarvis/jarvis_multi_agent/__init__.py +287 -55
- jarvis/jarvis_multi_agent/main.py +36 -4
- jarvis/jarvis_platform/base.py +524 -202
- jarvis/jarvis_platform/human.py +7 -8
- jarvis/jarvis_platform/kimi.py +30 -36
- jarvis/jarvis_platform/openai.py +88 -25
- jarvis/jarvis_platform/registry.py +26 -10
- jarvis/jarvis_platform/tongyi.py +24 -25
- jarvis/jarvis_platform/yuanbao.py +32 -43
- jarvis/jarvis_platform_manager/main.py +66 -77
- jarvis/jarvis_platform_manager/service.py +8 -13
- jarvis/jarvis_rag/cli.py +53 -55
- jarvis/jarvis_rag/embedding_manager.py +13 -18
- jarvis/jarvis_rag/llm_interface.py +8 -9
- jarvis/jarvis_rag/query_rewriter.py +10 -21
- jarvis/jarvis_rag/rag_pipeline.py +24 -27
- jarvis/jarvis_rag/reranker.py +4 -5
- jarvis/jarvis_rag/retriever.py +28 -30
- jarvis/jarvis_sec/__init__.py +305 -0
- jarvis/jarvis_sec/agents.py +143 -0
- jarvis/jarvis_sec/analysis.py +276 -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 +139 -0
- jarvis/jarvis_sec/clustering.py +1439 -0
- jarvis/jarvis_sec/file_manager.py +427 -0
- jarvis/jarvis_sec/parsers.py +73 -0
- jarvis/jarvis_sec/prompts.py +268 -0
- jarvis/jarvis_sec/report.py +336 -0
- jarvis/jarvis_sec/review.py +453 -0
- jarvis/jarvis_sec/status.py +264 -0
- jarvis/jarvis_sec/types.py +20 -0
- jarvis/jarvis_sec/utils.py +499 -0
- jarvis/jarvis_sec/verification.py +848 -0
- jarvis/jarvis_sec/workflow.py +226 -0
- jarvis/jarvis_smart_shell/main.py +38 -87
- jarvis/jarvis_stats/cli.py +2 -2
- jarvis/jarvis_stats/stats.py +8 -8
- jarvis/jarvis_stats/storage.py +15 -21
- jarvis/jarvis_stats/visualizer.py +1 -1
- jarvis/jarvis_tools/clear_memory.py +3 -20
- jarvis/jarvis_tools/cli/main.py +21 -23
- jarvis/jarvis_tools/edit_file.py +1019 -132
- jarvis/jarvis_tools/execute_script.py +83 -25
- jarvis/jarvis_tools/file_analyzer.py +6 -9
- jarvis/jarvis_tools/generate_new_tool.py +14 -21
- jarvis/jarvis_tools/lsp_client.py +1552 -0
- jarvis/jarvis_tools/methodology.py +2 -3
- jarvis/jarvis_tools/read_code.py +1736 -35
- jarvis/jarvis_tools/read_symbols.py +140 -0
- jarvis/jarvis_tools/read_webpage.py +12 -13
- jarvis/jarvis_tools/registry.py +427 -200
- jarvis/jarvis_tools/retrieve_memory.py +20 -19
- jarvis/jarvis_tools/rewrite_file.py +72 -158
- jarvis/jarvis_tools/save_memory.py +3 -15
- jarvis/jarvis_tools/search_web.py +18 -18
- jarvis/jarvis_tools/sub_agent.py +36 -43
- jarvis/jarvis_tools/sub_code_agent.py +25 -26
- jarvis/jarvis_tools/virtual_tty.py +55 -33
- jarvis/jarvis_utils/clipboard.py +7 -10
- jarvis/jarvis_utils/config.py +232 -45
- jarvis/jarvis_utils/embedding.py +8 -5
- jarvis/jarvis_utils/fzf.py +8 -8
- jarvis/jarvis_utils/git_utils.py +225 -36
- jarvis/jarvis_utils/globals.py +3 -3
- jarvis/jarvis_utils/http.py +1 -1
- jarvis/jarvis_utils/input.py +99 -48
- jarvis/jarvis_utils/jsonnet_compat.py +465 -0
- jarvis/jarvis_utils/methodology.py +52 -48
- jarvis/jarvis_utils/utils.py +819 -491
- jarvis_ai_assistant-0.7.6.dist-info/METADATA +600 -0
- jarvis_ai_assistant-0.7.6.dist-info/RECORD +218 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/entry_points.txt +4 -0
- jarvis/jarvis_agent/config.py +0 -92
- jarvis/jarvis_agent/edit_file_handler.py +0 -296
- jarvis/jarvis_platform/ai8.py +0 -332
- jarvis/jarvis_tools/ask_user.py +0 -54
- jarvis_ai_assistant-0.3.30.dist-info/METADATA +0 -381
- jarvis_ai_assistant-0.3.30.dist-info/RECORD +0 -137
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, 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
|
+
# Create cache directory path relative to project root
|
|
40
|
+
cache_dir = os.path.join(project_root, ".jarvis", "symbol_cache")
|
|
41
|
+
self.symbol_table = SymbolTable(cache_dir)
|
|
42
|
+
self.dependency_graph = DependencyGraph()
|
|
43
|
+
self._file_cache: dict[str, str] = {} # Cache file contents
|
|
44
|
+
|
|
45
|
+
def get_edit_context(self, file_path: str, line_start: int, line_end: int) -> EditContext:
|
|
46
|
+
"""
|
|
47
|
+
Gets contextual information for a given edit location.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
EditContext with information about the current scope, used symbols,
|
|
51
|
+
imported symbols, and relevant files.
|
|
52
|
+
"""
|
|
53
|
+
# Get file content
|
|
54
|
+
content = self._get_file_content(file_path)
|
|
55
|
+
if not content:
|
|
56
|
+
return EditContext(file_path=file_path, line_start=line_start, line_end=line_end)
|
|
57
|
+
|
|
58
|
+
# Find current scope (function or class)
|
|
59
|
+
current_scope = self._find_current_scope(file_path, line_start)
|
|
60
|
+
|
|
61
|
+
# Find symbols used in the edit region
|
|
62
|
+
used_symbols = self._find_used_symbols(file_path, content, line_start, line_end)
|
|
63
|
+
|
|
64
|
+
# Find imported symbols
|
|
65
|
+
imported_symbols = self._find_imported_symbols(file_path)
|
|
66
|
+
|
|
67
|
+
# Find relevant files (dependencies and dependents)
|
|
68
|
+
relevant_files = self._find_relevant_files(file_path)
|
|
69
|
+
|
|
70
|
+
# Build context summary
|
|
71
|
+
context_summary = self._build_context_summary(
|
|
72
|
+
current_scope, used_symbols, imported_symbols, relevant_files
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return EditContext(
|
|
76
|
+
file_path=file_path,
|
|
77
|
+
line_start=line_start,
|
|
78
|
+
line_end=line_end,
|
|
79
|
+
current_scope=current_scope,
|
|
80
|
+
used_symbols=used_symbols,
|
|
81
|
+
imported_symbols=imported_symbols,
|
|
82
|
+
relevant_files=relevant_files,
|
|
83
|
+
context_summary=context_summary,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def find_references(self, symbol_name: str, file_path: Optional[str] = None) -> List[Reference]:
|
|
87
|
+
"""
|
|
88
|
+
Finds all references to a symbol.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
symbol_name: Name of the symbol to find references for
|
|
92
|
+
file_path: Optional file path to limit search scope
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
List of Reference objects pointing to where the symbol is used
|
|
96
|
+
"""
|
|
97
|
+
references: List[Reference] = []
|
|
98
|
+
|
|
99
|
+
# Check if file is stale and update if needed
|
|
100
|
+
if file_path and self.symbol_table.is_file_stale(file_path):
|
|
101
|
+
self._refresh_file_symbols(file_path)
|
|
102
|
+
|
|
103
|
+
# Find symbol definitions
|
|
104
|
+
symbols = self.symbol_table.find_symbol(symbol_name, file_path)
|
|
105
|
+
if not symbols:
|
|
106
|
+
return references
|
|
107
|
+
|
|
108
|
+
# Search in files that might reference this symbol
|
|
109
|
+
search_files: Set[str] = set()
|
|
110
|
+
|
|
111
|
+
# Add files that depend on the symbol's file
|
|
112
|
+
for symbol in symbols:
|
|
113
|
+
search_files.add(symbol.file_path)
|
|
114
|
+
dependents = self.dependency_graph.get_dependents(symbol.file_path)
|
|
115
|
+
search_files.update(dependents)
|
|
116
|
+
|
|
117
|
+
# If file_path is specified, limit search to that file
|
|
118
|
+
if file_path:
|
|
119
|
+
search_files = {f for f in search_files if f == file_path}
|
|
120
|
+
|
|
121
|
+
# Search for references in each file
|
|
122
|
+
for file_path_to_search in search_files:
|
|
123
|
+
# Check if file is stale and update if needed
|
|
124
|
+
if self.symbol_table.is_file_stale(file_path_to_search):
|
|
125
|
+
self._refresh_file_symbols(file_path_to_search)
|
|
126
|
+
|
|
127
|
+
content = self._get_file_content(file_path_to_search)
|
|
128
|
+
if not content:
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
# Simple pattern matching for symbol references
|
|
132
|
+
# This is a basic implementation; could be enhanced with AST analysis
|
|
133
|
+
pattern = r'\b' + re.escape(symbol_name) + r'\b'
|
|
134
|
+
for match in re.finditer(pattern, content):
|
|
135
|
+
line_num = content[:match.start()].count('\n') + 1
|
|
136
|
+
col_num = match.start() - content.rfind('\n', 0, match.start()) - 1
|
|
137
|
+
|
|
138
|
+
# Check if this is not a definition (basic check)
|
|
139
|
+
line_start = content.rfind('\n', 0, match.start()) + 1
|
|
140
|
+
line_end = content.find('\n', match.end())
|
|
141
|
+
if line_end == -1:
|
|
142
|
+
line_end = len(content)
|
|
143
|
+
line_content = content[line_start:line_end]
|
|
144
|
+
|
|
145
|
+
# Skip if it's a definition (contains 'def', 'class', etc.)
|
|
146
|
+
if any(keyword in line_content for keyword in ['def ', 'class ', 'import ', 'from ']):
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
# Use the first matching symbol definition
|
|
150
|
+
if symbols:
|
|
151
|
+
references.append(Reference(
|
|
152
|
+
symbol=symbols[0],
|
|
153
|
+
file_path=file_path_to_search,
|
|
154
|
+
line=line_num,
|
|
155
|
+
column=col_num,
|
|
156
|
+
))
|
|
157
|
+
|
|
158
|
+
return references
|
|
159
|
+
|
|
160
|
+
def find_definition(self, symbol_name: str, file_path: Optional[str] = None) -> Optional[Symbol]:
|
|
161
|
+
"""
|
|
162
|
+
Finds the definition of a symbol.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
symbol_name: Name of the symbol to find
|
|
166
|
+
file_path: Optional file path to limit search scope
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Symbol object if found, None otherwise
|
|
170
|
+
"""
|
|
171
|
+
# Check if file is stale and update if needed
|
|
172
|
+
if file_path and self.symbol_table.is_file_stale(file_path):
|
|
173
|
+
self._refresh_file_symbols(file_path)
|
|
174
|
+
|
|
175
|
+
symbols = self.symbol_table.find_symbol(symbol_name, file_path)
|
|
176
|
+
if symbols:
|
|
177
|
+
# Return the first definition (could be enhanced to find the most relevant one)
|
|
178
|
+
return symbols[0]
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
def update_context_for_file(self, file_path: str, content: str):
|
|
182
|
+
"""
|
|
183
|
+
Updates the symbol table and dependency graph for a single file.
|
|
184
|
+
"""
|
|
185
|
+
# 1. Clear old data for the file
|
|
186
|
+
self.symbol_table.clear_file_symbols(file_path)
|
|
187
|
+
self.dependency_graph.clear_file_dependencies(file_path)
|
|
188
|
+
|
|
189
|
+
# 2. Update file cache
|
|
190
|
+
self._file_cache[file_path] = content
|
|
191
|
+
|
|
192
|
+
# 3. Detect language and get the appropriate extractor
|
|
193
|
+
language = detect_language(file_path)
|
|
194
|
+
if not language:
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
# 4. Extract symbols
|
|
198
|
+
extractor = get_symbol_extractor(language)
|
|
199
|
+
if extractor:
|
|
200
|
+
symbols = extractor.extract_symbols(file_path, content)
|
|
201
|
+
for symbol in symbols:
|
|
202
|
+
self.symbol_table.add_symbol(symbol)
|
|
203
|
+
|
|
204
|
+
# Update file modification time after extracting symbols
|
|
205
|
+
if os.path.exists(file_path):
|
|
206
|
+
try:
|
|
207
|
+
self.symbol_table._file_mtimes[file_path] = os.path.getmtime(file_path)
|
|
208
|
+
except Exception:
|
|
209
|
+
pass
|
|
210
|
+
|
|
211
|
+
# 5. Analyze dependencies
|
|
212
|
+
analyzer = get_dependency_analyzer(language)
|
|
213
|
+
if analyzer:
|
|
214
|
+
dependencies = analyzer.analyze_imports(file_path, content)
|
|
215
|
+
for dep in dependencies:
|
|
216
|
+
# Resolve dependency path
|
|
217
|
+
dep_path = self._resolve_dependency_path(file_path, dep.from_module)
|
|
218
|
+
if dep_path:
|
|
219
|
+
self.dependency_graph.add_dependency(file_path, dep_path)
|
|
220
|
+
|
|
221
|
+
# 6. Save updated symbols to cache
|
|
222
|
+
self.symbol_table.save_cache()
|
|
223
|
+
|
|
224
|
+
def _refresh_file_symbols(self, file_path: str):
|
|
225
|
+
"""Refresh symbols for a file that has been modified externally.
|
|
226
|
+
|
|
227
|
+
This method is called when a file is detected to be stale (modified
|
|
228
|
+
outside of Jarvis's control).
|
|
229
|
+
"""
|
|
230
|
+
if not os.path.exists(file_path):
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
|
|
235
|
+
content = f.read()
|
|
236
|
+
self.update_context_for_file(file_path, content)
|
|
237
|
+
except Exception:
|
|
238
|
+
# If we can't read the file, skip refresh
|
|
239
|
+
pass
|
|
240
|
+
|
|
241
|
+
def _get_file_content(self, file_path: str) -> Optional[str]:
|
|
242
|
+
"""Get file content, using cache if available."""
|
|
243
|
+
if file_path in self._file_cache:
|
|
244
|
+
return self._file_cache[file_path]
|
|
245
|
+
|
|
246
|
+
if not os.path.exists(file_path):
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
|
|
251
|
+
content = f.read()
|
|
252
|
+
self._file_cache[file_path] = content
|
|
253
|
+
return content
|
|
254
|
+
except Exception:
|
|
255
|
+
return None
|
|
256
|
+
|
|
257
|
+
def _find_current_scope(self, file_path: str, line_num: int) -> Optional[Symbol]:
|
|
258
|
+
"""Find the function or class that contains the given line."""
|
|
259
|
+
# Check if file is stale and update if needed
|
|
260
|
+
if self.symbol_table.is_file_stale(file_path):
|
|
261
|
+
self._refresh_file_symbols(file_path)
|
|
262
|
+
symbols = self.symbol_table.get_file_symbols(file_path)
|
|
263
|
+
|
|
264
|
+
# Find the innermost scope containing the line
|
|
265
|
+
current_scope = None
|
|
266
|
+
for symbol in symbols:
|
|
267
|
+
if symbol.kind in ('function', 'class', 'method'):
|
|
268
|
+
if symbol.line_start <= line_num <= symbol.line_end:
|
|
269
|
+
# Choose the most nested scope
|
|
270
|
+
if current_scope is None or (
|
|
271
|
+
symbol.line_start >= current_scope.line_start and
|
|
272
|
+
symbol.line_end <= current_scope.line_end
|
|
273
|
+
):
|
|
274
|
+
current_scope = symbol
|
|
275
|
+
|
|
276
|
+
return current_scope
|
|
277
|
+
|
|
278
|
+
def _find_used_symbols(self, file_path: str, content: str, line_start: int, line_end: int) -> List[Symbol]:
|
|
279
|
+
"""Find symbols used in the specified line range.
|
|
280
|
+
|
|
281
|
+
改进版本:
|
|
282
|
+
1. 区分定义和调用:检查符号是否在当前行范围内定义
|
|
283
|
+
2. 获取定义位置:优先使用 LSP,如果不支持则使用 tree-sitter
|
|
284
|
+
3. 为每个使用的符号添加定义位置信息
|
|
285
|
+
"""
|
|
286
|
+
# Check if file is stale and update if needed
|
|
287
|
+
if self.symbol_table.is_file_stale(file_path):
|
|
288
|
+
self._refresh_file_symbols(file_path)
|
|
289
|
+
|
|
290
|
+
# Extract the code in the range
|
|
291
|
+
lines = content.split('\n')
|
|
292
|
+
region_content = '\n'.join(lines[line_start-1:line_end])
|
|
293
|
+
|
|
294
|
+
used_symbols: List[Symbol] = []
|
|
295
|
+
all_symbols = self.symbol_table.get_file_symbols(file_path)
|
|
296
|
+
|
|
297
|
+
# 尝试获取 LSP 客户端(优先使用)
|
|
298
|
+
lsp_client = None
|
|
299
|
+
try:
|
|
300
|
+
from jarvis.jarvis_tools.lsp_client import LSPClientTool
|
|
301
|
+
lsp_client = LSPClientTool.get_client_for_file(file_path, self.project_root)
|
|
302
|
+
except Exception:
|
|
303
|
+
pass
|
|
304
|
+
|
|
305
|
+
# 尝试获取 tree-sitter 提取器(作为后备)
|
|
306
|
+
treesitter_extractor = None
|
|
307
|
+
try:
|
|
308
|
+
from jarvis.jarvis_code_agent.code_analyzer.language_support import (
|
|
309
|
+
detect_language,
|
|
310
|
+
get_symbol_extractor,
|
|
311
|
+
)
|
|
312
|
+
language = detect_language(file_path)
|
|
313
|
+
if language:
|
|
314
|
+
treesitter_extractor = get_symbol_extractor(language)
|
|
315
|
+
except Exception:
|
|
316
|
+
pass
|
|
317
|
+
|
|
318
|
+
# 用于跟踪已处理的符号,避免重复
|
|
319
|
+
processed_symbols = {} # {symbol_name: (symbol, is_definition, definition_location)}
|
|
320
|
+
|
|
321
|
+
# Simple pattern matching to find symbol usage
|
|
322
|
+
for symbol in all_symbols:
|
|
323
|
+
if symbol.kind == 'import':
|
|
324
|
+
continue
|
|
325
|
+
|
|
326
|
+
pattern = r'\b' + re.escape(symbol.name) + r'\b'
|
|
327
|
+
matches = list(re.finditer(pattern, region_content))
|
|
328
|
+
if not matches:
|
|
329
|
+
continue
|
|
330
|
+
|
|
331
|
+
# 检查符号是否在当前行范围内定义
|
|
332
|
+
is_definition_in_range = (
|
|
333
|
+
symbol.file_path == file_path and
|
|
334
|
+
symbol.line_start >= line_start and
|
|
335
|
+
symbol.line_start <= line_end
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
# 检查是否有调用(不在定义行的使用)
|
|
339
|
+
has_calls = False
|
|
340
|
+
call_line = None
|
|
341
|
+
call_column = None
|
|
342
|
+
for match in matches:
|
|
343
|
+
match_start = match.start()
|
|
344
|
+
match_line_in_region = region_content[:match_start].count('\n') + 1
|
|
345
|
+
match_line = line_start + match_line_in_region - 1
|
|
346
|
+
|
|
347
|
+
# 如果使用位置不在定义行,则认为是调用
|
|
348
|
+
if not is_definition_in_range or match_line != symbol.line_start:
|
|
349
|
+
has_calls = True
|
|
350
|
+
call_line = match_line
|
|
351
|
+
# 计算列号
|
|
352
|
+
line_start_pos = region_content[:match_start].rfind('\n') + 1
|
|
353
|
+
call_column = match_start - line_start_pos
|
|
354
|
+
break
|
|
355
|
+
|
|
356
|
+
# 处理定义
|
|
357
|
+
if is_definition_in_range:
|
|
358
|
+
symbol.is_definition = True
|
|
359
|
+
if symbol.name not in processed_symbols:
|
|
360
|
+
processed_symbols[symbol.name] = (symbol, True, None)
|
|
361
|
+
|
|
362
|
+
# 处理调用
|
|
363
|
+
if has_calls or not is_definition_in_range:
|
|
364
|
+
# 创建或更新引用符号
|
|
365
|
+
if symbol.name in processed_symbols:
|
|
366
|
+
existing_symbol, existing_is_def, existing_def_loc = processed_symbols[symbol.name]
|
|
367
|
+
# 如果已有定义,跳过;否则更新定义位置
|
|
368
|
+
if not existing_is_def and not existing_def_loc:
|
|
369
|
+
# 尝试获取定义位置
|
|
370
|
+
definition_location = self._find_definition_location(
|
|
371
|
+
symbol.name,
|
|
372
|
+
file_path,
|
|
373
|
+
call_line or line_start,
|
|
374
|
+
call_column or 0,
|
|
375
|
+
lsp_client,
|
|
376
|
+
treesitter_extractor,
|
|
377
|
+
content,
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
if definition_location:
|
|
381
|
+
processed_symbols[symbol.name] = (existing_symbol, False, definition_location)
|
|
382
|
+
else:
|
|
383
|
+
# 从符号表中查找
|
|
384
|
+
definition_symbols = self.symbol_table.find_symbol(symbol.name)
|
|
385
|
+
if definition_symbols:
|
|
386
|
+
def_symbol = definition_symbols[0]
|
|
387
|
+
definition_location = Symbol(
|
|
388
|
+
name=def_symbol.name,
|
|
389
|
+
kind=def_symbol.kind,
|
|
390
|
+
file_path=def_symbol.file_path,
|
|
391
|
+
line_start=def_symbol.line_start,
|
|
392
|
+
line_end=def_symbol.line_end,
|
|
393
|
+
signature=def_symbol.signature,
|
|
394
|
+
)
|
|
395
|
+
processed_symbols[symbol.name] = (existing_symbol, False, definition_location)
|
|
396
|
+
else:
|
|
397
|
+
# 创建新的引用符号
|
|
398
|
+
reference_symbol = Symbol(
|
|
399
|
+
name=symbol.name,
|
|
400
|
+
kind=symbol.kind,
|
|
401
|
+
file_path=file_path,
|
|
402
|
+
line_start=call_line or line_start,
|
|
403
|
+
line_end=call_line or line_start,
|
|
404
|
+
signature=symbol.signature,
|
|
405
|
+
docstring=symbol.docstring,
|
|
406
|
+
parent=symbol.parent,
|
|
407
|
+
is_definition=False,
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
# 尝试获取定义位置
|
|
411
|
+
definition_location = self._find_definition_location(
|
|
412
|
+
symbol.name,
|
|
413
|
+
file_path,
|
|
414
|
+
call_line or line_start,
|
|
415
|
+
call_column or 0,
|
|
416
|
+
lsp_client,
|
|
417
|
+
treesitter_extractor,
|
|
418
|
+
content,
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
if definition_location:
|
|
422
|
+
reference_symbol.definition_location = definition_location
|
|
423
|
+
else:
|
|
424
|
+
# 从符号表中查找
|
|
425
|
+
definition_symbols = self.symbol_table.find_symbol(symbol.name)
|
|
426
|
+
if definition_symbols:
|
|
427
|
+
def_symbol = definition_symbols[0]
|
|
428
|
+
reference_symbol.definition_location = Symbol(
|
|
429
|
+
name=def_symbol.name,
|
|
430
|
+
kind=def_symbol.kind,
|
|
431
|
+
file_path=def_symbol.file_path,
|
|
432
|
+
line_start=def_symbol.line_start,
|
|
433
|
+
line_end=def_symbol.line_end,
|
|
434
|
+
signature=def_symbol.signature,
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
processed_symbols[symbol.name] = (reference_symbol, False, reference_symbol.definition_location)
|
|
438
|
+
|
|
439
|
+
# 将处理后的符号添加到结果列表
|
|
440
|
+
for symbol, is_def, def_loc in processed_symbols.values():
|
|
441
|
+
if is_def:
|
|
442
|
+
symbol.is_definition = True
|
|
443
|
+
if def_loc:
|
|
444
|
+
symbol.definition_location = def_loc
|
|
445
|
+
used_symbols.append(symbol)
|
|
446
|
+
|
|
447
|
+
return used_symbols
|
|
448
|
+
|
|
449
|
+
def _find_definition_location(
|
|
450
|
+
self,
|
|
451
|
+
symbol_name: str,
|
|
452
|
+
file_path: str,
|
|
453
|
+
line: int,
|
|
454
|
+
column: int,
|
|
455
|
+
lsp_client: Optional[Any],
|
|
456
|
+
treesitter_extractor: Optional[Any],
|
|
457
|
+
content: str,
|
|
458
|
+
) -> Optional[Symbol]:
|
|
459
|
+
"""查找符号的定义位置。
|
|
460
|
+
|
|
461
|
+
优先使用 LSP,如果不支持则使用 tree-sitter。
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
symbol_name: 符号名称
|
|
465
|
+
file_path: 文件路径
|
|
466
|
+
line: 行号(1-based)
|
|
467
|
+
column: 列号(0-based)
|
|
468
|
+
lsp_client: LSP 客户端(可选)
|
|
469
|
+
treesitter_extractor: tree-sitter 提取器(可选)
|
|
470
|
+
content: 文件内容
|
|
471
|
+
|
|
472
|
+
Returns:
|
|
473
|
+
定义位置的 Symbol 对象,如果找不到则返回 None
|
|
474
|
+
"""
|
|
475
|
+
# 优先使用 LSP
|
|
476
|
+
if lsp_client:
|
|
477
|
+
try:
|
|
478
|
+
# LSP 使用 0-based 行号
|
|
479
|
+
definition = lsp_client.get_definition(file_path, line - 1, column)
|
|
480
|
+
if definition:
|
|
481
|
+
# 处理 LSP 返回的定义(可能是单个对象或列表)
|
|
482
|
+
if isinstance(definition, list):
|
|
483
|
+
if definition:
|
|
484
|
+
definition = definition[0]
|
|
485
|
+
else:
|
|
486
|
+
return None
|
|
487
|
+
|
|
488
|
+
# 解析 LSP 返回的定义位置
|
|
489
|
+
uri = definition.get("uri", "")
|
|
490
|
+
if uri.startswith("file://"):
|
|
491
|
+
def_file_path = uri[7:] # 移除 "file://" 前缀
|
|
492
|
+
else:
|
|
493
|
+
def_file_path = uri
|
|
494
|
+
|
|
495
|
+
range_info = definition.get("range", {})
|
|
496
|
+
start = range_info.get("start", {})
|
|
497
|
+
end = range_info.get("end", {})
|
|
498
|
+
|
|
499
|
+
# LSP 使用 0-based 行号,转换为 1-based
|
|
500
|
+
def_line_start = start.get("line", 0) + 1
|
|
501
|
+
def_line_end = end.get("line", 0) + 1
|
|
502
|
+
|
|
503
|
+
return Symbol(
|
|
504
|
+
name=symbol_name,
|
|
505
|
+
kind="", # LSP 可能不提供类型信息
|
|
506
|
+
file_path=def_file_path,
|
|
507
|
+
line_start=def_line_start,
|
|
508
|
+
line_end=def_line_end,
|
|
509
|
+
)
|
|
510
|
+
except Exception:
|
|
511
|
+
# LSP 失败,继续尝试 tree-sitter
|
|
512
|
+
pass
|
|
513
|
+
|
|
514
|
+
# 后备:使用 tree-sitter
|
|
515
|
+
if treesitter_extractor:
|
|
516
|
+
try:
|
|
517
|
+
# 从符号表中查找定义(tree-sitter 已经提取了符号)
|
|
518
|
+
definition_symbols = self.symbol_table.find_symbol(symbol_name)
|
|
519
|
+
if definition_symbols:
|
|
520
|
+
# 选择第一个定义(可以改进为选择最相关的)
|
|
521
|
+
def_symbol = definition_symbols[0]
|
|
522
|
+
return Symbol(
|
|
523
|
+
name=def_symbol.name,
|
|
524
|
+
kind=def_symbol.kind,
|
|
525
|
+
file_path=def_symbol.file_path,
|
|
526
|
+
line_start=def_symbol.line_start,
|
|
527
|
+
line_end=def_symbol.line_end,
|
|
528
|
+
signature=def_symbol.signature,
|
|
529
|
+
)
|
|
530
|
+
except Exception:
|
|
531
|
+
pass
|
|
532
|
+
|
|
533
|
+
return None
|
|
534
|
+
|
|
535
|
+
def _find_imported_symbols(self, file_path: str) -> List[Symbol]:
|
|
536
|
+
"""Find all imported symbols in a file."""
|
|
537
|
+
# Check if file is stale and update if needed
|
|
538
|
+
if self.symbol_table.is_file_stale(file_path):
|
|
539
|
+
self._refresh_file_symbols(file_path)
|
|
540
|
+
|
|
541
|
+
symbols = self.symbol_table.get_file_symbols(file_path)
|
|
542
|
+
return [s for s in symbols if s.kind == 'import']
|
|
543
|
+
|
|
544
|
+
def _find_relevant_files(self, file_path: str) -> List[str]:
|
|
545
|
+
"""Find relevant files (dependencies and dependents)."""
|
|
546
|
+
relevant = set()
|
|
547
|
+
|
|
548
|
+
# Add dependencies
|
|
549
|
+
deps = self.dependency_graph.get_dependencies(file_path)
|
|
550
|
+
relevant.update(deps)
|
|
551
|
+
|
|
552
|
+
# Add dependents
|
|
553
|
+
dependents = self.dependency_graph.get_dependents(file_path)
|
|
554
|
+
relevant.update(dependents)
|
|
555
|
+
|
|
556
|
+
return list(relevant)
|
|
557
|
+
|
|
558
|
+
def _resolve_dependency_path(self, file_path: str, module_name: str) -> Optional[str]:
|
|
559
|
+
"""Resolve a module name to a file path."""
|
|
560
|
+
# Handle relative imports
|
|
561
|
+
if module_name.startswith('.'):
|
|
562
|
+
# Relative import
|
|
563
|
+
base_dir = os.path.dirname(file_path)
|
|
564
|
+
parts = module_name.split('.')
|
|
565
|
+
depth = len([p for p in parts if p == ''])
|
|
566
|
+
module_parts = [p for p in parts if p]
|
|
567
|
+
|
|
568
|
+
# Navigate up directories
|
|
569
|
+
current_dir = base_dir
|
|
570
|
+
for _ in range(depth - 1):
|
|
571
|
+
current_dir = os.path.dirname(current_dir)
|
|
572
|
+
|
|
573
|
+
# Try to find the module file
|
|
574
|
+
if module_parts:
|
|
575
|
+
module_path = os.path.join(current_dir, *module_parts)
|
|
576
|
+
else:
|
|
577
|
+
module_path = current_dir
|
|
578
|
+
|
|
579
|
+
# Try common extensions
|
|
580
|
+
for ext in ['.py', '.rs', '.go', '.js', '.ts']:
|
|
581
|
+
full_path = module_path + ext
|
|
582
|
+
if os.path.exists(full_path):
|
|
583
|
+
return full_path
|
|
584
|
+
|
|
585
|
+
# Try __init__.py for Python packages
|
|
586
|
+
if ext == '.py':
|
|
587
|
+
init_path = os.path.join(module_path, '__init__.py')
|
|
588
|
+
if os.path.exists(init_path):
|
|
589
|
+
return init_path
|
|
590
|
+
else:
|
|
591
|
+
# Absolute import - search in project
|
|
592
|
+
parts = module_name.split('.')
|
|
593
|
+
for root, dirs, files in os.walk(self.project_root):
|
|
594
|
+
# Skip hidden directories and common ignore patterns
|
|
595
|
+
dirs[:] = filter_walk_dirs(dirs)
|
|
596
|
+
|
|
597
|
+
if parts[0] in dirs or f"{parts[0]}.py" in files:
|
|
598
|
+
module_path = os.path.join(root, *parts)
|
|
599
|
+
|
|
600
|
+
# Try common extensions
|
|
601
|
+
for ext in ['.py', '.rs', '.go', '.js', '.ts']:
|
|
602
|
+
full_path = module_path + ext
|
|
603
|
+
if os.path.exists(full_path):
|
|
604
|
+
return full_path
|
|
605
|
+
|
|
606
|
+
# Try __init__.py for Python packages
|
|
607
|
+
if ext == '.py':
|
|
608
|
+
init_path = os.path.join(module_path, '__init__.py')
|
|
609
|
+
if os.path.exists(init_path):
|
|
610
|
+
return init_path
|
|
611
|
+
|
|
612
|
+
return None
|
|
613
|
+
|
|
614
|
+
def _build_context_summary(
|
|
615
|
+
self,
|
|
616
|
+
current_scope: Optional[Symbol],
|
|
617
|
+
used_symbols: List[Symbol],
|
|
618
|
+
imported_symbols: List[Symbol],
|
|
619
|
+
relevant_files: List[str],
|
|
620
|
+
) -> str:
|
|
621
|
+
"""Build a human-readable context summary."""
|
|
622
|
+
parts = []
|
|
623
|
+
|
|
624
|
+
if current_scope:
|
|
625
|
+
parts.append(f"Current scope: {current_scope.kind} {current_scope.name}")
|
|
626
|
+
if current_scope.signature:
|
|
627
|
+
parts.append(f" Signature: {current_scope.signature}")
|
|
628
|
+
|
|
629
|
+
if used_symbols:
|
|
630
|
+
symbol_names = [s.name for s in used_symbols[:5]]
|
|
631
|
+
parts.append(f"Used symbols: {', '.join(symbol_names)}")
|
|
632
|
+
if len(used_symbols) > 5:
|
|
633
|
+
parts.append(f" ... and {len(used_symbols) - 5} more")
|
|
634
|
+
|
|
635
|
+
if imported_symbols:
|
|
636
|
+
import_names = [s.name for s in imported_symbols[:5]]
|
|
637
|
+
parts.append(f"Imported symbols: {', '.join(import_names)}")
|
|
638
|
+
if len(imported_symbols) > 5:
|
|
639
|
+
parts.append(f" ... and {len(imported_symbols) - 5} more")
|
|
640
|
+
|
|
641
|
+
if relevant_files:
|
|
642
|
+
parts.append(f"Relevant files: {len(relevant_files)} files")
|
|
643
|
+
for f in relevant_files[:3]:
|
|
644
|
+
parts.append(f" - {os.path.relpath(f, self.project_root)}")
|
|
645
|
+
if len(relevant_files) > 3:
|
|
646
|
+
parts.append(f" ... and {len(relevant_files) - 3} more")
|
|
647
|
+
|
|
648
|
+
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] # 推荐的符号列表(包含文件路径和行号)
|