jarvis-ai-assistant 0.5.1__py3-none-any.whl → 0.6.0__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 (29) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +15 -4
  3. jarvis/jarvis_agent/agent_manager.py +3 -0
  4. jarvis/jarvis_agent/jarvis.py +44 -14
  5. jarvis/jarvis_agent/run_loop.py +6 -1
  6. jarvis/jarvis_agent/task_planner.py +1 -0
  7. jarvis/jarvis_c2rust/__init__.py +13 -0
  8. jarvis/jarvis_c2rust/cli.py +405 -0
  9. jarvis/jarvis_c2rust/collector.py +209 -0
  10. jarvis/jarvis_c2rust/library_replacer.py +933 -0
  11. jarvis/jarvis_c2rust/llm_module_agent.py +1265 -0
  12. jarvis/jarvis_c2rust/scanner.py +1671 -0
  13. jarvis/jarvis_c2rust/transpiler.py +1236 -0
  14. jarvis/jarvis_code_agent/code_agent.py +144 -18
  15. jarvis/jarvis_data/config_schema.json +8 -3
  16. jarvis/jarvis_tools/cli/main.py +1 -0
  17. jarvis/jarvis_tools/execute_script.py +1 -1
  18. jarvis/jarvis_tools/read_code.py +11 -1
  19. jarvis/jarvis_tools/read_symbols.py +129 -0
  20. jarvis/jarvis_tools/registry.py +9 -1
  21. jarvis/jarvis_utils/config.py +14 -4
  22. jarvis/jarvis_utils/git_utils.py +39 -0
  23. jarvis/jarvis_utils/utils.py +13 -5
  24. {jarvis_ai_assistant-0.5.1.dist-info → jarvis_ai_assistant-0.6.0.dist-info}/METADATA +13 -1
  25. {jarvis_ai_assistant-0.5.1.dist-info → jarvis_ai_assistant-0.6.0.dist-info}/RECORD +29 -21
  26. {jarvis_ai_assistant-0.5.1.dist-info → jarvis_ai_assistant-0.6.0.dist-info}/entry_points.txt +2 -0
  27. {jarvis_ai_assistant-0.5.1.dist-info → jarvis_ai_assistant-0.6.0.dist-info}/WHEEL +0 -0
  28. {jarvis_ai_assistant-0.5.1.dist-info → jarvis_ai_assistant-0.6.0.dist-info}/licenses/LICENSE +0 -0
  29. {jarvis_ai_assistant-0.5.1.dist-info → jarvis_ai_assistant-0.6.0.dist-info}/top_level.txt +0 -0
@@ -26,6 +26,7 @@ from jarvis.jarvis_utils.config import (
26
26
  get_git_check_mode,
27
27
  set_config,
28
28
  get_data_dir,
29
+ is_plan_enabled,
29
30
  )
30
31
  from jarvis.jarvis_utils.git_utils import (
31
32
  confirm_add_new_files,
@@ -67,7 +68,6 @@ class CodeAgent:
67
68
 
68
69
  # 检测 git username 和 email 是否已设置
69
70
  self._check_git_config()
70
- tool_registry = ToolRegistry() # type: ignore
71
71
  base_tools = [
72
72
  "execute_script",
73
73
  "search_web",
@@ -87,7 +87,6 @@ class CodeAgent:
87
87
  # 去重
88
88
  base_tools = list(dict.fromkeys(base_tools))
89
89
 
90
- tool_registry.use_tools(base_tools)
91
90
  code_system_prompt = self._get_system_prompt()
92
91
  # 先加载全局规则(数据目录 rules),再加载项目规则(.jarvis/rules),并拼接为单一规则块注入
93
92
  global_rules = self._read_global_rules()
@@ -114,7 +113,8 @@ class CodeAgent:
114
113
  use_methodology=False, # 禁用方法论
115
114
  use_analysis=False, # 禁用分析
116
115
  non_interactive=self.non_interactive,
117
- plan=bool(plan) if plan is not None else False,
116
+ plan=bool(plan) if plan is not None else is_plan_enabled(),
117
+ use_tools=base_tools, # 仅启用限定工具
118
118
  )
119
119
 
120
120
  self.agent.event_bus.subscribe(AFTER_TOOL_CALL, self._on_after_tool_call)
@@ -281,29 +281,148 @@ class CodeAgent:
281
281
  return git_dir
282
282
 
283
283
  def _update_gitignore(self, git_dir: str) -> None:
284
- """检查并更新.gitignore文件,确保忽略.jarvis目录
284
+ """检查并更新.gitignore文件,确保忽略.jarvis目录,并追加常用语言的忽略规则(若缺失)
285
285
 
286
286
  参数:
287
287
  git_dir: git根目录路径
288
288
  """
289
-
290
289
  gitignore_path = os.path.join(git_dir, ".gitignore")
291
- jarvis_ignore = ".jarvis"
290
+
291
+ # 常用忽略规则(按语言/场景分组)
292
+ sections = {
293
+ "General": [
294
+ ".jarvis",
295
+ ".DS_Store",
296
+ "Thumbs.db",
297
+ "*.log",
298
+ "*.tmp",
299
+ "*.swp",
300
+ "*.swo",
301
+ ".idea/",
302
+ ".vscode/",
303
+ ],
304
+ "Python": [
305
+ "__pycache__/",
306
+ "*.py[cod]",
307
+ "*$py.class",
308
+ ".Python",
309
+ "env/",
310
+ "venv/",
311
+ ".venv/",
312
+ "build/",
313
+ "dist/",
314
+ "develop-eggs/",
315
+ "downloads/",
316
+ "eggs/",
317
+ ".eggs/",
318
+ "lib/",
319
+ "lib64/",
320
+ "parts/",
321
+ "sdist/",
322
+ "var/",
323
+ "wheels/",
324
+ "pip-wheel-metadata/",
325
+ "share/python-wheels/",
326
+ "*.egg-info/",
327
+ ".installed.cfg",
328
+ "*.egg",
329
+ "MANIFEST",
330
+ ".mypy_cache/",
331
+ ".pytest_cache/",
332
+ ".ruff_cache/",
333
+ ".tox/",
334
+ ".coverage",
335
+ ".coverage.*",
336
+ "htmlcov/",
337
+ ".hypothesis/",
338
+ ".ipynb_checkpoints",
339
+ ".pyre/",
340
+ ".pytype/",
341
+ ],
342
+ "Rust": [
343
+ "target/",
344
+ ],
345
+ "Node": [
346
+ "node_modules/",
347
+ "npm-debug.log*",
348
+ "yarn-debug.log*",
349
+ "yarn-error.log*",
350
+ "pnpm-debug.log*",
351
+ "lerna-debug.log*",
352
+ "dist/",
353
+ "coverage/",
354
+ ".turbo/",
355
+ ".next/",
356
+ ".nuxt/",
357
+ "out/",
358
+ ],
359
+ "Go": [
360
+ "bin/",
361
+ "vendor/",
362
+ "coverage.out",
363
+ ],
364
+ "Java": [
365
+ "target/",
366
+ "*.class",
367
+ ".gradle/",
368
+ "build/",
369
+ "out/",
370
+ ],
371
+ "C/C++": [
372
+ "build/",
373
+ "cmake-build-*/",
374
+ "*.o",
375
+ "*.a",
376
+ "*.so",
377
+ "*.obj",
378
+ "*.dll",
379
+ "*.dylib",
380
+ "*.exe",
381
+ "*.pdb",
382
+ ],
383
+ ".NET": [
384
+ "bin/",
385
+ "obj/",
386
+ ],
387
+ }
388
+
389
+ existing_content = ""
390
+ if os.path.exists(gitignore_path):
391
+ with open(gitignore_path, "r", encoding="utf-8", errors="replace") as f:
392
+ existing_content = f.read()
393
+
394
+ # 已存在的忽略项(去除注释与空行)
395
+ existing_set = set(
396
+ ln.strip()
397
+ for ln in existing_content.splitlines()
398
+ if ln.strip() and not ln.strip().startswith("#")
399
+ )
400
+
401
+ # 计算缺失项并准备追加内容
402
+ new_lines: List[str] = []
403
+ for name, patterns in sections.items():
404
+ missing = [p for p in patterns if p not in existing_set]
405
+ if missing:
406
+ new_lines.append(f"# {name}")
407
+ new_lines.extend(missing)
408
+ new_lines.append("") # 分组空行
292
409
 
293
410
  if not os.path.exists(gitignore_path):
294
- with open(gitignore_path, "w", encoding="utf-8") as f:
295
- f.write(f"{jarvis_ignore}\n")
296
- PrettyOutput.print(
297
- f"已创建 .gitignore 并添加 '{jarvis_ignore}'", OutputType.SUCCESS
298
- )
411
+ # 新建 .gitignore(仅包含缺失项;此处即为全部常用规则)
412
+ with open(gitignore_path, "w", encoding="utf-8", newline="\n") as f:
413
+ content_to_write = "\n".join(new_lines).rstrip()
414
+ if content_to_write:
415
+ f.write(content_to_write + "\n")
416
+ PrettyOutput.print("已创建 .gitignore 并添加常用忽略规则", OutputType.SUCCESS)
299
417
  else:
300
- with open(gitignore_path, "r+", encoding="utf-8") as f:
301
- content = f.read()
302
- if jarvis_ignore not in content.splitlines():
303
- f.write(f"\n{jarvis_ignore}\n")
304
- PrettyOutput.print(
305
- f"已更新 .gitignore,添加 '{jarvis_ignore}'", OutputType.SUCCESS
306
- )
418
+ if new_lines:
419
+ # 追加缺失的规则
420
+ with open(gitignore_path, "a", encoding="utf-8", newline="\n") as f:
421
+ # 若原文件不以换行结尾,先补一行
422
+ if existing_content and not existing_content.endswith("\n"):
423
+ f.write("\n")
424
+ f.write("\n".join(new_lines).rstrip() + "\n")
425
+ PrettyOutput.print("已更新 .gitignore,追加常用忽略规则", OutputType.SUCCESS)
307
426
 
308
427
  def _handle_git_changes(self, prefix: str, suffix: str) -> None:
309
428
  """处理git仓库中的未提交修改"""
@@ -590,6 +709,7 @@ class CodeAgent:
590
709
  返回:
591
710
  str: 描述执行结果的输出,成功时返回None
592
711
  """
712
+ prev_dir = os.getcwd()
593
713
  try:
594
714
  self._init_env(prefix, suffix)
595
715
  start_commit = get_latest_commit_hash()
@@ -647,6 +767,12 @@ class CodeAgent:
647
767
 
648
768
  except RuntimeError as e:
649
769
  return f"Error during execution: {str(e)}"
770
+ finally:
771
+ # Ensure switching back to the original working directory after CodeAgent completes
772
+ try:
773
+ os.chdir(prev_dir)
774
+ except Exception:
775
+ pass
650
776
 
651
777
  def _on_after_tool_call(self, agent: Agent, current_response=None, need_return=None, tool_prompt=None, **kwargs) -> None:
652
778
  """工具调用后回调函数。"""
@@ -188,9 +188,14 @@
188
188
  "description": "AI工具筛选阈值:当可用工具数量超过此值时触发AI筛选",
189
189
  "default": 30
190
190
  },
191
+ "JARVIS_PLAN_ENABLED": {
192
+ "type": "boolean",
193
+ "description": "是否默认启用任务规划。当 Agent 初始化时 plan 参数未指定,将从此配置加载。默认 false。",
194
+ "default": false
195
+ },
191
196
  "JARVIS_PLAN_MAX_DEPTH": {
192
197
  "type": "number",
193
- "description": "任务规划的最大层数。用于限制 plan 模式的递归拆分深度。仅在启用规划时生效(通过 CLI --plan/--no-plan 控制),默认 3。",
198
+ "description": "任务规划的最大层数。用于限制 plan 模式的递归拆分深度。仅在启用规划时生效(通过 CLI --plan/--no-plan 控制),默认 2。",
194
199
  "default": 2
195
200
  },
196
201
  "JARVIS_SCRIPT_EXECUTION_TIMEOUT": {
@@ -315,12 +320,12 @@
315
320
  "JARVIS_ENABLE_GIT_JCA_SWITCH": {
316
321
  "type": "boolean",
317
322
  "description": "在初始化环境前检测Git仓库并提示可切换到代码开发模式(jca)",
318
- "default": false
323
+ "default": true
319
324
  },
320
325
  "JARVIS_ENABLE_STARTUP_CONFIG_SELECTOR": {
321
326
  "type": "boolean",
322
327
  "description": "在进入默认通用代理前,列出可用配置(agent/multi_agent/roles)供选择",
323
- "default": false
328
+ "default": true
324
329
  },
325
330
  "JARVIS_IMMEDIATE_ABORT": {
326
331
  "type": "boolean",
@@ -11,6 +11,7 @@ from jarvis.jarvis_utils.utils import init_env
11
11
  app = typer.Typer(help="Jarvis 工具系统命令行界面")
12
12
 
13
13
 
14
+
14
15
  @app.command("list")
15
16
  def list_tools(
16
17
  as_json: bool = typer.Option(False, "--json", help="以JSON格式输出"),
@@ -210,7 +210,7 @@ class ScriptTool:
210
210
  if __name__ == "__main__":
211
211
  script_tool = ScriptTool()
212
212
  PrettyOutput.print(
213
- script_tool.get_display_output("/home/wangmaobin/code/Jarvis/a.txt"),
213
+ script_tool.get_display_output("/path/to/a.txt"),
214
214
  OutputType.CODE,
215
215
  lang="text",
216
216
  )
@@ -150,6 +150,7 @@ class ReadCodeTool:
150
150
 
151
151
  all_outputs = []
152
152
  overall_success = True
153
+ status_lines = []
153
154
 
154
155
  for file_info in args["files"]:
155
156
  if not isinstance(file_info, dict) or "path" not in file_info:
@@ -164,13 +165,22 @@ class ReadCodeTool:
164
165
 
165
166
  if result["success"]:
166
167
  all_outputs.append(result["stdout"])
168
+ status_lines.append(f"✅ {file_info['path']} 文件读取成功")
167
169
  else:
168
170
  all_outputs.append(f"❌ {file_info['path']}: {result['stderr']}")
171
+ status_lines.append(f"❌ {file_info['path']} 文件读取失败")
169
172
  overall_success = False
170
173
 
174
+ stdout_text = "\n".join(all_outputs)
175
+ # 仅打印每个文件的读取状态,不打印具体内容
176
+ try:
177
+ if status_lines:
178
+ print("\n".join(status_lines), end="\n")
179
+ except Exception:
180
+ pass
171
181
  return {
172
182
  "success": overall_success,
173
- "stdout": "\n".join(all_outputs),
183
+ "stdout": stdout_text,
174
184
  "stderr": "",
175
185
  }
176
186
 
@@ -0,0 +1,129 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 按需读取 symbols.jsonl 的工具。
4
+
5
+ 用途:
6
+ - 避免Agent直接完整读取体积较大的符号表文件;
7
+ - 通过提供符号表路径与符号名称列表,仅返回匹配的符号记录。
8
+
9
+ 参数:
10
+ - symbols_file (str): 符号表文件路径(.jsonl),或项目根目录/包含 .jarvis/c2rust 的目录
11
+ - symbols (List[str]): 需要读取的符号名称列表(支持 name 与 qualified_name 匹配)
12
+
13
+ 返回:
14
+ - success (bool)
15
+ - stdout (str): JSON文本,包含查询结果
16
+ - stderr (str)
17
+ """
18
+ import json
19
+ import os
20
+ from pathlib import Path
21
+ from typing import Any, Dict, List
22
+
23
+ from jarvis.jarvis_utils.output import OutputType, PrettyOutput
24
+
25
+
26
+ class ReadSymbolsTool:
27
+ # 文件名必须与工具名一致,便于注册表自动加载
28
+ name = "read_symbols"
29
+ description = "从symbols.jsonl按需读取指定符号的记录,避免完整加载大文件。参数包含符号表路径和符号名列表(匹配 name 与 qualified_name)(C2Rust工具专用)"
30
+ parameters = {
31
+ "type": "object",
32
+ "properties": {
33
+ "symbols_file": {
34
+ "type": "string",
35
+ "description": "符号表文件路径(.jsonl)。若为目录,则解析为 <dir>/.jarvis/c2rust/symbols.jsonl",
36
+ },
37
+ "symbols": {
38
+ "type": "array",
39
+ "items": {"type": "string"},
40
+ "description": "要检索的符号名称列表(支持 name 或 qualified_name 完全匹配)",
41
+ },
42
+ },
43
+ "required": ["symbols_file", "symbols"],
44
+ }
45
+
46
+ @staticmethod
47
+ def _resolve_symbols_jsonl_path(path_hint: str) -> Path:
48
+ """
49
+ 解析符号表路径:
50
+ - 若为目录,返回 <dir>/.jarvis/c2rust/symbols.jsonl
51
+ - 若为文件,直接返回
52
+ """
53
+ p = Path(os.path.abspath(os.path.expanduser(path_hint)))
54
+ if p.is_dir():
55
+ candidate = p / ".jarvis" / "c2rust" / "symbols.jsonl"
56
+ return candidate
57
+ return p
58
+
59
+ def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
60
+ try:
61
+ symbols_file_arg = args.get("symbols_file")
62
+ symbols_arg = args.get("symbols")
63
+
64
+ if not isinstance(symbols_file_arg, str) or not symbols_file_arg.strip():
65
+ return {"success": False, "stdout": "", "stderr": "缺少或无效的 symbols_file 参数"}
66
+
67
+ if not isinstance(symbols_arg, list) or not all(isinstance(s, str) for s in symbols_arg):
68
+ return {"success": False, "stdout": "", "stderr": "symbols 参数必须是字符串列表"}
69
+
70
+ symbols_path = self._resolve_symbols_jsonl_path(symbols_file_arg)
71
+ if not symbols_path.exists():
72
+ return {"success": False, "stdout": "", "stderr": f"符号表文件不存在: {symbols_path}"}
73
+ if not symbols_path.is_file():
74
+ return {"success": False, "stdout": "", "stderr": f"符号表路径不是文件: {symbols_path}"}
75
+
76
+ # 使用集合提升匹配效率;保持原请求顺序以便输出
77
+ requested: List[str] = [s.strip() for s in symbols_arg if s and s.strip()]
78
+ wanted_set = set(requested)
79
+
80
+ results: Dict[str, List[Dict[str, Any]]] = {s: [] for s in requested}
81
+
82
+ # 流式读取,避免载入整个大文件
83
+ with open(symbols_path, "r", encoding="utf-8") as f:
84
+ for line in f:
85
+ line = line.strip()
86
+ if not line:
87
+ continue
88
+ try:
89
+ obj = json.loads(line)
90
+ except Exception:
91
+ continue
92
+
93
+ name = obj.get("name") or ""
94
+ qname = obj.get("qualified_name") or ""
95
+
96
+ # 仅当命中请求的符号时才记录
97
+ if name in wanted_set:
98
+ results[name].append(obj)
99
+ if qname in wanted_set and qname != name:
100
+ results[qname].append(obj)
101
+
102
+ not_found = [s for s in requested if not results.get(s)]
103
+ found_counts = {s: len(results.get(s, [])) for s in requested}
104
+
105
+ out_obj: Dict[str, Any] = {
106
+ "symbols_file": str(symbols_path),
107
+ "requested": requested,
108
+ "found_counts": found_counts,
109
+ "not_found": not_found,
110
+ "items": results,
111
+ }
112
+
113
+ stdout = json.dumps(out_obj, ensure_ascii=False, indent=2)
114
+ # 简要状态打印(不包含具体内容)
115
+ try:
116
+ status_lines = []
117
+ for s in requested:
118
+ cnt = found_counts.get(s, 0)
119
+ status_lines.append(f"[read_symbols] {s}: {cnt} 条匹配")
120
+ if status_lines:
121
+ print("\n".join(status_lines), end="\n")
122
+ except Exception:
123
+ pass
124
+
125
+ return {"success": True, "stdout": stdout, "stderr": ""}
126
+
127
+ except Exception as e:
128
+ PrettyOutput.print(str(e), OutputType.ERROR)
129
+ return {"success": False, "stdout": "", "stderr": f"读取符号表失败: {str(e)}"}
@@ -175,7 +175,15 @@ class ToolRegistry(OutputHandlerProtocol):
175
175
  try:
176
176
  tool_call, err_msg, auto_completed = self._extract_tool_calls(response)
177
177
  if err_msg:
178
- return False, err_msg
178
+ # 只要工具解析错误,追加工具使用帮助信息(相当于一次 <ToolUsage>)
179
+ try:
180
+ from jarvis.jarvis_agent import Agent
181
+ agent: Agent = agent_
182
+ tool_usage = agent.get_tool_usage_prompt()
183
+ return False, f"{err_msg}\n\n{tool_usage}"
184
+ except Exception:
185
+ # 兼容处理:无法获取Agent或ToolUsage时,至少返回工具系统帮助信息
186
+ return False, f"{err_msg}\n\n{tool_call_help}"
179
187
  result = self.handle_tool_calls(tool_call, agent_)
180
188
  if auto_completed:
181
189
  # 如果自动补全了结束标签,在结果中添加说明信息
@@ -713,6 +713,16 @@ def get_plan_max_depth() -> int:
713
713
  return 2
714
714
 
715
715
 
716
+ def is_plan_enabled() -> bool:
717
+ """
718
+ 获取是否默认启用任务规划。
719
+
720
+ 返回:
721
+ bool: 如果启用任务规划则返回True,默认为False
722
+ """
723
+ return GLOBAL_CONFIG_DATA.get("JARVIS_PLAN_ENABLED", False) is True
724
+
725
+
716
726
  def get_auto_summary_rounds() -> int:
717
727
  """
718
728
  获取基于对话轮次的自动总结阈值。
@@ -742,18 +752,18 @@ def get_script_execution_timeout() -> int:
742
752
  def is_enable_git_repo_jca_switch() -> bool:
743
753
  """
744
754
  是否启用:在初始化环境前检测Git仓库并提示可切换到代码开发模式(jca)
745
- 默认关闭
755
+ 默认开启
746
756
  """
747
- return GLOBAL_CONFIG_DATA.get("JARVIS_ENABLE_GIT_JCA_SWITCH", False) is True
757
+ return GLOBAL_CONFIG_DATA.get("JARVIS_ENABLE_GIT_JCA_SWITCH", True) is True
748
758
 
749
759
 
750
760
  def is_enable_builtin_config_selector() -> bool:
751
761
  """
752
762
  是否启用:在进入默认通用代理前,列出可用配置(agent/multi_agent/roles)供选择
753
- 默认关闭
763
+ 默认开启
754
764
  """
755
765
  return (
756
- GLOBAL_CONFIG_DATA.get("JARVIS_ENABLE_STARTUP_CONFIG_SELECTOR", False) is True
766
+ GLOBAL_CONFIG_DATA.get("JARVIS_ENABLE_STARTUP_CONFIG_SELECTOR", True) is True
757
767
  )
758
768
 
759
769
 
@@ -719,6 +719,45 @@ def confirm_add_new_files() -> None:
719
719
  "是否要添加这些变更(如果不需要请修改.gitignore文件以忽略不需要的文件)?",
720
720
  False,
721
721
  ):
722
+ # 用户选择 N:自动将未跟踪文件列表添加到仓库根目录的 .gitignore
723
+ try:
724
+ repo_root_result = subprocess.run(
725
+ ["git", "rev-parse", "--show-toplevel"],
726
+ capture_output=True,
727
+ text=True,
728
+ check=True,
729
+ )
730
+ repo_root = repo_root_result.stdout.strip() or "."
731
+ except Exception:
732
+ repo_root = "."
733
+ gitignore_path = os.path.join(repo_root, ".gitignore")
734
+
735
+ # 仅对未跟踪的新文件进行忽略(已跟踪文件无法通过 .gitignore 忽略)
736
+ files_to_ignore = sorted(set(new_files))
737
+
738
+ # 读取已存在的 .gitignore 以避免重复添加
739
+ existing_lines: Set[str] = set()
740
+ try:
741
+ if os.path.exists(gitignore_path):
742
+ with open(gitignore_path, "r", encoding="utf-8") as f:
743
+ existing_lines = set(line.strip() for line in f if line.strip())
744
+ except Exception:
745
+ existing_lines = set()
746
+
747
+ # 追加未存在的文件路径到 .gitignore(使用相对于仓库根目录的路径)
748
+ try:
749
+ with open(gitignore_path, "a", encoding="utf-8") as f:
750
+ for file in files_to_ignore:
751
+ abs_path = os.path.abspath(file)
752
+ rel_path = os.path.relpath(abs_path, repo_root)
753
+ # 避免无效的相对路径(不应出现 .. 前缀),有则回退用原始值
754
+ entry = rel_path if not rel_path.startswith("..") else file
755
+ if entry not in existing_lines:
756
+ f.write(entry + "\n")
757
+ PrettyOutput.print("已将未跟踪文件添加到 .gitignore,正在重新检测...", OutputType.INFO)
758
+ except Exception as e:
759
+ PrettyOutput.print(f"更新 .gitignore 失败: {str(e)}", OutputType.WARNING)
760
+
722
761
  continue
723
762
 
724
763
  break
@@ -464,14 +464,22 @@ def _check_jarvis_updates() -> bool:
464
464
  返回:
465
465
  bool: 是否需要重启进程
466
466
  """
467
- script_dir = Path(os.path.dirname(os.path.dirname(__file__)))
467
+ # 从当前文件目录向上查找包含 .git 的仓库根目录,修复原先只检查 src/jarvis 的问题
468
+ try:
469
+ script_path = Path(__file__).resolve()
470
+ repo_root: Optional[Path] = None
471
+ for d in [script_path.parent] + list(script_path.parents):
472
+ if (d / ".git").exists():
473
+ repo_root = d
474
+ break
475
+ except Exception:
476
+ repo_root = None
468
477
 
469
- # 先检查是否是git源码安装
470
- git_dir = script_dir / ".git"
471
- if git_dir.exists():
478
+ # 先检查是否是git源码安装(找到仓库根目录即认为是源码安装)
479
+ if repo_root and (repo_root / ".git").exists():
472
480
  from jarvis.jarvis_utils.git_utils import check_and_update_git_repo
473
481
 
474
- return check_and_update_git_repo(str(script_dir))
482
+ return check_and_update_git_repo(str(repo_root))
475
483
 
476
484
  # 检查是否是pip/uv pip安装的版本
477
485
  return _check_pip_updates()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jarvis-ai-assistant
3
- Version: 0.5.1
3
+ Version: 0.6.0
4
4
  Summary: Jarvis: An AI assistant that uses tools to interact with the system
5
5
  Home-page: https://github.com/skyfireitdiy/Jarvis
6
6
  Author: skyfire
@@ -78,6 +78,18 @@ Requires-Dist: sentence-transformers==2.7.0; extra == "rag"
78
78
  Requires-Dist: torch>=2.6; extra == "rag"
79
79
  Requires-Dist: unstructured[md]; extra == "rag"
80
80
  Requires-Dist: rank-bm25; extra == "rag"
81
+ Provides-Extra: clang16
82
+ Requires-Dist: clang==16.*; extra == "clang16"
83
+ Provides-Extra: clang17
84
+ Requires-Dist: clang==17.*; extra == "clang17"
85
+ Provides-Extra: clang18
86
+ Requires-Dist: clang==18.*; extra == "clang18"
87
+ Provides-Extra: clang19
88
+ Requires-Dist: clang==19.*; extra == "clang19"
89
+ Provides-Extra: clang20
90
+ Requires-Dist: clang==20.*; extra == "clang20"
91
+ Provides-Extra: clang21
92
+ Requires-Dist: clang==21.*; extra == "clang21"
81
93
  Dynamic: author
82
94
  Dynamic: home-page
83
95
  Dynamic: license-file