jarvis-ai-assistant 0.4.2__py3-none-any.whl → 0.5.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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +15 -1
- jarvis/jarvis_agent/share_manager.py +8 -1
- jarvis/jarvis_code_agent/code_agent.py +91 -2
- jarvis/jarvis_code_analysis/code_review.py +483 -568
- jarvis/jarvis_data/config_schema.json +2 -2
- jarvis/jarvis_tools/registry.py +20 -14
- jarvis/jarvis_utils/methodology.py +25 -19
- jarvis/jarvis_utils/utils.py +58 -2
- {jarvis_ai_assistant-0.4.2.dist-info → jarvis_ai_assistant-0.5.0.dist-info}/METADATA +1 -1
- {jarvis_ai_assistant-0.4.2.dist-info → jarvis_ai_assistant-0.5.0.dist-info}/RECORD +15 -15
- {jarvis_ai_assistant-0.4.2.dist-info → jarvis_ai_assistant-0.5.0.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.4.2.dist-info → jarvis_ai_assistant-0.5.0.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.4.2.dist-info → jarvis_ai_assistant-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.4.2.dist-info → jarvis_ai_assistant-0.5.0.dist-info}/top_level.txt +0 -0
jarvis/__init__.py
CHANGED
jarvis/jarvis_agent/__init__.py
CHANGED
|
@@ -249,8 +249,23 @@ class Agent:
|
|
|
249
249
|
def clear_history(self):
|
|
250
250
|
"""
|
|
251
251
|
Clears the current conversation history by delegating to the session manager.
|
|
252
|
+
Emits BEFORE_HISTORY_CLEAR/AFTER_HISTORY_CLEAR and reapplies system prompt to preserve constraints.
|
|
252
253
|
"""
|
|
254
|
+
# 广播清理历史前事件(不影响主流程)
|
|
255
|
+
try:
|
|
256
|
+
self.event_bus.emit(BEFORE_HISTORY_CLEAR, agent=self)
|
|
257
|
+
except Exception:
|
|
258
|
+
pass
|
|
259
|
+
|
|
260
|
+
# 清理会话历史并重置模型状态
|
|
253
261
|
self.session.clear_history()
|
|
262
|
+
|
|
263
|
+
# 重置后重新设置系统提示词,确保系统约束仍然生效
|
|
264
|
+
try:
|
|
265
|
+
self._setup_system_prompt()
|
|
266
|
+
except Exception:
|
|
267
|
+
pass
|
|
268
|
+
|
|
254
269
|
# 广播清理历史后的事件
|
|
255
270
|
try:
|
|
256
271
|
self.event_bus.emit(AFTER_HISTORY_CLEAR, agent=self)
|
|
@@ -1003,7 +1018,6 @@ class Agent:
|
|
|
1003
1018
|
{complete_prompt}
|
|
1004
1019
|
如果没有完成,请进行下一步操作:
|
|
1005
1020
|
- 仅包含一个操作
|
|
1006
|
-
- 不要询问用户是否继续,直接继续执行直至完成
|
|
1007
1021
|
- 如果信息不明确,请请求用户补充
|
|
1008
1022
|
- 如果执行过程中连续失败5次,请使用ask_user询问用户操作
|
|
1009
1023
|
- 操作列表:{action_handlers}{memory_prompts}
|
|
@@ -49,7 +49,14 @@ class ShareManager(ABC):
|
|
|
49
49
|
def __init__(self, central_repo_url: str, repo_name: str):
|
|
50
50
|
self.central_repo_url = central_repo_url
|
|
51
51
|
self.repo_name = repo_name
|
|
52
|
-
|
|
52
|
+
# 支持将中心仓库配置为本地目录(含git子路径)
|
|
53
|
+
expanded = os.path.expanduser(os.path.expandvars(central_repo_url))
|
|
54
|
+
if os.path.isdir(expanded):
|
|
55
|
+
# 直接使用本地目录作为中心仓库路径(支持git仓库子目录)
|
|
56
|
+
self.repo_path = expanded
|
|
57
|
+
else:
|
|
58
|
+
# 仍按原逻辑使用数据目录中的克隆路径
|
|
59
|
+
self.repo_path = os.path.join(get_data_dir(), repo_name)
|
|
53
60
|
|
|
54
61
|
def update_central_repo(self) -> None:
|
|
55
62
|
"""克隆或更新中心仓库"""
|
|
@@ -25,6 +25,7 @@ from jarvis.jarvis_utils.config import (
|
|
|
25
25
|
is_enable_static_analysis,
|
|
26
26
|
get_git_check_mode,
|
|
27
27
|
set_config,
|
|
28
|
+
get_data_dir,
|
|
28
29
|
)
|
|
29
30
|
from jarvis.jarvis_utils.git_utils import (
|
|
30
31
|
confirm_add_new_files,
|
|
@@ -86,6 +87,22 @@ class CodeAgent:
|
|
|
86
87
|
|
|
87
88
|
tool_registry.use_tools(base_tools)
|
|
88
89
|
code_system_prompt = self._get_system_prompt()
|
|
90
|
+
# 先加载全局规则(数据目录 rules),再加载项目规则(.jarvis/rules),并拼接为单一规则块注入
|
|
91
|
+
global_rules = self._read_global_rules()
|
|
92
|
+
project_rules = self._read_project_rules()
|
|
93
|
+
|
|
94
|
+
combined_parts: List[str] = []
|
|
95
|
+
if global_rules:
|
|
96
|
+
combined_parts.append(global_rules)
|
|
97
|
+
if project_rules:
|
|
98
|
+
combined_parts.append(project_rules)
|
|
99
|
+
|
|
100
|
+
if combined_parts:
|
|
101
|
+
merged_rules = "\n\n".join(combined_parts)
|
|
102
|
+
code_system_prompt = (
|
|
103
|
+
f"{code_system_prompt}\n\n"
|
|
104
|
+
f"<rules>\n{merged_rules}\n</rules>"
|
|
105
|
+
)
|
|
89
106
|
self.agent = Agent(
|
|
90
107
|
system_prompt=code_system_prompt,
|
|
91
108
|
name="CodeAgent",
|
|
@@ -164,6 +181,32 @@ class CodeAgent:
|
|
|
164
181
|
</say_to_llm>
|
|
165
182
|
"""
|
|
166
183
|
|
|
184
|
+
def _read_project_rules(self) -> Optional[str]:
|
|
185
|
+
"""读取 .jarvis/rules 内容,如果存在则返回字符串,否则返回 None"""
|
|
186
|
+
try:
|
|
187
|
+
rules_path = os.path.join(self.root_dir, ".jarvis", "rules")
|
|
188
|
+
if os.path.exists(rules_path) and os.path.isfile(rules_path):
|
|
189
|
+
with open(rules_path, "r", encoding="utf-8", errors="replace") as f:
|
|
190
|
+
content = f.read().strip()
|
|
191
|
+
return content if content else None
|
|
192
|
+
except Exception:
|
|
193
|
+
# 读取规则失败时忽略,不影响主流程
|
|
194
|
+
pass
|
|
195
|
+
return None
|
|
196
|
+
|
|
197
|
+
def _read_global_rules(self) -> Optional[str]:
|
|
198
|
+
"""读取数据目录 rules 内容,如果存在则返回字符串,否则返回 None"""
|
|
199
|
+
try:
|
|
200
|
+
rules_path = os.path.join(get_data_dir(), "rules")
|
|
201
|
+
if os.path.exists(rules_path) and os.path.isfile(rules_path):
|
|
202
|
+
with open(rules_path, "r", encoding="utf-8", errors="replace") as f:
|
|
203
|
+
content = f.read().strip()
|
|
204
|
+
return content if content else None
|
|
205
|
+
except Exception:
|
|
206
|
+
# 读取规则失败时忽略,不影响主流程
|
|
207
|
+
pass
|
|
208
|
+
return None
|
|
209
|
+
|
|
167
210
|
def _check_git_config(self) -> None:
|
|
168
211
|
"""检查 git username 和 email 是否已设置,如果没有则提示并退出"""
|
|
169
212
|
try:
|
|
@@ -672,12 +715,58 @@ class CodeAgent:
|
|
|
672
715
|
def _build_per_file_patch_preview(modified_files: List[str]) -> str:
|
|
673
716
|
status_map = _build_name_status_map()
|
|
674
717
|
lines: List[str] = []
|
|
718
|
+
|
|
719
|
+
def _get_file_numstat(file_path: str) -> Tuple[int, int]:
|
|
720
|
+
"""获取单文件的新增/删除行数,失败时返回(0,0)"""
|
|
721
|
+
head_exists = bool(get_latest_commit_hash())
|
|
722
|
+
try:
|
|
723
|
+
# 让未跟踪文件也能统计到新增行数
|
|
724
|
+
subprocess.run(["git", "add", "-N", "--", file_path], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
725
|
+
cmd = ["git", "diff", "--numstat"] + (["HEAD"] if head_exists else []) + ["--", file_path]
|
|
726
|
+
res = subprocess.run(
|
|
727
|
+
cmd,
|
|
728
|
+
capture_output=True,
|
|
729
|
+
text=True,
|
|
730
|
+
encoding="utf-8",
|
|
731
|
+
errors="replace",
|
|
732
|
+
check=False,
|
|
733
|
+
)
|
|
734
|
+
if res.returncode == 0 and res.stdout:
|
|
735
|
+
for line in res.stdout.splitlines():
|
|
736
|
+
parts = line.strip().split("\t")
|
|
737
|
+
if len(parts) >= 3:
|
|
738
|
+
add_s, del_s = parts[0], parts[1]
|
|
739
|
+
|
|
740
|
+
def to_int(x: str) -> int:
|
|
741
|
+
try:
|
|
742
|
+
return int(x)
|
|
743
|
+
except Exception:
|
|
744
|
+
# 二进制或无法解析时显示为0
|
|
745
|
+
return 0
|
|
746
|
+
|
|
747
|
+
return to_int(add_s), to_int(del_s)
|
|
748
|
+
finally:
|
|
749
|
+
subprocess.run(["git", "reset", "--", file_path], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
750
|
+
return (0, 0)
|
|
751
|
+
|
|
675
752
|
for f in modified_files:
|
|
676
753
|
status = status_map.get(f, "")
|
|
677
|
-
|
|
754
|
+
adds, dels = _get_file_numstat(f)
|
|
755
|
+
total_changes = adds + dels
|
|
756
|
+
|
|
757
|
+
# 删除文件:不展示diff,仅提示(附带删除行数信息如果可用)
|
|
678
758
|
if (status.startswith("D")) or (not os.path.exists(f)):
|
|
679
|
-
|
|
759
|
+
if dels > 0:
|
|
760
|
+
lines.append(f"- {f} 文件被删除(删除{dels}行)")
|
|
761
|
+
else:
|
|
762
|
+
lines.append(f"- {f} 文件被删除")
|
|
680
763
|
continue
|
|
764
|
+
|
|
765
|
+
# 变更过大:仅提示新增/删除行数,避免输出超长diff
|
|
766
|
+
if total_changes > 300:
|
|
767
|
+
lines.append(f"- {f} 新增{adds}行/删除{dels}行(变更过大,预览已省略)")
|
|
768
|
+
continue
|
|
769
|
+
|
|
681
770
|
# 其它情况:展示该文件的diff
|
|
682
771
|
file_diff = _get_file_diff(f)
|
|
683
772
|
if file_diff.strip():
|