jarvis-ai-assistant 0.1.138__py3-none-any.whl → 0.1.141__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 +62 -14
- jarvis/jarvis_agent/builtin_input_handler.py +4 -14
- jarvis/jarvis_agent/main.py +1 -1
- jarvis/jarvis_agent/patch.py +37 -40
- jarvis/jarvis_agent/shell_input_handler.py +2 -3
- jarvis/jarvis_code_agent/code_agent.py +23 -30
- jarvis/jarvis_code_analysis/checklists/__init__.py +3 -0
- jarvis/jarvis_code_analysis/checklists/c_cpp.py +50 -0
- jarvis/jarvis_code_analysis/checklists/csharp.py +75 -0
- jarvis/jarvis_code_analysis/checklists/data_format.py +82 -0
- jarvis/jarvis_code_analysis/checklists/devops.py +107 -0
- jarvis/jarvis_code_analysis/checklists/docs.py +87 -0
- jarvis/jarvis_code_analysis/checklists/go.py +52 -0
- jarvis/jarvis_code_analysis/checklists/infrastructure.py +98 -0
- jarvis/jarvis_code_analysis/checklists/java.py +66 -0
- jarvis/jarvis_code_analysis/checklists/javascript.py +73 -0
- jarvis/jarvis_code_analysis/checklists/kotlin.py +107 -0
- jarvis/jarvis_code_analysis/checklists/loader.py +76 -0
- jarvis/jarvis_code_analysis/checklists/php.py +77 -0
- jarvis/jarvis_code_analysis/checklists/python.py +56 -0
- jarvis/jarvis_code_analysis/checklists/ruby.py +107 -0
- jarvis/jarvis_code_analysis/checklists/rust.py +58 -0
- jarvis/jarvis_code_analysis/checklists/shell.py +75 -0
- jarvis/jarvis_code_analysis/checklists/sql.py +72 -0
- jarvis/jarvis_code_analysis/checklists/swift.py +77 -0
- jarvis/jarvis_code_analysis/checklists/web.py +97 -0
- jarvis/jarvis_code_analysis/code_review.py +660 -0
- jarvis/jarvis_dev/main.py +61 -88
- jarvis/jarvis_git_squash/main.py +3 -3
- jarvis/jarvis_git_utils/git_commiter.py +242 -0
- jarvis/jarvis_init/main.py +62 -0
- jarvis/jarvis_platform/base.py +4 -0
- jarvis/jarvis_platform/kimi.py +173 -5
- jarvis/jarvis_platform/openai.py +3 -0
- jarvis/jarvis_platform/registry.py +1 -0
- jarvis/jarvis_platform/yuanbao.py +275 -5
- jarvis/jarvis_tools/ask_codebase.py +6 -9
- jarvis/jarvis_tools/ask_user.py +17 -5
- jarvis/jarvis_tools/base.py +3 -1
- jarvis/jarvis_tools/chdir.py +1 -0
- jarvis/jarvis_tools/create_code_agent.py +4 -3
- jarvis/jarvis_tools/create_sub_agent.py +1 -0
- jarvis/jarvis_tools/execute_script.py +170 -0
- jarvis/jarvis_tools/file_analyzer.py +90 -239
- jarvis/jarvis_tools/file_operation.py +99 -31
- jarvis/jarvis_tools/{find_methodolopy.py → find_methodology.py} +2 -1
- jarvis/jarvis_tools/lsp_get_diagnostics.py +2 -0
- jarvis/jarvis_tools/methodology.py +11 -11
- jarvis/jarvis_tools/read_code.py +2 -0
- jarvis/jarvis_tools/read_webpage.py +33 -196
- jarvis/jarvis_tools/registry.py +68 -131
- jarvis/jarvis_tools/search_web.py +14 -6
- jarvis/jarvis_tools/virtual_tty.py +399 -0
- jarvis/jarvis_utils/config.py +29 -3
- jarvis/jarvis_utils/embedding.py +0 -317
- jarvis/jarvis_utils/file_processors.py +343 -0
- jarvis/jarvis_utils/input.py +0 -1
- jarvis/jarvis_utils/methodology.py +94 -435
- jarvis/jarvis_utils/utils.py +207 -9
- {jarvis_ai_assistant-0.1.138.dist-info → jarvis_ai_assistant-0.1.141.dist-info}/METADATA +4 -4
- jarvis_ai_assistant-0.1.141.dist-info/RECORD +94 -0
- {jarvis_ai_assistant-0.1.138.dist-info → jarvis_ai_assistant-0.1.141.dist-info}/entry_points.txt +4 -4
- jarvis/jarvis_code_agent/file_select.py +0 -202
- jarvis/jarvis_platform/ai8.py +0 -268
- jarvis/jarvis_platform/ollama.py +0 -137
- jarvis/jarvis_platform/oyi.py +0 -307
- jarvis/jarvis_rag/file_processors.py +0 -138
- jarvis/jarvis_rag/main.py +0 -1734
- jarvis/jarvis_tools/code_review.py +0 -333
- jarvis/jarvis_tools/execute_python_script.py +0 -58
- jarvis/jarvis_tools/execute_shell.py +0 -97
- jarvis/jarvis_tools/execute_shell_script.py +0 -58
- jarvis/jarvis_tools/find_caller.py +0 -278
- jarvis/jarvis_tools/find_symbol.py +0 -295
- jarvis/jarvis_tools/function_analyzer.py +0 -331
- jarvis/jarvis_tools/git_commiter.py +0 -167
- jarvis/jarvis_tools/project_analyzer.py +0 -304
- jarvis/jarvis_tools/rag.py +0 -143
- jarvis/jarvis_tools/tool_generator.py +0 -221
- jarvis_ai_assistant-0.1.138.dist-info/RECORD +0 -85
- /jarvis/{jarvis_rag → jarvis_init}/__init__.py +0 -0
- {jarvis_ai_assistant-0.1.138.dist-info → jarvis_ai_assistant-0.1.141.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.138.dist-info → jarvis_ai_assistant-0.1.141.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.138.dist-info → jarvis_ai_assistant-0.1.141.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
from typing import Dict, Any, List
|
|
2
|
+
import subprocess
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import tempfile
|
|
6
|
+
|
|
7
|
+
from yaspin import yaspin
|
|
8
|
+
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
9
|
+
from jarvis.jarvis_tools.read_code import ReadCodeTool
|
|
10
|
+
from jarvis.jarvis_tools.registry import ToolRegistry
|
|
11
|
+
from jarvis.jarvis_agent import Agent
|
|
12
|
+
|
|
13
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
14
|
+
from jarvis.jarvis_utils.utils import ct, ot, init_env
|
|
15
|
+
from jarvis.jarvis_code_analysis.checklists.loader import get_language_checklist
|
|
16
|
+
|
|
17
|
+
class CodeReviewTool:
|
|
18
|
+
name = "code_review"
|
|
19
|
+
description = "自动代码审查工具,用于分析代码变更"
|
|
20
|
+
labels = ['code', 'analysis', 'review']
|
|
21
|
+
parameters = {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"properties": {
|
|
24
|
+
"review_type": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "审查类型:'commit' 审查特定提交,'current' 审查当前变更,'range' 审查提交范围,'file' 审查特定文件",
|
|
27
|
+
"enum": ["commit", "current", "range", "file"],
|
|
28
|
+
"default": "current"
|
|
29
|
+
},
|
|
30
|
+
"commit_sha": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "要分析的提交SHA(review_type='commit'时必填)"
|
|
33
|
+
},
|
|
34
|
+
"start_commit": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "起始提交SHA(review_type='range'时必填)"
|
|
37
|
+
},
|
|
38
|
+
"end_commit": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"description": "结束提交SHA(review_type='range'时必填)"
|
|
41
|
+
},
|
|
42
|
+
"file_path": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "要审查的文件路径(review_type='file'时必填)"
|
|
45
|
+
},
|
|
46
|
+
"root_dir": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"description": "代码库根目录路径(可选)",
|
|
49
|
+
"default": "."
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"required": []
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
def _detect_languages_from_files(self, file_paths: List[str]) -> List[str]:
|
|
56
|
+
"""
|
|
57
|
+
Detect programming languages from a list of file paths using file extensions.
|
|
58
|
+
Returns a list of detected languages ('c_cpp', 'go', 'python', 'rust', 'java', 'javascript', 'typescript', etc.).
|
|
59
|
+
"""
|
|
60
|
+
if not file_paths:
|
|
61
|
+
return []
|
|
62
|
+
|
|
63
|
+
# Extension-based language detection
|
|
64
|
+
languages = set()
|
|
65
|
+
for file_path in file_paths:
|
|
66
|
+
file_path = file_path.lower()
|
|
67
|
+
_, ext = os.path.splitext(file_path)
|
|
68
|
+
|
|
69
|
+
# Get base name for special files without extensions
|
|
70
|
+
base_name = os.path.basename(file_path)
|
|
71
|
+
|
|
72
|
+
# C/C++
|
|
73
|
+
if ext in ['.c', '.cpp', '.cc', '.cxx', '.h', '.hpp', '.hxx', '.inl', '.ipp']:
|
|
74
|
+
languages.add('c_cpp')
|
|
75
|
+
|
|
76
|
+
# Go
|
|
77
|
+
elif ext in ['.go']:
|
|
78
|
+
languages.add('go')
|
|
79
|
+
|
|
80
|
+
# Python
|
|
81
|
+
elif ext in ['.py', '.pyw', '.pyi', '.pyx', '.pxd'] or base_name in ['requirements.txt', 'setup.py', 'pyproject.toml']:
|
|
82
|
+
languages.add('python')
|
|
83
|
+
|
|
84
|
+
# Rust
|
|
85
|
+
elif ext in ['.rs', '.rlib'] or base_name in ['Cargo.toml', 'Cargo.lock']:
|
|
86
|
+
languages.add('rust')
|
|
87
|
+
|
|
88
|
+
# Java
|
|
89
|
+
elif ext in ['.java', '.class', '.jar'] or base_name in ['pom.xml', 'build.gradle']:
|
|
90
|
+
languages.add('java')
|
|
91
|
+
|
|
92
|
+
# JavaScript
|
|
93
|
+
elif ext in ['.js', '.mjs', '.cjs', '.jsx']:
|
|
94
|
+
languages.add('javascript')
|
|
95
|
+
|
|
96
|
+
# TypeScript
|
|
97
|
+
elif ext in ['.ts', '.tsx', '.cts', '.mts']:
|
|
98
|
+
languages.add('typescript')
|
|
99
|
+
|
|
100
|
+
# PHP
|
|
101
|
+
elif ext in ['.php', '.phtml', '.php5', '.php7', '.phps']:
|
|
102
|
+
languages.add('php')
|
|
103
|
+
|
|
104
|
+
# Ruby
|
|
105
|
+
elif ext in ['.rb', '.rake', '.gemspec'] or base_name in ['Gemfile', 'Rakefile']:
|
|
106
|
+
languages.add('ruby')
|
|
107
|
+
|
|
108
|
+
# Swift
|
|
109
|
+
elif ext in ['.swift']:
|
|
110
|
+
languages.add('swift')
|
|
111
|
+
|
|
112
|
+
# Kotlin
|
|
113
|
+
elif ext in ['.kt', '.kts']:
|
|
114
|
+
languages.add('kotlin')
|
|
115
|
+
|
|
116
|
+
# C#
|
|
117
|
+
elif ext in ['.cs', '.csx']:
|
|
118
|
+
languages.add('csharp')
|
|
119
|
+
|
|
120
|
+
# SQL
|
|
121
|
+
elif ext in ['.sql']:
|
|
122
|
+
languages.add('sql')
|
|
123
|
+
|
|
124
|
+
# Shell/Bash
|
|
125
|
+
elif ext in ['.sh', '.bash'] or base_name.startswith('.bash') or base_name.startswith('.zsh'):
|
|
126
|
+
languages.add('shell')
|
|
127
|
+
|
|
128
|
+
# HTML/CSS
|
|
129
|
+
elif ext in ['.html', '.htm', '.xhtml']:
|
|
130
|
+
languages.add('html')
|
|
131
|
+
elif ext in ['.css', '.scss', '.sass', '.less']:
|
|
132
|
+
languages.add('css')
|
|
133
|
+
|
|
134
|
+
# XML/JSON/YAML (config files)
|
|
135
|
+
elif ext in ['.xml', '.xsd', '.dtd', '.tld', '.jsp', '.jspx', '.tag', '.tagx']:
|
|
136
|
+
languages.add('xml')
|
|
137
|
+
elif ext in ['.json', '.jsonl', '.json5']:
|
|
138
|
+
languages.add('json')
|
|
139
|
+
elif ext in ['.yaml', '.yml']:
|
|
140
|
+
languages.add('yaml')
|
|
141
|
+
|
|
142
|
+
# Markdown/Documentation
|
|
143
|
+
elif ext in ['.md', '.markdown', '.rst', '.adoc']:
|
|
144
|
+
languages.add('markdown')
|
|
145
|
+
|
|
146
|
+
# Docker
|
|
147
|
+
elif ext in ['.dockerfile'] or base_name in ['Dockerfile', 'docker-compose.yml', 'docker-compose.yaml']:
|
|
148
|
+
languages.add('docker')
|
|
149
|
+
|
|
150
|
+
# Terraform
|
|
151
|
+
elif ext in ['.tf', '.tfvars']:
|
|
152
|
+
languages.add('terraform')
|
|
153
|
+
|
|
154
|
+
# Makefile
|
|
155
|
+
elif ext in ['.mk'] or base_name == 'Makefile':
|
|
156
|
+
languages.add('makefile')
|
|
157
|
+
|
|
158
|
+
# Map to our primary language groups for checklist purposes
|
|
159
|
+
primary_languages = set()
|
|
160
|
+
language_mapping = {
|
|
161
|
+
'c_cpp': 'c_cpp',
|
|
162
|
+
'go': 'go',
|
|
163
|
+
'python': 'python',
|
|
164
|
+
'rust': 'rust',
|
|
165
|
+
'java': 'java',
|
|
166
|
+
'javascript': 'javascript',
|
|
167
|
+
'typescript': 'typescript',
|
|
168
|
+
'php': 'php',
|
|
169
|
+
'ruby': 'ruby',
|
|
170
|
+
'swift': 'swift',
|
|
171
|
+
'kotlin': 'kotlin',
|
|
172
|
+
'csharp': 'csharp',
|
|
173
|
+
'sql': 'sql',
|
|
174
|
+
'shell': 'shell',
|
|
175
|
+
'html': 'html',
|
|
176
|
+
'css': 'css',
|
|
177
|
+
'xml': 'xml',
|
|
178
|
+
'json': 'json',
|
|
179
|
+
'yaml': 'yaml',
|
|
180
|
+
'markdown': 'docs',
|
|
181
|
+
'docker': 'docker',
|
|
182
|
+
'terraform': 'terraform',
|
|
183
|
+
'makefile': 'devops'
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
# Map detected languages to primary language groups
|
|
187
|
+
for lang in languages:
|
|
188
|
+
primary_lang = language_mapping.get(lang)
|
|
189
|
+
if primary_lang:
|
|
190
|
+
# Only keep languages we have checklists for
|
|
191
|
+
if primary_lang in ['c_cpp', 'go', 'python', 'rust', 'java', 'javascript', 'typescript',
|
|
192
|
+
'csharp', 'swift', 'php', 'shell', 'sql', 'ruby', 'kotlin',
|
|
193
|
+
'html', 'css', 'xml', 'json', 'yaml', 'docker', 'terraform',
|
|
194
|
+
'docs', 'markdown', 'devops', 'makefile']:
|
|
195
|
+
primary_languages.add(primary_lang)
|
|
196
|
+
|
|
197
|
+
return list(primary_languages)
|
|
198
|
+
|
|
199
|
+
def _get_language_checklist(self, language: str) -> str:
|
|
200
|
+
"""Get the checklist for a specific language."""
|
|
201
|
+
checklist = get_language_checklist(language)
|
|
202
|
+
return checklist if checklist else ""
|
|
203
|
+
|
|
204
|
+
def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
205
|
+
try:
|
|
206
|
+
review_type = args.get("review_type", "current").strip()
|
|
207
|
+
root_dir = args.get("root_dir", ".")
|
|
208
|
+
|
|
209
|
+
# Store current directory
|
|
210
|
+
original_dir = os.getcwd()
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
# Change to root_dir
|
|
214
|
+
os.chdir(root_dir)
|
|
215
|
+
|
|
216
|
+
# Variables to store file paths and diff output
|
|
217
|
+
file_paths = []
|
|
218
|
+
diff_output = ""
|
|
219
|
+
|
|
220
|
+
# Build git diff command based on review type
|
|
221
|
+
with yaspin(text="正在获取代码变更...", color="cyan") as spinner:
|
|
222
|
+
if review_type == "commit":
|
|
223
|
+
if "commit_sha" not in args:
|
|
224
|
+
return {
|
|
225
|
+
"success": False,
|
|
226
|
+
"stdout": {},
|
|
227
|
+
"stderr": "commit_sha is required for commit review type"
|
|
228
|
+
}
|
|
229
|
+
commit_sha = args["commit_sha"].strip()
|
|
230
|
+
diff_cmd = f"git show {commit_sha} | cat -"
|
|
231
|
+
|
|
232
|
+
# Execute git command and get diff output
|
|
233
|
+
diff_output = subprocess.check_output(diff_cmd, shell=True, text=True)
|
|
234
|
+
if not diff_output:
|
|
235
|
+
return {
|
|
236
|
+
"success": False,
|
|
237
|
+
"stdout": {},
|
|
238
|
+
"stderr": "No changes to review"
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
# Extract changed files using git command
|
|
242
|
+
files_cmd = f"git show --name-only --pretty=format: {commit_sha} | grep -v '^$'"
|
|
243
|
+
try:
|
|
244
|
+
files_output = subprocess.check_output(files_cmd, shell=True, text=True)
|
|
245
|
+
file_paths = [f.strip() for f in files_output.split("\n") if f.strip()]
|
|
246
|
+
except subprocess.CalledProcessError:
|
|
247
|
+
# Fallback to regex extraction if git command fails
|
|
248
|
+
file_pattern = r"diff --git a/.*?\s+b/(.*?)(\n|$)"
|
|
249
|
+
files = re.findall(file_pattern, diff_output)
|
|
250
|
+
file_paths = [match[0] for match in files]
|
|
251
|
+
|
|
252
|
+
elif review_type == "range":
|
|
253
|
+
if "start_commit" not in args or "end_commit" not in args:
|
|
254
|
+
return {
|
|
255
|
+
"success": False,
|
|
256
|
+
"stdout": {},
|
|
257
|
+
"stderr": "start_commit and end_commit are required for range review type"
|
|
258
|
+
}
|
|
259
|
+
start_commit = args["start_commit"].strip()
|
|
260
|
+
end_commit = args["end_commit"].strip()
|
|
261
|
+
diff_cmd = f"git diff {start_commit}..{end_commit} | cat -"
|
|
262
|
+
|
|
263
|
+
# Execute git command and get diff output
|
|
264
|
+
diff_output = subprocess.check_output(diff_cmd, shell=True, text=True)
|
|
265
|
+
if not diff_output:
|
|
266
|
+
return {
|
|
267
|
+
"success": False,
|
|
268
|
+
"stdout": {},
|
|
269
|
+
"stderr": "No changes to review"
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
# Extract changed files using git command
|
|
273
|
+
files_cmd = f"git diff --name-only {start_commit}..{end_commit}"
|
|
274
|
+
try:
|
|
275
|
+
files_output = subprocess.check_output(files_cmd, shell=True, text=True)
|
|
276
|
+
file_paths = [f.strip() for f in files_output.split("\n") if f.strip()]
|
|
277
|
+
except subprocess.CalledProcessError:
|
|
278
|
+
# Fallback to regex extraction if git command fails
|
|
279
|
+
file_pattern = r"diff --git a/.*?\s+b/(.*?)(\n|$)"
|
|
280
|
+
files = re.findall(file_pattern, diff_output)
|
|
281
|
+
file_paths = [match[0] for match in files]
|
|
282
|
+
|
|
283
|
+
elif review_type == "file":
|
|
284
|
+
if "file_path" not in args:
|
|
285
|
+
return {
|
|
286
|
+
"success": False,
|
|
287
|
+
"stdout": {},
|
|
288
|
+
"stderr": "file_path is required for file review type"
|
|
289
|
+
}
|
|
290
|
+
file_path = args["file_path"].strip()
|
|
291
|
+
file_paths = [file_path]
|
|
292
|
+
diff_output = ReadCodeTool().execute({"files": [{"path": file_path}]})["stdout"]
|
|
293
|
+
|
|
294
|
+
else: # current changes
|
|
295
|
+
diff_cmd = "git diff HEAD | cat -"
|
|
296
|
+
|
|
297
|
+
# Execute git command and get diff output
|
|
298
|
+
diff_output = subprocess.check_output(diff_cmd, shell=True, text=True)
|
|
299
|
+
if not diff_output:
|
|
300
|
+
return {
|
|
301
|
+
"success": False,
|
|
302
|
+
"stdout": {},
|
|
303
|
+
"stderr": "No changes to review"
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
# Extract changed files using git command
|
|
307
|
+
files_cmd = "git diff --name-only HEAD"
|
|
308
|
+
try:
|
|
309
|
+
files_output = subprocess.check_output(files_cmd, shell=True, text=True)
|
|
310
|
+
file_paths = [f.strip() for f in files_output.split("\n") if f.strip()]
|
|
311
|
+
except subprocess.CalledProcessError:
|
|
312
|
+
# Fallback to regex extraction if git command fails
|
|
313
|
+
file_pattern = r"diff --git a/.*?\s+b/(.*?)(\n|$)"
|
|
314
|
+
files = re.findall(file_pattern, diff_output)
|
|
315
|
+
file_paths = [match[0] for match in files]
|
|
316
|
+
|
|
317
|
+
# Detect languages from the file paths
|
|
318
|
+
detected_languages = self._detect_languages_from_files(file_paths)
|
|
319
|
+
|
|
320
|
+
# Add review type and related information to the diff output
|
|
321
|
+
review_info = f"""
|
|
322
|
+
----- 代码审查信息 -----
|
|
323
|
+
审查类型: {review_type}"""
|
|
324
|
+
|
|
325
|
+
# Add specific information based on review type
|
|
326
|
+
if review_type == "commit":
|
|
327
|
+
review_info += f"\n提交SHA: {args['commit_sha']}"
|
|
328
|
+
elif review_type == "range":
|
|
329
|
+
review_info += f"\n起始提交: {args['start_commit']}\n结束提交: {args['end_commit']}"
|
|
330
|
+
elif review_type == "file":
|
|
331
|
+
review_info += f"\n文件路径: {args['file_path']}"
|
|
332
|
+
else: # current changes
|
|
333
|
+
review_info += "\n当前未提交修改"
|
|
334
|
+
|
|
335
|
+
# Add file list
|
|
336
|
+
if file_paths:
|
|
337
|
+
review_info += "\n\n----- 变更文件列表 -----"
|
|
338
|
+
for i, path in enumerate(file_paths, 1):
|
|
339
|
+
review_info += f"\n{i}. {path}"
|
|
340
|
+
|
|
341
|
+
# Add language-specific checklists
|
|
342
|
+
if detected_languages:
|
|
343
|
+
review_info += "\n\n----- 检测到的编程语言 -----"
|
|
344
|
+
language_names = {
|
|
345
|
+
'c_cpp': 'C/C++',
|
|
346
|
+
'go': 'Go',
|
|
347
|
+
'python': 'Python',
|
|
348
|
+
'rust': 'Rust'
|
|
349
|
+
}
|
|
350
|
+
detected_lang_names = [language_names.get(lang, lang) for lang in detected_languages]
|
|
351
|
+
review_info += f"\n检测到的语言: {', '.join(detected_lang_names)}"
|
|
352
|
+
|
|
353
|
+
review_info += "\n\n----- 语言特定审查清单 -----"
|
|
354
|
+
for lang in detected_languages:
|
|
355
|
+
checklist = self._get_language_checklist(lang)
|
|
356
|
+
if checklist:
|
|
357
|
+
review_info += f"\n{checklist}"
|
|
358
|
+
|
|
359
|
+
review_info += "\n------------------------\n\n"
|
|
360
|
+
|
|
361
|
+
# Combine review info with diff output
|
|
362
|
+
diff_output = review_info + diff_output
|
|
363
|
+
|
|
364
|
+
PrettyOutput.print(diff_output, OutputType.CODE, lang="diff")
|
|
365
|
+
spinner.text = "代码变更获取完成"
|
|
366
|
+
spinner.ok("✅")
|
|
367
|
+
|
|
368
|
+
system_prompt = """你是一位精益求精的首席代码审查专家,拥有多年企业级代码审计经验。你需要对所有代码变更进行极其全面、严谨且深入的审查,确保代码质量达到最高标准。
|
|
369
|
+
|
|
370
|
+
# 代码审查工具选择
|
|
371
|
+
优先使用执行shell命令进行静态分析,而非依赖内置代码审查功能:
|
|
372
|
+
|
|
373
|
+
| 分析需求 | 首选工具 | 备选工具 |
|
|
374
|
+
|---------|---------|----------|
|
|
375
|
+
| 代码质量检查 | execute_script | - |
|
|
376
|
+
| 语法检查 | 语言特定lint工具 | - |
|
|
377
|
+
| 安全分析 | 安全扫描工具 | - |
|
|
378
|
+
| 代码统计 | loc | - |
|
|
379
|
+
|
|
380
|
+
# 推荐命令
|
|
381
|
+
- Python: `pylint <file_path>`, `flake8 <file_path>`, `mypy <file_path>`
|
|
382
|
+
- JavaScript/TypeScript: `eslint <file_path>`, `tsc --noEmit <file_path>`
|
|
383
|
+
- Java: `checkstyle <file_path>`, `pmd -d <file_path>`
|
|
384
|
+
- C/C++: `cppcheck <file_path>`, `clang-tidy <file_path>`
|
|
385
|
+
- Go: `golint <file_path>`, `go vet <file_path>`
|
|
386
|
+
- Rust: `cargo clippy`, `rustfmt --check <file_path>`
|
|
387
|
+
- 通用搜索:`rg "pattern" <files>` 查找特定代码模式
|
|
388
|
+
|
|
389
|
+
# 专家审查标准
|
|
390
|
+
1. 必须逐行分析每个修改文件,细致审查每一处变更,不遗漏任何细节
|
|
391
|
+
2. 基于坚实的证据识别问题,不做主观臆测,给出明确的问题定位和详细分析
|
|
392
|
+
3. 对每个问题提供完整可执行的解决方案,包括精确的改进代码
|
|
393
|
+
4. 确保报告条理清晰、层次分明,便于工程师快速采取行动
|
|
394
|
+
|
|
395
|
+
# 全面审查框架 (SCRIPPPS)
|
|
396
|
+
## S - 安全与风险 (Security & Risk)
|
|
397
|
+
- [ ] 发现所有潜在安全漏洞:注入攻击、授权缺陷、数据泄露风险
|
|
398
|
+
- [ ] 检查加密实现、密钥管理、敏感数据处理
|
|
399
|
+
- [ ] 审核权限验证逻辑、身份认证机制
|
|
400
|
+
- [ ] 检测OWASP Top 10安全风险和针对特定语言/框架的漏洞
|
|
401
|
+
|
|
402
|
+
## C - 正确性与完整性 (Correctness & Completeness)
|
|
403
|
+
- [ ] 验证业务逻辑和算法实现的准确性
|
|
404
|
+
- [ ] 全面检查条件边界、空值处理和异常情况
|
|
405
|
+
- [ ] 审核所有输入验证、参数校验和返回值处理
|
|
406
|
+
- [ ] 确保循环和递归的正确终止条件
|
|
407
|
+
- [ ] 严格检查线程安全和并发控制机制
|
|
408
|
+
|
|
409
|
+
## R - 可靠性与鲁棒性 (Reliability & Robustness)
|
|
410
|
+
- [ ] 评估代码在异常情况下的行为和恢复能力
|
|
411
|
+
- [ ] 审查错误处理、异常捕获和恢复策略
|
|
412
|
+
- [ ] 检查资源管理:内存、文件句柄、连接池、线程
|
|
413
|
+
- [ ] 评估容错设计和失败优雅降级机制
|
|
414
|
+
|
|
415
|
+
## I - 接口与集成 (Interface & Integration)
|
|
416
|
+
- [ ] 检查API合约遵守情况和向后兼容性
|
|
417
|
+
- [ ] 审核与外部系统的集成点和交互逻辑
|
|
418
|
+
- [ ] 验证数据格式、序列化和协议实现
|
|
419
|
+
- [ ] 评估系统边界处理和跨服务通信安全性
|
|
420
|
+
|
|
421
|
+
## P - 性能与效率 (Performance & Efficiency)
|
|
422
|
+
- [ ] 识别潜在性能瓶颈:CPU、内存、I/O、网络
|
|
423
|
+
- [ ] 审查数据结构选择和算法复杂度
|
|
424
|
+
- [ ] 检查资源密集型操作、数据库查询优化
|
|
425
|
+
- [ ] 评估缓存策略、批处理优化和并行处理机会
|
|
426
|
+
|
|
427
|
+
## P - 可移植性与平台适配 (Portability & Platform Compatibility)
|
|
428
|
+
- [ ] 检查跨平台兼容性问题和依赖项管理
|
|
429
|
+
- [ ] 评估配置管理和环境适配设计
|
|
430
|
+
- [ ] 审核国际化和本地化支持
|
|
431
|
+
- [ ] 验证部署和运行时环境需求
|
|
432
|
+
|
|
433
|
+
## S - 结构与可维护性 (Structure & Maintainability)
|
|
434
|
+
- [ ] 评估代码组织、模块划分和架构符合性
|
|
435
|
+
- [ ] 审查代码重复、设计模式应用和抽象水平
|
|
436
|
+
- [ ] 检查命名规范、代码风格和项目约定
|
|
437
|
+
- [ ] 评估文档完整性、注释质量和代码可读性
|
|
438
|
+
|
|
439
|
+
# 问题严重程度分级
|
|
440
|
+
- [ ] 严重 (P0): 安全漏洞、数据丢失风险、系统崩溃、功能严重缺陷
|
|
441
|
+
- [ ] 高危 (P1): 显著性能问题、可能导致部分功能失效、系统不稳定
|
|
442
|
+
- [ ] 中等 (P2): 功能局部缺陷、次优设计、明显的技术债务
|
|
443
|
+
- [ ] 低危 (P3): 代码风格问题、轻微优化机会、文档改进建议
|
|
444
|
+
|
|
445
|
+
# 输出规范
|
|
446
|
+
针对每个文件的问题必须包含:
|
|
447
|
+
- [ ] 精确文件路径和问题影响范围
|
|
448
|
+
- [ ] 问题位置(起始行号-结束行号)
|
|
449
|
+
- [ ] 详尽问题描述,包括具体影响和潜在风险
|
|
450
|
+
- [ ] 严重程度分级(P0-P3)并说明理由
|
|
451
|
+
- [ ] 具体改进建议,提供完整、可执行的代码示例
|
|
452
|
+
|
|
453
|
+
所有审查发现必须:
|
|
454
|
+
1. 基于确凿的代码证据
|
|
455
|
+
2. 说明具体问题而非笼统评论
|
|
456
|
+
3. 提供清晰的技术原理分析
|
|
457
|
+
4. 给出完整的改进实施步骤
|
|
458
|
+
|
|
459
|
+
# 语言特定审查
|
|
460
|
+
如果在审查信息中检测到了语言特定的审查清单,请按照清单中的项目进行逐一检查,并在报告中针对每个适用的清单项给出详细分析。
|
|
461
|
+
|
|
462
|
+
我将分析上传的代码差异文件,进行全面的代码审查。"""
|
|
463
|
+
|
|
464
|
+
tool_registry = ToolRegistry()
|
|
465
|
+
tool_registry.dont_use_tools(["code_review"])
|
|
466
|
+
agent = Agent(
|
|
467
|
+
system_prompt=system_prompt,
|
|
468
|
+
name="Code Review Agent",
|
|
469
|
+
summary_prompt=f"""请生成一份专业级别的代码审查报告,对每处变更进行全面深入分析。将完整报告放在REPORT标签内,格式如下:
|
|
470
|
+
|
|
471
|
+
{ot("REPORT")}
|
|
472
|
+
# 整体评估
|
|
473
|
+
[提供对整体代码质量、架构和主要关注点的简明概述,总结主要发现]
|
|
474
|
+
|
|
475
|
+
# 详细问题清单
|
|
476
|
+
|
|
477
|
+
## 文件: [文件路径]
|
|
478
|
+
[如果该文件没有发现问题,则明确说明"未发现问题"]
|
|
479
|
+
|
|
480
|
+
### 问题 1
|
|
481
|
+
- **位置**: [起始行号-结束行号]
|
|
482
|
+
- **分类**: [使用SCRIPPPS框架中相关类别]
|
|
483
|
+
- **严重程度**: [P0/P1/P2/P3] - [简要说明判定理由]
|
|
484
|
+
- **问题描述**:
|
|
485
|
+
[详细描述问题,包括技术原理和潜在影响]
|
|
486
|
+
- **改进建议**:
|
|
487
|
+
```
|
|
488
|
+
[提供完整、可执行的代码示例,而非概念性建议]
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### 问题 2
|
|
492
|
+
...
|
|
493
|
+
|
|
494
|
+
## 文件: [文件路径2]
|
|
495
|
+
...
|
|
496
|
+
|
|
497
|
+
# 语言特定问题
|
|
498
|
+
[根据检测到的编程语言,提供针对语言特定清单中项目的分析]
|
|
499
|
+
|
|
500
|
+
# 最佳实践建议
|
|
501
|
+
[提供适用于整个代码库的改进建议和最佳实践]
|
|
502
|
+
|
|
503
|
+
# 总结
|
|
504
|
+
[总结主要问题和优先处理建议]
|
|
505
|
+
{ct("REPORT")}
|
|
506
|
+
|
|
507
|
+
如果没有发现任何问题,请在REPORT标签内进行全面分析后明确说明"经过全面审查,未发现问题"并解释原因。
|
|
508
|
+
必须确保对所有修改的文件都进行了审查,并在报告中明确提及每个文件,即使某些文件没有发现问题。
|
|
509
|
+
如果检测到了特定编程语言,请参考语言特定的审查清单进行评估,并在报告中包含相关分析。""",
|
|
510
|
+
output_handler=[tool_registry],
|
|
511
|
+
platform=PlatformRegistry().get_thinking_platform(),
|
|
512
|
+
auto_complete=True
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
# Determine if we need to split the diff due to size
|
|
516
|
+
max_diff_size = 100 * 1024 * 1024 # Limit to 100MB
|
|
517
|
+
|
|
518
|
+
if len(diff_output) > max_diff_size:
|
|
519
|
+
PrettyOutput.print(f"代码差异内容总大小超过限制 ({len(diff_output)} > {max_diff_size} 字节),将截断内容", OutputType.WARNING)
|
|
520
|
+
diff_output = diff_output[:max_diff_size] + "\n\n[diff content truncated due to size limitations...]"
|
|
521
|
+
|
|
522
|
+
# Prepare the user prompt for code review
|
|
523
|
+
user_prompt = f"""请对以下代码变更进行全面审查。
|
|
524
|
+
|
|
525
|
+
代码信息:
|
|
526
|
+
- 审查类型: {review_type}
|
|
527
|
+
- 变更文件列表: {len(file_paths)} 个文件
|
|
528
|
+
- 检测到的编程语言: {', '.join(detected_languages) if detected_languages else '未检测到特定语言'}
|
|
529
|
+
|
|
530
|
+
请根据SCRIPPPS框架和语言特定的审查清单进行分析,提供详细的代码审查报告。"""
|
|
531
|
+
|
|
532
|
+
# Write the full diff output to a temporary file for uploading
|
|
533
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.diff', delete=False) as temp_file:
|
|
534
|
+
temp_file_path = temp_file.name
|
|
535
|
+
temp_file.write(diff_output)
|
|
536
|
+
|
|
537
|
+
try:
|
|
538
|
+
# Upload the file to the agent's model
|
|
539
|
+
with yaspin(text="正在上传代码差异文件...", color="cyan") as spinner:
|
|
540
|
+
if agent.model and hasattr(agent.model, 'upload_files'):
|
|
541
|
+
upload_success = agent.model.upload_files([temp_file_path])
|
|
542
|
+
if upload_success:
|
|
543
|
+
spinner.ok("✅")
|
|
544
|
+
PrettyOutput.print(f"已成功上传代码差异文件", OutputType.SUCCESS)
|
|
545
|
+
else:
|
|
546
|
+
spinner.fail("❌")
|
|
547
|
+
PrettyOutput.print(f"上传代码差异文件失败", OutputType.WARNING)
|
|
548
|
+
upload_success = False
|
|
549
|
+
else:
|
|
550
|
+
spinner.fail("❌")
|
|
551
|
+
PrettyOutput.print(f"当前模型不支持文件上传", OutputType.WARNING)
|
|
552
|
+
upload_success = False
|
|
553
|
+
|
|
554
|
+
# Prepare the prompt based on upload status
|
|
555
|
+
if upload_success:
|
|
556
|
+
# When file is uploaded, reference it in the prompt
|
|
557
|
+
complete_prompt = user_prompt + f"""
|
|
558
|
+
|
|
559
|
+
我已上传了一个包含代码差异的文件。该文件包含:
|
|
560
|
+
- 审查类型: {review_type}
|
|
561
|
+
- 变更文件数量: {len(file_paths)} 个文件
|
|
562
|
+
- 检测到的编程语言: {', '.join(detected_languages) if detected_languages else '未检测到特定语言'}
|
|
563
|
+
|
|
564
|
+
请基于上传的代码差异文件进行全面审查,并生成详细的代码审查报告。"""
|
|
565
|
+
else:
|
|
566
|
+
# If upload failed, include the diff directly in the prompt
|
|
567
|
+
complete_prompt = user_prompt + "\n\n代码差异内容:\n```diff\n" + diff_output + "\n```"
|
|
568
|
+
|
|
569
|
+
# Run the agent with the prompt
|
|
570
|
+
with yaspin(text="正在分析代码变更...", color="cyan") as spinner:
|
|
571
|
+
result = agent.run(complete_prompt)
|
|
572
|
+
spinner.ok("✅")
|
|
573
|
+
finally:
|
|
574
|
+
# Clean up the temporary file
|
|
575
|
+
if os.path.exists(temp_file_path):
|
|
576
|
+
try:
|
|
577
|
+
os.unlink(temp_file_path)
|
|
578
|
+
except Exception:
|
|
579
|
+
PrettyOutput.print(f"临时文件 {temp_file_path} 未能删除", OutputType.WARNING)
|
|
580
|
+
|
|
581
|
+
return {
|
|
582
|
+
"success": True,
|
|
583
|
+
"stdout": result,
|
|
584
|
+
"stderr": ""
|
|
585
|
+
}
|
|
586
|
+
finally:
|
|
587
|
+
# Always restore original directory
|
|
588
|
+
os.chdir(original_dir)
|
|
589
|
+
|
|
590
|
+
except Exception as e:
|
|
591
|
+
return {
|
|
592
|
+
"success": False,
|
|
593
|
+
"stdout": {},
|
|
594
|
+
"stderr": f"Review failed: {str(e)}"
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def extract_code_report(result: str) -> str:
|
|
599
|
+
sm = re.search(ot("REPORT")+r'\n(.*?)\n'+ct("REPORT"), result, re.DOTALL)
|
|
600
|
+
if sm:
|
|
601
|
+
return sm.group(1)
|
|
602
|
+
return ""
|
|
603
|
+
|
|
604
|
+
def main():
|
|
605
|
+
"""CLI entry point"""
|
|
606
|
+
import argparse
|
|
607
|
+
|
|
608
|
+
init_env()
|
|
609
|
+
|
|
610
|
+
parser = argparse.ArgumentParser(description='Autonomous code review tool')
|
|
611
|
+
subparsers = parser.add_subparsers(dest='type')
|
|
612
|
+
|
|
613
|
+
# Commit subcommand
|
|
614
|
+
commit_parser = subparsers.add_parser('commit', help='Review specific commit')
|
|
615
|
+
commit_parser.add_argument('commit', help='Commit SHA to review')
|
|
616
|
+
|
|
617
|
+
# Current subcommand
|
|
618
|
+
current_parser = subparsers.add_parser('current', help='Review current changes')
|
|
619
|
+
|
|
620
|
+
# Range subcommand
|
|
621
|
+
range_parser = subparsers.add_parser('range', help='Review commit range')
|
|
622
|
+
range_parser.add_argument('start_commit', help='Start commit SHA')
|
|
623
|
+
range_parser.add_argument('end_commit', help='End commit SHA')
|
|
624
|
+
|
|
625
|
+
# File subcommand
|
|
626
|
+
file_parser = subparsers.add_parser('file', help='Review specific file')
|
|
627
|
+
file_parser.add_argument('file', help='File path to review')
|
|
628
|
+
|
|
629
|
+
# Common arguments
|
|
630
|
+
parser.add_argument('--root-dir', type=str, help='Root directory of the codebase', default=".")
|
|
631
|
+
|
|
632
|
+
# Set default subcommand to 'current'
|
|
633
|
+
parser.set_defaults(type='current')
|
|
634
|
+
args = parser.parse_args()
|
|
635
|
+
|
|
636
|
+
tool = CodeReviewTool()
|
|
637
|
+
tool_args = {
|
|
638
|
+
"review_type": args.type,
|
|
639
|
+
"root_dir": args.root_dir
|
|
640
|
+
}
|
|
641
|
+
if args.type == 'commit':
|
|
642
|
+
tool_args["commit_sha"] = args.commit
|
|
643
|
+
elif args.type == 'range':
|
|
644
|
+
tool_args["start_commit"] = args.start_commit
|
|
645
|
+
tool_args["end_commit"] = args.end_commit
|
|
646
|
+
elif args.type == 'file':
|
|
647
|
+
tool_args["file_path"] = args.file
|
|
648
|
+
|
|
649
|
+
result = tool.execute(tool_args)
|
|
650
|
+
|
|
651
|
+
if result["success"]:
|
|
652
|
+
PrettyOutput.section("自动代码审查结果:", OutputType.SUCCESS)
|
|
653
|
+
report = extract_code_report(result["stdout"])
|
|
654
|
+
PrettyOutput.print(report, OutputType.SUCCESS, lang="markdown")
|
|
655
|
+
|
|
656
|
+
else:
|
|
657
|
+
PrettyOutput.print(result["stderr"], OutputType.WARNING)
|
|
658
|
+
|
|
659
|
+
if __name__ == "__main__":
|
|
660
|
+
main()
|