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,226 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Jarvis 安全分析套件 —— Workflow(含可复现直扫基线)
|
|
4
|
+
|
|
5
|
+
目标:
|
|
6
|
+
- 识别指定模块的安全问题(内存管理、缓冲区操作、错误处理等),检出率≥60% 为目标。
|
|
7
|
+
- 在不依赖外部服务的前提下,提供一个“可复现、可离线”的直扫基线(direct scan)。
|
|
8
|
+
- 当前采用“先直扫拆分子任务,再由单Agent逐条分析”的模式;保留接口便于后续切换。
|
|
9
|
+
|
|
10
|
+
本模块提供:
|
|
11
|
+
- direct_scan(entry_path, languages=None, exclude_dirs=None) -> Dict:纯Python+正则/命令行辅助扫描,生成结构化结果
|
|
12
|
+
- format_markdown_report(result_json: Dict) -> str:将结构化结果转为可读的 Markdown
|
|
13
|
+
|
|
14
|
+
- run_with_agent(entry_path, languages=None) -> str:使用单Agent逐条子任务分析模式(复用 jarvis.jarvis_sec.__init__ 的实现)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from dataclasses import asdict
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any, Dict, Iterable, List, Optional, cast
|
|
20
|
+
|
|
21
|
+
import typer
|
|
22
|
+
|
|
23
|
+
from jarvis.jarvis_sec.checkers import analyze_c_files, analyze_rust_files
|
|
24
|
+
from jarvis.jarvis_sec.types import Issue
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ---------------------------
|
|
28
|
+
# 数据结构
|
|
29
|
+
# ---------------------------
|
|
30
|
+
|
|
31
|
+
# Issue dataclass is provided by jarvis.jarvis_sec.types to avoid circular imports
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ---------------------------
|
|
35
|
+
# 工具函数
|
|
36
|
+
# ---------------------------
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _iter_source_files(
|
|
40
|
+
entry_path: str,
|
|
41
|
+
languages: Optional[List[str]] = None,
|
|
42
|
+
exclude_dirs: Optional[List[str]] = None,
|
|
43
|
+
) -> Iterable[Path]:
|
|
44
|
+
"""
|
|
45
|
+
递归枚举源文件,支持按扩展名过滤与目录排除。
|
|
46
|
+
默认语言扩展:c, cpp, h, hpp, rs
|
|
47
|
+
"""
|
|
48
|
+
entry = Path(entry_path)
|
|
49
|
+
if not entry.exists():
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
exts = set((languages or ["c", "cpp", "h", "hpp", "rs"]))
|
|
53
|
+
excludes = set(exclude_dirs or [".git", "build", "out", "target", "dist", "bin", "obj", "third_party", "vendor", "deps", "dependencies", "libs", "libraries", "external", "node_modules", "test", "tests", "__tests__", "spec", "testsuite", "testdata", "benchmark", "benchmarks", "perf", "performance", "bench", "benches", "profiling", "profiler", "example", "examples", "tmp", "temp", "cache", ".cache", "docs", "doc", "documentation", "generated", "gen", "mocks", "fixtures", "samples", "sample", "playground", "sandbox"])
|
|
54
|
+
|
|
55
|
+
for p in entry.rglob("*"):
|
|
56
|
+
if not p.is_file():
|
|
57
|
+
continue
|
|
58
|
+
# 目录排除(任意祖先包含即排除)
|
|
59
|
+
skip = False
|
|
60
|
+
for parent in p.parents:
|
|
61
|
+
if parent.name in excludes:
|
|
62
|
+
skip = True
|
|
63
|
+
break
|
|
64
|
+
if skip:
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
suf = p.suffix.lstrip(".").lower()
|
|
68
|
+
if suf in exts:
|
|
69
|
+
yield p.relative_to(entry)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# ---------------------------
|
|
74
|
+
# 汇总与报告
|
|
75
|
+
# ---------------------------
|
|
76
|
+
|
|
77
|
+
def direct_scan(
|
|
78
|
+
entry_path: str,
|
|
79
|
+
languages: Optional[List[str]] = None,
|
|
80
|
+
exclude_dirs: Optional[List[str]] = None,
|
|
81
|
+
) -> Dict:
|
|
82
|
+
"""
|
|
83
|
+
直扫基线:对 C/C++/Rust 进行启发式扫描,输出结构化 JSON。
|
|
84
|
+
- 改进:委派至模块化检查器(oh_sec.checkers),统一规则与置信度模型。
|
|
85
|
+
"""
|
|
86
|
+
base = Path(entry_path).resolve()
|
|
87
|
+
# 计算实际使用的排除目录列表
|
|
88
|
+
default_excludes = [".git", "build", "out", "target", "dist", "bin", "obj", "third_party", "vendor", "deps", "dependencies", "libs", "libraries", "external", "node_modules", "test", "tests", "__tests__", "spec", "testsuite", "testdata", "benchmark", "benchmarks", "perf", "performance", "bench", "benches", "profiling", "profiler", "example", "examples", "tmp", "temp", "cache", ".cache", "docs", "doc", "documentation", "generated", "gen", "mocks", "fixtures", "samples", "sample", "playground", "sandbox"]
|
|
89
|
+
actual_excludes = exclude_dirs if exclude_dirs is not None else default_excludes
|
|
90
|
+
|
|
91
|
+
# 检查代码库中实际存在的排除目录
|
|
92
|
+
excludes_set = set(actual_excludes)
|
|
93
|
+
actual_excluded_dirs = []
|
|
94
|
+
for item in base.rglob("*"):
|
|
95
|
+
if item.is_dir() and item.name in excludes_set:
|
|
96
|
+
rel_path = item.relative_to(base)
|
|
97
|
+
if str(rel_path) not in actual_excluded_dirs:
|
|
98
|
+
actual_excluded_dirs.append(str(rel_path))
|
|
99
|
+
|
|
100
|
+
if actual_excluded_dirs:
|
|
101
|
+
typer.secho("[jarvis-sec] 实际排除的目录:", fg=typer.colors.BLUE)
|
|
102
|
+
for dir_path in sorted(actual_excluded_dirs):
|
|
103
|
+
typer.secho(f" - {dir_path}", fg=typer.colors.BLUE)
|
|
104
|
+
else:
|
|
105
|
+
typer.secho(f"[jarvis-sec] 未发现需要排除的目录(配置的排除目录: {', '.join(sorted(actual_excludes))})", fg=typer.colors.BLUE)
|
|
106
|
+
|
|
107
|
+
files = list(_iter_source_files(entry_path, languages, exclude_dirs))
|
|
108
|
+
|
|
109
|
+
# 按语言分组
|
|
110
|
+
c_like_exts = {".c", ".cpp", ".h", ".hpp"}
|
|
111
|
+
rust_exts = {".rs"}
|
|
112
|
+
c_files: List[Path] = [p for p in files if p.suffix.lower() in c_like_exts]
|
|
113
|
+
r_files: List[Path] = [p for p in files if p.suffix.lower() in rust_exts]
|
|
114
|
+
|
|
115
|
+
# 调用检查器(保持相对路径,基于 base_path 解析)
|
|
116
|
+
issues_c = analyze_c_files(str(base), [str(p) for p in c_files]) if c_files else []
|
|
117
|
+
issues_r = analyze_rust_files(str(base), [str(p) for p in r_files]) if r_files else []
|
|
118
|
+
issues: List[Issue] = issues_c + issues_r
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
summary: Dict[str, Any] = {
|
|
122
|
+
"total": len(issues),
|
|
123
|
+
"by_language": {"c/cpp": 0, "rust": 0},
|
|
124
|
+
"by_category": {},
|
|
125
|
+
"top_risk_files": [],
|
|
126
|
+
"scanned_files": len(files),
|
|
127
|
+
"scanned_root": str(base),
|
|
128
|
+
}
|
|
129
|
+
file_score: Dict[str, int] = {}
|
|
130
|
+
# Safely update language/category counts with explicit typing
|
|
131
|
+
lang_counts = cast(Dict[str, int], summary["by_language"])
|
|
132
|
+
cat_counts = cast(Dict[str, int], summary["by_category"])
|
|
133
|
+
for it in issues:
|
|
134
|
+
lang_counts[it.language] = lang_counts.get(it.language, 0) + 1
|
|
135
|
+
cat_counts[it.category] = cat_counts.get(it.category, 0) + 1
|
|
136
|
+
file_score[it.file] = file_score.get(it.file, 0) + 1
|
|
137
|
+
# Top 风险文件
|
|
138
|
+
summary["top_risk_files"] = [f for f, _ in sorted(file_score.items(), key=lambda x: x[1], reverse=True)[:10]]
|
|
139
|
+
|
|
140
|
+
result = {
|
|
141
|
+
"summary": summary,
|
|
142
|
+
"issues": [asdict(i) for i in issues],
|
|
143
|
+
}
|
|
144
|
+
return result
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def format_markdown_report(result_json: Dict) -> str:
|
|
148
|
+
"""
|
|
149
|
+
将结构化 JSON 转为 Markdown 可读报告。
|
|
150
|
+
"""
|
|
151
|
+
s = result_json.get("summary", {})
|
|
152
|
+
issues: List[Dict] = result_json.get("issues", [])
|
|
153
|
+
md: List[str] = []
|
|
154
|
+
md.append("# Jarvis 安全问题分析报告(直扫基线)")
|
|
155
|
+
md.append("")
|
|
156
|
+
md.append(f"- 扫描根目录: {s.get('scanned_root', '')}")
|
|
157
|
+
md.append(f"- 扫描文件数: {s.get('scanned_files', 0)}")
|
|
158
|
+
md.append(f"- 检出问题总数: {s.get('total', 0)}")
|
|
159
|
+
md.append("")
|
|
160
|
+
md.append("## 统计概览")
|
|
161
|
+
by_lang = s.get("by_language", {})
|
|
162
|
+
md.append(f"- 按语言: c/cpp={by_lang.get('c/cpp', 0)}, rust={by_lang.get('rust', 0)}")
|
|
163
|
+
md.append("- 按类别:")
|
|
164
|
+
for k, v in s.get("by_category", {}).items():
|
|
165
|
+
md.append(f" - {k}: {v}")
|
|
166
|
+
if s.get("top_risk_files"):
|
|
167
|
+
md.append("- Top 风险文件:")
|
|
168
|
+
for f in s["top_risk_files"]:
|
|
169
|
+
md.append(f" - {f}")
|
|
170
|
+
md.append("")
|
|
171
|
+
md.append("## 详细问题")
|
|
172
|
+
for i, it in enumerate(issues, start=1):
|
|
173
|
+
md.append(f"### [{i}] {it.get('file')}:{it.get('line')} ({it.get('language')}, {it.get('category')})")
|
|
174
|
+
md.append(f"- 模式: {it.get('pattern')}")
|
|
175
|
+
md.append(f"- 证据: `{it.get('evidence')}`")
|
|
176
|
+
md.append(f"- 描述: {it.get('description')}")
|
|
177
|
+
md.append(f"- 建议: {it.get('suggestion')}")
|
|
178
|
+
md.append(f"- 置信度: {it.get('confidence')}, 严重性: {it.get('severity')}")
|
|
179
|
+
md.append("")
|
|
180
|
+
return "\n".join(md)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def run_with_agent(
|
|
184
|
+
entry_path: str,
|
|
185
|
+
languages: Optional[List[str]] = None,
|
|
186
|
+
llm_group: Optional[str] = None,
|
|
187
|
+
report_file: Optional[str] = None,
|
|
188
|
+
cluster_limit: int = 50,
|
|
189
|
+
exclude_dirs: Optional[List[str]] = None,
|
|
190
|
+
enable_verification: bool = True,
|
|
191
|
+
force_save_memory: bool = False,
|
|
192
|
+
output_file: Optional[str] = None,
|
|
193
|
+
) -> str:
|
|
194
|
+
"""
|
|
195
|
+
使用单Agent逐条子任务分析模式运行(与 jarvis.jarvis_sec.__init__ 中保持一致)。
|
|
196
|
+
- 先执行本地直扫,生成候选问题
|
|
197
|
+
- 为每条候选创建一次普通Agent任务进行分析与验证
|
|
198
|
+
- 聚合为最终报告(JSON + Markdown)返回
|
|
199
|
+
|
|
200
|
+
其他:
|
|
201
|
+
- llm_group: 本次分析使用的模型组(仅透传给 Agent,不修改全局配置)
|
|
202
|
+
- report_file: JSONL 报告文件路径(可选,透传)
|
|
203
|
+
- cluster_limit: 聚类时每批次最多处理的告警数(默认 50),当单个文件告警过多时按批次进行聚类
|
|
204
|
+
- exclude_dirs: 要排除的目录列表(可选),默认已包含构建产物(build, out, target, dist, bin, obj)、依赖目录(third_party, vendor, deps, dependencies, libs, libraries, external, node_modules)、测试目录(test, tests, __tests__, spec, testsuite, testdata)、性能测试目录(benchmark, benchmarks, perf, performance, bench, benches, profiling, profiler)、示例目录(example, examples)、临时/缓存(tmp, temp, cache, .cache)、文档(docs, doc, documentation)、生成代码(generated, gen)和其他(mocks, fixtures, samples, sample, playground, sandbox)
|
|
205
|
+
- enable_verification: 是否启用二次验证(默认 True),关闭后分析Agent确认的问题将直接写入报告
|
|
206
|
+
"""
|
|
207
|
+
from jarvis.jarvis_sec import run_security_analysis # 延迟导入,避免循环
|
|
208
|
+
return run_security_analysis(
|
|
209
|
+
entry_path,
|
|
210
|
+
languages=languages,
|
|
211
|
+
llm_group=llm_group,
|
|
212
|
+
report_file=report_file,
|
|
213
|
+
cluster_limit=cluster_limit,
|
|
214
|
+
exclude_dirs=exclude_dirs,
|
|
215
|
+
enable_verification=enable_verification,
|
|
216
|
+
force_save_memory=force_save_memory,
|
|
217
|
+
output_file=output_file,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
__all__ = [
|
|
222
|
+
"Issue",
|
|
223
|
+
"direct_scan",
|
|
224
|
+
"format_markdown_report",
|
|
225
|
+
"run_with_agent",
|
|
226
|
+
]
|
|
@@ -9,7 +9,6 @@ from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
|
9
9
|
from jarvis.jarvis_utils.config import get_shell_name, set_config
|
|
10
10
|
from jarvis.jarvis_utils.input import get_multiline_input
|
|
11
11
|
from jarvis.jarvis_utils.utils import init_env
|
|
12
|
-
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
13
12
|
|
|
14
13
|
app = typer.Typer(
|
|
15
14
|
help="将自然语言要求转换为shell命令",
|
|
@@ -115,8 +114,8 @@ def install_jss_completion(
|
|
|
115
114
|
) -> None:
|
|
116
115
|
"""为指定的shell安装'命令未找到'处理器,实现自然语言命令建议"""
|
|
117
116
|
if shell not in ("fish", "bash", "zsh"):
|
|
118
|
-
|
|
119
|
-
f"错误: 不支持的shell类型: {shell}, 仅支持fish, bash, zsh"
|
|
117
|
+
print(
|
|
118
|
+
f"❌ 错误: 不支持的shell类型: {shell}, 仅支持fish, bash, zsh"
|
|
120
119
|
)
|
|
121
120
|
raise typer.Exit(code=1)
|
|
122
121
|
|
|
@@ -125,7 +124,7 @@ def install_jss_completion(
|
|
|
125
124
|
start_marker, end_marker = _get_markers()
|
|
126
125
|
|
|
127
126
|
if not os.path.exists(config_file):
|
|
128
|
-
|
|
127
|
+
print("ℹ️ 未找到 config.fish 文件,将创建新文件")
|
|
129
128
|
os.makedirs(os.path.dirname(config_file), exist_ok=True)
|
|
130
129
|
with open(config_file, "w") as f:
|
|
131
130
|
f.write("")
|
|
@@ -134,9 +133,8 @@ def install_jss_completion(
|
|
|
134
133
|
content = f.read()
|
|
135
134
|
|
|
136
135
|
if start_marker in content:
|
|
137
|
-
|
|
138
|
-
"JSS fish completion 已安装,请执行: source ~/.config/fish/config.fish"
|
|
139
|
-
OutputType.SUCCESS,
|
|
136
|
+
print(
|
|
137
|
+
"✅ JSS fish completion 已安装,请执行: source ~/.config/fish/config.fish"
|
|
140
138
|
)
|
|
141
139
|
return
|
|
142
140
|
|
|
@@ -157,16 +155,15 @@ end
|
|
|
157
155
|
{end_marker}
|
|
158
156
|
"""
|
|
159
157
|
)
|
|
160
|
-
|
|
161
|
-
"JSS fish completion 已安装,请执行: source ~/.config/fish/config.fish"
|
|
162
|
-
OutputType.SUCCESS,
|
|
158
|
+
print(
|
|
159
|
+
"✅ JSS fish completion 已安装,请执行: source ~/.config/fish/config.fish"
|
|
163
160
|
)
|
|
164
161
|
elif shell == "bash":
|
|
165
162
|
config_file = _get_bash_config_file()
|
|
166
163
|
start_marker, end_marker = _get_bash_markers()
|
|
167
164
|
|
|
168
165
|
if not os.path.exists(config_file):
|
|
169
|
-
|
|
166
|
+
print("ℹ️ 未找到 ~/.bashrc 文件,将创建新文件")
|
|
170
167
|
os.makedirs(os.path.dirname(config_file), exist_ok=True)
|
|
171
168
|
with open(config_file, "w") as f:
|
|
172
169
|
f.write("")
|
|
@@ -175,9 +172,8 @@ end
|
|
|
175
172
|
content = f.read()
|
|
176
173
|
|
|
177
174
|
if start_marker in content:
|
|
178
|
-
|
|
179
|
-
"JSS bash completion 已安装,请执行: source ~/.bashrc"
|
|
180
|
-
OutputType.SUCCESS,
|
|
175
|
+
print(
|
|
176
|
+
"✅ JSS bash completion 已安装,请执行: source ~/.bashrc"
|
|
181
177
|
)
|
|
182
178
|
return
|
|
183
179
|
else:
|
|
@@ -221,16 +217,15 @@ command_not_found_handle() {{
|
|
|
221
217
|
{end_marker}
|
|
222
218
|
"""
|
|
223
219
|
)
|
|
224
|
-
|
|
225
|
-
"JSS bash completion 已安装,请执行: source ~/.bashrc"
|
|
226
|
-
OutputType.SUCCESS,
|
|
220
|
+
print(
|
|
221
|
+
"✅ JSS bash completion 已安装,请执行: source ~/.bashrc"
|
|
227
222
|
)
|
|
228
223
|
elif shell == "zsh":
|
|
229
224
|
config_file = _get_zsh_config_file()
|
|
230
225
|
start_marker, end_marker = _get_zsh_markers()
|
|
231
226
|
|
|
232
227
|
if not os.path.exists(config_file):
|
|
233
|
-
|
|
228
|
+
print("ℹ️ 未找到 ~/.zshrc 文件,将创建新文件")
|
|
234
229
|
os.makedirs(os.path.dirname(config_file), exist_ok=True)
|
|
235
230
|
with open(config_file, "w") as f:
|
|
236
231
|
f.write("")
|
|
@@ -239,8 +234,8 @@ command_not_found_handle() {{
|
|
|
239
234
|
content = f.read()
|
|
240
235
|
|
|
241
236
|
if start_marker in content:
|
|
242
|
-
|
|
243
|
-
"JSS zsh completion 已安装,请执行: source ~/.zshrc"
|
|
237
|
+
print(
|
|
238
|
+
"✅ JSS zsh completion 已安装,请执行: source ~/.zshrc"
|
|
244
239
|
)
|
|
245
240
|
return
|
|
246
241
|
|
|
@@ -288,55 +283,11 @@ command_not_found_handler() {{
|
|
|
288
283
|
{end_marker}
|
|
289
284
|
"""
|
|
290
285
|
)
|
|
291
|
-
|
|
292
|
-
"JSS zsh completion 已安装,请执行: source ~/.zshrc"
|
|
286
|
+
print(
|
|
287
|
+
"✅ JSS zsh completion 已安装,请执行: source ~/.zshrc"
|
|
293
288
|
)
|
|
294
289
|
return
|
|
295
290
|
|
|
296
|
-
with open(config_file, "a") as f:
|
|
297
|
-
f.write(
|
|
298
|
-
f"""
|
|
299
|
-
{start_marker}
|
|
300
|
-
# Bash 'command not found' handler for JSS
|
|
301
|
-
# 行为:
|
|
302
|
-
# - 生成可编辑的建议命令,用户可直接编辑后回车执行
|
|
303
|
-
# - 非交互模式下仅打印建议
|
|
304
|
-
command_not_found_handle() {{
|
|
305
|
-
local cmd="$1"
|
|
306
|
-
shift || true
|
|
307
|
-
local text="$cmd $*"
|
|
308
|
-
|
|
309
|
-
# 与 fish 行为保持一致:对过短输入不处理
|
|
310
|
-
if [ ${{#text}} -lt 10 ]; then
|
|
311
|
-
return 127
|
|
312
|
-
fi
|
|
313
|
-
|
|
314
|
-
local suggestion edited
|
|
315
|
-
suggestion=$(jss request "$text")
|
|
316
|
-
if [ -n "$suggestion" ]; then
|
|
317
|
-
# 交互式:用 readline 预填命令,用户可直接回车执行或编辑
|
|
318
|
-
if [[ $- == *i* ]]; then
|
|
319
|
-
edited="$suggestion"
|
|
320
|
-
# -e 启用 readline;-i 预填默认值;无提示前缀,使体验更接近 fish 的“替换命令行”
|
|
321
|
-
read -e -i "$edited" edited
|
|
322
|
-
if [ -n "$edited" ]; then
|
|
323
|
-
eval "$edited"
|
|
324
|
-
return $?
|
|
325
|
-
fi
|
|
326
|
-
else
|
|
327
|
-
# 非交互:仅打印建议
|
|
328
|
-
printf '%s\n' "$suggestion"
|
|
329
|
-
fi
|
|
330
|
-
fi
|
|
331
|
-
return 127
|
|
332
|
-
}}
|
|
333
|
-
{end_marker}
|
|
334
|
-
"""
|
|
335
|
-
)
|
|
336
|
-
PrettyOutput.print(
|
|
337
|
-
"JSS bash completion 已安装,请执行: source ~/.bashrc", OutputType.SUCCESS
|
|
338
|
-
)
|
|
339
|
-
|
|
340
291
|
|
|
341
292
|
@app.command("uninstall")
|
|
342
293
|
def uninstall_jss_completion(
|
|
@@ -344,8 +295,8 @@ def uninstall_jss_completion(
|
|
|
344
295
|
) -> None:
|
|
345
296
|
"""卸载JSS shell'命令未找到'处理器"""
|
|
346
297
|
if shell not in ("fish", "bash", "zsh"):
|
|
347
|
-
|
|
348
|
-
f"错误: 不支持的shell类型: {shell}, 仅支持fish, bash, zsh"
|
|
298
|
+
print(
|
|
299
|
+
f"❌ 错误: 不支持的shell类型: {shell}, 仅支持fish, bash, zsh"
|
|
349
300
|
)
|
|
350
301
|
raise typer.Exit(code=1)
|
|
351
302
|
|
|
@@ -354,8 +305,8 @@ def uninstall_jss_completion(
|
|
|
354
305
|
start_marker, end_marker = _get_markers()
|
|
355
306
|
|
|
356
307
|
if not os.path.exists(config_file):
|
|
357
|
-
|
|
358
|
-
"未找到 JSS fish completion 配置,无需卸载"
|
|
308
|
+
print(
|
|
309
|
+
"ℹ️ 未找到 JSS fish completion 配置,无需卸载"
|
|
359
310
|
)
|
|
360
311
|
return
|
|
361
312
|
|
|
@@ -363,8 +314,8 @@ def uninstall_jss_completion(
|
|
|
363
314
|
content = f.read()
|
|
364
315
|
|
|
365
316
|
if start_marker not in content:
|
|
366
|
-
|
|
367
|
-
"未找到 JSS fish completion 配置,无需卸载"
|
|
317
|
+
print(
|
|
318
|
+
"ℹ️ 未找到 JSS fish completion 配置,无需卸载"
|
|
368
319
|
)
|
|
369
320
|
return
|
|
370
321
|
|
|
@@ -373,17 +324,16 @@ def uninstall_jss_completion(
|
|
|
373
324
|
with open(config_file, "w") as f:
|
|
374
325
|
f.write(new_content)
|
|
375
326
|
|
|
376
|
-
|
|
377
|
-
"JSS fish completion 已卸载,请执行: source ~/.config/fish/config.fish"
|
|
378
|
-
OutputType.SUCCESS,
|
|
327
|
+
print(
|
|
328
|
+
"✅ JSS fish completion 已卸载,请执行: source ~/.config/fish/config.fish"
|
|
379
329
|
)
|
|
380
330
|
elif shell == "bash":
|
|
381
331
|
config_file = _get_bash_config_file()
|
|
382
332
|
start_marker, end_marker = _get_bash_markers()
|
|
383
333
|
|
|
384
334
|
if not os.path.exists(config_file):
|
|
385
|
-
|
|
386
|
-
"未找到 JSS bash completion 配置,无需卸载"
|
|
335
|
+
print(
|
|
336
|
+
"ℹ️ 未找到 JSS bash completion 配置,无需卸载"
|
|
387
337
|
)
|
|
388
338
|
return
|
|
389
339
|
|
|
@@ -391,8 +341,8 @@ def uninstall_jss_completion(
|
|
|
391
341
|
content = f.read()
|
|
392
342
|
|
|
393
343
|
if start_marker not in content:
|
|
394
|
-
|
|
395
|
-
"未找到 JSS bash completion 配置,无需卸载"
|
|
344
|
+
print(
|
|
345
|
+
"ℹ️ 未找到 JSS bash completion 配置,无需卸载"
|
|
396
346
|
)
|
|
397
347
|
return
|
|
398
348
|
|
|
@@ -401,16 +351,16 @@ def uninstall_jss_completion(
|
|
|
401
351
|
with open(config_file, "w") as f:
|
|
402
352
|
f.write(new_content)
|
|
403
353
|
|
|
404
|
-
|
|
405
|
-
"JSS bash completion 已卸载,请执行: source ~/.bashrc"
|
|
354
|
+
print(
|
|
355
|
+
"✅ JSS bash completion 已卸载,请执行: source ~/.bashrc"
|
|
406
356
|
)
|
|
407
357
|
elif shell == "zsh":
|
|
408
358
|
config_file = _get_zsh_config_file()
|
|
409
359
|
start_marker, end_marker = _get_zsh_markers()
|
|
410
360
|
|
|
411
361
|
if not os.path.exists(config_file):
|
|
412
|
-
|
|
413
|
-
"未找到 JSS zsh completion 配置,无需卸载"
|
|
362
|
+
print(
|
|
363
|
+
"ℹ️ 未找到 JSS zsh completion 配置,无需卸载"
|
|
414
364
|
)
|
|
415
365
|
return
|
|
416
366
|
|
|
@@ -418,8 +368,8 @@ def uninstall_jss_completion(
|
|
|
418
368
|
content = f.read()
|
|
419
369
|
|
|
420
370
|
if start_marker not in content:
|
|
421
|
-
|
|
422
|
-
"未找到 JSS zsh completion 配置,无需卸载"
|
|
371
|
+
print(
|
|
372
|
+
"ℹ️ 未找到 JSS zsh completion 配置,无需卸载"
|
|
423
373
|
)
|
|
424
374
|
return
|
|
425
375
|
|
|
@@ -428,8 +378,8 @@ def uninstall_jss_completion(
|
|
|
428
378
|
with open(config_file, "w") as f:
|
|
429
379
|
f.write(new_content)
|
|
430
380
|
|
|
431
|
-
|
|
432
|
-
"JSS zsh completion 已卸载,请执行: source ~/.zshrc"
|
|
381
|
+
print(
|
|
382
|
+
"✅ JSS zsh completion 已卸载,请执行: source ~/.zshrc"
|
|
433
383
|
)
|
|
434
384
|
|
|
435
385
|
|
|
@@ -444,6 +394,7 @@ def process_request(request: str) -> Optional[str]:
|
|
|
444
394
|
"""
|
|
445
395
|
try:
|
|
446
396
|
# Get language model instance
|
|
397
|
+
# 使用normal平台,智能shell命令生成是一般任务
|
|
447
398
|
model = PlatformRegistry.get_global_platform_registry().get_normal_platform()
|
|
448
399
|
|
|
449
400
|
shell = get_shell_name()
|
jarvis/jarvis_stats/cli.py
CHANGED
|
@@ -190,7 +190,7 @@ def list():
|
|
|
190
190
|
try:
|
|
191
191
|
dt = datetime.fromisoformat(last_updated)
|
|
192
192
|
last_updated = dt.strftime("%Y-%m-%d %H:%M")
|
|
193
|
-
except:
|
|
193
|
+
except Exception:
|
|
194
194
|
pass
|
|
195
195
|
|
|
196
196
|
# 获取数据点数和标签
|
|
@@ -379,7 +379,7 @@ def demo():
|
|
|
379
379
|
def main():
|
|
380
380
|
"""主入口函数"""
|
|
381
381
|
# 初始化环境,防止设置初始化太迟
|
|
382
|
-
init_env(
|
|
382
|
+
init_env()
|
|
383
383
|
app()
|
|
384
384
|
|
|
385
385
|
|
jarvis/jarvis_stats/stats.py
CHANGED
|
@@ -9,7 +9,7 @@ from typing import Dict, List, Optional, Union, Any
|
|
|
9
9
|
|
|
10
10
|
from jarvis.jarvis_stats.storage import StatsStorage
|
|
11
11
|
from jarvis.jarvis_stats.visualizer import StatsVisualizer
|
|
12
|
-
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
12
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput # 保留用于语法高亮
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class StatsManager:
|
|
@@ -480,7 +480,7 @@ class StatsManager:
|
|
|
480
480
|
try:
|
|
481
481
|
dt = datetime.fromisoformat(last_updated)
|
|
482
482
|
last_updated = dt.strftime("%Y-%m-%d %H:%M")
|
|
483
|
-
except:
|
|
483
|
+
except Exception:
|
|
484
484
|
pass
|
|
485
485
|
|
|
486
486
|
total_value = sum(r.get("value", 0) for r in records)
|
|
@@ -549,8 +549,8 @@ class StatsManager:
|
|
|
549
549
|
)
|
|
550
550
|
|
|
551
551
|
if not aggregated:
|
|
552
|
-
|
|
553
|
-
f"没有找到指标 '{metric_name}' 的数据"
|
|
552
|
+
print(
|
|
553
|
+
f"⚠️ 没有找到指标 '{metric_name}' 的数据"
|
|
554
554
|
)
|
|
555
555
|
return
|
|
556
556
|
|
|
@@ -585,7 +585,7 @@ class StatsManager:
|
|
|
585
585
|
show_values=True,
|
|
586
586
|
)
|
|
587
587
|
|
|
588
|
-
PrettyOutput.print(chart, OutputType.CODE, lang="text")
|
|
588
|
+
PrettyOutput.print(chart, OutputType.CODE, lang="text") # 保留用于语法高亮
|
|
589
589
|
|
|
590
590
|
# 显示时间范围
|
|
591
591
|
from rich.panel import Panel
|
|
@@ -681,8 +681,8 @@ class StatsManager:
|
|
|
681
681
|
)
|
|
682
682
|
|
|
683
683
|
if not aggregated:
|
|
684
|
-
|
|
685
|
-
f"没有找到指标 '{metric_name}' 的数据"
|
|
684
|
+
print(
|
|
685
|
+
f"⚠️ 没有找到指标 '{metric_name}' 的数据"
|
|
686
686
|
)
|
|
687
687
|
return
|
|
688
688
|
|
|
@@ -693,7 +693,7 @@ class StatsManager:
|
|
|
693
693
|
# 显示汇总
|
|
694
694
|
summary = visualizer.show_summary(aggregated, metric_name, unit, tags)
|
|
695
695
|
if summary: # 如果返回了内容才打印(兼容性)
|
|
696
|
-
|
|
696
|
+
print(f"ℹ️ {summary}")
|
|
697
697
|
|
|
698
698
|
# 显示时间范围
|
|
699
699
|
from rich.panel import Panel
|
jarvis/jarvis_stats/storage.py
CHANGED
|
@@ -71,7 +71,7 @@ class StatsStorage:
|
|
|
71
71
|
with open(filepath, "r", encoding="utf-8") as f:
|
|
72
72
|
data = json.load(f)
|
|
73
73
|
return data
|
|
74
|
-
except
|
|
74
|
+
except Exception:
|
|
75
75
|
if attempt < max_retries - 1:
|
|
76
76
|
time.sleep(0.1 * (attempt + 1)) # 递增延迟
|
|
77
77
|
continue
|
|
@@ -160,6 +160,9 @@ class StatsStorage:
|
|
|
160
160
|
|
|
161
161
|
# 更新元数据
|
|
162
162
|
meta = self._load_json(self.meta_file)
|
|
163
|
+
# 确保 meta 字典中有 "metrics" 键
|
|
164
|
+
if "metrics" not in meta:
|
|
165
|
+
meta["metrics"] = {}
|
|
163
166
|
if metric_name not in meta["metrics"]:
|
|
164
167
|
meta["metrics"][metric_name] = {
|
|
165
168
|
"unit": unit,
|
|
@@ -218,9 +221,9 @@ class StatsStorage:
|
|
|
218
221
|
new_total = current_total + float(value)
|
|
219
222
|
self._save_text_atomic(total_file, str(new_total))
|
|
220
223
|
else:
|
|
221
|
-
#
|
|
222
|
-
#
|
|
223
|
-
|
|
224
|
+
# 首次生成:直接写入当前值,避免扫描所有历史文件
|
|
225
|
+
# 如果后续需要精确总量,可以通过 get_metric_total 重新计算
|
|
226
|
+
self._save_text_atomic(total_file, str(float(value)))
|
|
224
227
|
except Exception:
|
|
225
228
|
# 静默失败,不影响主流程
|
|
226
229
|
pass
|
|
@@ -452,21 +455,11 @@ class StatsStorage:
|
|
|
452
455
|
|
|
453
456
|
def list_metrics(self) -> List[str]:
|
|
454
457
|
"""列出所有指标"""
|
|
455
|
-
#
|
|
458
|
+
# 从元数据文件获取指标(主要来源,避免扫描所有历史文件)
|
|
456
459
|
meta = self._load_json(self.meta_file)
|
|
457
460
|
metrics_from_meta = set(meta.get("metrics", {}).keys())
|
|
458
461
|
|
|
459
|
-
#
|
|
460
|
-
metrics_from_data: Set[str] = set()
|
|
461
|
-
for data_file in self.data_dir.glob("stats_*.json"):
|
|
462
|
-
try:
|
|
463
|
-
data = self._load_json(data_file)
|
|
464
|
-
metrics_from_data.update(data.keys())
|
|
465
|
-
except (json.JSONDecodeError, OSError):
|
|
466
|
-
# 忽略无法读取的文件
|
|
467
|
-
continue
|
|
468
|
-
|
|
469
|
-
# 扫描总量缓存目录中已有的指标文件
|
|
462
|
+
# 扫描总量缓存目录中已有的指标文件(快速)
|
|
470
463
|
metrics_from_totals: Set[str] = set()
|
|
471
464
|
try:
|
|
472
465
|
for f in self.totals_dir.glob("*"):
|
|
@@ -475,10 +468,9 @@ class StatsStorage:
|
|
|
475
468
|
except Exception:
|
|
476
469
|
pass
|
|
477
470
|
|
|
478
|
-
#
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
)
|
|
471
|
+
# 合并两个来源的指标并返回排序后的列表
|
|
472
|
+
# 注意:不再扫描所有历史数据文件,避免性能问题
|
|
473
|
+
all_metrics = metrics_from_meta.union(metrics_from_totals)
|
|
482
474
|
return sorted(list(all_metrics))
|
|
483
475
|
|
|
484
476
|
def aggregate_metrics(
|
|
@@ -560,7 +552,9 @@ class StatsStorage:
|
|
|
560
552
|
"""
|
|
561
553
|
# 检查指标是否存在
|
|
562
554
|
meta = self._load_json(self.meta_file)
|
|
563
|
-
if
|
|
555
|
+
if "metrics" not in meta:
|
|
556
|
+
meta["metrics"] = {}
|
|
557
|
+
if metric_name not in meta["metrics"]:
|
|
564
558
|
return False
|
|
565
559
|
|
|
566
560
|
# 从元数据中删除指标
|