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.
Files changed (279) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +567 -222
  3. jarvis/jarvis_agent/agent_manager.py +19 -12
  4. jarvis/jarvis_agent/builtin_input_handler.py +79 -11
  5. jarvis/jarvis_agent/config_editor.py +7 -2
  6. jarvis/jarvis_agent/event_bus.py +24 -13
  7. jarvis/jarvis_agent/events.py +19 -1
  8. jarvis/jarvis_agent/file_context_handler.py +67 -64
  9. jarvis/jarvis_agent/file_methodology_manager.py +38 -24
  10. jarvis/jarvis_agent/jarvis.py +186 -114
  11. jarvis/jarvis_agent/language_extractors/__init__.py +8 -1
  12. jarvis/jarvis_agent/language_extractors/c_extractor.py +7 -4
  13. jarvis/jarvis_agent/language_extractors/cpp_extractor.py +9 -4
  14. jarvis/jarvis_agent/language_extractors/go_extractor.py +7 -4
  15. jarvis/jarvis_agent/language_extractors/java_extractor.py +27 -20
  16. jarvis/jarvis_agent/language_extractors/javascript_extractor.py +22 -17
  17. jarvis/jarvis_agent/language_extractors/python_extractor.py +7 -4
  18. jarvis/jarvis_agent/language_extractors/rust_extractor.py +7 -4
  19. jarvis/jarvis_agent/language_extractors/typescript_extractor.py +22 -17
  20. jarvis/jarvis_agent/language_support_info.py +250 -219
  21. jarvis/jarvis_agent/main.py +19 -23
  22. jarvis/jarvis_agent/memory_manager.py +9 -6
  23. jarvis/jarvis_agent/methodology_share_manager.py +21 -15
  24. jarvis/jarvis_agent/output_handler.py +4 -2
  25. jarvis/jarvis_agent/prompt_builder.py +7 -6
  26. jarvis/jarvis_agent/prompt_manager.py +113 -8
  27. jarvis/jarvis_agent/prompts.py +317 -85
  28. jarvis/jarvis_agent/protocols.py +5 -2
  29. jarvis/jarvis_agent/run_loop.py +192 -32
  30. jarvis/jarvis_agent/session_manager.py +7 -3
  31. jarvis/jarvis_agent/share_manager.py +23 -13
  32. jarvis/jarvis_agent/shell_input_handler.py +12 -8
  33. jarvis/jarvis_agent/stdio_redirect.py +25 -26
  34. jarvis/jarvis_agent/task_analyzer.py +29 -23
  35. jarvis/jarvis_agent/task_list.py +869 -0
  36. jarvis/jarvis_agent/task_manager.py +26 -23
  37. jarvis/jarvis_agent/tool_executor.py +6 -5
  38. jarvis/jarvis_agent/tool_share_manager.py +24 -14
  39. jarvis/jarvis_agent/user_interaction.py +3 -3
  40. jarvis/jarvis_agent/utils.py +9 -1
  41. jarvis/jarvis_agent/web_bridge.py +37 -17
  42. jarvis/jarvis_agent/web_output_sink.py +5 -2
  43. jarvis/jarvis_agent/web_server.py +165 -36
  44. jarvis/jarvis_c2rust/__init__.py +1 -1
  45. jarvis/jarvis_c2rust/cli.py +260 -141
  46. jarvis/jarvis_c2rust/collector.py +37 -18
  47. jarvis/jarvis_c2rust/constants.py +60 -0
  48. jarvis/jarvis_c2rust/library_replacer.py +242 -1010
  49. jarvis/jarvis_c2rust/library_replacer_checkpoint.py +133 -0
  50. jarvis/jarvis_c2rust/library_replacer_llm.py +287 -0
  51. jarvis/jarvis_c2rust/library_replacer_loader.py +191 -0
  52. jarvis/jarvis_c2rust/library_replacer_output.py +134 -0
  53. jarvis/jarvis_c2rust/library_replacer_prompts.py +124 -0
  54. jarvis/jarvis_c2rust/library_replacer_utils.py +188 -0
  55. jarvis/jarvis_c2rust/llm_module_agent.py +98 -1044
  56. jarvis/jarvis_c2rust/llm_module_agent_apply.py +170 -0
  57. jarvis/jarvis_c2rust/llm_module_agent_executor.py +288 -0
  58. jarvis/jarvis_c2rust/llm_module_agent_loader.py +170 -0
  59. jarvis/jarvis_c2rust/llm_module_agent_prompts.py +268 -0
  60. jarvis/jarvis_c2rust/llm_module_agent_types.py +57 -0
  61. jarvis/jarvis_c2rust/llm_module_agent_utils.py +150 -0
  62. jarvis/jarvis_c2rust/llm_module_agent_validator.py +119 -0
  63. jarvis/jarvis_c2rust/loaders.py +28 -10
  64. jarvis/jarvis_c2rust/models.py +5 -2
  65. jarvis/jarvis_c2rust/optimizer.py +192 -1974
  66. jarvis/jarvis_c2rust/optimizer_build_fix.py +286 -0
  67. jarvis/jarvis_c2rust/optimizer_clippy.py +766 -0
  68. jarvis/jarvis_c2rust/optimizer_config.py +49 -0
  69. jarvis/jarvis_c2rust/optimizer_docs.py +183 -0
  70. jarvis/jarvis_c2rust/optimizer_options.py +48 -0
  71. jarvis/jarvis_c2rust/optimizer_progress.py +469 -0
  72. jarvis/jarvis_c2rust/optimizer_report.py +52 -0
  73. jarvis/jarvis_c2rust/optimizer_unsafe.py +309 -0
  74. jarvis/jarvis_c2rust/optimizer_utils.py +469 -0
  75. jarvis/jarvis_c2rust/optimizer_visibility.py +185 -0
  76. jarvis/jarvis_c2rust/scanner.py +229 -166
  77. jarvis/jarvis_c2rust/transpiler.py +531 -2732
  78. jarvis/jarvis_c2rust/transpiler_agents.py +503 -0
  79. jarvis/jarvis_c2rust/transpiler_build.py +1294 -0
  80. jarvis/jarvis_c2rust/transpiler_codegen.py +204 -0
  81. jarvis/jarvis_c2rust/transpiler_compile.py +146 -0
  82. jarvis/jarvis_c2rust/transpiler_config.py +178 -0
  83. jarvis/jarvis_c2rust/transpiler_context.py +122 -0
  84. jarvis/jarvis_c2rust/transpiler_executor.py +516 -0
  85. jarvis/jarvis_c2rust/transpiler_generation.py +278 -0
  86. jarvis/jarvis_c2rust/transpiler_git.py +163 -0
  87. jarvis/jarvis_c2rust/transpiler_mod_utils.py +225 -0
  88. jarvis/jarvis_c2rust/transpiler_modules.py +336 -0
  89. jarvis/jarvis_c2rust/transpiler_planning.py +394 -0
  90. jarvis/jarvis_c2rust/transpiler_review.py +1196 -0
  91. jarvis/jarvis_c2rust/transpiler_symbols.py +176 -0
  92. jarvis/jarvis_c2rust/utils.py +269 -79
  93. jarvis/jarvis_code_agent/after_change.py +233 -0
  94. jarvis/jarvis_code_agent/build_validation_config.py +37 -30
  95. jarvis/jarvis_code_agent/builtin_rules.py +68 -0
  96. jarvis/jarvis_code_agent/code_agent.py +976 -1517
  97. jarvis/jarvis_code_agent/code_agent_build.py +227 -0
  98. jarvis/jarvis_code_agent/code_agent_diff.py +246 -0
  99. jarvis/jarvis_code_agent/code_agent_git.py +525 -0
  100. jarvis/jarvis_code_agent/code_agent_impact.py +177 -0
  101. jarvis/jarvis_code_agent/code_agent_lint.py +283 -0
  102. jarvis/jarvis_code_agent/code_agent_llm.py +159 -0
  103. jarvis/jarvis_code_agent/code_agent_postprocess.py +105 -0
  104. jarvis/jarvis_code_agent/code_agent_prompts.py +46 -0
  105. jarvis/jarvis_code_agent/code_agent_rules.py +305 -0
  106. jarvis/jarvis_code_agent/code_analyzer/__init__.py +52 -48
  107. jarvis/jarvis_code_agent/code_analyzer/base_language.py +12 -10
  108. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +12 -11
  109. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +16 -12
  110. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +26 -17
  111. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +558 -104
  112. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +27 -16
  113. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +22 -18
  114. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +21 -16
  115. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +20 -16
  116. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +27 -16
  117. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +47 -23
  118. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +71 -37
  119. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +162 -35
  120. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +111 -57
  121. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +18 -12
  122. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +185 -183
  123. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +2 -1
  124. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +24 -15
  125. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +227 -141
  126. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +321 -247
  127. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +37 -29
  128. jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -13
  129. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +15 -9
  130. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +75 -45
  131. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +87 -52
  132. jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +84 -51
  133. jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +94 -64
  134. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +109 -71
  135. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +97 -63
  136. jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +103 -69
  137. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +271 -268
  138. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +76 -64
  139. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +92 -19
  140. jarvis/jarvis_code_agent/diff_visualizer.py +998 -0
  141. jarvis/jarvis_code_agent/lint.py +223 -524
  142. jarvis/jarvis_code_agent/rule_share_manager.py +158 -0
  143. jarvis/jarvis_code_agent/rules/clean_code.md +144 -0
  144. jarvis/jarvis_code_agent/rules/code_review.md +115 -0
  145. jarvis/jarvis_code_agent/rules/documentation.md +165 -0
  146. jarvis/jarvis_code_agent/rules/generate_rules.md +52 -0
  147. jarvis/jarvis_code_agent/rules/performance.md +158 -0
  148. jarvis/jarvis_code_agent/rules/refactoring.md +139 -0
  149. jarvis/jarvis_code_agent/rules/security.md +160 -0
  150. jarvis/jarvis_code_agent/rules/tdd.md +78 -0
  151. jarvis/jarvis_code_agent/test_rules/cpp_test.md +118 -0
  152. jarvis/jarvis_code_agent/test_rules/go_test.md +98 -0
  153. jarvis/jarvis_code_agent/test_rules/java_test.md +99 -0
  154. jarvis/jarvis_code_agent/test_rules/javascript_test.md +113 -0
  155. jarvis/jarvis_code_agent/test_rules/php_test.md +117 -0
  156. jarvis/jarvis_code_agent/test_rules/python_test.md +91 -0
  157. jarvis/jarvis_code_agent/test_rules/ruby_test.md +102 -0
  158. jarvis/jarvis_code_agent/test_rules/rust_test.md +86 -0
  159. jarvis/jarvis_code_agent/utils.py +36 -26
  160. jarvis/jarvis_code_analysis/checklists/loader.py +21 -21
  161. jarvis/jarvis_code_analysis/code_review.py +64 -33
  162. jarvis/jarvis_data/config_schema.json +285 -192
  163. jarvis/jarvis_git_squash/main.py +8 -6
  164. jarvis/jarvis_git_utils/git_commiter.py +53 -76
  165. jarvis/jarvis_mcp/__init__.py +5 -2
  166. jarvis/jarvis_mcp/sse_mcp_client.py +40 -30
  167. jarvis/jarvis_mcp/stdio_mcp_client.py +27 -19
  168. jarvis/jarvis_mcp/streamable_mcp_client.py +35 -26
  169. jarvis/jarvis_memory_organizer/memory_organizer.py +78 -55
  170. jarvis/jarvis_methodology/main.py +48 -39
  171. jarvis/jarvis_multi_agent/__init__.py +56 -23
  172. jarvis/jarvis_multi_agent/main.py +15 -18
  173. jarvis/jarvis_platform/base.py +179 -111
  174. jarvis/jarvis_platform/human.py +27 -16
  175. jarvis/jarvis_platform/kimi.py +52 -45
  176. jarvis/jarvis_platform/openai.py +101 -40
  177. jarvis/jarvis_platform/registry.py +51 -33
  178. jarvis/jarvis_platform/tongyi.py +68 -38
  179. jarvis/jarvis_platform/yuanbao.py +59 -43
  180. jarvis/jarvis_platform_manager/main.py +68 -76
  181. jarvis/jarvis_platform_manager/service.py +24 -14
  182. jarvis/jarvis_rag/README_CONFIG.md +314 -0
  183. jarvis/jarvis_rag/README_DYNAMIC_LOADING.md +311 -0
  184. jarvis/jarvis_rag/README_ONLINE_MODELS.md +230 -0
  185. jarvis/jarvis_rag/__init__.py +57 -4
  186. jarvis/jarvis_rag/cache.py +3 -1
  187. jarvis/jarvis_rag/cli.py +48 -68
  188. jarvis/jarvis_rag/embedding_interface.py +39 -0
  189. jarvis/jarvis_rag/embedding_manager.py +7 -230
  190. jarvis/jarvis_rag/embeddings/__init__.py +41 -0
  191. jarvis/jarvis_rag/embeddings/base.py +114 -0
  192. jarvis/jarvis_rag/embeddings/cohere.py +66 -0
  193. jarvis/jarvis_rag/embeddings/edgefn.py +117 -0
  194. jarvis/jarvis_rag/embeddings/local.py +260 -0
  195. jarvis/jarvis_rag/embeddings/openai.py +62 -0
  196. jarvis/jarvis_rag/embeddings/registry.py +293 -0
  197. jarvis/jarvis_rag/llm_interface.py +8 -6
  198. jarvis/jarvis_rag/query_rewriter.py +8 -9
  199. jarvis/jarvis_rag/rag_pipeline.py +61 -52
  200. jarvis/jarvis_rag/reranker.py +7 -75
  201. jarvis/jarvis_rag/reranker_interface.py +32 -0
  202. jarvis/jarvis_rag/rerankers/__init__.py +41 -0
  203. jarvis/jarvis_rag/rerankers/base.py +109 -0
  204. jarvis/jarvis_rag/rerankers/cohere.py +67 -0
  205. jarvis/jarvis_rag/rerankers/edgefn.py +140 -0
  206. jarvis/jarvis_rag/rerankers/jina.py +79 -0
  207. jarvis/jarvis_rag/rerankers/local.py +89 -0
  208. jarvis/jarvis_rag/rerankers/registry.py +293 -0
  209. jarvis/jarvis_rag/retriever.py +58 -43
  210. jarvis/jarvis_sec/__init__.py +66 -141
  211. jarvis/jarvis_sec/agents.py +21 -17
  212. jarvis/jarvis_sec/analysis.py +80 -33
  213. jarvis/jarvis_sec/checkers/__init__.py +7 -13
  214. jarvis/jarvis_sec/checkers/c_checker.py +356 -164
  215. jarvis/jarvis_sec/checkers/rust_checker.py +47 -29
  216. jarvis/jarvis_sec/cli.py +43 -21
  217. jarvis/jarvis_sec/clustering.py +430 -272
  218. jarvis/jarvis_sec/file_manager.py +99 -55
  219. jarvis/jarvis_sec/parsers.py +9 -6
  220. jarvis/jarvis_sec/prompts.py +4 -3
  221. jarvis/jarvis_sec/report.py +44 -22
  222. jarvis/jarvis_sec/review.py +180 -107
  223. jarvis/jarvis_sec/status.py +50 -41
  224. jarvis/jarvis_sec/types.py +3 -0
  225. jarvis/jarvis_sec/utils.py +160 -83
  226. jarvis/jarvis_sec/verification.py +411 -181
  227. jarvis/jarvis_sec/workflow.py +132 -21
  228. jarvis/jarvis_smart_shell/main.py +28 -41
  229. jarvis/jarvis_stats/cli.py +14 -12
  230. jarvis/jarvis_stats/stats.py +28 -19
  231. jarvis/jarvis_stats/storage.py +14 -8
  232. jarvis/jarvis_stats/visualizer.py +12 -7
  233. jarvis/jarvis_tools/base.py +5 -2
  234. jarvis/jarvis_tools/clear_memory.py +13 -9
  235. jarvis/jarvis_tools/cli/main.py +23 -18
  236. jarvis/jarvis_tools/edit_file.py +572 -873
  237. jarvis/jarvis_tools/execute_script.py +10 -7
  238. jarvis/jarvis_tools/file_analyzer.py +7 -8
  239. jarvis/jarvis_tools/meta_agent.py +287 -0
  240. jarvis/jarvis_tools/methodology.py +5 -3
  241. jarvis/jarvis_tools/read_code.py +305 -1438
  242. jarvis/jarvis_tools/read_symbols.py +50 -17
  243. jarvis/jarvis_tools/read_webpage.py +19 -18
  244. jarvis/jarvis_tools/registry.py +435 -156
  245. jarvis/jarvis_tools/retrieve_memory.py +16 -11
  246. jarvis/jarvis_tools/save_memory.py +8 -6
  247. jarvis/jarvis_tools/search_web.py +31 -31
  248. jarvis/jarvis_tools/sub_agent.py +32 -28
  249. jarvis/jarvis_tools/sub_code_agent.py +44 -60
  250. jarvis/jarvis_tools/task_list_manager.py +1811 -0
  251. jarvis/jarvis_tools/virtual_tty.py +29 -19
  252. jarvis/jarvis_utils/__init__.py +4 -0
  253. jarvis/jarvis_utils/builtin_replace_map.py +2 -1
  254. jarvis/jarvis_utils/clipboard.py +9 -8
  255. jarvis/jarvis_utils/collections.py +331 -0
  256. jarvis/jarvis_utils/config.py +699 -194
  257. jarvis/jarvis_utils/dialogue_recorder.py +294 -0
  258. jarvis/jarvis_utils/embedding.py +6 -3
  259. jarvis/jarvis_utils/file_processors.py +7 -1
  260. jarvis/jarvis_utils/fzf.py +9 -3
  261. jarvis/jarvis_utils/git_utils.py +71 -42
  262. jarvis/jarvis_utils/globals.py +116 -32
  263. jarvis/jarvis_utils/http.py +6 -2
  264. jarvis/jarvis_utils/input.py +318 -83
  265. jarvis/jarvis_utils/jsonnet_compat.py +119 -104
  266. jarvis/jarvis_utils/methodology.py +37 -28
  267. jarvis/jarvis_utils/output.py +201 -44
  268. jarvis/jarvis_utils/utils.py +986 -628
  269. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/METADATA +49 -33
  270. jarvis_ai_assistant-1.0.2.dist-info/RECORD +304 -0
  271. jarvis/jarvis_code_agent/code_analyzer/structured_code.py +0 -556
  272. jarvis/jarvis_tools/generate_new_tool.py +0 -205
  273. jarvis/jarvis_tools/lsp_client.py +0 -1552
  274. jarvis/jarvis_tools/rewrite_file.py +0 -105
  275. jarvis_ai_assistant-0.7.8.dist-info/RECORD +0 -218
  276. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/WHEEL +0 -0
  277. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/entry_points.txt +0 -0
  278. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/licenses/LICENSE +0 -0
  279. {jarvis_ai_assistant-0.7.8.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/top_level.txt +0 -0
@@ -3,22 +3,26 @@
3
3
  提供代码编辑影响范围分析功能,识别可能受影响的文件、函数、测试等。
4
4
  """
5
5
 
6
+ import ast
6
7
  import os
7
8
  import re
8
- import ast
9
9
  import subprocess
10
- from dataclasses import dataclass, field
11
- from typing import List, Optional, Set, Dict
10
+ from dataclasses import dataclass
11
+ from dataclasses import field
12
12
  from enum import Enum
13
+ from typing import Dict
14
+ from typing import List
15
+ from typing import Optional
16
+ from typing import Set
13
17
 
14
18
  from .context_manager import ContextManager
15
19
  from .file_ignore import filter_walk_dirs
16
20
  from .symbol_extractor import Symbol
17
21
 
18
22
 
19
-
20
23
  class ImpactType(Enum):
21
24
  """影响类型枚举"""
25
+
22
26
  REFERENCE = "reference" # 符号引用
23
27
  DEPENDENT = "dependent" # 依赖的符号
24
28
  TEST = "test" # 测试文件
@@ -28,6 +32,7 @@ class ImpactType(Enum):
28
32
 
29
33
  class RiskLevel(Enum):
30
34
  """风险等级枚举"""
35
+
31
36
  LOW = "low"
32
37
  MEDIUM = "medium"
33
38
  HIGH = "high"
@@ -36,6 +41,7 @@ class RiskLevel(Enum):
36
41
  @dataclass
37
42
  class Impact:
38
43
  """表示一个影响项"""
44
+
39
45
  impact_type: ImpactType
40
46
  target: str # 受影响的目标(文件路径、符号名等)
41
47
  description: str = ""
@@ -46,6 +52,7 @@ class Impact:
46
52
  @dataclass
47
53
  class InterfaceChange:
48
54
  """表示接口变更"""
55
+
49
56
  symbol_name: str
50
57
  change_type: str # 'signature', 'return_type', 'parameter', 'removed', 'added'
51
58
  file_path: str
@@ -58,6 +65,7 @@ class InterfaceChange:
58
65
  @dataclass
59
66
  class ImpactReport:
60
67
  """影响分析报告"""
68
+
61
69
  affected_files: List[str] = field(default_factory=list)
62
70
  affected_symbols: List[Symbol] = field(default_factory=list)
63
71
  affected_tests: List[str] = field(default_factory=list)
@@ -65,48 +73,56 @@ class ImpactReport:
65
73
  impacts: List[Impact] = field(default_factory=list)
66
74
  risk_level: RiskLevel = RiskLevel.LOW
67
75
  recommendations: List[str] = field(default_factory=list)
68
-
76
+
69
77
  def to_string(self, project_root: str = "") -> str:
70
78
  """生成可读的影响报告字符串"""
71
79
  lines = []
72
80
  lines.append("=" * 60)
73
81
  lines.append("编辑影响范围分析报告")
74
82
  lines.append("=" * 60)
75
-
83
+
76
84
  # 风险等级
77
- risk_emoji = {
78
- RiskLevel.LOW: "🟢",
79
- RiskLevel.MEDIUM: "🟡",
80
- RiskLevel.HIGH: "🔴"
81
- }
82
- lines.append(f"\n风险等级: {risk_emoji.get(self.risk_level, '⚪')} {self.risk_level.value.upper()}")
83
-
85
+ risk_emoji = {RiskLevel.LOW: "🟢", RiskLevel.MEDIUM: "🟡", RiskLevel.HIGH: "🔴"}
86
+ lines.append(
87
+ f"\n风险等级: {risk_emoji.get(self.risk_level, '⚪')} {self.risk_level.value.upper()}"
88
+ )
89
+
84
90
  # 受影响文件
85
91
  if self.affected_files:
86
92
  lines.append(f"\n受影响文件 ({len(self.affected_files)}):")
87
93
  for file_path in self.affected_files[:10]:
88
- rel_path = os.path.relpath(file_path, project_root) if project_root else file_path
94
+ rel_path = (
95
+ os.path.relpath(file_path, project_root)
96
+ if project_root
97
+ else file_path
98
+ )
89
99
  lines.append(f" - {rel_path}")
90
100
  if len(self.affected_files) > 10:
91
101
  lines.append(f" ... 还有 {len(self.affected_files) - 10} 个文件")
92
-
102
+
93
103
  # 受影响符号
94
104
  if self.affected_symbols:
95
105
  lines.append(f"\n受影响符号 ({len(self.affected_symbols)}):")
96
106
  for symbol in self.affected_symbols[:10]:
97
- lines.append(f" - {symbol.kind} {symbol.name} ({os.path.basename(symbol.file_path)}:{symbol.line_start})")
107
+ lines.append(
108
+ f" - {symbol.kind} {symbol.name} ({os.path.basename(symbol.file_path)}:{symbol.line_start})"
109
+ )
98
110
  if len(self.affected_symbols) > 10:
99
111
  lines.append(f" ... 还有 {len(self.affected_symbols) - 10} 个符号")
100
-
112
+
101
113
  # 受影响测试
102
114
  if self.affected_tests:
103
115
  lines.append(f"\n受影响测试 ({len(self.affected_tests)}):")
104
116
  for test_file in self.affected_tests[:10]:
105
- rel_path = os.path.relpath(test_file, project_root) if project_root else test_file
117
+ rel_path = (
118
+ os.path.relpath(test_file, project_root)
119
+ if project_root
120
+ else test_file
121
+ )
106
122
  lines.append(f" - {rel_path}")
107
123
  if len(self.affected_tests) > 10:
108
124
  lines.append(f" ... 还有 {len(self.affected_tests) - 10} 个测试文件")
109
-
125
+
110
126
  # 接口变更
111
127
  if self.interface_changes:
112
128
  lines.append(f"\n接口变更 ({len(self.interface_changes)}):")
@@ -115,14 +131,16 @@ class ImpactReport:
115
131
  if change.description:
116
132
  lines.append(f" {change.description}")
117
133
  if len(self.interface_changes) > 10:
118
- lines.append(f" ... 还有 {len(self.interface_changes) - 10} 个接口变更")
119
-
134
+ lines.append(
135
+ f" ... 还有 {len(self.interface_changes) - 10} 个接口变更"
136
+ )
137
+
120
138
  # 建议
121
139
  if self.recommendations:
122
140
  lines.append("\n建议:")
123
141
  for i, rec in enumerate(self.recommendations, 1):
124
142
  lines.append(f" {i}. {rec}")
125
-
143
+
126
144
  lines.append("\n" + "=" * 60)
127
145
  return "\n".join(lines)
128
146
 
@@ -130,6 +148,7 @@ class ImpactReport:
130
148
  @dataclass
131
149
  class Edit:
132
150
  """表示一个编辑操作"""
151
+
133
152
  file_path: str
134
153
  line_start: int
135
154
  line_end: int
@@ -140,57 +159,57 @@ class Edit:
140
159
 
141
160
  class TestDiscoverer:
142
161
  """测试文件发现器"""
143
-
162
+
144
163
  # 测试文件命名模式
145
164
  TEST_PATTERNS = {
146
- 'python': [
147
- r'test_.*\.py$',
148
- r'.*_test\.py$',
165
+ "python": [
166
+ r"test_.*\.py$",
167
+ r".*_test\.py$",
149
168
  ],
150
- 'javascript': [
151
- r'.*\.test\.(js|ts|jsx|tsx)$',
152
- r'.*\.spec\.(js|ts|jsx|tsx)$',
169
+ "javascript": [
170
+ r".*\.test\.(js|ts|jsx|tsx)$",
171
+ r".*\.spec\.(js|ts|jsx|tsx)$",
153
172
  ],
154
- 'rust': [
155
- r'.*_test\.rs$',
173
+ "rust": [
174
+ r".*_test\.rs$",
156
175
  ],
157
- 'java': [
158
- r'.*Test\.java$',
159
- r'.*Tests\.java$',
176
+ "java": [
177
+ r".*Test\.java$",
178
+ r".*Tests\.java$",
160
179
  ],
161
- 'go': [
162
- r'.*_test\.go$',
180
+ "go": [
181
+ r".*_test\.go$",
163
182
  ],
164
183
  }
165
-
184
+
166
185
  def __init__(self, project_root: str):
167
186
  self.project_root = project_root
168
-
187
+
169
188
  def find_test_files(self, file_path: str) -> List[str]:
170
189
  """查找与文件相关的测试文件"""
171
190
  test_files: List[str] = []
172
-
191
+
173
192
  # 检测语言
174
193
  language = self._detect_language(file_path)
175
194
  if not language:
176
195
  return test_files
177
-
196
+
178
197
  # 获取测试文件模式
179
198
  patterns = self.TEST_PATTERNS.get(language, [])
180
199
  if not patterns:
181
200
  return test_files
182
-
201
+
183
202
  # 获取文件的基础名称(不含扩展名)
184
203
  base_name = os.path.splitext(os.path.basename(file_path))[0]
185
-
204
+
186
205
  # 在项目根目录搜索测试文件
187
206
  for root, dirs, files in os.walk(self.project_root):
188
207
  # 跳过隐藏目录和常见忽略目录
189
208
  dirs[:] = filter_walk_dirs(dirs)
190
-
209
+
191
210
  for file in files:
192
211
  file_path_full = os.path.join(root, file)
193
-
212
+
194
213
  # 检查是否匹配测试文件模式
195
214
  for pattern in patterns:
196
215
  if re.match(pattern, file, re.IGNORECASE):
@@ -198,71 +217,73 @@ class TestDiscoverer:
198
217
  if self._might_test_file(file_path_full, file_path, base_name):
199
218
  test_files.append(file_path_full)
200
219
  break
201
-
220
+
202
221
  return list(set(test_files))
203
-
204
- def _might_test_file(self, test_file: str, target_file: str, base_name: str) -> bool:
222
+
223
+ def _might_test_file(
224
+ self, test_file: str, target_file: str, base_name: str
225
+ ) -> bool:
205
226
  """判断测试文件是否可能测试目标文件"""
206
227
  # 读取测试文件内容,查找目标文件的引用
207
228
  try:
208
- with open(test_file, 'r', encoding='utf-8', errors='replace') as f:
229
+ with open(test_file, "r", encoding="utf-8", errors="replace") as f:
209
230
  content = f.read()
210
-
231
+
211
232
  # 检查是否导入或引用了目标文件
212
233
  # 简单的启发式方法:检查文件名、模块名等
213
234
  target_base = os.path.splitext(os.path.basename(target_file))[0]
214
-
235
+
215
236
  # 检查导入语句
216
237
  import_patterns = [
217
- rf'import\s+.*{re.escape(target_base)}',
218
- rf'from\s+.*{re.escape(target_base)}',
219
- rf'use\s+.*{re.escape(target_base)}', # Rust
238
+ rf"import\s+.*{re.escape(target_base)}",
239
+ rf"from\s+.*{re.escape(target_base)}",
240
+ rf"use\s+.*{re.escape(target_base)}", # Rust
220
241
  ]
221
-
242
+
222
243
  for pattern in import_patterns:
223
244
  if re.search(pattern, content, re.IGNORECASE):
224
245
  return True
225
-
246
+
226
247
  # 检查文件名是否出现在测试文件中
227
248
  if target_base.lower() in content.lower():
228
249
  return True
229
-
250
+
230
251
  except Exception:
231
252
  pass
232
-
253
+
233
254
  return False
234
-
255
+
235
256
  def _detect_language(self, file_path: str) -> Optional[str]:
236
257
  """检测文件语言"""
237
258
  ext = os.path.splitext(file_path)[1].lower()
238
259
  ext_map = {
239
- '.py': 'python',
240
- '.js': 'javascript',
241
- '.ts': 'javascript',
242
- '.jsx': 'javascript',
243
- '.tsx': 'javascript',
244
- '.rs': 'rust',
245
- '.java': 'java',
246
- '.go': 'go',
260
+ ".py": "python",
261
+ ".js": "javascript",
262
+ ".ts": "javascript",
263
+ ".jsx": "javascript",
264
+ ".tsx": "javascript",
265
+ ".rs": "rust",
266
+ ".java": "java",
267
+ ".go": "go",
247
268
  }
248
269
  return ext_map.get(ext)
249
270
 
250
271
 
251
272
  class ImpactAnalyzer:
252
273
  """编辑影响范围分析器"""
253
-
274
+
254
275
  def __init__(self, context_manager: ContextManager):
255
276
  self.context_manager = context_manager
256
277
  self.project_root = context_manager.project_root
257
278
  self.test_discoverer = TestDiscoverer(self.project_root)
258
-
279
+
259
280
  def analyze_edit_impact(self, file_path: str, edits: List[Edit]) -> ImpactReport:
260
281
  """分析编辑的影响范围
261
-
282
+
262
283
  Args:
263
284
  file_path: 被编辑的文件路径
264
285
  edits: 编辑操作列表
265
-
286
+
266
287
  Returns:
267
288
  ImpactReport: 影响分析报告
268
289
  """
@@ -270,7 +291,7 @@ class ImpactAnalyzer:
270
291
  affected_symbols: Set[Symbol] = set()
271
292
  affected_files: Set[str] = {file_path}
272
293
  interface_changes: List[InterfaceChange] = []
273
-
294
+
274
295
  # 1. 分析每个编辑的影响
275
296
  for edit in edits:
276
297
  # 分析符号影响
@@ -279,45 +300,47 @@ class ImpactAnalyzer:
279
300
  affected_symbols.add(symbol)
280
301
  symbol_impacts = self._analyze_symbol_impact(symbol, edit)
281
302
  impacts.extend(symbol_impacts)
282
-
303
+
283
304
  # 收集受影响的文件
284
305
  for impact in symbol_impacts:
285
306
  if impact.impact_type == ImpactType.REFERENCE:
286
307
  affected_files.add(impact.target)
287
308
  elif impact.impact_type == ImpactType.DEPENDENT:
288
309
  affected_files.add(impact.target)
289
-
310
+
290
311
  # 2. 分析依赖链影响
291
312
  dependency_impacts = self._analyze_dependency_chain(file_path)
292
313
  impacts.extend(dependency_impacts)
293
314
  for impact in dependency_impacts:
294
315
  affected_files.add(impact.target)
295
-
316
+
296
317
  # 3. 检测接口变更
297
318
  if edits:
298
319
  # 需要读取文件内容来比较
299
320
  interface_changes = self._detect_interface_changes(file_path, edits)
300
321
  for change in interface_changes:
301
322
  affected_files.add(change.file_path)
302
-
323
+
303
324
  # 4. 查找相关测试
304
325
  test_files = self.test_discoverer.find_test_files(file_path)
305
326
  for test_file in test_files:
306
- impacts.append(Impact(
307
- impact_type=ImpactType.TEST,
308
- target=test_file,
309
- description=f"可能测试 {os.path.basename(file_path)} 的测试文件"
310
- ))
327
+ impacts.append(
328
+ Impact(
329
+ impact_type=ImpactType.TEST,
330
+ target=test_file,
331
+ description=f"可能测试 {os.path.basename(file_path)} 的测试文件",
332
+ )
333
+ )
311
334
  affected_files.add(test_file)
312
-
335
+
313
336
  # 5. 评估风险等级
314
337
  risk_level = self._assess_risk(impacts, interface_changes)
315
-
338
+
316
339
  # 6. 生成建议
317
340
  recommendations = self._generate_recommendations(
318
341
  impacts, interface_changes, affected_files, test_files
319
342
  )
320
-
343
+
321
344
  return ImpactReport(
322
345
  affected_files=list(affected_files),
323
346
  affected_symbols=list(affected_symbols),
@@ -327,255 +350,291 @@ class ImpactAnalyzer:
327
350
  risk_level=risk_level,
328
351
  recommendations=recommendations,
329
352
  )
330
-
353
+
331
354
  def _find_symbols_in_edit(self, file_path: str, edit: Edit) -> List[Symbol]:
332
355
  """查找编辑区域内的符号"""
333
356
  symbols = self.context_manager.symbol_table.get_file_symbols(file_path)
334
-
357
+
335
358
  # 找出在编辑范围内的符号
336
359
  affected_symbols = []
337
360
  for symbol in symbols:
338
361
  # 检查符号是否与编辑区域重叠
339
- if (symbol.line_start <= edit.line_end and
340
- symbol.line_end >= edit.line_start):
362
+ if (
363
+ symbol.line_start <= edit.line_end
364
+ and symbol.line_end >= edit.line_start
365
+ ):
341
366
  affected_symbols.append(symbol)
342
-
367
+
343
368
  return affected_symbols
344
-
369
+
345
370
  def _analyze_symbol_impact(self, symbol: Symbol, edit: Edit) -> List[Impact]:
346
371
  """分析符号编辑的影响"""
347
372
  impacts = []
348
-
373
+
349
374
  # 1. 查找所有引用该符号的位置
350
375
  references = self.context_manager.find_references(symbol.name, symbol.file_path)
351
376
  for ref in references:
352
- impacts.append(Impact(
353
- impact_type=ImpactType.REFERENCE,
354
- target=ref.file_path,
355
- description=f"引用符号 {symbol.name}",
356
- line=ref.line,
357
- severity=RiskLevel.MEDIUM if symbol.kind in ('function', 'class') else RiskLevel.LOW
358
- ))
359
-
377
+ impacts.append(
378
+ Impact(
379
+ impact_type=ImpactType.REFERENCE,
380
+ target=ref.file_path,
381
+ description=f"引用符号 {symbol.name}",
382
+ line=ref.line,
383
+ severity=RiskLevel.MEDIUM
384
+ if symbol.kind in ("function", "class")
385
+ else RiskLevel.LOW,
386
+ )
387
+ )
388
+
360
389
  # 2. 查找依赖该符号的其他符号(在同一文件中)
361
- if symbol.kind in ('function', 'class'):
390
+ if symbol.kind in ("function", "class"):
362
391
  dependents = self._find_dependent_symbols(symbol)
363
392
  for dep in dependents:
364
- impacts.append(Impact(
365
- impact_type=ImpactType.DEPENDENT,
366
- target=dep.file_path,
367
- description=f"依赖符号 {symbol.name}",
368
- line=dep.line_start,
369
- severity=RiskLevel.MEDIUM
370
- ))
371
-
393
+ impacts.append(
394
+ Impact(
395
+ impact_type=ImpactType.DEPENDENT,
396
+ target=dep.file_path,
397
+ description=f"依赖符号 {symbol.name}",
398
+ line=dep.line_start,
399
+ severity=RiskLevel.MEDIUM,
400
+ )
401
+ )
402
+
372
403
  return impacts
373
-
404
+
374
405
  def _find_dependent_symbols(self, symbol: Symbol) -> List[Symbol]:
375
406
  """查找依赖该符号的其他符号"""
376
407
  dependents = []
377
-
408
+
378
409
  # 获取同一文件中的所有符号
379
- file_symbols = self.context_manager.symbol_table.get_file_symbols(symbol.file_path)
380
-
410
+ file_symbols = self.context_manager.symbol_table.get_file_symbols(
411
+ symbol.file_path
412
+ )
413
+
381
414
  # 查找在符号定义之后的符号(可能使用该符号)
382
415
  for other_symbol in file_symbols:
383
- if (other_symbol.line_start > symbol.line_end and
384
- other_symbol.name != symbol.name):
416
+ if (
417
+ other_symbol.line_start > symbol.line_end
418
+ and other_symbol.name != symbol.name
419
+ ):
385
420
  # 简单检查:如果符号名出现在其他符号的范围内,可能依赖
386
421
  # 这里使用简单的启发式方法
387
422
  content = self.context_manager._get_file_content(symbol.file_path)
388
423
  if content:
389
424
  # 提取其他符号的代码区域
390
- lines = content.split('\n')
391
- if other_symbol.line_start <= len(lines) and other_symbol.line_end <= len(lines):
392
- region = '\n'.join(lines[other_symbol.line_start-1:other_symbol.line_end])
425
+ lines = content.split("\n")
426
+ if other_symbol.line_start <= len(
427
+ lines
428
+ ) and other_symbol.line_end <= len(lines):
429
+ region = "\n".join(
430
+ lines[other_symbol.line_start - 1 : other_symbol.line_end]
431
+ )
393
432
  if symbol.name in region:
394
433
  dependents.append(other_symbol)
395
-
434
+
396
435
  return dependents
397
-
436
+
398
437
  def _analyze_dependency_chain(self, file_path: str) -> List[Impact]:
399
438
  """分析依赖链,找出所有可能受影响的文件"""
400
439
  impacts = []
401
-
440
+
402
441
  # 获取依赖该文件的所有文件(传递闭包)
403
442
  visited = set()
404
443
  to_process = [file_path]
405
-
444
+
406
445
  while to_process:
407
446
  current = to_process.pop(0)
408
447
  if current in visited:
409
448
  continue
410
449
  visited.add(current)
411
-
450
+
412
451
  dependents = self.context_manager.dependency_graph.get_dependents(current)
413
452
  for dependent in dependents:
414
453
  if dependent not in visited:
415
- impacts.append(Impact(
416
- impact_type=ImpactType.DEPENDENCY_CHAIN,
417
- target=dependent,
418
- description=f"间接依赖 {os.path.basename(file_path)}",
419
- severity=RiskLevel.LOW
420
- ))
454
+ impacts.append(
455
+ Impact(
456
+ impact_type=ImpactType.DEPENDENCY_CHAIN,
457
+ target=dependent,
458
+ description=f"间接依赖 {os.path.basename(file_path)}",
459
+ severity=RiskLevel.LOW,
460
+ )
461
+ )
421
462
  to_process.append(dependent)
422
-
463
+
423
464
  return impacts
424
-
425
- def _detect_interface_changes(self, file_path: str, edits: List[Edit]) -> List[InterfaceChange]:
465
+
466
+ def _detect_interface_changes(
467
+ self, file_path: str, edits: List[Edit]
468
+ ) -> List[InterfaceChange]:
426
469
  """检测接口变更(函数签名、类定义等)"""
427
470
  changes: List[InterfaceChange] = []
428
-
471
+
429
472
  # 读取文件内容
430
473
  content_before = self._get_file_content_before_edit(file_path, edits)
431
474
  content_after = self._get_file_content_after_edit(file_path, edits)
432
-
475
+
433
476
  if not content_before or not content_after:
434
477
  return changes
435
-
478
+
436
479
  # 解析AST并比较
437
480
  try:
438
481
  tree_before = ast.parse(content_before, filename=file_path)
439
482
  tree_after = ast.parse(content_after, filename=file_path)
440
-
483
+
441
484
  # 提取函数和类定义
442
485
  defs_before = self._extract_definitions(tree_before)
443
486
  defs_after = self._extract_definitions(tree_after)
444
-
487
+
445
488
  # 比较定义
446
489
  for name, def_before in defs_before.items():
447
490
  if name in defs_after:
448
491
  def_after = defs_after[name]
449
- change = self._compare_definition(name, def_before, def_after, file_path)
492
+ change = self._compare_definition(
493
+ name, def_before, def_after, file_path
494
+ )
450
495
  if change:
451
496
  changes.append(change)
452
497
  else:
453
498
  # 定义被删除
454
- changes.append(InterfaceChange(
455
- symbol_name=name,
456
- change_type='removed',
457
- file_path=file_path,
458
- line=def_before['line'],
459
- description=f"符号 {name} 被删除"
460
- ))
461
-
499
+ changes.append(
500
+ InterfaceChange(
501
+ symbol_name=name,
502
+ change_type="removed",
503
+ file_path=file_path,
504
+ line=def_before["line"],
505
+ description=f"符号 {name} 被删除",
506
+ )
507
+ )
508
+
462
509
  # 检查新增的定义
463
510
  for name, def_after in defs_after.items():
464
511
  if name not in defs_before:
465
- changes.append(InterfaceChange(
466
- symbol_name=name,
467
- change_type='added',
468
- file_path=file_path,
469
- line=def_after['line'],
470
- description=f"新增符号 {name}"
471
- ))
472
-
512
+ changes.append(
513
+ InterfaceChange(
514
+ symbol_name=name,
515
+ change_type="added",
516
+ file_path=file_path,
517
+ line=def_after["line"],
518
+ description=f"新增符号 {name}",
519
+ )
520
+ )
521
+
473
522
  except SyntaxError:
474
523
  # 如果解析失败,跳过接口变更检测
475
524
  pass
476
-
525
+
477
526
  return changes
478
-
527
+
479
528
  def _extract_definitions(self, tree: ast.AST) -> Dict[str, Dict]:
480
529
  """从AST中提取函数和类定义"""
481
530
  definitions = {}
482
-
531
+
483
532
  for node in ast.walk(tree):
484
533
  if isinstance(node, ast.FunctionDef):
485
534
  # 提取函数签名
486
535
  args = [arg.arg for arg in node.args.args]
487
536
  signature = f"{node.name}({', '.join(args)})"
488
537
  definitions[node.name] = {
489
- 'type': 'function',
490
- 'line': node.lineno,
491
- 'signature': signature,
492
- 'args': args,
493
- 'node': node,
538
+ "type": "function",
539
+ "line": node.lineno,
540
+ "signature": signature,
541
+ "args": args,
542
+ "node": node,
494
543
  }
495
544
  elif isinstance(node, ast.ClassDef):
496
545
  definitions[node.name] = {
497
- 'type': 'class',
498
- 'line': node.lineno,
499
- 'signature': node.name,
500
- 'node': node,
546
+ "type": "class",
547
+ "line": node.lineno,
548
+ "signature": node.name,
549
+ "node": node,
501
550
  }
502
-
551
+
503
552
  return definitions
504
-
505
- def _compare_definition(self, name: str, def_before: Dict, def_after: Dict, file_path: str) -> Optional[InterfaceChange]:
553
+
554
+ def _compare_definition(
555
+ self, name: str, def_before: Dict, def_after: Dict, file_path: str
556
+ ) -> Optional[InterfaceChange]:
506
557
  """比较两个定义,检测接口变更"""
507
- if def_before['type'] != def_after['type']:
558
+ if def_before["type"] != def_after["type"]:
508
559
  return InterfaceChange(
509
560
  symbol_name=name,
510
- change_type='signature',
561
+ change_type="signature",
511
562
  file_path=file_path,
512
- line=def_after['line'],
513
- before=def_before['signature'],
514
- after=def_after['signature'],
515
- description=f"符号 {name} 的类型从 {def_before['type']} 变为 {def_after['type']}"
563
+ line=def_after["line"],
564
+ before=def_before["signature"],
565
+ after=def_after["signature"],
566
+ description=f"符号 {name} 的类型从 {def_before['type']} 变为 {def_after['type']}",
516
567
  )
517
-
518
- if def_before['type'] == 'function':
568
+
569
+ if def_before["type"] == "function":
519
570
  # 比较函数参数
520
- args_before = def_before.get('args', [])
521
- args_after = def_after.get('args', [])
522
-
571
+ args_before = def_before.get("args", [])
572
+ args_after = def_after.get("args", [])
573
+
523
574
  if args_before != args_after:
524
575
  return InterfaceChange(
525
576
  symbol_name=name,
526
- change_type='signature',
577
+ change_type="signature",
527
578
  file_path=file_path,
528
- line=def_after['line'],
529
- before=def_before['signature'],
530
- after=def_after['signature'],
531
- description=f"函数 {name} 的参数从 ({', '.join(args_before)}) 变为 ({', '.join(args_after)})"
579
+ line=def_after["line"],
580
+ before=def_before["signature"],
581
+ after=def_after["signature"],
582
+ description=f"函数 {name} 的参数从 ({', '.join(args_before)}) 变为 ({', '.join(args_after)})",
532
583
  )
533
-
584
+
534
585
  return None
535
-
536
- def _get_file_content_before_edit(self, file_path: str, edits: List[Edit]) -> Optional[str]:
586
+
587
+ def _get_file_content_before_edit(
588
+ self, file_path: str, edits: List[Edit]
589
+ ) -> Optional[str]:
537
590
  """获取编辑前的文件内容"""
538
591
  try:
539
- with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
592
+ with open(file_path, "r", encoding="utf-8", errors="replace") as f:
540
593
  return f.read()
541
594
  except Exception:
542
595
  return None
543
-
544
- def _get_file_content_after_edit(self, file_path: str, edits: List[Edit]) -> Optional[str]:
596
+
597
+ def _get_file_content_after_edit(
598
+ self, file_path: str, edits: List[Edit]
599
+ ) -> Optional[str]:
545
600
  """获取编辑后的文件内容(模拟)"""
546
601
  # 这里应该根据edits模拟编辑后的内容
547
602
  # 为了简化,我们直接读取当前文件内容
548
603
  # 在实际使用中,应该根据edits应用变更
549
604
  try:
550
- with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
605
+ with open(file_path, "r", encoding="utf-8", errors="replace") as f:
551
606
  return f.read()
552
607
  except Exception:
553
608
  return None
554
-
555
- def _assess_risk(self, impacts: List[Impact], interface_changes: List[InterfaceChange]) -> RiskLevel:
609
+
610
+ def _assess_risk(
611
+ self, impacts: List[Impact], interface_changes: List[InterfaceChange]
612
+ ) -> RiskLevel:
556
613
  """评估编辑风险等级"""
557
614
  # 统计高风险因素
558
615
  high_risk_count = 0
559
616
  medium_risk_count = 0
560
-
617
+
561
618
  # 接口变更通常是高风险
562
619
  if interface_changes:
563
620
  high_risk_count += len(interface_changes)
564
-
621
+
565
622
  # 统计影响数量
566
- reference_count = sum(1 for i in impacts if i.impact_type == ImpactType.REFERENCE)
623
+ reference_count = sum(
624
+ 1 for i in impacts if i.impact_type == ImpactType.REFERENCE
625
+ )
567
626
  if reference_count > 10:
568
627
  high_risk_count += 1
569
628
  elif reference_count > 5:
570
629
  medium_risk_count += 1
571
-
630
+
572
631
  # 检查是否有高风险的影响
573
632
  for impact in impacts:
574
633
  if impact.severity == RiskLevel.HIGH:
575
634
  high_risk_count += 1
576
635
  elif impact.severity == RiskLevel.MEDIUM:
577
636
  medium_risk_count += 1
578
-
637
+
579
638
  # 评估风险等级
580
639
  if high_risk_count > 0 or medium_risk_count > 3:
581
640
  return RiskLevel.HIGH
@@ -583,7 +642,7 @@ class ImpactAnalyzer:
583
642
  return RiskLevel.MEDIUM
584
643
  else:
585
644
  return RiskLevel.LOW
586
-
645
+
587
646
  def _generate_recommendations(
588
647
  self,
589
648
  impacts: List[Impact],
@@ -593,56 +652,58 @@ class ImpactAnalyzer:
593
652
  ) -> List[str]:
594
653
  """生成修复建议"""
595
654
  recommendations = []
596
-
655
+
597
656
  # 如果有接口变更,建议检查所有调用点
598
657
  if interface_changes:
599
658
  recommendations.append(
600
659
  f"检测到 {len(interface_changes)} 个接口变更,请检查所有调用点并更新相关代码"
601
660
  )
602
-
661
+
603
662
  # 如果有测试文件,建议运行测试
604
663
  if test_files:
605
664
  recommendations.append(
606
665
  f"发现 {len(test_files)} 个相关测试文件,建议运行测试确保功能正常"
607
666
  )
608
-
667
+
609
668
  # 如果影响文件较多,建议增量测试
610
669
  if len(affected_files) > 5:
611
670
  recommendations.append(
612
671
  f"编辑影响了 {len(affected_files)} 个文件,建议进行增量测试"
613
672
  )
614
-
673
+
615
674
  # 如果有大量引用,建议代码审查
616
- reference_count = sum(1 for i in impacts if i.impact_type == ImpactType.REFERENCE)
675
+ reference_count = sum(
676
+ 1 for i in impacts if i.impact_type == ImpactType.REFERENCE
677
+ )
617
678
  if reference_count > 10:
618
679
  recommendations.append(
619
680
  f"检测到 {reference_count} 个符号引用,建议进行代码审查"
620
681
  )
621
-
682
+
622
683
  if not recommendations:
623
684
  recommendations.append("编辑影响范围较小,建议进行基本测试")
624
-
685
+
625
686
  return recommendations
626
687
 
627
688
 
628
689
  def parse_git_diff_to_edits(file_path: str, project_root: str) -> List[Edit]:
629
690
  """从git diff中解析编辑操作
630
-
691
+
631
692
  Args:
632
693
  file_path: 文件路径
633
694
  project_root: 项目根目录
634
-
695
+
635
696
  Returns:
636
697
  List[Edit]: 编辑操作列表
637
698
  """
638
699
  edits: List[Edit] = []
639
-
700
+
640
701
  try:
641
702
  # 获取文件的git diff
642
703
  abs_path = os.path.abspath(file_path)
643
704
  if not os.path.exists(abs_path):
644
705
  return edits
645
-
706
+
646
707
  # 检查是否有git仓库
647
708
  try:
648
709
  subprocess.run(
@@ -655,7 +716,7 @@ def parse_git_diff_to_edits(file_path: str, project_root: str) -> List[Edit]:
655
716
  except (subprocess.CalledProcessError, FileNotFoundError):
656
717
  # 不是git仓库或git不可用,返回空列表
657
718
  return edits
658
-
719
+
659
720
  # 获取HEAD的hash
660
721
  try:
661
722
  result = subprocess.run(
@@ -668,7 +729,7 @@ def parse_git_diff_to_edits(file_path: str, project_root: str) -> List[Edit]:
668
729
  head_exists = result.returncode == 0 and result.stdout.strip()
669
730
  except Exception:
670
731
  head_exists = False
671
-
732
+
672
733
  # 临时添加文件到git索引(如果是新文件)
673
734
  subprocess.run(
674
735
  ["git", "add", "-N", "--", abs_path],
@@ -677,7 +738,7 @@ def parse_git_diff_to_edits(file_path: str, project_root: str) -> List[Edit]:
677
738
  stdout=subprocess.DEVNULL,
678
739
  stderr=subprocess.DEVNULL,
679
740
  )
680
-
741
+
681
742
  try:
682
743
  # 获取diff
683
744
  cmd = ["git", "diff"] + (["HEAD"] if head_exists else []) + ["--", abs_path]
@@ -690,79 +751,93 @@ def parse_git_diff_to_edits(file_path: str, project_root: str) -> List[Edit]:
690
751
  errors="replace",
691
752
  check=False,
692
753
  )
693
-
754
+
694
755
  if result.returncode != 0 or not result.stdout:
695
756
  return edits
696
-
757
+
697
758
  diff_text = result.stdout
698
-
759
+
699
760
  # 解析diff文本
700
- lines = diff_text.split('\n')
761
+ lines = diff_text.split("\n")
701
762
  current_hunk_start = None
702
763
  current_line_num: Optional[int] = None
703
764
  before_lines: List[str] = []
704
765
  after_lines: List[str] = []
705
766
  in_hunk = False
706
-
767
+
707
768
  for line in lines:
708
769
  # 解析hunk header: @@ -start,count +start,count @@
709
- if line.startswith('@@'):
770
+ if line.startswith("@@"):
710
771
  # 保存之前的hunk
711
772
  if in_hunk and current_hunk_start is not None:
712
773
  if before_lines or after_lines:
713
- edits.append(Edit(
714
- file_path=abs_path,
715
- line_start=current_hunk_start,
716
- line_end=current_hunk_start + len(after_lines) - 1 if after_lines else current_hunk_start,
717
- before='\n'.join(before_lines),
718
- after='\n'.join(after_lines),
719
- edit_type='modify' if before_lines and after_lines else ('delete' if before_lines else 'add')
720
- ))
721
-
774
+ edits.append(
775
+ Edit(
776
+ file_path=abs_path,
777
+ line_start=current_hunk_start,
778
+ line_end=current_hunk_start + len(after_lines) - 1
779
+ if after_lines
780
+ else current_hunk_start,
781
+ before="\n".join(before_lines),
782
+ after="\n".join(after_lines),
783
+ edit_type="modify"
784
+ if before_lines and after_lines
785
+ else ("delete" if before_lines else "add"),
786
+ )
787
+ )
788
+
722
789
  # 解析新的hunk
723
- match = re.search(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@', line)
790
+ match = re.search(
791
+ r"@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@", line
792
+ )
724
793
  if match:
725
794
  old_start = int(match.group(1))
726
795
  new_start = int(match.group(3))
727
-
796
+
728
797
  current_hunk_start = new_start
729
798
  current_line_num = old_start
730
799
  before_lines = []
731
800
  after_lines = []
732
801
  in_hunk = True
733
802
  continue
734
-
803
+
735
804
  if not in_hunk:
736
805
  continue
737
-
806
+
738
807
  # 解析diff行
739
- if line.startswith('-') and not line.startswith('---'):
808
+ if line.startswith("-") and not line.startswith("---"):
740
809
  # 删除的行
741
810
  before_lines.append(line[1:])
742
811
  if current_line_num is not None:
743
812
  current_line_num += 1
744
- elif line.startswith('+') and not line.startswith('+++'):
813
+ elif line.startswith("+") and not line.startswith("+++"):
745
814
  # 新增的行
746
815
  after_lines.append(line[1:])
747
- elif line.startswith(' '):
816
+ elif line.startswith(" "):
748
817
  # 未改变的行
749
818
  before_lines.append(line[1:])
750
819
  after_lines.append(line[1:])
751
820
  if current_line_num is not None:
752
821
  current_line_num += 1
753
-
822
+
754
823
  # 保存最后一个hunk
755
824
  if in_hunk and current_hunk_start is not None:
756
825
  if before_lines or after_lines:
757
- edits.append(Edit(
758
- file_path=abs_path,
759
- line_start=current_hunk_start,
760
- line_end=current_hunk_start + len(after_lines) - 1 if after_lines else current_hunk_start,
761
- before='\n'.join(before_lines),
762
- after='\n'.join(after_lines),
763
- edit_type='modify' if before_lines and after_lines else ('delete' if before_lines else 'add')
764
- ))
765
-
826
+ edits.append(
827
+ Edit(
828
+ file_path=abs_path,
829
+ line_start=current_hunk_start,
830
+ line_end=current_hunk_start + len(after_lines) - 1
831
+ if after_lines
832
+ else current_hunk_start,
833
+ before="\n".join(before_lines),
834
+ after="\n".join(after_lines),
835
+ edit_type="modify"
836
+ if before_lines and after_lines
837
+ else ("delete" if before_lines else "add"),
838
+ )
839
+ )
840
+
766
841
  finally:
767
842
  # 清理临时添加的文件
768
843
  subprocess.run(
@@ -772,10 +847,9 @@ def parse_git_diff_to_edits(file_path: str, project_root: str) -> List[Edit]:
772
847
  stdout=subprocess.DEVNULL,
773
848
  stderr=subprocess.DEVNULL,
774
849
  )
775
-
850
+
776
851
  except Exception:
777
852
  # 解析失败时返回空列表
778
853
  pass
779
-
780
- return edits
781
854
 
855
+ return edits