jarvis-ai-assistant 0.3.30__py3-none-any.whl → 0.7.0__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 (115) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +289 -87
  3. jarvis/jarvis_agent/agent_manager.py +17 -8
  4. jarvis/jarvis_agent/edit_file_handler.py +374 -86
  5. jarvis/jarvis_agent/event_bus.py +1 -1
  6. jarvis/jarvis_agent/file_context_handler.py +79 -0
  7. jarvis/jarvis_agent/jarvis.py +601 -43
  8. jarvis/jarvis_agent/main.py +32 -2
  9. jarvis/jarvis_agent/rewrite_file_handler.py +141 -0
  10. jarvis/jarvis_agent/run_loop.py +38 -5
  11. jarvis/jarvis_agent/share_manager.py +8 -1
  12. jarvis/jarvis_agent/stdio_redirect.py +295 -0
  13. jarvis/jarvis_agent/task_analyzer.py +5 -2
  14. jarvis/jarvis_agent/task_planner.py +496 -0
  15. jarvis/jarvis_agent/utils.py +5 -1
  16. jarvis/jarvis_agent/web_bridge.py +189 -0
  17. jarvis/jarvis_agent/web_output_sink.py +53 -0
  18. jarvis/jarvis_agent/web_server.py +751 -0
  19. jarvis/jarvis_c2rust/__init__.py +26 -0
  20. jarvis/jarvis_c2rust/cli.py +613 -0
  21. jarvis/jarvis_c2rust/collector.py +258 -0
  22. jarvis/jarvis_c2rust/library_replacer.py +1122 -0
  23. jarvis/jarvis_c2rust/llm_module_agent.py +1300 -0
  24. jarvis/jarvis_c2rust/optimizer.py +960 -0
  25. jarvis/jarvis_c2rust/scanner.py +1681 -0
  26. jarvis/jarvis_c2rust/transpiler.py +2325 -0
  27. jarvis/jarvis_code_agent/build_validation_config.py +133 -0
  28. jarvis/jarvis_code_agent/code_agent.py +1171 -94
  29. jarvis/jarvis_code_agent/code_analyzer/__init__.py +62 -0
  30. jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
  31. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
  32. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +102 -0
  33. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +59 -0
  34. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
  35. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +69 -0
  36. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +38 -0
  37. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +44 -0
  38. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +38 -0
  39. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +50 -0
  40. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +93 -0
  41. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +129 -0
  42. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +54 -0
  43. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +154 -0
  44. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
  45. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +363 -0
  46. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
  47. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
  48. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
  49. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
  50. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
  51. jarvis/jarvis_code_agent/code_analyzer/language_support.py +89 -0
  52. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +31 -0
  53. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +231 -0
  54. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +183 -0
  55. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +219 -0
  56. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +209 -0
  57. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +451 -0
  58. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +77 -0
  59. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +48 -0
  60. jarvis/jarvis_code_agent/lint.py +270 -8
  61. jarvis/jarvis_code_agent/utils.py +142 -0
  62. jarvis/jarvis_code_analysis/code_review.py +483 -569
  63. jarvis/jarvis_data/config_schema.json +97 -8
  64. jarvis/jarvis_git_utils/git_commiter.py +38 -26
  65. jarvis/jarvis_mcp/sse_mcp_client.py +2 -2
  66. jarvis/jarvis_mcp/stdio_mcp_client.py +1 -1
  67. jarvis/jarvis_memory_organizer/memory_organizer.py +1 -1
  68. jarvis/jarvis_multi_agent/__init__.py +239 -25
  69. jarvis/jarvis_multi_agent/main.py +37 -1
  70. jarvis/jarvis_platform/base.py +103 -51
  71. jarvis/jarvis_platform/openai.py +26 -1
  72. jarvis/jarvis_platform/yuanbao.py +1 -1
  73. jarvis/jarvis_platform_manager/service.py +2 -2
  74. jarvis/jarvis_rag/cli.py +4 -4
  75. jarvis/jarvis_sec/__init__.py +3605 -0
  76. jarvis/jarvis_sec/checkers/__init__.py +32 -0
  77. jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
  78. jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
  79. jarvis/jarvis_sec/cli.py +116 -0
  80. jarvis/jarvis_sec/report.py +257 -0
  81. jarvis/jarvis_sec/status.py +264 -0
  82. jarvis/jarvis_sec/types.py +20 -0
  83. jarvis/jarvis_sec/workflow.py +219 -0
  84. jarvis/jarvis_stats/cli.py +1 -1
  85. jarvis/jarvis_stats/stats.py +1 -1
  86. jarvis/jarvis_stats/visualizer.py +1 -1
  87. jarvis/jarvis_tools/cli/main.py +1 -0
  88. jarvis/jarvis_tools/execute_script.py +46 -9
  89. jarvis/jarvis_tools/generate_new_tool.py +3 -1
  90. jarvis/jarvis_tools/read_code.py +275 -12
  91. jarvis/jarvis_tools/read_symbols.py +141 -0
  92. jarvis/jarvis_tools/read_webpage.py +5 -3
  93. jarvis/jarvis_tools/registry.py +73 -35
  94. jarvis/jarvis_tools/search_web.py +15 -11
  95. jarvis/jarvis_tools/sub_agent.py +24 -42
  96. jarvis/jarvis_tools/sub_code_agent.py +14 -13
  97. jarvis/jarvis_tools/virtual_tty.py +1 -1
  98. jarvis/jarvis_utils/config.py +187 -35
  99. jarvis/jarvis_utils/embedding.py +3 -0
  100. jarvis/jarvis_utils/git_utils.py +181 -6
  101. jarvis/jarvis_utils/globals.py +3 -3
  102. jarvis/jarvis_utils/http.py +1 -1
  103. jarvis/jarvis_utils/input.py +78 -2
  104. jarvis/jarvis_utils/methodology.py +25 -19
  105. jarvis/jarvis_utils/utils.py +644 -359
  106. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/METADATA +85 -1
  107. jarvis_ai_assistant-0.7.0.dist-info/RECORD +192 -0
  108. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/entry_points.txt +4 -0
  109. jarvis/jarvis_agent/config.py +0 -92
  110. jarvis/jarvis_tools/edit_file.py +0 -179
  111. jarvis/jarvis_tools/rewrite_file.py +0 -191
  112. jarvis_ai_assistant-0.3.30.dist-info/RECORD +0 -137
  113. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/WHEEL +0 -0
  114. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/licenses/LICENSE +0 -0
  115. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,116 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Jarvis 安全演进套件 —— 命令行入口(Typer 版本)
4
+
5
+ 用法示例:
6
+ - Agent模式(单Agent,逐条子任务分析)
7
+ python -m jarvis.jarvis_sec.cli agent --path ./target_project
8
+
9
+ 可选参数:
10
+
11
+ - --output: 最终Markdown报告输出路径(默认 ./report.md)
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import sys
17
+ from pathlib import Path
18
+ from typing import Optional
19
+
20
+ import typer
21
+ from jarvis.jarvis_utils.utils import init_env
22
+ # removed: set_config import(避免全局覆盖模型组配置)
23
+ from jarvis.jarvis_sec.workflow import run_with_agent, direct_scan, format_markdown_report
24
+
25
+ app = typer.Typer(
26
+ add_completion=False,
27
+ no_args_is_help=True,
28
+ help="Jarvis 安全演进套件(单Agent逐条子任务分析)",
29
+ )
30
+
31
+
32
+
33
+
34
+ @app.command("agent", help="Agent模式(单Agent逐条子任务分析)")
35
+ def agent(
36
+ path: str = typer.Option(..., "--path", "-p", help="待分析的根目录"),
37
+
38
+ llm_group: Optional[str] = typer.Option(
39
+ None, "--llm-group", "-g", help="使用的模型组(仅对本次运行生效,不修改全局配置)"
40
+ ),
41
+ output: Optional[str] = typer.Option(
42
+ "report.md", "--output", "-o", help="最终Markdown报告输出路径(默认 ./report.md)"
43
+ ),
44
+
45
+ cluster_limit: int = typer.Option(
46
+ 50, "--cluster-limit", "-c", help="聚类每批最多处理的告警数(按文件分批聚类,默认50)"
47
+ ),
48
+ ) -> None:
49
+ # 初始化环境,确保平台/模型等全局配置就绪(避免 NoneType 平台)
50
+ try:
51
+ init_env("欢迎使用 Jarvis 安全套件!", None)
52
+ except Exception:
53
+ # 环境初始化失败不应阻塞CLI基础功能,继续后续流程
54
+ pass
55
+
56
+ # 若指定了模型组:仅对本次运行生效,透传给 Agent;不修改全局配置(无需 set_config)
57
+
58
+
59
+ text: Optional[str] = None
60
+ try:
61
+ text = run_with_agent(
62
+ path,
63
+ llm_group=llm_group,
64
+ cluster_limit=cluster_limit,
65
+ )
66
+ except Exception as e:
67
+ try:
68
+ typer.secho(f"[jarvis_sec] Agent 分析过程出错,将回退到直扫基线(fast):{e}", fg=typer.colors.YELLOW, err=True)
69
+ except Exception:
70
+ pass
71
+ text = None
72
+
73
+ if not text or not str(text).strip():
74
+ try:
75
+ typer.secho("[jarvis_sec] Agent 无输出,回退到直扫基线(fast)。", fg=typer.colors.YELLOW, err=True)
76
+ except Exception:
77
+ pass
78
+ result = direct_scan(path)
79
+ text = format_markdown_report(result)
80
+
81
+ if output:
82
+ try:
83
+ md_text = text or ""
84
+ try:
85
+ lines = (text or "").splitlines()
86
+ idx = -1
87
+ for i, ln in enumerate(lines):
88
+ if ln.strip().startswith("# Jarvis 安全问题分析报告"):
89
+ idx = i
90
+ break
91
+ if idx >= 0:
92
+ md_text = "\n".join(lines[idx:])
93
+ except Exception:
94
+ md_text = text or ""
95
+ p = Path(output)
96
+ p.parent.mkdir(parents=True, exist_ok=True)
97
+ p.write_text(md_text, encoding="utf-8")
98
+ try:
99
+ typer.secho(f"[jarvis_sec] Markdown 报告已写入: {p}", fg=typer.colors.GREEN)
100
+ except Exception:
101
+ pass
102
+ except Exception as e:
103
+ try:
104
+ typer.secho(f"[jarvis_sec] 写入Markdown报告失败: {e}", fg=typer.colors.RED, err=True)
105
+ except Exception:
106
+ pass
107
+ typer.echo(text)
108
+
109
+
110
+ def main() -> int:
111
+ app()
112
+ return 0
113
+
114
+
115
+ if __name__ == "__main__":
116
+ sys.exit(main())
@@ -0,0 +1,257 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 安全分析套件 —— 报告聚合与评分模块
4
+
5
+ 目标:
6
+ - 将启发式检查器输出的结构化问题列表进行聚合与评分,生成统一的 JSON 与 Markdown 报告。
7
+ - 与 workflow.direct_scan / 多Agent Aggregator 保持输出结构一致,便于评测解析与专家审阅。
8
+
9
+ 输出结构(JSON示例):
10
+ {
11
+ "summary": {
12
+ "total": 0,
13
+ "by_language": {"c/cpp": 0, "rust": 0},
14
+ "by_category": {
15
+ "buffer_overflow": 0, "unsafe_api": 0, "memory_mgmt": 0, "error_handling": 0,
16
+ "unsafe_usage": 0, "concurrency": 0, "ffi": 0
17
+ },
18
+ "top_risk_files": ["path1", "path2"]
19
+ },
20
+ "issues": [
21
+ {
22
+ "id": "C001",
23
+ "language": "c/cpp",
24
+ "category": "unsafe_api",
25
+ "pattern": "strcpy",
26
+ "file": "src/foo.c",
27
+ "line": 123,
28
+ "evidence": "strcpy(dst, src);",
29
+ "preconditions": "N/A",
30
+ "trigger_path": "函数 foobar 调用 strcpy 时,其输入 src 来自于未经校验的网络数据包",
31
+ "consequences": "可能导致缓冲区溢出",
32
+ "suggestions": "使用 strncpy_s 或其他安全函数替代",
33
+ "confidence": 0.85,
34
+ "severity": "high | medium | low",
35
+ "score": 2.55
36
+ }
37
+ ]
38
+ }
39
+
40
+ 提供的函数:
41
+ - aggregate_issues(issues: List[Union[Issue, Dict]], scanned_root: Optional[str] = None, scanned_files: Optional[int] = None) -> Dict
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
44
+ """
45
+
46
+ from __future__ import annotations
47
+
48
+ import hashlib
49
+ from typing import Dict, List, Optional, Union
50
+
51
+ # 依赖 Issue 结构,但本模块不直接导入 dataclass,接受 dict/Issue 两种形态
52
+ try:
53
+ from jarvis.jarvis_sec.types import Issue # 类型提示用,避免循环依赖
54
+ except Exception:
55
+ Issue = dict # type: ignore
56
+
57
+
58
+ # ---------------------------
59
+ # 内部工具
60
+ # ---------------------------
61
+
62
+ _CATEGORY_ORDER = [
63
+ "unsafe_api",
64
+ "buffer_overflow",
65
+ "memory_mgmt",
66
+ "error_handling",
67
+ "unsafe_usage",
68
+ "concurrency",
69
+ "ffi",
70
+ ]
71
+
72
+ _SEVERITY_WEIGHT = {
73
+ "high": 3.0,
74
+ "medium": 2.0,
75
+ "low": 1.0,
76
+ }
77
+
78
+ def _as_dict(item: Union[Issue, Dict]) -> Dict:
79
+ """
80
+ 将 Issue/dataclass 或 dict 统一为 dict。
81
+ """
82
+ if isinstance(item, dict):
83
+ return item
84
+ # dataclass: 尝试属性访问
85
+ d: Dict = {}
86
+ for k in (
87
+ "language",
88
+ "category",
89
+ "pattern",
90
+ "file",
91
+ "line",
92
+ "evidence",
93
+ "preconditions",
94
+ "trigger_path",
95
+ "consequences",
96
+ "suggestions",
97
+ "confidence",
98
+ "severity",
99
+ ):
100
+ v = getattr(item, k, None)
101
+ if v is not None:
102
+ d[k] = v
103
+ return d
104
+
105
+
106
+ def _normalize_issue(i: Dict) -> Dict:
107
+ """
108
+ 归一化字段并补充缺省值。
109
+ """
110
+ j = {
111
+ "language": i.get("language", "c/cpp" if str(i.get("file", "")).endswith((".c", ".cpp", ".h", ".hpp")) else "rust"),
112
+ "category": i.get("category", "error_handling"),
113
+ "pattern": i.get("pattern", ""),
114
+ "file": i.get("file", ""),
115
+ "line": int(i.get("line", 0) or 0),
116
+ "evidence": i.get("evidence", ""),
117
+ "preconditions": i.get("preconditions", ""),
118
+ "trigger_path": i.get("trigger_path", ""),
119
+ "consequences": i.get("consequences", ""),
120
+ "suggestions": i.get("suggestions", ""),
121
+ "confidence": float(i.get("confidence", 0.6)),
122
+ "severity": i.get("severity", "medium"),
123
+ }
124
+ # 计算稳定ID(基于文件/行/类别/模式哈希)
125
+ base = f"{j['file']}:{j['line']}:{j['category']}:{j['pattern']}"
126
+ j["id"] = _make_issue_id(base, j["language"])
127
+ # 评分:confidence * severity_weight
128
+ j["score"] = round(j["confidence"] * _SEVERITY_WEIGHT.get(j["severity"], 1.0), 2)
129
+ return j
130
+
131
+
132
+ def _make_issue_id(base: str, lang: str) -> str:
133
+ h = hashlib.sha1(base.encode("utf-8")).hexdigest()[:6]
134
+ prefix = "C" if lang.startswith("c") else "R"
135
+ return f"{prefix}{h.upper()}"
136
+
137
+
138
+ # ---------------------------
139
+ # 聚合与评分
140
+ # ---------------------------
141
+
142
+ def aggregate_issues(
143
+ issues: List[Union[Issue, Dict]],
144
+ scanned_root: Optional[str] = None,
145
+ scanned_files: Optional[int] = None,
146
+ ) -> Dict:
147
+ """
148
+ 聚合问题列表并生成 JSON 报告。
149
+ """
150
+ items = [_normalize_issue(_as_dict(it)) for it in issues]
151
+
152
+ summary: Dict = {
153
+ "total": len(items),
154
+ "by_language": {"c/cpp": 0, "rust": 0},
155
+ "by_category": {k: 0 for k in _CATEGORY_ORDER},
156
+ "top_risk_files": [],
157
+ }
158
+ if scanned_root is not None:
159
+ summary["scanned_root"] = scanned_root
160
+ if scanned_files is not None:
161
+ summary["scanned_files"] = scanned_files
162
+
163
+ file_score: Dict[str, float] = {}
164
+ for it in items:
165
+ lang = it["language"]
166
+ summary["by_language"][lang] = summary["by_language"].get(lang, 0) + 1
167
+ cat = it["category"]
168
+ summary["by_category"][cat] = summary["by_category"].get(cat, 0) + 1
169
+ file_score[it["file"]] = file_score.get(it["file"], 0.0) + it["score"]
170
+
171
+ # Top 风险文件按累计分排序,更稳定、可解释
172
+ summary["top_risk_files"] = [
173
+ f for f, _ in sorted(file_score.items(), key=lambda x: x[1], reverse=True)[:10]
174
+ ]
175
+
176
+ report = {
177
+ "summary": summary,
178
+ "issues": items,
179
+ }
180
+ return report
181
+
182
+
183
+ # ---------------------------
184
+ # Markdown 渲染
185
+ # ---------------------------
186
+
187
+ def format_markdown_report(report_json: Dict) -> str:
188
+ """
189
+ 将聚合后的 JSON 报告渲染为 Markdown。
190
+ """
191
+ s = report_json.get("summary", {})
192
+ issues: List[Dict] = report_json.get("issues", [])
193
+ lines: List[str] = []
194
+
195
+ lines.append("# 安全问题分析报告(聚合)")
196
+ lines.append("")
197
+ if "scanned_root" in s:
198
+ lines.append(f"- 扫描根目录: {s.get('scanned_root')}")
199
+ if "scanned_files" in s:
200
+ lines.append(f"- 扫描文件数: {s.get('scanned_files')}")
201
+ lines.append(f"- 检出问题总数: {s.get('total', 0)}")
202
+ lines.append("")
203
+
204
+ # 概览
205
+ lines.append("## 统计概览")
206
+ by_lang = s.get("by_language", {})
207
+ lines.append(f"- 按语言: c/cpp={by_lang.get('c/cpp', 0)}, rust={by_lang.get('rust', 0)}")
208
+ lines.append("- 按类别:")
209
+ by_cat = s.get("by_category", {})
210
+ for k in _CATEGORY_ORDER:
211
+ v = by_cat.get(k, 0)
212
+ lines.append(f" - {k}: {v}")
213
+ if s.get("top_risk_files"):
214
+ lines.append("- Top 风险文件:")
215
+ for f in s["top_risk_files"]:
216
+ lines.append(f" - {f}")
217
+ lines.append("")
218
+
219
+ # 详细问题
220
+ lines.append("## 详细问题")
221
+ for i, it in enumerate(issues, start=1):
222
+ lines.append(f"### [{i}] {it.get('file')}:{it.get('line')} ({it.get('language')}, {it.get('category')})")
223
+ lines.append(f"- 模式: {it.get('pattern')}")
224
+ lines.append(f"- 证据: `{it.get('evidence')}`")
225
+ lines.append(f"- 前置条件: {it.get('preconditions')}")
226
+ lines.append(f"- 触发路径: {it.get('trigger_path')}")
227
+ lines.append(f"- 后果: {it.get('consequences')}")
228
+ lines.append(f"- 建议: {it.get('suggestions')}")
229
+ lines.append(f"- 置信度: {it.get('confidence')}, 严重性: {it.get('severity')}, 评分: {it.get('score')}")
230
+ lines.append("")
231
+
232
+ return "\n".join(lines)
233
+
234
+
235
+ def build_json_and_markdown(
236
+ issues: List[Union[Issue, Dict]],
237
+ scanned_root: Optional[str] = None,
238
+ scanned_files: Optional[int] = None,
239
+ meta: Optional[List[Dict]] = None,
240
+ ) -> str:
241
+ """
242
+ 一次性生成报告文本(仅 Markdown)。
243
+ """
244
+ report = aggregate_issues(issues, scanned_root=scanned_root, scanned_files=scanned_files)
245
+ if meta is not None:
246
+ try:
247
+ report["meta"] = meta # 注入可选审计信息(仅用于JSON时保留,为兼容未来需要)
248
+ except Exception:
249
+ pass
250
+ return format_markdown_report(report)
251
+
252
+
253
+ __all__ = [
254
+ "aggregate_issues",
255
+ "format_markdown_report",
256
+ "build_json_and_markdown",
257
+ ]
@@ -0,0 +1,264 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 进度状态管理模块
4
+
5
+ 提供结构化的进度状态文件,准确反映当前所处的阶段和进度。
6
+ 状态文件格式:JSON,包含当前阶段、进度百分比、已完成/总数等信息。
7
+ """
8
+
9
+ import json
10
+ from datetime import datetime
11
+ from pathlib import Path
12
+ from typing import Dict, Optional, Any
13
+
14
+
15
+ class StatusManager:
16
+ """进度状态管理器"""
17
+
18
+ def __init__(self, entry_path: str):
19
+ """
20
+ 初始化状态管理器
21
+
22
+ Args:
23
+ entry_path: 待分析的根目录路径(可以是项目根目录或 .jarvis/sec 目录)
24
+ """
25
+ self.entry_path = Path(entry_path)
26
+ # 检查 entry_path 是否已经是 .jarvis/sec 目录
27
+ if self.entry_path.name == "sec" and self.entry_path.parent.name == ".jarvis":
28
+ sec_dir = self.entry_path
29
+ else:
30
+ sec_dir = self.entry_path / ".jarvis" / "sec"
31
+ self.status_path = sec_dir / "status.json"
32
+ self._ensure_dir()
33
+
34
+ def _ensure_dir(self):
35
+ """确保状态文件目录存在"""
36
+ self.status_path.parent.mkdir(parents=True, exist_ok=True)
37
+
38
+ def _read_status(self) -> Dict[str, Any]:
39
+ """读取当前状态"""
40
+ if not self.status_path.exists():
41
+ return {}
42
+ try:
43
+ with self.status_path.open("r", encoding="utf-8") as f:
44
+ return json.load(f)
45
+ except Exception:
46
+ return {}
47
+
48
+ def _write_status(self, status: Dict[str, Any]):
49
+ """写入状态文件"""
50
+ try:
51
+ status["last_updated"] = datetime.utcnow().isoformat() + "Z"
52
+ with self.status_path.open("w", encoding="utf-8") as f:
53
+ json.dump(status, f, ensure_ascii=False, indent=2)
54
+ except Exception:
55
+ # 状态文件写入失败不影响主流程
56
+ pass
57
+
58
+ def update_stage(
59
+ self,
60
+ stage: str,
61
+ progress: Optional[float] = None,
62
+ current: Optional[int] = None,
63
+ total: Optional[int] = None,
64
+ message: Optional[str] = None,
65
+ details: Optional[Dict[str, Any]] = None,
66
+ ):
67
+ """
68
+ 更新当前阶段和进度
69
+
70
+ Args:
71
+ stage: 阶段名称(pre_scan, clustering, verification, completed, error)
72
+ progress: 进度百分比(0-100),如果为None则根据current/total计算
73
+ current: 当前已完成数量
74
+ total: 总数量
75
+ message: 状态消息
76
+ details: 额外的详细信息
77
+ """
78
+ status = self._read_status()
79
+
80
+ # 计算进度百分比
81
+ if progress is None and current is not None and total is not None and total > 0:
82
+ progress = (current / total) * 100
83
+
84
+ # 更新状态
85
+ status["stage"] = stage
86
+ if progress is not None:
87
+ status["progress"] = round(progress, 2)
88
+ if current is not None:
89
+ status["current"] = current
90
+ if total is not None:
91
+ status["total"] = total
92
+ if message:
93
+ status["message"] = message
94
+ if details:
95
+ status["details"] = details
96
+
97
+ # 设置阶段开始时间(如果是新阶段)
98
+ if "stage_history" not in status:
99
+ status["stage_history"] = []
100
+
101
+ # 检查是否是阶段切换
102
+ last_stage = status.get("stage")
103
+ if last_stage != stage:
104
+ status["stage_history"].append({
105
+ "stage": stage,
106
+ "started_at": datetime.utcnow().isoformat() + "Z"
107
+ })
108
+
109
+ self._write_status(status)
110
+
111
+ def update_pre_scan(
112
+ self,
113
+ current_files: Optional[int] = None,
114
+ total_files: Optional[int] = None,
115
+ issues_found: Optional[int] = None,
116
+ message: Optional[str] = None,
117
+ ):
118
+ """更新启发式扫描阶段状态"""
119
+ details = {}
120
+ if issues_found is not None:
121
+ details["issues_found"] = issues_found
122
+
123
+ progress = None
124
+ if current_files is not None and total_files is not None and total_files > 0:
125
+ progress = (current_files / total_files) * 100
126
+
127
+ self.update_stage(
128
+ stage="pre_scan",
129
+ progress=progress,
130
+ current=current_files,
131
+ total=total_files,
132
+ message=message or "正在进行启发式扫描...",
133
+ details=details,
134
+ )
135
+
136
+ def update_clustering(
137
+ self,
138
+ current_file: Optional[int] = None,
139
+ total_files: Optional[int] = None,
140
+ current_batch: Optional[int] = None,
141
+ total_batches: Optional[int] = None,
142
+ file_name: Optional[str] = None,
143
+ message: Optional[str] = None,
144
+ ):
145
+ """更新聚类阶段状态"""
146
+ details = {}
147
+ if file_name:
148
+ details["current_file"] = file_name
149
+ if current_batch is not None:
150
+ details["current_batch"] = current_batch
151
+ if total_batches is not None:
152
+ details["total_batches"] = total_batches
153
+
154
+ # 计算总体进度(文件级别)
155
+ progress = None
156
+ if current_file is not None and total_files is not None and total_files > 0:
157
+ progress = (current_file / total_files) * 100
158
+
159
+ self.update_stage(
160
+ stage="clustering",
161
+ progress=progress,
162
+ current=current_file,
163
+ total=total_files,
164
+ message=message or "正在进行聚类分析...",
165
+ details=details,
166
+ )
167
+
168
+ def update_review(
169
+ self,
170
+ current_review: Optional[int] = None,
171
+ total_reviews: Optional[int] = None,
172
+ message: Optional[str] = None,
173
+ ):
174
+ """更新复核阶段状态"""
175
+ details = {}
176
+
177
+ # 计算总体进度
178
+ progress = None
179
+ if current_review is not None and total_reviews is not None and total_reviews > 0:
180
+ progress = (current_review / total_reviews) * 100
181
+
182
+ self.update_stage(
183
+ stage="review",
184
+ progress=progress,
185
+ current=current_review,
186
+ total=total_reviews,
187
+ message=message or "正在进行无效聚类复核...",
188
+ details=details,
189
+ )
190
+
191
+ def update_verification(
192
+ self,
193
+ current_batch: Optional[int] = None,
194
+ total_batches: Optional[int] = None,
195
+ current_task: Optional[int] = None,
196
+ total_tasks: Optional[int] = None,
197
+ batch_id: Optional[str] = None,
198
+ file_name: Optional[str] = None,
199
+ issues_found: Optional[int] = None,
200
+ message: Optional[str] = None,
201
+ ):
202
+ """更新验证阶段状态"""
203
+ details = {}
204
+ if batch_id:
205
+ details["batch_id"] = batch_id
206
+ if file_name:
207
+ details["file"] = file_name
208
+ if issues_found is not None:
209
+ details["issues_found"] = issues_found
210
+
211
+ # 计算总体进度(批次级别)
212
+ progress = None
213
+ if current_batch is not None and total_batches is not None and total_batches > 0:
214
+ progress = (current_batch / total_batches) * 100
215
+
216
+ self.update_stage(
217
+ stage="verification",
218
+ progress=progress,
219
+ current=current_batch,
220
+ total=total_batches,
221
+ message=message or "正在进行安全验证...",
222
+ details=details,
223
+ )
224
+
225
+ def mark_completed(
226
+ self,
227
+ total_issues: Optional[int] = None,
228
+ message: Optional[str] = None,
229
+ ):
230
+ """标记分析完成"""
231
+ details = {}
232
+ if total_issues is not None:
233
+ details["total_issues"] = total_issues
234
+
235
+ self.update_stage(
236
+ stage="completed",
237
+ progress=100.0,
238
+ message=message or "安全分析已完成",
239
+ details=details,
240
+ )
241
+
242
+ def mark_error(
243
+ self,
244
+ error_message: str,
245
+ error_type: Optional[str] = None,
246
+ ):
247
+ """标记错误状态"""
248
+ details = {"error_message": error_message}
249
+ if error_type:
250
+ details["error_type"] = error_type
251
+
252
+ self.update_stage(
253
+ stage="error",
254
+ message=f"发生错误: {error_message}",
255
+ details=details,
256
+ )
257
+
258
+ def get_status(self) -> Dict[str, Any]:
259
+ """获取当前状态"""
260
+ return self._read_status()
261
+
262
+
263
+ __all__ = ["StatusManager"]
264
+
@@ -0,0 +1,20 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Shared types for jarvis.jarvis_sec to avoid circular imports.
4
+ """
5
+ from dataclasses import dataclass
6
+
7
+ @dataclass
8
+ class Issue:
9
+ language: str
10
+ category: str
11
+ pattern: str
12
+ file: str
13
+ line: int
14
+ evidence: str
15
+ description: str
16
+ suggestion: str
17
+ confidence: float
18
+ severity: str = "medium"
19
+
20
+ __all__ = ["Issue"]