jarvis-ai-assistant 0.2.2__py3-none-any.whl → 0.2.4__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 (39) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/edit_file_handler.py +5 -0
  3. jarvis/jarvis_agent/jarvis.py +22 -25
  4. jarvis/jarvis_agent/main.py +6 -6
  5. jarvis/jarvis_agent/prompts.py +26 -4
  6. jarvis/jarvis_code_agent/code_agent.py +279 -11
  7. jarvis/jarvis_code_analysis/code_review.py +21 -19
  8. jarvis/jarvis_data/config_schema.json +86 -18
  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_platform/tongyi.py +9 -9
  14. jarvis/jarvis_rag/cli.py +79 -23
  15. jarvis/jarvis_rag/query_rewriter.py +61 -12
  16. jarvis/jarvis_rag/rag_pipeline.py +143 -34
  17. jarvis/jarvis_rag/retriever.py +6 -6
  18. jarvis/jarvis_smart_shell/main.py +2 -2
  19. jarvis/jarvis_stats/__init__.py +13 -0
  20. jarvis/jarvis_stats/cli.py +337 -0
  21. jarvis/jarvis_stats/stats.py +433 -0
  22. jarvis/jarvis_stats/storage.py +329 -0
  23. jarvis/jarvis_stats/visualizer.py +443 -0
  24. jarvis/jarvis_tools/cli/main.py +84 -15
  25. jarvis/jarvis_tools/generate_new_tool.py +22 -1
  26. jarvis/jarvis_tools/registry.py +35 -16
  27. jarvis/jarvis_tools/search_web.py +3 -3
  28. jarvis/jarvis_tools/virtual_tty.py +315 -26
  29. jarvis/jarvis_utils/config.py +98 -11
  30. jarvis/jarvis_utils/git_utils.py +8 -16
  31. jarvis/jarvis_utils/globals.py +29 -8
  32. jarvis/jarvis_utils/input.py +114 -121
  33. jarvis/jarvis_utils/utils.py +213 -37
  34. {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/METADATA +99 -9
  35. {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/RECORD +39 -34
  36. {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/entry_points.txt +2 -0
  37. {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/WHEEL +0 -0
  38. {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/licenses/LICENSE +0 -0
  39. {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.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.2"
4
+ __version__ = "0.2.4"
@@ -55,6 +55,11 @@ 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
+ stats_manager = StatsManager()
62
+ stats_manager.increment("edit_file", group="tool")
58
63
 
59
64
  results = []
60
65
 
@@ -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"""
@@ -113,6 +113,31 @@ TASK_ANALYSIS_PROMPT = f"""<task_analysis>
113
113
  "stderr": f"操作失败: {{str(e)}}"
114
114
  }}
115
115
  ```
116
+ 4. **在工具中调用大模型**:如果工具需要调用大模型来完成子任务(例如,生成代码、分析文本等),为了避免干扰主对话流程,建议创建一个独立的大模型实例。
117
+ ```python
118
+ # 通过 agent 实例获取模型配置
119
+ agent = args.get("agent")
120
+ if not agent:
121
+ return {{"success": False, "stderr": "Agent not found."}}
122
+
123
+ current_model = agent.model
124
+ platform_name = current_model.platform_name()
125
+ model_name = current_model.name()
126
+
127
+ # 创建独立的模型实例
128
+ from jarvis.jarvis_platform.registry import PlatformRegistry
129
+ llm = PlatformRegistry().create_platform(platform_name)
130
+ if not llm:
131
+ return {{"success": False, "stderr": f"Platform {{platform_name}} not found."}}
132
+
133
+ llm.set_model_name(model_name)
134
+ llm.set_suppress_output(True) # 工具内的调用通常不需要流式输出
135
+
136
+ # 使用新实例调用大模型
137
+ PrettyOutput.print("正在执行子任务...", OutputType.INFO)
138
+ response = llm.chat_until_success("你的提示词")
139
+ PrettyOutput.print("子任务完成", OutputType.SUCCESS)
140
+ ```
116
141
  </tool_requirements>
117
142
  <methodology_requirements>
118
143
  方法论格式要求:
@@ -139,10 +164,7 @@ arguments:
139
164
  from jarvis.jarvis_utils.output import PrettyOutput, OutputType
140
165
  class 工具名称:
141
166
  name = "工具名称"
142
- description = "Tool for text transformation"
143
- Tool description
144
- 适用场景:1. 格式化文本; 2. 处理标题; 3. 标准化输出
145
- \"\"\"
167
+ description = "Tool description"
146
168
  parameters = {{
147
169
  "type": "object",
148
170
  "properties": {{
@@ -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,138 @@ 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
+ stats_manager = StatsManager()
352
+
353
+ # 匹配插入行数
354
+ insertions_match = re.search(r"(\d+)\s+insertions?\(\+\)", diff_text)
355
+ if insertions_match:
356
+ insertions = int(insertions_match.group(1))
357
+ stats_manager.increment(
358
+ "code_lines_inserted", amount=insertions, group="code_agent"
359
+ )
360
+
361
+ # 匹配删除行数
362
+ deletions_match = re.search(r"(\d+)\s+deletions?\(\-\)", diff_text)
363
+ if deletions_match:
364
+ deletions = int(deletions_match.group(1))
365
+ stats_manager.increment(
366
+ "code_lines_deleted", amount=deletions, group="code_agent"
367
+ )
368
+
195
369
  def _handle_uncommitted_changes(self) -> None:
196
370
  """处理未提交的修改,包括:
197
371
  1. 提示用户确认是否提交
@@ -201,10 +375,31 @@ class CodeAgent:
201
375
  5. 暂存并提交所有修改
202
376
  """
203
377
  if has_uncommitted_changes():
378
+ # 获取代码变更统计
379
+ try:
380
+ diff_result = subprocess.run(
381
+ ["git", "diff", "HEAD", "--shortstat"],
382
+ capture_output=True,
383
+ text=True,
384
+ encoding="utf-8",
385
+ errors="replace",
386
+ check=True,
387
+ )
388
+ if diff_result.returncode == 0 and diff_result.stdout:
389
+ self._record_code_changes_stats(diff_result.stdout)
390
+ except subprocess.CalledProcessError:
391
+ pass
392
+
204
393
  PrettyOutput.print("检测到未提交的修改,是否要提交?", OutputType.WARNING)
205
394
  if not user_confirm("是否要提交?", True):
206
395
  return
207
396
 
397
+ # 用户确认修改,统计修改次数
398
+ from jarvis.jarvis_stats.stats import StatsManager
399
+
400
+ stats_manager = StatsManager()
401
+ stats_manager.increment("code_modification_confirmed", group="code_agent")
402
+
208
403
  try:
209
404
  confirm_add_new_files()
210
405
 
@@ -216,6 +411,8 @@ class CodeAgent:
216
411
  ["git", "rev-list", "--count", "HEAD"],
217
412
  capture_output=True,
218
413
  text=True,
414
+ encoding="utf-8",
415
+ errors="replace",
219
416
  check=True,
220
417
  )
221
418
  if commit_result.returncode != 0:
@@ -231,6 +428,9 @@ class CodeAgent:
231
428
  ["git", "commit", "-m", f"CheckPoint #{commit_count + 1}"],
232
429
  check=True,
233
430
  )
431
+
432
+ # 统计提交次数
433
+ stats_manager.increment("code_commits_accepted", group="code_agent")
234
434
  except subprocess.CalledProcessError as e:
235
435
  PrettyOutput.print(f"提交失败: {str(e)}", OutputType.ERROR)
236
436
 
@@ -252,6 +452,12 @@ class CodeAgent:
252
452
  commits = []
253
453
 
254
454
  if commits:
455
+ # 统计生成的commit数量
456
+ from jarvis.jarvis_stats.stats import StatsManager
457
+
458
+ stats_manager = StatsManager()
459
+ stats_manager.increment("commits_generated", group="code_agent")
460
+
255
461
  commit_messages = "检测到以下提交记录:\n" + "\n".join(
256
462
  f"- {commit_hash[:7]}: {message}" for commit_hash, message in commits
257
463
  )
@@ -263,6 +469,12 @@ class CodeAgent:
263
469
  ) -> None:
264
470
  """处理提交确认和可能的重置"""
265
471
  if commits and user_confirm("是否接受以上提交记录?", True):
472
+ # 统计接受的commit数量
473
+ from jarvis.jarvis_stats.stats import StatsManager
474
+
475
+ stats_manager = StatsManager()
476
+ stats_manager.increment("commits_accepted", group="code_agent")
477
+
266
478
  subprocess.run(
267
479
  ["git", "reset", "--mixed", str(start_commit)],
268
480
  stdout=subprocess.DEVNULL,
@@ -350,6 +562,28 @@ class CodeAgent:
350
562
  modified_files = get_diff_file_list()
351
563
  commited = handle_commit_workflow()
352
564
  if commited:
565
+ # 统计代码行数变化
566
+ # 获取diff的统计信息
567
+ try:
568
+ diff_result = subprocess.run(
569
+ ["git", "diff", "HEAD~1", "HEAD", "--shortstat"],
570
+ capture_output=True,
571
+ text=True,
572
+ encoding="utf-8",
573
+ errors="replace",
574
+ check=True,
575
+ )
576
+ if diff_result.returncode == 0 and diff_result.stdout:
577
+ self._record_code_changes_stats(diff_result.stdout)
578
+ except subprocess.CalledProcessError:
579
+ pass
580
+
581
+ # 统计修改次数
582
+ from jarvis.jarvis_stats.stats import StatsManager
583
+
584
+ stats_manager = StatsManager()
585
+ stats_manager.increment("code_modifications", group="code_agent")
586
+
353
587
  # 获取提交信息
354
588
  end_hash = get_latest_commit_hash()
355
589
  commits = get_commits_between(start_hash, end_hash)
@@ -409,23 +643,57 @@ def cli(
409
643
  llm_type: str = typer.Option(
410
644
  "normal",
411
645
  "--llm_type",
412
- help="LLM type to use, choices are 'normal' and 'thinking'",
646
+ help="使用的LLM类型,可选值:'normal'(普通)或 'thinking'(思考模式)",
413
647
  ),
414
648
  model_group: Optional[str] = typer.Option(
415
- None, "--model_group", help="Model group to use, overriding config"
649
+ None, "--llm_group", help="使用的模型组,覆盖配置文件中的设置"
416
650
  ),
417
651
  requirement: Optional[str] = typer.Option(
418
- None, "-r", "--requirement", help="Requirement to process"
652
+ None, "-r", "--requirement", help="要处理的需求描述"
419
653
  ),
420
654
  restore_session: bool = typer.Option(
421
655
  False,
422
656
  "--restore-session",
423
- help="Restore session from .jarvis/saved_session.json",
657
+ help=" .jarvis/saved_session.json 恢复会话状态",
424
658
  ),
425
659
  ) -> None:
426
660
  """Jarvis主入口点。"""
427
661
  init_env("欢迎使用 Jarvis-CodeAgent,您的代码工程助手已准备就绪!")
428
662
 
663
+ try:
664
+ subprocess.run(
665
+ ["git", "rev-parse", "--git-dir"],
666
+ check=True,
667
+ stdout=subprocess.DEVNULL,
668
+ stderr=subprocess.DEVNULL,
669
+ )
670
+ except (subprocess.CalledProcessError, FileNotFoundError):
671
+ curr_dir_path = os.getcwd()
672
+ PrettyOutput.print(
673
+ f"警告:当前目录 '{curr_dir_path}' 不是一个git仓库。", OutputType.WARNING
674
+ )
675
+ if user_confirm(
676
+ f"是否要在 '{curr_dir_path}' 中初始化一个新的git仓库?", default=True
677
+ ):
678
+ try:
679
+ subprocess.run(
680
+ ["git", "init"],
681
+ check=True,
682
+ capture_output=True,
683
+ text=True,
684
+ encoding="utf-8",
685
+ errors="replace",
686
+ )
687
+ PrettyOutput.print("✅ 已成功初始化git仓库。", OutputType.SUCCESS)
688
+ except (subprocess.CalledProcessError, FileNotFoundError) as e:
689
+ PrettyOutput.print(f"❌ 初始化git仓库失败: {e}", OutputType.ERROR)
690
+ sys.exit(1)
691
+ else:
692
+ PrettyOutput.print(
693
+ "操作已取消。Jarvis需要在git仓库中运行。", OutputType.INFO
694
+ )
695
+ sys.exit(0)
696
+
429
697
  curr_dir = os.getcwd()
430
698
  git_dir = find_git_root_and_cd(curr_dir)
431
699
  PrettyOutput.print(f"当前目录: {git_dir}", OutputType.INFO)