jarvis-ai-assistant 0.7.8__py3-none-any.whl → 1.0.2__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 (279) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +567 -222
  3. jarvis/jarvis_agent/agent_manager.py +19 -12
  4. jarvis/jarvis_agent/builtin_input_handler.py +79 -11
  5. jarvis/jarvis_agent/config_editor.py +7 -2
  6. jarvis/jarvis_agent/event_bus.py +24 -13
  7. jarvis/jarvis_agent/events.py +19 -1
  8. jarvis/jarvis_agent/file_context_handler.py +67 -64
  9. jarvis/jarvis_agent/file_methodology_manager.py +38 -24
  10. jarvis/jarvis_agent/jarvis.py +186 -114
  11. jarvis/jarvis_agent/language_extractors/__init__.py +8 -1
  12. jarvis/jarvis_agent/language_extractors/c_extractor.py +7 -4
  13. jarvis/jarvis_agent/language_extractors/cpp_extractor.py +9 -4
  14. jarvis/jarvis_agent/language_extractors/go_extractor.py +7 -4
  15. jarvis/jarvis_agent/language_extractors/java_extractor.py +27 -20
  16. jarvis/jarvis_agent/language_extractors/javascript_extractor.py +22 -17
  17. jarvis/jarvis_agent/language_extractors/python_extractor.py +7 -4
  18. jarvis/jarvis_agent/language_extractors/rust_extractor.py +7 -4
  19. jarvis/jarvis_agent/language_extractors/typescript_extractor.py +22 -17
  20. jarvis/jarvis_agent/language_support_info.py +250 -219
  21. jarvis/jarvis_agent/main.py +19 -23
  22. jarvis/jarvis_agent/memory_manager.py +9 -6
  23. jarvis/jarvis_agent/methodology_share_manager.py +21 -15
  24. jarvis/jarvis_agent/output_handler.py +4 -2
  25. jarvis/jarvis_agent/prompt_builder.py +7 -6
  26. jarvis/jarvis_agent/prompt_manager.py +113 -8
  27. jarvis/jarvis_agent/prompts.py +317 -85
  28. jarvis/jarvis_agent/protocols.py +5 -2
  29. jarvis/jarvis_agent/run_loop.py +192 -32
  30. jarvis/jarvis_agent/session_manager.py +7 -3
  31. jarvis/jarvis_agent/share_manager.py +23 -13
  32. jarvis/jarvis_agent/shell_input_handler.py +12 -8
  33. jarvis/jarvis_agent/stdio_redirect.py +25 -26
  34. jarvis/jarvis_agent/task_analyzer.py +29 -23
  35. jarvis/jarvis_agent/task_list.py +869 -0
  36. jarvis/jarvis_agent/task_manager.py +26 -23
  37. jarvis/jarvis_agent/tool_executor.py +6 -5
  38. jarvis/jarvis_agent/tool_share_manager.py +24 -14
  39. jarvis/jarvis_agent/user_interaction.py +3 -3
  40. jarvis/jarvis_agent/utils.py +9 -1
  41. jarvis/jarvis_agent/web_bridge.py +37 -17
  42. jarvis/jarvis_agent/web_output_sink.py +5 -2
  43. jarvis/jarvis_agent/web_server.py +165 -36
  44. jarvis/jarvis_c2rust/__init__.py +1 -1
  45. jarvis/jarvis_c2rust/cli.py +260 -141
  46. jarvis/jarvis_c2rust/collector.py +37 -18
  47. jarvis/jarvis_c2rust/constants.py +60 -0
  48. jarvis/jarvis_c2rust/library_replacer.py +242 -1010
  49. jarvis/jarvis_c2rust/library_replacer_checkpoint.py +133 -0
  50. jarvis/jarvis_c2rust/library_replacer_llm.py +287 -0
  51. jarvis/jarvis_c2rust/library_replacer_loader.py +191 -0
  52. jarvis/jarvis_c2rust/library_replacer_output.py +134 -0
  53. jarvis/jarvis_c2rust/library_replacer_prompts.py +124 -0
  54. jarvis/jarvis_c2rust/library_replacer_utils.py +188 -0
  55. jarvis/jarvis_c2rust/llm_module_agent.py +98 -1044
  56. jarvis/jarvis_c2rust/llm_module_agent_apply.py +170 -0
  57. jarvis/jarvis_c2rust/llm_module_agent_executor.py +288 -0
  58. jarvis/jarvis_c2rust/llm_module_agent_loader.py +170 -0
  59. jarvis/jarvis_c2rust/llm_module_agent_prompts.py +268 -0
  60. jarvis/jarvis_c2rust/llm_module_agent_types.py +57 -0
  61. jarvis/jarvis_c2rust/llm_module_agent_utils.py +150 -0
  62. jarvis/jarvis_c2rust/llm_module_agent_validator.py +119 -0
  63. jarvis/jarvis_c2rust/loaders.py +28 -10
  64. jarvis/jarvis_c2rust/models.py +5 -2
  65. jarvis/jarvis_c2rust/optimizer.py +192 -1974
  66. jarvis/jarvis_c2rust/optimizer_build_fix.py +286 -0
  67. jarvis/jarvis_c2rust/optimizer_clippy.py +766 -0
  68. jarvis/jarvis_c2rust/optimizer_config.py +49 -0
  69. jarvis/jarvis_c2rust/optimizer_docs.py +183 -0
  70. jarvis/jarvis_c2rust/optimizer_options.py +48 -0
  71. jarvis/jarvis_c2rust/optimizer_progress.py +469 -0
  72. jarvis/jarvis_c2rust/optimizer_report.py +52 -0
  73. jarvis/jarvis_c2rust/optimizer_unsafe.py +309 -0
  74. jarvis/jarvis_c2rust/optimizer_utils.py +469 -0
  75. jarvis/jarvis_c2rust/optimizer_visibility.py +185 -0
  76. jarvis/jarvis_c2rust/scanner.py +229 -166
  77. jarvis/jarvis_c2rust/transpiler.py +531 -2732
  78. jarvis/jarvis_c2rust/transpiler_agents.py +503 -0
  79. jarvis/jarvis_c2rust/transpiler_build.py +1294 -0
  80. jarvis/jarvis_c2rust/transpiler_codegen.py +204 -0
  81. jarvis/jarvis_c2rust/transpiler_compile.py +146 -0
  82. jarvis/jarvis_c2rust/transpiler_config.py +178 -0
  83. jarvis/jarvis_c2rust/transpiler_context.py +122 -0
  84. jarvis/jarvis_c2rust/transpiler_executor.py +516 -0
  85. jarvis/jarvis_c2rust/transpiler_generation.py +278 -0
  86. jarvis/jarvis_c2rust/transpiler_git.py +163 -0
  87. jarvis/jarvis_c2rust/transpiler_mod_utils.py +225 -0
  88. jarvis/jarvis_c2rust/transpiler_modules.py +336 -0
  89. jarvis/jarvis_c2rust/transpiler_planning.py +394 -0
  90. jarvis/jarvis_c2rust/transpiler_review.py +1196 -0
  91. jarvis/jarvis_c2rust/transpiler_symbols.py +176 -0
  92. jarvis/jarvis_c2rust/utils.py +269 -79
  93. jarvis/jarvis_code_agent/after_change.py +233 -0
  94. jarvis/jarvis_code_agent/build_validation_config.py +37 -30
  95. jarvis/jarvis_code_agent/builtin_rules.py +68 -0
  96. jarvis/jarvis_code_agent/code_agent.py +976 -1517
  97. jarvis/jarvis_code_agent/code_agent_build.py +227 -0
  98. jarvis/jarvis_code_agent/code_agent_diff.py +246 -0
  99. jarvis/jarvis_code_agent/code_agent_git.py +525 -0
  100. jarvis/jarvis_code_agent/code_agent_impact.py +177 -0
  101. jarvis/jarvis_code_agent/code_agent_lint.py +283 -0
  102. jarvis/jarvis_code_agent/code_agent_llm.py +159 -0
  103. jarvis/jarvis_code_agent/code_agent_postprocess.py +105 -0
  104. jarvis/jarvis_code_agent/code_agent_prompts.py +46 -0
  105. jarvis/jarvis_code_agent/code_agent_rules.py +305 -0
  106. jarvis/jarvis_code_agent/code_analyzer/__init__.py +52 -48
  107. jarvis/jarvis_code_agent/code_analyzer/base_language.py +12 -10
  108. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +12 -11
  109. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +16 -12
  110. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +26 -17
  111. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +558 -104
  112. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +27 -16
  113. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +22 -18
  114. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +21 -16
  115. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +20 -16
  116. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +27 -16
  117. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +47 -23
  118. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +71 -37
  119. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +162 -35
  120. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +111 -57
  121. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +18 -12
  122. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +185 -183
  123. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +2 -1
  124. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +24 -15
  125. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +227 -141
  126. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +321 -247
  127. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +37 -29
  128. jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -13
  129. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +15 -9
  130. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +75 -45
  131. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +87 -52
  132. jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +84 -51
  133. jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +94 -64
  134. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +109 -71
  135. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +97 -63
  136. jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +103 -69
  137. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +271 -268
  138. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +76 -64
  139. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +92 -19
  140. jarvis/jarvis_code_agent/diff_visualizer.py +998 -0
  141. jarvis/jarvis_code_agent/lint.py +223 -524
  142. jarvis/jarvis_code_agent/rule_share_manager.py +158 -0
  143. jarvis/jarvis_code_agent/rules/clean_code.md +144 -0
  144. jarvis/jarvis_code_agent/rules/code_review.md +115 -0
  145. jarvis/jarvis_code_agent/rules/documentation.md +165 -0
  146. jarvis/jarvis_code_agent/rules/generate_rules.md +52 -0
  147. jarvis/jarvis_code_agent/rules/performance.md +158 -0
  148. jarvis/jarvis_code_agent/rules/refactoring.md +139 -0
  149. jarvis/jarvis_code_agent/rules/security.md +160 -0
  150. jarvis/jarvis_code_agent/rules/tdd.md +78 -0
  151. jarvis/jarvis_code_agent/test_rules/cpp_test.md +118 -0
  152. jarvis/jarvis_code_agent/test_rules/go_test.md +98 -0
  153. jarvis/jarvis_code_agent/test_rules/java_test.md +99 -0
  154. jarvis/jarvis_code_agent/test_rules/javascript_test.md +113 -0
  155. jarvis/jarvis_code_agent/test_rules/php_test.md +117 -0
  156. jarvis/jarvis_code_agent/test_rules/python_test.md +91 -0
  157. jarvis/jarvis_code_agent/test_rules/ruby_test.md +102 -0
  158. jarvis/jarvis_code_agent/test_rules/rust_test.md +86 -0
  159. jarvis/jarvis_code_agent/utils.py +36 -26
  160. jarvis/jarvis_code_analysis/checklists/loader.py +21 -21
  161. jarvis/jarvis_code_analysis/code_review.py +64 -33
  162. jarvis/jarvis_data/config_schema.json +285 -192
  163. jarvis/jarvis_git_squash/main.py +8 -6
  164. jarvis/jarvis_git_utils/git_commiter.py +53 -76
  165. jarvis/jarvis_mcp/__init__.py +5 -2
  166. jarvis/jarvis_mcp/sse_mcp_client.py +40 -30
  167. jarvis/jarvis_mcp/stdio_mcp_client.py +27 -19
  168. jarvis/jarvis_mcp/streamable_mcp_client.py +35 -26
  169. jarvis/jarvis_memory_organizer/memory_organizer.py +78 -55
  170. jarvis/jarvis_methodology/main.py +48 -39
  171. jarvis/jarvis_multi_agent/__init__.py +56 -23
  172. jarvis/jarvis_multi_agent/main.py +15 -18
  173. jarvis/jarvis_platform/base.py +179 -111
  174. jarvis/jarvis_platform/human.py +27 -16
  175. jarvis/jarvis_platform/kimi.py +52 -45
  176. jarvis/jarvis_platform/openai.py +101 -40
  177. jarvis/jarvis_platform/registry.py +51 -33
  178. jarvis/jarvis_platform/tongyi.py +68 -38
  179. jarvis/jarvis_platform/yuanbao.py +59 -43
  180. jarvis/jarvis_platform_manager/main.py +68 -76
  181. jarvis/jarvis_platform_manager/service.py +24 -14
  182. jarvis/jarvis_rag/README_CONFIG.md +314 -0
  183. jarvis/jarvis_rag/README_DYNAMIC_LOADING.md +311 -0
  184. jarvis/jarvis_rag/README_ONLINE_MODELS.md +230 -0
  185. jarvis/jarvis_rag/__init__.py +57 -4
  186. jarvis/jarvis_rag/cache.py +3 -1
  187. jarvis/jarvis_rag/cli.py +48 -68
  188. jarvis/jarvis_rag/embedding_interface.py +39 -0
  189. jarvis/jarvis_rag/embedding_manager.py +7 -230
  190. jarvis/jarvis_rag/embeddings/__init__.py +41 -0
  191. jarvis/jarvis_rag/embeddings/base.py +114 -0
  192. jarvis/jarvis_rag/embeddings/cohere.py +66 -0
  193. jarvis/jarvis_rag/embeddings/edgefn.py +117 -0
  194. jarvis/jarvis_rag/embeddings/local.py +260 -0
  195. jarvis/jarvis_rag/embeddings/openai.py +62 -0
  196. jarvis/jarvis_rag/embeddings/registry.py +293 -0
  197. jarvis/jarvis_rag/llm_interface.py +8 -6
  198. jarvis/jarvis_rag/query_rewriter.py +8 -9
  199. jarvis/jarvis_rag/rag_pipeline.py +61 -52
  200. jarvis/jarvis_rag/reranker.py +7 -75
  201. jarvis/jarvis_rag/reranker_interface.py +32 -0
  202. jarvis/jarvis_rag/rerankers/__init__.py +41 -0
  203. jarvis/jarvis_rag/rerankers/base.py +109 -0
  204. jarvis/jarvis_rag/rerankers/cohere.py +67 -0
  205. jarvis/jarvis_rag/rerankers/edgefn.py +140 -0
  206. jarvis/jarvis_rag/rerankers/jina.py +79 -0
  207. jarvis/jarvis_rag/rerankers/local.py +89 -0
  208. jarvis/jarvis_rag/rerankers/registry.py +293 -0
  209. jarvis/jarvis_rag/retriever.py +58 -43
  210. jarvis/jarvis_sec/__init__.py +66 -141
  211. jarvis/jarvis_sec/agents.py +21 -17
  212. jarvis/jarvis_sec/analysis.py +80 -33
  213. jarvis/jarvis_sec/checkers/__init__.py +7 -13
  214. jarvis/jarvis_sec/checkers/c_checker.py +356 -164
  215. jarvis/jarvis_sec/checkers/rust_checker.py +47 -29
  216. jarvis/jarvis_sec/cli.py +43 -21
  217. jarvis/jarvis_sec/clustering.py +430 -272
  218. jarvis/jarvis_sec/file_manager.py +99 -55
  219. jarvis/jarvis_sec/parsers.py +9 -6
  220. jarvis/jarvis_sec/prompts.py +4 -3
  221. jarvis/jarvis_sec/report.py +44 -22
  222. jarvis/jarvis_sec/review.py +180 -107
  223. jarvis/jarvis_sec/status.py +50 -41
  224. jarvis/jarvis_sec/types.py +3 -0
  225. jarvis/jarvis_sec/utils.py +160 -83
  226. jarvis/jarvis_sec/verification.py +411 -181
  227. jarvis/jarvis_sec/workflow.py +132 -21
  228. jarvis/jarvis_smart_shell/main.py +28 -41
  229. jarvis/jarvis_stats/cli.py +14 -12
  230. jarvis/jarvis_stats/stats.py +28 -19
  231. jarvis/jarvis_stats/storage.py +14 -8
  232. jarvis/jarvis_stats/visualizer.py +12 -7
  233. jarvis/jarvis_tools/base.py +5 -2
  234. jarvis/jarvis_tools/clear_memory.py +13 -9
  235. jarvis/jarvis_tools/cli/main.py +23 -18
  236. jarvis/jarvis_tools/edit_file.py +572 -873
  237. jarvis/jarvis_tools/execute_script.py +10 -7
  238. jarvis/jarvis_tools/file_analyzer.py +7 -8
  239. jarvis/jarvis_tools/meta_agent.py +287 -0
  240. jarvis/jarvis_tools/methodology.py +5 -3
  241. jarvis/jarvis_tools/read_code.py +305 -1438
  242. jarvis/jarvis_tools/read_symbols.py +50 -17
  243. jarvis/jarvis_tools/read_webpage.py +19 -18
  244. jarvis/jarvis_tools/registry.py +435 -156
  245. jarvis/jarvis_tools/retrieve_memory.py +16 -11
  246. jarvis/jarvis_tools/save_memory.py +8 -6
  247. jarvis/jarvis_tools/search_web.py +31 -31
  248. jarvis/jarvis_tools/sub_agent.py +32 -28
  249. jarvis/jarvis_tools/sub_code_agent.py +44 -60
  250. jarvis/jarvis_tools/task_list_manager.py +1811 -0
  251. jarvis/jarvis_tools/virtual_tty.py +29 -19
  252. jarvis/jarvis_utils/__init__.py +4 -0
  253. jarvis/jarvis_utils/builtin_replace_map.py +2 -1
  254. jarvis/jarvis_utils/clipboard.py +9 -8
  255. jarvis/jarvis_utils/collections.py +331 -0
  256. jarvis/jarvis_utils/config.py +699 -194
  257. jarvis/jarvis_utils/dialogue_recorder.py +294 -0
  258. jarvis/jarvis_utils/embedding.py +6 -3
  259. jarvis/jarvis_utils/file_processors.py +7 -1
  260. jarvis/jarvis_utils/fzf.py +9 -3
  261. jarvis/jarvis_utils/git_utils.py +71 -42
  262. jarvis/jarvis_utils/globals.py +116 -32
  263. jarvis/jarvis_utils/http.py +6 -2
  264. jarvis/jarvis_utils/input.py +318 -83
  265. jarvis/jarvis_utils/jsonnet_compat.py +119 -104
  266. jarvis/jarvis_utils/methodology.py +37 -28
  267. jarvis/jarvis_utils/output.py +201 -44
  268. jarvis/jarvis_utils/utils.py +986 -628
  269. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/METADATA +49 -33
  270. jarvis_ai_assistant-1.0.2.dist-info/RECORD +304 -0
  271. jarvis/jarvis_code_agent/code_analyzer/structured_code.py +0 -556
  272. jarvis/jarvis_tools/generate_new_tool.py +0 -205
  273. jarvis/jarvis_tools/lsp_client.py +0 -1552
  274. jarvis/jarvis_tools/rewrite_file.py +0 -105
  275. jarvis_ai_assistant-0.7.8.dist-info/RECORD +0 -218
  276. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/WHEEL +0 -0
  277. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/entry_points.txt +0 -0
  278. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/licenses/LICENSE +0 -0
  279. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/top_level.txt +0 -0
@@ -8,14 +8,19 @@
8
8
  3. analysis.jsonl - 分析结果文件:包括所有聚类,聚类中哪些问题是问题,哪些问题是误报
9
9
  """
10
10
 
11
- from typing import Dict, List, Set, Tuple
12
- from pathlib import Path
13
11
  import json
14
- import typer
12
+ from pathlib import Path
13
+ from typing import Any
14
+ from typing import Dict
15
+ from typing import List
16
+ from typing import Set
17
+ from typing import Tuple
15
18
 
19
+ from jarvis.jarvis_utils.output import PrettyOutput
16
20
 
17
21
  # ==================== 文件路径定义 ====================
18
22
 
23
+
19
24
  def get_candidates_file(sec_dir: Path) -> Path:
20
25
  """获取只扫结果文件路径"""
21
26
  return sec_dir / "candidates.jsonl"
@@ -33,10 +38,11 @@ def get_analysis_file(sec_dir: Path) -> Path:
33
38
 
34
39
  # ==================== 只扫结果文件 (candidates.jsonl) ====================
35
40
 
41
+
36
42
  def save_candidates(sec_dir: Path, candidates: List[Dict]) -> None:
37
43
  """
38
44
  保存只扫结果到 candidates.jsonl
39
-
45
+
40
46
  格式:每行一个候选,包含所有原始信息 + gid
41
47
  {
42
48
  "gid": 1,
@@ -52,14 +58,16 @@ def save_candidates(sec_dir: Path, candidates: List[Dict]) -> None:
52
58
  """
53
59
  path = get_candidates_file(sec_dir)
54
60
  path.parent.mkdir(parents=True, exist_ok=True)
55
-
61
+
56
62
  # 覆盖模式,确保文件内容是最新的
57
63
  with path.open("w", encoding="utf-8") as f:
58
64
  for candidate in candidates:
59
65
  f.write(json.dumps(candidate, ensure_ascii=False) + "\n")
60
-
66
+
61
67
  try:
62
- typer.secho(f"[jarvis-sec] 已保存 {len(candidates)} 个候选到 {path}", fg=typer.colors.GREEN)
68
+ PrettyOutput.auto_print(
69
+ f"✅ [jarvis-sec] 已保存 {len(candidates)} 个候选到 {path}"
70
+ )
63
71
  except Exception:
64
72
  pass
65
73
 
@@ -67,12 +75,12 @@ def save_candidates(sec_dir: Path, candidates: List[Dict]) -> None:
67
75
  def load_candidates(sec_dir: Path) -> List[Dict]:
68
76
  """
69
77
  从 candidates.jsonl 加载只扫结果
70
-
78
+
71
79
  返回: 候选列表,每个候选包含gid
72
80
  """
73
81
  path = get_candidates_file(sec_dir)
74
82
  candidates = []
75
-
83
+
76
84
  if path.exists():
77
85
  try:
78
86
  with path.open("r", encoding="utf-8", errors="ignore") as f:
@@ -87,10 +95,12 @@ def load_candidates(sec_dir: Path) -> List[Dict]:
87
95
  pass
88
96
  except Exception as e:
89
97
  try:
90
- typer.secho(f"[jarvis-sec] 警告:加载 candidates.jsonl 失败: {e}", fg=typer.colors.YELLOW)
98
+ PrettyOutput.auto_print(
99
+ f"⚠️ [jarvis-sec] 警告:加载 candidates.jsonl 失败: {e}"
100
+ )
91
101
  except Exception:
92
102
  pass
93
-
103
+
94
104
  return candidates
95
105
 
96
106
 
@@ -110,10 +120,11 @@ def get_all_candidate_gids(sec_dir: Path) -> Set[int]:
110
120
 
111
121
  # ==================== 聚类信息文件 (clusters.jsonl) ====================
112
122
 
123
+
113
124
  def save_cluster(sec_dir: Path, cluster: Dict) -> None:
114
125
  """
115
126
  保存单个聚类到 clusters.jsonl(追加模式)
116
-
127
+
117
128
  格式:每行一个聚类记录
118
129
  {
119
130
  "cluster_id": "file_path|batch_index|index", # 唯一标识
@@ -129,7 +140,7 @@ def save_cluster(sec_dir: Path, cluster: Dict) -> None:
129
140
  """
130
141
  path = get_clusters_file(sec_dir)
131
142
  path.parent.mkdir(parents=True, exist_ok=True)
132
-
143
+
133
144
  # 追加模式
134
145
  with path.open("a", encoding="utf-8") as f:
135
146
  f.write(json.dumps(cluster, ensure_ascii=False) + "\n")
@@ -138,12 +149,12 @@ def save_cluster(sec_dir: Path, cluster: Dict) -> None:
138
149
  def load_clusters(sec_dir: Path) -> List[Dict]:
139
150
  """
140
151
  从 clusters.jsonl 加载所有聚类
141
-
152
+
142
153
  返回: 聚类列表
143
154
  """
144
155
  path = get_clusters_file(sec_dir)
145
156
  clusters = []
146
-
157
+
147
158
  if path.exists():
148
159
  try:
149
160
  # 使用字典合并:key 为 cluster_id,合并同一个 cluster_id 的所有记录的 gid
@@ -159,28 +170,35 @@ def load_clusters(sec_dir: Path) -> List[Dict]:
159
170
  if cluster_id:
160
171
  if cluster_id in seen_clusters:
161
172
  # 如果已存在,合并 gid 列表(去重)
162
- existing_gids = set(seen_clusters[cluster_id].get("gids", []))
173
+ existing_gids = set(
174
+ seen_clusters[cluster_id].get("gids", [])
175
+ )
163
176
  new_gids = set(cluster.get("gids", []))
164
177
  merged_gids = sorted(list(existing_gids | new_gids))
165
178
  seen_clusters[cluster_id]["gids"] = merged_gids
166
179
  # 保留最新的其他字段(verification, is_invalid 等)
167
- seen_clusters[cluster_id].update({
168
- k: v for k, v in cluster.items()
169
- if k != "gids" and k != "cluster_id"
170
- })
180
+ seen_clusters[cluster_id].update(
181
+ {
182
+ k: v
183
+ for k, v in cluster.items()
184
+ if k != "gids" and k != "cluster_id"
185
+ }
186
+ )
171
187
  else:
172
188
  # 第一次遇到这个 cluster_id,直接保存
173
189
  seen_clusters[cluster_id] = cluster
174
190
  except Exception:
175
191
  pass
176
-
192
+
177
193
  clusters = list(seen_clusters.values())
178
194
  except Exception as e:
179
195
  try:
180
- typer.secho(f"[jarvis-sec] 警告:加载 clusters.jsonl 失败: {e}", fg=typer.colors.YELLOW)
196
+ PrettyOutput.auto_print(
197
+ f"⚠️ [jarvis-sec] 警告:加载 clusters.jsonl 失败: {e}"
198
+ )
181
199
  except Exception:
182
200
  pass
183
-
201
+
184
202
  return clusters
185
203
 
186
204
 
@@ -204,22 +222,23 @@ def get_all_clustered_gids(sec_dir: Path) -> Set[int]:
204
222
  def validate_clustering_completeness(sec_dir: Path) -> Tuple[bool, Set[int]]:
205
223
  """
206
224
  校验聚类完整性,确保所有候选的gid都被聚类
207
-
225
+
208
226
  返回: (is_complete, missing_gids)
209
227
  """
210
228
  all_candidate_gids = get_all_candidate_gids(sec_dir)
211
229
  all_clustered_gids = get_all_clustered_gids(sec_dir)
212
230
  missing_gids = all_candidate_gids - all_clustered_gids
213
-
231
+
214
232
  return len(missing_gids) == 0, missing_gids
215
233
 
216
234
 
217
235
  # ==================== 分析结果文件 (analysis.jsonl) ====================
218
236
 
237
+
219
238
  def save_analysis_result(sec_dir: Path, analysis: Dict) -> None:
220
239
  """
221
240
  保存单个分析结果到 analysis.jsonl(追加模式)
222
-
241
+
223
242
  格式:每行一个分析结果记录
224
243
  {
225
244
  "cluster_id": "file_path|batch_index|index", # 对应的聚类ID
@@ -244,7 +263,7 @@ def save_analysis_result(sec_dir: Path, analysis: Dict) -> None:
244
263
  """
245
264
  path = get_analysis_file(sec_dir)
246
265
  path.parent.mkdir(parents=True, exist_ok=True)
247
-
266
+
248
267
  # 追加模式
249
268
  with path.open("a", encoding="utf-8") as f:
250
269
  f.write(json.dumps(analysis, ensure_ascii=False) + "\n")
@@ -253,12 +272,12 @@ def save_analysis_result(sec_dir: Path, analysis: Dict) -> None:
253
272
  def load_analysis_results(sec_dir: Path) -> List[Dict]:
254
273
  """
255
274
  从 analysis.jsonl 加载所有分析结果
256
-
275
+
257
276
  返回: 分析结果列表
258
277
  """
259
278
  path = get_analysis_file(sec_dir)
260
279
  results = []
261
-
280
+
262
281
  if path.exists():
263
282
  try:
264
283
  # 使用字典合并:key 为 cluster_id,合并同一个 cluster_id 的所有记录
@@ -275,48 +294,73 @@ def load_analysis_results(sec_dir: Path) -> List[Dict]:
275
294
  if cluster_id in seen_results:
276
295
  # 如果已存在,合并 gid、verified_gids、false_positive_gids 和 issues
277
296
  existing = seen_results[cluster_id]
278
-
297
+
279
298
  # 合并 gids(去重)
280
299
  existing_gids = set(existing.get("gids", []))
281
300
  new_gids = set(result.get("gids", []))
282
- existing["gids"] = sorted(list(existing_gids | new_gids))
283
-
301
+ existing["gids"] = sorted(
302
+ list(existing_gids | new_gids)
303
+ )
304
+
284
305
  # 合并 verified_gids(去重)
285
- existing_verified = set(existing.get("verified_gids", []))
306
+ existing_verified = set(
307
+ existing.get("verified_gids", [])
308
+ )
286
309
  new_verified = set(result.get("verified_gids", []))
287
- existing["verified_gids"] = sorted(list(existing_verified | new_verified))
288
-
310
+ existing["verified_gids"] = sorted(
311
+ list(existing_verified | new_verified)
312
+ )
313
+
289
314
  # 合并 false_positive_gids(去重)
290
- existing_false = set(existing.get("false_positive_gids", []))
315
+ existing_false = set(
316
+ existing.get("false_positive_gids", [])
317
+ )
291
318
  new_false = set(result.get("false_positive_gids", []))
292
- existing["false_positive_gids"] = sorted(list(existing_false | new_false))
293
-
319
+ existing["false_positive_gids"] = sorted(
320
+ list(existing_false | new_false)
321
+ )
322
+
294
323
  # 合并 issues(通过 gid 去重)
295
- existing_issues = {issue.get("gid"): issue for issue in existing.get("issues", [])}
324
+ existing_issues = {
325
+ issue.get("gid"): issue
326
+ for issue in existing.get("issues", [])
327
+ }
296
328
  for issue in result.get("issues", []):
297
329
  gid = issue.get("gid")
298
330
  if gid:
299
331
  existing_issues[gid] = issue # 保留最新的 issue
300
332
  existing["issues"] = list(existing_issues.values())
301
-
333
+
302
334
  # 保留最新的其他字段
303
- existing.update({
304
- k: v for k, v in result.items()
305
- if k not in ["gids", "verified_gids", "false_positive_gids", "issues", "cluster_id"]
306
- })
335
+ existing.update(
336
+ {
337
+ k: v
338
+ for k, v in result.items()
339
+ if k
340
+ not in [
341
+ "gids",
342
+ "verified_gids",
343
+ "false_positive_gids",
344
+ "issues",
345
+ "cluster_id",
346
+ ]
347
+ }
348
+ )
307
349
  else:
308
350
  # 第一次遇到这个 cluster_id,直接保存
309
351
  seen_results[cluster_id] = result
310
352
  except Exception:
311
353
  pass
312
-
354
+
313
355
  results = list(seen_results.values())
314
356
  except Exception as e:
315
357
  try:
316
- typer.secho(f"[jarvis-sec] 警告:加载 analysis.jsonl 失败: {e}", fg=typer.colors.YELLOW)
358
+ PrettyOutput.auto_print(
359
+ f"⚠️ [jarvis-sec] 警告:加载 analysis.jsonl 失败: {e}"
360
+ )
317
361
  except Exception:
318
362
  pass
319
-
363
+
320
364
  return results
321
365
 
322
366
 
@@ -373,10 +417,11 @@ def get_false_positive_gids(sec_dir: Path) -> Set[int]:
373
417
 
374
418
  # ==================== 断点恢复状态检查 ====================
375
419
 
376
- def get_resume_status(sec_dir: Path) -> Dict[str, any]:
420
+
421
+ def get_resume_status(sec_dir: Path) -> Dict[str, Any]:
377
422
  """
378
423
  根据3个配置文件的存在性和状态,推断断点恢复状态
379
-
424
+
380
425
  返回: {
381
426
  "has_candidates": bool, # 是否有只扫结果
382
427
  "has_clusters": bool, # 是否有聚类结果
@@ -398,30 +443,29 @@ def get_resume_status(sec_dir: Path) -> Dict[str, any]:
398
443
  "clustering_complete": False,
399
444
  "missing_gids": set(),
400
445
  }
401
-
446
+
402
447
  # 检查只扫结果
403
448
  candidates = load_candidates(sec_dir)
404
449
  if candidates:
405
450
  status["has_candidates"] = True
406
451
  status["candidates_count"] = len(candidates)
407
-
452
+
408
453
  # 检查聚类结果
409
454
  clusters = load_clusters(sec_dir)
410
455
  if clusters:
411
456
  status["has_clusters"] = True
412
457
  status["clusters_count"] = len(clusters)
413
-
458
+
414
459
  # 检查分析结果
415
460
  results = load_analysis_results(sec_dir)
416
461
  if results:
417
462
  status["has_analysis"] = True
418
463
  status["analysis_count"] = len(results)
419
-
464
+
420
465
  # 校验聚类完整性
421
466
  if status["has_candidates"]:
422
467
  is_complete, missing_gids = validate_clustering_completeness(sec_dir)
423
468
  status["clustering_complete"] = is_complete
424
469
  status["missing_gids"] = missing_gids
425
-
426
- return status
427
470
 
471
+ return status
@@ -1,7 +1,9 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """解析模块 - 用于解析Agent返回的JSON格式摘要"""
3
3
 
4
- from typing import List, Optional
4
+ from typing import List
5
+ from typing import Optional
6
+
5
7
  from jarvis.jarvis_utils.jsonnet_compat import loads as json_loads
6
8
 
7
9
 
@@ -9,6 +11,7 @@ def parse_clusters_from_text(text: str) -> tuple[Optional[List], Optional[str]]:
9
11
  """解析聚类文本,返回(解析结果, 错误信息)"""
10
12
  try:
11
13
  import re as _re
14
+
12
15
  # 使用正则表达式进行大小写不敏感的匹配
13
16
  pattern = r"<CLUSTERS>([\s\S]*?)</CLUSTERS>"
14
17
  match = _re.search(pattern, text, flags=_re.IGNORECASE)
@@ -18,10 +21,10 @@ def parse_clusters_from_text(text: str) -> tuple[Optional[List], Optional[str]]:
18
21
  end = text.find("</CLUSTERS>")
19
22
  if start == -1 or end == -1 or end <= start:
20
23
  return None, "未找到 <CLUSTERS> 或 </CLUSTERS> 标签,或标签顺序错误"
21
- content = text[start + len("<CLUSTERS>"):end].strip()
24
+ content = text[start + len("<CLUSTERS>") : end].strip()
22
25
  else:
23
26
  content = match.group(1).strip()
24
-
27
+
25
28
  if not content:
26
29
  return None, "JSON 内容为空"
27
30
  try:
@@ -45,6 +48,7 @@ def try_parse_summary_report(text: str) -> tuple[Optional[object], Optional[str]
45
48
  """
46
49
  try:
47
50
  import re as _re
51
+
48
52
  # 使用正则表达式进行大小写不敏感的匹配
49
53
  pattern = r"<REPORT>([\s\S]*?)</REPORT>"
50
54
  match = _re.search(pattern, text, flags=_re.IGNORECASE)
@@ -54,10 +58,10 @@ def try_parse_summary_report(text: str) -> tuple[Optional[object], Optional[str]
54
58
  end = text.find("</REPORT>")
55
59
  if start == -1 or end == -1 or end <= start:
56
60
  return None, "未找到 <REPORT> 或 </REPORT> 标签,或标签顺序错误"
57
- content = text[start + len("<REPORT>"):end].strip()
61
+ content = text[start + len("<REPORT>") : end].strip()
58
62
  else:
59
63
  content = match.group(1).strip()
60
-
64
+
61
65
  if not content:
62
66
  return None, "JSON 内容为空"
63
67
  try:
@@ -70,4 +74,3 @@ def try_parse_summary_report(text: str) -> tuple[Optional[object], Optional[str]
70
74
  return None, f"JSON 解析结果不是字典或数组,而是 {type(data).__name__}"
71
75
  except Exception as e:
72
76
  return None, f"解析过程发生异常: {str(e)}"
73
-
@@ -1,6 +1,8 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """提示词构建模块"""
3
3
 
4
+ from jarvis.jarvis_utils.tag import ot
5
+
4
6
 
5
7
  def build_summary_prompt() -> str:
6
8
  """
@@ -121,7 +123,7 @@ def build_verification_summary_prompt() -> str:
121
123
 
122
124
  def get_review_system_prompt() -> str:
123
125
  """获取复核Agent的系统提示词"""
124
- return """
126
+ return f"""
125
127
  # 复核Agent约束
126
128
  - 你的核心任务是复核聚类Agent给出的无效结论是否充分和正确。
127
129
  - 你需要仔细检查聚类Agent提供的invalid_reason是否充分,是否真的考虑了所有可能的路径。
@@ -137,7 +139,7 @@ def get_review_system_prompt() -> str:
137
139
  - 必须验证聚类Agent是否真的确认了所有路径都有保护措施。
138
140
  - 如果发现聚类Agent遗漏了某些路径、调用者或边界情况,必须判定为理由不充分。
139
141
  - 保守策略:有疑问时,一律判定为理由不充分,将候选重新加入验证流程。
140
- - 完成复核后,主输出仅打印结束符 <!!!COMPLETE!!!> ,不需要汇总结果。
142
+ - 完成复核后,主输出仅打印结束符 {ot("!!!COMPLETE!!!")},不要输出其他任何内容。任务总结将会在后面的交互中被询问。
141
143
  """.strip()
142
144
 
143
145
 
@@ -265,4 +267,3 @@ def get_cluster_summary_prompt() -> str:
265
267
  ]
266
268
  </CLUSTERS>
267
269
  """.strip()
268
-
@@ -50,13 +50,17 @@ from __future__ import annotations
50
50
  import csv
51
51
  import hashlib
52
52
  import io
53
- from typing import Dict, List, Optional, Union
53
+ from typing import Any
54
+ from typing import Dict
55
+ from typing import List
56
+ from typing import Optional
57
+ from typing import Union
54
58
 
55
59
  # 依赖 Issue 结构,但本模块不直接导入 dataclass,接受 dict/Issue 两种形态
56
60
  try:
57
61
  from jarvis.jarvis_sec.types import Issue # 类型提示用,避免循环依赖
58
62
  except Exception:
59
- Issue = dict # type: ignore
63
+ Issue = Dict[str, Any] # type: ignore
60
64
 
61
65
 
62
66
  # ---------------------------
@@ -79,6 +83,7 @@ _SEVERITY_WEIGHT = {
79
83
  "low": 1.0,
80
84
  }
81
85
 
86
+
82
87
  def _as_dict(item: Union[Issue, Dict]) -> Dict:
83
88
  """
84
89
  将 Issue/dataclass 或 dict 统一为 dict。
@@ -112,7 +117,12 @@ def _normalize_issue(i: Dict) -> Dict:
112
117
  归一化字段并补充缺省值。
113
118
  """
114
119
  j = {
115
- "language": i.get("language", "c/cpp" if str(i.get("file", "")).endswith((".c", ".cpp", ".h", ".hpp")) else "rust"),
120
+ "language": i.get(
121
+ "language",
122
+ "c/cpp"
123
+ if str(i.get("file", "")).endswith((".c", ".cpp", ".h", ".hpp"))
124
+ else "rust",
125
+ ),
116
126
  "category": i.get("category", "error_handling"),
117
127
  "pattern": i.get("pattern", ""),
118
128
  "file": i.get("file", ""),
@@ -143,6 +153,7 @@ def _make_issue_id(base: str, lang: str) -> str:
143
153
  # 聚合与评分
144
154
  # ---------------------------
145
155
 
156
+
146
157
  def aggregate_issues(
147
158
  issues: List[Union[Issue, Dict]],
148
159
  scanned_root: Optional[str] = None,
@@ -153,7 +164,7 @@ def aggregate_issues(
153
164
  """
154
165
  # 归一化所有 issues
155
166
  normalized_items = [_normalize_issue(_as_dict(it)) for it in issues]
156
-
167
+
157
168
  # 去重:通过 gid 去重(如果存在),否则通过 file:line:category:pattern 去重
158
169
  # 保留第一个出现的 issue(因为 load_analysis_results 已经保留了最新的)
159
170
  seen_items: Dict[str, Dict] = {}
@@ -165,10 +176,10 @@ def aggregate_issues(
165
176
  else:
166
177
  # 如果没有 gid,使用 file:line:category:pattern 作为唯一标识
167
178
  key = f"{item.get('file')}:{item.get('line')}:{item.get('category')}:{item.get('pattern')}"
168
-
179
+
169
180
  if key not in seen_items:
170
181
  seen_items[key] = item
171
-
182
+
172
183
  items = list(seen_items.values())
173
184
 
174
185
  summary: Dict = {
@@ -206,6 +217,7 @@ def aggregate_issues(
206
217
  # Markdown 渲染
207
218
  # ---------------------------
208
219
 
220
+
209
221
  def format_markdown_report(report_json: Dict) -> str:
210
222
  """
211
223
  将聚合后的 JSON 报告渲染为 Markdown。
@@ -226,7 +238,9 @@ def format_markdown_report(report_json: Dict) -> str:
226
238
  # 概览
227
239
  lines.append("## 统计概览")
228
240
  by_lang = s.get("by_language", {})
229
- lines.append(f"- 按语言: c/cpp={by_lang.get('c/cpp', 0)}, rust={by_lang.get('rust', 0)}")
241
+ lines.append(
242
+ f"- 按语言: c/cpp={by_lang.get('c/cpp', 0)}, rust={by_lang.get('rust', 0)}"
243
+ )
230
244
  lines.append("- 按类别:")
231
245
  by_cat = s.get("by_category", {})
232
246
  for k in _CATEGORY_ORDER:
@@ -241,14 +255,18 @@ def format_markdown_report(report_json: Dict) -> str:
241
255
  # 详细问题
242
256
  lines.append("## 详细问题")
243
257
  for i, it in enumerate(issues, start=1):
244
- lines.append(f"### [{i}] {it.get('file')}:{it.get('line')} ({it.get('language')}, {it.get('category')})")
258
+ lines.append(
259
+ f"### [{i}] {it.get('file')}:{it.get('line')} ({it.get('language')}, {it.get('category')})"
260
+ )
245
261
  lines.append(f"- 模式: {it.get('pattern')}")
246
262
  lines.append(f"- 证据: `{it.get('evidence')}`")
247
263
  lines.append(f"- 前置条件: {it.get('preconditions')}")
248
264
  lines.append(f"- 触发路径: {it.get('trigger_path')}")
249
265
  lines.append(f"- 后果: {it.get('consequences')}")
250
266
  lines.append(f"- 建议: {it.get('suggestions')}")
251
- lines.append(f"- 置信度: {it.get('confidence')}, 严重性: {it.get('severity')}, 评分: {it.get('score')}")
267
+ lines.append(
268
+ f"- 置信度: {it.get('confidence')}, 严重性: {it.get('severity')}, 评分: {it.get('score')}"
269
+ )
252
270
  lines.append("")
253
271
 
254
272
  return "\n".join(lines)
@@ -259,7 +277,7 @@ def format_csv_report(report_json: Dict) -> str:
259
277
  将聚合后的 JSON 报告渲染为 CSV 格式。
260
278
  """
261
279
  issues: List[Dict] = report_json.get("issues", [])
262
-
280
+
263
281
  # 定义 CSV 列
264
282
  fieldnames = [
265
283
  "id",
@@ -277,19 +295,19 @@ def format_csv_report(report_json: Dict) -> str:
277
295
  "severity",
278
296
  "score",
279
297
  ]
280
-
298
+
281
299
  # 使用 StringIO 来构建 CSV 字符串
282
300
  output = io.StringIO()
283
- writer = csv.DictWriter(output, fieldnames=fieldnames, extrasaction='ignore')
284
-
301
+ writer = csv.DictWriter(output, fieldnames=fieldnames, extrasaction="ignore")
302
+
285
303
  # 写入表头
286
304
  writer.writeheader()
287
-
305
+
288
306
  # 写入数据行
289
307
  for it in issues:
290
308
  row = {field: str(it.get(field, "")) for field in fieldnames}
291
309
  writer.writerow(row)
292
-
310
+
293
311
  return output.getvalue()
294
312
 
295
313
 
@@ -303,26 +321,30 @@ def build_json_and_markdown(
303
321
  """
304
322
  一次性生成报告文本。
305
323
  如果 output_file 后缀为 .csv,则输出 CSV 格式;否则输出 Markdown 格式。
306
-
324
+
307
325
  Args:
308
326
  issues: 问题列表
309
327
  scanned_root: 扫描根目录
310
328
  scanned_files: 扫描文件数
311
329
  meta: 可选元数据
312
330
  output_file: 输出文件名(可选),用于判断输出格式
313
-
331
+
314
332
  Returns:
315
333
  报告文本(Markdown 或 CSV 格式)
316
334
  """
317
- report = aggregate_issues(issues, scanned_root=scanned_root, scanned_files=scanned_files)
335
+ report = aggregate_issues(
336
+ issues, scanned_root=scanned_root, scanned_files=scanned_files
337
+ )
318
338
  if meta is not None:
319
339
  try:
320
- report["meta"] = meta # 注入可选审计信息(仅用于JSON时保留,为兼容未来需要)
340
+ report["meta"] = (
341
+ meta # 注入可选审计信息(仅用于JSON时保留,为兼容未来需要)
342
+ )
321
343
  except Exception:
322
344
  pass
323
-
345
+
324
346
  # 根据输出文件名后缀选择格式
325
- if output_file and output_file.lower().endswith('.csv'):
347
+ if output_file and output_file.lower().endswith(".csv"):
326
348
  return format_csv_report(report)
327
349
  else:
328
350
  return format_markdown_report(report)
@@ -333,4 +355,4 @@ __all__ = [
333
355
  "format_markdown_report",
334
356
  "format_csv_report",
335
357
  "build_json_and_markdown",
336
- ]
358
+ ]