jarvis-ai-assistant 0.7.0__py3-none-any.whl → 0.7.6__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 (159) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +243 -139
  3. jarvis/jarvis_agent/agent_manager.py +5 -10
  4. jarvis/jarvis_agent/builtin_input_handler.py +2 -6
  5. jarvis/jarvis_agent/config_editor.py +2 -7
  6. jarvis/jarvis_agent/event_bus.py +82 -12
  7. jarvis/jarvis_agent/file_context_handler.py +265 -15
  8. jarvis/jarvis_agent/file_methodology_manager.py +3 -4
  9. jarvis/jarvis_agent/jarvis.py +113 -98
  10. jarvis/jarvis_agent/language_extractors/__init__.py +57 -0
  11. jarvis/jarvis_agent/language_extractors/c_extractor.py +21 -0
  12. jarvis/jarvis_agent/language_extractors/cpp_extractor.py +21 -0
  13. jarvis/jarvis_agent/language_extractors/go_extractor.py +21 -0
  14. jarvis/jarvis_agent/language_extractors/java_extractor.py +84 -0
  15. jarvis/jarvis_agent/language_extractors/javascript_extractor.py +79 -0
  16. jarvis/jarvis_agent/language_extractors/python_extractor.py +21 -0
  17. jarvis/jarvis_agent/language_extractors/rust_extractor.py +21 -0
  18. jarvis/jarvis_agent/language_extractors/typescript_extractor.py +84 -0
  19. jarvis/jarvis_agent/language_support_info.py +486 -0
  20. jarvis/jarvis_agent/main.py +6 -12
  21. jarvis/jarvis_agent/memory_manager.py +7 -16
  22. jarvis/jarvis_agent/methodology_share_manager.py +10 -16
  23. jarvis/jarvis_agent/prompt_manager.py +1 -1
  24. jarvis/jarvis_agent/prompts.py +193 -171
  25. jarvis/jarvis_agent/protocols.py +8 -12
  26. jarvis/jarvis_agent/run_loop.py +77 -14
  27. jarvis/jarvis_agent/session_manager.py +2 -3
  28. jarvis/jarvis_agent/share_manager.py +12 -21
  29. jarvis/jarvis_agent/shell_input_handler.py +1 -2
  30. jarvis/jarvis_agent/task_analyzer.py +26 -4
  31. jarvis/jarvis_agent/task_manager.py +11 -27
  32. jarvis/jarvis_agent/tool_executor.py +2 -3
  33. jarvis/jarvis_agent/tool_share_manager.py +12 -24
  34. jarvis/jarvis_agent/web_server.py +55 -20
  35. jarvis/jarvis_c2rust/__init__.py +5 -5
  36. jarvis/jarvis_c2rust/cli.py +461 -499
  37. jarvis/jarvis_c2rust/collector.py +45 -53
  38. jarvis/jarvis_c2rust/constants.py +26 -0
  39. jarvis/jarvis_c2rust/library_replacer.py +264 -132
  40. jarvis/jarvis_c2rust/llm_module_agent.py +162 -190
  41. jarvis/jarvis_c2rust/loaders.py +207 -0
  42. jarvis/jarvis_c2rust/models.py +28 -0
  43. jarvis/jarvis_c2rust/optimizer.py +1592 -395
  44. jarvis/jarvis_c2rust/transpiler.py +1722 -1064
  45. jarvis/jarvis_c2rust/utils.py +385 -0
  46. jarvis/jarvis_code_agent/build_validation_config.py +2 -3
  47. jarvis/jarvis_code_agent/code_agent.py +394 -320
  48. jarvis/jarvis_code_agent/code_analyzer/__init__.py +3 -0
  49. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +4 -0
  50. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +17 -2
  51. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +3 -0
  52. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +36 -4
  53. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +9 -0
  54. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +9 -0
  55. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +12 -1
  56. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +22 -5
  57. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +57 -32
  58. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +62 -6
  59. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +8 -9
  60. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +290 -5
  61. jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -0
  62. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +21 -3
  63. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +72 -4
  64. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +35 -3
  65. jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +212 -0
  66. jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +254 -0
  67. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +52 -2
  68. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +73 -1
  69. jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +280 -0
  70. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +306 -152
  71. jarvis/jarvis_code_agent/code_analyzer/structured_code.py +556 -0
  72. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +193 -18
  73. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +18 -8
  74. jarvis/jarvis_code_agent/lint.py +258 -27
  75. jarvis/jarvis_code_agent/utils.py +0 -1
  76. jarvis/jarvis_code_analysis/code_review.py +19 -24
  77. jarvis/jarvis_data/config_schema.json +53 -26
  78. jarvis/jarvis_git_squash/main.py +4 -5
  79. jarvis/jarvis_git_utils/git_commiter.py +44 -49
  80. jarvis/jarvis_mcp/sse_mcp_client.py +20 -27
  81. jarvis/jarvis_mcp/stdio_mcp_client.py +11 -12
  82. jarvis/jarvis_mcp/streamable_mcp_client.py +15 -14
  83. jarvis/jarvis_memory_organizer/memory_organizer.py +55 -74
  84. jarvis/jarvis_methodology/main.py +32 -48
  85. jarvis/jarvis_multi_agent/__init__.py +79 -61
  86. jarvis/jarvis_multi_agent/main.py +3 -7
  87. jarvis/jarvis_platform/base.py +469 -199
  88. jarvis/jarvis_platform/human.py +7 -8
  89. jarvis/jarvis_platform/kimi.py +30 -36
  90. jarvis/jarvis_platform/openai.py +65 -27
  91. jarvis/jarvis_platform/registry.py +26 -10
  92. jarvis/jarvis_platform/tongyi.py +24 -25
  93. jarvis/jarvis_platform/yuanbao.py +31 -42
  94. jarvis/jarvis_platform_manager/main.py +66 -77
  95. jarvis/jarvis_platform_manager/service.py +8 -13
  96. jarvis/jarvis_rag/cli.py +49 -51
  97. jarvis/jarvis_rag/embedding_manager.py +13 -18
  98. jarvis/jarvis_rag/llm_interface.py +8 -9
  99. jarvis/jarvis_rag/query_rewriter.py +10 -21
  100. jarvis/jarvis_rag/rag_pipeline.py +24 -27
  101. jarvis/jarvis_rag/reranker.py +4 -5
  102. jarvis/jarvis_rag/retriever.py +28 -30
  103. jarvis/jarvis_sec/__init__.py +220 -3520
  104. jarvis/jarvis_sec/agents.py +143 -0
  105. jarvis/jarvis_sec/analysis.py +276 -0
  106. jarvis/jarvis_sec/cli.py +29 -6
  107. jarvis/jarvis_sec/clustering.py +1439 -0
  108. jarvis/jarvis_sec/file_manager.py +427 -0
  109. jarvis/jarvis_sec/parsers.py +73 -0
  110. jarvis/jarvis_sec/prompts.py +268 -0
  111. jarvis/jarvis_sec/report.py +83 -4
  112. jarvis/jarvis_sec/review.py +453 -0
  113. jarvis/jarvis_sec/utils.py +499 -0
  114. jarvis/jarvis_sec/verification.py +848 -0
  115. jarvis/jarvis_sec/workflow.py +7 -0
  116. jarvis/jarvis_smart_shell/main.py +38 -87
  117. jarvis/jarvis_stats/cli.py +1 -1
  118. jarvis/jarvis_stats/stats.py +7 -7
  119. jarvis/jarvis_stats/storage.py +15 -21
  120. jarvis/jarvis_tools/clear_memory.py +3 -20
  121. jarvis/jarvis_tools/cli/main.py +20 -23
  122. jarvis/jarvis_tools/edit_file.py +1066 -0
  123. jarvis/jarvis_tools/execute_script.py +42 -21
  124. jarvis/jarvis_tools/file_analyzer.py +6 -9
  125. jarvis/jarvis_tools/generate_new_tool.py +11 -20
  126. jarvis/jarvis_tools/lsp_client.py +1552 -0
  127. jarvis/jarvis_tools/methodology.py +2 -3
  128. jarvis/jarvis_tools/read_code.py +1525 -87
  129. jarvis/jarvis_tools/read_symbols.py +2 -3
  130. jarvis/jarvis_tools/read_webpage.py +7 -10
  131. jarvis/jarvis_tools/registry.py +370 -181
  132. jarvis/jarvis_tools/retrieve_memory.py +20 -19
  133. jarvis/jarvis_tools/rewrite_file.py +105 -0
  134. jarvis/jarvis_tools/save_memory.py +3 -15
  135. jarvis/jarvis_tools/search_web.py +3 -7
  136. jarvis/jarvis_tools/sub_agent.py +17 -6
  137. jarvis/jarvis_tools/sub_code_agent.py +14 -16
  138. jarvis/jarvis_tools/virtual_tty.py +54 -32
  139. jarvis/jarvis_utils/clipboard.py +7 -10
  140. jarvis/jarvis_utils/config.py +98 -63
  141. jarvis/jarvis_utils/embedding.py +5 -5
  142. jarvis/jarvis_utils/fzf.py +8 -8
  143. jarvis/jarvis_utils/git_utils.py +81 -67
  144. jarvis/jarvis_utils/input.py +24 -49
  145. jarvis/jarvis_utils/jsonnet_compat.py +465 -0
  146. jarvis/jarvis_utils/methodology.py +33 -35
  147. jarvis/jarvis_utils/utils.py +245 -202
  148. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/METADATA +205 -70
  149. jarvis_ai_assistant-0.7.6.dist-info/RECORD +218 -0
  150. jarvis/jarvis_agent/edit_file_handler.py +0 -584
  151. jarvis/jarvis_agent/rewrite_file_handler.py +0 -141
  152. jarvis/jarvis_agent/task_planner.py +0 -496
  153. jarvis/jarvis_platform/ai8.py +0 -332
  154. jarvis/jarvis_tools/ask_user.py +0 -54
  155. jarvis_ai_assistant-0.7.0.dist-info/RECORD +0 -192
  156. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/WHEEL +0 -0
  157. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/entry_points.txt +0 -0
  158. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/licenses/LICENSE +0 -0
  159. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.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