agcodex_core/subagents/built_in/
refactorer.rs1use crate::code_tools::ast_agent_tools::ASTAgentTools;
11use crate::code_tools::ast_agent_tools::AgentToolOp;
12use crate::code_tools::ast_agent_tools::AgentToolResult;
13use crate::modes::OperatingMode;
14use crate::subagents::AgentResult;
15use crate::subagents::AgentStatus;
16use crate::subagents::Finding;
17use crate::subagents::Severity;
18use crate::subagents::Subagent;
19use crate::subagents::SubagentContext;
20use crate::subagents::SubagentError;
21use crate::subagents::SubagentResult;
22use std::collections::HashMap;
23use std::future::Future;
24use std::path::Path;
25use std::path::PathBuf;
26use std::pin::Pin;
27use std::sync::Arc;
28use std::sync::atomic::AtomicBool;
29use std::sync::atomic::Ordering;
30use std::time::Duration;
31use std::time::SystemTime;
32
33#[derive(Debug)]
35pub struct RefactorerAgent {
36 name: String,
37 description: String,
38 _mode_override: Option<OperatingMode>,
39 _tool_permissions: Vec<String>,
40 _prompt_template: String,
41 risk_tolerance: RiskLevel,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum RiskLevel {
47 Conservative, Moderate, Aggressive, }
51
52impl Default for RefactorerAgent {
53 fn default() -> Self {
54 Self::new()
55 }
56}
57
58impl RefactorerAgent {
59 pub fn new() -> Self {
61 Self {
62 name: "refactorer".to_string(),
63 description: "Performs systematic code refactoring with risk assessment".to_string(),
64 _mode_override: Some(OperatingMode::Build),
65 _tool_permissions: vec![
66 "search".to_string(),
67 "edit".to_string(),
68 "patch".to_string(),
69 "tree".to_string(),
70 "grep".to_string(),
71 ],
72 _prompt_template: r#"
73You are an expert refactoring specialist focused on:
74- Improving code structure without changing behavior
75- Reducing complexity and duplication
76- Applying SOLID principles
77- Enhancing readability and maintainability
78- Ensuring backwards compatibility
79
80Always verify that refactorings preserve functionality.
81Create comprehensive test cases before major changes.
82"#
83 .to_string(),
84 risk_tolerance: RiskLevel::Moderate,
85 }
86 }
87
88 pub const fn with_risk_level(mut self, level: RiskLevel) -> Self {
90 self.risk_tolerance = level;
91 self
92 }
93
94 async fn identify_refactoring_opportunities(
96 &self,
97 ast_tools: &mut ASTAgentTools,
98 file: &Path,
99 ) -> Vec<Finding> {
100 let mut findings = Vec::new();
101
102 if let Ok(AgentToolResult::DuplicateCode(duplicates)) =
104 ast_tools.execute(AgentToolOp::FindDuplicateCode {
105 min_lines: 5,
106 similarity_threshold: 0.85,
107 })
108 {
109 for duplicate in duplicates {
110 findings.push(Finding {
111 category: "refactoring".to_string(),
112 severity: Severity::Medium,
113 title: "Duplicate Code Detected".to_string(),
114 description: format!(
115 "Found {} duplicate code blocks with {} lines each",
116 duplicate.locations.len(),
117 duplicate.line_count
118 ),
119 location: duplicate.locations.first().cloned(),
120 suggestion: Some("Extract duplicate code into a shared function".to_string()),
121 metadata: HashMap::from([
122 (
123 "refactoring_type".to_string(),
124 serde_json::json!("extract_method"),
125 ),
126 (
127 "duplicate_count".to_string(),
128 serde_json::json!(duplicate.locations.len()),
129 ),
130 (
131 "line_count".to_string(),
132 serde_json::json!(duplicate.line_count),
133 ),
134 ]),
135 });
136 }
137 }
138
139 if let Ok(AgentToolResult::Functions(functions)) =
141 ast_tools.execute(AgentToolOp::ExtractFunctions {
142 file: file.to_path_buf(),
143 language: self.detect_language(file),
144 })
145 {
146 for func in functions {
147 if func.parameters.len() > 4 {
148 findings.push(Finding {
149 category: "refactoring".to_string(),
150 severity: Severity::Low,
151 title: format!("Long Parameter List: {}", func.name),
152 description: format!(
153 "Function '{}' has {} parameters. Consider using a parameter object.",
154 func.name,
155 func.parameters.len()
156 ),
157 location: Some(crate::code_tools::ast_agent_tools::Location {
158 file: file.to_path_buf(),
159 line: func.start_line,
160 column: 0,
161 byte_offset: 0,
162 }),
163 suggestion: Some(
164 "Introduce a parameter object or builder pattern".to_string(),
165 ),
166 metadata: HashMap::from([
167 (
168 "refactoring_type".to_string(),
169 serde_json::json!("introduce_parameter_object"),
170 ),
171 (
172 "parameter_count".to_string(),
173 serde_json::json!(func.parameters.len()),
174 ),
175 ]),
176 });
177 }
178 }
179 }
180
181 findings
182 }
183
184 async fn apply_refactorings(
186 &self,
187 ast_tools: &mut ASTAgentTools,
188 findings: &[Finding],
189 ) -> Vec<PathBuf> {
190 let mut modified_files = Vec::new();
191
192 for finding in findings {
193 let should_apply = match self.risk_tolerance {
195 RiskLevel::Conservative => finding.severity == Severity::Low,
196 RiskLevel::Moderate => finding.severity != Severity::Critical,
197 RiskLevel::Aggressive => true,
198 };
199
200 if should_apply && let Some(refactoring_type) = finding.metadata.get("refactoring_type")
201 {
202 match refactoring_type.as_str() {
203 Some("extract_method") => {
204 if let Some(location) = &finding.location
206 && let Ok(AgentToolResult::Refactored(result)) =
207 ast_tools.execute(AgentToolOp::RefactorExtractMethod {
208 location: location.clone(),
209 new_name: "extracted_function".to_string(),
210 })
211 && result.success
212 {
213 modified_files.push(location.file.clone());
214 }
215 }
216 Some("introduce_parameter_object") => {
217 if let Some(location) = &finding.location
219 && let Ok(AgentToolResult::Refactored(result)) =
220 ast_tools.execute(AgentToolOp::RefactorIntroduceParameterObject {
221 location: location.clone(),
222 object_name: "Parameters".to_string(),
223 })
224 && result.success
225 {
226 modified_files.push(location.file.clone());
227 }
228 }
229 _ => {}
230 }
231 }
232 }
233
234 modified_files
235 }
236
237 fn detect_language(&self, file: &Path) -> String {
239 file.extension()
240 .and_then(|ext| ext.to_str())
241 .map(|ext| match ext {
242 "rs" => "rust",
243 "py" => "python",
244 "js" | "jsx" => "javascript",
245 "ts" | "tsx" => "typescript",
246 "go" => "go",
247 "java" => "java",
248 _ => "text",
249 })
250 .unwrap_or("text")
251 .to_string()
252 }
253}
254
255impl Subagent for RefactorerAgent {
256 fn name(&self) -> &str {
257 &self.name
258 }
259
260 fn description(&self) -> &str {
261 &self.description
262 }
263
264 fn execute<'a>(
265 &'a self,
266 context: &'a SubagentContext,
267 ast_tools: &'a mut ASTAgentTools,
268 cancel_flag: Arc<AtomicBool>,
269 ) -> Pin<Box<dyn Future<Output = SubagentResult<AgentResult>> + Send + 'a>> {
270 Box::pin(async move {
271 let start_time = SystemTime::now();
272 let mut all_findings = Vec::new();
273 let mut analyzed_files = Vec::new();
274 let mut modified_files = Vec::new();
275
276 let files = self.get_files_from_context(context)?;
278
279 for file in &files {
280 if cancel_flag.load(Ordering::Acquire) {
281 return Err(SubagentError::ExecutionFailed(
282 "Refactoring cancelled".to_string(),
283 ));
284 }
285
286 analyzed_files.push(file.clone());
287
288 let findings = self
290 .identify_refactoring_opportunities(ast_tools, file)
291 .await;
292 all_findings.extend(findings.clone());
293
294 if context.mode == OperatingMode::Build {
296 let modified = self.apply_refactorings(ast_tools, &findings).await;
297 modified_files.extend(modified);
298 }
299 }
300
301 let summary = format!(
302 "Refactoring analysis completed: {} files analyzed, {} opportunities found, {} files modified",
303 analyzed_files.len(),
304 all_findings.len(),
305 modified_files.len()
306 );
307
308 let refactoring_opportunities = all_findings.len();
310 let files_modified = modified_files.len();
311
312 let execution_time = SystemTime::now()
313 .duration_since(start_time)
314 .unwrap_or_else(|_| Duration::from_secs(0));
315
316 Ok(AgentResult {
317 agent_name: self.name.clone(),
318 status: AgentStatus::Completed,
319 findings: all_findings,
320 analyzed_files,
321 modified_files,
322 execution_time,
323 summary,
324 metrics: HashMap::from([
325 (
326 "refactoring_opportunities".to_string(),
327 serde_json::json!(refactoring_opportunities),
328 ),
329 (
330 "files_modified".to_string(),
331 serde_json::json!(files_modified),
332 ),
333 (
334 "risk_level".to_string(),
335 serde_json::json!(format!("{:?}", self.risk_tolerance)),
336 ),
337 ]),
338 })
339 })
340 }
341
342 fn capabilities(&self) -> Vec<String> {
343 vec![
344 "code-refactoring".to_string(),
345 "duplicate-detection".to_string(),
346 "pattern-application".to_string(),
347 "complexity-reduction".to_string(),
348 ]
349 }
350
351 fn supports_file_type(&self, file_path: &Path) -> bool {
352 let supported = ["rs", "py", "js", "ts", "jsx", "tsx", "go", "java"];
353 file_path
354 .extension()
355 .and_then(|ext| ext.to_str())
356 .map(|ext| supported.contains(&ext))
357 .unwrap_or(false)
358 }
359
360 fn execution_time_estimate(&self) -> Duration {
361 Duration::from_secs(90)
362 }
363}
364
365impl RefactorerAgent {
366 fn get_files_from_context(
367 &self,
368 context: &SubagentContext,
369 ) -> Result<Vec<PathBuf>, SubagentError> {
370 if let Some(files) = context.parameters.get("files") {
371 Ok(files.split(',').map(|s| PathBuf::from(s.trim())).collect())
372 } else {
373 Ok(vec![context.working_directory.clone()])
374 }
375 }
376}