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.
Files changed (33) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +13 -7
  3. jarvis/jarvis_agent/edit_file_handler.py +4 -0
  4. jarvis/jarvis_agent/jarvis.py +22 -25
  5. jarvis/jarvis_agent/main.py +6 -6
  6. jarvis/jarvis_code_agent/code_agent.py +273 -11
  7. jarvis/jarvis_code_analysis/code_review.py +21 -19
  8. jarvis/jarvis_data/config_schema.json +25 -29
  9. jarvis/jarvis_git_squash/main.py +3 -3
  10. jarvis/jarvis_git_utils/git_commiter.py +32 -11
  11. jarvis/jarvis_mcp/sse_mcp_client.py +4 -6
  12. jarvis/jarvis_mcp/streamable_mcp_client.py +5 -9
  13. jarvis/jarvis_rag/retriever.py +1 -1
  14. jarvis/jarvis_smart_shell/main.py +2 -2
  15. jarvis/jarvis_stats/__init__.py +13 -0
  16. jarvis/jarvis_stats/cli.py +404 -0
  17. jarvis/jarvis_stats/stats.py +538 -0
  18. jarvis/jarvis_stats/storage.py +381 -0
  19. jarvis/jarvis_stats/visualizer.py +282 -0
  20. jarvis/jarvis_tools/cli/main.py +82 -15
  21. jarvis/jarvis_tools/registry.py +32 -16
  22. jarvis/jarvis_tools/search_web.py +3 -3
  23. jarvis/jarvis_tools/virtual_tty.py +315 -26
  24. jarvis/jarvis_utils/config.py +12 -8
  25. jarvis/jarvis_utils/git_utils.py +8 -16
  26. jarvis/jarvis_utils/methodology.py +74 -67
  27. jarvis/jarvis_utils/utils.py +468 -72
  28. {jarvis_ai_assistant-0.2.3.dist-info → jarvis_ai_assistant-0.2.5.dist-info}/METADATA +29 -3
  29. {jarvis_ai_assistant-0.2.3.dist-info → jarvis_ai_assistant-0.2.5.dist-info}/RECORD +33 -28
  30. {jarvis_ai_assistant-0.2.3.dist-info → jarvis_ai_assistant-0.2.5.dist-info}/entry_points.txt +2 -0
  31. {jarvis_ai_assistant-0.2.3.dist-info → jarvis_ai_assistant-0.2.5.dist-info}/WHEEL +0 -0
  32. {jarvis_ai_assistant-0.2.3.dist-info → jarvis_ai_assistant-0.2.5.dist-info}/licenses/LICENSE +0 -0
  33. {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
@@ -1,4 +1,4 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """Jarvis AI Assistant"""
3
3
 
4
- __version__ = "0.2.3"
4
+ __version__ = "0.2.5"
@@ -511,7 +511,7 @@ class Agent:
511
511
  )
512
512
  if user_input:
513
513
  run_input_handlers = True
514
- # 如果有工具调用且用户确认继续,则将干预信息和工具执行结果拼接为prompt
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
- self.session.prompt = (
523
- f"{user_input}\n\n{current_response}"
524
- )
525
- run_input_handlers = False
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
- self.session.prompt += f"{user_input}"
528
- continue
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
 
@@ -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 assistant")
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
- editors = ["nvim", "vim", "vi"]
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 (nvim, vim, vi).", OutputType.ERROR
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 type to use, choices are 'normal' and 'thinking'",
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, "--model_group", help="Model group to use, overriding config"
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="Restore session from .jarvis/saved_session.json",
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)
@@ -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 assistant")
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="Path to agent config file"
43
+ None, "-f", "--config", help="代理配置文件路径"
44
44
  ),
45
45
  agent_definition: Optional[str] = typer.Option(
46
- None, "-c", "--agent_definition", help="Path to agent definition file"
46
+ None, "-c", "--agent_definition", help="代理定义文件路径"
47
47
  ),
48
48
  task: Optional[str] = typer.Option(
49
- None, "-t", "--task", help="Initial task to execute"
49
+ None, "-t", "--task", help="初始任务内容"
50
50
  ),
51
51
  llm_type: str = typer.Option(
52
52
  "normal",
53
53
  "--llm_type",
54
- help="LLM type to use, overriding config",
54
+ help="使用的LLM类型,覆盖配置文件中的设置",
55
55
  ),
56
56
  model_group: Optional[str] = typer.Option(
57
- None, "--model_group", help="Model group to use, overriding config"
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 Code Agent")
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
- - 结构分析:优先使用 fd 命令或 find 工具快速定位文件和目录结构
88
- - 内容搜索:优先使用 rg(ripgrep)进行函数、类、变量等内容的全文搜索,避免遗漏
89
- - 依赖关系:如需分析依赖、调用关系,可结合 grep、ctags、pyan3 等工具辅助
90
+ - 结构分析:优先使用文件搜索工具快速定位文件和目录结构
91
+ - 内容搜索:优先使用全文搜索工具进行函数、类、变量等内容的搜索,避免遗漏
92
+ - 依赖关系:如需分析依赖、调用关系,可结合代码分析工具辅助
90
93
  - 代码阅读:使用 read_code 工具获取目标文件的完整内容或指定范围内容,禁止凭空假设代码
91
- - 变更影响:如需分析变更影响范围,可结合 git diff、git log 等命令辅助判断
94
+ - 变更影响:如需分析变更影响范围,可结合版本控制工具辅助判断
92
95
  - 工具优先级:优先使用自动化工具,减少人工推断,确保分析结果准确
93
96
  4. **方案设计**:确定最小变更方案,保持代码结构
94
97
  5. **实施修改**:遵循"先读后写"原则,保持代码风格一致性
95
98
 
96
99
  ## 工具使用
97
- - 项目结构:优先使用fd命令查找文件
98
- - 代码搜索:优先使用rg进行内容搜索
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 type to use, choices are 'normal' and 'thinking'",
640
+ help="使用的LLM类型,可选值:'normal'(普通)或 'thinking'(思考模式)",
413
641
  ),
414
642
  model_group: Optional[str] = typer.Option(
415
- None, "--model_group", help="Model group to use, overriding config"
643
+ None, "--llm_group", help="使用的模型组,覆盖配置文件中的设置"
416
644
  ),
417
645
  requirement: Optional[str] = typer.Option(
418
- None, "-r", "--requirement", help="Requirement to process"
646
+ None, "-r", "--requirement", help="要处理的需求描述"
419
647
  ),
420
648
  restore_session: bool = typer.Option(
421
649
  False,
422
650
  "--restore-session",
423
- help="Restore session from .jarvis/saved_session.json",
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)