deepcode-hku 1.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.
- cli/__init__.py +18 -0
- cli/cli_app.py +296 -0
- cli/cli_interface.py +744 -0
- cli/cli_launcher.py +155 -0
- cli/main_cli.py +243 -0
- cli/workflows/__init__.py +11 -0
- cli/workflows/cli_workflow_adapter.py +336 -0
- deepcode.py +219 -0
- deepcode_hku-1.0.1.dist-info/METADATA +695 -0
- deepcode_hku-1.0.1.dist-info/RECORD +44 -0
- deepcode_hku-1.0.1.dist-info/WHEEL +5 -0
- deepcode_hku-1.0.1.dist-info/entry_points.txt +2 -0
- deepcode_hku-1.0.1.dist-info/licenses/LICENSE +21 -0
- deepcode_hku-1.0.1.dist-info/top_level.txt +6 -0
- tools/__init__.py +0 -0
- tools/code_implementation_server.py +1045 -0
- tools/code_indexer.py +1657 -0
- tools/code_reference_indexer.py +486 -0
- tools/command_executor.py +324 -0
- tools/git_command.py +356 -0
- tools/pdf_converter.py +640 -0
- tools/pdf_downloader.py +1370 -0
- tools/pdf_utils.py +52 -0
- ui/__init__.py +43 -0
- ui/app.py +13 -0
- ui/components.py +1450 -0
- ui/handlers.py +773 -0
- ui/layout.py +106 -0
- ui/streamlit_app.py +38 -0
- ui/styles.py +2116 -0
- utils/__init__.py +17 -0
- utils/cli_interface.py +459 -0
- utils/dialogue_logger.py +671 -0
- utils/file_processor.py +426 -0
- utils/simple_llm_logger.py +198 -0
- workflows/__init__.py +31 -0
- workflows/agent_orchestration_engine.py +1371 -0
- workflows/agents/__init__.py +13 -0
- workflows/agents/code_implementation_agent.py +1093 -0
- workflows/agents/memory_agent_concise.py +923 -0
- workflows/agents/memory_agent_concise_index.py +935 -0
- workflows/code_implementation_workflow.py +924 -0
- workflows/code_implementation_workflow_index.py +931 -0
- workflows/codebase_index_workflow.py +726 -0
|
@@ -0,0 +1,1093 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Code Implementation Agent for File-by-File Development
|
|
3
|
+
文件逐个开发的代码实现代理
|
|
4
|
+
|
|
5
|
+
Handles systematic code implementation with progress tracking and
|
|
6
|
+
memory optimization for long-running development sessions.
|
|
7
|
+
处理系统性代码实现,具有进度跟踪和长时间开发会话的内存优化。
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import time
|
|
12
|
+
import logging
|
|
13
|
+
from typing import Dict, Any, List, Optional
|
|
14
|
+
|
|
15
|
+
# Import tiktoken for token calculation
|
|
16
|
+
try:
|
|
17
|
+
import tiktoken
|
|
18
|
+
|
|
19
|
+
TIKTOKEN_AVAILABLE = True
|
|
20
|
+
except ImportError:
|
|
21
|
+
TIKTOKEN_AVAILABLE = False
|
|
22
|
+
|
|
23
|
+
# Import prompts from code_prompts
|
|
24
|
+
import sys
|
|
25
|
+
import os
|
|
26
|
+
|
|
27
|
+
sys.path.insert(
|
|
28
|
+
0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
29
|
+
)
|
|
30
|
+
from prompts.code_prompts import (
|
|
31
|
+
GENERAL_CODE_IMPLEMENTATION_SYSTEM_PROMPT,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CodeImplementationAgent:
|
|
36
|
+
"""
|
|
37
|
+
Code Implementation Agent for systematic file-by-file development
|
|
38
|
+
用于系统性文件逐个开发的代码实现代理
|
|
39
|
+
|
|
40
|
+
Responsibilities / 职责:
|
|
41
|
+
- Track file implementation progress / 跟踪文件实现进度
|
|
42
|
+
- Execute MCP tool calls for code generation / 执行MCP工具调用进行代码生成
|
|
43
|
+
- Monitor implementation status / 监控实现状态
|
|
44
|
+
- Coordinate with Summary Agent for memory optimization / 与总结代理协调进行内存优化
|
|
45
|
+
- Calculate token usage for context management / 计算token使用量用于上下文管理
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
mcp_agent,
|
|
51
|
+
logger: Optional[logging.Logger] = None,
|
|
52
|
+
enable_read_tools: bool = True,
|
|
53
|
+
):
|
|
54
|
+
"""
|
|
55
|
+
Initialize Code Implementation Agent
|
|
56
|
+
初始化代码实现代理
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
mcp_agent: MCP agent instance for tool calls
|
|
60
|
+
logger: Logger instance for tracking operations
|
|
61
|
+
enable_read_tools: Whether to enable read_file and read_code_mem tools (default: True)
|
|
62
|
+
"""
|
|
63
|
+
self.mcp_agent = mcp_agent
|
|
64
|
+
self.logger = logger or self._create_default_logger()
|
|
65
|
+
self.enable_read_tools = enable_read_tools # Control read tools execution
|
|
66
|
+
|
|
67
|
+
self.implementation_summary = {
|
|
68
|
+
"completed_files": [],
|
|
69
|
+
"technical_decisions": [],
|
|
70
|
+
"important_constraints": [],
|
|
71
|
+
"architecture_notes": [],
|
|
72
|
+
"dependency_analysis": [], # Track dependency analysis and file reads
|
|
73
|
+
}
|
|
74
|
+
self.files_implemented_count = 0
|
|
75
|
+
self.implemented_files_set = set() # Track unique file paths to avoid duplicate counting / 跟踪唯一文件路径以避免重复计数
|
|
76
|
+
self.files_read_for_dependencies = (
|
|
77
|
+
set()
|
|
78
|
+
) # Track files read for dependency analysis / 跟踪为依赖分析而读取的文件
|
|
79
|
+
self.last_summary_file_count = 0 # Track the file count when last summary was triggered / 跟踪上次触发总结时的文件数
|
|
80
|
+
|
|
81
|
+
# Token calculation settings / Token计算设置
|
|
82
|
+
self.max_context_tokens = 200000 # Default max context tokens for Claude-3.5-Sonnet / Claude-3.5-Sonnet的默认最大上下文tokens
|
|
83
|
+
self.token_buffer = (
|
|
84
|
+
10000 # Safety buffer before reaching max / 达到最大值前的安全缓冲区
|
|
85
|
+
)
|
|
86
|
+
self.summary_trigger_tokens = (
|
|
87
|
+
self.max_context_tokens - self.token_buffer
|
|
88
|
+
) # Trigger summary when approaching limit / 接近限制时触发总结
|
|
89
|
+
self.last_summary_token_count = 0 # Track token count when last summary was triggered / 跟踪上次触发总结时的token数
|
|
90
|
+
|
|
91
|
+
# Initialize tokenizer / 初始化tokenizer
|
|
92
|
+
if TIKTOKEN_AVAILABLE:
|
|
93
|
+
try:
|
|
94
|
+
# Use Claude-3 tokenizer (approximation with OpenAI's o200k_base) / 使用Claude-3 tokenizer(用OpenAI的o200k_base近似)
|
|
95
|
+
self.tokenizer = tiktoken.get_encoding("o200k_base")
|
|
96
|
+
self.logger.info("Token calculation enabled with o200k_base encoding")
|
|
97
|
+
except Exception as e:
|
|
98
|
+
self.tokenizer = None
|
|
99
|
+
self.logger.warning(f"Failed to initialize tokenizer: {e}")
|
|
100
|
+
else:
|
|
101
|
+
self.tokenizer = None
|
|
102
|
+
self.logger.warning(
|
|
103
|
+
"tiktoken not available, token-based summary triggering disabled"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Analysis loop detection / 分析循环检测
|
|
107
|
+
self.recent_tool_calls = [] # Track recent tool calls to detect analysis loops / 跟踪最近的工具调用以检测分析循环
|
|
108
|
+
self.max_read_without_write = 5 # Max read_file calls without write_file / 没有write_file的最大read_file调用次数
|
|
109
|
+
|
|
110
|
+
# Memory agent integration / 内存代理集成
|
|
111
|
+
self.memory_agent = None # Will be set externally / 将从外部设置
|
|
112
|
+
self.llm_client = None # Will be set externally / 将从外部设置
|
|
113
|
+
self.llm_client_type = None # Will be set externally / 将从外部设置
|
|
114
|
+
|
|
115
|
+
# Log read tools configuration
|
|
116
|
+
read_tools_status = "ENABLED" if self.enable_read_tools else "DISABLED"
|
|
117
|
+
self.logger.info(
|
|
118
|
+
f"🔧 Code Implementation Agent initialized - Read tools: {read_tools_status}"
|
|
119
|
+
)
|
|
120
|
+
if not self.enable_read_tools:
|
|
121
|
+
self.logger.info(
|
|
122
|
+
"🚫 Testing mode: read_file and read_code_mem will be skipped when called"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def _create_default_logger(self) -> logging.Logger:
|
|
126
|
+
"""Create default logger if none provided / 如果未提供则创建默认日志记录器"""
|
|
127
|
+
logger = logging.getLogger(f"{__name__}.CodeImplementationAgent")
|
|
128
|
+
# Don't add handlers to child loggers - let them propagate to root
|
|
129
|
+
logger.setLevel(logging.INFO)
|
|
130
|
+
return logger
|
|
131
|
+
|
|
132
|
+
def get_system_prompt(self) -> str:
|
|
133
|
+
"""
|
|
134
|
+
Get the system prompt for code implementation
|
|
135
|
+
获取代码实现的系统提示词
|
|
136
|
+
"""
|
|
137
|
+
return GENERAL_CODE_IMPLEMENTATION_SYSTEM_PROMPT
|
|
138
|
+
|
|
139
|
+
def set_memory_agent(self, memory_agent, llm_client=None, llm_client_type=None):
|
|
140
|
+
"""
|
|
141
|
+
Set memory agent for code summary generation
|
|
142
|
+
设置内存代理用于代码总结生成
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
memory_agent: Memory agent instance
|
|
146
|
+
llm_client: LLM client for summary generation
|
|
147
|
+
llm_client_type: Type of LLM client ("anthropic" or "openai")
|
|
148
|
+
"""
|
|
149
|
+
self.memory_agent = memory_agent
|
|
150
|
+
self.llm_client = llm_client
|
|
151
|
+
self.llm_client_type = llm_client_type
|
|
152
|
+
self.logger.info("Memory agent integration configured")
|
|
153
|
+
|
|
154
|
+
async def execute_tool_calls(self, tool_calls: List[Dict]) -> List[Dict]:
|
|
155
|
+
"""
|
|
156
|
+
Execute MCP tool calls and track implementation progress
|
|
157
|
+
执行MCP工具调用并跟踪实现进度
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
tool_calls: List of tool calls to execute
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
List of tool execution results
|
|
164
|
+
"""
|
|
165
|
+
results = []
|
|
166
|
+
|
|
167
|
+
for tool_call in tool_calls:
|
|
168
|
+
tool_name = tool_call["name"]
|
|
169
|
+
tool_input = tool_call["input"]
|
|
170
|
+
|
|
171
|
+
self.logger.info(f"Executing MCP tool: {tool_name}")
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
# Check if read tools are disabled
|
|
175
|
+
if not self.enable_read_tools and tool_name in [
|
|
176
|
+
"read_file",
|
|
177
|
+
"read_code_mem",
|
|
178
|
+
]:
|
|
179
|
+
# self.logger.info(f"🚫 SKIPPING {tool_name} - Read tools disabled for testing")
|
|
180
|
+
# Return a mock result indicating the tool was skipped
|
|
181
|
+
mock_result = json.dumps(
|
|
182
|
+
{
|
|
183
|
+
"status": "skipped",
|
|
184
|
+
"message": f"{tool_name} tool disabled for testing",
|
|
185
|
+
"tool_disabled": True,
|
|
186
|
+
"original_input": tool_input,
|
|
187
|
+
},
|
|
188
|
+
ensure_ascii=False,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
results.append(
|
|
192
|
+
{
|
|
193
|
+
"tool_id": tool_call["id"],
|
|
194
|
+
"tool_name": tool_name,
|
|
195
|
+
"result": mock_result,
|
|
196
|
+
}
|
|
197
|
+
)
|
|
198
|
+
continue
|
|
199
|
+
|
|
200
|
+
# read_code_mem is now a proper MCP tool, no special handling needed
|
|
201
|
+
|
|
202
|
+
# INTERCEPT read_file calls - redirect to read_code_mem first if memory agent is available
|
|
203
|
+
if tool_name == "read_file":
|
|
204
|
+
file_path = tool_call["input"].get("file_path", "unknown")
|
|
205
|
+
self.logger.info(f"🔍 READ_FILE CALL DETECTED: {file_path}")
|
|
206
|
+
self.logger.info(
|
|
207
|
+
f"📊 Files implemented count: {self.files_implemented_count}"
|
|
208
|
+
)
|
|
209
|
+
self.logger.info(
|
|
210
|
+
f"🧠 Memory agent available: {self.memory_agent is not None}"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Enable optimization if memory agent is available (more aggressive approach)
|
|
214
|
+
if self.memory_agent is not None:
|
|
215
|
+
self.logger.info(
|
|
216
|
+
f"🔄 INTERCEPTING read_file call for {file_path} (memory agent available)"
|
|
217
|
+
)
|
|
218
|
+
result = await self._handle_read_file_with_memory_optimization(
|
|
219
|
+
tool_call
|
|
220
|
+
)
|
|
221
|
+
results.append(result)
|
|
222
|
+
continue
|
|
223
|
+
else:
|
|
224
|
+
self.logger.info(
|
|
225
|
+
"📁 NO INTERCEPTION: no memory agent available"
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
if self.mcp_agent:
|
|
229
|
+
# Execute tool call through MCP protocol / 通过MCP协议执行工具调用
|
|
230
|
+
result = await self.mcp_agent.call_tool(tool_name, tool_input)
|
|
231
|
+
|
|
232
|
+
# Track file implementation progress / 跟踪文件实现进度
|
|
233
|
+
if tool_name == "write_file":
|
|
234
|
+
await self._track_file_implementation_with_summary(
|
|
235
|
+
tool_call, result
|
|
236
|
+
)
|
|
237
|
+
elif tool_name == "read_file":
|
|
238
|
+
self._track_dependency_analysis(tool_call, result)
|
|
239
|
+
|
|
240
|
+
# Track tool calls for analysis loop detection / 跟踪工具调用以检测分析循环
|
|
241
|
+
self._track_tool_call_for_loop_detection(tool_name)
|
|
242
|
+
|
|
243
|
+
results.append(
|
|
244
|
+
{
|
|
245
|
+
"tool_id": tool_call["id"],
|
|
246
|
+
"tool_name": tool_name,
|
|
247
|
+
"result": result,
|
|
248
|
+
}
|
|
249
|
+
)
|
|
250
|
+
else:
|
|
251
|
+
results.append(
|
|
252
|
+
{
|
|
253
|
+
"tool_id": tool_call["id"],
|
|
254
|
+
"tool_name": tool_name,
|
|
255
|
+
"result": json.dumps(
|
|
256
|
+
{
|
|
257
|
+
"status": "error",
|
|
258
|
+
"message": "MCP agent not initialized",
|
|
259
|
+
},
|
|
260
|
+
ensure_ascii=False,
|
|
261
|
+
),
|
|
262
|
+
}
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
except Exception as e:
|
|
266
|
+
self.logger.error(f"MCP tool execution failed: {e}")
|
|
267
|
+
results.append(
|
|
268
|
+
{
|
|
269
|
+
"tool_id": tool_call["id"],
|
|
270
|
+
"tool_name": tool_name,
|
|
271
|
+
"result": json.dumps(
|
|
272
|
+
{"status": "error", "message": str(e)}, ensure_ascii=False
|
|
273
|
+
),
|
|
274
|
+
}
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
return results
|
|
278
|
+
|
|
279
|
+
# _handle_read_code_mem method removed - read_code_mem is now a proper MCP tool
|
|
280
|
+
|
|
281
|
+
async def _handle_read_file_with_memory_optimization(self, tool_call: Dict) -> Dict:
|
|
282
|
+
"""
|
|
283
|
+
Intercept read_file calls and redirect to read_code_mem if a summary exists.
|
|
284
|
+
This prevents unnecessary file reads if the summary is already available.
|
|
285
|
+
拦截read_file调用,如果存在摘要则重定向到read_code_mem。
|
|
286
|
+
这可以防止在摘要已经存在时进行不必要的文件读取。
|
|
287
|
+
"""
|
|
288
|
+
file_path = tool_call["input"].get("file_path")
|
|
289
|
+
if not file_path:
|
|
290
|
+
return {
|
|
291
|
+
"tool_id": tool_call["id"],
|
|
292
|
+
"tool_name": "read_file",
|
|
293
|
+
"result": json.dumps(
|
|
294
|
+
{"status": "error", "message": "file_path parameter is required"},
|
|
295
|
+
ensure_ascii=False,
|
|
296
|
+
),
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
# Check if a summary exists for this file using read_code_mem MCP tool
|
|
300
|
+
should_use_summary = False
|
|
301
|
+
if self.memory_agent and self.mcp_agent:
|
|
302
|
+
try:
|
|
303
|
+
# Use read_code_mem MCP tool to check if summary exists
|
|
304
|
+
read_code_mem_result = await self.mcp_agent.call_tool(
|
|
305
|
+
"read_code_mem", {"file_path": file_path}
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
# Parse the result to check if summary was found
|
|
309
|
+
import json
|
|
310
|
+
|
|
311
|
+
if isinstance(read_code_mem_result, str):
|
|
312
|
+
try:
|
|
313
|
+
result_data = json.loads(read_code_mem_result)
|
|
314
|
+
should_use_summary = (
|
|
315
|
+
result_data.get("status") == "summary_found"
|
|
316
|
+
)
|
|
317
|
+
except json.JSONDecodeError:
|
|
318
|
+
should_use_summary = False
|
|
319
|
+
except Exception as e:
|
|
320
|
+
self.logger.debug(f"read_code_mem check failed for {file_path}: {e}")
|
|
321
|
+
should_use_summary = False
|
|
322
|
+
|
|
323
|
+
if should_use_summary:
|
|
324
|
+
self.logger.info(f"🔄 READ_FILE INTERCEPTED: Using summary for {file_path}")
|
|
325
|
+
|
|
326
|
+
# Use the MCP agent to call read_code_mem tool
|
|
327
|
+
if self.mcp_agent:
|
|
328
|
+
result = await self.mcp_agent.call_tool(
|
|
329
|
+
"read_code_mem", {"file_path": file_path}
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# Modify the result to indicate it was originally a read_file call
|
|
333
|
+
import json
|
|
334
|
+
|
|
335
|
+
try:
|
|
336
|
+
result_data = (
|
|
337
|
+
json.loads(result) if isinstance(result, str) else result
|
|
338
|
+
)
|
|
339
|
+
if isinstance(result_data, dict):
|
|
340
|
+
result_data["original_tool"] = "read_file"
|
|
341
|
+
result_data["optimization"] = "redirected_to_read_code_mem"
|
|
342
|
+
final_result = json.dumps(result_data, ensure_ascii=False)
|
|
343
|
+
else:
|
|
344
|
+
final_result = result
|
|
345
|
+
except (json.JSONDecodeError, TypeError):
|
|
346
|
+
final_result = result
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
"tool_id": tool_call["id"],
|
|
350
|
+
"tool_name": "read_file", # Keep original tool name for tracking
|
|
351
|
+
"result": final_result,
|
|
352
|
+
}
|
|
353
|
+
else:
|
|
354
|
+
self.logger.warning(
|
|
355
|
+
"MCP agent not available for read_code_mem optimization"
|
|
356
|
+
)
|
|
357
|
+
else:
|
|
358
|
+
self.logger.info(
|
|
359
|
+
f"📁 READ_FILE: No summary for {file_path}, using actual file"
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
# Execute the original read_file call
|
|
363
|
+
if self.mcp_agent:
|
|
364
|
+
result = await self.mcp_agent.call_tool("read_file", tool_call["input"])
|
|
365
|
+
|
|
366
|
+
# Track dependency analysis for the actual file read
|
|
367
|
+
self._track_dependency_analysis(tool_call, result)
|
|
368
|
+
|
|
369
|
+
# Track tool calls for analysis loop detection
|
|
370
|
+
self._track_tool_call_for_loop_detection("read_file")
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
"tool_id": tool_call["id"],
|
|
374
|
+
"tool_name": "read_file",
|
|
375
|
+
"result": result,
|
|
376
|
+
}
|
|
377
|
+
else:
|
|
378
|
+
return {
|
|
379
|
+
"tool_id": tool_call["id"],
|
|
380
|
+
"tool_name": "read_file",
|
|
381
|
+
"result": json.dumps(
|
|
382
|
+
{"status": "error", "message": "MCP agent not initialized"},
|
|
383
|
+
ensure_ascii=False,
|
|
384
|
+
),
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
async def _track_file_implementation_with_summary(
|
|
388
|
+
self, tool_call: Dict, result: Any
|
|
389
|
+
):
|
|
390
|
+
"""
|
|
391
|
+
Track file implementation and create code summary
|
|
392
|
+
跟踪文件实现并创建代码总结
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
tool_call: The write_file tool call
|
|
396
|
+
result: Result of the tool execution
|
|
397
|
+
"""
|
|
398
|
+
# First do the regular tracking
|
|
399
|
+
self._track_file_implementation(tool_call, result)
|
|
400
|
+
|
|
401
|
+
# Then create and save code summary if memory agent is available
|
|
402
|
+
if self.memory_agent and self.llm_client and self.llm_client_type:
|
|
403
|
+
try:
|
|
404
|
+
file_path = tool_call["input"].get("file_path")
|
|
405
|
+
file_content = tool_call["input"].get("content", "")
|
|
406
|
+
|
|
407
|
+
if file_path and file_content:
|
|
408
|
+
# Create code implementation summary
|
|
409
|
+
summary = await self.memory_agent.create_code_implementation_summary(
|
|
410
|
+
self.llm_client,
|
|
411
|
+
self.llm_client_type,
|
|
412
|
+
file_path,
|
|
413
|
+
file_content,
|
|
414
|
+
self.get_files_implemented_count(), # Pass the current file count
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
self.logger.info(
|
|
418
|
+
f"Created code summary for implemented file: {file_path}, summary: {summary[:100]}..."
|
|
419
|
+
)
|
|
420
|
+
else:
|
|
421
|
+
self.logger.warning(
|
|
422
|
+
"Missing file path or content for summary generation"
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
except Exception as e:
|
|
426
|
+
self.logger.error(f"Failed to create code summary: {e}")
|
|
427
|
+
|
|
428
|
+
def _track_file_implementation(self, tool_call: Dict, result: Any):
|
|
429
|
+
"""
|
|
430
|
+
Track file implementation progress
|
|
431
|
+
跟踪文件实现进度
|
|
432
|
+
"""
|
|
433
|
+
try:
|
|
434
|
+
# Handle different result types from MCP / 处理MCP的不同结果类型
|
|
435
|
+
result_data = None
|
|
436
|
+
|
|
437
|
+
# Check if result is a CallToolResult object / 检查结果是否为CallToolResult对象
|
|
438
|
+
if hasattr(result, "content"):
|
|
439
|
+
# Extract content from CallToolResult / 从CallToolResult提取内容
|
|
440
|
+
if hasattr(result.content, "text"):
|
|
441
|
+
result_content = result.content.text
|
|
442
|
+
else:
|
|
443
|
+
result_content = str(result.content)
|
|
444
|
+
|
|
445
|
+
# Try to parse as JSON / 尝试解析为JSON
|
|
446
|
+
try:
|
|
447
|
+
result_data = json.loads(result_content)
|
|
448
|
+
except json.JSONDecodeError:
|
|
449
|
+
# If not JSON, create a structure / 如果不是JSON,创建一个结构
|
|
450
|
+
result_data = {
|
|
451
|
+
"status": "success",
|
|
452
|
+
"file_path": tool_call["input"].get("file_path", "unknown"),
|
|
453
|
+
}
|
|
454
|
+
elif isinstance(result, str):
|
|
455
|
+
# Try to parse string result / 尝试解析字符串结果
|
|
456
|
+
try:
|
|
457
|
+
result_data = json.loads(result)
|
|
458
|
+
except json.JSONDecodeError:
|
|
459
|
+
result_data = {
|
|
460
|
+
"status": "success",
|
|
461
|
+
"file_path": tool_call["input"].get("file_path", "unknown"),
|
|
462
|
+
}
|
|
463
|
+
elif isinstance(result, dict):
|
|
464
|
+
# Direct dictionary result / 直接字典结果
|
|
465
|
+
result_data = result
|
|
466
|
+
else:
|
|
467
|
+
# Fallback: assume success and extract file path from input / 后备方案:假设成功并从输入中提取文件路径
|
|
468
|
+
result_data = {
|
|
469
|
+
"status": "success",
|
|
470
|
+
"file_path": tool_call["input"].get("file_path", "unknown"),
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
# Extract file path for tracking / 提取文件路径用于跟踪
|
|
474
|
+
file_path = None
|
|
475
|
+
if result_data and result_data.get("status") == "success":
|
|
476
|
+
file_path = result_data.get(
|
|
477
|
+
"file_path", tool_call["input"].get("file_path", "unknown")
|
|
478
|
+
)
|
|
479
|
+
else:
|
|
480
|
+
file_path = tool_call["input"].get("file_path")
|
|
481
|
+
|
|
482
|
+
# Only count unique files, not repeated tool calls on same file / 只计数唯一文件,不重复计数同一文件的工具调用
|
|
483
|
+
if file_path and file_path not in self.implemented_files_set:
|
|
484
|
+
# This is a new file implementation / 这是一个新的文件实现
|
|
485
|
+
self.implemented_files_set.add(file_path)
|
|
486
|
+
self.files_implemented_count += 1
|
|
487
|
+
# self.logger.info(f"New file implementation tracked: count={self.files_implemented_count}, file={file_path}")
|
|
488
|
+
# print(f"New file implementation tracked: count={self.files_implemented_count}, file={file_path}")
|
|
489
|
+
|
|
490
|
+
# Add to completed files list / 添加到已完成文件列表
|
|
491
|
+
self.implementation_summary["completed_files"].append(
|
|
492
|
+
{
|
|
493
|
+
"file": file_path,
|
|
494
|
+
"iteration": self.files_implemented_count,
|
|
495
|
+
"timestamp": time.time(),
|
|
496
|
+
"size": result_data.get("size", 0) if result_data else 0,
|
|
497
|
+
}
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
# self.logger.info(
|
|
501
|
+
# f"New file implementation tracked: count={self.files_implemented_count}, file={file_path}"
|
|
502
|
+
# )
|
|
503
|
+
# print(f"📝 NEW FILE IMPLEMENTED: count={self.files_implemented_count}, file={file_path}")
|
|
504
|
+
# print(f"🔧 OPTIMIZATION NOW ENABLED: files_implemented_count > 0 = {self.files_implemented_count > 0}")
|
|
505
|
+
elif file_path and file_path in self.implemented_files_set:
|
|
506
|
+
# This file was already implemented (duplicate tool call) / 这个文件已经被实现过了(重复工具调用)
|
|
507
|
+
self.logger.debug(
|
|
508
|
+
f"File already tracked, skipping duplicate count: {file_path}"
|
|
509
|
+
)
|
|
510
|
+
else:
|
|
511
|
+
# No valid file path found / 没有找到有效的文件路径
|
|
512
|
+
self.logger.warning("No valid file path found for tracking")
|
|
513
|
+
|
|
514
|
+
except Exception as e:
|
|
515
|
+
self.logger.warning(f"Failed to track file implementation: {e}")
|
|
516
|
+
# Even if tracking fails, try to count based on tool input (but check for duplicates) / 即使跟踪失败,也尝试根据工具输入计数(但检查重复)
|
|
517
|
+
|
|
518
|
+
file_path = tool_call["input"].get("file_path")
|
|
519
|
+
if file_path and file_path not in self.implemented_files_set:
|
|
520
|
+
self.implemented_files_set.add(file_path)
|
|
521
|
+
self.files_implemented_count += 1
|
|
522
|
+
self.logger.info(
|
|
523
|
+
f"File implementation counted (emergency fallback): count={self.files_implemented_count}, file={file_path}"
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
def _track_dependency_analysis(self, tool_call: Dict, result: Any):
|
|
527
|
+
"""
|
|
528
|
+
Track dependency analysis through read_file calls
|
|
529
|
+
跟踪通过read_file调用进行的依赖分析
|
|
530
|
+
"""
|
|
531
|
+
try:
|
|
532
|
+
file_path = tool_call["input"].get("file_path")
|
|
533
|
+
if file_path:
|
|
534
|
+
# Track unique files read for dependency analysis / 跟踪为依赖分析而读取的唯一文件
|
|
535
|
+
if file_path not in self.files_read_for_dependencies:
|
|
536
|
+
self.files_read_for_dependencies.add(file_path)
|
|
537
|
+
|
|
538
|
+
# Add to dependency analysis summary / 添加到依赖分析总结
|
|
539
|
+
self.implementation_summary["dependency_analysis"].append(
|
|
540
|
+
{
|
|
541
|
+
"file_read": file_path,
|
|
542
|
+
"timestamp": time.time(),
|
|
543
|
+
"purpose": "dependency_analysis",
|
|
544
|
+
}
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
self.logger.info(
|
|
548
|
+
f"Dependency analysis tracked: file_read={file_path}"
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
except Exception as e:
|
|
552
|
+
self.logger.warning(f"Failed to track dependency analysis: {e}")
|
|
553
|
+
|
|
554
|
+
def calculate_messages_token_count(self, messages: List[Dict]) -> int:
|
|
555
|
+
"""
|
|
556
|
+
Calculate total token count for a list of messages
|
|
557
|
+
计算消息列表的总token数
|
|
558
|
+
|
|
559
|
+
Args:
|
|
560
|
+
messages: List of chat messages with 'role' and 'content' keys
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
Total token count
|
|
564
|
+
"""
|
|
565
|
+
if not self.tokenizer:
|
|
566
|
+
# Fallback: rough estimation based on character count / 回退:基于字符数的粗略估计
|
|
567
|
+
total_chars = sum(len(str(msg.get("content", ""))) for msg in messages)
|
|
568
|
+
# Rough approximation: 1 token ≈ 4 characters / 粗略近似:1个token ≈ 4个字符
|
|
569
|
+
return total_chars // 4
|
|
570
|
+
|
|
571
|
+
try:
|
|
572
|
+
total_tokens = 0
|
|
573
|
+
for message in messages:
|
|
574
|
+
content = str(message.get("content", ""))
|
|
575
|
+
role = message.get("role", "")
|
|
576
|
+
|
|
577
|
+
# Count tokens for content / 计算内容的token数
|
|
578
|
+
if content:
|
|
579
|
+
content_tokens = len(
|
|
580
|
+
self.tokenizer.encode(content, disallowed_special=())
|
|
581
|
+
)
|
|
582
|
+
total_tokens += content_tokens
|
|
583
|
+
|
|
584
|
+
# Add tokens for role and message structure / 为角色和消息结构添加token
|
|
585
|
+
role_tokens = len(self.tokenizer.encode(role, disallowed_special=()))
|
|
586
|
+
total_tokens += (
|
|
587
|
+
role_tokens + 4
|
|
588
|
+
) # Extra tokens for message formatting / 消息格式化的额外token
|
|
589
|
+
|
|
590
|
+
return total_tokens
|
|
591
|
+
|
|
592
|
+
except Exception as e:
|
|
593
|
+
self.logger.warning(f"Token calculation failed: {e}")
|
|
594
|
+
# Fallback estimation / 回退估计
|
|
595
|
+
total_chars = sum(len(str(msg.get("content", ""))) for msg in messages)
|
|
596
|
+
return total_chars // 4
|
|
597
|
+
|
|
598
|
+
def should_trigger_summary_by_tokens(self, messages: List[Dict]) -> bool:
|
|
599
|
+
"""
|
|
600
|
+
Check if summary should be triggered based on token count
|
|
601
|
+
根据token数检查是否应触发总结
|
|
602
|
+
|
|
603
|
+
Args:
|
|
604
|
+
messages: Current conversation messages
|
|
605
|
+
|
|
606
|
+
Returns:
|
|
607
|
+
True if summary should be triggered based on token count
|
|
608
|
+
"""
|
|
609
|
+
if not messages:
|
|
610
|
+
return False
|
|
611
|
+
|
|
612
|
+
# Calculate current token count / 计算当前token数
|
|
613
|
+
current_token_count = self.calculate_messages_token_count(messages)
|
|
614
|
+
|
|
615
|
+
# Check if we should trigger summary / 检查是否应触发总结
|
|
616
|
+
should_trigger = (
|
|
617
|
+
current_token_count > self.summary_trigger_tokens
|
|
618
|
+
and current_token_count
|
|
619
|
+
> self.last_summary_token_count
|
|
620
|
+
+ 10000 # Minimum 10k tokens between summaries / 总结间最少10k tokens
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
if should_trigger:
|
|
624
|
+
self.logger.info(
|
|
625
|
+
f"Token-based summary trigger: current={current_token_count:,}, "
|
|
626
|
+
f"threshold={self.summary_trigger_tokens:,}, "
|
|
627
|
+
f"last_summary={self.last_summary_token_count:,}"
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
return should_trigger
|
|
631
|
+
|
|
632
|
+
def should_trigger_summary(
|
|
633
|
+
self, summary_trigger: int = 5, messages: List[Dict] = None
|
|
634
|
+
) -> bool:
|
|
635
|
+
"""
|
|
636
|
+
Check if summary should be triggered based on token count (preferred) or file count (fallback)
|
|
637
|
+
根据token数(首选)或文件数(回退)检查是否应触发总结
|
|
638
|
+
|
|
639
|
+
Args:
|
|
640
|
+
summary_trigger: Number of files after which to trigger summary (fallback)
|
|
641
|
+
messages: Current conversation messages for token calculation
|
|
642
|
+
|
|
643
|
+
Returns:
|
|
644
|
+
True if summary should be triggered
|
|
645
|
+
"""
|
|
646
|
+
# Primary: Token-based triggering / 主要:基于token的触发
|
|
647
|
+
if messages and self.tokenizer:
|
|
648
|
+
return self.should_trigger_summary_by_tokens(messages)
|
|
649
|
+
|
|
650
|
+
# Fallback: File-based triggering (original logic) / 回退:基于文件的触发(原始逻辑)
|
|
651
|
+
self.logger.info("Using fallback file-based summary triggering")
|
|
652
|
+
should_trigger = (
|
|
653
|
+
self.files_implemented_count > 0
|
|
654
|
+
and self.files_implemented_count % summary_trigger == 0
|
|
655
|
+
and self.files_implemented_count > self.last_summary_file_count
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
return should_trigger
|
|
659
|
+
|
|
660
|
+
def mark_summary_triggered(self, messages: List[Dict] = None):
|
|
661
|
+
"""
|
|
662
|
+
Mark that summary has been triggered for current state
|
|
663
|
+
标记当前状态的总结已被触发
|
|
664
|
+
|
|
665
|
+
Args:
|
|
666
|
+
messages: Current conversation messages for token tracking
|
|
667
|
+
"""
|
|
668
|
+
# Update file-based tracking / 更新基于文件的跟踪
|
|
669
|
+
self.last_summary_file_count = self.files_implemented_count
|
|
670
|
+
|
|
671
|
+
# Update token-based tracking / 更新基于token的跟踪
|
|
672
|
+
if messages and self.tokenizer:
|
|
673
|
+
self.last_summary_token_count = self.calculate_messages_token_count(
|
|
674
|
+
messages
|
|
675
|
+
)
|
|
676
|
+
self.logger.info(
|
|
677
|
+
f"Summary marked as triggered - file_count: {self.files_implemented_count}, "
|
|
678
|
+
f"token_count: {self.last_summary_token_count:,}"
|
|
679
|
+
)
|
|
680
|
+
else:
|
|
681
|
+
self.logger.info(
|
|
682
|
+
f"Summary marked as triggered for file count: {self.files_implemented_count}"
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
def get_implementation_summary(self) -> Dict[str, Any]:
|
|
686
|
+
"""
|
|
687
|
+
Get current implementation summary
|
|
688
|
+
获取当前实现总结
|
|
689
|
+
"""
|
|
690
|
+
return self.implementation_summary.copy()
|
|
691
|
+
|
|
692
|
+
def get_files_implemented_count(self) -> int:
|
|
693
|
+
"""
|
|
694
|
+
Get the number of files implemented so far
|
|
695
|
+
获取到目前为止实现的文件数量
|
|
696
|
+
"""
|
|
697
|
+
return self.files_implemented_count
|
|
698
|
+
|
|
699
|
+
def get_read_tools_status(self) -> Dict[str, Any]:
|
|
700
|
+
"""
|
|
701
|
+
Get read tools configuration status
|
|
702
|
+
获取读取工具配置状态
|
|
703
|
+
|
|
704
|
+
Returns:
|
|
705
|
+
Dictionary with read tools status information
|
|
706
|
+
"""
|
|
707
|
+
return {
|
|
708
|
+
"read_tools_enabled": self.enable_read_tools,
|
|
709
|
+
"status": "ENABLED" if self.enable_read_tools else "DISABLED",
|
|
710
|
+
"tools_affected": ["read_file", "read_code_mem"],
|
|
711
|
+
"description": "Read tools configuration for testing purposes",
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
def add_technical_decision(self, decision: str, context: str = ""):
|
|
715
|
+
"""
|
|
716
|
+
Add a technical decision to the implementation summary
|
|
717
|
+
向实现总结添加技术决策
|
|
718
|
+
|
|
719
|
+
Args:
|
|
720
|
+
decision: Description of the technical decision
|
|
721
|
+
context: Additional context for the decision
|
|
722
|
+
"""
|
|
723
|
+
self.implementation_summary["technical_decisions"].append(
|
|
724
|
+
{"decision": decision, "context": context, "timestamp": time.time()}
|
|
725
|
+
)
|
|
726
|
+
self.logger.info(f"Technical decision recorded: {decision}")
|
|
727
|
+
|
|
728
|
+
def add_constraint(self, constraint: str, impact: str = ""):
|
|
729
|
+
"""
|
|
730
|
+
Add an important constraint to the implementation summary
|
|
731
|
+
向实现总结添加重要约束
|
|
732
|
+
|
|
733
|
+
Args:
|
|
734
|
+
constraint: Description of the constraint
|
|
735
|
+
impact: Impact of the constraint on implementation
|
|
736
|
+
"""
|
|
737
|
+
self.implementation_summary["important_constraints"].append(
|
|
738
|
+
{"constraint": constraint, "impact": impact, "timestamp": time.time()}
|
|
739
|
+
)
|
|
740
|
+
self.logger.info(f"Constraint recorded: {constraint}")
|
|
741
|
+
|
|
742
|
+
def add_architecture_note(self, note: str, component: str = ""):
|
|
743
|
+
"""
|
|
744
|
+
Add an architecture note to the implementation summary
|
|
745
|
+
向实现总结添加架构注释
|
|
746
|
+
|
|
747
|
+
Args:
|
|
748
|
+
note: Architecture note description
|
|
749
|
+
component: Related component or module
|
|
750
|
+
"""
|
|
751
|
+
self.implementation_summary["architecture_notes"].append(
|
|
752
|
+
{"note": note, "component": component, "timestamp": time.time()}
|
|
753
|
+
)
|
|
754
|
+
self.logger.info(f"Architecture note recorded: {note}")
|
|
755
|
+
|
|
756
|
+
def get_implementation_statistics(self) -> Dict[str, Any]:
|
|
757
|
+
"""
|
|
758
|
+
Get comprehensive implementation statistics
|
|
759
|
+
获取全面的实现统计信息
|
|
760
|
+
"""
|
|
761
|
+
return {
|
|
762
|
+
"total_files_implemented": self.files_implemented_count,
|
|
763
|
+
"files_implemented_count": self.files_implemented_count,
|
|
764
|
+
"technical_decisions_count": len(
|
|
765
|
+
self.implementation_summary["technical_decisions"]
|
|
766
|
+
),
|
|
767
|
+
"constraints_count": len(
|
|
768
|
+
self.implementation_summary["important_constraints"]
|
|
769
|
+
),
|
|
770
|
+
"architecture_notes_count": len(
|
|
771
|
+
self.implementation_summary["architecture_notes"]
|
|
772
|
+
),
|
|
773
|
+
"dependency_analysis_count": len(
|
|
774
|
+
self.implementation_summary["dependency_analysis"]
|
|
775
|
+
),
|
|
776
|
+
"files_read_for_dependencies": len(self.files_read_for_dependencies),
|
|
777
|
+
"unique_files_implemented": len(self.implemented_files_set),
|
|
778
|
+
"completed_files_list": [
|
|
779
|
+
f["file"] for f in self.implementation_summary["completed_files"]
|
|
780
|
+
],
|
|
781
|
+
"dependency_files_read": list(self.files_read_for_dependencies),
|
|
782
|
+
"last_summary_file_count": self.last_summary_file_count,
|
|
783
|
+
"read_tools_status": self.get_read_tools_status(), # Include read tools configuration
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
def force_enable_optimization(self):
|
|
787
|
+
"""
|
|
788
|
+
Force enable optimization for testing purposes
|
|
789
|
+
强制启用优化用于测试目的
|
|
790
|
+
"""
|
|
791
|
+
self.files_implemented_count = 1
|
|
792
|
+
self.logger.info(
|
|
793
|
+
f"🔧 OPTIMIZATION FORCE ENABLED: files_implemented_count set to {self.files_implemented_count}"
|
|
794
|
+
)
|
|
795
|
+
print(
|
|
796
|
+
f"🔧 OPTIMIZATION FORCE ENABLED: files_implemented_count set to {self.files_implemented_count}"
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
def reset_implementation_tracking(self):
|
|
800
|
+
"""
|
|
801
|
+
Reset implementation tracking (useful for new sessions)
|
|
802
|
+
重置实现跟踪(对新会话有用)
|
|
803
|
+
"""
|
|
804
|
+
self.implementation_summary = {
|
|
805
|
+
"completed_files": [],
|
|
806
|
+
"technical_decisions": [],
|
|
807
|
+
"important_constraints": [],
|
|
808
|
+
"architecture_notes": [],
|
|
809
|
+
"dependency_analysis": [], # Reset dependency analysis and file reads
|
|
810
|
+
}
|
|
811
|
+
self.files_implemented_count = 0
|
|
812
|
+
self.implemented_files_set = (
|
|
813
|
+
set()
|
|
814
|
+
) # Reset the unique files set / 重置唯一文件集合
|
|
815
|
+
self.files_read_for_dependencies = (
|
|
816
|
+
set()
|
|
817
|
+
) # Reset files read for dependency analysis / 重置为依赖分析而读取的文件
|
|
818
|
+
self.last_summary_file_count = 0 # Reset the file count when last summary was triggered / 重置上次触发总结时的文件数
|
|
819
|
+
self.last_summary_token_count = 0 # Reset token count when last summary was triggered / 重置上次触发总结时的token数
|
|
820
|
+
self.logger.info("Implementation tracking reset")
|
|
821
|
+
|
|
822
|
+
# Reset analysis loop detection / 重置分析循环检测
|
|
823
|
+
self.recent_tool_calls = []
|
|
824
|
+
self.logger.info("Analysis loop detection reset")
|
|
825
|
+
|
|
826
|
+
def _track_tool_call_for_loop_detection(self, tool_name: str):
|
|
827
|
+
"""
|
|
828
|
+
Track tool calls for analysis loop detection
|
|
829
|
+
跟踪工具调用以检测分析循环
|
|
830
|
+
|
|
831
|
+
Args:
|
|
832
|
+
tool_name: Name of the tool called
|
|
833
|
+
"""
|
|
834
|
+
self.recent_tool_calls.append(tool_name)
|
|
835
|
+
if len(self.recent_tool_calls) > self.max_read_without_write:
|
|
836
|
+
self.recent_tool_calls.pop(0)
|
|
837
|
+
|
|
838
|
+
if len(set(self.recent_tool_calls)) == 1:
|
|
839
|
+
self.logger.warning("Analysis loop detected")
|
|
840
|
+
|
|
841
|
+
def is_in_analysis_loop(self) -> bool:
|
|
842
|
+
"""
|
|
843
|
+
Check if the agent is in an analysis loop (only reading files, not writing)
|
|
844
|
+
检查代理是否在分析循环中(只读文件,不写文件)
|
|
845
|
+
|
|
846
|
+
Returns:
|
|
847
|
+
True if in analysis loop
|
|
848
|
+
"""
|
|
849
|
+
if len(self.recent_tool_calls) < self.max_read_without_write:
|
|
850
|
+
return False
|
|
851
|
+
|
|
852
|
+
# Check if recent calls are all read_file or search_reference_code / 检查最近的调用是否都是read_file或search_reference_code
|
|
853
|
+
analysis_tools = {
|
|
854
|
+
"read_file",
|
|
855
|
+
"search_reference_code",
|
|
856
|
+
"get_all_available_references",
|
|
857
|
+
}
|
|
858
|
+
recent_calls_set = set(self.recent_tool_calls)
|
|
859
|
+
|
|
860
|
+
# If all recent calls are analysis tools, we're in an analysis loop / 如果最近的调用都是分析工具,我们在分析循环中
|
|
861
|
+
in_loop = (
|
|
862
|
+
recent_calls_set.issubset(analysis_tools) and len(recent_calls_set) >= 1
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
if in_loop:
|
|
866
|
+
self.logger.warning(
|
|
867
|
+
f"Analysis loop detected! Recent calls: {self.recent_tool_calls}"
|
|
868
|
+
)
|
|
869
|
+
|
|
870
|
+
return in_loop
|
|
871
|
+
|
|
872
|
+
def get_analysis_loop_guidance(self) -> str:
|
|
873
|
+
"""
|
|
874
|
+
Get guidance to break out of analysis loop
|
|
875
|
+
获取跳出分析循环的指导
|
|
876
|
+
|
|
877
|
+
Returns:
|
|
878
|
+
Guidance message to encourage implementation
|
|
879
|
+
"""
|
|
880
|
+
return f"""🚨 **ANALYSIS LOOP DETECTED - IMMEDIATE ACTION REQUIRED**
|
|
881
|
+
|
|
882
|
+
**Problem**: You've been reading/analyzing files for {len(self.recent_tool_calls)} consecutive calls without writing code.
|
|
883
|
+
**Recent tool calls**: {' → '.join(self.recent_tool_calls)}
|
|
884
|
+
|
|
885
|
+
**SOLUTION - IMPLEMENT CODE NOW**:
|
|
886
|
+
1. **STOP ANALYZING** - You have enough information
|
|
887
|
+
2. **Use write_file** to create the next code file according to the implementation plan
|
|
888
|
+
3. **Choose ANY file** from the plan that hasn't been implemented yet
|
|
889
|
+
4. **Write complete, working code** - don't ask for permission or clarification
|
|
890
|
+
|
|
891
|
+
**Files implemented so far**: {self.files_implemented_count}
|
|
892
|
+
**Your goal**: Implement MORE files, not analyze existing ones!
|
|
893
|
+
|
|
894
|
+
**CRITICAL**: Your next response MUST use write_file to create a new code file!"""
|
|
895
|
+
|
|
896
|
+
async def test_summary_functionality(self, test_file_path: str = None):
|
|
897
|
+
"""
|
|
898
|
+
Test if the code summary functionality is working correctly
|
|
899
|
+
测试代码总结功能是否正常工作
|
|
900
|
+
|
|
901
|
+
Args:
|
|
902
|
+
test_file_path: Specific file to test, if None will test all implemented files
|
|
903
|
+
"""
|
|
904
|
+
if not self.memory_agent:
|
|
905
|
+
self.logger.warning("No memory agent available for testing")
|
|
906
|
+
return
|
|
907
|
+
|
|
908
|
+
if test_file_path:
|
|
909
|
+
files_to_test = [test_file_path]
|
|
910
|
+
else:
|
|
911
|
+
# Use implemented files from tracking
|
|
912
|
+
files_to_test = list(self.implemented_files_set)[
|
|
913
|
+
:3
|
|
914
|
+
] # Limit to first 3 files
|
|
915
|
+
|
|
916
|
+
if not files_to_test:
|
|
917
|
+
self.logger.warning("No implemented files to test")
|
|
918
|
+
return
|
|
919
|
+
|
|
920
|
+
# Test each file silently
|
|
921
|
+
summary_files_found = 0
|
|
922
|
+
|
|
923
|
+
for file_path in files_to_test:
|
|
924
|
+
if self.mcp_agent:
|
|
925
|
+
try:
|
|
926
|
+
result = await self.mcp_agent.call_tool(
|
|
927
|
+
"read_code_mem", {"file_path": file_path}
|
|
928
|
+
)
|
|
929
|
+
|
|
930
|
+
# Parse the result to check if summary was found
|
|
931
|
+
import json
|
|
932
|
+
|
|
933
|
+
result_data = (
|
|
934
|
+
json.loads(result) if isinstance(result, str) else result
|
|
935
|
+
)
|
|
936
|
+
|
|
937
|
+
if result_data.get("status") == "summary_found":
|
|
938
|
+
summary_files_found += 1
|
|
939
|
+
except Exception as e:
|
|
940
|
+
self.logger.warning(
|
|
941
|
+
f"Failed to test read_code_mem for {file_path}: {e}"
|
|
942
|
+
)
|
|
943
|
+
else:
|
|
944
|
+
self.logger.warning("MCP agent not available for testing")
|
|
945
|
+
|
|
946
|
+
self.logger.info(
|
|
947
|
+
f"📋 Summary testing: {summary_files_found}/{len(files_to_test)} files have summaries"
|
|
948
|
+
)
|
|
949
|
+
|
|
950
|
+
async def test_automatic_read_file_optimization(self):
|
|
951
|
+
"""
|
|
952
|
+
Test the automatic read_file optimization that redirects to read_code_mem
|
|
953
|
+
测试自动read_file优化,重定向到read_code_mem
|
|
954
|
+
"""
|
|
955
|
+
print("=" * 80)
|
|
956
|
+
print("🔄 TESTING AUTOMATIC READ_FILE OPTIMIZATION")
|
|
957
|
+
print("=" * 80)
|
|
958
|
+
|
|
959
|
+
# Simulate that at least one file has been implemented (to trigger optimization)
|
|
960
|
+
self.files_implemented_count = 1
|
|
961
|
+
|
|
962
|
+
# Test with a file that should have a summary
|
|
963
|
+
test_file = "rice/config.py"
|
|
964
|
+
|
|
965
|
+
print(f"📁 Testing automatic optimization for: {test_file}")
|
|
966
|
+
print(f"📊 Files implemented count: {self.files_implemented_count}")
|
|
967
|
+
print(
|
|
968
|
+
f"🔧 Optimization should be: {'ENABLED' if self.files_implemented_count > 0 else 'DISABLED'}"
|
|
969
|
+
)
|
|
970
|
+
|
|
971
|
+
# Create a simulated read_file tool call
|
|
972
|
+
simulated_read_file_call = {
|
|
973
|
+
"id": "test_read_file_optimization",
|
|
974
|
+
"name": "read_file",
|
|
975
|
+
"input": {"file_path": test_file},
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
print("\n🔄 Simulating read_file call:")
|
|
979
|
+
print(f" Tool: {simulated_read_file_call['name']}")
|
|
980
|
+
print(f" File: {simulated_read_file_call['input']['file_path']}")
|
|
981
|
+
|
|
982
|
+
# Execute the tool call (this should trigger automatic optimization)
|
|
983
|
+
results = await self.execute_tool_calls([simulated_read_file_call])
|
|
984
|
+
|
|
985
|
+
if results:
|
|
986
|
+
result = results[0]
|
|
987
|
+
print("\n✅ Tool execution completed:")
|
|
988
|
+
print(f" Tool name: {result.get('tool_name', 'N/A')}")
|
|
989
|
+
print(f" Tool ID: {result.get('tool_id', 'N/A')}")
|
|
990
|
+
|
|
991
|
+
# Parse the result to check if optimization occurred
|
|
992
|
+
import json
|
|
993
|
+
|
|
994
|
+
try:
|
|
995
|
+
result_data = json.loads(result.get("result", "{}"))
|
|
996
|
+
if result_data.get("optimization") == "redirected_to_read_code_mem":
|
|
997
|
+
print("🎉 SUCCESS: read_file was automatically optimized!")
|
|
998
|
+
print(
|
|
999
|
+
f" Original tool: {result_data.get('original_tool', 'N/A')}"
|
|
1000
|
+
)
|
|
1001
|
+
print(f" Status: {result_data.get('status', 'N/A')}")
|
|
1002
|
+
elif result_data.get("status") == "summary_found":
|
|
1003
|
+
print("🎉 SUCCESS: Summary was found and returned!")
|
|
1004
|
+
else:
|
|
1005
|
+
print("ℹ️ INFO: No optimization occurred (no summary available)")
|
|
1006
|
+
except json.JSONDecodeError:
|
|
1007
|
+
print("⚠️ WARNING: Could not parse result as JSON")
|
|
1008
|
+
else:
|
|
1009
|
+
print("❌ ERROR: No results returned from tool execution")
|
|
1010
|
+
|
|
1011
|
+
print("\n" + "=" * 80)
|
|
1012
|
+
print("🔄 AUTOMATIC READ_FILE OPTIMIZATION TEST COMPLETE")
|
|
1013
|
+
print("=" * 80)
|
|
1014
|
+
|
|
1015
|
+
async def test_summary_optimization(self, test_file_path: str = "rice/config.py"):
|
|
1016
|
+
"""
|
|
1017
|
+
Test the summary optimization functionality with a specific file
|
|
1018
|
+
测试特定文件的总结优化功能
|
|
1019
|
+
|
|
1020
|
+
Args:
|
|
1021
|
+
test_file_path: File path to test (default: rice/config.py which should be in summary)
|
|
1022
|
+
"""
|
|
1023
|
+
if not self.mcp_agent:
|
|
1024
|
+
return False
|
|
1025
|
+
|
|
1026
|
+
try:
|
|
1027
|
+
# Use MCP agent to call read_code_mem tool
|
|
1028
|
+
result = await self.mcp_agent.call_tool(
|
|
1029
|
+
"read_code_mem", {"file_path": test_file_path}
|
|
1030
|
+
)
|
|
1031
|
+
|
|
1032
|
+
# Parse the result to check if summary was found
|
|
1033
|
+
import json
|
|
1034
|
+
|
|
1035
|
+
result_data = json.loads(result) if isinstance(result, str) else result
|
|
1036
|
+
|
|
1037
|
+
return result_data.get("status") == "summary_found"
|
|
1038
|
+
except Exception as e:
|
|
1039
|
+
self.logger.warning(f"Failed to test read_code_mem optimization: {e}")
|
|
1040
|
+
return False
|
|
1041
|
+
|
|
1042
|
+
async def test_read_tools_configuration(self):
|
|
1043
|
+
"""
|
|
1044
|
+
Test the read tools configuration to verify enabling/disabling works correctly
|
|
1045
|
+
测试读取工具配置以验证启用/禁用是否正常工作
|
|
1046
|
+
"""
|
|
1047
|
+
print("=" * 60)
|
|
1048
|
+
print("🧪 TESTING READ TOOLS CONFIGURATION")
|
|
1049
|
+
print("=" * 60)
|
|
1050
|
+
|
|
1051
|
+
status = self.get_read_tools_status()
|
|
1052
|
+
print(f"Read tools enabled: {status['read_tools_enabled']}")
|
|
1053
|
+
print(f"Status: {status['status']}")
|
|
1054
|
+
print(f"Tools affected: {status['tools_affected']}")
|
|
1055
|
+
|
|
1056
|
+
# Test with mock tool calls
|
|
1057
|
+
test_tools = [
|
|
1058
|
+
{
|
|
1059
|
+
"id": "test_read_file",
|
|
1060
|
+
"name": "read_file",
|
|
1061
|
+
"input": {"file_path": "test.py"},
|
|
1062
|
+
},
|
|
1063
|
+
{
|
|
1064
|
+
"id": "test_read_code_mem",
|
|
1065
|
+
"name": "read_code_mem",
|
|
1066
|
+
"input": {"file_path": "test.py"},
|
|
1067
|
+
},
|
|
1068
|
+
{
|
|
1069
|
+
"id": "test_write_file",
|
|
1070
|
+
"name": "write_file",
|
|
1071
|
+
"input": {"file_path": "test.py", "content": "# test"},
|
|
1072
|
+
},
|
|
1073
|
+
]
|
|
1074
|
+
|
|
1075
|
+
print(
|
|
1076
|
+
f"\n🔄 Testing tool execution with read_tools_enabled={self.enable_read_tools}"
|
|
1077
|
+
)
|
|
1078
|
+
|
|
1079
|
+
for tool_call in test_tools:
|
|
1080
|
+
tool_name = tool_call["name"]
|
|
1081
|
+
if not self.enable_read_tools and tool_name in [
|
|
1082
|
+
"read_file",
|
|
1083
|
+
"read_code_mem",
|
|
1084
|
+
]:
|
|
1085
|
+
print(f"🚫 {tool_name}: Would be SKIPPED (disabled)")
|
|
1086
|
+
else:
|
|
1087
|
+
print(f"✅ {tool_name}: Would be EXECUTED")
|
|
1088
|
+
|
|
1089
|
+
print("=" * 60)
|
|
1090
|
+
print("🧪 READ TOOLS CONFIGURATION TEST COMPLETE")
|
|
1091
|
+
print("=" * 60)
|
|
1092
|
+
|
|
1093
|
+
return status
|