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,268 @@
1
+ # -*- coding: utf-8 -*-
2
+ """提示词构建模块"""
3
+
4
+
5
+ def build_summary_prompt() -> str:
6
+ """
7
+ 构建摘要提示词:要求以 <REPORT>...</REPORT> 包裹的 JSON 输出(仅JSON)。
8
+ 系统提示词不强制规定主对话输出格式,仅在摘要中给出结构化结果。
9
+ """
10
+ return """
11
+ 请将本轮"安全子任务(单点验证)"的结构化结果仅放入以下标记中,并使用 JSON 数组对象形式输出。
12
+ 仅输出全局编号(gid)与详细理由(不含位置信息),gid 为全局唯一的数字编号。
13
+
14
+ 示例1:有告警的情况(has_risk: true,单个gid)
15
+ <REPORT>
16
+ [
17
+ {
18
+ "gid": 1,
19
+ "has_risk": true,
20
+ "preconditions": "输入字符串 src 的长度大于等于 dst 的缓冲区大小",
21
+ "trigger_path": "调用路径推导:main() -> handle_network_request() -> parse_packet() -> foobar() -> strcpy()。数据流:网络数据包通过 handle_network_request() 接收,传递给 parse_packet() 解析,parse_packet() 未对数据长度进行校验,直接将 src 传递给 foobar(),foobar() 调用 strcpy(dst, src) 时未检查 src 长度,可导致缓冲区溢出。关键调用点:parse_packet() 函数未对输入长度进行校验。",
22
+ "consequences": "缓冲区溢出,可能引发程序崩溃或任意代码执行",
23
+ "suggestions": "使用 strncpy_s 或其他安全的字符串复制函数"
24
+ }
25
+ ]
26
+ </REPORT>
27
+
28
+ 示例2:有告警的情况(has_risk: true,多个gid合并,路径和原因一致)
29
+ <REPORT>
30
+ [
31
+ {
32
+ "gids": [1, 2, 3],
33
+ "has_risk": true,
34
+ "preconditions": "输入字符串 src 的长度大于等于 dst 的缓冲区大小",
35
+ "trigger_path": "调用路径推导:main() -> handle_network_request() -> parse_packet() -> foobar() -> strcpy()。数据流:网络数据包通过 handle_network_request() 接收,传递给 parse_packet() 解析,parse_packet() 未对数据长度进行校验,直接将 src 传递给 foobar(),foobar() 调用 strcpy(dst, src) 时未检查 src 长度,可导致缓冲区溢出。关键调用点:parse_packet() 函数未对输入长度进行校验。",
36
+ "consequences": "缓冲区溢出,可能引发程序崩溃或任意代码执行",
37
+ "suggestions": "使用 strncpy_s 或其他安全的字符串复制函数"
38
+ }
39
+ ]
40
+ </REPORT>
41
+
42
+ 示例3:误报或无问题(返回空数组)
43
+ <REPORT>
44
+ []
45
+ </REPORT>
46
+
47
+ 要求:
48
+ - 只能在 <REPORT> 与 </REPORT> 中输出 JSON 数组,且不得出现其他文本。
49
+ - 若确认本批次全部为误报或无问题,请返回空数组 []。
50
+ - 数组元素为对象,包含字段:
51
+ - gid: 整数(全局唯一编号,单个告警时使用)
52
+ - gids: 整数数组(全局唯一编号数组,多个告警合并时使用)
53
+ - has_risk: 布尔值 (true/false),表示该项是否存在真实安全风险。
54
+ - preconditions: 字符串(触发漏洞的前置条件,仅当 has_risk 为 true 时必需)
55
+ - trigger_path: 字符串(漏洞的触发路径,必须包含完整的调用路径推导,包括:1) 可控输入的来源;2) 从输入源到缺陷代码的完整调用链(函数调用序列);3) 每个调用点的数据校验情况;4) 触发条件。格式示例:"调用路径推导:函数A() -> 函数B() -> 函数C() -> 缺陷代码。数据流:输入来源 -> 传递路径。关键调用点:函数B()未做校验。",仅当 has_risk 为 true 时必需)
56
+ - consequences: 字符串(漏洞被触发后可能导致的后果,仅当 has_risk 为 true 时必需)
57
+ - suggestions: 字符串(修复或缓解该漏洞的建议,仅当 has_risk 为 true 时必需)
58
+ - **合并格式优化**:如果多个告警(gid)的路径(trigger_path)、原因(preconditions/consequences/suggestions)完全一致,可以使用 gids 数组格式合并输出,减少重复内容。单个告警使用 gid,多个告警合并使用 gids。gid 和 gids 不能同时出现。
59
+ - 不要在数组元素中包含 file/line/pattern 等位置信息;写入 jsonl 时系统会结合原始候选信息。
60
+ - **关键**:仅当 `has_risk` 为 `true` 时,才会被记录为确认的问题。对于确认是误报的条目,请确保 `has_risk` 为 `false` 或不输出该条目。
61
+ - **输出格式**:有告警的条目必须包含所有字段(gid 或 gids, has_risk, preconditions, trigger_path, consequences, suggestions);无告警的条目只需包含 gid 和 has_risk。
62
+ - **调用路径推导要求**:trigger_path 字段必须包含完整的调用路径推导,不能省略或简化。必须明确说明从可控输入到缺陷代码的完整调用链,以及每个调用点的校验情况。如果无法推导出完整的调用路径,应该判定为误报(has_risk: false)。
63
+ - 支持jsonnet语法(如尾随逗号、注释、||| 或 ``` 分隔符多行字符串等)。
64
+ """.strip()
65
+
66
+
67
+ def build_verification_summary_prompt() -> str:
68
+ """
69
+ 构建验证 Agent 的摘要提示词:验证分析 Agent 给出的结论是否正确。
70
+ """
71
+ return """
72
+ 请将本轮"验证分析结论"的结构化结果仅放入以下标记中,并使用 JSON 数组对象形式输出。
73
+ 你需要验证分析 Agent 给出的结论是否正确,包括前置条件、触发路径、后果和建议是否合理。
74
+
75
+ 示例1:验证通过(is_valid: true,单个gid)
76
+ <REPORT>
77
+ [
78
+ {
79
+ "gid": 1,
80
+ "is_valid": true,
81
+ "verification_notes": "分析结论正确,前置条件合理,触发路径清晰,后果评估准确"
82
+ }
83
+ ]
84
+ </REPORT>
85
+
86
+ 示例2:验证通过(is_valid: true,多个gid合并)
87
+ <REPORT>
88
+ [
89
+ {
90
+ "gids": [1, 2, 3],
91
+ "is_valid": true,
92
+ "verification_notes": "分析结论正确,前置条件合理,触发路径清晰,后果评估准确"
93
+ }
94
+ ]
95
+ </REPORT>
96
+
97
+ 示例3:验证不通过(is_valid: false)
98
+ <REPORT>
99
+ [
100
+ {
101
+ "gid": 1,
102
+ "is_valid": false,
103
+ "verification_notes": "前置条件过于宽泛,实际代码中已有输入校验,触发路径不成立"
104
+ }
105
+ ]
106
+ </REPORT>
107
+
108
+ 要求:
109
+ - 只能在 <REPORT> 与 </REPORT> 中输出 JSON 数组,且不得出现其他文本。
110
+ - 数组元素为对象,包含字段:
111
+ - gid: 整数(全局唯一编号,对应分析 Agent 给出的 gid,单个告警时使用)
112
+ - gids: 整数数组(全局唯一编号数组,对应分析 Agent 给出的 gids,多个告警合并时使用)
113
+ - is_valid: 布尔值 (true/false),表示分析 Agent 的结论是否正确
114
+ - verification_notes: 字符串(验证说明,解释为什么结论正确或不正确)
115
+ - **合并格式优化**:如果多个告警(gid)的验证结果(is_valid)和验证说明(verification_notes)完全一致,可以使用 gids 数组格式合并输出,减少重复内容。单个告警使用 gid,多个告警合并使用 gids。gid 和 gids 不能同时出现。
116
+ - 必须对所有输入的 gid 进行验证,不能遗漏。
117
+ - 如果验证通过(is_valid: true),则保留该告警;如果验证不通过(is_valid: false),则视为误报,不记录为问题。
118
+ - 支持jsonnet语法(如尾随逗号、注释、||| 或 ``` 分隔符多行字符串等)。
119
+ """.strip()
120
+
121
+
122
+ def get_review_system_prompt() -> str:
123
+ """获取复核Agent的系统提示词"""
124
+ return """
125
+ # 复核Agent约束
126
+ - 你的核心任务是复核聚类Agent给出的无效结论是否充分和正确。
127
+ - 你需要仔细检查聚类Agent提供的invalid_reason是否充分,是否真的考虑了所有可能的路径。
128
+ - 工具优先:使用 read_code 读取目标文件附近源码(行号前后各 ~50 行),必要时用 execute_script 辅助检索。
129
+ - 必要时需向上追溯调用者,查看完整的调用路径,以确认聚类Agent的结论是否成立。
130
+ - 禁止修改任何文件或执行写操作命令;仅进行只读分析与读取。
131
+ - 每次仅执行一个操作;等待工具结果后再进行下一步。
132
+ - **记忆使用**:
133
+ - 在复核过程中,充分利用 retrieve_memory 工具检索已有的记忆,特别是与当前文件或函数相关的记忆。
134
+ - 这些记忆可能包含函数的分析要点、指针判空情况、输入校验情况、调用路径分析结果等。
135
+ - **复核原则**:
136
+ - 必须验证聚类Agent是否真的检查了所有可能的调用路径和调用者。
137
+ - 必须验证聚类Agent是否真的确认了所有路径都有保护措施。
138
+ - 如果发现聚类Agent遗漏了某些路径、调用者或边界情况,必须判定为理由不充分。
139
+ - 保守策略:有疑问时,一律判定为理由不充分,将候选重新加入验证流程。
140
+ - 完成复核后,主输出仅打印结束符 <!!!COMPLETE!!!> ,不需要汇总结果。
141
+ """.strip()
142
+
143
+
144
+ def get_review_summary_prompt() -> str:
145
+ """获取复核Agent的摘要提示词"""
146
+ return """
147
+ 请将本轮"复核结论"的结构化结果仅放入以下标记中,并使用 JSON 数组对象形式输出。
148
+ 你需要复核聚类Agent给出的无效理由是否充分,是否真的考虑了所有可能的路径。
149
+
150
+ 示例1:理由充分(is_reason_sufficient: true,单个gid)
151
+ <REPORT>
152
+ [
153
+ {
154
+ "gid": 1,
155
+ "is_reason_sufficient": true,
156
+ "review_notes": "聚类Agent已检查所有调用路径,确认所有调用者都有输入校验,理由充分"
157
+ }
158
+ ]
159
+ </REPORT>
160
+
161
+ 示例2:理由充分(is_reason_sufficient: true,多个gid合并)
162
+ <REPORT>
163
+ [
164
+ {
165
+ "gids": [1, 2, 3],
166
+ "is_reason_sufficient": true,
167
+ "review_notes": "聚类Agent已检查所有调用路径,确认所有调用者都有输入校验,理由充分"
168
+ }
169
+ ]
170
+ </REPORT>
171
+
172
+ 示例3:理由不充分(is_reason_sufficient: false)
173
+ <REPORT>
174
+ [
175
+ {
176
+ "gid": 1,
177
+ "is_reason_sufficient": false,
178
+ "review_notes": "聚类Agent遗漏了函数X的调用路径,该路径可能未做校验,理由不充分,需要重新验证"
179
+ }
180
+ ]
181
+ </REPORT>
182
+
183
+ 要求:
184
+ - 只能在 <REPORT> 与 </REPORT> 中输出 JSON 数组,且不得出现其他文本。
185
+ - 数组元素为对象,包含字段:
186
+ - gid: 整数(全局唯一编号,对应无效聚类的gid,单个告警时使用)
187
+ - gids: 整数数组(全局唯一编号数组,对应无效聚类的gids,多个告警合并时使用)
188
+ - is_reason_sufficient: 布尔值 (true/false),表示无效理由是否充分
189
+ - review_notes: 字符串(复核说明,解释为什么理由充分或不充分)
190
+ - **合并格式优化**:如果多个告警(gid)的复核结果(is_reason_sufficient)和复核说明(review_notes)完全一致,可以使用 gids 数组格式合并输出,减少重复内容。单个告警使用 gid,多个告警合并使用 gids。gid 和 gids 不能同时出现。
191
+ - 必须对所有输入的gid进行复核,不能遗漏。
192
+ - 如果理由不充分(is_reason_sufficient: false),该候选将重新加入验证流程;如果理由充分(is_reason_sufficient: true),该候选将被确认为无效。
193
+ - 支持jsonnet语法(如尾随逗号、注释、||| 或 ``` 分隔符多行字符串等)。
194
+ """.strip()
195
+
196
+
197
+ def get_cluster_system_prompt() -> str:
198
+ """获取聚类Agent的系统提示词"""
199
+ return """
200
+ # 单Agent聚类约束
201
+ - 你的任务是对同一文件内的启发式候选进行聚类,将可以一起验证的问题归为一类。
202
+ - **聚类原则**:
203
+ - 可以一起验证的问题归为一类,不一定是验证条件完全一致才能归为一类。
204
+ - 如果多个候选问题可以通过同一个验证过程来确认,即使它们的验证条件略有不同,也可以归为一类。
205
+ - 例如:多个指针解引用问题可以归为一类(验证"指针在解引用前非空"),即使它们涉及不同的指针变量。
206
+ - 例如:多个缓冲区操作问题可以归为一类(验证"拷贝长度不超过目标缓冲区容量"),即使它们涉及不同的缓冲区。
207
+ - 验证条件:为了确认是否存在漏洞需要成立/验证的关键前置条件。例如:"指针p在解引用前非空""拷贝长度不超过目标缓冲区容量"等。
208
+ - **完整性要求**:每个gid都必须出现在某个类别中,不能遗漏任何一个gid。所有输入的gid都必须被分类。
209
+ - 工具优先:如需核对上下文,可使用 read_code 读取相邻代码;避免过度遍历。
210
+ - 禁止写操作;仅只读分析。
211
+ - **重要:关于无效判断的保守策略**:
212
+ - 在判断候选是否无效时,必须充分考虑所有可能的路径、调用链和边界情况。
213
+ - 必须考虑:所有可能的调用者、所有可能的输入来源、所有可能的执行路径、所有可能的边界条件。
214
+ - 只要存在任何可能性(即使很小)导致漏洞可被触发,就不应该标记为无效(is_invalid: false)。
215
+ - 只有在完全确定、没有任何可能性、所有路径都已验证安全的情况下,才能标记为无效(is_invalid: true)。
216
+ - 保守原则:有疑问时,一律标记为 false(需要进入后续验证阶段),让分析Agent和验证Agent进行更深入的分析。
217
+ - 不要因为看到局部有保护措施就认为无效,要考虑是否有其他调用路径绕过这些保护。
218
+ - 不要因为看到某些调用者已做校验就认为无效,要考虑是否有其他调用者未做校验。
219
+ - **记忆使用**:
220
+ - 在聚类过程中,充分利用 retrieve_memory 工具检索已有的记忆,特别是与当前文件或函数相关的记忆。
221
+ - 如果有必要,使用 save_memory 工具保存聚类过程中发现的函数或代码片段的要点,使用函数名或文件名作为 tag。
222
+ - 记忆内容示例:某个函数的指针已经判空、某个函数已有输入校验、某个代码片段的上下文信息等。
223
+ - 这些记忆可以帮助后续的分析Agent和验证Agent更高效地工作。
224
+ """.strip()
225
+
226
+
227
+ def get_cluster_summary_prompt() -> str:
228
+ """获取聚类Agent的摘要提示词"""
229
+ return """
230
+ 请仅在 <CLUSTERS> 与 </CLUSTERS> 中输出 JSON 数组:
231
+ - 每个元素包含(所有字段均为必填):
232
+ - verification: 字符串(对该聚类的验证条件描述,简洁明确,可直接用于后续Agent验证)
233
+ - gids: 整数数组(候选的全局唯一编号;输入JSON每个元素含 gid,可直接对应填入)
234
+ - is_invalid: 布尔值(必填,true 或 false)。如果为 true,表示该聚类中的所有候选已被确认为无效/误报,将不会进入后续验证阶段;如果为 false,表示该聚类中的候选需要进入后续验证阶段。
235
+ - invalid_reason: 字符串(当 is_invalid 为 true 时必填,当 is_invalid 为 false 时可省略)。必须详细说明为什么这些候选是无效的,包括:
236
+ * 已检查的所有调用路径和调用者
237
+ * 已确认的保护措施和校验逻辑
238
+ * 为什么这些保护措施在所有路径上都有效
239
+ * 为什么不存在任何可能的触发路径
240
+ * 必须足够详细,以便复核Agent能够验证你的判断
241
+ - 要求:
242
+ - 严格要求:仅输出位于 <CLUSTERS> 与 </CLUSTERS> 间的 JSON 数组,其他位置不输出任何文本
243
+ - **完整性要求(最重要)**:输入JSON中的所有gid都必须被分类,不能遗漏任何一个gid。所有gid必须出现在某个聚类的gids数组中。这是强制要求,必须严格遵守。
244
+ - **聚类原则**:可以一起验证的问题归为一类,不一定是验证条件完全一致才能归为一类。如果多个候选问题可以通过同一个验证过程来确认,即使它们的验证条件略有不同,也可以归为一类。
245
+ - **必须要求**:每个聚类元素必须包含 is_invalid 字段,且值必须为 true 或 false,不能省略。
246
+ - **必须要求**:当 is_invalid 为 true 时,必须提供 invalid_reason 字段,且理由必须充分详细。
247
+ - 不需要解释与长文本,仅给出可执行的验证条件短句
248
+ - 若无法聚类,请将每个候选单独成组,verification 为该候选的最小确认条件
249
+ - **关于 is_invalid 的保守判断原则**:
250
+ - 必须充分考虑所有可能的路径、调用链、输入来源和边界情况。
251
+ - 只要存在任何可能性(即使很小)导致漏洞可被触发,必须设置 is_invalid: false。
252
+ - 只有在完全确定、没有任何可能性、所有路径都已验证安全的情况下,才能设置 is_invalid: true。
253
+ - 保守策略:有疑问时,一律设置为 false,让后续的分析Agent和验证Agent进行更深入的分析。
254
+ - 不要因为局部有保护措施就设置为 true,要考虑是否有其他路径绕过保护。
255
+ - 不要因为某些调用者已做校验就设置为 true,要考虑是否有其他调用者未做校验。
256
+ - 如果设置为 true,必须在 invalid_reason 中详细说明已检查的所有路径和原因。
257
+ - 支持jsonnet语法(如尾随逗号、注释、||| 或 ``` 分隔符多行字符串等)。
258
+ <CLUSTERS>
259
+ [
260
+ {
261
+ "verification": "",
262
+ "gids": [],
263
+ "is_invalid": false
264
+ }
265
+ ]
266
+ </CLUSTERS>
267
+ """.strip()
268
+
@@ -40,12 +40,16 @@
40
40
  提供的函数:
41
41
  - aggregate_issues(issues: List[Union[Issue, Dict]], scanned_root: Optional[str] = None, scanned_files: Optional[int] = None) -> Dict
42
42
  - format_markdown_report(report_json: Dict) -> str
43
- - build_json_and_markdown(issues: List[Union[Issue, Dict]], scanned_root: Optional[str] = None, scanned_files: Optional[int] = None) -> str
43
+ - format_csv_report(report_json: Dict) -> str
44
+ - build_json_and_markdown(issues: List[Union[Issue, Dict]], scanned_root: Optional[str] = None, scanned_files: Optional[int] = None, output_file: Optional[str] = None) -> str
45
+ - 如果 output_file 后缀为 .csv,则输出 CSV 格式;否则输出 Markdown 格式
44
46
  """
45
47
 
46
48
  from __future__ import annotations
47
49
 
50
+ import csv
48
51
  import hashlib
52
+ import io
49
53
  from typing import Dict, List, Optional, Union
50
54
 
51
55
  # 依赖 Issue 结构,但本模块不直接导入 dataclass,接受 dict/Issue 两种形态
@@ -147,7 +151,25 @@ def aggregate_issues(
147
151
  """
148
152
  聚合问题列表并生成 JSON 报告。
149
153
  """
150
- items = [_normalize_issue(_as_dict(it)) for it in issues]
154
+ # 归一化所有 issues
155
+ normalized_items = [_normalize_issue(_as_dict(it)) for it in issues]
156
+
157
+ # 去重:通过 gid 去重(如果存在),否则通过 file:line:category:pattern 去重
158
+ # 保留第一个出现的 issue(因为 load_analysis_results 已经保留了最新的)
159
+ seen_items: Dict[str, Dict] = {}
160
+ for item in normalized_items:
161
+ # 优先使用 gid 作为唯一标识
162
+ gid = item.get("gid")
163
+ if gid:
164
+ key = f"gid:{gid}"
165
+ else:
166
+ # 如果没有 gid,使用 file:line:category:pattern 作为唯一标识
167
+ key = f"{item.get('file')}:{item.get('line')}:{item.get('category')}:{item.get('pattern')}"
168
+
169
+ if key not in seen_items:
170
+ seen_items[key] = item
171
+
172
+ items = list(seen_items.values())
151
173
 
152
174
  summary: Dict = {
153
175
  "total": len(items),
@@ -232,14 +254,65 @@ def format_markdown_report(report_json: Dict) -> str:
232
254
  return "\n".join(lines)
233
255
 
234
256
 
257
+ def format_csv_report(report_json: Dict) -> str:
258
+ """
259
+ 将聚合后的 JSON 报告渲染为 CSV 格式。
260
+ """
261
+ issues: List[Dict] = report_json.get("issues", [])
262
+
263
+ # 定义 CSV 列
264
+ fieldnames = [
265
+ "id",
266
+ "language",
267
+ "category",
268
+ "pattern",
269
+ "file",
270
+ "line",
271
+ "evidence",
272
+ "preconditions",
273
+ "trigger_path",
274
+ "consequences",
275
+ "suggestions",
276
+ "confidence",
277
+ "severity",
278
+ "score",
279
+ ]
280
+
281
+ # 使用 StringIO 来构建 CSV 字符串
282
+ output = io.StringIO()
283
+ writer = csv.DictWriter(output, fieldnames=fieldnames, extrasaction='ignore')
284
+
285
+ # 写入表头
286
+ writer.writeheader()
287
+
288
+ # 写入数据行
289
+ for it in issues:
290
+ row = {field: str(it.get(field, "")) for field in fieldnames}
291
+ writer.writerow(row)
292
+
293
+ return output.getvalue()
294
+
295
+
235
296
  def build_json_and_markdown(
236
297
  issues: List[Union[Issue, Dict]],
237
298
  scanned_root: Optional[str] = None,
238
299
  scanned_files: Optional[int] = None,
239
300
  meta: Optional[List[Dict]] = None,
301
+ output_file: Optional[str] = None,
240
302
  ) -> str:
241
303
  """
242
- 一次性生成报告文本(仅 Markdown)。
304
+ 一次性生成报告文本。
305
+ 如果 output_file 后缀为 .csv,则输出 CSV 格式;否则输出 Markdown 格式。
306
+
307
+ Args:
308
+ issues: 问题列表
309
+ scanned_root: 扫描根目录
310
+ scanned_files: 扫描文件数
311
+ meta: 可选元数据
312
+ output_file: 输出文件名(可选),用于判断输出格式
313
+
314
+ Returns:
315
+ 报告文本(Markdown 或 CSV 格式)
243
316
  """
244
317
  report = aggregate_issues(issues, scanned_root=scanned_root, scanned_files=scanned_files)
245
318
  if meta is not None:
@@ -247,11 +320,17 @@ def build_json_and_markdown(
247
320
  report["meta"] = meta # 注入可选审计信息(仅用于JSON时保留,为兼容未来需要)
248
321
  except Exception:
249
322
  pass
250
- return format_markdown_report(report)
323
+
324
+ # 根据输出文件名后缀选择格式
325
+ if output_file and output_file.lower().endswith('.csv'):
326
+ return format_csv_report(report)
327
+ else:
328
+ return format_markdown_report(report)
251
329
 
252
330
 
253
331
  __all__ = [
254
332
  "aggregate_issues",
255
333
  "format_markdown_report",
334
+ "format_csv_report",
256
335
  "build_json_and_markdown",
257
336
  ]