Skip to main content

classify_modification

Function classify_modification 

Source
pub fn classify_modification(
    path: &Path,
    old_content: &str,
    new_content: &str,
) -> (ModificationKind, ChangeImportance)
Expand description

Classify what kind of modification happened to a file and its review importance.

This is the core engine behind “147 files changed → 11 things worth reviewing”: it separates noise (formatting, imports, comments) from signal (logic changes).

Returns (kind, importance, confidence) where confidence is 0.0–1.0. AST-backed classification gets high confidence (0.9+), token-fallback gets medium (0.6–0.7).

Examples found in repository?
examples/classify_demo.rs (line 172)
12fn main() {
13    let pairs: Vec<(&str, &str, &str, &str)> = vec![
14        // 1. Rust: renamed function, added retry logic, new imports — LOGIC
15        (
16            "main.rs",
17            concat!(
18                "use std::collections::HashMap;\n",
19                "use std::io;\n\n",
20                "// Main entry point\n",
21                "fn main() {\n",
22                "    let config = load_config();\n",
23                "    let result = process(&config);\n",
24                "    println!(\"Result: {}\", result);\n",
25                "}\n\n",
26                "fn load_config() -> HashMap<String, String> {\n",
27                "    let mut map = HashMap::new();\n",
28                "    map.insert(\"key\".to_string(), \"value\".to_string());\n",
29                "    map\n",
30                "}\n\n",
31                "fn process(config: &HashMap<String, String>) -> String {\n",
32                "    let key = config.get(\"key\").unwrap_or(&String::new());\n",
33                "    format!(\"Processed: {}\", key)\n",
34                "}\n",
35            ),
36            concat!(
37                "use std::collections::HashMap;\n",
38                "use std::io;\n",
39                "use std::thread;\n",
40                "use std::time::Duration;\n\n",
41                "// Main entry point — now with retry support\n",
42                "fn main() {\n",
43                "    let config = load_config();\n",
44                "    let result = process_with_retry(&config, 3);\n",
45                "    println!(\"Result: {}\", result);\n",
46                "}\n\n",
47                "fn load_config() -> HashMap<String, String> {\n",
48                "    let mut map = HashMap::new();\n",
49                "    map.insert(\"key\".to_string(), \"value\".to_string());\n",
50                "    map\n",
51                "}\n\n",
52                "fn process_with_retry(config: &HashMap<String, String>, max_retries: u32) -> String {\n",
53                "    let mut attempts = 0;\n",
54                "    loop {\n",
55                "        match try_process(config) {\n",
56                "            Ok(result) => return result,\n",
57                "            Err(e) if attempts < max_retries => {\n",
58                "                attempts += 1;\n",
59                "                eprintln!(\"Retry {}/{}: {}\", attempts, max_retries, e);\n",
60                "                thread::sleep(Duration::from_millis(100 * attempts as u64));\n",
61                "            }\n",
62                "            Err(e) => return format!(\"Failed after {} retries: {}\", max_retries, e),\n",
63                "        }\n",
64                "    }\n",
65                "}\n\n",
66                "fn try_process(config: &HashMap<String, String>) -> Result<String, String> {\n",
67                "    let key = config.get(\"key\").ok_or(\"missing key\")?;\n",
68                "    Ok(format!(\"Processed: {}\", key))\n",
69                "}\n",
70            ),
71            "Logic: renamed fn + retry logic + new imports",
72        ),
73        // 2. Rust: formatting only (indentation added) — FORMATTING
74        (
75            "utils.rs",
76            "fn helper_one() {\nlet x = 1;\nlet y = 2;\nx + y\n}\n\nfn helper_two(a: i32, b: i32) -> i32 {\na * b + 1\n}\n\nfn helper_three() -> String {\n\"hello world\".to_string()\n}\n",
77            "fn helper_one() {\n    let x = 1;\n    let y = 2;\n    x + y\n}\n\nfn helper_two(a: i32, b: i32) -> i32 {\n    a * b + 1\n}\n\nfn helper_three() -> String {\n    \"hello world\".to_string()\n}\n",
78            "Noise: formatting only (indentation)",
79        ),
80        // 3. Rust: only comments changed — COMMENTS
81        (
82            "config.rs",
83            "// Configuration module\n// Handles loading and parsing config files\nuse std::path::Path;\n\nfn read_config(path: &Path) -> String {\n    std::fs::read_to_string(path).unwrap_or_default()\n}\n",
84            "// Configuration module — refactored for clarity\n// Handles loading, parsing, and validating config files\n// See DESIGN.md for the full config spec\nuse std::path::Path;\n\nfn read_config(path: &Path) -> String {\n    std::fs::read_to_string(path).unwrap_or_default()\n}\n",
85            "Low: comments only",
86        ),
87        // 4. Rust: only imports changed — IMPORTS
88        (
89            "imports.rs",
90            "use std::io;\n\nfn do_work() -> i32 {\n    42\n}\n",
91            "use std::io;\nuse std::fs;\nuse std::path::PathBuf;\n\nfn do_work() -> i32 {\n    42\n}\n",
92            "Low: imports only",
93        ),
94        // 5. Python: formatting only (spaces around operators) — FORMATTING
95        (
96            "script.py",
97            "def calculate(x,y):\n    return x+y\n\ndef transform(data):\n    result=[]\n    for item in data:\n        result.append(item*2)\n    return result\n",
98            "def calculate(x, y):\n    return x + y\n\ndef transform(data):\n    result = []\n    for item in data:\n        result.append(item * 2)\n    return result\n",
99            "Noise: Python formatting (spaces around ops)",
100        ),
101        // 6. TypeScript: function deleted + new function + logic — LOGIC
102        (
103            "handler.ts",
104            concat!(
105                "function handleRequest(req: Request): Response {\n",
106                "    const body = parseBody(req);\n",
107                "    return new Response(JSON.stringify(body));\n",
108                "}\n\n",
109                "function parseBody(req: Request): any {\n",
110                "    return JSON.parse(req.body as string);\n",
111                "}\n\n",
112                "function legacyHandler(data: string): string {\n",
113                "    return data.toUpperCase();\n",
114                "}\n",
115            ),
116            concat!(
117                "function handleRequest(req: Request): Response {\n",
118                "    const body = parseBody(req);\n",
119                "    const validated = validateBody(body);\n",
120                "    return new Response(JSON.stringify(validated));\n",
121                "}\n\n",
122                "function parseBody(req: Request): any {\n",
123                "    return JSON.parse(req.body as string);\n",
124                "}\n\n",
125                "function validateBody(body: any): any {\n",
126                "    if (!body || typeof body !== 'object') {\n",
127                "        throw new Error('Invalid body');\n",
128                "    }\n",
129                "    return body;\n",
130                "}\n",
131            ),
132            "Logic: fn deleted + new fn + logic change",
133        ),
134        // 7. Go: pure logic change — LOGIC
135        (
136            "server.go",
137            "package main\n\nimport \"fmt\"\n\nfunc handleHealth() string {\n\treturn \"ok\"\n}\n\nfunc handleMetrics() string {\n\treturn fmt.Sprintf(\"uptime=%d\", 42)\n}\n",
138            "package main\n\nimport \"fmt\"\n\nfunc handleHealth() string {\n\treturn \"healthy\"\n}\n\nfunc handleMetrics() string {\n\treturn fmt.Sprintf(\"uptime=%d,errors=%d\", 42, 0)\n}\n",
139            "Logic: Go return value changes",
140        ),
141        // 8. Java: formatting only (brace style) — FORMATTING
142        (
143            "App.java",
144            "class App {\npublic static void main(String[] args) {\nSystem.out.println(\"hello\");\n}\n}\n",
145            "class App {\n    public static void main(String[] args) {\n        System.out.println(\"hello\");\n    }\n}\n",
146            "Noise: Java formatting (brace indentation)",
147        ),
148        // 9. Unknown file type: TOML config — token-level fallback
149        (
150            "config.toml",
151            "[database]\nhost = \"localhost\"\nport = 5432\n",
152            "[database]\nhost = \"db.prod.internal\"\nport = 5432\ntimeout = 30\n",
153            "Logic: TOML config value change (token fallback)",
154        ),
155    ];
156
157    println!();
158    println!("  Heddle Semantic Diff — Classification Demo");
159    println!("  =========================================");
160    println!();
161    println!(
162        "  {:<14} {:<18} {:<10} Expected",
163        "File", "Classification", "Importance"
164    );
165    println!("  {}", "─".repeat(78));
166
167    let mut noise_count = 0;
168    let mut low_count = 0;
169    let mut logic_count = 0;
170
171    for (name, old, new, expected) in &pairs {
172        let (kind, importance) = classify_modification(Path::new(name), old, new);
173        let imp_str = format!("{:?}", importance);
174        println!(
175            "  {:<14} {:<18} {:<10} {}",
176            name,
177            format!("{:?}", kind),
178            imp_str,
179            expected
180        );
181        match importance {
182            objects::object::ChangeImportance::Noise => noise_count += 1,
183            objects::object::ChangeImportance::Low => low_count += 1,
184            _ => logic_count += 1,
185        }
186    }
187
188    println!("  {}", "─".repeat(78));
189    println!();
190    println!(
191        "  {} files changed → {} things worth reviewing",
192        pairs.len(),
193        logic_count
194    );
195    println!(
196        "  Filtered: {} noise (formatting) + {} low (imports/comments)",
197        noise_count, low_count
198    );
199    println!();
200}