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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +567 -222
- jarvis/jarvis_agent/agent_manager.py +19 -12
- jarvis/jarvis_agent/builtin_input_handler.py +79 -11
- jarvis/jarvis_agent/config_editor.py +7 -2
- jarvis/jarvis_agent/event_bus.py +24 -13
- jarvis/jarvis_agent/events.py +19 -1
- jarvis/jarvis_agent/file_context_handler.py +67 -64
- jarvis/jarvis_agent/file_methodology_manager.py +38 -24
- jarvis/jarvis_agent/jarvis.py +186 -114
- jarvis/jarvis_agent/language_extractors/__init__.py +8 -1
- jarvis/jarvis_agent/language_extractors/c_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/cpp_extractor.py +9 -4
- jarvis/jarvis_agent/language_extractors/go_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/java_extractor.py +27 -20
- jarvis/jarvis_agent/language_extractors/javascript_extractor.py +22 -17
- jarvis/jarvis_agent/language_extractors/python_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/rust_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/typescript_extractor.py +22 -17
- jarvis/jarvis_agent/language_support_info.py +250 -219
- jarvis/jarvis_agent/main.py +19 -23
- jarvis/jarvis_agent/memory_manager.py +9 -6
- jarvis/jarvis_agent/methodology_share_manager.py +21 -15
- jarvis/jarvis_agent/output_handler.py +4 -2
- jarvis/jarvis_agent/prompt_builder.py +7 -6
- jarvis/jarvis_agent/prompt_manager.py +113 -8
- jarvis/jarvis_agent/prompts.py +317 -85
- jarvis/jarvis_agent/protocols.py +5 -2
- jarvis/jarvis_agent/run_loop.py +192 -32
- jarvis/jarvis_agent/session_manager.py +7 -3
- jarvis/jarvis_agent/share_manager.py +23 -13
- jarvis/jarvis_agent/shell_input_handler.py +12 -8
- jarvis/jarvis_agent/stdio_redirect.py +25 -26
- jarvis/jarvis_agent/task_analyzer.py +29 -23
- jarvis/jarvis_agent/task_list.py +869 -0
- jarvis/jarvis_agent/task_manager.py +26 -23
- jarvis/jarvis_agent/tool_executor.py +6 -5
- jarvis/jarvis_agent/tool_share_manager.py +24 -14
- jarvis/jarvis_agent/user_interaction.py +3 -3
- jarvis/jarvis_agent/utils.py +9 -1
- jarvis/jarvis_agent/web_bridge.py +37 -17
- jarvis/jarvis_agent/web_output_sink.py +5 -2
- jarvis/jarvis_agent/web_server.py +165 -36
- jarvis/jarvis_c2rust/__init__.py +1 -1
- jarvis/jarvis_c2rust/cli.py +260 -141
- jarvis/jarvis_c2rust/collector.py +37 -18
- jarvis/jarvis_c2rust/constants.py +60 -0
- jarvis/jarvis_c2rust/library_replacer.py +242 -1010
- jarvis/jarvis_c2rust/library_replacer_checkpoint.py +133 -0
- jarvis/jarvis_c2rust/library_replacer_llm.py +287 -0
- jarvis/jarvis_c2rust/library_replacer_loader.py +191 -0
- jarvis/jarvis_c2rust/library_replacer_output.py +134 -0
- jarvis/jarvis_c2rust/library_replacer_prompts.py +124 -0
- jarvis/jarvis_c2rust/library_replacer_utils.py +188 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +98 -1044
- jarvis/jarvis_c2rust/llm_module_agent_apply.py +170 -0
- jarvis/jarvis_c2rust/llm_module_agent_executor.py +288 -0
- jarvis/jarvis_c2rust/llm_module_agent_loader.py +170 -0
- jarvis/jarvis_c2rust/llm_module_agent_prompts.py +268 -0
- jarvis/jarvis_c2rust/llm_module_agent_types.py +57 -0
- jarvis/jarvis_c2rust/llm_module_agent_utils.py +150 -0
- jarvis/jarvis_c2rust/llm_module_agent_validator.py +119 -0
- jarvis/jarvis_c2rust/loaders.py +28 -10
- jarvis/jarvis_c2rust/models.py +5 -2
- jarvis/jarvis_c2rust/optimizer.py +192 -1974
- jarvis/jarvis_c2rust/optimizer_build_fix.py +286 -0
- jarvis/jarvis_c2rust/optimizer_clippy.py +766 -0
- jarvis/jarvis_c2rust/optimizer_config.py +49 -0
- jarvis/jarvis_c2rust/optimizer_docs.py +183 -0
- jarvis/jarvis_c2rust/optimizer_options.py +48 -0
- jarvis/jarvis_c2rust/optimizer_progress.py +469 -0
- jarvis/jarvis_c2rust/optimizer_report.py +52 -0
- jarvis/jarvis_c2rust/optimizer_unsafe.py +309 -0
- jarvis/jarvis_c2rust/optimizer_utils.py +469 -0
- jarvis/jarvis_c2rust/optimizer_visibility.py +185 -0
- jarvis/jarvis_c2rust/scanner.py +229 -166
- jarvis/jarvis_c2rust/transpiler.py +531 -2732
- jarvis/jarvis_c2rust/transpiler_agents.py +503 -0
- jarvis/jarvis_c2rust/transpiler_build.py +1294 -0
- jarvis/jarvis_c2rust/transpiler_codegen.py +204 -0
- jarvis/jarvis_c2rust/transpiler_compile.py +146 -0
- jarvis/jarvis_c2rust/transpiler_config.py +178 -0
- jarvis/jarvis_c2rust/transpiler_context.py +122 -0
- jarvis/jarvis_c2rust/transpiler_executor.py +516 -0
- jarvis/jarvis_c2rust/transpiler_generation.py +278 -0
- jarvis/jarvis_c2rust/transpiler_git.py +163 -0
- jarvis/jarvis_c2rust/transpiler_mod_utils.py +225 -0
- jarvis/jarvis_c2rust/transpiler_modules.py +336 -0
- jarvis/jarvis_c2rust/transpiler_planning.py +394 -0
- jarvis/jarvis_c2rust/transpiler_review.py +1196 -0
- jarvis/jarvis_c2rust/transpiler_symbols.py +176 -0
- jarvis/jarvis_c2rust/utils.py +269 -79
- jarvis/jarvis_code_agent/after_change.py +233 -0
- jarvis/jarvis_code_agent/build_validation_config.py +37 -30
- jarvis/jarvis_code_agent/builtin_rules.py +68 -0
- jarvis/jarvis_code_agent/code_agent.py +976 -1517
- jarvis/jarvis_code_agent/code_agent_build.py +227 -0
- jarvis/jarvis_code_agent/code_agent_diff.py +246 -0
- jarvis/jarvis_code_agent/code_agent_git.py +525 -0
- jarvis/jarvis_code_agent/code_agent_impact.py +177 -0
- jarvis/jarvis_code_agent/code_agent_lint.py +283 -0
- jarvis/jarvis_code_agent/code_agent_llm.py +159 -0
- jarvis/jarvis_code_agent/code_agent_postprocess.py +105 -0
- jarvis/jarvis_code_agent/code_agent_prompts.py +46 -0
- jarvis/jarvis_code_agent/code_agent_rules.py +305 -0
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +52 -48
- jarvis/jarvis_code_agent/code_analyzer/base_language.py +12 -10
- jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +12 -11
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +16 -12
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +26 -17
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +558 -104
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +27 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +22 -18
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +21 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +20 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +27 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +47 -23
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +71 -37
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +162 -35
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +111 -57
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +18 -12
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +185 -183
- jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +2 -1
- jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +24 -15
- jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +227 -141
- jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +321 -247
- jarvis/jarvis_code_agent/code_analyzer/language_registry.py +37 -29
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -13
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +15 -9
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +75 -45
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +87 -52
- jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +84 -51
- jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +94 -64
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +109 -71
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +97 -63
- jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +103 -69
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +271 -268
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +76 -64
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +92 -19
- jarvis/jarvis_code_agent/diff_visualizer.py +998 -0
- jarvis/jarvis_code_agent/lint.py +223 -524
- jarvis/jarvis_code_agent/rule_share_manager.py +158 -0
- jarvis/jarvis_code_agent/rules/clean_code.md +144 -0
- jarvis/jarvis_code_agent/rules/code_review.md +115 -0
- jarvis/jarvis_code_agent/rules/documentation.md +165 -0
- jarvis/jarvis_code_agent/rules/generate_rules.md +52 -0
- jarvis/jarvis_code_agent/rules/performance.md +158 -0
- jarvis/jarvis_code_agent/rules/refactoring.md +139 -0
- jarvis/jarvis_code_agent/rules/security.md +160 -0
- jarvis/jarvis_code_agent/rules/tdd.md +78 -0
- jarvis/jarvis_code_agent/test_rules/cpp_test.md +118 -0
- jarvis/jarvis_code_agent/test_rules/go_test.md +98 -0
- jarvis/jarvis_code_agent/test_rules/java_test.md +99 -0
- jarvis/jarvis_code_agent/test_rules/javascript_test.md +113 -0
- jarvis/jarvis_code_agent/test_rules/php_test.md +117 -0
- jarvis/jarvis_code_agent/test_rules/python_test.md +91 -0
- jarvis/jarvis_code_agent/test_rules/ruby_test.md +102 -0
- jarvis/jarvis_code_agent/test_rules/rust_test.md +86 -0
- jarvis/jarvis_code_agent/utils.py +36 -26
- jarvis/jarvis_code_analysis/checklists/loader.py +21 -21
- jarvis/jarvis_code_analysis/code_review.py +64 -33
- jarvis/jarvis_data/config_schema.json +285 -192
- jarvis/jarvis_git_squash/main.py +8 -6
- jarvis/jarvis_git_utils/git_commiter.py +53 -76
- jarvis/jarvis_mcp/__init__.py +5 -2
- jarvis/jarvis_mcp/sse_mcp_client.py +40 -30
- jarvis/jarvis_mcp/stdio_mcp_client.py +27 -19
- jarvis/jarvis_mcp/streamable_mcp_client.py +35 -26
- jarvis/jarvis_memory_organizer/memory_organizer.py +78 -55
- jarvis/jarvis_methodology/main.py +48 -39
- jarvis/jarvis_multi_agent/__init__.py +56 -23
- jarvis/jarvis_multi_agent/main.py +15 -18
- jarvis/jarvis_platform/base.py +179 -111
- jarvis/jarvis_platform/human.py +27 -16
- jarvis/jarvis_platform/kimi.py +52 -45
- jarvis/jarvis_platform/openai.py +101 -40
- jarvis/jarvis_platform/registry.py +51 -33
- jarvis/jarvis_platform/tongyi.py +68 -38
- jarvis/jarvis_platform/yuanbao.py +59 -43
- jarvis/jarvis_platform_manager/main.py +68 -76
- jarvis/jarvis_platform_manager/service.py +24 -14
- jarvis/jarvis_rag/README_CONFIG.md +314 -0
- jarvis/jarvis_rag/README_DYNAMIC_LOADING.md +311 -0
- jarvis/jarvis_rag/README_ONLINE_MODELS.md +230 -0
- jarvis/jarvis_rag/__init__.py +57 -4
- jarvis/jarvis_rag/cache.py +3 -1
- jarvis/jarvis_rag/cli.py +48 -68
- jarvis/jarvis_rag/embedding_interface.py +39 -0
- jarvis/jarvis_rag/embedding_manager.py +7 -230
- jarvis/jarvis_rag/embeddings/__init__.py +41 -0
- jarvis/jarvis_rag/embeddings/base.py +114 -0
- jarvis/jarvis_rag/embeddings/cohere.py +66 -0
- jarvis/jarvis_rag/embeddings/edgefn.py +117 -0
- jarvis/jarvis_rag/embeddings/local.py +260 -0
- jarvis/jarvis_rag/embeddings/openai.py +62 -0
- jarvis/jarvis_rag/embeddings/registry.py +293 -0
- jarvis/jarvis_rag/llm_interface.py +8 -6
- jarvis/jarvis_rag/query_rewriter.py +8 -9
- jarvis/jarvis_rag/rag_pipeline.py +61 -52
- jarvis/jarvis_rag/reranker.py +7 -75
- jarvis/jarvis_rag/reranker_interface.py +32 -0
- jarvis/jarvis_rag/rerankers/__init__.py +41 -0
- jarvis/jarvis_rag/rerankers/base.py +109 -0
- jarvis/jarvis_rag/rerankers/cohere.py +67 -0
- jarvis/jarvis_rag/rerankers/edgefn.py +140 -0
- jarvis/jarvis_rag/rerankers/jina.py +79 -0
- jarvis/jarvis_rag/rerankers/local.py +89 -0
- jarvis/jarvis_rag/rerankers/registry.py +293 -0
- jarvis/jarvis_rag/retriever.py +58 -43
- jarvis/jarvis_sec/__init__.py +66 -141
- jarvis/jarvis_sec/agents.py +21 -17
- jarvis/jarvis_sec/analysis.py +80 -33
- jarvis/jarvis_sec/checkers/__init__.py +7 -13
- jarvis/jarvis_sec/checkers/c_checker.py +356 -164
- jarvis/jarvis_sec/checkers/rust_checker.py +47 -29
- jarvis/jarvis_sec/cli.py +43 -21
- jarvis/jarvis_sec/clustering.py +430 -272
- jarvis/jarvis_sec/file_manager.py +99 -55
- jarvis/jarvis_sec/parsers.py +9 -6
- jarvis/jarvis_sec/prompts.py +4 -3
- jarvis/jarvis_sec/report.py +44 -22
- jarvis/jarvis_sec/review.py +180 -107
- jarvis/jarvis_sec/status.py +50 -41
- jarvis/jarvis_sec/types.py +3 -0
- jarvis/jarvis_sec/utils.py +160 -83
- jarvis/jarvis_sec/verification.py +411 -181
- jarvis/jarvis_sec/workflow.py +132 -21
- jarvis/jarvis_smart_shell/main.py +28 -41
- jarvis/jarvis_stats/cli.py +14 -12
- jarvis/jarvis_stats/stats.py +28 -19
- jarvis/jarvis_stats/storage.py +14 -8
- jarvis/jarvis_stats/visualizer.py +12 -7
- jarvis/jarvis_tools/base.py +5 -2
- jarvis/jarvis_tools/clear_memory.py +13 -9
- jarvis/jarvis_tools/cli/main.py +23 -18
- jarvis/jarvis_tools/edit_file.py +572 -873
- jarvis/jarvis_tools/execute_script.py +10 -7
- jarvis/jarvis_tools/file_analyzer.py +7 -8
- jarvis/jarvis_tools/meta_agent.py +287 -0
- jarvis/jarvis_tools/methodology.py +5 -3
- jarvis/jarvis_tools/read_code.py +305 -1438
- jarvis/jarvis_tools/read_symbols.py +50 -17
- jarvis/jarvis_tools/read_webpage.py +19 -18
- jarvis/jarvis_tools/registry.py +435 -156
- jarvis/jarvis_tools/retrieve_memory.py +16 -11
- jarvis/jarvis_tools/save_memory.py +8 -6
- jarvis/jarvis_tools/search_web.py +31 -31
- jarvis/jarvis_tools/sub_agent.py +32 -28
- jarvis/jarvis_tools/sub_code_agent.py +44 -60
- jarvis/jarvis_tools/task_list_manager.py +1811 -0
- jarvis/jarvis_tools/virtual_tty.py +29 -19
- jarvis/jarvis_utils/__init__.py +4 -0
- jarvis/jarvis_utils/builtin_replace_map.py +2 -1
- jarvis/jarvis_utils/clipboard.py +9 -8
- jarvis/jarvis_utils/collections.py +331 -0
- jarvis/jarvis_utils/config.py +699 -194
- jarvis/jarvis_utils/dialogue_recorder.py +294 -0
- jarvis/jarvis_utils/embedding.py +6 -3
- jarvis/jarvis_utils/file_processors.py +7 -1
- jarvis/jarvis_utils/fzf.py +9 -3
- jarvis/jarvis_utils/git_utils.py +71 -42
- jarvis/jarvis_utils/globals.py +116 -32
- jarvis/jarvis_utils/http.py +6 -2
- jarvis/jarvis_utils/input.py +318 -83
- jarvis/jarvis_utils/jsonnet_compat.py +119 -104
- jarvis/jarvis_utils/methodology.py +37 -28
- jarvis/jarvis_utils/output.py +201 -44
- jarvis/jarvis_utils/utils.py +986 -628
- {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/METADATA +49 -33
- jarvis_ai_assistant-1.0.2.dist-info/RECORD +304 -0
- jarvis/jarvis_code_agent/code_analyzer/structured_code.py +0 -556
- jarvis/jarvis_tools/generate_new_tool.py +0 -205
- jarvis/jarvis_tools/lsp_client.py +0 -1552
- jarvis/jarvis_tools/rewrite_file.py +0 -105
- jarvis_ai_assistant-0.7.8.dist-info/RECORD +0 -218
- {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
169
|
-
|
|
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
|
-
|
|
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(
|
|
283
|
-
|
|
301
|
+
existing["gids"] = sorted(
|
|
302
|
+
list(existing_gids | new_gids)
|
|
303
|
+
)
|
|
304
|
+
|
|
284
305
|
# 合并 verified_gids(去重)
|
|
285
|
-
existing_verified = set(
|
|
306
|
+
existing_verified = set(
|
|
307
|
+
existing.get("verified_gids", [])
|
|
308
|
+
)
|
|
286
309
|
new_verified = set(result.get("verified_gids", []))
|
|
287
|
-
existing["verified_gids"] = sorted(
|
|
288
|
-
|
|
310
|
+
existing["verified_gids"] = sorted(
|
|
311
|
+
list(existing_verified | new_verified)
|
|
312
|
+
)
|
|
313
|
+
|
|
289
314
|
# 合并 false_positive_gids(去重)
|
|
290
|
-
existing_false = set(
|
|
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(
|
|
293
|
-
|
|
319
|
+
existing["false_positive_gids"] = sorted(
|
|
320
|
+
list(existing_false | new_false)
|
|
321
|
+
)
|
|
322
|
+
|
|
294
323
|
# 合并 issues(通过 gid 去重)
|
|
295
|
-
existing_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
|
-
|
|
305
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
jarvis/jarvis_sec/parsers.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""解析模块 - 用于解析Agent返回的JSON格式摘要"""
|
|
3
3
|
|
|
4
|
-
from typing import List
|
|
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
|
-
|
jarvis/jarvis_sec/prompts.py
CHANGED
|
@@ -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
|
-
- 完成复核后,主输出仅打印结束符
|
|
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
|
-
|
jarvis/jarvis_sec/report.py
CHANGED
|
@@ -50,13 +50,17 @@ from __future__ import annotations
|
|
|
50
50
|
import csv
|
|
51
51
|
import hashlib
|
|
52
52
|
import io
|
|
53
|
-
from typing import
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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=
|
|
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(
|
|
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"] =
|
|
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(
|
|
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
|
+
]
|