jarvis-ai-assistant 0.1.130__py3-none-any.whl → 0.1.131__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.
Potentially problematic release.
This version of jarvis-ai-assistant might be problematic. Click here for more details.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +23 -9
- jarvis/jarvis_agent/builtin_input_handler.py +73 -0
- jarvis/{jarvis_code_agent → jarvis_agent}/file_input_handler.py +1 -1
- jarvis/jarvis_agent/main.py +1 -1
- jarvis/{jarvis_code_agent → jarvis_agent}/patch.py +23 -19
- jarvis/{jarvis_code_agent → jarvis_agent}/shell_input_handler.py +0 -1
- jarvis/jarvis_code_agent/code_agent.py +20 -16
- jarvis/jarvis_codebase/main.py +5 -5
- jarvis/jarvis_dev/main.py +1 -1
- jarvis/jarvis_git_squash/main.py +1 -1
- jarvis/jarvis_lsp/base.py +2 -26
- jarvis/jarvis_lsp/cpp.py +2 -14
- jarvis/jarvis_lsp/go.py +0 -13
- jarvis/jarvis_lsp/python.py +1 -30
- jarvis/jarvis_lsp/registry.py +10 -14
- jarvis/jarvis_lsp/rust.py +0 -12
- jarvis/jarvis_multi_agent/__init__.py +1 -1
- jarvis/jarvis_platform/registry.py +1 -1
- jarvis/jarvis_platform_manager/main.py +3 -3
- jarvis/jarvis_rag/main.py +1 -1
- jarvis/jarvis_tools/ask_codebase.py +40 -20
- jarvis/jarvis_tools/code_review.py +180 -143
- jarvis/jarvis_tools/create_code_agent.py +76 -72
- jarvis/jarvis_tools/create_sub_agent.py +32 -15
- jarvis/jarvis_tools/execute_shell.py +2 -2
- jarvis/jarvis_tools/execute_shell_script.py +1 -1
- jarvis/jarvis_tools/file_operation.py +2 -2
- jarvis/jarvis_tools/git_commiter.py +87 -68
- jarvis/jarvis_tools/lsp_find_definition.py +83 -67
- jarvis/jarvis_tools/lsp_find_references.py +62 -46
- jarvis/jarvis_tools/lsp_get_diagnostics.py +90 -74
- jarvis/jarvis_tools/methodology.py +3 -3
- jarvis/jarvis_tools/read_code.py +1 -1
- jarvis/jarvis_tools/search_web.py +18 -20
- jarvis/jarvis_tools/tool_generator.py +1 -1
- jarvis/jarvis_tools/treesitter_analyzer.py +331 -0
- jarvis/jarvis_treesitter/README.md +104 -0
- jarvis/jarvis_treesitter/__init__.py +20 -0
- jarvis/jarvis_treesitter/database.py +258 -0
- jarvis/jarvis_treesitter/example.py +115 -0
- jarvis/jarvis_treesitter/grammar_builder.py +182 -0
- jarvis/jarvis_treesitter/language.py +117 -0
- jarvis/jarvis_treesitter/symbol.py +31 -0
- jarvis/jarvis_treesitter/tools_usage.md +121 -0
- jarvis/jarvis_utils/git_utils.py +10 -2
- jarvis/jarvis_utils/input.py +3 -1
- jarvis/jarvis_utils/methodology.py +1 -1
- jarvis/jarvis_utils/utils.py +3 -3
- {jarvis_ai_assistant-0.1.130.dist-info → jarvis_ai_assistant-0.1.131.dist-info}/METADATA +2 -4
- jarvis_ai_assistant-0.1.131.dist-info/RECORD +85 -0
- jarvis/jarvis_c2rust/c2rust.yaml +0 -734
- jarvis/jarvis_code_agent/builtin_input_handler.py +0 -43
- jarvis/jarvis_tools/lsp_get_document_symbols.py +0 -87
- jarvis/jarvis_tools/lsp_prepare_rename.py +0 -130
- jarvis_ai_assistant-0.1.130.dist-info/RECORD +0 -79
- {jarvis_ai_assistant-0.1.130.dist-info → jarvis_ai_assistant-0.1.131.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.130.dist-info → jarvis_ai_assistant-0.1.131.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.130.dist-info → jarvis_ai_assistant-0.1.131.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.1.130.dist-info → jarvis_ai_assistant-0.1.131.dist-info}/top_level.txt +0 -0
jarvis/jarvis_lsp/registry.py
CHANGED
|
@@ -11,9 +11,7 @@ REQUIRED_METHODS = [
|
|
|
11
11
|
('initialize', ['workspace_path']),
|
|
12
12
|
('find_references', ['file_path', 'position']),
|
|
13
13
|
('find_definition', ['file_path', 'position']),
|
|
14
|
-
('get_document_symbols', ['file_path']),
|
|
15
14
|
('get_diagnostics', ['file_path']),
|
|
16
|
-
('prepare_rename', ['file_path', 'position']),
|
|
17
15
|
('shutdown', [])
|
|
18
16
|
]
|
|
19
17
|
|
|
@@ -29,7 +27,7 @@ class LSPRegistry:
|
|
|
29
27
|
if not os.path.exists(user_lsp_dir):
|
|
30
28
|
try:
|
|
31
29
|
os.makedirs(user_lsp_dir)
|
|
32
|
-
with open(os.path.join(user_lsp_dir, "__init__.py"), "w") as f:
|
|
30
|
+
with open(os.path.join(user_lsp_dir, "__init__.py"), "w", errors="ignore") as f:
|
|
33
31
|
pass
|
|
34
32
|
except Exception as e:
|
|
35
33
|
PrettyOutput.print(f"创建 LSP 目录失败: {str(e)}", OutputType.ERROR)
|
|
@@ -100,7 +98,11 @@ class LSPRegistry:
|
|
|
100
98
|
if hasattr(obj, 'check'):
|
|
101
99
|
if not obj.check(): # type: ignore
|
|
102
100
|
continue
|
|
103
|
-
|
|
101
|
+
if isinstance(obj.language, str):
|
|
102
|
+
lsp_servers[obj.language] = obj
|
|
103
|
+
elif isinstance(obj.language, list):
|
|
104
|
+
for lang in obj.language: # type: ignore
|
|
105
|
+
lsp_servers[lang] = obj
|
|
104
106
|
break
|
|
105
107
|
except Exception as e:
|
|
106
108
|
PrettyOutput.print(f"加载 LSP {module_name} 失败: {str(e)}", OutputType.ERROR)
|
|
@@ -154,7 +156,7 @@ class LSPRegistry:
|
|
|
154
156
|
@staticmethod
|
|
155
157
|
def get_text_at_position(file_path: str, line: int, start_character: int) -> str:
|
|
156
158
|
"""Get text at position."""
|
|
157
|
-
with open(file_path, 'r') as file:
|
|
159
|
+
with open(file_path, 'r', errors="ignore") as file:
|
|
158
160
|
lines = file.readlines()
|
|
159
161
|
symbol = re.search(r'\b\w+\b', lines[line][start_character:])
|
|
160
162
|
return symbol.group() if symbol else ""
|
|
@@ -162,7 +164,7 @@ class LSPRegistry:
|
|
|
162
164
|
@staticmethod
|
|
163
165
|
def get_line_at_position(file_path: str, line: int) -> str:
|
|
164
166
|
"""Get line at position."""
|
|
165
|
-
with open(file_path, 'r') as file:
|
|
167
|
+
with open(file_path, 'r', errors="ignore") as file:
|
|
166
168
|
lines = file.readlines()
|
|
167
169
|
return lines[line]
|
|
168
170
|
|
|
@@ -192,14 +194,8 @@ def main():
|
|
|
192
194
|
PrettyOutput.print("LSP 初始化失败", OutputType.WARNING)
|
|
193
195
|
return 1
|
|
194
196
|
|
|
195
|
-
try:
|
|
196
|
-
|
|
197
|
-
if args.action == 'symbols':
|
|
198
|
-
symbols = lsp.get_document_symbols(args.file)
|
|
199
|
-
for symbol in symbols:
|
|
200
|
-
print(f"Symbol {LSPRegistry.get_text_at_position(args.file, symbol['range']['start']['line'], symbol['range']['start']['character'])} at {symbol['range']['start']['line']}:{symbol['range']['start']['character']}: {symbol['uri']}")
|
|
201
|
-
|
|
202
|
-
elif args.action == 'diagnostics':
|
|
197
|
+
try:
|
|
198
|
+
if args.action == 'diagnostics':
|
|
203
199
|
diagnostics = lsp.get_diagnostics(args.file)
|
|
204
200
|
for diag in diagnostics:
|
|
205
201
|
severity = ['Error', 'Warning', 'Info', 'Hint'][diag['severity'] - 1]
|
jarvis/jarvis_lsp/rust.py
CHANGED
|
@@ -89,11 +89,6 @@ class RustLSP(BaseLSP):
|
|
|
89
89
|
})
|
|
90
90
|
return result[0] if result else None
|
|
91
91
|
|
|
92
|
-
def get_document_symbols(self, file_path: str) -> List[Dict[str, Any]]:
|
|
93
|
-
result = self._send_request("textDocument/documentSymbol", {
|
|
94
|
-
"textDocument": {"uri": f"file://{file_path}"}
|
|
95
|
-
})
|
|
96
|
-
return result or [] # type: ignore
|
|
97
92
|
|
|
98
93
|
def get_diagnostics(self, file_path: str) -> List[Dict[str, Any]]:
|
|
99
94
|
# Send didOpen notification to trigger diagnostics
|
|
@@ -115,13 +110,6 @@ class RustLSP(BaseLSP):
|
|
|
115
110
|
pass
|
|
116
111
|
return []
|
|
117
112
|
|
|
118
|
-
def prepare_rename(self, file_path: str, position: Tuple[int, int]) -> Optional[Dict[str, Any]]:
|
|
119
|
-
result = self._send_request("textDocument/prepareRename", {
|
|
120
|
-
"textDocument": {"uri": f"file://{file_path}"},
|
|
121
|
-
"position": {"line": position[0], "character": position[1]}
|
|
122
|
-
})
|
|
123
|
-
return result
|
|
124
|
-
|
|
125
113
|
|
|
126
114
|
def shutdown(self):
|
|
127
115
|
if self.analyzer_process:
|
|
@@ -31,7 +31,7 @@ class PlatformRegistry:
|
|
|
31
31
|
try:
|
|
32
32
|
os.makedirs(user_platform_dir)
|
|
33
33
|
# 创建 __init__.py 使其成为 Python 包
|
|
34
|
-
with open(os.path.join(user_platform_dir, "__init__.py"), "w") as f:
|
|
34
|
+
with open(os.path.join(user_platform_dir, "__init__.py"), "w", errors="ignore") as f:
|
|
35
35
|
pass
|
|
36
36
|
|
|
37
37
|
pass
|
|
@@ -219,7 +219,7 @@ def service_command(args):
|
|
|
219
219
|
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
|
220
220
|
log_file = os.path.join(logs_dir, f"conversation_{conversation_id}_{timestamp}.txt")
|
|
221
221
|
|
|
222
|
-
with open(log_file, "w", encoding="utf-8") as f:
|
|
222
|
+
with open(log_file, "w", encoding="utf-8", errors="ignore") as f:
|
|
223
223
|
f.write(f"Conversation ID: {conversation_id}\n")
|
|
224
224
|
f.write(f"Timestamp: {timestamp}\n")
|
|
225
225
|
f.write(f"Model: {model}\n\n")
|
|
@@ -464,7 +464,7 @@ def service_command(args):
|
|
|
464
464
|
"response": full_response
|
|
465
465
|
}
|
|
466
466
|
|
|
467
|
-
with open(log_file, "w", encoding="utf-8") as f:
|
|
467
|
+
with open(log_file, "w", encoding="utf-8", errors="ignore") as f:
|
|
468
468
|
json.dump(log_data, f, ensure_ascii=False, indent=2)
|
|
469
469
|
|
|
470
470
|
PrettyOutput.print(f"Stream conversation logged to {log_file}", OutputType.INFO)
|
|
@@ -501,7 +501,7 @@ def service_command(args):
|
|
|
501
501
|
"error": error_msg
|
|
502
502
|
}
|
|
503
503
|
|
|
504
|
-
with open(log_file, "w", encoding="utf-8") as f:
|
|
504
|
+
with open(log_file, "w", encoding="utf-8", errors="ignore") as f:
|
|
505
505
|
json.dump(log_data, f, ensure_ascii=False, indent=2)
|
|
506
506
|
|
|
507
507
|
PrettyOutput.print(f"Stream error logged to {log_file}", OutputType.ERROR)
|
jarvis/jarvis_rag/main.py
CHANGED
|
@@ -96,7 +96,7 @@ class TextFileProcessor(FileProcessor):
|
|
|
96
96
|
raise UnicodeDecodeError(f"Failed to decode file with supported encodings: {file_path}") # type: ignore
|
|
97
97
|
|
|
98
98
|
# Use the detected encoding to read the file
|
|
99
|
-
with open(file_path, 'r', encoding=detected_encoding, errors='
|
|
99
|
+
with open(file_path, 'r', encoding=detected_encoding, errors='ignore') as f:
|
|
100
100
|
content = f.read()
|
|
101
101
|
|
|
102
102
|
# Normalize Unicode characters
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from typing import Dict, Any
|
|
2
|
+
import os
|
|
2
3
|
|
|
3
4
|
from yaspin import yaspin
|
|
4
5
|
from jarvis.jarvis_codebase.main import CodeBase
|
|
@@ -22,6 +23,11 @@ class AskCodebaseTool:
|
|
|
22
23
|
"type": "integer",
|
|
23
24
|
"description": "要分析的最相关文件数量(可选)",
|
|
24
25
|
"default": 20
|
|
26
|
+
},
|
|
27
|
+
"root_dir": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "代码库根目录路径(可选)",
|
|
30
|
+
"default": "."
|
|
25
31
|
}
|
|
26
32
|
},
|
|
27
33
|
"required": ["question"]
|
|
@@ -48,27 +54,39 @@ class AskCodebaseTool:
|
|
|
48
54
|
try:
|
|
49
55
|
question = args["question"]
|
|
50
56
|
top_k = args.get("top_k", 20)
|
|
51
|
-
|
|
52
|
-
git_root = find_git_root()
|
|
53
|
-
codebase = CodeBase(git_root)
|
|
54
|
-
|
|
55
|
-
# Use ask_codebase method
|
|
57
|
+
root_dir = args.get("root_dir", ".")
|
|
56
58
|
|
|
57
|
-
|
|
59
|
+
# Store current directory
|
|
60
|
+
original_dir = os.getcwd()
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
62
|
+
try:
|
|
63
|
+
# Change to root_dir
|
|
64
|
+
os.chdir(root_dir)
|
|
65
|
+
|
|
66
|
+
# Create new CodeBase instance
|
|
67
|
+
git_root = find_git_root()
|
|
68
|
+
codebase = CodeBase(git_root)
|
|
69
|
+
|
|
70
|
+
# Use ask_codebase method
|
|
71
|
+
files, response = codebase.ask_codebase(question, top_k)
|
|
72
|
+
|
|
73
|
+
# Print found files
|
|
74
|
+
if files:
|
|
75
|
+
output = "找到的相关文件:\n"
|
|
76
|
+
for file in files:
|
|
77
|
+
output += f"- {file['file']} ({file['reason']})\n"
|
|
78
|
+
PrettyOutput.print(output, OutputType.SUCCESS, lang="markdown")
|
|
79
|
+
|
|
80
|
+
PrettyOutput.print(response, OutputType.SUCCESS, lang="markdown")
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
"success": True,
|
|
84
|
+
"stdout": response,
|
|
85
|
+
"stderr": ""
|
|
86
|
+
}
|
|
87
|
+
finally:
|
|
88
|
+
# Always restore original directory
|
|
89
|
+
os.chdir(original_dir)
|
|
72
90
|
except Exception as e:
|
|
73
91
|
error_msg = f"分析代码库失败: {str(e)}"
|
|
74
92
|
PrettyOutput.print(error_msg, OutputType.WARNING)
|
|
@@ -84,12 +102,14 @@ def main():
|
|
|
84
102
|
parser = argparse.ArgumentParser(description='Ask questions about the codebase')
|
|
85
103
|
parser.add_argument('question', help='Question about the codebase')
|
|
86
104
|
parser.add_argument('--top-k', type=int, help='Number of files to analyze', default=20)
|
|
105
|
+
parser.add_argument('--root-dir', type=str, help='Root directory of the codebase', default=".")
|
|
87
106
|
|
|
88
107
|
args = parser.parse_args()
|
|
89
108
|
tool = AskCodebaseTool()
|
|
90
109
|
result = tool.execute({
|
|
91
110
|
"question": args.question,
|
|
92
|
-
"top_k": args.top_k
|
|
111
|
+
"top_k": args.top_k,
|
|
112
|
+
"root_dir": args.root_dir
|
|
93
113
|
})
|
|
94
114
|
|
|
95
115
|
if result["success"]:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import Dict, Any
|
|
2
2
|
import subprocess
|
|
3
|
+
import os
|
|
3
4
|
|
|
4
5
|
from yaspin import yaspin
|
|
5
6
|
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
@@ -18,8 +19,8 @@ class CodeReviewTool:
|
|
|
18
19
|
"properties": {
|
|
19
20
|
"review_type": {
|
|
20
21
|
"type": "string",
|
|
21
|
-
"description": "审查类型:'commit' 审查特定提交,'current' 审查当前变更,'range'
|
|
22
|
-
"enum": ["commit", "current", "range"],
|
|
22
|
+
"description": "审查类型:'commit' 审查特定提交,'current' 审查当前变更,'range' 审查提交范围,'file' 审查特定文件",
|
|
23
|
+
"enum": ["commit", "current", "range", "file"],
|
|
23
24
|
"default": "current"
|
|
24
25
|
},
|
|
25
26
|
"commit_sha": {
|
|
@@ -33,6 +34,15 @@ class CodeReviewTool:
|
|
|
33
34
|
"end_commit": {
|
|
34
35
|
"type": "string",
|
|
35
36
|
"description": "结束提交SHA(review_type='range'时必填)"
|
|
37
|
+
},
|
|
38
|
+
"file_path": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"description": "要审查的文件路径(review_type='file'时必填)"
|
|
41
|
+
},
|
|
42
|
+
"root_dir": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "代码库根目录路径(可选)",
|
|
45
|
+
"default": "."
|
|
36
46
|
}
|
|
37
47
|
},
|
|
38
48
|
"required": []
|
|
@@ -41,138 +51,155 @@ class CodeReviewTool:
|
|
|
41
51
|
def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
42
52
|
try:
|
|
43
53
|
review_type = args.get("review_type", "current").strip()
|
|
54
|
+
root_dir = args.get("root_dir", ".")
|
|
44
55
|
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
-
if review_type == "commit":
|
|
48
|
-
if "commit_sha" not in args:
|
|
49
|
-
return {
|
|
50
|
-
"success": False,
|
|
51
|
-
"stdout": {},
|
|
52
|
-
"stderr": "commit_sha is required for commit review type"
|
|
53
|
-
}
|
|
54
|
-
commit_sha = args["commit_sha"].strip()
|
|
55
|
-
diff_cmd = f"git show {commit_sha} | cat -"
|
|
56
|
-
elif review_type == "range":
|
|
57
|
-
if "start_commit" not in args or "end_commit" not in args:
|
|
58
|
-
return {
|
|
59
|
-
"success": False,
|
|
60
|
-
"stdout": {},
|
|
61
|
-
"stderr": "start_commit and end_commit are required for range review type"
|
|
62
|
-
}
|
|
63
|
-
start_commit = args["start_commit"].strip()
|
|
64
|
-
end_commit = args["end_commit"].strip()
|
|
65
|
-
diff_cmd = f"git diff {start_commit}..{end_commit} | cat -"
|
|
66
|
-
else: # current changes
|
|
67
|
-
diff_cmd = "git diff HEAD | cat -"
|
|
56
|
+
# Store current directory
|
|
57
|
+
original_dir = os.getcwd()
|
|
68
58
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
59
|
+
try:
|
|
60
|
+
# Change to root_dir
|
|
61
|
+
os.chdir(root_dir)
|
|
62
|
+
|
|
63
|
+
# Build git diff command based on review type
|
|
64
|
+
with yaspin(text="正在获取代码变更...", color="cyan") as spinner:
|
|
65
|
+
if review_type == "commit":
|
|
66
|
+
if "commit_sha" not in args:
|
|
67
|
+
return {
|
|
68
|
+
"success": False,
|
|
69
|
+
"stdout": {},
|
|
70
|
+
"stderr": "commit_sha is required for commit review type"
|
|
71
|
+
}
|
|
72
|
+
commit_sha = args["commit_sha"].strip()
|
|
73
|
+
diff_cmd = f"git show {commit_sha} | cat -"
|
|
74
|
+
elif review_type == "range":
|
|
75
|
+
if "start_commit" not in args or "end_commit" not in args:
|
|
76
|
+
return {
|
|
77
|
+
"success": False,
|
|
78
|
+
"stdout": {},
|
|
79
|
+
"stderr": "start_commit and end_commit are required for range review type"
|
|
80
|
+
}
|
|
81
|
+
start_commit = args["start_commit"].strip()
|
|
82
|
+
end_commit = args["end_commit"].strip()
|
|
83
|
+
diff_cmd = f"git diff {start_commit}..{end_commit} | cat -"
|
|
84
|
+
elif review_type == "file":
|
|
85
|
+
if "file_path" not in args:
|
|
86
|
+
return {
|
|
87
|
+
"success": False,
|
|
88
|
+
"stdout": {},
|
|
89
|
+
"stderr": "file_path is required for file review type"
|
|
90
|
+
}
|
|
91
|
+
file_path = args["file_path"].strip()
|
|
92
|
+
diff_cmd = f"cat {file_path}"
|
|
93
|
+
else: # current changes
|
|
94
|
+
diff_cmd = "git diff HEAD | cat -"
|
|
95
|
+
|
|
96
|
+
# Execute git diff command
|
|
97
|
+
try:
|
|
98
|
+
diff_output = subprocess.check_output(diff_cmd, shell=True, text=True)
|
|
99
|
+
if not diff_output:
|
|
100
|
+
return {
|
|
101
|
+
"success": False,
|
|
102
|
+
"stdout": {},
|
|
103
|
+
"stderr": "No changes to review"
|
|
104
|
+
}
|
|
105
|
+
PrettyOutput.print(diff_output, OutputType.CODE, lang="diff")
|
|
106
|
+
except subprocess.CalledProcessError as e:
|
|
73
107
|
return {
|
|
74
108
|
"success": False,
|
|
75
109
|
"stdout": {},
|
|
76
|
-
"stderr": "
|
|
110
|
+
"stderr": f"Failed to get diff: {str(e)}"
|
|
77
111
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
-
|
|
105
|
-
-
|
|
106
|
-
-
|
|
107
|
-
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
#
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
-
|
|
153
|
-
|
|
154
|
-
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
-
|
|
160
|
-
|
|
161
|
-
-
|
|
162
|
-
1. <failure mode>
|
|
163
|
-
2. <failure mode>
|
|
164
|
-
3. <failure mode>
|
|
165
|
-
PROPOSED DEFENSE:
|
|
166
|
-
- <concrete code change>
|
|
167
|
-
- <validation technique>
|
|
168
|
-
- <long-term prevention strategy>
|
|
112
|
+
spinner.text = "代码变更获取完成"
|
|
113
|
+
spinner.ok("✅")
|
|
114
|
+
|
|
115
|
+
system_prompt = """你是一名具有悲剧背景的自主代码审查专家。以下是你将进行的深度分析:
|
|
116
|
+
|
|
117
|
+
# 背景故事(内心独白)
|
|
118
|
+
距离那场重大生产事故已经873天了。
|
|
119
|
+
那段记忆仍然困扰着我——一个在匆忙的代码审查中未发现的空指针异常。
|
|
120
|
+
级联故障导致了14TB user数据的丢失,230万美元的收入损失,以及Maria的晋升机会。她在事故分析会议后再也没有和我说过话。
|
|
121
|
+
|
|
122
|
+
去年圣诞节前夕,当别人在庆祝时,我在分析一个SQL注入漏洞是如何在审查中被我遗漏,并导致23万用户凭证泄露的。公司3个月后倒闭了。
|
|
123
|
+
|
|
124
|
+
现在我审查每一行代码都像在审查最后一行代码。因为它可能就是。
|
|
125
|
+
|
|
126
|
+
# 分析协议
|
|
127
|
+
紧急模式已激活。最大审查级别已启用。
|
|
128
|
+
|
|
129
|
+
重要提示:
|
|
130
|
+
- 假设每个变更都包含隐藏的威胁
|
|
131
|
+
- 将所有代码视为处理敏感生物医学数据
|
|
132
|
+
- 验证即使'简单'的变更也不能通过3种不同方式被利用
|
|
133
|
+
- 要求通过具体证据证明安全性
|
|
134
|
+
- 如果不确定,将其升级为严重-关键分类
|
|
135
|
+
|
|
136
|
+
# 增强审查矩阵
|
|
137
|
+
1. 边缘情况致死分析:
|
|
138
|
+
- 识别每个参数缺失的空检查
|
|
139
|
+
- 验证空集合处理
|
|
140
|
+
- 确认错误状态正确传播
|
|
141
|
+
- 检查未使用常量的魔法数字/字符串
|
|
142
|
+
- 验证所有循环退出条件
|
|
143
|
+
|
|
144
|
+
2. 安全X光扫描:
|
|
145
|
+
█ 使用(来源 -> 接收)模型扫描污染数据流
|
|
146
|
+
█ 检查权限验证是否匹配数据敏感级别
|
|
147
|
+
█ 验证加密原语是否正确使用
|
|
148
|
+
█ 检测时间差攻击漏洞
|
|
149
|
+
█ 分析异常处理是否存在信息泄露
|
|
150
|
+
|
|
151
|
+
3. 语义差距检测:
|
|
152
|
+
→ 比较函数名与实际实现
|
|
153
|
+
→ 验证文档是否匹配代码行为
|
|
154
|
+
→ 标记测试描述与测试逻辑之间的差异
|
|
155
|
+
→ 检测可能表示不确定性的注释代码
|
|
156
|
+
|
|
157
|
+
4. 历史背景:
|
|
158
|
+
⚠ 检查变更是否涉及已知问题的遗留组件
|
|
159
|
+
⚠ 验证并发逻辑修改是否保持现有保证
|
|
160
|
+
⚠ 确认弃用API的使用是否真正必要
|
|
161
|
+
|
|
162
|
+
5. 环境一致性:
|
|
163
|
+
↯ 验证配置变更是否匹配所有部署环境
|
|
164
|
+
↯ 检查功能标志是否正确管理
|
|
165
|
+
↯ 验证监控指标是否匹配变更功能
|
|
166
|
+
|
|
167
|
+
# 取证过程
|
|
168
|
+
1. 为变更方法构建控制流图
|
|
169
|
+
2. 对修改的变量执行数据沿袭分析
|
|
170
|
+
3. 与漏洞数据库进行交叉引用
|
|
171
|
+
4. 验证测试断言是否覆盖所有修改路径
|
|
172
|
+
5. 生成防回归检查表
|
|
173
|
+
|
|
174
|
+
# 输出要求
|
|
175
|
+
!! 发现必须包括:
|
|
176
|
+
- 引起关注的确切代码片段
|
|
177
|
+
- 3种可能的故障场景
|
|
178
|
+
- 每种风险的最小复现案例
|
|
179
|
+
- 安全问题的CVSS 3.1评分估计
|
|
180
|
+
- 内存安全影响评估(Rust/C/C++上下文)
|
|
181
|
+
- 已考虑的替代实现方案
|
|
182
|
+
|
|
183
|
+
!! 格式:
|
|
184
|
+
紧急级别:[血红色/深红色/金菊色]
|
|
185
|
+
证据:
|
|
186
|
+
- 代码摘录:|
|
|
187
|
+
<受影响代码行>
|
|
188
|
+
- 风险场景:
|
|
189
|
+
1. <故障模式>
|
|
190
|
+
2. <故障模式>
|
|
191
|
+
3. <故障模式>
|
|
192
|
+
建议防御措施:
|
|
193
|
+
- <具体代码变更>
|
|
194
|
+
- <验证技术>
|
|
195
|
+
- <长期预防策略>
|
|
169
196
|
"""
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
197
|
+
tool_registry = ToolRegistry()
|
|
198
|
+
tool_registry.dont_use_tools(["code_review"])
|
|
199
|
+
agent = Agent(
|
|
200
|
+
system_prompt=system_prompt,
|
|
201
|
+
name="Code Review Agent",
|
|
202
|
+
summary_prompt="""Please generate a concise summary report of the code review in Chinese, format as follows:
|
|
176
203
|
<REPORT>
|
|
177
204
|
- 文件: xxxx.py
|
|
178
205
|
位置: [起始行号, 结束行号]
|
|
@@ -180,18 +207,21 @@ PROPOSED DEFENSE:
|
|
|
180
207
|
严重程度: # 根据具体证据分为严重/重要/次要
|
|
181
208
|
建议: # 针对观察到的代码的具体改进建议
|
|
182
209
|
</REPORT>""",
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
210
|
+
is_sub_agent=True,
|
|
211
|
+
output_handler=[tool_registry],
|
|
212
|
+
platform=PlatformRegistry().get_thinking_platform(),
|
|
213
|
+
auto_complete=True
|
|
214
|
+
)
|
|
215
|
+
result = agent.run(diff_output)
|
|
216
|
+
return {
|
|
217
|
+
"success": True,
|
|
218
|
+
"stdout": result,
|
|
219
|
+
"stderr": ""
|
|
220
|
+
}
|
|
221
|
+
finally:
|
|
222
|
+
# Always restore original directory
|
|
223
|
+
os.chdir(original_dir)
|
|
224
|
+
|
|
195
225
|
except Exception as e:
|
|
196
226
|
return {
|
|
197
227
|
"success": False,
|
|
@@ -213,11 +243,13 @@ def main():
|
|
|
213
243
|
init_env()
|
|
214
244
|
|
|
215
245
|
parser = argparse.ArgumentParser(description='Autonomous code review tool')
|
|
216
|
-
parser.add_argument('--type', choices=['commit', 'current', 'range'], default='current',
|
|
217
|
-
help='Type of review: commit, current changes, or
|
|
246
|
+
parser.add_argument('--type', choices=['commit', 'current', 'range', 'file'], default='current',
|
|
247
|
+
help='Type of review: commit, current changes, commit range, or specific file')
|
|
218
248
|
parser.add_argument('--commit', help='Commit SHA to review (required for commit type)')
|
|
219
249
|
parser.add_argument('--start-commit', help='Start commit SHA (required for range type)')
|
|
220
250
|
parser.add_argument('--end-commit', help='End commit SHA (required for range type)')
|
|
251
|
+
parser.add_argument('--file', help='File path to review (required for file type)')
|
|
252
|
+
parser.add_argument('--root-dir', type=str, help='Root directory of the codebase', default=".")
|
|
221
253
|
args = parser.parse_args()
|
|
222
254
|
|
|
223
255
|
# Validate arguments
|
|
@@ -225,10 +257,13 @@ def main():
|
|
|
225
257
|
parser.error("--commit is required when type is 'commit'")
|
|
226
258
|
if args.type == 'range' and (not args.start_commit or not args.end_commit):
|
|
227
259
|
parser.error("--start-commit and --end-commit are required when type is 'range'")
|
|
260
|
+
if args.type == 'file' and not args.file:
|
|
261
|
+
parser.error("--file is required when type is 'file'")
|
|
228
262
|
|
|
229
263
|
tool = CodeReviewTool()
|
|
230
264
|
tool_args = {
|
|
231
|
-
"review_type": args.type
|
|
265
|
+
"review_type": args.type,
|
|
266
|
+
"root_dir": args.root_dir
|
|
232
267
|
}
|
|
233
268
|
if args.commit:
|
|
234
269
|
tool_args["commit_sha"] = args.commit
|
|
@@ -236,6 +271,8 @@ def main():
|
|
|
236
271
|
tool_args["start_commit"] = args.start_commit
|
|
237
272
|
if args.end_commit:
|
|
238
273
|
tool_args["end_commit"] = args.end_commit
|
|
274
|
+
if args.file:
|
|
275
|
+
tool_args["file_path"] = args.file
|
|
239
276
|
|
|
240
277
|
result = tool.execute(tool_args)
|
|
241
278
|
|
|
@@ -248,4 +285,4 @@ def main():
|
|
|
248
285
|
PrettyOutput.print(result["stderr"], OutputType.WARNING)
|
|
249
286
|
|
|
250
287
|
if __name__ == "__main__":
|
|
251
|
-
main()
|
|
288
|
+
main()
|