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
|
@@ -34,2063 +34,281 @@ Rust 代码优化器:对转译或生成后的 Rust 项目执行若干保守优
|
|
|
34
34
|
|
|
35
35
|
from __future__ import annotations
|
|
36
36
|
|
|
37
|
-
import
|
|
38
|
-
import os
|
|
39
|
-
import shutil
|
|
40
|
-
import subprocess
|
|
41
|
-
from dataclasses import dataclass, asdict
|
|
37
|
+
from dataclasses import asdict
|
|
42
38
|
from pathlib import Path
|
|
43
|
-
from typing import
|
|
44
|
-
import
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
from jarvis.
|
|
50
|
-
from jarvis.
|
|
51
|
-
from jarvis.jarvis_c2rust.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
non_interactive: bool = True
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
@dataclass
|
|
76
|
-
class OptimizeStats:
|
|
77
|
-
files_scanned: int = 0
|
|
78
|
-
unsafe_removed: int = 0
|
|
79
|
-
unsafe_annotated: int = 0
|
|
80
|
-
visibility_downgraded: int = 0
|
|
81
|
-
docs_added: int = 0
|
|
82
|
-
cargo_checks: int = 0
|
|
83
|
-
errors: List[str] = None
|
|
84
|
-
|
|
85
|
-
def __post_init__(self):
|
|
86
|
-
if self.errors is None:
|
|
87
|
-
self.errors = []
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def _run_cmd(cmd: List[str], cwd: Path, env: Optional[Dict[str, str]] = None, timeout: Optional[int] = None) -> Tuple[int, str, str]:
|
|
91
|
-
p = subprocess.Popen(
|
|
92
|
-
cmd,
|
|
93
|
-
cwd=str(cwd),
|
|
94
|
-
stdout=subprocess.PIPE,
|
|
95
|
-
stderr=subprocess.PIPE,
|
|
96
|
-
text=True,
|
|
97
|
-
env=dict(os.environ, **(env or {})),
|
|
98
|
-
)
|
|
99
|
-
try:
|
|
100
|
-
out, err = p.communicate(timeout=timeout if timeout and timeout > 0 else None)
|
|
101
|
-
return p.returncode, out, err
|
|
102
|
-
except subprocess.TimeoutExpired:
|
|
103
|
-
p.kill()
|
|
104
|
-
out, err = p.communicate()
|
|
105
|
-
err_msg = f"Command '{' '.join(cmd)}' timed out after {timeout} seconds."
|
|
106
|
-
if err:
|
|
107
|
-
err_msg += f"\n{err}"
|
|
108
|
-
return -1, out, err_msg
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def _cargo_check(crate_dir: Path, stats: OptimizeStats, max_checks: int, timeout: Optional[int] = None) -> Tuple[bool, str]:
|
|
112
|
-
# 统一使用 cargo test 作为验证手段
|
|
113
|
-
if max_checks and stats.cargo_checks >= max_checks:
|
|
114
|
-
return False, "cargo test budget exhausted"
|
|
115
|
-
code, out, err = _run_cmd(["cargo", "test", "-q"], crate_dir, timeout=timeout)
|
|
116
|
-
stats.cargo_checks += 1
|
|
117
|
-
ok = code == 0
|
|
118
|
-
diag = err.strip() or out.strip()
|
|
119
|
-
# 取首行作为摘要
|
|
120
|
-
first_line = next((ln for ln in diag.splitlines() if ln.strip()), "")
|
|
121
|
-
return ok, first_line
|
|
122
|
-
|
|
123
|
-
def _run_cargo_fmt(crate_dir: Path) -> None:
|
|
124
|
-
"""
|
|
125
|
-
执行 cargo fmt 格式化代码。
|
|
126
|
-
fmt 失败不影响主流程,只记录警告。
|
|
127
|
-
"""
|
|
128
|
-
try:
|
|
129
|
-
res = subprocess.run(
|
|
130
|
-
["cargo", "fmt"],
|
|
131
|
-
capture_output=True,
|
|
132
|
-
text=True,
|
|
133
|
-
check=False,
|
|
134
|
-
cwd=str(crate_dir),
|
|
135
|
-
)
|
|
136
|
-
if res.returncode == 0:
|
|
137
|
-
typer.secho("[c2rust-optimizer][fmt] 代码格式化完成", fg=typer.colors.CYAN)
|
|
138
|
-
else:
|
|
139
|
-
# fmt 失败不影响主流程,只记录警告
|
|
140
|
-
typer.secho(f"[c2rust-optimizer][fmt] 代码格式化失败(非致命): {res.stderr or res.stdout}", fg=typer.colors.YELLOW)
|
|
141
|
-
except Exception as e:
|
|
142
|
-
# fmt 失败不影响主流程,只记录警告
|
|
143
|
-
typer.secho(f"[c2rust-optimizer][fmt] 代码格式化异常(非致命): {e}", fg=typer.colors.YELLOW)
|
|
144
|
-
|
|
145
|
-
def _check_clippy_warnings(crate_dir: Path) -> Tuple[bool, str]:
|
|
146
|
-
"""
|
|
147
|
-
检查是否有 clippy 告警。
|
|
148
|
-
使用 JSON 格式输出,便于精确解析和指定警告。
|
|
149
|
-
返回 (has_warnings, json_output),has_warnings 为 True 表示有告警,json_output 为 JSON 格式的输出。
|
|
150
|
-
"""
|
|
151
|
-
try:
|
|
152
|
-
res = subprocess.run(
|
|
153
|
-
["cargo", "clippy", "--message-format=json", "--", "-W", "clippy::all"],
|
|
154
|
-
capture_output=True,
|
|
155
|
-
text=True,
|
|
156
|
-
check=False,
|
|
157
|
-
cwd=str(crate_dir),
|
|
158
|
-
)
|
|
159
|
-
# clippy 的 JSON 输出通常在 stdout
|
|
160
|
-
stdout_output = (res.stdout or "").strip()
|
|
161
|
-
stderr_output = (res.stderr or "").strip()
|
|
162
|
-
|
|
163
|
-
# 解析 JSON 输出,提取警告信息
|
|
164
|
-
warnings = []
|
|
165
|
-
if stdout_output:
|
|
166
|
-
for line in stdout_output.splitlines():
|
|
167
|
-
line = line.strip()
|
|
168
|
-
if not line:
|
|
169
|
-
continue
|
|
170
|
-
try:
|
|
171
|
-
msg = json.loads(line)
|
|
172
|
-
# 只处理 warning 类型的消息
|
|
173
|
-
if msg.get("reason") == "compiler-message" and msg.get("message", {}).get("level") == "warning":
|
|
174
|
-
warnings.append(msg)
|
|
175
|
-
except (json.JSONDecodeError, KeyError, TypeError):
|
|
176
|
-
# 忽略无法解析的行(可能是其他输出)
|
|
177
|
-
continue
|
|
178
|
-
|
|
179
|
-
has_warnings = len(warnings) > 0
|
|
180
|
-
|
|
181
|
-
# 调试输出
|
|
182
|
-
if has_warnings:
|
|
183
|
-
typer.secho(f"[c2rust-optimizer][clippy-check] 检测到 {len(warnings)} 个 Clippy 告警", fg=typer.colors.YELLOW)
|
|
184
|
-
elif res.returncode != 0:
|
|
185
|
-
# 如果返回码非零但没有警告,可能是编译错误
|
|
186
|
-
typer.secho(f"[c2rust-optimizer][clippy-check] Clippy 返回非零退出码({res.returncode}),但未检测到告警,可能是编译错误", fg=typer.colors.CYAN)
|
|
187
|
-
if stderr_output:
|
|
188
|
-
typer.secho(f"[c2rust-optimizer][clippy-check] 错误输出预览(前200字符): {stderr_output[:200]}", fg=typer.colors.CYAN)
|
|
189
|
-
|
|
190
|
-
# 返回 JSON 格式的输出(用于后续解析)
|
|
191
|
-
return has_warnings, stdout_output
|
|
192
|
-
except Exception as e:
|
|
193
|
-
# 检查失败时假设没有告警,避免阻塞流程
|
|
194
|
-
typer.secho(f"[c2rust-optimizer][clippy-check] 检查 Clippy 告警异常(非致命): {e}", fg=typer.colors.YELLOW)
|
|
195
|
-
return False, ""
|
|
196
|
-
|
|
197
|
-
def _check_missing_safety_doc_warnings(crate_dir: Path) -> Tuple[bool, str]:
|
|
198
|
-
"""
|
|
199
|
-
检查是否有 missing_safety_doc 告警。
|
|
200
|
-
使用 JSON 格式输出,便于精确解析和指定警告。
|
|
201
|
-
返回 (has_warnings, json_output),has_warnings 为 True 表示有告警,json_output 为 JSON 格式的输出。
|
|
202
|
-
"""
|
|
203
|
-
try:
|
|
204
|
-
res = subprocess.run(
|
|
205
|
-
["cargo", "clippy", "--message-format=json", "--", "-W", "clippy::missing_safety_doc"],
|
|
206
|
-
capture_output=True,
|
|
207
|
-
text=True,
|
|
208
|
-
check=False,
|
|
209
|
-
cwd=str(crate_dir),
|
|
210
|
-
)
|
|
211
|
-
# clippy 的 JSON 输出通常在 stdout
|
|
212
|
-
stdout_output = (res.stdout or "").strip()
|
|
213
|
-
stderr_output = (res.stderr or "").strip()
|
|
214
|
-
|
|
215
|
-
# 解析 JSON 输出,提取警告信息
|
|
216
|
-
warnings = []
|
|
217
|
-
if stdout_output:
|
|
218
|
-
for line in stdout_output.splitlines():
|
|
219
|
-
line = line.strip()
|
|
220
|
-
if not line:
|
|
221
|
-
continue
|
|
222
|
-
try:
|
|
223
|
-
msg = json.loads(line)
|
|
224
|
-
# 只处理 warning 类型的消息,且是 missing_safety_doc
|
|
225
|
-
if msg.get("reason") == "compiler-message":
|
|
226
|
-
message = msg.get("message", {})
|
|
227
|
-
if message.get("level") == "warning":
|
|
228
|
-
code = message.get("code", {})
|
|
229
|
-
code_str = code.get("code", "") if code else ""
|
|
230
|
-
if "missing_safety_doc" in code_str:
|
|
231
|
-
warnings.append(msg)
|
|
232
|
-
except (json.JSONDecodeError, KeyError, TypeError):
|
|
233
|
-
# 忽略无法解析的行(可能是其他输出)
|
|
234
|
-
continue
|
|
235
|
-
|
|
236
|
-
has_warnings = len(warnings) > 0
|
|
237
|
-
|
|
238
|
-
# 调试输出
|
|
239
|
-
if has_warnings:
|
|
240
|
-
typer.secho(f"[c2rust-optimizer][missing-safety-doc-check] 检测到 {len(warnings)} 个 missing_safety_doc 告警", fg=typer.colors.YELLOW)
|
|
241
|
-
elif res.returncode != 0:
|
|
242
|
-
# 如果返回码非零但没有警告,可能是编译错误
|
|
243
|
-
typer.secho(f"[c2rust-optimizer][missing-safety-doc-check] Clippy 返回非零退出码({res.returncode}),但未检测到告警,可能是编译错误", fg=typer.colors.CYAN)
|
|
244
|
-
if stderr_output:
|
|
245
|
-
typer.secho(f"[c2rust-optimizer][missing-safety-doc-check] 错误输出预览(前200字符): {stderr_output[:200]}", fg=typer.colors.CYAN)
|
|
246
|
-
|
|
247
|
-
# 返回 JSON 格式的输出(用于后续解析)
|
|
248
|
-
return has_warnings, stdout_output
|
|
249
|
-
except Exception as e:
|
|
250
|
-
# 检查失败时假设没有告警,避免阻塞流程
|
|
251
|
-
typer.secho(f"[c2rust-optimizer][missing-safety-doc-check] 检查 missing_safety_doc 告警异常(非致命): {e}", fg=typer.colors.YELLOW)
|
|
252
|
-
return False, ""
|
|
253
|
-
|
|
254
|
-
def _cargo_check_full(crate_dir: Path, stats: OptimizeStats, max_checks: int, timeout: Optional[int] = None) -> Tuple[bool, str]:
|
|
255
|
-
"""
|
|
256
|
-
执行 cargo test,返回是否成功与完整输出(stdout+stderr)。
|
|
257
|
-
会计入 stats.cargo_checks,并受 max_checks 预算限制。
|
|
258
|
-
"""
|
|
259
|
-
if max_checks and stats.cargo_checks >= max_checks:
|
|
260
|
-
return False, "cargo test budget exhausted"
|
|
261
|
-
try:
|
|
262
|
-
res = subprocess.run(
|
|
263
|
-
["cargo", "test", "-q"],
|
|
264
|
-
capture_output=True,
|
|
265
|
-
text=True,
|
|
266
|
-
check=False,
|
|
267
|
-
cwd=str(crate_dir),
|
|
268
|
-
timeout=timeout if timeout and timeout > 0 else None,
|
|
269
|
-
)
|
|
270
|
-
stats.cargo_checks += 1
|
|
271
|
-
ok = (res.returncode == 0)
|
|
272
|
-
out = (res.stdout or "")
|
|
273
|
-
err = (res.stderr or "")
|
|
274
|
-
msg = (out + ("\n" + err if err else "")).strip()
|
|
275
|
-
return ok, msg
|
|
276
|
-
except subprocess.TimeoutExpired as e:
|
|
277
|
-
stats.cargo_checks += 1
|
|
278
|
-
out_s = e.stdout.decode("utf-8", errors="ignore") if e.stdout else ""
|
|
279
|
-
err_s = e.stderr.decode("utf-8", errors="ignore") if e.stderr else ""
|
|
280
|
-
msg = f"cargo test timed out after {timeout} seconds"
|
|
281
|
-
full_output = (out_s + ("\n" + err_s if err_s else "")).strip()
|
|
282
|
-
if full_output:
|
|
283
|
-
msg += f"\nOutput:\n{full_output}"
|
|
284
|
-
return False, msg
|
|
285
|
-
except Exception as e:
|
|
286
|
-
stats.cargo_checks += 1
|
|
287
|
-
return False, f"cargo test exception: {e}"
|
|
288
|
-
|
|
289
|
-
def _git_toplevel(start: Path) -> Optional[Path]:
|
|
290
|
-
"""
|
|
291
|
-
返回包含 start 的 Git 仓库根目录(--show-toplevel)。若不在仓库中则返回 None。
|
|
292
|
-
"""
|
|
293
|
-
try:
|
|
294
|
-
code, out, err = _run_cmd(["git", "rev-parse", "--show-toplevel"], start)
|
|
295
|
-
if code == 0:
|
|
296
|
-
p = (out or "").strip()
|
|
297
|
-
if p:
|
|
298
|
-
return Path(p)
|
|
299
|
-
return None
|
|
300
|
-
except Exception:
|
|
301
|
-
return None
|
|
302
|
-
|
|
303
|
-
def _git_head_commit(root: Path) -> Optional[str]:
|
|
304
|
-
try:
|
|
305
|
-
code, out, err = _run_cmd(["git", "rev-parse", "--verify", "HEAD"], root)
|
|
306
|
-
if code == 0:
|
|
307
|
-
return out.strip()
|
|
308
|
-
return None
|
|
309
|
-
except Exception:
|
|
310
|
-
return None
|
|
311
|
-
|
|
312
|
-
def _git_reset_hard(root: Path, commit: str) -> bool:
|
|
313
|
-
try:
|
|
314
|
-
code, _, _ = _run_cmd(["git", "reset", "--hard", commit], root)
|
|
315
|
-
if code != 0:
|
|
316
|
-
return False
|
|
317
|
-
return True
|
|
318
|
-
except Exception:
|
|
319
|
-
return False
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
def _iter_rust_files(crate_dir: Path) -> Iterable[Path]:
|
|
323
|
-
src = crate_dir / "src"
|
|
324
|
-
if not src.exists():
|
|
325
|
-
# 仍尝试遍历整个 crate 目录,但优先 src
|
|
326
|
-
yield from crate_dir.rglob("*.rs")
|
|
327
|
-
return
|
|
328
|
-
# 遍历 src 优先
|
|
329
|
-
yield from src.rglob("*.rs")
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
def _read_file(path: Path) -> str:
|
|
333
|
-
return path.read_text(encoding="utf-8")
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
def _write_file(path: Path, content: str) -> None:
|
|
337
|
-
path.write_text(content, encoding="utf-8")
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
def _backup_file(path: Path) -> Path:
|
|
341
|
-
bak = path.with_suffix(path.suffix + ".bak_opt")
|
|
342
|
-
shutil.copy2(path, bak)
|
|
343
|
-
return bak
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
def _restore_file_from_backup(path: Path, backup: Path) -> None:
|
|
347
|
-
shutil.move(str(backup), str(path))
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
def _remove_backup(backup: Path) -> None:
|
|
351
|
-
if backup.exists():
|
|
352
|
-
backup.unlink(missing_ok=True)
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
def _ensure_report_dir(crate_dir: Path) -> Path:
|
|
356
|
-
report_dir = crate_dir / ".jarvis" / "c2rust"
|
|
357
|
-
report_dir.mkdir(parents=True, exist_ok=True)
|
|
358
|
-
return report_dir
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
def _find_project_root() -> Optional[Path]:
|
|
362
|
-
"""
|
|
363
|
-
查找项目根目录(包含 .jarvis/c2rust 的目录)。
|
|
364
|
-
从当前目录向上查找,最多向上查找 5 层。
|
|
365
|
-
"""
|
|
366
|
-
from jarvis.jarvis_c2rust.constants import C2RUST_DIRNAME
|
|
367
|
-
cwd = Path(".").resolve()
|
|
368
|
-
current = cwd
|
|
369
|
-
for _ in range(5):
|
|
370
|
-
if current and current.exists():
|
|
371
|
-
jarvis_dir = current / C2RUST_DIRNAME
|
|
372
|
-
if jarvis_dir.exists() and jarvis_dir.is_dir():
|
|
373
|
-
return current
|
|
374
|
-
parent = current.parent
|
|
375
|
-
if parent == current: # 已到达根目录
|
|
376
|
-
break
|
|
377
|
-
current = parent
|
|
378
|
-
return None
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
def detect_crate_dir(preferred: Optional[Path]) -> Path:
|
|
382
|
-
"""
|
|
383
|
-
选择 crate 目录策略:
|
|
384
|
-
- 若提供 preferred 且包含 Cargo.toml,则使用
|
|
385
|
-
- 否则:尝试从项目根目录推断(查找包含 .jarvis/c2rust 的目录)
|
|
386
|
-
- 否则:优先 <cwd>/<cwd.name>_rs;若存在 Cargo.toml 则用之
|
|
387
|
-
- 否则:在当前目录下递归寻找第一个包含 Cargo.toml 的目录
|
|
388
|
-
- 若失败:若当前目录有 Cargo.toml 则返回当前目录,否则抛错
|
|
389
|
-
"""
|
|
390
|
-
if preferred:
|
|
391
|
-
preferred = preferred.resolve()
|
|
392
|
-
if (preferred / "Cargo.toml").exists():
|
|
393
|
-
return preferred
|
|
394
|
-
|
|
395
|
-
# 尝试从项目根目录推断 crate 目录
|
|
396
|
-
project_root = _find_project_root()
|
|
397
|
-
if project_root:
|
|
398
|
-
# 策略1: project_root 的父目录下的 <project_root.name>_rs
|
|
399
|
-
candidate1 = project_root.parent / f"{project_root.name}_rs"
|
|
400
|
-
if (candidate1 / "Cargo.toml").exists():
|
|
401
|
-
return candidate1
|
|
402
|
-
# 策略2: project_root 本身(如果包含 Cargo.toml)
|
|
403
|
-
if (project_root / "Cargo.toml").exists():
|
|
404
|
-
return project_root
|
|
405
|
-
# 策略3: project_root 下的子目录中包含 Cargo.toml 的
|
|
406
|
-
for d in project_root.iterdir():
|
|
407
|
-
if d.is_dir() and (d / "Cargo.toml").exists():
|
|
408
|
-
return d
|
|
409
|
-
|
|
410
|
-
cwd = Path(".").resolve()
|
|
411
|
-
candidate = cwd / f"{cwd.name}_rs"
|
|
412
|
-
if (candidate / "Cargo.toml").exists():
|
|
413
|
-
return candidate
|
|
414
|
-
|
|
415
|
-
# 搜索第一个包含 Cargo.toml 的目录(限制深度2以避免过慢)
|
|
416
|
-
for p in [cwd] + [d for d in cwd.iterdir() if d.is_dir()]:
|
|
417
|
-
if (p / "Cargo.toml").exists():
|
|
418
|
-
return p
|
|
419
|
-
|
|
420
|
-
if (cwd / "Cargo.toml").exists():
|
|
421
|
-
return cwd
|
|
422
|
-
raise FileNotFoundError("未找到 Cargo.toml,对应 crate 目录无法确定。")
|
|
39
|
+
from typing import Dict
|
|
40
|
+
from typing import List
|
|
41
|
+
from typing import Optional
|
|
42
|
+
|
|
43
|
+
from jarvis.jarvis_utils.output import PrettyOutput
|
|
44
|
+
|
|
45
|
+
from jarvis.jarvis_c2rust.optimizer_build_fix import BuildFixOptimizer
|
|
46
|
+
from jarvis.jarvis_c2rust.optimizer_clippy import ClippyOptimizer
|
|
47
|
+
from jarvis.jarvis_c2rust.optimizer_config import (
|
|
48
|
+
append_additional_notes as append_notes,
|
|
49
|
+
)
|
|
50
|
+
from jarvis.jarvis_c2rust.optimizer_config import load_additional_notes
|
|
51
|
+
from jarvis.jarvis_c2rust.optimizer_docs import DocsOptimizer
|
|
52
|
+
|
|
53
|
+
# 导入拆分后的模块
|
|
54
|
+
from jarvis.jarvis_c2rust.optimizer_options import OptimizeOptions
|
|
55
|
+
from jarvis.jarvis_c2rust.optimizer_options import OptimizeStats
|
|
56
|
+
from jarvis.jarvis_c2rust.optimizer_progress import ProgressManager
|
|
57
|
+
from jarvis.jarvis_c2rust.optimizer_report import get_report_display_path
|
|
58
|
+
from jarvis.jarvis_c2rust.optimizer_report import write_final_report
|
|
59
|
+
from jarvis.jarvis_c2rust.optimizer_unsafe import UnsafeOptimizer
|
|
60
|
+
from jarvis.jarvis_c2rust.optimizer_utils import compute_target_files
|
|
61
|
+
from jarvis.jarvis_c2rust.optimizer_utils import detect_crate_dir
|
|
62
|
+
from jarvis.jarvis_c2rust.optimizer_utils import ensure_report_dir
|
|
63
|
+
from jarvis.jarvis_c2rust.optimizer_utils import find_project_root
|
|
64
|
+
from jarvis.jarvis_c2rust.optimizer_utils import iter_rust_files
|
|
65
|
+
from jarvis.jarvis_c2rust.optimizer_visibility import VisibilityOptimizer
|
|
66
|
+
|
|
67
|
+
# 工具函数已迁移到 optimizer_utils.py
|
|
423
68
|
|
|
424
69
|
|
|
425
70
|
class Optimizer:
|
|
426
|
-
def __init__(
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
crate_dir: Path,
|
|
74
|
+
options: OptimizeOptions,
|
|
75
|
+
project_root: Optional[Path] = None,
|
|
76
|
+
):
|
|
427
77
|
self.crate_dir = crate_dir
|
|
428
|
-
self.project_root =
|
|
78
|
+
self.project_root = (
|
|
79
|
+
project_root if project_root else crate_dir.parent
|
|
80
|
+
) # 默认使用 crate_dir 的父目录
|
|
429
81
|
self.options = options
|
|
430
82
|
self.stats = OptimizeStats()
|
|
431
83
|
# 进度文件
|
|
432
|
-
self.report_dir =
|
|
84
|
+
self.report_dir = ensure_report_dir(self.crate_dir)
|
|
433
85
|
self.progress_path = self.report_dir / "optimize_progress.json"
|
|
434
|
-
self.processed: Set[str] = set()
|
|
435
|
-
self.steps_completed: Set[str] = set() # 已完成的步骤集合
|
|
436
|
-
self._step_commits: Dict[str, str] = {} # 每个步骤的 commit id
|
|
437
86
|
self._target_files: List[Path] = []
|
|
438
|
-
self._load_or_reset_progress()
|
|
439
|
-
self._last_snapshot_commit: Optional[str] = None
|
|
440
|
-
# 每个 Agent 对应的工具调用前的 commit id(用于细粒度检测)
|
|
441
|
-
self._agent_before_commits: Dict[str, Optional[str]] = {}
|
|
442
|
-
# 读取附加说明
|
|
443
|
-
self.additional_notes = self._load_additional_notes()
|
|
444
|
-
|
|
445
|
-
def _load_additional_notes(self) -> str:
|
|
446
|
-
"""从配置文件加载附加说明"""
|
|
447
|
-
try:
|
|
448
|
-
from jarvis.jarvis_c2rust.constants import CONFIG_JSON
|
|
449
|
-
# 尝试从项目根目录读取配置(crate_dir 的父目录或同级目录)
|
|
450
|
-
# 首先尝试 crate_dir 的父目录
|
|
451
|
-
project_root = self.crate_dir.parent
|
|
452
|
-
config_path = project_root / ".jarvis" / "c2rust" / CONFIG_JSON
|
|
453
|
-
if config_path.exists():
|
|
454
|
-
with config_path.open("r", encoding="utf-8") as f:
|
|
455
|
-
config = json.load(f)
|
|
456
|
-
if isinstance(config, dict):
|
|
457
|
-
return str(config.get("additional_notes", "") or "").strip()
|
|
458
|
-
# 如果父目录没有,尝试当前目录
|
|
459
|
-
config_path = self.crate_dir / ".jarvis" / "c2rust" / CONFIG_JSON
|
|
460
|
-
if config_path.exists():
|
|
461
|
-
with config_path.open("r", encoding="utf-8") as f:
|
|
462
|
-
config = json.load(f)
|
|
463
|
-
if isinstance(config, dict):
|
|
464
|
-
return str(config.get("additional_notes", "") or "").strip()
|
|
465
|
-
except Exception:
|
|
466
|
-
pass
|
|
467
|
-
return ""
|
|
468
|
-
|
|
469
|
-
def _append_additional_notes(self, prompt: str) -> str:
|
|
470
|
-
"""
|
|
471
|
-
在提示词末尾追加附加说明(如果存在)。
|
|
472
|
-
|
|
473
|
-
Args:
|
|
474
|
-
prompt: 原始提示词
|
|
475
|
-
|
|
476
|
-
Returns:
|
|
477
|
-
追加了附加说明的提示词
|
|
478
|
-
"""
|
|
479
|
-
if self.additional_notes and self.additional_notes.strip():
|
|
480
|
-
return prompt + "\n\n" + "【附加说明(用户自定义)】\n" + self.additional_notes.strip()
|
|
481
|
-
return prompt
|
|
482
|
-
|
|
483
|
-
def _snapshot_commit(self) -> None:
|
|
484
|
-
"""
|
|
485
|
-
在启用 git_guard 时记录当前 HEAD commit(仅记录,不提交未暂存更改)。
|
|
486
|
-
统一在仓库根目录执行 git 命令,避免子目录导致的意外。
|
|
487
|
-
"""
|
|
488
|
-
if not self.options.git_guard:
|
|
489
|
-
return
|
|
490
|
-
try:
|
|
491
|
-
repo_root = _git_toplevel(self.crate_dir)
|
|
492
|
-
if repo_root is None:
|
|
493
|
-
return
|
|
494
|
-
head = _git_head_commit(repo_root)
|
|
495
|
-
if head:
|
|
496
|
-
self._last_snapshot_commit = head
|
|
497
|
-
except Exception:
|
|
498
|
-
# 忽略快照失败,不阻塞流程
|
|
499
|
-
pass
|
|
500
|
-
|
|
501
|
-
def _reset_to_snapshot(self) -> bool:
|
|
502
|
-
"""
|
|
503
|
-
在启用 git_guard 且存在快照时,将工作区 reset --hard 回快照。
|
|
504
|
-
统一在仓库根目录执行 git 命令,避免子目录导致的意外。
|
|
505
|
-
返回是否成功执行 reset。
|
|
506
|
-
"""
|
|
507
|
-
if not self.options.git_guard:
|
|
508
|
-
return False
|
|
509
|
-
snap = getattr(self, "_last_snapshot_commit", None)
|
|
510
|
-
if not snap:
|
|
511
|
-
return False
|
|
512
|
-
repo_root = _git_toplevel(self.crate_dir)
|
|
513
|
-
if repo_root is None:
|
|
514
|
-
return False
|
|
515
|
-
ok = _git_reset_hard(repo_root, snap)
|
|
516
|
-
return ok
|
|
517
|
-
|
|
518
|
-
# ---------- 进度管理与文件选择 ----------
|
|
519
|
-
|
|
520
|
-
def _load_or_reset_progress(self) -> None:
|
|
521
|
-
if self.options.reset_progress:
|
|
522
|
-
try:
|
|
523
|
-
self.progress_path.write_text(json.dumps({"processed": [], "steps_completed": []}, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
524
|
-
except Exception:
|
|
525
|
-
pass
|
|
526
|
-
self.processed = set()
|
|
527
|
-
self.steps_completed: Set[str] = set()
|
|
528
|
-
self._step_commits = {}
|
|
529
|
-
return
|
|
530
|
-
try:
|
|
531
|
-
if self.progress_path.exists():
|
|
532
|
-
obj = json.loads(self.progress_path.read_text(encoding="utf-8"))
|
|
533
|
-
if isinstance(obj, dict):
|
|
534
|
-
arr = obj.get("processed") or []
|
|
535
|
-
if isinstance(arr, list):
|
|
536
|
-
self.processed = {str(x) for x in arr if isinstance(x, str)}
|
|
537
|
-
else:
|
|
538
|
-
self.processed = set()
|
|
539
|
-
# 加载已完成的步骤
|
|
540
|
-
steps = obj.get("steps_completed") or []
|
|
541
|
-
if isinstance(steps, list):
|
|
542
|
-
self.steps_completed = {str(x) for x in steps if isinstance(x, str)}
|
|
543
|
-
else:
|
|
544
|
-
self.steps_completed = set()
|
|
545
|
-
# 加载步骤的 commit id
|
|
546
|
-
step_commits = obj.get("step_commits") or {}
|
|
547
|
-
if isinstance(step_commits, dict):
|
|
548
|
-
self._step_commits = {str(k): str(v) for k, v in step_commits.items() if isinstance(k, str) and isinstance(v, str)}
|
|
549
|
-
else:
|
|
550
|
-
self._step_commits = {}
|
|
551
|
-
|
|
552
|
-
# 恢复时,reset 到最后一个步骤的 commit id
|
|
553
|
-
if self.options.resume and self._step_commits:
|
|
554
|
-
last_commit = None
|
|
555
|
-
# 按照步骤顺序找到最后一个已完成步骤的 commit
|
|
556
|
-
step_order = ["clippy_elimination", "unsafe_cleanup", "visibility_opt", "doc_opt"]
|
|
557
|
-
for step in reversed(step_order):
|
|
558
|
-
if step in self.steps_completed and step in self._step_commits:
|
|
559
|
-
last_commit = self._step_commits[step]
|
|
560
|
-
break
|
|
561
|
-
|
|
562
|
-
if last_commit:
|
|
563
|
-
current_commit = self._get_crate_commit_hash()
|
|
564
|
-
if current_commit != last_commit:
|
|
565
|
-
typer.secho(f"[c2rust-optimizer][resume] 检测到代码状态不一致,正在 reset 到最后一个步骤的 commit: {last_commit}", fg=typer.colors.YELLOW)
|
|
566
|
-
if self._reset_to_commit(last_commit):
|
|
567
|
-
typer.secho(f"[c2rust-optimizer][resume] 已 reset 到 commit: {last_commit}", fg=typer.colors.GREEN)
|
|
568
|
-
else:
|
|
569
|
-
typer.secho("[c2rust-optimizer][resume] reset 失败,继续使用当前代码状态", fg=typer.colors.YELLOW)
|
|
570
|
-
else:
|
|
571
|
-
typer.secho("[c2rust-optimizer][resume] 代码状态一致,无需 reset", fg=typer.colors.CYAN)
|
|
572
|
-
else:
|
|
573
|
-
self.processed = set()
|
|
574
|
-
self.steps_completed = set()
|
|
575
|
-
self._step_commits = {}
|
|
576
|
-
else:
|
|
577
|
-
self.processed = set()
|
|
578
|
-
self.steps_completed = set()
|
|
579
|
-
self._step_commits = {}
|
|
580
|
-
except Exception:
|
|
581
|
-
self.processed = set()
|
|
582
|
-
self.steps_completed = set()
|
|
583
|
-
self._step_commits = {}
|
|
584
|
-
|
|
585
|
-
def _get_crate_commit_hash(self) -> Optional[str]:
|
|
586
|
-
"""获取 crate 目录的当前 commit id"""
|
|
587
|
-
try:
|
|
588
|
-
repo_root = _git_toplevel(self.crate_dir)
|
|
589
|
-
if repo_root is None:
|
|
590
|
-
return None
|
|
591
|
-
return _git_head_commit(repo_root)
|
|
592
|
-
except Exception:
|
|
593
|
-
return None
|
|
594
|
-
|
|
595
|
-
def _reset_to_commit(self, commit_hash: str) -> bool:
|
|
596
|
-
"""回退 crate 目录到指定的 commit"""
|
|
597
|
-
try:
|
|
598
|
-
repo_root = _git_toplevel(self.crate_dir)
|
|
599
|
-
if repo_root is None:
|
|
600
|
-
return False
|
|
601
|
-
return _git_reset_hard(repo_root, commit_hash)
|
|
602
|
-
except Exception:
|
|
603
|
-
return False
|
|
604
|
-
|
|
605
|
-
def _on_before_tool_call(self, agent: Any, current_response=None, **kwargs) -> None:
|
|
606
|
-
"""
|
|
607
|
-
工具调用前的事件处理器,用于记录工具调用前的 commit id。
|
|
608
|
-
|
|
609
|
-
在每次工具调用前记录当前的 commit,以便在工具调用后检测到问题时能够回退。
|
|
610
|
-
"""
|
|
611
|
-
try:
|
|
612
|
-
# 只关注可能修改代码的工具
|
|
613
|
-
# 注意:在 BEFORE_TOOL_CALL 时,工具还未执行,无法获取工具名称
|
|
614
|
-
# 但我们可以在 AFTER_TOOL_CALL 时检查工具名称,这里先记录 commit
|
|
615
|
-
agent_id = id(agent)
|
|
616
|
-
agent_key = f"agent_{agent_id}"
|
|
617
|
-
current_commit = self._get_crate_commit_hash()
|
|
618
|
-
if current_commit:
|
|
619
|
-
# 记录工具调用前的 commit(如果之前没有记录,或者 commit 已变化)
|
|
620
|
-
if agent_key not in self._agent_before_commits or self._agent_before_commits[agent_key] != current_commit:
|
|
621
|
-
self._agent_before_commits[agent_key] = current_commit
|
|
622
|
-
except Exception as e:
|
|
623
|
-
# 事件处理器异常不应影响主流程
|
|
624
|
-
typer.secho(f"[c2rust-optimizer][test-detection] BEFORE_TOOL_CALL 事件处理器异常: {e}", fg=typer.colors.YELLOW)
|
|
625
|
-
|
|
626
|
-
def _on_after_tool_call(self, agent: Any, current_response=None, need_return=None, tool_prompt=None, **kwargs) -> None:
|
|
627
|
-
"""
|
|
628
|
-
工具调用后的事件处理器,用于细粒度检测测试代码删除。
|
|
629
|
-
|
|
630
|
-
在每次工具调用后立即检测,如果检测到测试代码被错误删除,立即回退。
|
|
631
|
-
"""
|
|
632
|
-
try:
|
|
633
|
-
# 只检测编辑文件的工具调用
|
|
634
|
-
last_tool = agent.get_user_data("__last_executed_tool__") if hasattr(agent, "get_user_data") else None
|
|
635
|
-
if not last_tool:
|
|
636
|
-
return
|
|
637
|
-
|
|
638
|
-
# 只关注可能修改代码的工具
|
|
639
|
-
edit_tools = {"edit_file", "rewrite_file", "apply_patch"}
|
|
640
|
-
if last_tool not in edit_tools:
|
|
641
|
-
return
|
|
642
|
-
|
|
643
|
-
# 获取该 Agent 对应的工具调用前的 commit id
|
|
644
|
-
agent_id = id(agent)
|
|
645
|
-
agent_key = f"agent_{agent_id}"
|
|
646
|
-
before_commit = self._agent_before_commits.get(agent_key)
|
|
647
|
-
|
|
648
|
-
# 如果没有 commit 信息,无法检测
|
|
649
|
-
if not before_commit:
|
|
650
|
-
return
|
|
651
|
-
|
|
652
|
-
# 检测测试代码删除
|
|
653
|
-
from jarvis.jarvis_c2rust.utils import detect_test_deletion, ask_llm_about_test_deletion
|
|
654
|
-
|
|
655
|
-
detection_result = detect_test_deletion("[c2rust-optimizer]")
|
|
656
|
-
if not detection_result:
|
|
657
|
-
# 没有检测到删除,更新 commit 记录
|
|
658
|
-
current_commit = self._get_crate_commit_hash()
|
|
659
|
-
if current_commit and current_commit != before_commit:
|
|
660
|
-
self._agent_before_commits[agent_key] = current_commit
|
|
661
|
-
return
|
|
662
|
-
|
|
663
|
-
typer.secho("[c2rust-optimizer][test-detection] 检测到可能错误删除了测试代码标记(工具调用后检测)", fg=typer.colors.YELLOW)
|
|
664
|
-
|
|
665
|
-
# 询问 LLM 是否合理
|
|
666
|
-
need_reset = ask_llm_about_test_deletion(detection_result, agent, "[c2rust-optimizer]")
|
|
667
|
-
|
|
668
|
-
if need_reset:
|
|
669
|
-
typer.secho(f"[c2rust-optimizer][test-detection] LLM 确认删除不合理,正在回退到 commit: {before_commit}", fg=typer.colors.RED)
|
|
670
|
-
if self._reset_to_commit(before_commit):
|
|
671
|
-
typer.secho("[c2rust-optimizer][test-detection] 已回退到之前的 commit(工具调用后检测)", fg=typer.colors.GREEN)
|
|
672
|
-
# 回退后,保持之前的 commit 记录
|
|
673
|
-
self._agent_before_commits[agent_key] = before_commit
|
|
674
|
-
# 在 agent 的 session 中添加提示,告知修改被撤销
|
|
675
|
-
if hasattr(agent, "session") and hasattr(agent.session, "prompt"):
|
|
676
|
-
agent.session.prompt += "\n\n⚠️ 修改被撤销:检测到测试代码被错误删除,已回退到之前的版本。\n"
|
|
677
|
-
else:
|
|
678
|
-
typer.secho("[c2rust-optimizer][test-detection] 回退失败", fg=typer.colors.RED)
|
|
679
|
-
else:
|
|
680
|
-
# LLM 认为删除合理,更新 commit 记录
|
|
681
|
-
current_commit = self._get_crate_commit_hash()
|
|
682
|
-
if current_commit and current_commit != before_commit:
|
|
683
|
-
self._agent_before_commits[agent_key] = current_commit
|
|
684
|
-
except Exception as e:
|
|
685
|
-
# 事件处理器异常不应影响主流程
|
|
686
|
-
typer.secho(f"[c2rust-optimizer][test-detection] AFTER_TOOL_CALL 事件处理器异常: {e}", fg=typer.colors.YELLOW)
|
|
687
87
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
参数:
|
|
693
|
-
before_commit: agent 运行前的 commit hash
|
|
694
|
-
agent: 代码优化或修复的 agent 实例,使用其 model 进行询问
|
|
695
|
-
|
|
696
|
-
返回:
|
|
697
|
-
bool: 如果检测到问题且已回退,返回 True;否则返回 False
|
|
698
|
-
"""
|
|
699
|
-
return check_and_handle_test_deletion(
|
|
700
|
-
before_commit,
|
|
701
|
-
agent,
|
|
702
|
-
self._reset_to_commit,
|
|
703
|
-
"[c2rust-optimizer]"
|
|
88
|
+
# 初始化进度管理器
|
|
89
|
+
self.progress_manager = ProgressManager(
|
|
90
|
+
self.crate_dir, self.options, self.progress_path
|
|
704
91
|
)
|
|
92
|
+
self.progress_manager.load_or_reset_progress()
|
|
705
93
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
# 获取当前 commit id 并记录
|
|
719
|
-
current_commit = self._get_crate_commit_hash()
|
|
720
|
-
|
|
721
|
-
data = {
|
|
722
|
-
"processed": sorted(self.processed),
|
|
723
|
-
"steps_completed": sorted(self.steps_completed)
|
|
724
|
-
}
|
|
725
|
-
if current_commit:
|
|
726
|
-
data["last_commit"] = current_commit
|
|
727
|
-
typer.secho(f"[c2rust-optimizer][progress] 已记录当前 commit: {current_commit}", fg=typer.colors.CYAN)
|
|
728
|
-
|
|
729
|
-
self.progress_path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
730
|
-
except Exception:
|
|
731
|
-
pass
|
|
732
|
-
|
|
733
|
-
def _save_fix_progress(self, step_name: str, fix_key: str, files: Optional[List[Path]] = None) -> None:
|
|
734
|
-
"""
|
|
735
|
-
保存单个修复的进度(包括 commit id)。
|
|
736
|
-
|
|
737
|
-
Args:
|
|
738
|
-
step_name: 步骤名称(如 "clippy_elimination", "unsafe_cleanup")
|
|
739
|
-
fix_key: 修复的唯一标识(如 "warning-1", "file_path.rs")
|
|
740
|
-
files: 修改的文件列表(可选)
|
|
741
|
-
"""
|
|
742
|
-
try:
|
|
743
|
-
# 获取当前 commit id
|
|
744
|
-
current_commit = self._get_crate_commit_hash()
|
|
745
|
-
if not current_commit:
|
|
746
|
-
typer.secho("[c2rust-optimizer][progress] 无法获取 commit id,跳过进度记录", fg=typer.colors.YELLOW)
|
|
747
|
-
return
|
|
748
|
-
|
|
749
|
-
# 加载现有进度
|
|
750
|
-
if self.progress_path.exists():
|
|
751
|
-
try:
|
|
752
|
-
obj = json.loads(self.progress_path.read_text(encoding="utf-8"))
|
|
753
|
-
except Exception:
|
|
754
|
-
obj = {}
|
|
755
|
-
else:
|
|
756
|
-
obj = {}
|
|
757
|
-
|
|
758
|
-
# 初始化修复进度结构
|
|
759
|
-
if "fix_progress" not in obj:
|
|
760
|
-
obj["fix_progress"] = {}
|
|
761
|
-
if step_name not in obj["fix_progress"]:
|
|
762
|
-
obj["fix_progress"][step_name] = {}
|
|
763
|
-
|
|
764
|
-
# 记录修复进度
|
|
765
|
-
obj["fix_progress"][step_name][fix_key] = {
|
|
766
|
-
"commit": current_commit,
|
|
767
|
-
"timestamp": None, # 可以添加时间戳如果需要
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
# 更新已处理的文件列表
|
|
771
|
-
if files:
|
|
772
|
-
rels = []
|
|
773
|
-
for p in files:
|
|
774
|
-
try:
|
|
775
|
-
rel = p.resolve().relative_to(self.crate_dir.resolve()).as_posix()
|
|
776
|
-
except Exception:
|
|
777
|
-
rel = str(p)
|
|
778
|
-
rels.append(rel)
|
|
779
|
-
self.processed.update(rels)
|
|
780
|
-
obj["processed"] = sorted(self.processed)
|
|
781
|
-
|
|
782
|
-
# 更新 last_commit
|
|
783
|
-
obj["last_commit"] = current_commit
|
|
784
|
-
|
|
785
|
-
# 保存进度
|
|
786
|
-
self.progress_path.write_text(json.dumps(obj, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
787
|
-
typer.secho(f"[c2rust-optimizer][progress] 已记录修复进度: {step_name}/{fix_key} -> commit {current_commit[:8]}", fg=typer.colors.CYAN)
|
|
788
|
-
except Exception as e:
|
|
789
|
-
typer.secho(f"[c2rust-optimizer] 保存修复进度失败(非致命): {e}", fg=typer.colors.YELLOW)
|
|
790
|
-
|
|
791
|
-
def _save_step_progress(self, step_name: str, files: List[Path]) -> None:
|
|
792
|
-
"""保存步骤进度:标记步骤完成并更新文件列表"""
|
|
793
|
-
try:
|
|
794
|
-
# 标记步骤为已完成
|
|
795
|
-
self.steps_completed.add(step_name)
|
|
796
|
-
# 更新已处理的文件列表
|
|
797
|
-
rels = []
|
|
798
|
-
for p in files:
|
|
799
|
-
try:
|
|
800
|
-
rel = p.resolve().relative_to(self.crate_dir.resolve()).as_posix()
|
|
801
|
-
except Exception:
|
|
802
|
-
rel = str(p)
|
|
803
|
-
rels.append(rel)
|
|
804
|
-
self.processed.update(rels)
|
|
805
|
-
|
|
806
|
-
# 获取当前 commit id 并记录
|
|
807
|
-
current_commit = self._get_crate_commit_hash()
|
|
808
|
-
|
|
809
|
-
# 保存进度
|
|
810
|
-
data = {
|
|
811
|
-
"processed": sorted(self.processed),
|
|
812
|
-
"steps_completed": sorted(self.steps_completed)
|
|
813
|
-
}
|
|
814
|
-
if current_commit:
|
|
815
|
-
# 记录每个步骤的 commit id
|
|
816
|
-
step_commits = getattr(self, "_step_commits", {})
|
|
817
|
-
step_commits[step_name] = current_commit
|
|
818
|
-
self._step_commits = step_commits
|
|
819
|
-
data["step_commits"] = step_commits
|
|
820
|
-
data["last_commit"] = current_commit
|
|
821
|
-
typer.secho(f"[c2rust-optimizer][progress] 已记录步骤 '{step_name}' 的 commit: {current_commit}", fg=typer.colors.CYAN)
|
|
822
|
-
|
|
823
|
-
self.progress_path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
824
|
-
typer.secho(f"[c2rust-optimizer] 步骤 '{step_name}' 进度已保存", fg=typer.colors.CYAN)
|
|
825
|
-
except Exception as e:
|
|
826
|
-
typer.secho(f"[c2rust-optimizer] 保存步骤进度失败(非致命): {e}", fg=typer.colors.YELLOW)
|
|
827
|
-
|
|
828
|
-
def _parse_patterns(self, s: Optional[str]) -> List[str]:
|
|
829
|
-
if not s or not isinstance(s, str):
|
|
830
|
-
return []
|
|
831
|
-
parts = [x.strip() for x in s.replace("\n", ",").split(",")]
|
|
832
|
-
return [x for x in parts if x]
|
|
833
|
-
|
|
834
|
-
def _match_any(self, rel: str, patterns: List[str]) -> bool:
|
|
835
|
-
if not patterns:
|
|
836
|
-
return False
|
|
837
|
-
return any(fnmatch.fnmatch(rel, pat) for pat in patterns)
|
|
838
|
-
|
|
839
|
-
def _compute_target_files(self) -> List[Path]:
|
|
840
|
-
include = self._parse_patterns(self.options.include_patterns)
|
|
841
|
-
exclude = self._parse_patterns(self.options.exclude_patterns)
|
|
842
|
-
maxn = int(self.options.max_files or 0)
|
|
843
|
-
take: List[Path] = []
|
|
844
|
-
for p in sorted(_iter_rust_files(self.crate_dir), key=lambda x: x.as_posix()):
|
|
845
|
-
try:
|
|
846
|
-
rel = p.resolve().relative_to(self.crate_dir.resolve()).as_posix()
|
|
847
|
-
except Exception:
|
|
848
|
-
rel = p.as_posix()
|
|
849
|
-
# include 过滤(若提供,则必须命中其一)
|
|
850
|
-
if include and not self._match_any(rel, include):
|
|
851
|
-
continue
|
|
852
|
-
# exclude 过滤
|
|
853
|
-
if exclude and self._match_any(rel, exclude):
|
|
854
|
-
continue
|
|
855
|
-
# resume:跳过已处理文件
|
|
856
|
-
if self.options.resume and rel in self.processed:
|
|
857
|
-
continue
|
|
858
|
-
take.append(p)
|
|
859
|
-
if maxn > 0 and len(take) >= maxn:
|
|
860
|
-
break
|
|
861
|
-
self._target_files = take
|
|
862
|
-
return take
|
|
94
|
+
# 读取附加说明
|
|
95
|
+
self.additional_notes = load_additional_notes(self.crate_dir)
|
|
96
|
+
|
|
97
|
+
# 初始化各个优化器模块
|
|
98
|
+
self.build_fix_optimizer = BuildFixOptimizer(
|
|
99
|
+
self.crate_dir,
|
|
100
|
+
self.options,
|
|
101
|
+
self.stats,
|
|
102
|
+
self.progress_manager,
|
|
103
|
+
lambda p: append_notes(p, self.additional_notes),
|
|
104
|
+
)
|
|
863
105
|
|
|
864
|
-
|
|
106
|
+
self.clippy_optimizer = ClippyOptimizer(
|
|
107
|
+
self.crate_dir,
|
|
108
|
+
self.options,
|
|
109
|
+
self.stats,
|
|
110
|
+
self.progress_manager,
|
|
111
|
+
lambda p: append_notes(p, self.additional_notes),
|
|
112
|
+
self.build_fix_optimizer.verify_and_fix_after_step,
|
|
113
|
+
)
|
|
865
114
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
"""
|
|
876
|
-
try:
|
|
877
|
-
return str(report_path.relative_to(self.project_root))
|
|
878
|
-
except ValueError:
|
|
879
|
-
try:
|
|
880
|
-
return str(report_path.relative_to(self.crate_dir))
|
|
881
|
-
except ValueError:
|
|
882
|
-
try:
|
|
883
|
-
return str(report_path.relative_to(Path.cwd()))
|
|
884
|
-
except ValueError:
|
|
885
|
-
return str(report_path)
|
|
115
|
+
self.unsafe_optimizer = UnsafeOptimizer(
|
|
116
|
+
self.crate_dir,
|
|
117
|
+
self.options,
|
|
118
|
+
self.stats,
|
|
119
|
+
self.progress_manager,
|
|
120
|
+
lambda p: append_notes(p, self.additional_notes),
|
|
121
|
+
self.clippy_optimizer.extract_warnings_by_file,
|
|
122
|
+
self.clippy_optimizer.format_warnings_for_prompt,
|
|
123
|
+
)
|
|
886
124
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
try:
|
|
895
|
-
_write_file(report_path, json.dumps(asdict(self.stats), ensure_ascii=False, indent=2))
|
|
896
|
-
except Exception:
|
|
897
|
-
pass
|
|
125
|
+
self.visibility_optimizer = VisibilityOptimizer(
|
|
126
|
+
self.crate_dir,
|
|
127
|
+
self.options,
|
|
128
|
+
self.stats,
|
|
129
|
+
self.progress_manager,
|
|
130
|
+
lambda p: append_notes(p, self.additional_notes),
|
|
131
|
+
)
|
|
898
132
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
Returns:
|
|
908
|
-
True: 测试通过或修复成功
|
|
909
|
-
False: 测试失败且修复失败(已回滚)
|
|
910
|
-
"""
|
|
911
|
-
ok, diag_full = _cargo_check_full(self.crate_dir, self.stats, self.options.max_checks, timeout=self.options.cargo_test_timeout)
|
|
912
|
-
if not ok:
|
|
913
|
-
fixed = self._build_fix_loop(target_files)
|
|
914
|
-
if not fixed:
|
|
915
|
-
first = (diag_full.splitlines()[0] if isinstance(diag_full, str) and diag_full else "failed")
|
|
916
|
-
self.stats.errors.append(f"test after {step_name} failed: {first}")
|
|
917
|
-
try:
|
|
918
|
-
self._reset_to_snapshot()
|
|
919
|
-
finally:
|
|
920
|
-
return False
|
|
921
|
-
return True
|
|
133
|
+
self.docs_optimizer = DocsOptimizer(
|
|
134
|
+
self.crate_dir,
|
|
135
|
+
self.options,
|
|
136
|
+
self.stats,
|
|
137
|
+
self.progress_manager,
|
|
138
|
+
lambda p: append_notes(p, self.additional_notes),
|
|
139
|
+
)
|
|
922
140
|
|
|
923
|
-
|
|
924
|
-
|
|
141
|
+
# 配置加载相关方法已迁移到 optimizer_config.py
|
|
142
|
+
# Git 快照相关方法已迁移到 ProgressManager
|
|
143
|
+
# 文件选择相关方法已迁移到 optimizer_utils.py
|
|
144
|
+
# 报告相关方法已迁移到 optimizer_report.py
|
|
145
|
+
# 验证和修复相关方法已迁移到 BuildFixOptimizer
|
|
146
|
+
|
|
147
|
+
def _run_optimization_step(
|
|
148
|
+
self,
|
|
149
|
+
step_name: str,
|
|
150
|
+
step_display_name: str,
|
|
151
|
+
step_num: int,
|
|
152
|
+
target_files: List[Path],
|
|
153
|
+
opt_func,
|
|
154
|
+
) -> Optional[int]:
|
|
925
155
|
"""
|
|
926
156
|
执行单个优化步骤(unsafe_cleanup, visibility_opt, doc_opt)。
|
|
927
|
-
|
|
157
|
+
|
|
928
158
|
Args:
|
|
929
159
|
step_name: 步骤名称(用于进度保存和错误消息)
|
|
930
160
|
step_display_name: 步骤显示名称(用于日志)
|
|
931
161
|
step_num: 步骤编号
|
|
932
162
|
target_files: 目标文件列表
|
|
933
163
|
opt_func: 优化函数(接受 target_files 作为参数)
|
|
934
|
-
|
|
164
|
+
|
|
935
165
|
Returns:
|
|
936
166
|
下一个步骤编号,如果失败则返回 None
|
|
937
167
|
"""
|
|
938
|
-
|
|
939
|
-
|
|
168
|
+
PrettyOutput.auto_print(
|
|
169
|
+
f"\n🔧 [c2rust-optimizer] 第 {step_num} 步:{step_display_name}",
|
|
170
|
+
color="magenta",
|
|
171
|
+
)
|
|
172
|
+
self.progress_manager.snapshot_commit()
|
|
940
173
|
if not self.options.dry_run:
|
|
941
174
|
opt_func(target_files)
|
|
942
|
-
if not self.
|
|
175
|
+
if not self.build_fix_optimizer.verify_and_fix_after_step(
|
|
176
|
+
step_name, target_files
|
|
177
|
+
):
|
|
943
178
|
# 验证失败,已回滚,返回 None 表示失败
|
|
944
179
|
return None
|
|
945
180
|
# 保存步骤进度
|
|
946
|
-
self.
|
|
181
|
+
self.progress_manager.save_step_progress(step_name, target_files)
|
|
947
182
|
return step_num + 1
|
|
948
183
|
|
|
949
|
-
|
|
950
|
-
"""
|
|
951
|
-
处理自动修复后的 clippy 告警检查。
|
|
952
|
-
如果仍有告警,使用 CodeAgent 继续修复。
|
|
953
|
-
|
|
954
|
-
Args:
|
|
955
|
-
clippy_targets: 目标文件列表
|
|
956
|
-
clippy_output: 当前的 clippy 输出
|
|
957
|
-
|
|
958
|
-
Returns:
|
|
959
|
-
True: 所有告警已消除
|
|
960
|
-
False: 仍有告警未消除(步骤未完成)
|
|
961
|
-
"""
|
|
962
|
-
typer.secho("[c2rust-optimizer] 自动修复后仍有告警,继续使用 CodeAgent 修复...", fg=typer.colors.CYAN)
|
|
963
|
-
all_warnings_eliminated = self._codeagent_eliminate_clippy_warnings(clippy_targets, clippy_output)
|
|
964
|
-
|
|
965
|
-
# 验证修复后是否还有告警
|
|
966
|
-
if not self._verify_and_fix_after_step("clippy_elimination", clippy_targets):
|
|
967
|
-
return False
|
|
968
|
-
|
|
969
|
-
# 再次检查是否还有告警
|
|
970
|
-
has_warnings_after, _ = _check_clippy_warnings(self.crate_dir)
|
|
971
|
-
if not has_warnings_after and all_warnings_eliminated:
|
|
972
|
-
typer.secho("[c2rust-optimizer] Clippy 告警已全部消除", fg=typer.colors.GREEN)
|
|
973
|
-
self._save_step_progress("clippy_elimination", clippy_targets)
|
|
974
|
-
return True
|
|
975
|
-
else:
|
|
976
|
-
typer.secho("[c2rust-optimizer] 仍有部分 Clippy 告警无法自动消除,步骤未完成,停止后续优化步骤", fg=typer.colors.YELLOW)
|
|
977
|
-
return False
|
|
978
|
-
|
|
979
|
-
def _run_clippy_elimination_step(self) -> bool:
|
|
980
|
-
"""
|
|
981
|
-
执行 Clippy 告警修复步骤(第 0 步)。
|
|
982
|
-
|
|
983
|
-
Returns:
|
|
984
|
-
True: 步骤完成(无告警或已修复)
|
|
985
|
-
False: 步骤未完成(仍有告警未修复,应停止后续步骤)
|
|
986
|
-
"""
|
|
987
|
-
if self.options.dry_run:
|
|
988
|
-
return True
|
|
989
|
-
|
|
990
|
-
typer.secho("[c2rust-optimizer] 检查 Clippy 告警...", fg=typer.colors.CYAN)
|
|
991
|
-
has_warnings, clippy_output = _check_clippy_warnings(self.crate_dir)
|
|
992
|
-
|
|
993
|
-
# 如果步骤已标记为完成,但仍有告警,说明之前的完成标记是错误的,需要清除
|
|
994
|
-
if "clippy_elimination" in self.steps_completed and has_warnings:
|
|
995
|
-
typer.secho("[c2rust-optimizer] 检测到步骤已标记为完成,但仍有 Clippy 告警,清除完成标记并继续修复", fg=typer.colors.YELLOW)
|
|
996
|
-
self.steps_completed.discard("clippy_elimination")
|
|
997
|
-
if "clippy_elimination" in self._step_commits:
|
|
998
|
-
del self._step_commits["clippy_elimination"]
|
|
999
|
-
|
|
1000
|
-
if not has_warnings:
|
|
1001
|
-
typer.secho("[c2rust-optimizer] 未发现 Clippy 告警,跳过消除步骤", fg=typer.colors.CYAN)
|
|
1002
|
-
# 如果没有告警,标记 clippy_elimination 为完成(跳过状态)
|
|
1003
|
-
if "clippy_elimination" not in self.steps_completed:
|
|
1004
|
-
clippy_targets = list(_iter_rust_files(self.crate_dir))
|
|
1005
|
-
if clippy_targets:
|
|
1006
|
-
self._save_step_progress("clippy_elimination", clippy_targets)
|
|
1007
|
-
return True
|
|
1008
|
-
|
|
1009
|
-
# 有告警,需要修复
|
|
1010
|
-
typer.secho("\n[c2rust-optimizer] 第 0 步:消除 Clippy 告警(必须完成此步骤才能继续其他优化)", fg=typer.colors.MAGENTA)
|
|
1011
|
-
self._snapshot_commit()
|
|
1012
|
-
|
|
1013
|
-
clippy_targets = list(_iter_rust_files(self.crate_dir))
|
|
1014
|
-
if not clippy_targets:
|
|
1015
|
-
typer.secho("[c2rust-optimizer] 警告:未找到任何 Rust 文件,无法修复 Clippy 告警", fg=typer.colors.YELLOW)
|
|
1016
|
-
return False
|
|
1017
|
-
|
|
1018
|
-
# 先尝试使用 clippy --fix 自动修复
|
|
1019
|
-
auto_fix_success = self._try_clippy_auto_fix()
|
|
1020
|
-
if auto_fix_success:
|
|
1021
|
-
typer.secho("[c2rust-optimizer] clippy 自动修复成功,继续检查是否还有告警...", fg=typer.colors.GREEN)
|
|
1022
|
-
# 重新检查告警
|
|
1023
|
-
has_warnings, clippy_output = _check_clippy_warnings(self.crate_dir)
|
|
1024
|
-
if not has_warnings:
|
|
1025
|
-
typer.secho("[c2rust-optimizer] 所有 Clippy 告警已通过自动修复消除", fg=typer.colors.GREEN)
|
|
1026
|
-
self._save_step_progress("clippy_elimination", clippy_targets)
|
|
1027
|
-
return True
|
|
1028
|
-
else:
|
|
1029
|
-
# 仍有告警,使用 CodeAgent 继续修复
|
|
1030
|
-
return self._handle_clippy_after_auto_fix(clippy_targets, clippy_output)
|
|
1031
|
-
else:
|
|
1032
|
-
# 自动修复失败或未执行,继续使用 CodeAgent 修复
|
|
1033
|
-
typer.secho("[c2rust-optimizer] clippy 自动修复未成功,继续使用 CodeAgent 修复...", fg=typer.colors.CYAN)
|
|
1034
|
-
all_warnings_eliminated = self._codeagent_eliminate_clippy_warnings(clippy_targets, clippy_output)
|
|
1035
|
-
|
|
1036
|
-
# 验证修复后是否还有告警
|
|
1037
|
-
if not self._verify_and_fix_after_step("clippy_elimination", clippy_targets):
|
|
1038
|
-
return False
|
|
1039
|
-
|
|
1040
|
-
# 再次检查是否还有告警
|
|
1041
|
-
has_warnings_after, _ = _check_clippy_warnings(self.crate_dir)
|
|
1042
|
-
if not has_warnings_after and all_warnings_eliminated:
|
|
1043
|
-
typer.secho("[c2rust-optimizer] Clippy 告警已全部消除", fg=typer.colors.GREEN)
|
|
1044
|
-
self._save_step_progress("clippy_elimination", clippy_targets)
|
|
1045
|
-
return True
|
|
1046
|
-
else:
|
|
1047
|
-
typer.secho("[c2rust-optimizer] 仍有部分 Clippy 告警无法自动消除,步骤未完成,停止后续优化步骤", fg=typer.colors.YELLOW)
|
|
1048
|
-
return False
|
|
184
|
+
# Clippy 相关方法已迁移到 ClippyOptimizer
|
|
1049
185
|
|
|
1050
186
|
def run(self) -> OptimizeStats:
|
|
1051
187
|
"""
|
|
1052
188
|
执行优化流程的主入口。
|
|
1053
|
-
|
|
189
|
+
|
|
1054
190
|
Returns:
|
|
1055
191
|
优化统计信息
|
|
1056
192
|
"""
|
|
1057
193
|
report_path = self.report_dir / "optimize_report.json"
|
|
1058
|
-
|
|
194
|
+
PrettyOutput.auto_print(
|
|
195
|
+
f"🚀 [c2rust-optimizer][start] 开始优化 Crate: {self.crate_dir}",
|
|
196
|
+
color="blue",
|
|
197
|
+
)
|
|
1059
198
|
try:
|
|
1060
199
|
# 批次开始前记录快照
|
|
1061
|
-
self.
|
|
200
|
+
self.progress_manager.snapshot_commit()
|
|
1062
201
|
|
|
1063
202
|
# ========== 第 0 步:Clippy 告警修复(必须第一步,且必须完成) ==========
|
|
1064
203
|
# 注意:clippy 告警修复不依赖于是否有新文件需要处理,即使所有文件都已处理,也应该检查并修复告警
|
|
1065
|
-
if not self.
|
|
204
|
+
if not self.clippy_optimizer.run_clippy_elimination_step():
|
|
1066
205
|
# Clippy 告警修复未完成,停止后续步骤
|
|
1067
206
|
return self.stats
|
|
1068
207
|
|
|
1069
208
|
# ========== 后续优化步骤(只有在 clippy 告警修复完成后才执行) ==========
|
|
1070
209
|
# 计算本次批次的目标文件列表(按 include/exclude/resume/max_files)
|
|
1071
|
-
targets =
|
|
1072
|
-
|
|
210
|
+
targets = compute_target_files(
|
|
211
|
+
self.crate_dir, self.options, self.progress_manager.processed
|
|
212
|
+
)
|
|
213
|
+
self._target_files = targets
|
|
214
|
+
|
|
1073
215
|
# 检查是否有未完成的步骤需要执行
|
|
1074
216
|
has_pending_steps = False
|
|
1075
|
-
if
|
|
217
|
+
if (
|
|
218
|
+
self.options.enable_unsafe_cleanup
|
|
219
|
+
and "unsafe_cleanup" not in self.progress_manager.steps_completed
|
|
220
|
+
):
|
|
1076
221
|
has_pending_steps = True
|
|
1077
|
-
if
|
|
222
|
+
if (
|
|
223
|
+
self.options.enable_visibility_opt
|
|
224
|
+
and "visibility_opt" not in self.progress_manager.steps_completed
|
|
225
|
+
):
|
|
1078
226
|
has_pending_steps = True
|
|
1079
|
-
if
|
|
227
|
+
if (
|
|
228
|
+
self.options.enable_doc_opt
|
|
229
|
+
and "doc_opt" not in self.progress_manager.steps_completed
|
|
230
|
+
):
|
|
1080
231
|
has_pending_steps = True
|
|
1081
|
-
|
|
232
|
+
|
|
1082
233
|
# 如果没有新文件但有未完成的步骤,使用所有 Rust 文件作为目标
|
|
1083
234
|
if not targets and has_pending_steps:
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
235
|
+
PrettyOutput.auto_print(
|
|
236
|
+
"📝 [c2rust-optimizer] 无新文件需要处理,但检测到未完成的步骤,使用所有 Rust 文件作为目标。",
|
|
237
|
+
color="cyan",
|
|
238
|
+
)
|
|
239
|
+
targets = list(iter_rust_files(self.crate_dir))
|
|
240
|
+
|
|
1087
241
|
if not targets:
|
|
1088
|
-
|
|
242
|
+
PrettyOutput.auto_print(
|
|
243
|
+
"✅ [c2rust-optimizer] 根据当前选项,无新文件需要处理,且所有步骤均已完成。",
|
|
244
|
+
color="cyan",
|
|
245
|
+
)
|
|
1089
246
|
else:
|
|
1090
|
-
|
|
247
|
+
PrettyOutput.auto_print(
|
|
248
|
+
f"📊 [c2rust-optimizer] 本次批次发现 {len(targets)} 个待处理文件。",
|
|
249
|
+
color="blue",
|
|
250
|
+
)
|
|
1091
251
|
|
|
1092
252
|
# 所有优化步骤都使用 CodeAgent
|
|
1093
253
|
step_num = 1
|
|
1094
|
-
|
|
254
|
+
|
|
1095
255
|
if self.options.enable_unsafe_cleanup:
|
|
1096
|
-
|
|
1097
|
-
"unsafe_cleanup",
|
|
1098
|
-
|
|
256
|
+
result_step_num = self._run_optimization_step(
|
|
257
|
+
"unsafe_cleanup",
|
|
258
|
+
"unsafe 清理",
|
|
259
|
+
step_num,
|
|
260
|
+
targets,
|
|
261
|
+
self.unsafe_optimizer.codeagent_opt_unsafe_cleanup,
|
|
1099
262
|
)
|
|
1100
|
-
if
|
|
263
|
+
if result_step_num is None: # 步骤失败,已回滚
|
|
1101
264
|
return self.stats
|
|
265
|
+
step_num = result_step_num
|
|
1102
266
|
|
|
1103
267
|
if self.options.enable_visibility_opt:
|
|
1104
|
-
|
|
1105
|
-
"visibility_opt",
|
|
1106
|
-
|
|
268
|
+
result_step_num = self._run_optimization_step(
|
|
269
|
+
"visibility_opt",
|
|
270
|
+
"可见性优化",
|
|
271
|
+
step_num,
|
|
272
|
+
targets,
|
|
273
|
+
self.visibility_optimizer.codeagent_opt_visibility,
|
|
1107
274
|
)
|
|
1108
|
-
if
|
|
275
|
+
if result_step_num is None: # 步骤失败,已回滚
|
|
1109
276
|
return self.stats
|
|
277
|
+
step_num = result_step_num
|
|
1110
278
|
|
|
1111
279
|
if self.options.enable_doc_opt:
|
|
1112
|
-
|
|
1113
|
-
"doc_opt",
|
|
1114
|
-
|
|
280
|
+
result_step_num = self._run_optimization_step(
|
|
281
|
+
"doc_opt",
|
|
282
|
+
"文档补充",
|
|
283
|
+
step_num,
|
|
284
|
+
targets,
|
|
285
|
+
self.docs_optimizer.codeagent_opt_docs,
|
|
1115
286
|
)
|
|
1116
|
-
if
|
|
287
|
+
if result_step_num is None: # 步骤失败,已回滚
|
|
1117
288
|
return self.stats
|
|
289
|
+
step_num = result_step_num
|
|
1118
290
|
|
|
1119
291
|
# 最终保存进度(确保所有步骤的进度都已记录)
|
|
1120
|
-
self.
|
|
292
|
+
self.progress_manager.save_progress_for_batch(targets)
|
|
1121
293
|
|
|
1122
294
|
except Exception as e:
|
|
1123
|
-
self.stats.errors
|
|
295
|
+
if self.stats.errors is not None:
|
|
296
|
+
self.stats.errors.append(f"fatal: {e}")
|
|
1124
297
|
finally:
|
|
1125
298
|
# 写出简要报告
|
|
1126
|
-
report_display =
|
|
1127
|
-
|
|
1128
|
-
self._write_final_report(report_path)
|
|
1129
|
-
return self.stats
|
|
1130
|
-
|
|
1131
|
-
# ========== 0) clippy warnings elimination (CodeAgent) ==========
|
|
1132
|
-
|
|
1133
|
-
def _try_clippy_auto_fix(self) -> bool:
|
|
1134
|
-
"""
|
|
1135
|
-
尝试使用 `cargo clippy --fix` 自动修复 clippy 告警。
|
|
1136
|
-
修复时同时包含测试代码(--tests),避免删除测试中使用的变量。
|
|
1137
|
-
修复后运行测试验证,如果测试失败则撤销修复。
|
|
1138
|
-
|
|
1139
|
-
返回:
|
|
1140
|
-
True: 自动修复成功且测试通过
|
|
1141
|
-
False: 自动修复失败或测试未通过(已撤销修复)
|
|
1142
|
-
"""
|
|
1143
|
-
crate = self.crate_dir.resolve()
|
|
1144
|
-
typer.secho("[c2rust-optimizer][clippy-auto-fix] 尝试使用 clippy --fix 自动修复(包含测试代码)...", fg=typer.colors.CYAN)
|
|
1145
|
-
|
|
1146
|
-
# 记录修复前的 commit id
|
|
1147
|
-
commit_before = self._get_crate_commit_hash()
|
|
1148
|
-
if not commit_before:
|
|
1149
|
-
typer.secho("[c2rust-optimizer][clippy-auto-fix] 无法获取 commit id,跳过自动修复", fg=typer.colors.YELLOW)
|
|
1150
|
-
return False
|
|
1151
|
-
|
|
1152
|
-
# 执行 cargo clippy --fix,添加 --tests 标志以包含测试代码
|
|
1153
|
-
try:
|
|
1154
|
-
res = subprocess.run(
|
|
1155
|
-
["cargo", "clippy", "--fix", "--tests", "--allow-dirty", "--allow-staged", "--", "-W", "clippy::all"],
|
|
1156
|
-
capture_output=True,
|
|
1157
|
-
text=True,
|
|
1158
|
-
check=False,
|
|
1159
|
-
cwd=str(crate),
|
|
1160
|
-
timeout=300, # 5 分钟超时
|
|
299
|
+
report_display = get_report_display_path(
|
|
300
|
+
report_path, self.project_root, self.crate_dir
|
|
1161
301
|
)
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
# 检查是否有文件被修改(通过 git status 或直接检查)
|
|
1170
|
-
# 如果没有修改,说明 clippy --fix 没有修复任何问题
|
|
1171
|
-
repo_root = _git_toplevel(crate)
|
|
1172
|
-
has_changes = False
|
|
1173
|
-
if repo_root:
|
|
1174
|
-
try:
|
|
1175
|
-
code, out, _ = _run_cmd(["git", "diff", "--quiet", "--exit-code"], repo_root)
|
|
1176
|
-
has_changes = (code != 0) # 非零表示有修改
|
|
1177
|
-
except Exception:
|
|
1178
|
-
# 如果无法检查 git 状态,假设有修改
|
|
1179
|
-
has_changes = True
|
|
1180
|
-
else:
|
|
1181
|
-
# 不在 git 仓库中,假设有修改
|
|
1182
|
-
has_changes = True
|
|
1183
|
-
|
|
1184
|
-
if not has_changes:
|
|
1185
|
-
typer.secho("[c2rust-optimizer][clippy-auto-fix] clippy --fix 未修改任何文件", fg=typer.colors.CYAN)
|
|
1186
|
-
return False
|
|
1187
|
-
|
|
1188
|
-
typer.secho("[c2rust-optimizer][clippy-auto-fix] clippy --fix 已执行,正在验证测试...", fg=typer.colors.CYAN)
|
|
1189
|
-
|
|
1190
|
-
# 运行 cargo test 验证
|
|
1191
|
-
ok, diag_full = _cargo_check_full(self.crate_dir, self.stats, self.options.max_checks, timeout=self.options.cargo_test_timeout)
|
|
1192
|
-
|
|
1193
|
-
if ok:
|
|
1194
|
-
typer.secho("[c2rust-optimizer][clippy-auto-fix] 自动修复成功且测试通过", fg=typer.colors.GREEN)
|
|
1195
|
-
return True
|
|
1196
|
-
else:
|
|
1197
|
-
typer.secho("[c2rust-optimizer][clippy-auto-fix] 自动修复后测试失败,正在撤销修复...", fg=typer.colors.YELLOW)
|
|
1198
|
-
# 撤销修复:回退到修复前的 commit
|
|
1199
|
-
if commit_before and self._reset_to_commit(commit_before):
|
|
1200
|
-
typer.secho(f"[c2rust-optimizer][clippy-auto-fix] 已成功撤销自动修复,回退到 commit: {commit_before[:8]}", fg=typer.colors.CYAN)
|
|
1201
|
-
else:
|
|
1202
|
-
typer.secho("[c2rust-optimizer][clippy-auto-fix] 撤销修复失败,请手动检查代码状态", fg=typer.colors.RED)
|
|
1203
|
-
return False
|
|
1204
|
-
|
|
1205
|
-
except subprocess.TimeoutExpired:
|
|
1206
|
-
typer.secho("[c2rust-optimizer][clippy-auto-fix] clippy --fix 执行超时,正在检查是否有修改并撤销...", fg=typer.colors.YELLOW)
|
|
1207
|
-
# 检查是否有修改,如果有则回退
|
|
1208
|
-
if commit_before:
|
|
1209
|
-
repo_root = _git_toplevel(crate)
|
|
1210
|
-
if repo_root:
|
|
1211
|
-
try:
|
|
1212
|
-
code, _, _ = _run_cmd(["git", "diff", "--quiet", "--exit-code"], repo_root)
|
|
1213
|
-
has_changes = (code != 0) # 非零表示有修改
|
|
1214
|
-
if has_changes:
|
|
1215
|
-
if self._reset_to_commit(commit_before):
|
|
1216
|
-
typer.secho(f"[c2rust-optimizer][clippy-auto-fix] 已撤销超时前的修改,回退到 commit: {commit_before[:8]}", fg=typer.colors.CYAN)
|
|
1217
|
-
else:
|
|
1218
|
-
typer.secho("[c2rust-optimizer][clippy-auto-fix] 撤销修改失败,请手动检查代码状态", fg=typer.colors.RED)
|
|
1219
|
-
except Exception:
|
|
1220
|
-
# 无法检查状态,尝试直接回退
|
|
1221
|
-
self._reset_to_commit(commit_before)
|
|
1222
|
-
return False
|
|
1223
|
-
except Exception as e:
|
|
1224
|
-
typer.secho(f"[c2rust-optimizer][clippy-auto-fix] clippy --fix 执行异常: {e},正在检查是否有修改并撤销...", fg=typer.colors.YELLOW)
|
|
1225
|
-
# 检查是否有修改,如果有则回退
|
|
1226
|
-
if commit_before:
|
|
1227
|
-
repo_root = _git_toplevel(crate)
|
|
1228
|
-
if repo_root:
|
|
1229
|
-
try:
|
|
1230
|
-
code, _, _ = _run_cmd(["git", "diff", "--quiet", "--exit-code"], repo_root)
|
|
1231
|
-
has_changes = (code != 0) # 非零表示有修改
|
|
1232
|
-
if has_changes:
|
|
1233
|
-
if self._reset_to_commit(commit_before):
|
|
1234
|
-
typer.secho(f"[c2rust-optimizer][clippy-auto-fix] 已撤销异常前的修改,回退到 commit: {commit_before[:8]}", fg=typer.colors.CYAN)
|
|
1235
|
-
else:
|
|
1236
|
-
typer.secho("[c2rust-optimizer][clippy-auto-fix] 撤销修改失败,请手动检查代码状态", fg=typer.colors.RED)
|
|
1237
|
-
except Exception:
|
|
1238
|
-
# 无法检查状态,尝试直接回退
|
|
1239
|
-
self._reset_to_commit(commit_before)
|
|
1240
|
-
return False
|
|
1241
|
-
|
|
1242
|
-
def _codeagent_eliminate_clippy_warnings(self, target_files: List[Path], clippy_output: str) -> bool:
|
|
1243
|
-
"""
|
|
1244
|
-
使用 CodeAgent 消除 clippy 告警。
|
|
1245
|
-
按文件修复,每次修复单个文件的前10个告警(不足10个就全部给出),修复后重新扫描,不断迭代。
|
|
1246
|
-
|
|
1247
|
-
注意:CodeAgent 必须在 crate 目录下创建和执行,以确保所有文件操作和命令执行都在正确的上下文中进行。
|
|
1248
|
-
|
|
1249
|
-
返回:
|
|
1250
|
-
True: 所有告警已消除
|
|
1251
|
-
False: 仍有告警未消除(达到最大迭代次数或无法提取告警)
|
|
1252
|
-
"""
|
|
1253
|
-
crate = self.crate_dir.resolve()
|
|
1254
|
-
file_list: List[str] = []
|
|
1255
|
-
for p in target_files:
|
|
1256
|
-
try:
|
|
1257
|
-
rel = p.resolve().relative_to(crate).as_posix()
|
|
1258
|
-
except Exception:
|
|
1259
|
-
rel = p.as_posix()
|
|
1260
|
-
file_list.append(rel)
|
|
1261
|
-
self.stats.files_scanned += 1
|
|
1262
|
-
|
|
1263
|
-
# 切换到 crate 目录,确保 CodeAgent 在正确的上下文中创建和执行
|
|
1264
|
-
prev_cwd = os.getcwd()
|
|
1265
|
-
iteration = 0
|
|
1266
|
-
|
|
1267
|
-
try:
|
|
1268
|
-
os.chdir(str(crate))
|
|
1269
|
-
|
|
1270
|
-
# 循环修复告警,按文件处理
|
|
1271
|
-
while True:
|
|
1272
|
-
iteration += 1
|
|
1273
|
-
|
|
1274
|
-
# 检查当前告警
|
|
1275
|
-
has_warnings, current_clippy_output = _check_clippy_warnings(crate)
|
|
1276
|
-
if not has_warnings:
|
|
1277
|
-
typer.secho(f"[c2rust-optimizer][codeagent][clippy] 所有告警已消除(共迭代 {iteration - 1} 次)", fg=typer.colors.GREEN)
|
|
1278
|
-
return True # 所有告警已消除
|
|
1279
|
-
|
|
1280
|
-
# 按文件提取告警
|
|
1281
|
-
warnings_by_file = self._extract_warnings_by_file(current_clippy_output)
|
|
1282
|
-
if not warnings_by_file:
|
|
1283
|
-
typer.secho("[c2rust-optimizer][codeagent][clippy] 无法提取告警,停止修复", fg=typer.colors.YELLOW)
|
|
1284
|
-
return False # 仍有告警未消除
|
|
1285
|
-
|
|
1286
|
-
# 找到第一个有告警的文件(优先处理目标文件列表中的文件)
|
|
1287
|
-
target_file_path = None
|
|
1288
|
-
target_warnings = None
|
|
1289
|
-
|
|
1290
|
-
# 优先处理目标文件列表中的文件
|
|
1291
|
-
for file_rel in file_list:
|
|
1292
|
-
# 尝试匹配文件路径(可能是相对路径或绝对路径)
|
|
1293
|
-
for file_path, warnings in warnings_by_file.items():
|
|
1294
|
-
if file_rel in file_path or file_path.endswith(file_rel):
|
|
1295
|
-
target_file_path = file_path
|
|
1296
|
-
target_warnings = warnings
|
|
1297
|
-
break
|
|
1298
|
-
if target_file_path:
|
|
1299
|
-
break
|
|
1300
|
-
|
|
1301
|
-
# 如果目标文件列表中没有告警,选择第一个有告警的文件
|
|
1302
|
-
if not target_file_path:
|
|
1303
|
-
target_file_path = next(iter(warnings_by_file.keys()))
|
|
1304
|
-
target_warnings = warnings_by_file[target_file_path]
|
|
1305
|
-
|
|
1306
|
-
# 获取该文件的前10个告警(不足10个就全部给出)
|
|
1307
|
-
warnings_to_fix = target_warnings[:10]
|
|
1308
|
-
warning_count = len(warnings_to_fix)
|
|
1309
|
-
total_warnings_in_file = len(target_warnings)
|
|
1310
|
-
|
|
1311
|
-
typer.secho(f"[c2rust-optimizer][codeagent][clippy] 第 {iteration} 次迭代:修复文件 {target_file_path} 的前 {warning_count} 个告警(共 {total_warnings_in_file} 个)", fg=typer.colors.CYAN)
|
|
1312
|
-
|
|
1313
|
-
# 格式化告警信息
|
|
1314
|
-
formatted_warnings = self._format_warnings_for_prompt(warnings_to_fix, max_count=10)
|
|
1315
|
-
|
|
1316
|
-
# 构建提示词,修复该文件的前10个告警
|
|
1317
|
-
prompt_lines: List[str] = [
|
|
1318
|
-
"你是资深 Rust 代码工程师。请在当前 crate 下修复指定文件中的 Clippy 告警,并以补丁形式输出修改:",
|
|
1319
|
-
f"- crate 根目录:{crate}",
|
|
1320
|
-
"",
|
|
1321
|
-
"本次修复仅允许修改以下文件(严格限制,只处理这一个文件):",
|
|
1322
|
-
f"- {target_file_path}",
|
|
1323
|
-
"",
|
|
1324
|
-
f"重要:本次修复仅修复该文件中的前 {warning_count} 个告警,不要修复其他告警。",
|
|
1325
|
-
"",
|
|
1326
|
-
"优化目标:",
|
|
1327
|
-
f"1) 修复文件 {target_file_path} 中的 {warning_count} 个 Clippy 告警:",
|
|
1328
|
-
" - 根据以下 Clippy 告警信息,修复这些告警;",
|
|
1329
|
-
" - 告警信息包含文件路径、行号、警告类型、消息和建议,请根据这些信息进行修复;",
|
|
1330
|
-
" - 对于无法自动修复的告警,请根据 Clippy 的建议进行手动修复;",
|
|
1331
|
-
" - **如果确认是误报**(例如:告警建议的修改会导致性能下降、代码可读性降低、或与项目设计意图不符),可以添加 `#[allow(clippy::...)]` 注释来屏蔽该告警;",
|
|
1332
|
-
" - 使用 `#[allow(...)]` 时,必须在注释中说明为什么这是误报,例如:`#[allow(clippy::unnecessary_wraps)] // 保持 API 一致性,返回值类型需要与接口定义一致`;",
|
|
1333
|
-
" - 优先尝试修复告警,只有在确认是误报时才使用 `#[allow(...)]` 屏蔽。",
|
|
1334
|
-
"",
|
|
1335
|
-
"2) 修复已有实现的问题:",
|
|
1336
|
-
" - 如果在修复告警的过程中,发现代码已有的实现有问题(如逻辑错误、潜在 bug、性能问题、内存安全问题等),也需要一并修复;",
|
|
1337
|
-
" - 这些问题可能包括但不限于:空指针解引用、数组越界、未初始化的变量、资源泄漏、竞态条件等;",
|
|
1338
|
-
" - 修复时应该保持最小改动原则,优先修复最严重的问题。",
|
|
1339
|
-
"",
|
|
1340
|
-
"约束与范围:",
|
|
1341
|
-
f"- **仅修改文件 {target_file_path},不要修改其他文件**;除非必须(如修复引用路径),否则不要修改其他文件。",
|
|
1342
|
-
"- 保持最小改动,不要进行与消除告警无关的重构或格式化。",
|
|
1343
|
-
f"- **只修复该文件中的前 {warning_count} 个告警,不要修复其他告警**。",
|
|
1344
|
-
"- 修改后需保证 `cargo test` 可以通过;如需引入少量配套改动,请一并包含在补丁中以确保通过。",
|
|
1345
|
-
"- 输出仅为补丁,不要输出解释或多余文本。",
|
|
1346
|
-
"",
|
|
1347
|
-
"优先级说明:",
|
|
1348
|
-
"- **如果优化过程中出现了测试不通过或编译错误,必须优先解决这些问题**;",
|
|
1349
|
-
"- 在修复告警之前,先确保代码能够正常编译和通过测试;",
|
|
1350
|
-
"- 如果修复告警导致了编译错误或测试失败,必须立即修复这些错误,然后再继续优化。",
|
|
1351
|
-
"",
|
|
1352
|
-
"自检要求:在每次输出补丁后,请使用 execute_script 工具在 crate 根目录执行 `cargo test -q` 进行验证;",
|
|
1353
|
-
"若出现编译错误或测试失败,请优先修复这些问题,然后再继续修复告警;",
|
|
1354
|
-
"若未通过,请继续输出新的补丁进行最小修复并再次自检,直至 `cargo test` 通过为止。",
|
|
1355
|
-
"",
|
|
1356
|
-
f"文件 {target_file_path} 中的 Clippy 告警信息如下:",
|
|
1357
|
-
"<WARNINGS>",
|
|
1358
|
-
formatted_warnings,
|
|
1359
|
-
"</WARNINGS>",
|
|
1360
|
-
]
|
|
1361
|
-
prompt = "\n".join(prompt_lines)
|
|
1362
|
-
prompt = self._append_additional_notes(prompt)
|
|
1363
|
-
|
|
1364
|
-
# 修复前执行 cargo fmt
|
|
1365
|
-
_run_cargo_fmt(crate)
|
|
1366
|
-
|
|
1367
|
-
# 记录运行前的 commit id
|
|
1368
|
-
commit_before = self._get_crate_commit_hash()
|
|
1369
|
-
|
|
1370
|
-
# CodeAgent 在 crate 目录下创建和执行
|
|
1371
|
-
agent = CodeAgent(name=f"ClippyWarningEliminator-iter{iteration}", need_summary=False, non_interactive=self.options.non_interactive, model_group=self.options.llm_group)
|
|
1372
|
-
# 订阅 BEFORE_TOOL_CALL 和 AFTER_TOOL_CALL 事件,用于细粒度检测测试代码删除
|
|
1373
|
-
agent.event_bus.subscribe(BEFORE_TOOL_CALL, self._on_before_tool_call)
|
|
1374
|
-
agent.event_bus.subscribe(AFTER_TOOL_CALL, self._on_after_tool_call)
|
|
1375
|
-
# 记录 Agent 创建时的 commit id(作为初始值)
|
|
1376
|
-
agent_id = id(agent)
|
|
1377
|
-
agent_key = f"agent_{agent_id}"
|
|
1378
|
-
initial_commit = self._get_crate_commit_hash()
|
|
1379
|
-
if initial_commit:
|
|
1380
|
-
self._agent_before_commits[agent_key] = initial_commit
|
|
1381
|
-
agent.run(prompt, prefix="[c2rust-optimizer][codeagent][clippy]", suffix="")
|
|
1382
|
-
|
|
1383
|
-
# 检测并处理测试代码删除
|
|
1384
|
-
if self._check_and_handle_test_deletion(commit_before, agent):
|
|
1385
|
-
# 如果回退了,需要重新运行 agent
|
|
1386
|
-
typer.secho(f"[c2rust-optimizer][codeagent][clippy] 检测到测试代码删除问题,已回退,重新运行 agent (iter={iteration})", fg=typer.colors.YELLOW)
|
|
1387
|
-
commit_before = self._get_crate_commit_hash()
|
|
1388
|
-
agent.run(prompt, prefix="[c2rust-optimizer][codeagent][clippy][retry]", suffix="")
|
|
1389
|
-
# 再次检测
|
|
1390
|
-
if self._check_and_handle_test_deletion(commit_before, agent):
|
|
1391
|
-
typer.secho(f"[c2rust-optimizer][codeagent][clippy] 再次检测到测试代码删除问题,已回退 (iter={iteration})", fg=typer.colors.RED)
|
|
1392
|
-
|
|
1393
|
-
# 验证修复是否成功(通过 cargo test)
|
|
1394
|
-
ok, _ = _cargo_check_full(crate, self.stats, self.options.max_checks, timeout=self.options.cargo_test_timeout)
|
|
1395
|
-
if ok:
|
|
1396
|
-
# 修复成功,保存进度和 commit id
|
|
1397
|
-
try:
|
|
1398
|
-
file_path = crate / target_file_path if not Path(target_file_path).is_absolute() else Path(target_file_path)
|
|
1399
|
-
if file_path.exists():
|
|
1400
|
-
self._save_fix_progress("clippy_elimination", f"{target_file_path}-iter{iteration}", [file_path])
|
|
1401
|
-
else:
|
|
1402
|
-
self._save_fix_progress("clippy_elimination", f"{target_file_path}-iter{iteration}", None)
|
|
1403
|
-
except Exception:
|
|
1404
|
-
self._save_fix_progress("clippy_elimination", f"{target_file_path}-iter{iteration}", None)
|
|
1405
|
-
typer.secho(f"[c2rust-optimizer][codeagent][clippy] 文件 {target_file_path} 的前 {warning_count} 个告警修复成功,已保存进度", fg=typer.colors.GREEN)
|
|
1406
|
-
else:
|
|
1407
|
-
# 测试失败,回退到运行前的 commit
|
|
1408
|
-
if commit_before:
|
|
1409
|
-
typer.secho(f"[c2rust-optimizer][codeagent][clippy] 文件 {target_file_path} 修复后测试失败,回退到运行前的 commit: {commit_before[:8]}", fg=typer.colors.YELLOW)
|
|
1410
|
-
if self._reset_to_commit(commit_before):
|
|
1411
|
-
typer.secho(f"[c2rust-optimizer][codeagent][clippy] 已成功回退到 commit: {commit_before[:8]}", fg=typer.colors.CYAN)
|
|
1412
|
-
else:
|
|
1413
|
-
typer.secho("[c2rust-optimizer][codeagent][clippy] 回退失败,请手动检查代码状态", fg=typer.colors.RED)
|
|
1414
|
-
else:
|
|
1415
|
-
typer.secho(f"[c2rust-optimizer][codeagent][clippy] 文件 {target_file_path} 修复后测试失败,但无法获取运行前的 commit,继续修复", fg=typer.colors.YELLOW)
|
|
1416
|
-
|
|
1417
|
-
# 修复后再次检查告警,如果告警数量没有减少,可能需要停止
|
|
1418
|
-
has_warnings_after, _ = _check_clippy_warnings(crate)
|
|
1419
|
-
if not has_warnings_after:
|
|
1420
|
-
typer.secho(f"[c2rust-optimizer][codeagent][clippy] 所有告警已消除(共迭代 {iteration} 次)", fg=typer.colors.GREEN)
|
|
1421
|
-
return True # 所有告警已消除
|
|
1422
|
-
finally:
|
|
1423
|
-
os.chdir(prev_cwd)
|
|
1424
|
-
|
|
1425
|
-
# 默认返回 False(仍有告警)
|
|
1426
|
-
return False
|
|
1427
|
-
|
|
1428
|
-
def _extract_warnings_by_file(self, clippy_json_output: str) -> Dict[str, List[Dict]]:
|
|
1429
|
-
"""
|
|
1430
|
-
从 clippy JSON 输出中提取所有告警并按文件分组。
|
|
1431
|
-
|
|
1432
|
-
Returns:
|
|
1433
|
-
字典,键为文件路径,值为该文件的告警列表
|
|
1434
|
-
"""
|
|
1435
|
-
if not clippy_json_output:
|
|
1436
|
-
return {}
|
|
1437
|
-
|
|
1438
|
-
warnings_by_file: Dict[str, List[Dict]] = {}
|
|
1439
|
-
|
|
1440
|
-
for line in clippy_json_output.splitlines():
|
|
1441
|
-
line = line.strip()
|
|
1442
|
-
if not line:
|
|
1443
|
-
continue
|
|
1444
|
-
try:
|
|
1445
|
-
msg = json.loads(line)
|
|
1446
|
-
# 只处理 warning 类型的消息
|
|
1447
|
-
if msg.get("reason") == "compiler-message" and msg.get("message", {}).get("level") == "warning":
|
|
1448
|
-
message = msg.get("message", {})
|
|
1449
|
-
spans = message.get("spans", [])
|
|
1450
|
-
if spans:
|
|
1451
|
-
primary_span = spans[0]
|
|
1452
|
-
file_path = primary_span.get("file_name", "")
|
|
1453
|
-
if file_path:
|
|
1454
|
-
if file_path not in warnings_by_file:
|
|
1455
|
-
warnings_by_file[file_path] = []
|
|
1456
|
-
warnings_by_file[file_path].append(msg)
|
|
1457
|
-
except (json.JSONDecodeError, KeyError, TypeError):
|
|
1458
|
-
continue
|
|
1459
|
-
|
|
1460
|
-
return warnings_by_file
|
|
1461
|
-
|
|
1462
|
-
def _format_warnings_for_prompt(self, warnings: List[Dict], max_count: int = 10) -> str:
|
|
1463
|
-
"""
|
|
1464
|
-
格式化告警列表,用于提示词。
|
|
1465
|
-
|
|
1466
|
-
Args:
|
|
1467
|
-
warnings: 告警消息列表
|
|
1468
|
-
max_count: 最多格式化多少个告警(默认10个)
|
|
1469
|
-
|
|
1470
|
-
Returns:
|
|
1471
|
-
格式化后的告警信息字符串
|
|
1472
|
-
"""
|
|
1473
|
-
if not warnings:
|
|
1474
|
-
return ""
|
|
1475
|
-
|
|
1476
|
-
# 只取前 max_count 个告警
|
|
1477
|
-
warnings_to_format = warnings[:max_count]
|
|
1478
|
-
formatted_warnings = []
|
|
1479
|
-
|
|
1480
|
-
for idx, warning_msg in enumerate(warnings_to_format, 1):
|
|
1481
|
-
message = warning_msg.get("message", {})
|
|
1482
|
-
spans = message.get("spans", [])
|
|
1483
|
-
|
|
1484
|
-
warning_parts = [f"告警 {idx}:"]
|
|
1485
|
-
|
|
1486
|
-
# 警告类型和消息
|
|
1487
|
-
code = message.get("code", {})
|
|
1488
|
-
code_str = code.get("code", "") if code else ""
|
|
1489
|
-
message_text = message.get("message", "")
|
|
1490
|
-
warning_parts.append(f" 警告类型: {code_str}")
|
|
1491
|
-
warning_parts.append(f" 消息: {message_text}")
|
|
1492
|
-
|
|
1493
|
-
# 文件位置
|
|
1494
|
-
if spans:
|
|
1495
|
-
primary_span = spans[0]
|
|
1496
|
-
line_start = primary_span.get("line_start", 0)
|
|
1497
|
-
column_start = primary_span.get("column_start", 0)
|
|
1498
|
-
line_end = primary_span.get("line_end", 0)
|
|
1499
|
-
column_end = primary_span.get("column_end", 0)
|
|
1500
|
-
|
|
1501
|
-
if line_start == line_end:
|
|
1502
|
-
warning_parts.append(f" 位置: {line_start}:{column_start}-{column_end}")
|
|
1503
|
-
else:
|
|
1504
|
-
warning_parts.append(f" 位置: {line_start}:{column_start} - {line_end}:{column_end}")
|
|
1505
|
-
|
|
1506
|
-
# 代码片段
|
|
1507
|
-
label = primary_span.get("label", "")
|
|
1508
|
-
if label:
|
|
1509
|
-
warning_parts.append(f" 代码: {label}")
|
|
1510
|
-
|
|
1511
|
-
# 建议(help 消息)
|
|
1512
|
-
children = message.get("children", [])
|
|
1513
|
-
for child in children:
|
|
1514
|
-
if child.get("level") == "help":
|
|
1515
|
-
help_message = child.get("message", "")
|
|
1516
|
-
help_spans = child.get("spans", [])
|
|
1517
|
-
if help_message:
|
|
1518
|
-
warning_parts.append(f" 建议: {help_message}")
|
|
1519
|
-
if help_spans:
|
|
1520
|
-
help_span = help_spans[0]
|
|
1521
|
-
help_label = help_span.get("label", "")
|
|
1522
|
-
if help_label:
|
|
1523
|
-
warning_parts.append(f" 建议代码: {help_label}")
|
|
1524
|
-
|
|
1525
|
-
formatted_warnings.append("\n".join(warning_parts))
|
|
1526
|
-
|
|
1527
|
-
if len(warnings) > max_count:
|
|
1528
|
-
formatted_warnings.append(f"\n(该文件还有 {len(warnings) - max_count} 个告警,将在后续迭代中处理)")
|
|
1529
|
-
|
|
1530
|
-
return "\n\n".join(formatted_warnings)
|
|
1531
|
-
|
|
1532
|
-
# ========== 1) unsafe cleanup (CodeAgent) ==========
|
|
1533
|
-
|
|
1534
|
-
def _codeagent_opt_unsafe_cleanup(self, target_files: List[Path]) -> None:
|
|
1535
|
-
"""
|
|
1536
|
-
使用 CodeAgent 进行 unsafe 清理优化。
|
|
1537
|
-
使用 clippy 的 missing_safety_doc checker 来查找 unsafe 告警,按文件处理,每次处理一个文件的所有告警。
|
|
1538
|
-
|
|
1539
|
-
注意:CodeAgent 必须在 crate 目录下创建和执行,以确保所有文件操作和命令执行都在正确的上下文中进行。
|
|
1540
|
-
"""
|
|
1541
|
-
crate = self.crate_dir.resolve()
|
|
1542
|
-
file_list: List[str] = []
|
|
1543
|
-
for p in target_files:
|
|
1544
|
-
try:
|
|
1545
|
-
rel = p.resolve().relative_to(crate).as_posix()
|
|
1546
|
-
except Exception:
|
|
1547
|
-
rel = p.as_posix()
|
|
1548
|
-
file_list.append(rel)
|
|
1549
|
-
self.stats.files_scanned += 1
|
|
1550
|
-
|
|
1551
|
-
# 切换到 crate 目录,确保 CodeAgent 在正确的上下文中创建和执行
|
|
1552
|
-
prev_cwd = os.getcwd()
|
|
1553
|
-
iteration = 0
|
|
1554
|
-
|
|
1555
|
-
try:
|
|
1556
|
-
os.chdir(str(crate))
|
|
1557
|
-
|
|
1558
|
-
# 循环修复 unsafe 告警,按文件处理
|
|
1559
|
-
while True:
|
|
1560
|
-
iteration += 1
|
|
1561
|
-
|
|
1562
|
-
# 检查当前 missing_safety_doc 告警
|
|
1563
|
-
has_warnings, current_clippy_output = _check_missing_safety_doc_warnings(crate)
|
|
1564
|
-
if not has_warnings:
|
|
1565
|
-
typer.secho(f"[c2rust-optimizer][codeagent][unsafe-cleanup] 所有 missing_safety_doc 告警已消除(共迭代 {iteration - 1} 次)", fg=typer.colors.GREEN)
|
|
1566
|
-
return # 所有告警已消除
|
|
1567
|
-
|
|
1568
|
-
# 按文件提取告警
|
|
1569
|
-
warnings_by_file = self._extract_warnings_by_file(current_clippy_output)
|
|
1570
|
-
if not warnings_by_file:
|
|
1571
|
-
typer.secho("[c2rust-optimizer][codeagent][unsafe-cleanup] 无法提取告警,停止修复", fg=typer.colors.YELLOW)
|
|
1572
|
-
return # 仍有告警未消除
|
|
1573
|
-
|
|
1574
|
-
# 找到第一个有告警的文件(优先处理目标文件列表中的文件)
|
|
1575
|
-
target_file_path = None
|
|
1576
|
-
target_warnings = None
|
|
1577
|
-
|
|
1578
|
-
# 优先处理目标文件列表中的文件
|
|
1579
|
-
for file_rel in file_list:
|
|
1580
|
-
# 尝试匹配文件路径(可能是相对路径或绝对路径)
|
|
1581
|
-
for file_path, warnings in warnings_by_file.items():
|
|
1582
|
-
if file_rel in file_path or file_path.endswith(file_rel):
|
|
1583
|
-
target_file_path = file_path
|
|
1584
|
-
target_warnings = warnings
|
|
1585
|
-
break
|
|
1586
|
-
if target_file_path:
|
|
1587
|
-
break
|
|
1588
|
-
|
|
1589
|
-
# 如果目标文件列表中没有告警,选择第一个有告警的文件
|
|
1590
|
-
if not target_file_path:
|
|
1591
|
-
target_file_path = next(iter(warnings_by_file.keys()))
|
|
1592
|
-
target_warnings = warnings_by_file[target_file_path]
|
|
1593
|
-
|
|
1594
|
-
# 获取该文件的所有告警(一次处理一个文件的所有告警)
|
|
1595
|
-
warnings_to_fix = target_warnings
|
|
1596
|
-
warning_count = len(warnings_to_fix)
|
|
1597
|
-
|
|
1598
|
-
typer.secho(f"[c2rust-optimizer][codeagent][unsafe-cleanup] 第 {iteration} 次迭代:修复文件 {target_file_path} 的 {warning_count} 个 missing_safety_doc 告警", fg=typer.colors.CYAN)
|
|
1599
|
-
|
|
1600
|
-
# 格式化告警信息
|
|
1601
|
-
formatted_warnings = self._format_warnings_for_prompt(warnings_to_fix, max_count=len(warnings_to_fix))
|
|
1602
|
-
|
|
1603
|
-
# 构建提示词,修复该文件的所有 missing_safety_doc 告警
|
|
1604
|
-
prompt_lines: List[str] = [
|
|
1605
|
-
"你是资深 Rust 代码工程师。请在当前 crate 下修复指定文件中的 missing_safety_doc 告警,并以补丁形式输出修改:",
|
|
1606
|
-
f"- crate 根目录:{crate}",
|
|
1607
|
-
"",
|
|
1608
|
-
"本次优化仅允许修改以下文件(严格限制,只处理这一个文件):",
|
|
1609
|
-
f"- {target_file_path}",
|
|
1610
|
-
"",
|
|
1611
|
-
f"重要:本次修复仅修复该文件中的 {warning_count} 个 missing_safety_doc 告警。",
|
|
1612
|
-
"",
|
|
1613
|
-
"优化目标:",
|
|
1614
|
-
f"1) 修复文件 {target_file_path} 中的 {warning_count} 个 missing_safety_doc 告警:",
|
|
1615
|
-
" **修复原则:能消除就消除,不能消除才增加 SAFETY 注释**",
|
|
1616
|
-
"",
|
|
1617
|
-
" 优先级 1(优先尝试):消除 unsafe",
|
|
1618
|
-
" - 如果 unsafe 函数或方法实际上不需要是 unsafe 的,应该移除 unsafe 关键字;",
|
|
1619
|
-
" - 如果 unsafe 块可以移除,应该移除整个 unsafe 块;",
|
|
1620
|
-
" - 如果 unsafe 块可以缩小范围,应该缩小范围;",
|
|
1621
|
-
" - 仔细分析代码,判断是否真的需要 unsafe,如果可以通过安全的方式实现,优先使用安全的方式。",
|
|
1622
|
-
"",
|
|
1623
|
-
" 优先级 2(无法消除时):添加 SAFETY 注释",
|
|
1624
|
-
" - 只有在确认无法消除 unsafe 的情况下,才为 unsafe 函数或方法添加 `/// SAFETY: ...` 文档注释;",
|
|
1625
|
-
" - SAFETY 注释必须详细说明为什么该函数或方法是 unsafe 的,包括:",
|
|
1626
|
-
" * 哪些不变量必须由调用者维护;",
|
|
1627
|
-
" * 哪些前提条件必须满足;",
|
|
1628
|
-
" * 可能导致未定义行为的情况;",
|
|
1629
|
-
" * 为什么不能使用安全的替代方案;",
|
|
1630
|
-
" - 如果 unsafe 块无法移除但可以缩小范围,应该缩小范围并在紧邻位置添加 `/// SAFETY: ...` 注释。",
|
|
1631
|
-
"",
|
|
1632
|
-
"2) 修复已有实现的问题:",
|
|
1633
|
-
" - 如果在修复 missing_safety_doc 告警的过程中,发现代码已有的实现有问题(如逻辑错误、潜在 bug、性能问题、内存安全问题等),也需要一并修复;",
|
|
1634
|
-
" - 这些问题可能包括但不限于:不正确的 unsafe 使用、未检查的边界条件、资源泄漏、竞态条件、数据竞争等;",
|
|
1635
|
-
" - 修复时应该保持最小改动原则,优先修复最严重的问题。",
|
|
1636
|
-
"",
|
|
1637
|
-
"约束与范围:",
|
|
1638
|
-
f"- **仅修改文件 {target_file_path},不要修改其他文件**;除非必须(如修复引用路径),否则不要修改其他文件。",
|
|
1639
|
-
"- 保持最小改动,不要进行与修复 missing_safety_doc 告警无关的重构或格式化。",
|
|
1640
|
-
f"- **只修复该文件中的 {warning_count} 个 missing_safety_doc 告警,不要修复其他告警**。",
|
|
1641
|
-
"- 修改后需保证 `cargo test` 可以通过;如需引入少量配套改动,请一并包含在补丁中以确保通过。",
|
|
1642
|
-
"- 输出仅为补丁,不要输出解释或多余文本。",
|
|
1643
|
-
"",
|
|
1644
|
-
"优先级说明:",
|
|
1645
|
-
"- **修复 unsafe 的优先级:能消除就消除,不能消除才增加 SAFETY 注释**;",
|
|
1646
|
-
"- 对于每个 unsafe,首先尝试分析是否可以安全地移除,只有在确认无法移除时才添加 SAFETY 注释;",
|
|
1647
|
-
"- **如果优化过程中出现了测试不通过或编译错误,必须优先解决这些问题**;",
|
|
1648
|
-
"- 在修复告警之前,先确保代码能够正常编译和通过测试;",
|
|
1649
|
-
"- 如果修复告警导致了编译错误或测试失败,必须立即修复这些错误,然后再继续优化。",
|
|
1650
|
-
"",
|
|
1651
|
-
"自检要求:在每次输出补丁后,请使用 execute_script 工具在 crate 根目录执行 `cargo test -q` 进行验证;",
|
|
1652
|
-
"若出现编译错误或测试失败,请优先修复这些问题,然后再继续修复告警;",
|
|
1653
|
-
"若未通过,请继续输出新的补丁进行最小修复并再次自检,直至 `cargo test` 通过为止。",
|
|
1654
|
-
"",
|
|
1655
|
-
f"文件 {target_file_path} 中的 missing_safety_doc 告警信息如下:",
|
|
1656
|
-
"<WARNINGS>",
|
|
1657
|
-
formatted_warnings,
|
|
1658
|
-
"</WARNINGS>",
|
|
1659
|
-
]
|
|
1660
|
-
prompt = "\n".join(prompt_lines)
|
|
1661
|
-
prompt = self._append_additional_notes(prompt)
|
|
1662
|
-
|
|
1663
|
-
# 修复前执行 cargo fmt
|
|
1664
|
-
_run_cargo_fmt(crate)
|
|
1665
|
-
|
|
1666
|
-
# 记录运行前的 commit id
|
|
1667
|
-
commit_before = self._get_crate_commit_hash()
|
|
1668
|
-
|
|
1669
|
-
# CodeAgent 在 crate 目录下创建和执行
|
|
1670
|
-
agent = CodeAgent(name=f"UnsafeCleanupAgent-iter{iteration}", need_summary=False, non_interactive=self.options.non_interactive, model_group=self.options.llm_group)
|
|
1671
|
-
# 订阅 BEFORE_TOOL_CALL 和 AFTER_TOOL_CALL 事件,用于细粒度检测测试代码删除
|
|
1672
|
-
agent.event_bus.subscribe(BEFORE_TOOL_CALL, self._on_before_tool_call)
|
|
1673
|
-
agent.event_bus.subscribe(AFTER_TOOL_CALL, self._on_after_tool_call)
|
|
1674
|
-
# 记录 Agent 创建时的 commit id(作为初始值)
|
|
1675
|
-
agent_id = id(agent)
|
|
1676
|
-
agent_key = f"agent_{agent_id}"
|
|
1677
|
-
initial_commit = self._get_crate_commit_hash()
|
|
1678
|
-
if initial_commit:
|
|
1679
|
-
self._agent_before_commits[agent_key] = initial_commit
|
|
1680
|
-
agent.run(prompt, prefix=f"[c2rust-optimizer][codeagent][unsafe-cleanup][iter{iteration}]", suffix="")
|
|
1681
|
-
|
|
1682
|
-
# 检测并处理测试代码删除
|
|
1683
|
-
if self._check_and_handle_test_deletion(commit_before, agent):
|
|
1684
|
-
# 如果回退了,需要重新运行 agent
|
|
1685
|
-
typer.secho(f"[c2rust-optimizer][codeagent][unsafe-cleanup] 检测到测试代码删除问题,已回退,重新运行 agent (iter={iteration})", fg=typer.colors.YELLOW)
|
|
1686
|
-
commit_before = self._get_crate_commit_hash()
|
|
1687
|
-
agent.run(prompt, prefix=f"[c2rust-optimizer][codeagent][unsafe-cleanup][iter{iteration}][retry]", suffix="")
|
|
1688
|
-
# 再次检测
|
|
1689
|
-
if self._check_and_handle_test_deletion(commit_before, agent):
|
|
1690
|
-
typer.secho(f"[c2rust-optimizer][codeagent][unsafe-cleanup] 再次检测到测试代码删除问题,已回退 (iter={iteration})", fg=typer.colors.RED)
|
|
1691
|
-
|
|
1692
|
-
# 验证修复是否成功(通过 cargo test)
|
|
1693
|
-
ok, _ = _cargo_check_full(crate, self.stats, self.options.max_checks, timeout=self.options.cargo_test_timeout)
|
|
1694
|
-
if ok:
|
|
1695
|
-
# 修复成功,保存进度和 commit id
|
|
1696
|
-
try:
|
|
1697
|
-
file_path = crate / target_file_path if not Path(target_file_path).is_absolute() else Path(target_file_path)
|
|
1698
|
-
if file_path.exists():
|
|
1699
|
-
self._save_fix_progress("unsafe_cleanup", f"{target_file_path}-iter{iteration}", [file_path])
|
|
1700
|
-
else:
|
|
1701
|
-
self._save_fix_progress("unsafe_cleanup", f"{target_file_path}-iter{iteration}", None)
|
|
1702
|
-
except Exception:
|
|
1703
|
-
self._save_fix_progress("unsafe_cleanup", f"{target_file_path}-iter{iteration}", None)
|
|
1704
|
-
typer.secho(f"[c2rust-optimizer][codeagent][unsafe-cleanup] 文件 {target_file_path} 的 {warning_count} 个告警修复成功,已保存进度", fg=typer.colors.GREEN)
|
|
1705
|
-
else:
|
|
1706
|
-
# 测试失败,回退到运行前的 commit
|
|
1707
|
-
if commit_before:
|
|
1708
|
-
typer.secho(f"[c2rust-optimizer][codeagent][unsafe-cleanup] 文件 {target_file_path} 修复后测试失败,回退到运行前的 commit: {commit_before[:8]}", fg=typer.colors.YELLOW)
|
|
1709
|
-
if self._reset_to_commit(commit_before):
|
|
1710
|
-
typer.secho(f"[c2rust-optimizer][codeagent][unsafe-cleanup] 已成功回退到 commit: {commit_before[:8]}", fg=typer.colors.CYAN)
|
|
1711
|
-
else:
|
|
1712
|
-
typer.secho("[c2rust-optimizer][codeagent][unsafe-cleanup] 回退失败,请手动检查代码状态", fg=typer.colors.RED)
|
|
1713
|
-
else:
|
|
1714
|
-
typer.secho(f"[c2rust-optimizer][codeagent][unsafe-cleanup] 文件 {target_file_path} 修复后测试失败,但无法获取运行前的 commit,继续修复", fg=typer.colors.YELLOW)
|
|
1715
|
-
|
|
1716
|
-
# 修复后再次检查告警
|
|
1717
|
-
has_warnings_after, _ = _check_missing_safety_doc_warnings(crate)
|
|
1718
|
-
if not has_warnings_after:
|
|
1719
|
-
typer.secho(f"[c2rust-optimizer][codeagent][unsafe-cleanup] 所有 missing_safety_doc 告警已消除(共迭代 {iteration} 次)", fg=typer.colors.GREEN)
|
|
1720
|
-
return # 所有告警已消除
|
|
1721
|
-
|
|
1722
|
-
finally:
|
|
1723
|
-
os.chdir(prev_cwd)
|
|
1724
|
-
|
|
1725
|
-
# ========== 2) visibility optimization (CodeAgent) ==========
|
|
1726
|
-
|
|
1727
|
-
def _codeagent_opt_visibility(self, target_files: List[Path]) -> None:
|
|
1728
|
-
"""
|
|
1729
|
-
使用 CodeAgent 进行可见性优化。
|
|
1730
|
-
|
|
1731
|
-
注意:CodeAgent 必须在 crate 目录下创建和执行,以确保所有文件操作和命令执行都在正确的上下文中进行。
|
|
1732
|
-
"""
|
|
1733
|
-
crate = self.crate_dir.resolve()
|
|
1734
|
-
file_list: List[str] = []
|
|
1735
|
-
for p in target_files:
|
|
1736
|
-
try:
|
|
1737
|
-
rel = p.resolve().relative_to(crate).as_posix()
|
|
1738
|
-
except Exception:
|
|
1739
|
-
rel = p.as_posix()
|
|
1740
|
-
file_list.append(rel)
|
|
1741
|
-
self.stats.files_scanned += 1
|
|
1742
|
-
|
|
1743
|
-
prompt_lines: List[str] = [
|
|
1744
|
-
"你是资深 Rust 代码工程师。请在当前 crate 下执行可见性优化,并以补丁形式输出修改:",
|
|
1745
|
-
f"- crate 根目录:{crate}",
|
|
1746
|
-
"",
|
|
1747
|
-
"本次优化仅允许修改以下文件范围(严格限制):",
|
|
1748
|
-
*[f"- {rel}" for rel in file_list],
|
|
1749
|
-
"",
|
|
1750
|
-
"优化目标:",
|
|
1751
|
-
"1) 可见性最小化:",
|
|
1752
|
-
" - 优先将 `pub fn` 降为 `pub(crate) fn`(如果函数仅在 crate 内部使用);",
|
|
1753
|
-
" - 保持对外接口(跨 crate 使用的接口,如 lib.rs 中的顶层导出)为 `pub`;",
|
|
1754
|
-
" - 在 lib.rs 中的顶层导出保持现状,不要修改。",
|
|
1755
|
-
"",
|
|
1756
|
-
"2) 修复已有实现的问题:",
|
|
1757
|
-
" - 如果在进行可见性优化的过程中,发现代码已有的实现有问题(如逻辑错误、潜在 bug、性能问题、内存安全问题等),也需要一并修复;",
|
|
1758
|
-
" - 这些问题可能包括但不限于:不正确的可见性设计、未检查的边界条件、资源泄漏、竞态条件等;",
|
|
1759
|
-
" - 修复时应该保持最小改动原则,优先修复最严重的问题。",
|
|
1760
|
-
"",
|
|
1761
|
-
"约束与范围:",
|
|
1762
|
-
"- 仅修改上述列出的文件;除非必须(如修复引用路径),否则不要修改其他文件。",
|
|
1763
|
-
"- 保持最小改动,不要进行与可见性优化无关的重构或格式化。",
|
|
1764
|
-
"- 修改后需保证 `cargo test` 可以通过;如需引入少量配套改动,请一并包含在补丁中以确保通过。",
|
|
1765
|
-
"- 输出仅为补丁,不要输出解释或多余文本。",
|
|
1766
|
-
"",
|
|
1767
|
-
"优先级说明:",
|
|
1768
|
-
"- **如果优化过程中出现了测试不通过或编译错误,必须优先解决这些问题**;",
|
|
1769
|
-
"- 在进行可见性优化之前,先确保代码能够正常编译和通过测试;",
|
|
1770
|
-
"- 如果可见性优化导致了编译错误或测试失败,必须立即修复这些错误,然后再继续优化。",
|
|
1771
|
-
"",
|
|
1772
|
-
"自检要求:在每次输出补丁后,请使用 execute_script 工具在 crate 根目录执行 `cargo test -q` 进行验证;",
|
|
1773
|
-
"若出现编译错误或测试失败,请优先修复这些问题,然后再继续可见性优化;",
|
|
1774
|
-
"若未通过,请继续输出新的补丁进行最小修复并再次自检,直至 `cargo test` 通过为止。"
|
|
1775
|
-
]
|
|
1776
|
-
prompt = "\n".join(prompt_lines)
|
|
1777
|
-
prompt = self._append_additional_notes(prompt)
|
|
1778
|
-
# 切换到 crate 目录,确保 CodeAgent 在正确的上下文中创建和执行
|
|
1779
|
-
prev_cwd = os.getcwd()
|
|
1780
|
-
typer.secho("[c2rust-optimizer][codeagent][visibility] 正在调用 CodeAgent 进行可见性优化...", fg=typer.colors.CYAN)
|
|
1781
|
-
try:
|
|
1782
|
-
os.chdir(str(crate))
|
|
1783
|
-
# 修复前执行 cargo fmt
|
|
1784
|
-
_run_cargo_fmt(crate)
|
|
1785
|
-
|
|
1786
|
-
# 记录运行前的 commit id
|
|
1787
|
-
commit_before = self._get_crate_commit_hash()
|
|
1788
|
-
|
|
1789
|
-
# CodeAgent 在 crate 目录下创建和执行
|
|
1790
|
-
agent = CodeAgent(name="VisibilityOptimizer", need_summary=False, non_interactive=self.options.non_interactive, model_group=self.options.llm_group)
|
|
1791
|
-
# 订阅 BEFORE_TOOL_CALL 和 AFTER_TOOL_CALL 事件,用于细粒度检测测试代码删除
|
|
1792
|
-
agent.event_bus.subscribe(BEFORE_TOOL_CALL, self._on_before_tool_call)
|
|
1793
|
-
agent.event_bus.subscribe(AFTER_TOOL_CALL, self._on_after_tool_call)
|
|
1794
|
-
# 记录 Agent 创建时的 commit id(作为初始值)
|
|
1795
|
-
agent_id = id(agent)
|
|
1796
|
-
agent_key = f"agent_{agent_id}"
|
|
1797
|
-
initial_commit = self._get_crate_commit_hash()
|
|
1798
|
-
if initial_commit:
|
|
1799
|
-
self._agent_before_commits[agent_key] = initial_commit
|
|
1800
|
-
agent.run(prompt, prefix="[c2rust-optimizer][codeagent][visibility]", suffix="")
|
|
1801
|
-
|
|
1802
|
-
# 检测并处理测试代码删除
|
|
1803
|
-
if self._check_and_handle_test_deletion(commit_before, agent):
|
|
1804
|
-
# 如果回退了,需要重新运行 agent
|
|
1805
|
-
typer.secho("[c2rust-optimizer][codeagent][visibility] 检测到测试代码删除问题,已回退,重新运行 agent", fg=typer.colors.YELLOW)
|
|
1806
|
-
commit_before = self._get_crate_commit_hash()
|
|
1807
|
-
agent.run(prompt, prefix="[c2rust-optimizer][codeagent][visibility][retry]", suffix="")
|
|
1808
|
-
# 再次检测
|
|
1809
|
-
if self._check_and_handle_test_deletion(commit_before, agent):
|
|
1810
|
-
typer.secho("[c2rust-optimizer][codeagent][visibility] 再次检测到测试代码删除问题,已回退", fg=typer.colors.RED)
|
|
1811
|
-
|
|
1812
|
-
# 验证修复是否成功(通过 cargo test)
|
|
1813
|
-
ok, _ = _cargo_check_full(crate, self.stats, self.options.max_checks, timeout=self.options.cargo_test_timeout)
|
|
1814
|
-
if ok:
|
|
1815
|
-
# 修复成功,保存进度和 commit id
|
|
1816
|
-
file_paths = [crate / f for f in file_list if (crate / f).exists()]
|
|
1817
|
-
self._save_fix_progress("visibility_opt", "batch", file_paths if file_paths else None)
|
|
1818
|
-
typer.secho("[c2rust-optimizer][codeagent][visibility] 可见性优化成功,已保存进度", fg=typer.colors.GREEN)
|
|
1819
|
-
else:
|
|
1820
|
-
# 测试失败,回退到运行前的 commit
|
|
1821
|
-
if commit_before:
|
|
1822
|
-
typer.secho(f"[c2rust-optimizer][codeagent][visibility] 可见性优化后测试失败,回退到运行前的 commit: {commit_before[:8]}", fg=typer.colors.YELLOW)
|
|
1823
|
-
if self._reset_to_commit(commit_before):
|
|
1824
|
-
typer.secho(f"[c2rust-optimizer][codeagent][visibility] 已成功回退到 commit: {commit_before[:8]}", fg=typer.colors.CYAN)
|
|
1825
|
-
else:
|
|
1826
|
-
typer.secho("[c2rust-optimizer][codeagent][visibility] 回退失败,请手动检查代码状态", fg=typer.colors.RED)
|
|
1827
|
-
else:
|
|
1828
|
-
typer.secho("[c2rust-optimizer][codeagent][visibility] 可见性优化后测试失败,但无法获取运行前的 commit", fg=typer.colors.YELLOW)
|
|
1829
|
-
finally:
|
|
1830
|
-
os.chdir(prev_cwd)
|
|
1831
|
-
|
|
1832
|
-
# ========== 3) doc augmentation (CodeAgent) ==========
|
|
1833
|
-
|
|
1834
|
-
def _codeagent_opt_docs(self, target_files: List[Path]) -> None:
|
|
1835
|
-
"""
|
|
1836
|
-
使用 CodeAgent 进行文档补充。
|
|
1837
|
-
|
|
1838
|
-
注意:CodeAgent 必须在 crate 目录下创建和执行,以确保所有文件操作和命令执行都在正确的上下文中进行。
|
|
1839
|
-
"""
|
|
1840
|
-
crate = self.crate_dir.resolve()
|
|
1841
|
-
file_list: List[str] = []
|
|
1842
|
-
for p in target_files:
|
|
1843
|
-
try:
|
|
1844
|
-
rel = p.resolve().relative_to(crate).as_posix()
|
|
1845
|
-
except Exception:
|
|
1846
|
-
rel = p.as_posix()
|
|
1847
|
-
file_list.append(rel)
|
|
1848
|
-
self.stats.files_scanned += 1
|
|
1849
|
-
|
|
1850
|
-
prompt_lines: List[str] = [
|
|
1851
|
-
"你是资深 Rust 代码工程师。请在当前 crate 下执行文档补充优化,并以补丁形式输出修改:",
|
|
1852
|
-
f"- crate 根目录:{crate}",
|
|
1853
|
-
"",
|
|
1854
|
-
"本次优化仅允许修改以下文件范围(严格限制):",
|
|
1855
|
-
*[f"- {rel}" for rel in file_list],
|
|
1856
|
-
"",
|
|
1857
|
-
"优化目标:",
|
|
1858
|
-
"1) 文档补充:",
|
|
1859
|
-
" - 为缺少模块级文档的文件添加 `//! ...` 模块文档注释(放在文件开头);",
|
|
1860
|
-
" - 为缺少函数文档的公共函数(pub 或 pub(crate))添加 `/// ...` 文档注释;",
|
|
1861
|
-
" - 文档内容可以是占位注释(如 `//! TODO: Add module-level documentation` 或 `/// TODO: Add documentation`),也可以根据函数签名和实现提供简要说明。",
|
|
1862
|
-
"",
|
|
1863
|
-
"2) 修复已有实现的问题:",
|
|
1864
|
-
" - 如果在进行文档补充的过程中,发现代码已有的实现有问题(如逻辑错误、潜在 bug、性能问题、内存安全问题等),也需要一并修复;",
|
|
1865
|
-
" - 这些问题可能包括但不限于:不正确的算法实现、未检查的边界条件、资源泄漏、竞态条件等;",
|
|
1866
|
-
" - 修复时应该保持最小改动原则,优先修复最严重的问题。",
|
|
1867
|
-
"",
|
|
1868
|
-
"约束与范围:",
|
|
1869
|
-
"- 仅修改上述列出的文件;除非必须(如修复引用路径),否则不要修改其他文件。",
|
|
1870
|
-
"- 保持最小改动,不要进行与文档补充无关的重构或格式化。",
|
|
1871
|
-
"- 修改后需保证 `cargo test` 可以通过;如需引入少量配套改动,请一并包含在补丁中以确保通过。",
|
|
1872
|
-
"- 输出仅为补丁,不要输出解释或多余文本。",
|
|
1873
|
-
"",
|
|
1874
|
-
"优先级说明:",
|
|
1875
|
-
"- **如果优化过程中出现了测试不通过或编译错误,必须优先解决这些问题**;",
|
|
1876
|
-
"- 在进行文档补充之前,先确保代码能够正常编译和通过测试;",
|
|
1877
|
-
"- 如果文档补充导致了编译错误或测试失败,必须立即修复这些错误,然后再继续优化。",
|
|
1878
|
-
"",
|
|
1879
|
-
"自检要求:在每次输出补丁后,请使用 execute_script 工具在 crate 根目录执行 `cargo test -q` 进行验证;",
|
|
1880
|
-
"若出现编译错误或测试失败,请优先修复这些问题,然后再继续文档补充;",
|
|
1881
|
-
"若未通过,请继续输出新的补丁进行最小修复并再次自检,直至 `cargo test` 通过为止。"
|
|
1882
|
-
]
|
|
1883
|
-
prompt = "\n".join(prompt_lines)
|
|
1884
|
-
prompt = self._append_additional_notes(prompt)
|
|
1885
|
-
# 切换到 crate 目录,确保 CodeAgent 在正确的上下文中创建和执行
|
|
1886
|
-
prev_cwd = os.getcwd()
|
|
1887
|
-
typer.secho("[c2rust-optimizer][codeagent][doc] 正在调用 CodeAgent 进行文档补充...", fg=typer.colors.CYAN)
|
|
1888
|
-
try:
|
|
1889
|
-
os.chdir(str(crate))
|
|
1890
|
-
# 修复前执行 cargo fmt
|
|
1891
|
-
_run_cargo_fmt(crate)
|
|
1892
|
-
|
|
1893
|
-
# 记录运行前的 commit id
|
|
1894
|
-
commit_before = self._get_crate_commit_hash()
|
|
1895
|
-
|
|
1896
|
-
# CodeAgent 在 crate 目录下创建和执行
|
|
1897
|
-
agent = CodeAgent(name="DocumentationAgent", need_summary=False, non_interactive=self.options.non_interactive, model_group=self.options.llm_group)
|
|
1898
|
-
# 订阅 BEFORE_TOOL_CALL 和 AFTER_TOOL_CALL 事件,用于细粒度检测测试代码删除
|
|
1899
|
-
agent.event_bus.subscribe(BEFORE_TOOL_CALL, self._on_before_tool_call)
|
|
1900
|
-
agent.event_bus.subscribe(AFTER_TOOL_CALL, self._on_after_tool_call)
|
|
1901
|
-
# 记录 Agent 创建时的 commit id(作为初始值)
|
|
1902
|
-
agent_id = id(agent)
|
|
1903
|
-
agent_key = f"agent_{agent_id}"
|
|
1904
|
-
initial_commit = self._get_crate_commit_hash()
|
|
1905
|
-
if initial_commit:
|
|
1906
|
-
self._agent_before_commits[agent_key] = initial_commit
|
|
1907
|
-
agent.run(prompt, prefix="[c2rust-optimizer][codeagent][doc]", suffix="")
|
|
1908
|
-
|
|
1909
|
-
# 检测并处理测试代码删除
|
|
1910
|
-
if self._check_and_handle_test_deletion(commit_before, agent):
|
|
1911
|
-
# 如果回退了,需要重新运行 agent
|
|
1912
|
-
typer.secho("[c2rust-optimizer][codeagent][doc] 检测到测试代码删除问题,已回退,重新运行 agent", fg=typer.colors.YELLOW)
|
|
1913
|
-
commit_before = self._get_crate_commit_hash()
|
|
1914
|
-
agent.run(prompt, prefix="[c2rust-optimizer][codeagent][doc][retry]", suffix="")
|
|
1915
|
-
# 再次检测
|
|
1916
|
-
if self._check_and_handle_test_deletion(commit_before, agent):
|
|
1917
|
-
typer.secho("[c2rust-optimizer][codeagent][doc] 再次检测到测试代码删除问题,已回退", fg=typer.colors.RED)
|
|
1918
|
-
|
|
1919
|
-
# 验证修复是否成功(通过 cargo test)
|
|
1920
|
-
ok, _ = _cargo_check_full(crate, self.stats, self.options.max_checks, timeout=self.options.cargo_test_timeout)
|
|
1921
|
-
if ok:
|
|
1922
|
-
# 修复成功,保存进度和 commit id
|
|
1923
|
-
file_paths = [crate / f for f in file_list if (crate / f).exists()]
|
|
1924
|
-
self._save_fix_progress("doc_opt", "batch", file_paths if file_paths else None)
|
|
1925
|
-
typer.secho("[c2rust-optimizer][codeagent][doc] 文档补充成功,已保存进度", fg=typer.colors.GREEN)
|
|
1926
|
-
else:
|
|
1927
|
-
# 测试失败,回退到运行前的 commit
|
|
1928
|
-
if commit_before:
|
|
1929
|
-
typer.secho(f"[c2rust-optimizer][codeagent][doc] 文档补充后测试失败,回退到运行前的 commit: {commit_before[:8]}", fg=typer.colors.YELLOW)
|
|
1930
|
-
if self._reset_to_commit(commit_before):
|
|
1931
|
-
typer.secho(f"[c2rust-optimizer][codeagent][doc] 已成功回退到 commit: {commit_before[:8]}", fg=typer.colors.CYAN)
|
|
1932
|
-
else:
|
|
1933
|
-
typer.secho("[c2rust-optimizer][codeagent][doc] 回退失败,请手动检查代码状态", fg=typer.colors.RED)
|
|
1934
|
-
else:
|
|
1935
|
-
typer.secho("[c2rust-optimizer][codeagent][doc] 文档补充后测试失败,但无法获取运行前的 commit", fg=typer.colors.YELLOW)
|
|
1936
|
-
finally:
|
|
1937
|
-
os.chdir(prev_cwd)
|
|
1938
|
-
|
|
1939
|
-
def _build_fix_loop(self, scope_files: List[Path]) -> bool:
|
|
1940
|
-
"""
|
|
1941
|
-
循环执行 cargo check 并用 CodeAgent 进行最小修复,直到通过或达到重试上限或检查预算耗尽。
|
|
1942
|
-
仅允许(优先)修改 scope_files(除非确有必要),以支持分批优化。
|
|
1943
|
-
返回 True 表示修复成功构建通过;False 表示未能在限制内修复。
|
|
1944
|
-
|
|
1945
|
-
注意:CodeAgent 必须在 crate 目录下创建和执行,以确保所有文件操作和命令执行都在正确的上下文中进行。
|
|
1946
|
-
"""
|
|
1947
|
-
maxr = int(self.options.build_fix_retries or 0)
|
|
1948
|
-
if maxr <= 0:
|
|
1949
|
-
return False
|
|
1950
|
-
crate = self.crate_dir.resolve()
|
|
1951
|
-
allowed: List[str] = []
|
|
1952
|
-
for p in scope_files:
|
|
1953
|
-
try:
|
|
1954
|
-
rel = p.resolve().relative_to(crate).as_posix()
|
|
1955
|
-
except Exception:
|
|
1956
|
-
rel = p.as_posix()
|
|
1957
|
-
allowed.append(rel)
|
|
1958
|
-
|
|
1959
|
-
attempt = 0
|
|
1960
|
-
while True:
|
|
1961
|
-
# 检查预算
|
|
1962
|
-
if self.options.max_checks and self.stats.cargo_checks >= self.options.max_checks:
|
|
1963
|
-
return False
|
|
1964
|
-
# 执行构建
|
|
1965
|
-
output = ""
|
|
1966
|
-
try:
|
|
1967
|
-
res = subprocess.run(
|
|
1968
|
-
["cargo", "test", "-q"],
|
|
1969
|
-
capture_output=True,
|
|
1970
|
-
text=True,
|
|
1971
|
-
check=False,
|
|
1972
|
-
cwd=str(crate),
|
|
1973
|
-
timeout=self.options.cargo_test_timeout if self.options.cargo_test_timeout > 0 else None,
|
|
1974
|
-
)
|
|
1975
|
-
self.stats.cargo_checks += 1
|
|
1976
|
-
if res.returncode == 0:
|
|
1977
|
-
typer.secho("[c2rust-optimizer][build-fix] 构建修复成功。", fg=typer.colors.GREEN)
|
|
1978
|
-
return True
|
|
1979
|
-
output = ((res.stdout or "") + ("\n" + (res.stderr or ""))).strip()
|
|
1980
|
-
except subprocess.TimeoutExpired as e:
|
|
1981
|
-
self.stats.cargo_checks += 1
|
|
1982
|
-
out_s = e.stdout.decode("utf-8", errors="ignore") if e.stdout else ""
|
|
1983
|
-
err_s = e.stderr.decode("utf-8", errors="ignore") if e.stderr else ""
|
|
1984
|
-
output = f"cargo test timed out after {self.options.cargo_test_timeout} seconds"
|
|
1985
|
-
full_output = (out_s + ("\n" + err_s if err_s else "")).strip()
|
|
1986
|
-
if full_output:
|
|
1987
|
-
output += f"\nOutput:\n{full_output}"
|
|
1988
|
-
except Exception as e:
|
|
1989
|
-
self.stats.cargo_checks += 1
|
|
1990
|
-
output = f"cargo test exception: {e}"
|
|
1991
|
-
|
|
1992
|
-
# 达到重试上限则失败
|
|
1993
|
-
attempt += 1
|
|
1994
|
-
if attempt > maxr:
|
|
1995
|
-
typer.secho("[c2rust-optimizer][build-fix] 构建修复重试次数已用尽。", fg=typer.colors.RED)
|
|
1996
|
-
return False
|
|
302
|
+
PrettyOutput.auto_print(
|
|
303
|
+
f"✅ [c2rust-optimizer] 优化流程结束。报告已生成于: {report_display}",
|
|
304
|
+
color="green",
|
|
305
|
+
)
|
|
306
|
+
write_final_report(report_path, self.stats)
|
|
307
|
+
return self.stats
|
|
1997
308
|
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
prompt_lines = [
|
|
2001
|
-
"请根据以下测试/构建错误对 crate 进行最小必要的修复以通过 `cargo test`:",
|
|
2002
|
-
f"- crate 根目录:{crate}",
|
|
2003
|
-
"",
|
|
2004
|
-
"本次修复优先且仅允许修改以下文件(除非确有必要,否则不要修改范围外文件):",
|
|
2005
|
-
*[f"- {rel}" for rel in allowed],
|
|
2006
|
-
"",
|
|
2007
|
-
"约束与范围:",
|
|
2008
|
-
"- 保持最小改动,不要进行与错误无关的重构或格式化;",
|
|
2009
|
-
"- 仅输出补丁,不要输出解释或多余文本。",
|
|
2010
|
-
"",
|
|
2011
|
-
"优化目标:",
|
|
2012
|
-
"1) 修复构建/测试错误:",
|
|
2013
|
-
" - 必须优先解决所有编译错误和测试失败问题;",
|
|
2014
|
-
" - 修复时应该先解决编译错误,然后再解决测试失败;",
|
|
2015
|
-
" - 如果修复过程中引入了新的错误,必须立即修复这些新错误。",
|
|
2016
|
-
"",
|
|
2017
|
-
"2) 修复已有实现的问题:",
|
|
2018
|
-
" - 如果在修复构建/测试错误的过程中,发现代码已有的实现有问题(如逻辑错误、潜在 bug、性能问题、内存安全问题等),也需要一并修复;",
|
|
2019
|
-
" - 这些问题可能包括但不限于:不正确的算法实现、未检查的边界条件、资源泄漏、竞态条件、数据竞争等;",
|
|
2020
|
-
" - 修复时应该保持最小改动原则,优先修复最严重的问题。",
|
|
2021
|
-
"",
|
|
2022
|
-
"优先级说明:",
|
|
2023
|
-
"- **必须优先解决所有编译错误和测试失败问题**;",
|
|
2024
|
-
"- 修复时应该先解决编译错误,然后再解决测试失败;",
|
|
2025
|
-
"- 如果修复过程中引入了新的错误,必须立即修复这些新错误。",
|
|
2026
|
-
"",
|
|
2027
|
-
"自检要求:在每次输出补丁后,请使用 execute_script 工具在 crate 根目录执行 `cargo test -q` 进行验证;",
|
|
2028
|
-
"若出现编译错误或测试失败,请优先修复这些问题;",
|
|
2029
|
-
"若未通过,请继续输出新的补丁进行最小修复并再次自检,直至 `cargo test` 通过为止。",
|
|
2030
|
-
"",
|
|
2031
|
-
"构建错误如下:",
|
|
2032
|
-
"<BUILD_ERROR>",
|
|
2033
|
-
output,
|
|
2034
|
-
"</BUILD_ERROR>",
|
|
2035
|
-
]
|
|
2036
|
-
prompt = "\n".join(prompt_lines)
|
|
2037
|
-
prompt = self._append_additional_notes(prompt)
|
|
2038
|
-
# 切换到 crate 目录,确保 CodeAgent 在正确的上下文中创建和执行
|
|
2039
|
-
prev_cwd = os.getcwd()
|
|
2040
|
-
try:
|
|
2041
|
-
os.chdir(str(crate))
|
|
2042
|
-
# 修复前执行 cargo fmt
|
|
2043
|
-
_run_cargo_fmt(crate)
|
|
2044
|
-
|
|
2045
|
-
# 记录运行前的 commit id
|
|
2046
|
-
commit_before = self._get_crate_commit_hash()
|
|
2047
|
-
|
|
2048
|
-
# CodeAgent 在 crate 目录下创建和执行
|
|
2049
|
-
agent = CodeAgent(name=f"BuildFixAgent-iter{attempt}", need_summary=False, non_interactive=self.options.non_interactive, model_group=self.options.llm_group)
|
|
2050
|
-
# 订阅 BEFORE_TOOL_CALL 和 AFTER_TOOL_CALL 事件,用于细粒度检测测试代码删除
|
|
2051
|
-
agent.event_bus.subscribe(BEFORE_TOOL_CALL, self._on_before_tool_call)
|
|
2052
|
-
agent.event_bus.subscribe(AFTER_TOOL_CALL, self._on_after_tool_call)
|
|
2053
|
-
# 记录 Agent 创建时的 commit id(作为初始值)
|
|
2054
|
-
agent_id = id(agent)
|
|
2055
|
-
agent_key = f"agent_{agent_id}"
|
|
2056
|
-
initial_commit = self._get_crate_commit_hash()
|
|
2057
|
-
if initial_commit:
|
|
2058
|
-
self._agent_before_commits[agent_key] = initial_commit
|
|
2059
|
-
agent.run(prompt, prefix=f"[c2rust-optimizer][build-fix iter={attempt}]", suffix="")
|
|
2060
|
-
|
|
2061
|
-
# 检测并处理测试代码删除
|
|
2062
|
-
if self._check_and_handle_test_deletion(commit_before, agent):
|
|
2063
|
-
# 如果回退了,需要重新运行 agent
|
|
2064
|
-
typer.secho(f"[c2rust-optimizer][build-fix] 检测到测试代码删除问题,已回退,重新运行 agent (iter={attempt})", fg=typer.colors.YELLOW)
|
|
2065
|
-
commit_before = self._get_crate_commit_hash()
|
|
2066
|
-
agent.run(prompt, prefix=f"[c2rust-optimizer][build-fix iter={attempt}][retry]", suffix="")
|
|
2067
|
-
# 再次检测
|
|
2068
|
-
if self._check_and_handle_test_deletion(commit_before, agent):
|
|
2069
|
-
typer.secho(f"[c2rust-optimizer][build-fix] 再次检测到测试代码删除问题,已回退 (iter={attempt})", fg=typer.colors.RED)
|
|
2070
|
-
|
|
2071
|
-
# 验证修复是否成功(通过 cargo test)
|
|
2072
|
-
ok, _ = _cargo_check_full(crate, self.stats, self.options.max_checks, timeout=self.options.cargo_test_timeout)
|
|
2073
|
-
if ok:
|
|
2074
|
-
# 修复成功,保存进度和 commit id
|
|
2075
|
-
file_paths = [crate / f for f in allowed if (crate / f).exists()]
|
|
2076
|
-
self._save_fix_progress("build_fix", f"iter{attempt}", file_paths if file_paths else None)
|
|
2077
|
-
typer.secho(f"[c2rust-optimizer][build-fix] 第 {attempt} 次修复成功,已保存进度", fg=typer.colors.GREEN)
|
|
2078
|
-
# 返回 True 表示修复成功
|
|
2079
|
-
return True
|
|
2080
|
-
else:
|
|
2081
|
-
# 测试失败,回退到运行前的 commit
|
|
2082
|
-
if commit_before:
|
|
2083
|
-
typer.secho(f"[c2rust-optimizer][build-fix] 第 {attempt} 次修复后测试失败,回退到运行前的 commit: {commit_before[:8]}", fg=typer.colors.YELLOW)
|
|
2084
|
-
if self._reset_to_commit(commit_before):
|
|
2085
|
-
typer.secho(f"[c2rust-optimizer][build-fix] 已成功回退到 commit: {commit_before[:8]}", fg=typer.colors.CYAN)
|
|
2086
|
-
else:
|
|
2087
|
-
typer.secho("[c2rust-optimizer][build-fix] 回退失败,请手动检查代码状态", fg=typer.colors.RED)
|
|
2088
|
-
else:
|
|
2089
|
-
typer.secho(f"[c2rust-optimizer][build-fix] 第 {attempt} 次修复后测试失败,但无法获取运行前的 commit,继续尝试", fg=typer.colors.YELLOW)
|
|
2090
|
-
finally:
|
|
2091
|
-
os.chdir(prev_cwd)
|
|
309
|
+
# Clippy、Unsafe、Visibility、Docs 和 BuildFix 相关方法已迁移到各自的模块
|
|
310
|
+
# 向后兼容方法已删除,请直接使用各模块中的对应方法
|
|
2092
311
|
|
|
2093
|
-
return False
|
|
2094
312
|
|
|
2095
313
|
def optimize_project(
|
|
2096
314
|
project_root: Optional[Path] = None,
|
|
@@ -2125,13 +343,13 @@ def optimize_project(
|
|
|
2125
343
|
"""
|
|
2126
344
|
# 如果 project_root 为 None,尝试从当前目录查找
|
|
2127
345
|
if project_root is None:
|
|
2128
|
-
project_root =
|
|
346
|
+
project_root = find_project_root()
|
|
2129
347
|
if project_root is None:
|
|
2130
348
|
# 如果找不到项目根目录,使用当前目录
|
|
2131
349
|
project_root = Path(".").resolve()
|
|
2132
350
|
else:
|
|
2133
351
|
project_root = Path(project_root).resolve()
|
|
2134
|
-
|
|
352
|
+
|
|
2135
353
|
# 如果 crate_dir 为 None,使用 detect_crate_dir 自动检测
|
|
2136
354
|
# detect_crate_dir 内部已经包含了从项目根目录推断的逻辑
|
|
2137
355
|
crate = detect_crate_dir(crate_dir)
|
|
@@ -2154,4 +372,4 @@ def optimize_project(
|
|
|
2154
372
|
)
|
|
2155
373
|
optimizer = Optimizer(crate, opts, project_root=project_root)
|
|
2156
374
|
stats = optimizer.run()
|
|
2157
|
-
return asdict(stats)
|
|
375
|
+
return asdict(stats)
|