siada-cli 0.0.1__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.
- agent_config.yaml +34 -0
- siada/__init__.py +13 -0
- siada/agent_hub/__init__.py +10 -0
- siada/agent_hub/coder/bug_fix_agent.py +398 -0
- siada/agent_hub/coder/code_gen_agent.py +235 -0
- siada/agent_hub/coder/fe_gen_agent.py +37 -0
- siada/agent_hub/coder/issue_review_agent.py +68 -0
- siada/agent_hub/coder/prompt/__init__.py +0 -0
- siada/agent_hub/coder/prompt/base/__init__.py +0 -0
- siada/agent_hub/coder/prompt/base/capabilities.py +18 -0
- siada/agent_hub/coder/prompt/base/non_interactive.py +14 -0
- siada/agent_hub/coder/prompt/base/prompt_builder.py +33 -0
- siada/agent_hub/coder/prompt/base/rules.py +40 -0
- siada/agent_hub/coder/prompt/base/tool_use.py +14 -0
- siada/agent_hub/coder/prompt/bug_prompt/bug_fix_prompt.py +51 -0
- siada/agent_hub/coder/prompt/bug_prompt/rules.py +53 -0
- siada/agent_hub/coder/prompt/code_gen_prompt.py +45 -0
- siada/agent_hub/coder/prompt/fe_gen_prompt.py +45 -0
- siada/agent_hub/coder/prompt/issue_review_prompt.py +121 -0
- siada/agent_hub/coder/tracing/__init__.py +22 -0
- siada/agent_hub/coder/tracing/logger_tracing_processor.py +458 -0
- siada/agent_hub/hooks/__init__.py +0 -0
- siada/agent_hub/hooks/io_hooks.py +30 -0
- siada/agent_hub/siada_agent.py +214 -0
- siada/config/__init__.py +0 -0
- siada/config/config_loader.py +67 -0
- siada/entrypoint/__init__.py +3 -0
- siada/entrypoint/args_parser/__init__.py +0 -0
- siada/entrypoint/args_parser/args.py +195 -0
- siada/entrypoint/interaction/__init__.py +0 -0
- siada/entrypoint/interaction/base_turn.py +107 -0
- siada/entrypoint/interaction/config.py +30 -0
- siada/entrypoint/interaction/controller.py +122 -0
- siada/entrypoint/interaction/nointeractive_controller.py +27 -0
- siada/entrypoint/interaction/turn/interface.py +116 -0
- siada/entrypoint/interaction/turn/models.py +34 -0
- siada/entrypoint/interaction/turn/run_turn.py +641 -0
- siada/entrypoint/interaction/turn_factory.py +35 -0
- siada/entrypoint/interaction/turn_models.py +28 -0
- siada/entrypoint/siadahub.py +413 -0
- siada/foundation/__init__.py +0 -0
- siada/foundation/code_agent_context.py +48 -0
- siada/foundation/config.py +59 -0
- siada/foundation/constants.py +2 -0
- siada/foundation/context.py +76 -0
- siada/foundation/logging.py +341 -0
- siada/foundation/tools/__init__.py +0 -0
- siada/foundation/tools/get_git_diff.py +105 -0
- siada/io/__init__.py +0 -0
- siada/io/banner.py +148 -0
- siada/io/color_settings.py +207 -0
- siada/io/color_utils.py +16 -0
- siada/io/components/__init__.py +0 -0
- siada/io/components/mdstream.py +204 -0
- siada/io/console_printer.py +68 -0
- siada/io/io.py +661 -0
- siada/io/key_bindings.py +129 -0
- siada/io/notification_command.py +40 -0
- siada/io/test_banner.py +64 -0
- siada/models/__init__.py +0 -0
- siada/models/agent.py +47 -0
- siada/models/coder/__init__.py +0 -0
- siada/models/converter.py +27 -0
- siada/models/model_base_config.py +121 -0
- siada/models/model_run_config.py +153 -0
- siada/provider/__init__.py +0 -0
- siada/provider/client_factory.py +112 -0
- siada/provider/llm_client.py +11 -0
- siada/provider/openrouter/__init__.py +0 -0
- siada/provider/openrouter/coverter.py +17 -0
- siada/provider/openrouter/openroute_provider.py +33 -0
- siada/provider/provider_factory.py +53 -0
- siada/queries/tree-sitter-language-pack/README.md +7 -0
- siada/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
- siada/queries/tree-sitter-language-pack/c-tags.scm +9 -0
- siada/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
- siada/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
- siada/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
- siada/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
- siada/queries/tree-sitter-language-pack/d-tags.scm +26 -0
- siada/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
- siada/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
- siada/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
- siada/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
- siada/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
- siada/queries/tree-sitter-language-pack/go-tags.scm +42 -0
- siada/queries/tree-sitter-language-pack/java-tags.scm +20 -0
- siada/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
- siada/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
- siada/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
- siada/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
- siada/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
- siada/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
- siada/queries/tree-sitter-language-pack/python-tags.scm +14 -0
- siada/queries/tree-sitter-language-pack/r-tags.scm +21 -0
- siada/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
- siada/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
- siada/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
- siada/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
- siada/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
- siada/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
- siada/queries/tree-sitter-languages/README.md +23 -0
- siada/queries/tree-sitter-languages/c-tags.scm +9 -0
- siada/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
- siada/queries/tree-sitter-languages/cpp-tags.scm +15 -0
- siada/queries/tree-sitter-languages/dart-tags.scm +91 -0
- siada/queries/tree-sitter-languages/elisp-tags.scm +8 -0
- siada/queries/tree-sitter-languages/elixir-tags.scm +54 -0
- siada/queries/tree-sitter-languages/elm-tags.scm +19 -0
- siada/queries/tree-sitter-languages/go-tags.scm +30 -0
- siada/queries/tree-sitter-languages/hcl-tags.scm +77 -0
- siada/queries/tree-sitter-languages/java-tags.scm +20 -0
- siada/queries/tree-sitter-languages/javascript-tags.scm +88 -0
- siada/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
- siada/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
- siada/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
- siada/queries/tree-sitter-languages/php-tags.scm +26 -0
- siada/queries/tree-sitter-languages/python-tags.scm +12 -0
- siada/queries/tree-sitter-languages/ql-tags.scm +26 -0
- siada/queries/tree-sitter-languages/ruby-tags.scm +64 -0
- siada/queries/tree-sitter-languages/rust-tags.scm +60 -0
- siada/queries/tree-sitter-languages/scala-tags.scm +65 -0
- siada/queries/tree-sitter-languages/typescript-tags.scm +41 -0
- siada/services/__init__.py +30 -0
- siada/services/at_file_recommendation_design_document.md +1213 -0
- siada/services/code_context_manager.py +136 -0
- siada/services/enhanced_fix_result_check.py +546 -0
- siada/services/execution_trace_collector.py +207 -0
- siada/services/file_recommendation/__init__.py +136 -0
- siada/services/file_recommendation/core/__init__.py +17 -0
- siada/services/file_recommendation/core/completion_engine.py +351 -0
- siada/services/file_recommendation/core/config.py +62 -0
- siada/services/file_recommendation/core/file_discovery.py +353 -0
- siada/services/file_recommendation/core/suggestion.py +110 -0
- siada/services/file_recommendation/utils/__init__.py +22 -0
- siada/services/file_recommendation/utils/path_utils.py +138 -0
- siada/services/file_recommendation/utils/text_utils.py +253 -0
- siada/services/fix_result_check.py +377 -0
- siada/services/handle_at_command/README.md +247 -0
- siada/services/handle_at_command/__init__.py +115 -0
- siada/services/handle_at_command/exceptions.py +46 -0
- siada/services/handle_at_command/models.py +92 -0
- siada/services/handle_at_command/parser.py +189 -0
- siada/services/handle_at_command/processor.py +321 -0
- siada/services/handle_at_command/resolver.py +318 -0
- siada/services/handle_at_command/utils.py +345 -0
- siada/services/model_info_service.py +103 -0
- siada/services/siada_runner.py +170 -0
- siada/session/__init__.py +29 -0
- siada/session/session_manager.py +66 -0
- siada/session/session_models.py +43 -0
- siada/support/__init__.py +0 -0
- siada/support/completer.py +204 -0
- siada/support/editor.py +146 -0
- siada/support/envprocessor.py +54 -0
- siada/support/repo.py +51 -0
- siada/support/slash_commands.py +410 -0
- siada/support/spinner.py +320 -0
- siada/tools/__init__.py +0 -0
- siada/tools/ast/README.md +345 -0
- siada/tools/ast/ast_tool.py +341 -0
- siada/tools/ast/models.py +11 -0
- siada/tools/coder/__init__.py +0 -0
- siada/tools/coder/ask_followup_question.py +70 -0
- siada/tools/coder/cmd_runner.py +132 -0
- siada/tools/coder/file_operator.py +284 -0
- siada/tools/coder/file_search/README.md +223 -0
- siada/tools/coder/file_search/__init__.py +10 -0
- siada/tools/coder/file_search/bin/rg-linux-arm64 +0 -0
- siada/tools/coder/file_search/bin/rg-linux-x64 +0 -0
- siada/tools/coder/file_search/bin/rg-macos-arm64 +0 -0
- siada/tools/coder/file_search/bin/rg-macos-x64 +0 -0
- siada/tools/coder/file_search/bin/rg.exe +0 -0
- siada/tools/coder/file_search/search.py +437 -0
- siada/tools/coder/file_search/setup_package.py +150 -0
- siada/tools/coder/file_search/test_search.py +142 -0
- siada/tools/coder/files.py +147 -0
- siada/tools/coder/fix_attempt_completion.py +58 -0
- siada/tools/coder/issue_review_completion.py +29 -0
- siada/tools/coder/observation/__init__.py +0 -0
- siada/tools/coder/observation/error.py +22 -0
- siada/tools/coder/observation/file_observation.py +216 -0
- siada/tools/coder/observation/observation.py +71 -0
- siada/tools/coder/repo_map/__init__.py +157 -0
- siada/tools/coder/repo_map/dump.py +29 -0
- siada/tools/coder/repo_map/io.py +226 -0
- siada/tools/coder/repo_map/repo_map.py +881 -0
- siada/tools/coder/repo_map/special.py +203 -0
- siada/tools/coder/repo_map/token_counter.py +154 -0
- siada/tools/coder/repo_map/waiting.py +221 -0
- siada/tools/coder/reproduce_completion.py +39 -0
- siada/tools/coder/run_cmd.py +60 -0
- siada/tools/coder/test_completion.py +25 -0
- siada/tools/coder/tool_docs.py +44 -0
- siada/tools/compression_tool.py +200 -0
- siada/tools/read_many_files/README.md +220 -0
- siada/tools/read_many_files/__init__.py +25 -0
- siada/tools/read_many_files/file_processor.py +486 -0
- siada/tools/read_many_files/filters.py +326 -0
- siada/tools/read_many_files/formatters.py +204 -0
- siada/tools/read_many_files/models.py +189 -0
- siada/tools/read_many_files_tool.py +233 -0
- siada/tools/tool_call_format/__init__.py +55 -0
- siada/tools/tool_call_format/file_to_language.py +231 -0
- siada/tools/tool_call_format/formatter_factory.py +80 -0
- siada/tools/tool_call_format/formatters.py +282 -0
- siada/tools/tool_call_format/tool_call_formatter.py +54 -0
- siada/tools/web/__init__.py +0 -0
- siada/tools/web/web_crawl.py +208 -0
- siada/utils.py +194 -0
- siada_cli-0.0.1.dist-info/LICENSE +201 -0
- siada_cli-0.0.1.dist-info/METADATA +457 -0
- siada_cli-0.0.1.dist-info/RECORD +215 -0
- siada_cli-0.0.1.dist-info/WHEEL +4 -0
- siada_cli-0.0.1.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Text processing utilities for file recommendation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List, Tuple, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def is_completion_active(text: str, cursor_row: int, cursor_col: int, lines: List[str]) -> bool:
|
|
9
|
+
"""
|
|
10
|
+
Check if @ file recommendation should be activated
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
text: Complete input text
|
|
14
|
+
cursor_row: Cursor row number
|
|
15
|
+
cursor_col: Cursor column number
|
|
16
|
+
lines: Text line list
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
bool: Whether to activate recommendation
|
|
20
|
+
"""
|
|
21
|
+
# If it's a slash command, handle that first
|
|
22
|
+
if text.strip().startswith('/'):
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
# Get current line
|
|
26
|
+
current_line = lines[cursor_row] if cursor_row < len(lines) else ""
|
|
27
|
+
code_points = list(current_line) # Unicode code point array
|
|
28
|
+
|
|
29
|
+
# Search backwards from cursor position
|
|
30
|
+
for i in range(cursor_col - 1, -1, -1):
|
|
31
|
+
char = code_points[i]
|
|
32
|
+
|
|
33
|
+
if char == ' ':
|
|
34
|
+
# Check if it's escaped space
|
|
35
|
+
backslash_count = 0
|
|
36
|
+
j = i - 1
|
|
37
|
+
while j >= 0 and code_points[j] == '\\':
|
|
38
|
+
backslash_count += 1
|
|
39
|
+
j -= 1
|
|
40
|
+
|
|
41
|
+
# Even number of backslashes means unescaped space
|
|
42
|
+
if backslash_count % 2 == 0:
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
elif char == '@':
|
|
46
|
+
# Found @ character, activate recommendation
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def parse_at_command_path(text: str) -> Tuple[str, str, str]:
|
|
53
|
+
"""
|
|
54
|
+
Parse path information from @ command
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
text: Input text
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Tuple[base_directory, path_prefix, original_partial_path]
|
|
61
|
+
|
|
62
|
+
Note: Modified to remove relative path support and use full path as search prefix
|
|
63
|
+
"""
|
|
64
|
+
at_index = text.rfind('@')
|
|
65
|
+
if at_index == -1:
|
|
66
|
+
return ".", "", ""
|
|
67
|
+
|
|
68
|
+
partial_path = text[at_index + 1:]
|
|
69
|
+
|
|
70
|
+
# Use the entire path after @ as search prefix, no directory-based splitting
|
|
71
|
+
# This eliminates relative path navigation issues
|
|
72
|
+
full_prefix = unescape_path(partial_path)
|
|
73
|
+
|
|
74
|
+
return ".", full_prefix, partial_path
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def to_code_points(text: str) -> List[str]:
|
|
78
|
+
"""
|
|
79
|
+
Convert string to Unicode code point array
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
text: Input string
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
List[str]: Unicode code point list
|
|
86
|
+
"""
|
|
87
|
+
return list(text)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def cp_len(text: str) -> int:
|
|
91
|
+
"""
|
|
92
|
+
Get Unicode code point length of string
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
text: Input string
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
int: Code point length
|
|
99
|
+
"""
|
|
100
|
+
return len(list(text))
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def cp_slice(text: str, start: int, end: Optional[int] = None) -> str:
|
|
104
|
+
"""
|
|
105
|
+
Slice string by Unicode code points
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
text: Input string
|
|
109
|
+
start: Start position
|
|
110
|
+
end: End position
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
str: Sliced string
|
|
114
|
+
"""
|
|
115
|
+
code_points = list(text)
|
|
116
|
+
return ''.join(code_points[start:end])
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def unescape_path(file_path: str) -> str:
|
|
120
|
+
"""
|
|
121
|
+
Unescape file path spaces
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
file_path: Escaped file path
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
str: Unescaped path
|
|
128
|
+
"""
|
|
129
|
+
return file_path.replace('\\ ', ' ')
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def extract_at_path_from_text(text: str, cursor_pos: int = None) -> Optional[str]:
|
|
133
|
+
"""
|
|
134
|
+
Extract the @ path from text at cursor position
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
text: Input text
|
|
138
|
+
cursor_pos: Cursor position (default: end of text)
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Optional[str]: Extracted @ path or None if not found
|
|
142
|
+
"""
|
|
143
|
+
if cursor_pos is None:
|
|
144
|
+
cursor_pos = len(text)
|
|
145
|
+
|
|
146
|
+
# Find the last @ before cursor position
|
|
147
|
+
at_pos = -1
|
|
148
|
+
for i in range(cursor_pos - 1, -1, -1):
|
|
149
|
+
if i < len(text) and text[i] == '@':
|
|
150
|
+
# Check if it's escaped
|
|
151
|
+
if i == 0 or text[i-1] != '\\':
|
|
152
|
+
at_pos = i
|
|
153
|
+
break
|
|
154
|
+
|
|
155
|
+
if at_pos == -1:
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
# Find the end of the @ path (next unescaped space or end of text)
|
|
159
|
+
end_pos = at_pos + 1
|
|
160
|
+
while end_pos < len(text):
|
|
161
|
+
if text[end_pos] == ' ':
|
|
162
|
+
# Check if it's escaped
|
|
163
|
+
if end_pos == 0 or text[end_pos - 1] != '\\':
|
|
164
|
+
break
|
|
165
|
+
elif text[end_pos] == '\\' and end_pos + 1 < len(text):
|
|
166
|
+
# Skip escaped character
|
|
167
|
+
end_pos += 2
|
|
168
|
+
continue
|
|
169
|
+
end_pos += 1
|
|
170
|
+
|
|
171
|
+
# Extract from @ to end of path
|
|
172
|
+
return text[at_pos:end_pos]
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def is_at_command_start(text: str, position: int) -> bool:
|
|
176
|
+
"""
|
|
177
|
+
Check if position is at the start of an @ command
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
text: Input text
|
|
181
|
+
position: Position to check
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
bool: True if position is at @ command start
|
|
185
|
+
"""
|
|
186
|
+
if position >= len(text) or text[position] != '@':
|
|
187
|
+
return False
|
|
188
|
+
|
|
189
|
+
# Check if @ is escaped
|
|
190
|
+
if position > 0 and text[position - 1] == '\\':
|
|
191
|
+
return False
|
|
192
|
+
|
|
193
|
+
return True
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def find_at_commands_in_text(text: str) -> List[Tuple[int, str]]:
|
|
197
|
+
"""
|
|
198
|
+
Find all @ commands in text
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
text: Input text
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
List[Tuple[int, str]]: List of (position, at_command) tuples
|
|
205
|
+
"""
|
|
206
|
+
commands = []
|
|
207
|
+
i = 0
|
|
208
|
+
|
|
209
|
+
while i < len(text):
|
|
210
|
+
if text[i] == '@' and is_at_command_start(text, i):
|
|
211
|
+
# Find end of command
|
|
212
|
+
j = i + 1
|
|
213
|
+
while j < len(text) and not text[j].isspace():
|
|
214
|
+
if text[j] == '\\' and j + 1 < len(text):
|
|
215
|
+
j += 2 # Skip escaped character
|
|
216
|
+
else:
|
|
217
|
+
j += 1
|
|
218
|
+
|
|
219
|
+
command = text[i:j]
|
|
220
|
+
commands.append((i, command))
|
|
221
|
+
i = j
|
|
222
|
+
else:
|
|
223
|
+
i += 1
|
|
224
|
+
|
|
225
|
+
return commands
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def validate_at_path_syntax(at_path: str) -> bool:
|
|
229
|
+
"""
|
|
230
|
+
Validate @ path syntax
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
at_path: @ path to validate
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
bool: True if valid syntax
|
|
237
|
+
"""
|
|
238
|
+
if not at_path or not at_path.startswith('@'):
|
|
239
|
+
return False
|
|
240
|
+
|
|
241
|
+
path_part = at_path[1:]
|
|
242
|
+
|
|
243
|
+
# Empty path after @ is only valid for lone @
|
|
244
|
+
if not path_part and at_path != '@':
|
|
245
|
+
return False
|
|
246
|
+
|
|
247
|
+
# Check for invalid characters
|
|
248
|
+
invalid_chars = ['<', '>', '|', '"', '*', '?']
|
|
249
|
+
for char in invalid_chars:
|
|
250
|
+
if char in path_part:
|
|
251
|
+
return False
|
|
252
|
+
|
|
253
|
+
return True
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Fix Result Checker - 通过模型判断代码修复是否真正解决了问题
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Dict, Any, Union
|
|
9
|
+
|
|
10
|
+
from openai.types.chat import ChatCompletionMessageParam
|
|
11
|
+
|
|
12
|
+
from siada.provider.client_factory import get_client_with_kwargs
|
|
13
|
+
from siada.foundation.config import settings
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
class FixResultChecker:
|
|
18
|
+
"""修复结果检查器
|
|
19
|
+
|
|
20
|
+
使用模型分析代码修复是否真正解决了描述的问题
|
|
21
|
+
"""
|
|
22
|
+
async def check(self, issue_desc: str, fix_code: str, context: Any) -> Dict[str, Any]:
|
|
23
|
+
"""检查修复代码是否真正解决了问题
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
issue_desc: 问题描述
|
|
27
|
+
fix_code: 修复代码
|
|
28
|
+
context: 上下文对象,包含provider等信息
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Dict[str, Any]: 包含检查结果的字典
|
|
32
|
+
{
|
|
33
|
+
"is_fixed": bool, # 是否修复(部分修复算作未修复)
|
|
34
|
+
"check_summary": str, # 检查摘要,说明修复状态和原因
|
|
35
|
+
"analysis": str # 完整的分析过程
|
|
36
|
+
}
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
analysis_result = await self._call_model_for_analysis(issue_desc, fix_code, context)
|
|
40
|
+
return self._parse_analysis_result(analysis_result)
|
|
41
|
+
except Exception as e:
|
|
42
|
+
logger.error(f"Fix result check failed: {e}", exc_info=True)
|
|
43
|
+
return {
|
|
44
|
+
"is_fixed": False,
|
|
45
|
+
"check_summary": f"分析过程中发生错误: {str(e)}",
|
|
46
|
+
"analysis": f"错误详情: {str(e)}"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async def _call_model_for_analysis(self, issue_desc: str, fix_code: str, context: Any) -> str:
|
|
50
|
+
"""调用模型进行分析
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
issue_desc: 问题描述
|
|
54
|
+
fix_code: 修复代码
|
|
55
|
+
context: 上下文对象,包含provider等信息
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
str: 模型分析结果
|
|
59
|
+
"""
|
|
60
|
+
# 构建用户任务提示词
|
|
61
|
+
user_task = self.build_prompt(issue_desc, fix_code)
|
|
62
|
+
|
|
63
|
+
# 构建请求消息
|
|
64
|
+
model_messages: list[ChatCompletionMessageParam] = [
|
|
65
|
+
{"role": "user", "content": user_task},
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# Call the model
|
|
70
|
+
default_kwargs = {
|
|
71
|
+
"model": settings.Claude_4_0_SONNET,
|
|
72
|
+
"messages": model_messages,
|
|
73
|
+
"stream": False,
|
|
74
|
+
"temperature": 0.2, # Lower temperature for accuracy and consistency
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Use get_client_with_kwargs to support context parameter overrides
|
|
78
|
+
client, complete_kwargs = get_client_with_kwargs(context, default_kwargs)
|
|
79
|
+
response = await client.chat_complete(**complete_kwargs)
|
|
80
|
+
|
|
81
|
+
# 提取分析结果
|
|
82
|
+
if response and response.choices and response.choices[0].message:
|
|
83
|
+
analysis = response.choices[0].message.content
|
|
84
|
+
if analysis:
|
|
85
|
+
return analysis.strip()
|
|
86
|
+
|
|
87
|
+
# 如果无法获取有效分析结果,返回错误信息
|
|
88
|
+
raise Exception("无法从模型获取有效的分析结果")
|
|
89
|
+
|
|
90
|
+
def build_prompt(self, issue_desc: str, fix_code: str) -> str:
|
|
91
|
+
return f"""**Please systematically analyze whether the code modification truly resolves the issue by following the steps below and return your analysis in JSON format:**
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
Please systematically analyze whether the code modifications truly fix the problem by following these steps:
|
|
95
|
+
|
|
96
|
+
## Step 1: Deep Root Cause Analysis
|
|
97
|
+
1. **Core Problem Identification**: Extract the fundamental cause of the problem from the issue description, distinguishing between symptoms and true root causes
|
|
98
|
+
2. **Problem Impact Scope**: List all affected code paths, usage scenarios, and boundary conditions
|
|
99
|
+
3. **Problem Trigger Conditions**: Clarify under what conditions this problem will be triggered, with special attention to edge cases
|
|
100
|
+
4. **Expected Behavior Definition**: Based on the problem description, clearly define the specific behavior that should be achieved after the fix
|
|
101
|
+
5. **Reverse Logic Check**: Confirm whether the fix direction is correct, avoiding going in the opposite direction of expectations
|
|
102
|
+
|
|
103
|
+
## Step 2: Fix Strategy Rationality Assessment
|
|
104
|
+
1. **Fix Type Classification**:
|
|
105
|
+
- Fundamental fix: Directly addresses the root cause
|
|
106
|
+
- Symptomatic fix: Only masks or bypasses the error phenomenon
|
|
107
|
+
- Compensatory fix: Avoids the problem through other mechanisms
|
|
108
|
+
2. **Solution Alignment**: Whether the fix solution directly targets the root cause
|
|
109
|
+
3. **Complexity Rationality**: Assess whether there is over-complication or over-engineering
|
|
110
|
+
4. **Minimal Intrusion Principle**: Whether it follows the principle of minimal changes, avoiding unnecessary modifications
|
|
111
|
+
|
|
112
|
+
## Step 3: Fix Code Implementation Quality Analysis
|
|
113
|
+
### 3.1 Coverage Assessment
|
|
114
|
+
1. **Modification Point Mapping**: Map each code modification point to specific problem scenarios
|
|
115
|
+
2. **Coverage Range Check**: Verify whether modifications cover all problem scenarios
|
|
116
|
+
3. **Missing Scenario Identification**: Identify uncovered scenarios that may have the same problem
|
|
117
|
+
|
|
118
|
+
### 3.2 Implementation Detail Analysis
|
|
119
|
+
1. **API Usage Appropriateness**: Verify whether the APIs used are the most direct and standard methods
|
|
120
|
+
2. **Code Execution Path**: Analyze whether there are unnecessary intermediate steps or roundabout implementations
|
|
121
|
+
3. **Error Handling Completeness**: Check whether all possible exception situations are correctly handled
|
|
122
|
+
4. **Performance Impact Assessment**: Analyze whether the fix introduces unnecessary performance overhead
|
|
123
|
+
|
|
124
|
+
## Step 4: Data Security and System Stability Check
|
|
125
|
+
1. **Data Security Risk**: Whether modifications may lead to data loss or inconsistency
|
|
126
|
+
2. **State Consistency**: Whether system state remains consistent after modifications
|
|
127
|
+
3. **Side Effect Assessment**: Evaluate whether modifications may introduce new problems
|
|
128
|
+
4. **Backward Compatibility**: Whether modifications maintain backward compatibility
|
|
129
|
+
5. **Rollback Safety**: Whether modifications support safe rollback
|
|
130
|
+
|
|
131
|
+
## Step 5: Design Principles and Architecture Consistency
|
|
132
|
+
1. **Architecture Alignment**: Whether modifications align with existing architecture and design patterns
|
|
133
|
+
2. **Framework Best Practices**: Whether they conform to the design philosophy and best practices of relevant frameworks
|
|
134
|
+
3. **Code Simplicity**: Whether the solution is concise, clear, easy to understand and maintain
|
|
135
|
+
4. **Maintainability Assessment**: Analyze the long-term maintainability and extensibility of the fix code
|
|
136
|
+
|
|
137
|
+
## Step 6: Test Verification Completeness
|
|
138
|
+
1. **Test Scenario Coverage**: Whether test cases cover all problem scenarios and boundary conditions
|
|
139
|
+
2. **Failed Case Analysis**: If there are test failures, analyze whether they indicate incomplete fixes
|
|
140
|
+
3. **Regression Test Verification**: Whether it's verified that modifications don't break existing functionality
|
|
141
|
+
4. **Performance Test Consideration**: Assess whether performance-related tests are needed to verify fix quality
|
|
142
|
+
|
|
143
|
+
## Step 7: Comprehensive Judgment and Recommendations
|
|
144
|
+
Based on the above analysis, provide clear conclusions:
|
|
145
|
+
|
|
146
|
+
### Required Output Fields:
|
|
147
|
+
1. **is_fixed**: true/false (partial fixes count as false)
|
|
148
|
+
2. **check_summary**: Detailed analysis summary, must include:
|
|
149
|
+
- Specific basis for fix status judgment
|
|
150
|
+
- If not fixed, clearly explain reasons for non-fix
|
|
151
|
+
- If fixed, assess implementation quality and potential risks
|
|
152
|
+
- Specific improvement suggestions or alternative solutions
|
|
153
|
+
|
|
154
|
+
## Key Analysis Focus:
|
|
155
|
+
- Whether the fundamental problem is truly solved rather than just making errors disappear
|
|
156
|
+
- Whether the fix direction is correct, avoiding directional errors
|
|
157
|
+
- Whether there's a tendency toward over-engineering
|
|
158
|
+
- Whether API usage is appropriate, avoiding roundabout or inefficient implementations
|
|
159
|
+
- Whether data security and system stability are ensured
|
|
160
|
+
- Long-term maintainability and extensibility of the code
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## **Required JSON Output Format**
|
|
164
|
+
|
|
165
|
+
You must return your analysis in the following JSON format:
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{{
|
|
169
|
+
"analysis": "The analysis results of each step",
|
|
170
|
+
"result": {{
|
|
171
|
+
"is_fixed": True,
|
|
172
|
+
"check_summary": "Summary of each step of the analysis"
|
|
173
|
+
}}
|
|
174
|
+
}}
|
|
175
|
+
```
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
**Problem Description:**
|
|
179
|
+
{issue_desc}
|
|
180
|
+
|
|
181
|
+
**Code Change:**
|
|
182
|
+
{fix_code}
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
def _parse_analysis_result(self, analysis_result: str) -> Dict[str, Any]:
|
|
186
|
+
"""解析模型分析结果
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
analysis_result: 模型返回的分析结果(应为JSON格式)
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Dict[str, Any]: 解析后的结果
|
|
193
|
+
"""
|
|
194
|
+
try:
|
|
195
|
+
# 尝试直接解析JSON,处理可能的markdown包装
|
|
196
|
+
json_content = analysis_result.strip()
|
|
197
|
+
|
|
198
|
+
# 如果响应被包装在markdown代码块中,提取JSON部分
|
|
199
|
+
if json_content.startswith('```json') or json_content.startswith('```'):
|
|
200
|
+
lines = json_content.split('\n')
|
|
201
|
+
json_lines = []
|
|
202
|
+
in_json_block = False
|
|
203
|
+
for line in lines:
|
|
204
|
+
line_stripped = line.strip()
|
|
205
|
+
if line_stripped in ['```json', '```']:
|
|
206
|
+
if not in_json_block:
|
|
207
|
+
in_json_block = True
|
|
208
|
+
else:
|
|
209
|
+
break
|
|
210
|
+
continue
|
|
211
|
+
elif in_json_block:
|
|
212
|
+
json_lines.append(line)
|
|
213
|
+
json_content = '\n'.join(json_lines)
|
|
214
|
+
|
|
215
|
+
parsed_json = json.loads(json_content)
|
|
216
|
+
|
|
217
|
+
# 验证JSON结构
|
|
218
|
+
if not isinstance(parsed_json, dict):
|
|
219
|
+
raise ValueError("返回的不是有效的JSON对象")
|
|
220
|
+
|
|
221
|
+
# 提取结果信息
|
|
222
|
+
result = parsed_json.get("result", {})
|
|
223
|
+
analysis = parsed_json.get("analysis", "")
|
|
224
|
+
|
|
225
|
+
# 如果analysis是字典类型,构建完整的分析文本
|
|
226
|
+
if isinstance(analysis, dict):
|
|
227
|
+
analysis_text = self._build_analysis_text(analysis)
|
|
228
|
+
else:
|
|
229
|
+
analysis_text = str(analysis)
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
"is_fixed": result.get("is_fixed", False),
|
|
233
|
+
"check_summary": result.get("check_summary", "未提供原因说明"),
|
|
234
|
+
"analysis": analysis_text
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
except (json.JSONDecodeError, ValueError, KeyError) as e:
|
|
238
|
+
# JSON解析失败,回退到文本解析
|
|
239
|
+
return self._fallback_text_parsing(analysis_result, str(e))
|
|
240
|
+
|
|
241
|
+
def _build_analysis_text(self, analysis: Dict[str, Any]) -> str:
|
|
242
|
+
"""从JSON分析数据构建完整的分析文本
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
analysis: 分析数据字典
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
str: 格式化的分析文本
|
|
249
|
+
"""
|
|
250
|
+
# 如果analysis是字符串,直接返回
|
|
251
|
+
if isinstance(analysis, str):
|
|
252
|
+
return analysis
|
|
253
|
+
|
|
254
|
+
# 如果analysis是字典,尝试构建结构化文本
|
|
255
|
+
if isinstance(analysis, dict):
|
|
256
|
+
# 预定义的步骤名称映射
|
|
257
|
+
step_mappings = [
|
|
258
|
+
("step1_problem_scope", "Step 1: Deep Root Cause Analysis"),
|
|
259
|
+
("step2_fix_coverage", "Step 2: Fix Strategy Rationality Assessment"),
|
|
260
|
+
("step3_test_validation", "Step 3: Fix Code Implementation Quality Analysis"),
|
|
261
|
+
("step4_logical_consistency", "Step 4: Data Security and System Stability Check"),
|
|
262
|
+
("step5_final_assessment", "Step 5: Design Principles and Architecture Consistency"),
|
|
263
|
+
("step6_test_verification", "Step 6: Test Verification Completeness"),
|
|
264
|
+
("step7_comprehensive_judgment", "Step 7: Comprehensive Judgment and Recommendations")
|
|
265
|
+
]
|
|
266
|
+
|
|
267
|
+
formatted_sections = []
|
|
268
|
+
|
|
269
|
+
# 处理结构化步骤
|
|
270
|
+
for key, title in step_mappings:
|
|
271
|
+
content = analysis.get(key, "")
|
|
272
|
+
if content:
|
|
273
|
+
formatted_sections.append(f"## {title}\n{content}")
|
|
274
|
+
|
|
275
|
+
# 如果没有找到标准步骤,处理其他键值对
|
|
276
|
+
if not formatted_sections:
|
|
277
|
+
for key, value in analysis.items():
|
|
278
|
+
if value: # 只处理非空值
|
|
279
|
+
# 格式化键名为更可读的标题
|
|
280
|
+
title = key.replace("_", " ").title()
|
|
281
|
+
formatted_sections.append(f"## {title}\n{str(value)}")
|
|
282
|
+
|
|
283
|
+
return "\n\n".join(formatted_sections) if formatted_sections else str(analysis)
|
|
284
|
+
|
|
285
|
+
# 其他类型转换为字符串
|
|
286
|
+
return str(analysis)
|
|
287
|
+
|
|
288
|
+
def _fallback_text_parsing(self, analysis_result: str, error_msg: str) -> Dict[str, Any]:
|
|
289
|
+
"""当JSON解析失败时的回退文本解析方法
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
analysis_result: 原始分析结果
|
|
293
|
+
error_msg: 错误信息
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
Dict[str, Any]: 解析后的结果
|
|
297
|
+
"""
|
|
298
|
+
# 使用原有的文本解析方法作为回退
|
|
299
|
+
is_fixed = self._extract_fix_status(analysis_result)
|
|
300
|
+
check_summary = self._extract_check_summary(analysis_result, is_fixed)
|
|
301
|
+
|
|
302
|
+
# 在分析文本中添加解析错误信息
|
|
303
|
+
analysis_with_error = f"[JSON解析失败: {error_msg}]\n\n{analysis_result}"
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
"is_fixed": is_fixed,
|
|
307
|
+
"check_summary": check_summary,
|
|
308
|
+
"analysis": analysis_with_error
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
def _extract_fix_status(self, analysis: str) -> bool:
|
|
312
|
+
"""从分析结果中提取修复状态
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
analysis: 分析结果文本
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
bool: 是否已修复
|
|
319
|
+
"""
|
|
320
|
+
analysis_lower = analysis.lower()
|
|
321
|
+
|
|
322
|
+
# 查找明确的修复状态指示
|
|
323
|
+
if "fully fixed" in analysis_lower:
|
|
324
|
+
return True
|
|
325
|
+
elif "partially fixed" in analysis_lower or "not fixed" in analysis_lower:
|
|
326
|
+
return False
|
|
327
|
+
|
|
328
|
+
# 查找是否修复的明确回答
|
|
329
|
+
if "is the issue fixed: yes" in analysis_lower:
|
|
330
|
+
return True
|
|
331
|
+
elif "is the issue fixed: no" in analysis_lower:
|
|
332
|
+
return False
|
|
333
|
+
|
|
334
|
+
# 如果没有明确指示,查找其他关键词
|
|
335
|
+
positive_indicators = ["resolved", "addressed", "fixed", "solved"]
|
|
336
|
+
negative_indicators = ["not resolved", "not addressed", "not fixed", "incomplete", "missing"]
|
|
337
|
+
|
|
338
|
+
positive_count = sum(1 for indicator in positive_indicators if indicator in analysis_lower)
|
|
339
|
+
negative_count = sum(1 for indicator in negative_indicators if indicator in analysis_lower)
|
|
340
|
+
|
|
341
|
+
# 如果负面指示更多,认为未修复
|
|
342
|
+
if negative_count > positive_count:
|
|
343
|
+
return False
|
|
344
|
+
|
|
345
|
+
# 默认情况下,如果有正面指示且没有明确的负面结论,认为已修复
|
|
346
|
+
return positive_count > 0
|
|
347
|
+
|
|
348
|
+
def _extract_check_summary(self, analysis: str, is_fixed: bool) -> str:
|
|
349
|
+
"""提取未修复的原因
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
analysis: 分析结果文本
|
|
353
|
+
is_fixed: 是否已修复
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
str: 原因说明
|
|
357
|
+
"""
|
|
358
|
+
if is_fixed:
|
|
359
|
+
return "问题已完全修复"
|
|
360
|
+
|
|
361
|
+
# 尝试提取原因
|
|
362
|
+
lines = analysis.split('\n')
|
|
363
|
+
check_summary_lines = []
|
|
364
|
+
|
|
365
|
+
# 查找包含原因的行
|
|
366
|
+
for line in lines:
|
|
367
|
+
line_lower = line.lower().strip()
|
|
368
|
+
if any(keyword in line_lower for keyword in [
|
|
369
|
+
"check_summary", "because", "however", "but", "missing",
|
|
370
|
+
"not covered", "incomplete", "still exists"
|
|
371
|
+
]):
|
|
372
|
+
check_summary_lines.append(line.strip())
|
|
373
|
+
|
|
374
|
+
if check_summary_lines:
|
|
375
|
+
return " ".join(check_summary_lines)
|
|
376
|
+
|
|
377
|
+
return "分析表明问题未完全修复,但未明确说明具体原因"
|