Skip to main content

claude_native/
fix.rs

1use std::fs;
2
3use anyhow::Result;
4
5use crate::init;
6use crate::rules::RuleResult;
7use crate::scan::ProjectContext;
8
9/// Auto-fix quick wins: create missing files, append missing .claudeignore patterns.
10/// Returns list of actions taken.
11pub fn apply_fixes(ctx: &ProjectContext, results: &[RuleResult]) -> Result<Vec<String>> {
12    let mut actions = Vec::new();
13
14    // Fix missing foundation files via --init
15    let init_actions = init::init_project(ctx)?;
16    for a in &init_actions {
17        actions.push(format!("Created {a}"));
18    }
19
20    // Fix missing .claudeignore patterns
21    let ignore_actions = fix_claudeignore(ctx, results)?;
22    actions.extend(ignore_actions);
23
24    // Fix missing .claude subdirectories
25    fix_claude_dirs(ctx, &mut actions)?;
26
27    Ok(actions)
28}
29
30fn fix_claudeignore(ctx: &ProjectContext, results: &[RuleResult]) -> Result<Vec<String>> {
31    let mut actions = Vec::new();
32    let ignore_path = ctx.root.join(".claudeignore");
33
34    // Collect patterns that rules say are missing
35    let mut missing_patterns: Vec<&str> = Vec::new();
36
37    for r in results {
38        if !r.status.is_failure() && !r.status.is_warning() { continue; }
39        match r.rule_id.as_str() {
40            "2.3" => missing_patterns.extend_from_slice(&[
41                "Cargo.lock", "package-lock.json", "yarn.lock", "go.sum",
42            ]),
43            "2.4" => missing_patterns.extend_from_slice(&[
44                "dist/", "build/", "target/", "coverage/",
45            ]),
46            _ => {}
47        }
48    }
49
50    if missing_patterns.is_empty() { return Ok(actions); }
51
52    let existing = fs::read_to_string(&ignore_path).unwrap_or_default();
53    let mut to_add: Vec<&str> = missing_patterns.iter()
54        .filter(|p| !existing.contains(*p))
55        .copied()
56        .collect();
57    to_add.dedup();
58
59    if !to_add.is_empty() {
60        let mut content = existing;
61        if !content.ends_with('\n') && !content.is_empty() {
62            content.push('\n');
63        }
64        content.push_str("\n# Auto-added by claude-native --fix\n");
65        for p in &to_add {
66            content.push_str(p);
67            content.push('\n');
68        }
69        fs::write(&ignore_path, content)?;
70        actions.push(format!("Appended {} patterns to .claudeignore", to_add.len()));
71    }
72
73    Ok(actions)
74}
75
76fn fix_claude_dirs(ctx: &ProjectContext, actions: &mut Vec<String>) -> Result<()> {
77    let rules_dir = ctx.root.join(".claude").join("rules");
78    if !rules_dir.exists() {
79        fs::create_dir_all(&rules_dir)?;
80        fs::write(rules_dir.join(".gitkeep"), "")?;
81        actions.push("Created .claude/rules/".into());
82    }
83
84    let skills_dir = ctx.root.join(".claude").join("skills");
85    if !skills_dir.exists() {
86        fs::create_dir_all(&skills_dir)?;
87        fs::write(skills_dir.join(".gitkeep"), "")?;
88        actions.push("Created .claude/skills/".into());
89    }
90
91    let mcp_path = ctx.root.join(".claude").join(".mcp.json");
92    if !mcp_path.exists() {
93        fs::write(&mcp_path, "{\"mcpServers\": {}}\n")?;
94        actions.push("Created .claude/.mcp.json".into());
95    }
96
97    Ok(())
98}