jarvis-ai-assistant 0.7.0__py3-none-any.whl → 0.7.8__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 +243 -139
- jarvis/jarvis_agent/agent_manager.py +5 -10
- jarvis/jarvis_agent/builtin_input_handler.py +2 -6
- jarvis/jarvis_agent/config_editor.py +2 -7
- jarvis/jarvis_agent/event_bus.py +82 -12
- jarvis/jarvis_agent/file_context_handler.py +265 -15
- jarvis/jarvis_agent/file_methodology_manager.py +3 -4
- jarvis/jarvis_agent/jarvis.py +113 -98
- jarvis/jarvis_agent/language_extractors/__init__.py +57 -0
- jarvis/jarvis_agent/language_extractors/c_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/cpp_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/go_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/java_extractor.py +84 -0
- jarvis/jarvis_agent/language_extractors/javascript_extractor.py +79 -0
- jarvis/jarvis_agent/language_extractors/python_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/rust_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/typescript_extractor.py +84 -0
- jarvis/jarvis_agent/language_support_info.py +486 -0
- jarvis/jarvis_agent/main.py +6 -12
- jarvis/jarvis_agent/memory_manager.py +7 -16
- jarvis/jarvis_agent/methodology_share_manager.py +10 -16
- jarvis/jarvis_agent/prompt_manager.py +1 -1
- jarvis/jarvis_agent/prompts.py +193 -171
- jarvis/jarvis_agent/protocols.py +8 -12
- jarvis/jarvis_agent/run_loop.py +77 -14
- jarvis/jarvis_agent/session_manager.py +2 -3
- jarvis/jarvis_agent/share_manager.py +12 -21
- jarvis/jarvis_agent/shell_input_handler.py +1 -2
- jarvis/jarvis_agent/task_analyzer.py +26 -4
- jarvis/jarvis_agent/task_manager.py +11 -27
- jarvis/jarvis_agent/tool_executor.py +2 -3
- jarvis/jarvis_agent/tool_share_manager.py +12 -24
- jarvis/jarvis_agent/web_server.py +55 -20
- jarvis/jarvis_c2rust/__init__.py +5 -5
- jarvis/jarvis_c2rust/cli.py +461 -499
- jarvis/jarvis_c2rust/collector.py +45 -53
- jarvis/jarvis_c2rust/constants.py +26 -0
- jarvis/jarvis_c2rust/library_replacer.py +264 -132
- jarvis/jarvis_c2rust/llm_module_agent.py +162 -190
- jarvis/jarvis_c2rust/loaders.py +207 -0
- jarvis/jarvis_c2rust/models.py +28 -0
- jarvis/jarvis_c2rust/optimizer.py +1592 -395
- jarvis/jarvis_c2rust/transpiler.py +1722 -1064
- jarvis/jarvis_c2rust/utils.py +385 -0
- jarvis/jarvis_code_agent/build_validation_config.py +2 -3
- jarvis/jarvis_code_agent/code_agent.py +394 -320
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +3 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +4 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +17 -2
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +3 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +36 -4
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +9 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +9 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +12 -1
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +22 -5
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +57 -32
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +62 -6
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +8 -9
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +290 -5
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +21 -3
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +72 -4
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +35 -3
- jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +212 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +254 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +52 -2
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +73 -1
- jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +280 -0
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +306 -152
- jarvis/jarvis_code_agent/code_analyzer/structured_code.py +556 -0
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +193 -18
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +18 -8
- jarvis/jarvis_code_agent/lint.py +258 -27
- jarvis/jarvis_code_agent/utils.py +0 -1
- jarvis/jarvis_code_analysis/code_review.py +19 -24
- jarvis/jarvis_data/config_schema.json +53 -26
- jarvis/jarvis_git_squash/main.py +4 -5
- jarvis/jarvis_git_utils/git_commiter.py +44 -49
- jarvis/jarvis_mcp/sse_mcp_client.py +20 -27
- jarvis/jarvis_mcp/stdio_mcp_client.py +11 -12
- jarvis/jarvis_mcp/streamable_mcp_client.py +15 -14
- jarvis/jarvis_memory_organizer/memory_organizer.py +55 -74
- jarvis/jarvis_methodology/main.py +32 -48
- jarvis/jarvis_multi_agent/__init__.py +79 -61
- jarvis/jarvis_multi_agent/main.py +3 -7
- jarvis/jarvis_platform/base.py +469 -199
- jarvis/jarvis_platform/human.py +7 -8
- jarvis/jarvis_platform/kimi.py +30 -36
- jarvis/jarvis_platform/openai.py +65 -27
- jarvis/jarvis_platform/registry.py +26 -10
- jarvis/jarvis_platform/tongyi.py +24 -25
- jarvis/jarvis_platform/yuanbao.py +31 -42
- jarvis/jarvis_platform_manager/main.py +66 -77
- jarvis/jarvis_platform_manager/service.py +8 -13
- jarvis/jarvis_rag/cli.py +49 -51
- jarvis/jarvis_rag/embedding_manager.py +13 -18
- jarvis/jarvis_rag/llm_interface.py +8 -9
- jarvis/jarvis_rag/query_rewriter.py +10 -21
- jarvis/jarvis_rag/rag_pipeline.py +24 -27
- jarvis/jarvis_rag/reranker.py +4 -5
- jarvis/jarvis_rag/retriever.py +28 -30
- jarvis/jarvis_sec/__init__.py +220 -3520
- jarvis/jarvis_sec/agents.py +143 -0
- jarvis/jarvis_sec/analysis.py +276 -0
- jarvis/jarvis_sec/cli.py +29 -6
- jarvis/jarvis_sec/clustering.py +1439 -0
- jarvis/jarvis_sec/file_manager.py +427 -0
- jarvis/jarvis_sec/parsers.py +73 -0
- jarvis/jarvis_sec/prompts.py +268 -0
- jarvis/jarvis_sec/report.py +83 -4
- jarvis/jarvis_sec/review.py +453 -0
- jarvis/jarvis_sec/utils.py +499 -0
- jarvis/jarvis_sec/verification.py +848 -0
- jarvis/jarvis_sec/workflow.py +7 -0
- jarvis/jarvis_smart_shell/main.py +38 -87
- jarvis/jarvis_stats/cli.py +1 -1
- jarvis/jarvis_stats/stats.py +7 -7
- jarvis/jarvis_stats/storage.py +15 -21
- jarvis/jarvis_tools/clear_memory.py +3 -20
- jarvis/jarvis_tools/cli/main.py +20 -23
- jarvis/jarvis_tools/edit_file.py +1066 -0
- jarvis/jarvis_tools/execute_script.py +42 -21
- jarvis/jarvis_tools/file_analyzer.py +6 -9
- jarvis/jarvis_tools/generate_new_tool.py +11 -20
- jarvis/jarvis_tools/lsp_client.py +1552 -0
- jarvis/jarvis_tools/methodology.py +2 -3
- jarvis/jarvis_tools/read_code.py +1525 -87
- jarvis/jarvis_tools/read_symbols.py +2 -3
- jarvis/jarvis_tools/read_webpage.py +7 -10
- jarvis/jarvis_tools/registry.py +370 -181
- jarvis/jarvis_tools/retrieve_memory.py +20 -19
- jarvis/jarvis_tools/rewrite_file.py +105 -0
- jarvis/jarvis_tools/save_memory.py +3 -15
- jarvis/jarvis_tools/search_web.py +3 -7
- jarvis/jarvis_tools/sub_agent.py +17 -6
- jarvis/jarvis_tools/sub_code_agent.py +14 -16
- jarvis/jarvis_tools/virtual_tty.py +54 -32
- jarvis/jarvis_utils/clipboard.py +7 -10
- jarvis/jarvis_utils/config.py +98 -63
- jarvis/jarvis_utils/embedding.py +5 -5
- jarvis/jarvis_utils/fzf.py +8 -8
- jarvis/jarvis_utils/git_utils.py +81 -67
- jarvis/jarvis_utils/input.py +24 -49
- jarvis/jarvis_utils/jsonnet_compat.py +465 -0
- jarvis/jarvis_utils/methodology.py +33 -35
- jarvis/jarvis_utils/utils.py +245 -202
- {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.8.dist-info}/METADATA +205 -70
- jarvis_ai_assistant-0.7.8.dist-info/RECORD +218 -0
- jarvis/jarvis_agent/edit_file_handler.py +0 -584
- jarvis/jarvis_agent/rewrite_file_handler.py +0 -141
- jarvis/jarvis_agent/task_planner.py +0 -496
- jarvis/jarvis_platform/ai8.py +0 -332
- jarvis/jarvis_tools/ask_user.py +0 -54
- jarvis_ai_assistant-0.7.0.dist-info/RECORD +0 -192
- {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.8.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.8.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.8.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.8.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""复核相关模块"""
|
|
3
|
+
|
|
4
|
+
from typing import Dict, List, Optional
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from jarvis.jarvis_sec.prompts import get_review_system_prompt, get_review_summary_prompt, build_verification_summary_prompt
|
|
8
|
+
from jarvis.jarvis_sec.parsers import try_parse_summary_report
|
|
9
|
+
from jarvis.jarvis_sec.agents import create_review_agent, subscribe_summary_event
|
|
10
|
+
from jarvis.jarvis_sec.utils import git_restore_if_dirty
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def build_review_task(review_batch: List[Dict], entry_path: str, langs: List[str]) -> str:
|
|
14
|
+
"""构建复核任务上下文"""
|
|
15
|
+
import json as _json_review
|
|
16
|
+
return f"""
|
|
17
|
+
# 复核无效聚类任务
|
|
18
|
+
上下文参数:
|
|
19
|
+
- entry_path: {entry_path}
|
|
20
|
+
- languages: {langs}
|
|
21
|
+
|
|
22
|
+
需要复核的无效聚类(JSON数组):
|
|
23
|
+
{_json_review.dumps(review_batch, ensure_ascii=False, indent=2)}
|
|
24
|
+
|
|
25
|
+
请仔细复核每个无效聚类的invalid_reason是否充分,是否真的考虑了所有可能的路径、调用者和边界情况。
|
|
26
|
+
对于每个gid,请判断无效理由是否充分(is_reason_sufficient: true/false),并给出复核说明。
|
|
27
|
+
""".strip()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def is_valid_review_item(item: Dict) -> bool:
|
|
31
|
+
"""验证复核结果项的格式"""
|
|
32
|
+
if not isinstance(item, dict) or "is_reason_sufficient" not in item:
|
|
33
|
+
return False
|
|
34
|
+
has_gid = "gid" in item
|
|
35
|
+
has_gids = "gids" in item
|
|
36
|
+
if not has_gid and not has_gids:
|
|
37
|
+
return False
|
|
38
|
+
if has_gid and has_gids:
|
|
39
|
+
return False # gid 和 gids 不能同时出现
|
|
40
|
+
if has_gid:
|
|
41
|
+
try:
|
|
42
|
+
return int(item["gid"]) >= 1
|
|
43
|
+
except Exception:
|
|
44
|
+
return False
|
|
45
|
+
elif has_gids:
|
|
46
|
+
if not isinstance(item["gids"], list) or len(item["gids"]) == 0:
|
|
47
|
+
return False
|
|
48
|
+
try:
|
|
49
|
+
return all(int(gid_val) >= 1 for gid_val in item["gids"])
|
|
50
|
+
except Exception:
|
|
51
|
+
return False
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def build_gid_to_review_mapping(review_results: List[Dict]) -> Dict[int, Dict]:
|
|
56
|
+
"""构建gid到复核结果的映射(支持 gid 和 gids 两种格式)"""
|
|
57
|
+
gid_to_review: Dict[int, Dict] = {}
|
|
58
|
+
for rr in review_results:
|
|
59
|
+
if not isinstance(rr, dict):
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
# 支持 gid 和 gids 两种格式
|
|
63
|
+
gids_to_process: List[int] = []
|
|
64
|
+
if "gids" in rr and isinstance(rr.get("gids"), list):
|
|
65
|
+
# 合并格式:gids 数组
|
|
66
|
+
for gid_val in rr.get("gids", []):
|
|
67
|
+
try:
|
|
68
|
+
gid_int = int(gid_val)
|
|
69
|
+
if gid_int >= 1:
|
|
70
|
+
gids_to_process.append(gid_int)
|
|
71
|
+
except Exception:
|
|
72
|
+
pass
|
|
73
|
+
elif "gid" in rr:
|
|
74
|
+
# 单个格式:gid
|
|
75
|
+
try:
|
|
76
|
+
gid_int = int(rr.get("gid", 0))
|
|
77
|
+
if gid_int >= 1:
|
|
78
|
+
gids_to_process.append(gid_int)
|
|
79
|
+
except Exception:
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
# 为每个 gid 创建复核结果映射
|
|
83
|
+
is_reason_sufficient = rr.get("is_reason_sufficient")
|
|
84
|
+
review_notes = str(rr.get("review_notes", "")).strip()
|
|
85
|
+
for gid in gids_to_process:
|
|
86
|
+
gid_to_review[gid] = {
|
|
87
|
+
"is_reason_sufficient": is_reason_sufficient,
|
|
88
|
+
"review_notes": review_notes
|
|
89
|
+
}
|
|
90
|
+
return gid_to_review
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def process_review_batch(
|
|
94
|
+
review_batch: List[Dict],
|
|
95
|
+
review_results: Optional[List[Dict]],
|
|
96
|
+
reviewed_clusters: List[Dict],
|
|
97
|
+
reinstated_candidates: List[Dict],
|
|
98
|
+
sec_dir=None,
|
|
99
|
+
) -> None:
|
|
100
|
+
"""处理单个复核批次的结果"""
|
|
101
|
+
if review_results:
|
|
102
|
+
# 构建gid到复核结果的映射
|
|
103
|
+
gid_to_review = build_gid_to_review_mapping(review_results)
|
|
104
|
+
|
|
105
|
+
# 处理每个无效聚类
|
|
106
|
+
for invalid_cluster in review_batch:
|
|
107
|
+
cluster_gids = invalid_cluster.get("gids", [])
|
|
108
|
+
cluster_members = invalid_cluster.get("members", [])
|
|
109
|
+
|
|
110
|
+
# 检查该聚类中的所有gid的复核结果
|
|
111
|
+
all_sufficient = True
|
|
112
|
+
any_reviewed = False
|
|
113
|
+
insufficient_review_result = None
|
|
114
|
+
for gid in cluster_gids:
|
|
115
|
+
review_result = gid_to_review.get(gid)
|
|
116
|
+
if review_result:
|
|
117
|
+
any_reviewed = True
|
|
118
|
+
if review_result.get("is_reason_sufficient") is not True:
|
|
119
|
+
all_sufficient = False
|
|
120
|
+
if not insufficient_review_result:
|
|
121
|
+
insufficient_review_result = review_result
|
|
122
|
+
break
|
|
123
|
+
|
|
124
|
+
if any_reviewed and not all_sufficient:
|
|
125
|
+
# 理由不充分,重新加入验证流程
|
|
126
|
+
typer.secho(f"[jarvis-sec] 复核结果:无效聚类(gids={cluster_gids})理由不充分,重新加入验证流程", fg=typer.colors.BLUE)
|
|
127
|
+
for member in cluster_members:
|
|
128
|
+
reinstated_candidates.append(member)
|
|
129
|
+
reviewed_clusters.append({
|
|
130
|
+
**invalid_cluster,
|
|
131
|
+
"review_result": "reinstated",
|
|
132
|
+
"review_notes": insufficient_review_result.get("review_notes", "") if insufficient_review_result else "",
|
|
133
|
+
})
|
|
134
|
+
else:
|
|
135
|
+
# 理由充分,确认无效
|
|
136
|
+
review_notes = ""
|
|
137
|
+
if cluster_gids and gid_to_review.get(cluster_gids[0]):
|
|
138
|
+
review_notes = gid_to_review[cluster_gids[0]].get("review_notes", "")
|
|
139
|
+
typer.secho(f"[jarvis-sec] 复核结果:无效聚类(gids={cluster_gids})理由充分,确认为无效", fg=typer.colors.GREEN)
|
|
140
|
+
reviewed_clusters.append({
|
|
141
|
+
**invalid_cluster,
|
|
142
|
+
"review_result": "confirmed_invalid",
|
|
143
|
+
"review_notes": review_notes,
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
# 将确认无效的gids保存到analysis.jsonl的false_positive_gids中,以便断点恢复时能正确识别已复核的无效聚类
|
|
147
|
+
if sec_dir:
|
|
148
|
+
try:
|
|
149
|
+
from pathlib import Path
|
|
150
|
+
from jarvis.jarvis_sec.file_manager import save_analysis_result
|
|
151
|
+
from datetime import datetime
|
|
152
|
+
|
|
153
|
+
# 构建cluster_id
|
|
154
|
+
file_name = invalid_cluster.get("file", "")
|
|
155
|
+
batch_index = invalid_cluster.get("batch_index", 1)
|
|
156
|
+
cluster_id = f"{file_name}|{batch_index}|review"
|
|
157
|
+
|
|
158
|
+
# 将gids转换为整数列表
|
|
159
|
+
false_positive_gids = []
|
|
160
|
+
for gid_val in cluster_gids:
|
|
161
|
+
try:
|
|
162
|
+
gid_int = int(gid_val)
|
|
163
|
+
if gid_int >= 1:
|
|
164
|
+
false_positive_gids.append(gid_int)
|
|
165
|
+
except Exception:
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
# 保存分析结果
|
|
169
|
+
if false_positive_gids:
|
|
170
|
+
analysis_result = {
|
|
171
|
+
"cluster_id": cluster_id,
|
|
172
|
+
"file": file_name,
|
|
173
|
+
"batch_index": batch_index,
|
|
174
|
+
"gids": false_positive_gids,
|
|
175
|
+
"verified_gids": [],
|
|
176
|
+
"false_positive_gids": false_positive_gids,
|
|
177
|
+
"issues": [],
|
|
178
|
+
"analyzed_at": datetime.now().isoformat(),
|
|
179
|
+
}
|
|
180
|
+
save_analysis_result(Path(sec_dir), analysis_result)
|
|
181
|
+
except Exception as e:
|
|
182
|
+
# 保存失败不影响主流程,只记录警告
|
|
183
|
+
try:
|
|
184
|
+
typer.secho(f"[jarvis-sec] 警告:保存复核结果到analysis.jsonl失败: {str(e)}", fg=typer.colors.YELLOW)
|
|
185
|
+
except Exception:
|
|
186
|
+
pass
|
|
187
|
+
else:
|
|
188
|
+
# 复核结果解析失败,保守策略:重新加入验证流程
|
|
189
|
+
typer.secho("[jarvis-sec] 警告:复核结果解析失败,保守策略:将批次中的所有候选重新加入验证流程", fg=typer.colors.YELLOW)
|
|
190
|
+
for invalid_cluster in review_batch:
|
|
191
|
+
cluster_members = invalid_cluster.get("members", [])
|
|
192
|
+
for member in cluster_members:
|
|
193
|
+
reinstated_candidates.append(member)
|
|
194
|
+
reviewed_clusters.append({
|
|
195
|
+
**invalid_cluster,
|
|
196
|
+
"review_result": "reinstated",
|
|
197
|
+
"review_notes": "复核结果解析失败,保守策略重新加入验证",
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def process_review_batch_items(
|
|
202
|
+
review_batch: List[Dict],
|
|
203
|
+
review_results: Optional[List[Dict]],
|
|
204
|
+
reviewed_clusters: List[Dict],
|
|
205
|
+
reinstated_candidates: List[Dict],
|
|
206
|
+
sec_dir=None,
|
|
207
|
+
) -> None:
|
|
208
|
+
"""处理单个复核批次的结果"""
|
|
209
|
+
process_review_batch(
|
|
210
|
+
review_batch,
|
|
211
|
+
review_results,
|
|
212
|
+
reviewed_clusters,
|
|
213
|
+
reinstated_candidates,
|
|
214
|
+
sec_dir,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def reinstated_candidates_to_cluster_batches(
|
|
219
|
+
reinstated_candidates: List[Dict],
|
|
220
|
+
cluster_batches: List[List[Dict]],
|
|
221
|
+
_progress_append,
|
|
222
|
+
) -> None:
|
|
223
|
+
"""将重新加入的候选添加到cluster_batches"""
|
|
224
|
+
from collections import defaultdict as _dd2
|
|
225
|
+
|
|
226
|
+
if not reinstated_candidates:
|
|
227
|
+
return
|
|
228
|
+
|
|
229
|
+
typer.secho(f"[jarvis-sec] 复核完成:{len(reinstated_candidates)} 个候选重新加入验证流程", fg=typer.colors.GREEN)
|
|
230
|
+
# 按文件分组重新加入的候选
|
|
231
|
+
reinstated_by_file: Dict[str, List[Dict]] = _dd2(list)
|
|
232
|
+
for cand in reinstated_candidates:
|
|
233
|
+
file_key = str(cand.get("file") or "")
|
|
234
|
+
reinstated_by_file[file_key].append(cand)
|
|
235
|
+
|
|
236
|
+
# 为每个文件的重新加入候选创建批次
|
|
237
|
+
for file_key, cands in reinstated_by_file.items():
|
|
238
|
+
if cands:
|
|
239
|
+
cluster_batches.append(cands)
|
|
240
|
+
_progress_append({
|
|
241
|
+
"event": "review_reinstated",
|
|
242
|
+
"file": file_key,
|
|
243
|
+
"gids": [c.get("gid") for c in cands],
|
|
244
|
+
"count": len(cands),
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def run_review_agent_with_retry(
|
|
249
|
+
review_agent,
|
|
250
|
+
review_task: str,
|
|
251
|
+
review_summary_prompt: str,
|
|
252
|
+
entry_path: str,
|
|
253
|
+
review_summary_container: Dict[str, str],
|
|
254
|
+
) -> tuple[Optional[List[Dict]], Optional[str]]:
|
|
255
|
+
"""运行复核Agent并永久重试直到格式正确,返回(复核结果, 解析错误)"""
|
|
256
|
+
use_direct_model_review = False
|
|
257
|
+
prev_parse_error_review: Optional[str] = None
|
|
258
|
+
review_attempt = 0
|
|
259
|
+
|
|
260
|
+
while True:
|
|
261
|
+
review_attempt += 1
|
|
262
|
+
review_summary_container["text"] = ""
|
|
263
|
+
|
|
264
|
+
if use_direct_model_review:
|
|
265
|
+
# 格式校验失败后,直接调用模型接口
|
|
266
|
+
review_summary_prompt_text = build_verification_summary_prompt()
|
|
267
|
+
error_guidance = ""
|
|
268
|
+
if prev_parse_error_review:
|
|
269
|
+
error_guidance = f"\n\n**格式错误详情(请根据以下错误修复输出格式):**\n- JSON解析失败: {prev_parse_error_review}\n\n请确保输出的JSON格式正确,包括正确的引号、逗号、大括号等。仅输出一个 <REPORT> 块,块内直接包含 JSON 数组(不需要额外的标签)。支持jsonnet语法(如尾随逗号、注释、||| 或 ``` 分隔符多行字符串等)。"
|
|
270
|
+
|
|
271
|
+
full_review_prompt = f"{review_task}{error_guidance}\n\n{review_summary_prompt_text}"
|
|
272
|
+
try:
|
|
273
|
+
review_response = review_agent.model.chat_until_success(full_review_prompt) # type: ignore
|
|
274
|
+
review_summary_container["text"] = review_response
|
|
275
|
+
except Exception as e:
|
|
276
|
+
try:
|
|
277
|
+
typer.secho(f"[jarvis-sec] 复核阶段直接模型调用失败: {e},回退到 run()", fg=typer.colors.YELLOW)
|
|
278
|
+
except Exception:
|
|
279
|
+
pass
|
|
280
|
+
review_agent.run(review_task)
|
|
281
|
+
else:
|
|
282
|
+
review_agent.run(review_task)
|
|
283
|
+
|
|
284
|
+
# 工作区保护
|
|
285
|
+
try:
|
|
286
|
+
_changed_review = git_restore_if_dirty(entry_path)
|
|
287
|
+
if _changed_review:
|
|
288
|
+
try:
|
|
289
|
+
typer.secho(f"[jarvis-sec] 复核 Agent 工作区已恢复 ({_changed_review} 个文件)", fg=typer.colors.BLUE)
|
|
290
|
+
except Exception:
|
|
291
|
+
pass
|
|
292
|
+
except Exception:
|
|
293
|
+
pass
|
|
294
|
+
|
|
295
|
+
# 解析复核结果
|
|
296
|
+
review_summary_text = review_summary_container.get("text", "")
|
|
297
|
+
parse_error_review = None
|
|
298
|
+
if review_summary_text:
|
|
299
|
+
review_parsed, parse_error_review = try_parse_summary_report(review_summary_text)
|
|
300
|
+
if parse_error_review:
|
|
301
|
+
prev_parse_error_review = parse_error_review
|
|
302
|
+
try:
|
|
303
|
+
typer.secho(f"[jarvis-sec] 复核结果JSON解析失败: {parse_error_review}", fg=typer.colors.YELLOW)
|
|
304
|
+
except Exception:
|
|
305
|
+
pass
|
|
306
|
+
else:
|
|
307
|
+
prev_parse_error_review = None
|
|
308
|
+
if isinstance(review_parsed, list):
|
|
309
|
+
# 验证复核结果格式
|
|
310
|
+
if review_parsed and all(is_valid_review_item(item) for item in review_parsed):
|
|
311
|
+
return review_parsed, None
|
|
312
|
+
|
|
313
|
+
# 格式校验失败,后续重试使用直接模型调用
|
|
314
|
+
use_direct_model_review = True
|
|
315
|
+
if parse_error_review:
|
|
316
|
+
try:
|
|
317
|
+
typer.secho(f"[jarvis-sec] 复核结果JSON解析失败 -> 重试第 {review_attempt} 次 (使用直接模型调用,将反馈解析错误)", fg=typer.colors.YELLOW)
|
|
318
|
+
except Exception:
|
|
319
|
+
pass
|
|
320
|
+
else:
|
|
321
|
+
try:
|
|
322
|
+
typer.secho(f"[jarvis-sec] 复核结果格式无效 -> 重试第 {review_attempt} 次 (使用直接模型调用)", fg=typer.colors.YELLOW)
|
|
323
|
+
except Exception:
|
|
324
|
+
pass
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def process_review_phase(
|
|
328
|
+
invalid_clusters_for_review: List[Dict],
|
|
329
|
+
entry_path: str,
|
|
330
|
+
langs: List[str],
|
|
331
|
+
llm_group: Optional[str],
|
|
332
|
+
status_mgr,
|
|
333
|
+
_progress_append,
|
|
334
|
+
cluster_batches: List[List[Dict]],
|
|
335
|
+
sec_dir=None,
|
|
336
|
+
) -> List[List[Dict]]:
|
|
337
|
+
"""
|
|
338
|
+
处理复核阶段:验证所有标记为无效的聚类。
|
|
339
|
+
|
|
340
|
+
返回: 更新后的 cluster_batches(包含重新加入验证的候选)
|
|
341
|
+
"""
|
|
342
|
+
if not invalid_clusters_for_review:
|
|
343
|
+
typer.secho("[jarvis-sec] 无无效聚类需要复核", fg=typer.colors.BLUE)
|
|
344
|
+
return cluster_batches
|
|
345
|
+
|
|
346
|
+
typer.secho(f"\n[jarvis-sec] 开始复核 {len(invalid_clusters_for_review)} 个无效聚类...", fg=typer.colors.MAGENTA)
|
|
347
|
+
status_mgr.update_review(
|
|
348
|
+
current_review=0,
|
|
349
|
+
total_reviews=len(invalid_clusters_for_review),
|
|
350
|
+
message="开始复核无效聚类..."
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
# 按批次复核(每批最多10个无效聚类,避免上下文过长)
|
|
354
|
+
review_batch_size = 10
|
|
355
|
+
reviewed_clusters: List[Dict] = []
|
|
356
|
+
reinstated_candidates: List[Dict] = [] # 重新加入验证的候选
|
|
357
|
+
|
|
358
|
+
get_review_system_prompt()
|
|
359
|
+
review_summary_prompt = get_review_summary_prompt()
|
|
360
|
+
|
|
361
|
+
for review_idx in range(0, len(invalid_clusters_for_review), review_batch_size):
|
|
362
|
+
review_batch = invalid_clusters_for_review[review_idx:review_idx + review_batch_size]
|
|
363
|
+
current_review_num = review_idx // review_batch_size + 1
|
|
364
|
+
total_review_batches = (len(invalid_clusters_for_review) + review_batch_size - 1) // review_batch_size
|
|
365
|
+
|
|
366
|
+
typer.secho(f"[jarvis-sec] 复核批次 {current_review_num}/{total_review_batches}: {len(review_batch)} 个无效聚类", fg=typer.colors.CYAN)
|
|
367
|
+
status_mgr.update_review(
|
|
368
|
+
current_review=current_review_num,
|
|
369
|
+
total_reviews=total_review_batches,
|
|
370
|
+
message=f"正在复核批次 {current_review_num}/{total_review_batches}"
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
# 构建复核任务
|
|
374
|
+
review_task = build_review_task(review_batch, entry_path, langs)
|
|
375
|
+
|
|
376
|
+
# 创建复核Agent
|
|
377
|
+
review_agent = create_review_agent(current_review_num, llm_group)
|
|
378
|
+
|
|
379
|
+
# 订阅复核Agent的摘要
|
|
380
|
+
review_summary_container = subscribe_summary_event(review_agent)
|
|
381
|
+
|
|
382
|
+
# 运行复核Agent(永久重试直到格式正确)
|
|
383
|
+
review_results, parse_error = run_review_agent_with_retry(
|
|
384
|
+
review_agent,
|
|
385
|
+
review_task,
|
|
386
|
+
review_summary_prompt,
|
|
387
|
+
entry_path,
|
|
388
|
+
review_summary_container,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
# 处理复核结果
|
|
392
|
+
process_review_batch_items(
|
|
393
|
+
review_batch,
|
|
394
|
+
review_results,
|
|
395
|
+
reviewed_clusters,
|
|
396
|
+
reinstated_candidates,
|
|
397
|
+
sec_dir,
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
# 记录每个已复核的无效聚类的 gids(包括确认无效的和重新加入验证的)
|
|
401
|
+
for invalid_cluster in review_batch:
|
|
402
|
+
cluster_gids = invalid_cluster.get("gids", [])
|
|
403
|
+
if cluster_gids:
|
|
404
|
+
_progress_append({
|
|
405
|
+
"event": "review_invalid_cluster",
|
|
406
|
+
"gids": cluster_gids,
|
|
407
|
+
"file": invalid_cluster.get("file"),
|
|
408
|
+
"batch_index": invalid_cluster.get("batch_index"),
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
# 将重新加入验证的候选添加到cluster_batches
|
|
412
|
+
reinstated_candidates_to_cluster_batches(
|
|
413
|
+
reinstated_candidates,
|
|
414
|
+
cluster_batches,
|
|
415
|
+
_progress_append,
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
if not reinstated_candidates:
|
|
419
|
+
typer.secho("[jarvis-sec] 复核完成:所有无效聚类理由充分,确认为无效", fg=typer.colors.GREEN)
|
|
420
|
+
|
|
421
|
+
# 记录复核结果(汇总)
|
|
422
|
+
_progress_append({
|
|
423
|
+
"event": "review_completed",
|
|
424
|
+
"total_reviewed": len(invalid_clusters_for_review),
|
|
425
|
+
"reinstated": len(reinstated_candidates),
|
|
426
|
+
"confirmed_invalid": len(invalid_clusters_for_review) - len(reinstated_candidates),
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
# 记录所有已复核的无效聚类的 gids(用于断点恢复时跳过已复核的聚类)
|
|
430
|
+
all_reviewed_gids = set()
|
|
431
|
+
for invalid_cluster in invalid_clusters_for_review:
|
|
432
|
+
cluster_gids = invalid_cluster.get("gids", [])
|
|
433
|
+
for gid_val in cluster_gids:
|
|
434
|
+
try:
|
|
435
|
+
gid_int = int(gid_val)
|
|
436
|
+
if gid_int >= 1:
|
|
437
|
+
all_reviewed_gids.add(gid_int)
|
|
438
|
+
except Exception:
|
|
439
|
+
pass
|
|
440
|
+
|
|
441
|
+
if all_reviewed_gids:
|
|
442
|
+
_progress_append({
|
|
443
|
+
"event": "review_all_gids",
|
|
444
|
+
"gids": sorted(list(all_reviewed_gids)),
|
|
445
|
+
"total": len(all_reviewed_gids),
|
|
446
|
+
})
|
|
447
|
+
status_mgr.update_review(
|
|
448
|
+
current_review=len(invalid_clusters_for_review),
|
|
449
|
+
total_reviews=len(invalid_clusters_for_review),
|
|
450
|
+
message=f"复核完成:{len(reinstated_candidates)} 个候选重新加入验证"
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
return cluster_batches
|