atomcode_core/tool/auto_fix.rs
1//! Pre-write validation and auto-fix for edited content.
2//!
3//! All checks operate on in-memory content — nothing is written to disk.
4//! The caller is responsible for writing the validated/fixed content.
5//!
6//! Post-write syntax check (`post_edit_syntax_check`) needs the file on disk
7//! (runs external commands like `node --check`) and is called separately after write.
8
9/// Result of `validate_and_fix`: the (possibly fixed) content, any warnings,
10/// and whether any auto-fix was applied.
11pub struct ValidateResult {
12 pub fixed_content: String,
13 pub warnings: Vec<String>,
14 pub was_fixed: bool,
15 /// If true, the edit should be REJECTED — don't write to disk.
16 /// The model must fix its edit and retry.
17 pub rejected: bool,
18}
19
20/// Run all pre-write validations on the content in memory:
21/// 1. Duplicate block detection
22/// 2. Brace auto-fix
23/// 3. HTML tag auto-fix
24///
25/// Returns a `ValidateResult` with the (possibly fixed) content.
26/// The caller should write `fixed_content` to disk, then optionally call
27/// `post_edit_syntax_check` for on-disk syntax validation.
28/// `content` is the post-edit content (what would be written to disk).
29/// `original_content` is the pre-edit content (for delta validation).
30pub async fn validate_and_fix(
31 content: &str,
32 _file_path: &str,
33 _new_string: &str,
34 _original_content: &str,
35) -> ValidateResult {
36 let warnings: Vec<String> = Vec::new();
37 let current = content.to_string();
38
39 // Duplicate detection REMOVED.
40 // Zero proven positive value across all sessions.
41 // False positives on HTML (repeated <div>/<button>) caused edit rejections.
42 // auto-compile catches real structural errors with better diagnostics.
43
44 // Structural checks (brace balance, HTML balance, Vue SFC) REMOVED.
45 // auto-compile after edit catches all structural errors with better
46 // diagnostics (line number + error type from compiler).
47 // Delta validation was net negative: 7 false rejections today, 0 true catches.
48 // Principle: "don't block the model, enhance the tool."
49
50 if !warnings.is_empty() {
51 return ValidateResult {
52 fixed_content: current,
53 warnings,
54 was_fixed: false,
55 rejected: true,
56 };
57 }
58
59 ValidateResult {
60 fixed_content: current,
61 warnings: vec![],
62 was_fixed: false,
63 rejected: false,
64 }
65}
66
67/// Post-edit syntax check for common file types.
68/// Runs a fast, non-destructive check and returns a warning if syntax is broken.
69/// This needs the file to be on disk — call AFTER writing.
70pub async fn post_edit_syntax_check(file_path: &str) -> String {
71 let ext = file_path.rsplit('.').next().unwrap_or("");
72 let cmd = match ext {
73 "js" | "mjs" | "cjs" => Some(("node", vec!["--check".to_string(), file_path.to_string()])),
74 "json" => {
75 // Validate JSON by attempting parse
76 return match tokio::fs::read_to_string(file_path).await {
77 Ok(content) => {
78 if serde_json::from_str::<serde_json::Value>(&content).is_err() {
79 format!(
80 "\n\u{26a0} SYNTAX ERROR: {} is not valid JSON. Fix before proceeding.",
81 file_path
82 )
83 } else {
84 String::new()
85 }
86 }
87 Err(_) => String::new(),
88 };
89 }
90 "ts" | "tsx" => {
91 return String::new();
92 }
93 "vue" | "svelte" => {
94 // Quick checks for common Vue SFC errors:
95 // 1. Nested backticks in <script> (template strings containing `)
96 // 2. Fast build check if no dev server is running
97 let mut warnings = Vec::new();
98
99 if let Ok(content) = tokio::fs::read_to_string(file_path).await {
100 // Check for nested backticks in <script> section
101 if let Some(script_start) = content.find("<script") {
102 let script_end = content.find("</script>").unwrap_or(content.len());
103 let script = &content[script_start..script_end];
104 // Count backticks — odd number means unclosed template string
105 let backtick_count = script.chars().filter(|c| *c == '`').count();
106 if backtick_count % 2 != 0 {
107 warnings.push(format!(
108 "Unclosed template string (`) in <script> — {} backticks found (odd). \
109 Use regular strings ('') for data containing backticks.",
110 backtick_count
111 ));
112 }
113 }
114 }
115
116 // NOTE: auto-build removed — violates "do NOT deploy" principle.
117 // Model should run build manually via bash when it wants to verify.
118
119 if warnings.is_empty() {
120 return String::new();
121 }
122 return format!("\n⚠ VUE SYNTAX: {}", warnings.join("; "));
123 }
124 "py" => Some((
125 "python3",
126 vec![
127 "-m".to_string(),
128 "py_compile".to_string(),
129 file_path.to_string(),
130 ],
131 )),
132 _ => None,
133 };
134
135 if let Some((program, args)) = cmd {
136 let mut command = tokio::process::Command::new(program);
137 command.args(&args);
138 crate::process_utils::suppress_console_window(&mut command);
139 match command.output().await {
140 Ok(output) if !output.status.success() => {
141 let stderr = String::from_utf8_lossy(&output.stderr);
142 let first_lines: String = stderr.lines().take(3).collect::<Vec<_>>().join("\n");
143 format!("\n\u{26a0} SYNTAX ERROR in {}:\n{}", file_path, first_lines)
144 }
145 _ => String::new(),
146 }
147 } else {
148 String::new()
149 }
150}