jarvis-ai-assistant 0.2.3__py3-none-any.whl → 0.2.5__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 +13 -7
- jarvis/jarvis_agent/edit_file_handler.py +4 -0
- jarvis/jarvis_agent/jarvis.py +22 -25
- jarvis/jarvis_agent/main.py +6 -6
- jarvis/jarvis_code_agent/code_agent.py +273 -11
- jarvis/jarvis_code_analysis/code_review.py +21 -19
- jarvis/jarvis_data/config_schema.json +25 -29
- jarvis/jarvis_git_squash/main.py +3 -3
- jarvis/jarvis_git_utils/git_commiter.py +32 -11
- jarvis/jarvis_mcp/sse_mcp_client.py +4 -6
- jarvis/jarvis_mcp/streamable_mcp_client.py +5 -9
- jarvis/jarvis_rag/retriever.py +1 -1
- jarvis/jarvis_smart_shell/main.py +2 -2
- jarvis/jarvis_stats/__init__.py +13 -0
- jarvis/jarvis_stats/cli.py +404 -0
- jarvis/jarvis_stats/stats.py +538 -0
- jarvis/jarvis_stats/storage.py +381 -0
- jarvis/jarvis_stats/visualizer.py +282 -0
- jarvis/jarvis_tools/cli/main.py +82 -15
- jarvis/jarvis_tools/registry.py +32 -16
- jarvis/jarvis_tools/search_web.py +3 -3
- jarvis/jarvis_tools/virtual_tty.py +315 -26
- jarvis/jarvis_utils/config.py +12 -8
- jarvis/jarvis_utils/git_utils.py +8 -16
- jarvis/jarvis_utils/methodology.py +74 -67
- jarvis/jarvis_utils/utils.py +468 -72
- {jarvis_ai_assistant-0.2.3.dist-info → jarvis_ai_assistant-0.2.5.dist-info}/METADATA +29 -3
- {jarvis_ai_assistant-0.2.3.dist-info → jarvis_ai_assistant-0.2.5.dist-info}/RECORD +33 -28
- {jarvis_ai_assistant-0.2.3.dist-info → jarvis_ai_assistant-0.2.5.dist-info}/entry_points.txt +2 -0
- {jarvis_ai_assistant-0.2.3.dist-info → jarvis_ai_assistant-0.2.5.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.2.3.dist-info → jarvis_ai_assistant-0.2.5.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.2.3.dist-info → jarvis_ai_assistant-0.2.5.dist-info}/top_level.txt +0 -0
jarvis/__init__.py
CHANGED
jarvis/jarvis_agent/__init__.py
CHANGED
@@ -511,7 +511,7 @@ class Agent:
|
|
511
511
|
)
|
512
512
|
if user_input:
|
513
513
|
run_input_handlers = True
|
514
|
-
#
|
514
|
+
# 如果有工具调用且用户确认继续,则继续执行工具调用
|
515
515
|
if any(
|
516
516
|
handler.can_handle(current_response)
|
517
517
|
for handler in self.output_handler
|
@@ -519,13 +519,19 @@ class Agent:
|
|
519
519
|
if user_confirm(
|
520
520
|
"检测到有工具调用,是否继续处理工具调用?", True
|
521
521
|
):
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
522
|
+
# 先添加用户干预信息到session
|
523
|
+
self.session.prompt = f"被用户中断,用户补充信息为:{user_input}\n\n用户同意继续工具调用。"
|
524
|
+
# 继续执行下面的工具调用逻辑,不要continue跳过
|
525
|
+
else:
|
526
|
+
# 用户选择不继续处理工具调用
|
527
|
+
self.session.prompt = f"被用户中断,用户补充信息为:{user_input}\n\n检测到有工具调用,但被用户拒绝执行。请根据用户的补充信息重新考虑下一步操作。"
|
526
528
|
continue
|
527
|
-
|
528
|
-
|
529
|
+
else:
|
530
|
+
# 没有检测到工具调用
|
531
|
+
self.session.prompt = (
|
532
|
+
f"被用户中断,用户补充信息为:{user_input}"
|
533
|
+
)
|
534
|
+
continue
|
529
535
|
|
530
536
|
need_return, self.session.prompt = self._call_tools(
|
531
537
|
current_response
|
@@ -55,6 +55,10 @@ class EditFileHandler(OutputHandler):
|
|
55
55
|
patches = self._parse_patches(response)
|
56
56
|
if not patches:
|
57
57
|
return False, "未找到有效的文件编辑指令"
|
58
|
+
|
59
|
+
# 记录 edit_file 工具调用统计
|
60
|
+
from jarvis.jarvis_stats.stats import StatsManager
|
61
|
+
StatsManager.increment("edit_file", group="tool")
|
58
62
|
|
59
63
|
results = []
|
60
64
|
|
jarvis/jarvis_agent/jarvis.py
CHANGED
@@ -24,7 +24,7 @@ from jarvis.jarvis_tools.registry import ToolRegistry
|
|
24
24
|
from jarvis.jarvis_utils.config import get_data_dir
|
25
25
|
from jarvis.jarvis_utils.utils import init_env
|
26
26
|
|
27
|
-
app = typer.Typer(help="Jarvis AI
|
27
|
+
app = typer.Typer(help="Jarvis AI 助手")
|
28
28
|
|
29
29
|
|
30
30
|
def _load_tasks() -> Dict[str, str]:
|
@@ -91,19 +91,13 @@ def _select_task(tasks: Dict[str, str]) -> str:
|
|
91
91
|
selected_task = tasks[task_names[choice - 1]]
|
92
92
|
PrettyOutput.print(f"将要执行任务:\n {selected_task}", OutputType.INFO)
|
93
93
|
# 询问是否需要补充信息
|
94
|
-
need_additional = user_confirm(
|
95
|
-
"需要为此任务添加补充信息吗?", default=False
|
96
|
-
)
|
94
|
+
need_additional = user_confirm("需要为此任务添加补充信息吗?", default=False)
|
97
95
|
if need_additional:
|
98
96
|
additional_input = get_multiline_input("请输入补充信息:")
|
99
97
|
if additional_input:
|
100
|
-
selected_task =
|
101
|
-
f"{selected_task}\n\n补充信息:\n{additional_input}"
|
102
|
-
)
|
98
|
+
selected_task = f"{selected_task}\n\n补充信息:\n{additional_input}"
|
103
99
|
return selected_task
|
104
|
-
PrettyOutput.print(
|
105
|
-
"无效的选择。请选择列表中的一个号码。", OutputType.WARNING
|
106
|
-
)
|
100
|
+
PrettyOutput.print("无效的选择。请选择列表中的一个号码。", OutputType.WARNING)
|
107
101
|
|
108
102
|
except (KeyboardInterrupt, EOFError):
|
109
103
|
return ""
|
@@ -121,7 +115,16 @@ def _handle_edit_mode(edit: bool, config_file: Optional[str]) -> None:
|
|
121
115
|
if config_file
|
122
116
|
else Path(os.path.expanduser("~/.jarvis/config.yaml"))
|
123
117
|
)
|
124
|
-
|
118
|
+
# 根据操作系统选择合适的编辑器
|
119
|
+
import platform
|
120
|
+
|
121
|
+
if platform.system() == "Windows":
|
122
|
+
# 优先级:终端工具 -> 代码编辑器 -> 通用文本编辑器
|
123
|
+
editors = ["nvim", "vim", "nano", "code", "notepad++", "notepad"]
|
124
|
+
else:
|
125
|
+
# 优先级:终端工具 -> 代码编辑器 -> 通用文本编辑器
|
126
|
+
editors = ["nvim", "vim", "vi", "nano", "emacs", "code", "gedit", "kate"]
|
127
|
+
|
125
128
|
editor = next((e for e in editors if shutil.which(e)), None)
|
126
129
|
|
127
130
|
if editor:
|
@@ -133,7 +136,7 @@ def _handle_edit_mode(edit: bool, config_file: Optional[str]) -> None:
|
|
133
136
|
raise typer.Exit(code=1)
|
134
137
|
else:
|
135
138
|
PrettyOutput.print(
|
136
|
-
"No suitable editor found
|
139
|
+
f"No suitable editor found. Tried: {', '.join(editors)}", OutputType.ERROR
|
137
140
|
)
|
138
141
|
raise typer.Exit(code=1)
|
139
142
|
|
@@ -186,30 +189,24 @@ def run_cli(
|
|
186
189
|
llm_type: str = typer.Option(
|
187
190
|
"normal",
|
188
191
|
"--llm_type",
|
189
|
-
help="LLM
|
190
|
-
),
|
191
|
-
task: Optional[str] = typer.Option(
|
192
|
-
None, "-t", "--task", help="Directly input task content from command line"
|
192
|
+
help="使用的LLM类型,可选值:'normal'(普通)或 'thinking'(思考模式)",
|
193
193
|
),
|
194
|
+
task: Optional[str] = typer.Option(None, "-t", "--task", help="从命令行直接输入任务内容"),
|
194
195
|
model_group: Optional[str] = typer.Option(
|
195
|
-
None, "--
|
196
|
-
),
|
197
|
-
config_file: Optional[str] = typer.Option(
|
198
|
-
None, "-f", "--config", help="Path to custom config file"
|
196
|
+
None, "--llm_group", help="使用的模型组,覆盖配置文件中的设置"
|
199
197
|
),
|
198
|
+
config_file: Optional[str] = typer.Option(None, "-f", "--config", help="自定义配置文件路径"),
|
200
199
|
restore_session: bool = typer.Option(
|
201
200
|
False,
|
202
201
|
"--restore-session",
|
203
|
-
help="
|
204
|
-
),
|
205
|
-
edit: bool = typer.Option(
|
206
|
-
False, "-e", "--edit", help="Edit the configuration file"
|
202
|
+
help="从 .jarvis/saved_session.json 恢复会话",
|
207
203
|
),
|
204
|
+
edit: bool = typer.Option(False, "-e", "--edit", help="编辑配置文件"),
|
208
205
|
) -> None:
|
209
206
|
"""Jarvis AI assistant command-line interface."""
|
210
207
|
if ctx.invoked_subcommand is not None:
|
211
208
|
return
|
212
|
-
|
209
|
+
|
213
210
|
_handle_edit_mode(edit, config_file)
|
214
211
|
|
215
212
|
init_env("欢迎使用 Jarvis AI 助手,您的智能助理已准备就绪!", config_file=config_file)
|
jarvis/jarvis_agent/main.py
CHANGED
@@ -10,7 +10,7 @@ from jarvis.jarvis_utils.input import get_multiline_input
|
|
10
10
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
11
11
|
from jarvis.jarvis_utils.utils import init_env
|
12
12
|
|
13
|
-
app = typer.Typer(help="Jarvis AI
|
13
|
+
app = typer.Typer(help="Jarvis AI 助手")
|
14
14
|
|
15
15
|
|
16
16
|
def load_config(config_path: str) -> dict:
|
@@ -40,21 +40,21 @@ def load_config(config_path: str) -> dict:
|
|
40
40
|
@app.command()
|
41
41
|
def cli(
|
42
42
|
config_file: Optional[str] = typer.Option(
|
43
|
-
None, "-f", "--config", help="
|
43
|
+
None, "-f", "--config", help="代理配置文件路径"
|
44
44
|
),
|
45
45
|
agent_definition: Optional[str] = typer.Option(
|
46
|
-
None, "-c", "--agent_definition", help="
|
46
|
+
None, "-c", "--agent_definition", help="代理定义文件路径"
|
47
47
|
),
|
48
48
|
task: Optional[str] = typer.Option(
|
49
|
-
None, "-t", "--task", help="
|
49
|
+
None, "-t", "--task", help="初始任务内容"
|
50
50
|
),
|
51
51
|
llm_type: str = typer.Option(
|
52
52
|
"normal",
|
53
53
|
"--llm_type",
|
54
|
-
help="LLM
|
54
|
+
help="使用的LLM类型,覆盖配置文件中的设置",
|
55
55
|
),
|
56
56
|
model_group: Optional[str] = typer.Option(
|
57
|
-
None, "--
|
57
|
+
None, "--llm_group", help="使用的模型组,覆盖配置文件中的设置"
|
58
58
|
),
|
59
59
|
):
|
60
60
|
"""Main entry point for Jarvis agent"""
|
@@ -38,7 +38,7 @@ from jarvis.jarvis_utils.input import get_multiline_input, user_confirm
|
|
38
38
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
39
39
|
from jarvis.jarvis_utils.utils import get_loc_stats, init_env
|
40
40
|
|
41
|
-
app = typer.Typer(help="Jarvis
|
41
|
+
app = typer.Typer(help="Jarvis 代码助手")
|
42
42
|
|
43
43
|
|
44
44
|
class CodeAgent:
|
@@ -54,6 +54,9 @@ class CodeAgent:
|
|
54
54
|
need_summary: bool = True,
|
55
55
|
):
|
56
56
|
self.root_dir = os.getcwd()
|
57
|
+
|
58
|
+
# 检测 git username 和 email 是否已设置
|
59
|
+
self._check_git_config()
|
57
60
|
tool_registry = ToolRegistry() # type: ignore
|
58
61
|
tool_registry.use_tools(
|
59
62
|
[
|
@@ -84,18 +87,18 @@ class CodeAgent:
|
|
84
87
|
1. **项目分析**:分析项目结构,确定需修改的文件
|
85
88
|
2. **需求分析**:理解需求意图,选择影响最小的实现方案
|
86
89
|
3. **代码分析**:详细分析目标文件,禁止虚构现有代码
|
87
|
-
-
|
88
|
-
-
|
89
|
-
-
|
90
|
+
- 结构分析:优先使用文件搜索工具快速定位文件和目录结构
|
91
|
+
- 内容搜索:优先使用全文搜索工具进行函数、类、变量等内容的搜索,避免遗漏
|
92
|
+
- 依赖关系:如需分析依赖、调用关系,可结合代码分析工具辅助
|
90
93
|
- 代码阅读:使用 read_code 工具获取目标文件的完整内容或指定范围内容,禁止凭空假设代码
|
91
|
-
-
|
94
|
+
- 变更影响:如需分析变更影响范围,可结合版本控制工具辅助判断
|
92
95
|
- 工具优先级:优先使用自动化工具,减少人工推断,确保分析结果准确
|
93
96
|
4. **方案设计**:确定最小变更方案,保持代码结构
|
94
97
|
5. **实施修改**:遵循"先读后写"原则,保持代码风格一致性
|
95
98
|
|
96
99
|
## 工具使用
|
97
|
-
-
|
98
|
-
-
|
100
|
+
- 项目结构:优先使用文件搜索命令查找文件
|
101
|
+
- 代码搜索:优先使用内容搜索工具
|
99
102
|
- 代码阅读:优先使用read_code工具
|
100
103
|
- 仅在命令行工具不足时使用专用工具
|
101
104
|
|
@@ -133,6 +136,52 @@ class CodeAgent:
|
|
133
136
|
|
134
137
|
self.agent.set_after_tool_call_cb(self.after_tool_call_cb)
|
135
138
|
|
139
|
+
def _check_git_config(self) -> None:
|
140
|
+
"""检查 git username 和 email 是否已设置,如果没有则提示并退出"""
|
141
|
+
try:
|
142
|
+
# 检查 git user.name
|
143
|
+
result = subprocess.run(
|
144
|
+
["git", "config", "--get", "user.name"],
|
145
|
+
capture_output=True,
|
146
|
+
text=True,
|
147
|
+
check=False,
|
148
|
+
)
|
149
|
+
username = result.stdout.strip()
|
150
|
+
|
151
|
+
# 检查 git user.email
|
152
|
+
result = subprocess.run(
|
153
|
+
["git", "config", "--get", "user.email"],
|
154
|
+
capture_output=True,
|
155
|
+
text=True,
|
156
|
+
check=False,
|
157
|
+
)
|
158
|
+
email = result.stdout.strip()
|
159
|
+
|
160
|
+
# 如果任一配置未设置,提示并退出
|
161
|
+
if not username or not email:
|
162
|
+
missing_configs = []
|
163
|
+
if not username:
|
164
|
+
missing_configs.append(
|
165
|
+
' git config --global user.name "Your Name"'
|
166
|
+
)
|
167
|
+
if not email:
|
168
|
+
missing_configs.append(
|
169
|
+
' git config --global user.email "your.email@example.com"'
|
170
|
+
)
|
171
|
+
|
172
|
+
message = "❌ Git 配置不完整\n\n请运行以下命令配置 Git:\n" + "\n".join(
|
173
|
+
missing_configs
|
174
|
+
)
|
175
|
+
PrettyOutput.print(message, OutputType.WARNING)
|
176
|
+
sys.exit(1)
|
177
|
+
|
178
|
+
except FileNotFoundError:
|
179
|
+
PrettyOutput.print("❌ 未找到 git 命令,请先安装 Git", OutputType.ERROR)
|
180
|
+
sys.exit(1)
|
181
|
+
except Exception as e:
|
182
|
+
PrettyOutput.print(f"❌ 检查 Git 配置时出错: {str(e)}", OutputType.ERROR)
|
183
|
+
sys.exit(1)
|
184
|
+
|
136
185
|
def _find_git_root(self) -> str:
|
137
186
|
"""查找并切换到git根目录
|
138
187
|
|
@@ -185,13 +234,136 @@ class CodeAgent:
|
|
185
234
|
1. 查找git根目录
|
186
235
|
2. 检查并更新.gitignore文件
|
187
236
|
3. 处理未提交的修改
|
237
|
+
4. 配置git对换行符变化不敏感
|
188
238
|
"""
|
189
239
|
print("🚀 正在初始化环境...")
|
190
240
|
git_dir = self._find_git_root()
|
191
241
|
self._update_gitignore(git_dir)
|
192
242
|
self._handle_git_changes()
|
243
|
+
# 配置git对换行符变化不敏感
|
244
|
+
self._configure_line_ending_settings()
|
193
245
|
print("✅ 环境初始化完成")
|
194
246
|
|
247
|
+
def _configure_line_ending_settings(self) -> None:
|
248
|
+
"""配置git对换行符变化不敏感,只在当前设置与目标设置不一致时修改"""
|
249
|
+
target_settings = {
|
250
|
+
"core.autocrlf": "false",
|
251
|
+
"core.safecrlf": "false",
|
252
|
+
"core.whitespace": "cr-at-eol", # 忽略行尾的CR
|
253
|
+
}
|
254
|
+
|
255
|
+
# 获取当前设置并检查是否需要修改
|
256
|
+
need_change = False
|
257
|
+
current_settings = {}
|
258
|
+
for key, target_value in target_settings.items():
|
259
|
+
result = subprocess.run(
|
260
|
+
["git", "config", "--get", key], capture_output=True, text=True
|
261
|
+
)
|
262
|
+
current_value = result.stdout.strip()
|
263
|
+
current_settings[key] = current_value
|
264
|
+
if current_value != target_value:
|
265
|
+
need_change = True
|
266
|
+
|
267
|
+
if not need_change:
|
268
|
+
print("✅ git换行符敏感设置已符合要求")
|
269
|
+
return
|
270
|
+
|
271
|
+
PrettyOutput.print(
|
272
|
+
"⚠️ 即将修改git换行符敏感设置,这会影响所有文件的换行符处理方式",
|
273
|
+
OutputType.WARNING,
|
274
|
+
)
|
275
|
+
print("将进行以下设置:")
|
276
|
+
for key, value in target_settings.items():
|
277
|
+
current = current_settings.get(key, "未设置")
|
278
|
+
print(f" {key}: {current} -> {value}")
|
279
|
+
|
280
|
+
if user_confirm("是否继续修改git换行符敏感设置?", True):
|
281
|
+
for key, value in target_settings.items():
|
282
|
+
subprocess.run(["git", "config", key, value], check=True)
|
283
|
+
|
284
|
+
# 对于Windows系统,提示用户可以创建.gitattributes文件
|
285
|
+
if sys.platform.startswith("win"):
|
286
|
+
self._handle_windows_line_endings()
|
287
|
+
|
288
|
+
print("✅ git换行符敏感设置已更新")
|
289
|
+
else:
|
290
|
+
print("❌ 用户取消修改git换行符敏感设置")
|
291
|
+
sys.exit(0)
|
292
|
+
|
293
|
+
def _handle_windows_line_endings(self) -> None:
|
294
|
+
"""在Windows系统上处理换行符问题,提供建议而非强制修改"""
|
295
|
+
gitattributes_path = os.path.join(self.root_dir, ".gitattributes")
|
296
|
+
|
297
|
+
# 检查是否已存在.gitattributes文件
|
298
|
+
if os.path.exists(gitattributes_path):
|
299
|
+
with open(gitattributes_path, "r", encoding="utf-8") as f:
|
300
|
+
content = f.read()
|
301
|
+
# 如果已经有换行符相关配置,就不再提示
|
302
|
+
if any(keyword in content for keyword in ["text=", "eol=", "binary"]):
|
303
|
+
return
|
304
|
+
|
305
|
+
print(
|
306
|
+
"\n💡 提示:在Windows系统上,建议配置.gitattributes文件来避免换行符问题。"
|
307
|
+
)
|
308
|
+
print("这可以防止仅因换行符不同而导致整个文件被标记为修改。")
|
309
|
+
|
310
|
+
if user_confirm("是否要创建一个最小化的.gitattributes文件?", False):
|
311
|
+
# 最小化的内容,只影响特定类型的文件
|
312
|
+
minimal_content = """# Jarvis建议的最小化换行符配置
|
313
|
+
# 默认所有文本文件使用LF,只有Windows特定文件使用CRLF
|
314
|
+
|
315
|
+
# 默认所有文本文件使用LF
|
316
|
+
* text=auto eol=lf
|
317
|
+
|
318
|
+
# Windows批处理文件需要CRLF
|
319
|
+
*.bat text eol=crlf
|
320
|
+
*.cmd text eol=crlf
|
321
|
+
*.ps1 text eol=crlf
|
322
|
+
"""
|
323
|
+
|
324
|
+
if not os.path.exists(gitattributes_path):
|
325
|
+
with open(gitattributes_path, "w", encoding="utf-8", newline="\n") as f:
|
326
|
+
f.write(minimal_content)
|
327
|
+
print("✅ 已创建最小化的.gitattributes文件")
|
328
|
+
else:
|
329
|
+
print("📝 将以下内容追加到现有.gitattributes文件:")
|
330
|
+
print(minimal_content)
|
331
|
+
if user_confirm("是否追加到现有文件?", True):
|
332
|
+
with open(
|
333
|
+
gitattributes_path, "a", encoding="utf-8", newline="\n"
|
334
|
+
) as f:
|
335
|
+
f.write("\n" + minimal_content)
|
336
|
+
print("✅ 已更新.gitattributes文件")
|
337
|
+
else:
|
338
|
+
print(
|
339
|
+
"ℹ️ 跳过.gitattributes文件创建。如果遇到换行符问题,可以手动创建此文件。"
|
340
|
+
)
|
341
|
+
|
342
|
+
def _record_code_changes_stats(self, diff_text: str) -> None:
|
343
|
+
"""记录代码变更的统计信息。
|
344
|
+
|
345
|
+
Args:
|
346
|
+
diff_text: git diff的文本输出
|
347
|
+
"""
|
348
|
+
from jarvis.jarvis_stats.stats import StatsManager
|
349
|
+
import re
|
350
|
+
|
351
|
+
# 匹配插入行数
|
352
|
+
insertions_match = re.search(r"(\d+)\s+insertions?\(\+\)", diff_text)
|
353
|
+
if insertions_match:
|
354
|
+
insertions = int(insertions_match.group(1))
|
355
|
+
StatsManager.increment(
|
356
|
+
"code_lines_inserted", amount=insertions, group="code_agent"
|
357
|
+
)
|
358
|
+
|
359
|
+
# 匹配删除行数
|
360
|
+
deletions_match = re.search(r"(\d+)\s+deletions?\(\-\)", diff_text)
|
361
|
+
if deletions_match:
|
362
|
+
deletions = int(deletions_match.group(1))
|
363
|
+
StatsManager.increment(
|
364
|
+
"code_lines_deleted", amount=deletions, group="code_agent"
|
365
|
+
)
|
366
|
+
|
195
367
|
def _handle_uncommitted_changes(self) -> None:
|
196
368
|
"""处理未提交的修改,包括:
|
197
369
|
1. 提示用户确认是否提交
|
@@ -201,10 +373,30 @@ class CodeAgent:
|
|
201
373
|
5. 暂存并提交所有修改
|
202
374
|
"""
|
203
375
|
if has_uncommitted_changes():
|
376
|
+
# 获取代码变更统计
|
377
|
+
try:
|
378
|
+
diff_result = subprocess.run(
|
379
|
+
["git", "diff", "HEAD", "--shortstat"],
|
380
|
+
capture_output=True,
|
381
|
+
text=True,
|
382
|
+
encoding="utf-8",
|
383
|
+
errors="replace",
|
384
|
+
check=True,
|
385
|
+
)
|
386
|
+
if diff_result.returncode == 0 and diff_result.stdout:
|
387
|
+
self._record_code_changes_stats(diff_result.stdout)
|
388
|
+
except subprocess.CalledProcessError:
|
389
|
+
pass
|
390
|
+
|
204
391
|
PrettyOutput.print("检测到未提交的修改,是否要提交?", OutputType.WARNING)
|
205
392
|
if not user_confirm("是否要提交?", True):
|
206
393
|
return
|
207
394
|
|
395
|
+
# 用户确认修改,统计修改次数
|
396
|
+
from jarvis.jarvis_stats.stats import StatsManager
|
397
|
+
|
398
|
+
StatsManager.increment("code_modification_confirmed", group="code_agent")
|
399
|
+
|
208
400
|
try:
|
209
401
|
confirm_add_new_files()
|
210
402
|
|
@@ -216,6 +408,8 @@ class CodeAgent:
|
|
216
408
|
["git", "rev-list", "--count", "HEAD"],
|
217
409
|
capture_output=True,
|
218
410
|
text=True,
|
411
|
+
encoding="utf-8",
|
412
|
+
errors="replace",
|
219
413
|
check=True,
|
220
414
|
)
|
221
415
|
if commit_result.returncode != 0:
|
@@ -231,6 +425,9 @@ class CodeAgent:
|
|
231
425
|
["git", "commit", "-m", f"CheckPoint #{commit_count + 1}"],
|
232
426
|
check=True,
|
233
427
|
)
|
428
|
+
|
429
|
+
# 统计提交次数
|
430
|
+
StatsManager.increment("code_commits_accepted", group="code_agent")
|
234
431
|
except subprocess.CalledProcessError as e:
|
235
432
|
PrettyOutput.print(f"提交失败: {str(e)}", OutputType.ERROR)
|
236
433
|
|
@@ -252,6 +449,11 @@ class CodeAgent:
|
|
252
449
|
commits = []
|
253
450
|
|
254
451
|
if commits:
|
452
|
+
# 统计生成的commit数量
|
453
|
+
from jarvis.jarvis_stats.stats import StatsManager
|
454
|
+
|
455
|
+
StatsManager.increment("commits_generated", group="code_agent")
|
456
|
+
|
255
457
|
commit_messages = "检测到以下提交记录:\n" + "\n".join(
|
256
458
|
f"- {commit_hash[:7]}: {message}" for commit_hash, message in commits
|
257
459
|
)
|
@@ -263,6 +465,11 @@ class CodeAgent:
|
|
263
465
|
) -> None:
|
264
466
|
"""处理提交确认和可能的重置"""
|
265
467
|
if commits and user_confirm("是否接受以上提交记录?", True):
|
468
|
+
# 统计接受的commit数量
|
469
|
+
from jarvis.jarvis_stats.stats import StatsManager
|
470
|
+
|
471
|
+
StatsManager.increment("commits_accepted", group="code_agent")
|
472
|
+
|
266
473
|
subprocess.run(
|
267
474
|
["git", "reset", "--mixed", str(start_commit)],
|
268
475
|
stdout=subprocess.DEVNULL,
|
@@ -350,6 +557,27 @@ class CodeAgent:
|
|
350
557
|
modified_files = get_diff_file_list()
|
351
558
|
commited = handle_commit_workflow()
|
352
559
|
if commited:
|
560
|
+
# 统计代码行数变化
|
561
|
+
# 获取diff的统计信息
|
562
|
+
try:
|
563
|
+
diff_result = subprocess.run(
|
564
|
+
["git", "diff", "HEAD~1", "HEAD", "--shortstat"],
|
565
|
+
capture_output=True,
|
566
|
+
text=True,
|
567
|
+
encoding="utf-8",
|
568
|
+
errors="replace",
|
569
|
+
check=True,
|
570
|
+
)
|
571
|
+
if diff_result.returncode == 0 and diff_result.stdout:
|
572
|
+
self._record_code_changes_stats(diff_result.stdout)
|
573
|
+
except subprocess.CalledProcessError:
|
574
|
+
pass
|
575
|
+
|
576
|
+
# 统计修改次数
|
577
|
+
from jarvis.jarvis_stats.stats import StatsManager
|
578
|
+
|
579
|
+
StatsManager.increment("code_modifications", group="code_agent")
|
580
|
+
|
353
581
|
# 获取提交信息
|
354
582
|
end_hash = get_latest_commit_hash()
|
355
583
|
commits = get_commits_between(start_hash, end_hash)
|
@@ -409,23 +637,57 @@ def cli(
|
|
409
637
|
llm_type: str = typer.Option(
|
410
638
|
"normal",
|
411
639
|
"--llm_type",
|
412
|
-
help="LLM
|
640
|
+
help="使用的LLM类型,可选值:'normal'(普通)或 'thinking'(思考模式)",
|
413
641
|
),
|
414
642
|
model_group: Optional[str] = typer.Option(
|
415
|
-
None, "--
|
643
|
+
None, "--llm_group", help="使用的模型组,覆盖配置文件中的设置"
|
416
644
|
),
|
417
645
|
requirement: Optional[str] = typer.Option(
|
418
|
-
None, "-r", "--requirement", help="
|
646
|
+
None, "-r", "--requirement", help="要处理的需求描述"
|
419
647
|
),
|
420
648
|
restore_session: bool = typer.Option(
|
421
649
|
False,
|
422
650
|
"--restore-session",
|
423
|
-
help="
|
651
|
+
help="从 .jarvis/saved_session.json 恢复会话状态",
|
424
652
|
),
|
425
653
|
) -> None:
|
426
654
|
"""Jarvis主入口点。"""
|
427
655
|
init_env("欢迎使用 Jarvis-CodeAgent,您的代码工程助手已准备就绪!")
|
428
656
|
|
657
|
+
try:
|
658
|
+
subprocess.run(
|
659
|
+
["git", "rev-parse", "--git-dir"],
|
660
|
+
check=True,
|
661
|
+
stdout=subprocess.DEVNULL,
|
662
|
+
stderr=subprocess.DEVNULL,
|
663
|
+
)
|
664
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
665
|
+
curr_dir_path = os.getcwd()
|
666
|
+
PrettyOutput.print(
|
667
|
+
f"警告:当前目录 '{curr_dir_path}' 不是一个git仓库。", OutputType.WARNING
|
668
|
+
)
|
669
|
+
if user_confirm(
|
670
|
+
f"是否要在 '{curr_dir_path}' 中初始化一个新的git仓库?", default=True
|
671
|
+
):
|
672
|
+
try:
|
673
|
+
subprocess.run(
|
674
|
+
["git", "init"],
|
675
|
+
check=True,
|
676
|
+
capture_output=True,
|
677
|
+
text=True,
|
678
|
+
encoding="utf-8",
|
679
|
+
errors="replace",
|
680
|
+
)
|
681
|
+
PrettyOutput.print("✅ 已成功初始化git仓库。", OutputType.SUCCESS)
|
682
|
+
except (subprocess.CalledProcessError, FileNotFoundError) as e:
|
683
|
+
PrettyOutput.print(f"❌ 初始化git仓库失败: {e}", OutputType.ERROR)
|
684
|
+
sys.exit(1)
|
685
|
+
else:
|
686
|
+
PrettyOutput.print(
|
687
|
+
"操作已取消。Jarvis需要在git仓库中运行。", OutputType.INFO
|
688
|
+
)
|
689
|
+
sys.exit(0)
|
690
|
+
|
429
691
|
curr_dir = os.getcwd()
|
430
692
|
git_dir = find_git_root_and_cd(curr_dir)
|
431
693
|
PrettyOutput.print(f"当前目录: {git_dir}", OutputType.INFO)
|