jarvis-ai-assistant 0.3.30__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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +458 -152
- jarvis/jarvis_agent/agent_manager.py +17 -13
- jarvis/jarvis_agent/builtin_input_handler.py +2 -6
- jarvis/jarvis_agent/config_editor.py +2 -7
- jarvis/jarvis_agent/event_bus.py +82 -12
- jarvis/jarvis_agent/file_context_handler.py +329 -0
- jarvis/jarvis_agent/file_methodology_manager.py +3 -4
- jarvis/jarvis_agent/jarvis.py +628 -55
- jarvis/jarvis_agent/language_extractors/__init__.py +57 -0
- jarvis/jarvis_agent/language_extractors/c_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/cpp_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/go_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/java_extractor.py +84 -0
- jarvis/jarvis_agent/language_extractors/javascript_extractor.py +79 -0
- jarvis/jarvis_agent/language_extractors/python_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/rust_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/typescript_extractor.py +84 -0
- jarvis/jarvis_agent/language_support_info.py +486 -0
- jarvis/jarvis_agent/main.py +34 -10
- jarvis/jarvis_agent/memory_manager.py +7 -16
- jarvis/jarvis_agent/methodology_share_manager.py +10 -16
- jarvis/jarvis_agent/prompt_manager.py +1 -1
- jarvis/jarvis_agent/prompts.py +193 -171
- jarvis/jarvis_agent/protocols.py +8 -12
- jarvis/jarvis_agent/run_loop.py +105 -9
- jarvis/jarvis_agent/session_manager.py +2 -3
- jarvis/jarvis_agent/share_manager.py +20 -22
- jarvis/jarvis_agent/shell_input_handler.py +1 -2
- jarvis/jarvis_agent/stdio_redirect.py +295 -0
- jarvis/jarvis_agent/task_analyzer.py +31 -6
- jarvis/jarvis_agent/task_manager.py +11 -27
- jarvis/jarvis_agent/tool_executor.py +2 -3
- jarvis/jarvis_agent/tool_share_manager.py +12 -24
- jarvis/jarvis_agent/utils.py +5 -1
- jarvis/jarvis_agent/web_bridge.py +189 -0
- jarvis/jarvis_agent/web_output_sink.py +53 -0
- jarvis/jarvis_agent/web_server.py +786 -0
- jarvis/jarvis_c2rust/__init__.py +26 -0
- jarvis/jarvis_c2rust/cli.py +575 -0
- jarvis/jarvis_c2rust/collector.py +250 -0
- jarvis/jarvis_c2rust/constants.py +26 -0
- jarvis/jarvis_c2rust/library_replacer.py +1254 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +1272 -0
- jarvis/jarvis_c2rust/loaders.py +207 -0
- jarvis/jarvis_c2rust/models.py +28 -0
- jarvis/jarvis_c2rust/optimizer.py +2157 -0
- jarvis/jarvis_c2rust/scanner.py +1681 -0
- jarvis/jarvis_c2rust/transpiler.py +2983 -0
- jarvis/jarvis_c2rust/utils.py +385 -0
- jarvis/jarvis_code_agent/build_validation_config.py +132 -0
- jarvis/jarvis_code_agent/code_agent.py +1371 -220
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +65 -0
- jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +106 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +74 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +72 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +70 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +53 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +47 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +61 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +110 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +154 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +110 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +153 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +648 -0
- jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
- jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
- jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
- jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
- jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +110 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +49 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +299 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +215 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +212 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +254 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +269 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +281 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +280 -0
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +605 -0
- jarvis/jarvis_code_agent/code_analyzer/structured_code.py +556 -0
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +252 -0
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +58 -0
- jarvis/jarvis_code_agent/lint.py +501 -8
- jarvis/jarvis_code_agent/utils.py +141 -0
- jarvis/jarvis_code_analysis/code_review.py +493 -584
- jarvis/jarvis_data/config_schema.json +128 -12
- jarvis/jarvis_git_squash/main.py +4 -5
- jarvis/jarvis_git_utils/git_commiter.py +82 -75
- jarvis/jarvis_mcp/sse_mcp_client.py +22 -29
- jarvis/jarvis_mcp/stdio_mcp_client.py +12 -13
- jarvis/jarvis_mcp/streamable_mcp_client.py +15 -14
- jarvis/jarvis_memory_organizer/memory_organizer.py +55 -74
- jarvis/jarvis_methodology/main.py +32 -48
- jarvis/jarvis_multi_agent/__init__.py +287 -55
- jarvis/jarvis_multi_agent/main.py +36 -4
- jarvis/jarvis_platform/base.py +524 -202
- jarvis/jarvis_platform/human.py +7 -8
- jarvis/jarvis_platform/kimi.py +30 -36
- jarvis/jarvis_platform/openai.py +88 -25
- jarvis/jarvis_platform/registry.py +26 -10
- jarvis/jarvis_platform/tongyi.py +24 -25
- jarvis/jarvis_platform/yuanbao.py +32 -43
- jarvis/jarvis_platform_manager/main.py +66 -77
- jarvis/jarvis_platform_manager/service.py +8 -13
- jarvis/jarvis_rag/cli.py +53 -55
- jarvis/jarvis_rag/embedding_manager.py +13 -18
- jarvis/jarvis_rag/llm_interface.py +8 -9
- jarvis/jarvis_rag/query_rewriter.py +10 -21
- jarvis/jarvis_rag/rag_pipeline.py +24 -27
- jarvis/jarvis_rag/reranker.py +4 -5
- jarvis/jarvis_rag/retriever.py +28 -30
- jarvis/jarvis_sec/__init__.py +305 -0
- jarvis/jarvis_sec/agents.py +143 -0
- jarvis/jarvis_sec/analysis.py +276 -0
- jarvis/jarvis_sec/checkers/__init__.py +32 -0
- jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
- jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
- jarvis/jarvis_sec/cli.py +139 -0
- jarvis/jarvis_sec/clustering.py +1439 -0
- jarvis/jarvis_sec/file_manager.py +427 -0
- jarvis/jarvis_sec/parsers.py +73 -0
- jarvis/jarvis_sec/prompts.py +268 -0
- jarvis/jarvis_sec/report.py +336 -0
- jarvis/jarvis_sec/review.py +453 -0
- jarvis/jarvis_sec/status.py +264 -0
- jarvis/jarvis_sec/types.py +20 -0
- jarvis/jarvis_sec/utils.py +499 -0
- jarvis/jarvis_sec/verification.py +848 -0
- jarvis/jarvis_sec/workflow.py +226 -0
- jarvis/jarvis_smart_shell/main.py +38 -87
- jarvis/jarvis_stats/cli.py +2 -2
- jarvis/jarvis_stats/stats.py +8 -8
- jarvis/jarvis_stats/storage.py +15 -21
- jarvis/jarvis_stats/visualizer.py +1 -1
- jarvis/jarvis_tools/clear_memory.py +3 -20
- jarvis/jarvis_tools/cli/main.py +21 -23
- jarvis/jarvis_tools/edit_file.py +1019 -132
- jarvis/jarvis_tools/execute_script.py +83 -25
- jarvis/jarvis_tools/file_analyzer.py +6 -9
- jarvis/jarvis_tools/generate_new_tool.py +14 -21
- jarvis/jarvis_tools/lsp_client.py +1552 -0
- jarvis/jarvis_tools/methodology.py +2 -3
- jarvis/jarvis_tools/read_code.py +1736 -35
- jarvis/jarvis_tools/read_symbols.py +140 -0
- jarvis/jarvis_tools/read_webpage.py +12 -13
- jarvis/jarvis_tools/registry.py +427 -200
- jarvis/jarvis_tools/retrieve_memory.py +20 -19
- jarvis/jarvis_tools/rewrite_file.py +72 -158
- jarvis/jarvis_tools/save_memory.py +3 -15
- jarvis/jarvis_tools/search_web.py +18 -18
- jarvis/jarvis_tools/sub_agent.py +36 -43
- jarvis/jarvis_tools/sub_code_agent.py +25 -26
- jarvis/jarvis_tools/virtual_tty.py +55 -33
- jarvis/jarvis_utils/clipboard.py +7 -10
- jarvis/jarvis_utils/config.py +232 -45
- jarvis/jarvis_utils/embedding.py +8 -5
- jarvis/jarvis_utils/fzf.py +8 -8
- jarvis/jarvis_utils/git_utils.py +225 -36
- jarvis/jarvis_utils/globals.py +3 -3
- jarvis/jarvis_utils/http.py +1 -1
- jarvis/jarvis_utils/input.py +99 -48
- jarvis/jarvis_utils/jsonnet_compat.py +465 -0
- jarvis/jarvis_utils/methodology.py +52 -48
- jarvis/jarvis_utils/utils.py +819 -491
- jarvis_ai_assistant-0.7.6.dist-info/METADATA +600 -0
- jarvis_ai_assistant-0.7.6.dist-info/RECORD +218 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/entry_points.txt +4 -0
- jarvis/jarvis_agent/config.py +0 -92
- jarvis/jarvis_agent/edit_file_handler.py +0 -296
- jarvis/jarvis_platform/ai8.py +0 -332
- jarvis/jarvis_tools/ask_user.py +0 -54
- jarvis_ai_assistant-0.3.30.dist-info/METADATA +0 -381
- jarvis_ai_assistant-0.3.30.dist-info/RECORD +0 -137
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""工具函数模块"""
|
|
3
|
+
|
|
4
|
+
from typing import Dict, List, Optional
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import json
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from jarvis.jarvis_sec.workflow import direct_scan
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def git_restore_if_dirty(repo_root: str) -> int:
|
|
13
|
+
"""
|
|
14
|
+
若 repo_root 为 git 仓库:检测工作区是否有变更;如有则使用 'git checkout -- .' 恢复。
|
|
15
|
+
返回估算的变更文件数(基于 git status --porcelain 的行数)。
|
|
16
|
+
"""
|
|
17
|
+
try:
|
|
18
|
+
import subprocess as _sub
|
|
19
|
+
root = Path(repo_root)
|
|
20
|
+
if not (root / ".git").exists():
|
|
21
|
+
return 0
|
|
22
|
+
proc = _sub.run(["git", "status", "--porcelain"], cwd=str(root), capture_output=True, text=True)
|
|
23
|
+
if proc.returncode != 0:
|
|
24
|
+
return 0
|
|
25
|
+
lines = [line for line in proc.stdout.splitlines() if line.strip()]
|
|
26
|
+
if lines:
|
|
27
|
+
_sub.run(["git", "checkout", "--", "."], cwd=str(root), capture_output=True, text=True)
|
|
28
|
+
return len(lines)
|
|
29
|
+
except Exception:
|
|
30
|
+
pass
|
|
31
|
+
return 0
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_sec_dir(base_path: str) -> Path:
|
|
35
|
+
"""获取 .jarvis/sec 目录路径,支持 base_path 是项目根目录或已经是 .jarvis/sec 目录"""
|
|
36
|
+
base = Path(base_path)
|
|
37
|
+
# 检查 base_path 是否已经是 .jarvis/sec 目录
|
|
38
|
+
if base.name == "sec" and base.parent.name == ".jarvis":
|
|
39
|
+
return base
|
|
40
|
+
# 否则,假设 base_path 是项目根目录
|
|
41
|
+
return base / ".jarvis" / "sec"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def initialize_analysis_context(
|
|
45
|
+
entry_path: str,
|
|
46
|
+
status_mgr,
|
|
47
|
+
) -> tuple:
|
|
48
|
+
"""
|
|
49
|
+
初始化分析上下文,包括状态管理、进度文件、目录等。
|
|
50
|
+
|
|
51
|
+
返回: (sec_dir, progress_path, _progress_append)
|
|
52
|
+
"""
|
|
53
|
+
# 获取 .jarvis/sec 目录
|
|
54
|
+
sec_dir = get_sec_dir(entry_path)
|
|
55
|
+
progress_path = None # 不再使用 progress.jsonl
|
|
56
|
+
|
|
57
|
+
# 进度追加函数(空函数,不再记录)
|
|
58
|
+
def _progress_append(rec: Dict) -> None:
|
|
59
|
+
pass # 不再记录进度日志
|
|
60
|
+
|
|
61
|
+
return sec_dir, progress_path, _progress_append
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def load_or_run_heuristic_scan(
|
|
65
|
+
entry_path: str,
|
|
66
|
+
langs: List[str],
|
|
67
|
+
exclude_dirs: Optional[List[str]],
|
|
68
|
+
sec_dir: Path,
|
|
69
|
+
status_mgr,
|
|
70
|
+
_progress_append,
|
|
71
|
+
) -> tuple[List[Dict], Dict]:
|
|
72
|
+
"""
|
|
73
|
+
加载或运行启发式扫描。
|
|
74
|
+
|
|
75
|
+
优先从新的 candidates.jsonl 文件加载,如果不存在则回退到旧的 heuristic_issues.jsonl。
|
|
76
|
+
|
|
77
|
+
返回: (candidates, summary)
|
|
78
|
+
"""
|
|
79
|
+
candidates: List[Dict] = []
|
|
80
|
+
summary: Dict = {}
|
|
81
|
+
|
|
82
|
+
# 优先使用新的 candidates.jsonl 文件
|
|
83
|
+
from jarvis.jarvis_sec.file_manager import load_candidates, get_candidates_file
|
|
84
|
+
candidates = load_candidates(sec_dir)
|
|
85
|
+
|
|
86
|
+
if candidates:
|
|
87
|
+
try:
|
|
88
|
+
typer.secho(f"[jarvis-sec] 从 {get_candidates_file(sec_dir)} 恢复启发式扫描", fg=typer.colors.BLUE)
|
|
89
|
+
_progress_append({
|
|
90
|
+
"event": "pre_scan_resumed",
|
|
91
|
+
"path": str(get_candidates_file(sec_dir)),
|
|
92
|
+
"issues_found": len(candidates)
|
|
93
|
+
})
|
|
94
|
+
except Exception:
|
|
95
|
+
pass
|
|
96
|
+
else:
|
|
97
|
+
# 回退到旧的 heuristic_issues.jsonl 文件(向后兼容)
|
|
98
|
+
_heuristic_path = sec_dir / "heuristic_issues.jsonl"
|
|
99
|
+
if _heuristic_path.exists():
|
|
100
|
+
try:
|
|
101
|
+
typer.secho(f"[jarvis-sec] 从 {_heuristic_path} 恢复启发式扫描(旧格式)", fg=typer.colors.BLUE)
|
|
102
|
+
with _heuristic_path.open("r", encoding="utf-8") as f:
|
|
103
|
+
for line in f:
|
|
104
|
+
if line.strip():
|
|
105
|
+
candidates.append(json.loads(line))
|
|
106
|
+
_progress_append({
|
|
107
|
+
"event": "pre_scan_resumed",
|
|
108
|
+
"path": str(_heuristic_path),
|
|
109
|
+
"issues_found": len(candidates)
|
|
110
|
+
})
|
|
111
|
+
except Exception as e:
|
|
112
|
+
typer.secho(f"[jarvis-sec] 恢复启发式扫描失败,执行完整扫描: {e}", fg=typer.colors.YELLOW)
|
|
113
|
+
candidates = [] # 重置以便执行完整扫描
|
|
114
|
+
|
|
115
|
+
if not candidates:
|
|
116
|
+
_progress_append({"event": "pre_scan_start", "entry_path": entry_path, "languages": langs})
|
|
117
|
+
status_mgr.update_pre_scan(message="开始启发式扫描...")
|
|
118
|
+
pre_scan = direct_scan(entry_path, languages=langs, exclude_dirs=exclude_dirs)
|
|
119
|
+
candidates = pre_scan.get("issues", [])
|
|
120
|
+
summary = pre_scan.get("summary", {})
|
|
121
|
+
scanned_files = summary.get("scanned_files", 0)
|
|
122
|
+
status_mgr.update_pre_scan(
|
|
123
|
+
current_files=scanned_files,
|
|
124
|
+
total_files=scanned_files,
|
|
125
|
+
issues_found=len(candidates),
|
|
126
|
+
message=f"启发式扫描完成,发现 {len(candidates)} 个候选问题"
|
|
127
|
+
)
|
|
128
|
+
_progress_append({
|
|
129
|
+
"event": "pre_scan_done",
|
|
130
|
+
"entry_path": entry_path,
|
|
131
|
+
"languages": langs,
|
|
132
|
+
"scanned_files": scanned_files,
|
|
133
|
+
"issues_found": len(candidates)
|
|
134
|
+
})
|
|
135
|
+
# 持久化
|
|
136
|
+
try:
|
|
137
|
+
_heuristic_path.parent.mkdir(parents=True, exist_ok=True)
|
|
138
|
+
with _heuristic_path.open("w", encoding="utf-8") as f:
|
|
139
|
+
for item in candidates:
|
|
140
|
+
f.write(json.dumps(item, ensure_ascii=False) + "\n")
|
|
141
|
+
_progress_append({
|
|
142
|
+
"event": "heuristic_report_written",
|
|
143
|
+
"path": str(_heuristic_path),
|
|
144
|
+
"issues_count": len(candidates),
|
|
145
|
+
})
|
|
146
|
+
typer.secho(f"[jarvis-sec] 已将 {len(candidates)} 个启发式扫描问题写入 {_heuristic_path}", fg=typer.colors.GREEN)
|
|
147
|
+
except Exception:
|
|
148
|
+
pass
|
|
149
|
+
else:
|
|
150
|
+
# 从断点恢复启发式扫描结果
|
|
151
|
+
status_mgr.update_pre_scan(
|
|
152
|
+
issues_found=len(candidates),
|
|
153
|
+
message=f"从断点恢复,已发现 {len(candidates)} 个候选问题"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return candidates, summary
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def compact_candidate(it: Dict) -> Dict:
|
|
160
|
+
"""精简候选问题,只保留必要字段"""
|
|
161
|
+
result = {
|
|
162
|
+
"language": it.get("language"),
|
|
163
|
+
"category": it.get("category"),
|
|
164
|
+
"pattern": it.get("pattern"),
|
|
165
|
+
"file": it.get("file"),
|
|
166
|
+
"line": it.get("line"),
|
|
167
|
+
"evidence": it.get("evidence"),
|
|
168
|
+
"confidence": it.get("confidence"),
|
|
169
|
+
"severity": it.get("severity", "medium"),
|
|
170
|
+
}
|
|
171
|
+
# 如果候选已经有gid,保留它(用于断点恢复)
|
|
172
|
+
if "gid" in it:
|
|
173
|
+
try:
|
|
174
|
+
gid_val = int(it.get("gid", 0))
|
|
175
|
+
if gid_val >= 1:
|
|
176
|
+
result["gid"] = gid_val
|
|
177
|
+
except Exception:
|
|
178
|
+
pass
|
|
179
|
+
return result
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def prepare_candidates(candidates: List[Dict]) -> List[Dict]:
|
|
183
|
+
"""
|
|
184
|
+
将候选问题精简为子任务清单,控制上下文长度,并分配全局唯一ID。
|
|
185
|
+
|
|
186
|
+
返回: compact_candidates (已分配gid的候选列表)
|
|
187
|
+
"""
|
|
188
|
+
compact_candidates = [compact_candidate(it) for it in candidates]
|
|
189
|
+
|
|
190
|
+
# 检查是否所有候选都已经有gid(从heuristic_issues.jsonl恢复时)
|
|
191
|
+
all_have_gid = all("gid" in it and isinstance(it.get("gid"), int) and it.get("gid", 0) >= 1 for it in compact_candidates)
|
|
192
|
+
|
|
193
|
+
if not all_have_gid:
|
|
194
|
+
# 如果有候选没有gid,需要分配
|
|
195
|
+
# 优先保留已有的gid,为没有gid的候选分配新的gid
|
|
196
|
+
existing_gids = set()
|
|
197
|
+
for it in compact_candidates:
|
|
198
|
+
try:
|
|
199
|
+
gid_val = int(it.get("gid", 0))
|
|
200
|
+
if gid_val >= 1:
|
|
201
|
+
existing_gids.add(gid_val)
|
|
202
|
+
except Exception:
|
|
203
|
+
pass
|
|
204
|
+
|
|
205
|
+
# 为没有gid的候选分配新的gid
|
|
206
|
+
next_gid = 1
|
|
207
|
+
for it in compact_candidates:
|
|
208
|
+
if "gid" not in it or not isinstance(it.get("gid"), int) or it.get("gid", 0) < 1:
|
|
209
|
+
# 找到一个未使用的gid
|
|
210
|
+
while next_gid in existing_gids:
|
|
211
|
+
next_gid += 1
|
|
212
|
+
try:
|
|
213
|
+
it["gid"] = next_gid
|
|
214
|
+
existing_gids.add(next_gid)
|
|
215
|
+
next_gid += 1
|
|
216
|
+
except Exception:
|
|
217
|
+
pass
|
|
218
|
+
|
|
219
|
+
return compact_candidates
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def group_candidates_by_file(candidates: List[Dict]) -> Dict[str, List[Dict]]:
|
|
223
|
+
"""按文件分组候选问题"""
|
|
224
|
+
from collections import defaultdict
|
|
225
|
+
groups: Dict[str, List[Dict]] = defaultdict(list)
|
|
226
|
+
for it in candidates:
|
|
227
|
+
groups[str(it.get("file") or "")].append(it)
|
|
228
|
+
return groups
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def create_report_writer(sec_dir: Path, report_file: Optional[str]):
|
|
232
|
+
"""创建报告写入函数"""
|
|
233
|
+
from jarvis.jarvis_sec.file_manager import save_analysis_result, load_clusters
|
|
234
|
+
|
|
235
|
+
def _append_report(items, source: str, task_id: str, cand: Dict):
|
|
236
|
+
"""
|
|
237
|
+
将当前子任务的检测结果追加写入 analysis.jsonl 文件。
|
|
238
|
+
|
|
239
|
+
参数:
|
|
240
|
+
- items: 验证通过的问题列表(has_risk: true)
|
|
241
|
+
- source: 来源("analysis_only" 或 "verified")
|
|
242
|
+
- task_id: 任务ID(如 "JARVIS-SEC-Batch-1")
|
|
243
|
+
- cand: 候选信息,包含 batch 和 candidates
|
|
244
|
+
"""
|
|
245
|
+
if not items:
|
|
246
|
+
return
|
|
247
|
+
|
|
248
|
+
try:
|
|
249
|
+
# 从批次中提取信息
|
|
250
|
+
batch = cand.get("batch", False)
|
|
251
|
+
candidates = cand.get("candidates", [])
|
|
252
|
+
|
|
253
|
+
if not batch or not candidates:
|
|
254
|
+
# 如果没有批次信息,回退到旧格式(向后兼容)
|
|
255
|
+
path = Path(report_file) if report_file else sec_dir / "agent_issues.jsonl"
|
|
256
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
257
|
+
with path.open("a", encoding="utf-8") as f:
|
|
258
|
+
for item in items:
|
|
259
|
+
line = json.dumps(item, ensure_ascii=False)
|
|
260
|
+
f.write(line + "\n")
|
|
261
|
+
try:
|
|
262
|
+
typer.secho(f"[jarvis-sec] 已将 {len(items)} 个问题写入 {path}(旧格式)", fg=typer.colors.GREEN)
|
|
263
|
+
except Exception:
|
|
264
|
+
pass
|
|
265
|
+
return
|
|
266
|
+
|
|
267
|
+
# 从批次中提取 file 和 gids
|
|
268
|
+
batch_file = candidates[0].get("file") if candidates else ""
|
|
269
|
+
batch_gids = []
|
|
270
|
+
for c in candidates:
|
|
271
|
+
try:
|
|
272
|
+
gid = int(c.get("gid", 0))
|
|
273
|
+
if gid >= 1:
|
|
274
|
+
batch_gids.append(gid)
|
|
275
|
+
except Exception:
|
|
276
|
+
pass
|
|
277
|
+
|
|
278
|
+
# 从 clusters.jsonl 中查找对应的 cluster_id
|
|
279
|
+
clusters = load_clusters(sec_dir)
|
|
280
|
+
cluster_id = None
|
|
281
|
+
batch_index = None
|
|
282
|
+
cluster_index = None
|
|
283
|
+
|
|
284
|
+
# 尝试从 task_id 中提取 batch_index(格式:JARVIS-SEC-Batch-1)
|
|
285
|
+
try:
|
|
286
|
+
if "Batch-" in task_id:
|
|
287
|
+
batch_index = int(task_id.split("Batch-")[1])
|
|
288
|
+
except Exception:
|
|
289
|
+
pass
|
|
290
|
+
|
|
291
|
+
# 查找匹配的聚类(通过 file 和 gids)
|
|
292
|
+
for cluster in clusters:
|
|
293
|
+
cluster_file = str(cluster.get("file", ""))
|
|
294
|
+
cluster_gids = cluster.get("gids", [])
|
|
295
|
+
|
|
296
|
+
if cluster_file == batch_file and set(cluster_gids) == set(batch_gids):
|
|
297
|
+
cluster_id = cluster.get("cluster_id", "")
|
|
298
|
+
if not cluster_id:
|
|
299
|
+
# 如果没有 cluster_id,生成一个
|
|
300
|
+
cluster_id = f"{cluster_file}|{cluster.get('batch_index', batch_index or 0)}|{cluster.get('cluster_index', 0)}"
|
|
301
|
+
batch_index = cluster.get("batch_index", batch_index or 0)
|
|
302
|
+
cluster_index = cluster.get("cluster_index", 0)
|
|
303
|
+
break
|
|
304
|
+
|
|
305
|
+
# 如果找不到匹配的聚类,生成一个临时的 cluster_id
|
|
306
|
+
if not cluster_id:
|
|
307
|
+
cluster_id = f"{batch_file}|{batch_index or 0}|0"
|
|
308
|
+
batch_index = batch_index or 0
|
|
309
|
+
cluster_index = 0
|
|
310
|
+
|
|
311
|
+
# 分离验证为问题的gid和误报的gid
|
|
312
|
+
verified_gids = []
|
|
313
|
+
false_positive_gids = []
|
|
314
|
+
issues = []
|
|
315
|
+
|
|
316
|
+
# 从 items 中提取已验证的问题
|
|
317
|
+
for item in items:
|
|
318
|
+
try:
|
|
319
|
+
gid = int(item.get("gid", 0))
|
|
320
|
+
if gid >= 1:
|
|
321
|
+
has_risk = item.get("has_risk", False)
|
|
322
|
+
if has_risk:
|
|
323
|
+
verified_gids.append(gid)
|
|
324
|
+
issues.append(item)
|
|
325
|
+
else:
|
|
326
|
+
false_positive_gids.append(gid)
|
|
327
|
+
except Exception:
|
|
328
|
+
pass
|
|
329
|
+
|
|
330
|
+
# 从 candidates 中提取所有未在 items 中的 gid(这些可能是误报)
|
|
331
|
+
for c in candidates:
|
|
332
|
+
try:
|
|
333
|
+
gid = int(c.get("gid", 0))
|
|
334
|
+
if gid >= 1 and gid not in verified_gids and gid not in false_positive_gids:
|
|
335
|
+
# 如果这个 gid 不在已验证的问题中,可能是误报
|
|
336
|
+
false_positive_gids.append(gid)
|
|
337
|
+
except Exception:
|
|
338
|
+
pass
|
|
339
|
+
|
|
340
|
+
# 构建分析结果记录
|
|
341
|
+
analysis_result = {
|
|
342
|
+
"cluster_id": cluster_id,
|
|
343
|
+
"file": batch_file,
|
|
344
|
+
"batch_index": batch_index,
|
|
345
|
+
"cluster_index": cluster_index,
|
|
346
|
+
"gids": batch_gids,
|
|
347
|
+
"verified_gids": verified_gids,
|
|
348
|
+
"false_positive_gids": false_positive_gids,
|
|
349
|
+
"issues": issues,
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
# 保存到 analysis.jsonl
|
|
353
|
+
save_analysis_result(sec_dir, analysis_result)
|
|
354
|
+
|
|
355
|
+
try:
|
|
356
|
+
typer.secho(f"[jarvis-sec] 已将批次 {batch_index} 的分析结果写入 analysis.jsonl(问题: {len(verified_gids)}, 误报: {len(false_positive_gids)})", fg=typer.colors.GREEN)
|
|
357
|
+
except Exception:
|
|
358
|
+
pass
|
|
359
|
+
except Exception as e:
|
|
360
|
+
# 报告写入失败不影响主流程
|
|
361
|
+
try:
|
|
362
|
+
typer.secho(f"[jarvis-sec] 警告:保存分析结果失败: {e}", fg=typer.colors.YELLOW)
|
|
363
|
+
except Exception:
|
|
364
|
+
pass
|
|
365
|
+
|
|
366
|
+
return _append_report
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def sig_of(c: Dict) -> str:
|
|
370
|
+
"""生成候选问题的签名"""
|
|
371
|
+
return f"{c.get('language','')}|{c.get('file','')}|{c.get('line','')}|{c.get('pattern','')}"
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def load_processed_gids_from_issues(sec_dir: Path) -> set:
|
|
375
|
+
"""从 agent_issues.jsonl 中读取已处理的 gid"""
|
|
376
|
+
processed_gids = set()
|
|
377
|
+
try:
|
|
378
|
+
_agent_issues_path = sec_dir / "agent_issues.jsonl"
|
|
379
|
+
if _agent_issues_path.exists():
|
|
380
|
+
with _agent_issues_path.open("r", encoding="utf-8", errors="ignore") as f:
|
|
381
|
+
for line in f:
|
|
382
|
+
line = line.strip()
|
|
383
|
+
if not line:
|
|
384
|
+
continue
|
|
385
|
+
try:
|
|
386
|
+
issue_obj = json.loads(line)
|
|
387
|
+
_gid = int(issue_obj.get("gid", 0))
|
|
388
|
+
if _gid >= 1:
|
|
389
|
+
processed_gids.add(_gid)
|
|
390
|
+
except Exception:
|
|
391
|
+
pass
|
|
392
|
+
if processed_gids:
|
|
393
|
+
try:
|
|
394
|
+
typer.secho(f"[jarvis-sec] 断点恢复:从 agent_issues.jsonl 读取到 {len(processed_gids)} 个已处理的 gid", fg=typer.colors.BLUE)
|
|
395
|
+
except Exception:
|
|
396
|
+
pass
|
|
397
|
+
except Exception:
|
|
398
|
+
pass
|
|
399
|
+
return processed_gids
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def count_issues_from_file(sec_dir: Path) -> int:
|
|
403
|
+
"""从 analysis.jsonl 读取问题数量"""
|
|
404
|
+
from jarvis.jarvis_sec.file_manager import get_verified_issue_gids
|
|
405
|
+
verified_gids = get_verified_issue_gids(sec_dir)
|
|
406
|
+
return len(verified_gids)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def count_issues_from_file_old(sec_dir: Path) -> int:
|
|
410
|
+
"""从 agent_issues.jsonl 中读取当前问题总数(用于状态显示)"""
|
|
411
|
+
count = 0
|
|
412
|
+
try:
|
|
413
|
+
_agent_issues_path = sec_dir / "agent_issues.jsonl"
|
|
414
|
+
if _agent_issues_path.exists():
|
|
415
|
+
saved_gids = set()
|
|
416
|
+
with _agent_issues_path.open("r", encoding="utf-8", errors="ignore") as f:
|
|
417
|
+
for line in f:
|
|
418
|
+
line = line.strip()
|
|
419
|
+
if not line:
|
|
420
|
+
continue
|
|
421
|
+
try:
|
|
422
|
+
item = json.loads(line)
|
|
423
|
+
gid = item.get("gid", 0)
|
|
424
|
+
if gid >= 1 and gid not in saved_gids:
|
|
425
|
+
# 只统计验证通过的告警(has_risk: true 且有 verification_notes)
|
|
426
|
+
if item.get("has_risk") is True and "verification_notes" in item:
|
|
427
|
+
count += 1
|
|
428
|
+
saved_gids.add(gid)
|
|
429
|
+
except Exception:
|
|
430
|
+
pass
|
|
431
|
+
except Exception:
|
|
432
|
+
pass
|
|
433
|
+
return count
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def load_all_issues_from_file(sec_dir: Path) -> List[Dict]:
|
|
437
|
+
"""从 agent_issues.jsonl 读取所有已保存的告警"""
|
|
438
|
+
all_issues: List[Dict] = []
|
|
439
|
+
try:
|
|
440
|
+
_agent_issues_path = sec_dir / "agent_issues.jsonl"
|
|
441
|
+
if _agent_issues_path.exists():
|
|
442
|
+
saved_gids_from_file = set()
|
|
443
|
+
with _agent_issues_path.open("r", encoding="utf-8", errors="ignore") as f:
|
|
444
|
+
for line in f:
|
|
445
|
+
line = line.strip()
|
|
446
|
+
if not line:
|
|
447
|
+
continue
|
|
448
|
+
try:
|
|
449
|
+
item = json.loads(line)
|
|
450
|
+
gid = item.get("gid", 0)
|
|
451
|
+
if gid >= 1 and gid not in saved_gids_from_file:
|
|
452
|
+
# 只保留验证通过的告警(has_risk: true 且有 verification_notes)
|
|
453
|
+
if item.get("has_risk") is True and "verification_notes" in item:
|
|
454
|
+
all_issues.append(item)
|
|
455
|
+
saved_gids_from_file.add(gid)
|
|
456
|
+
except Exception:
|
|
457
|
+
pass
|
|
458
|
+
|
|
459
|
+
if all_issues:
|
|
460
|
+
try:
|
|
461
|
+
typer.secho(f"[jarvis-sec] 从 agent_issues.jsonl 加载了 {len(all_issues)} 个已保存的告警", fg=typer.colors.BLUE)
|
|
462
|
+
except Exception:
|
|
463
|
+
pass
|
|
464
|
+
else:
|
|
465
|
+
try:
|
|
466
|
+
typer.secho("[jarvis-sec] agent_issues.jsonl 不存在,当前运行未发现任何问题", fg=typer.colors.BLUE)
|
|
467
|
+
except Exception:
|
|
468
|
+
pass
|
|
469
|
+
except Exception as e:
|
|
470
|
+
# 加载失败不影响主流程
|
|
471
|
+
try:
|
|
472
|
+
typer.secho(f"[jarvis-sec] 警告:从 agent_issues.jsonl 加载告警失败: {e}", fg=typer.colors.YELLOW)
|
|
473
|
+
except Exception:
|
|
474
|
+
pass
|
|
475
|
+
return all_issues
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def load_processed_gids_from_agent_issues(sec_dir: Path) -> set:
|
|
479
|
+
"""从 agent_issues.jsonl 读取已处理的 gid"""
|
|
480
|
+
processed_gids = set()
|
|
481
|
+
try:
|
|
482
|
+
_agent_issues_path = sec_dir / "agent_issues.jsonl"
|
|
483
|
+
if _agent_issues_path.exists():
|
|
484
|
+
with _agent_issues_path.open("r", encoding="utf-8", errors="ignore") as f:
|
|
485
|
+
for line in f:
|
|
486
|
+
line = line.strip()
|
|
487
|
+
if not line:
|
|
488
|
+
continue
|
|
489
|
+
try:
|
|
490
|
+
issue_obj = json.loads(line)
|
|
491
|
+
_gid = int(issue_obj.get("gid", 0))
|
|
492
|
+
if _gid >= 1:
|
|
493
|
+
processed_gids.add(_gid)
|
|
494
|
+
except Exception:
|
|
495
|
+
pass
|
|
496
|
+
except Exception:
|
|
497
|
+
pass
|
|
498
|
+
return processed_gids
|
|
499
|
+
|