1use std::fs;
2
3use anyhow::Result;
4
5use crate::init;
6use crate::rules::RuleResult;
7use crate::scan::ProjectContext;
8
9pub fn apply_fixes(ctx: &ProjectContext, results: &[RuleResult]) -> Result<Vec<String>> {
12 let mut actions = Vec::new();
13
14 let init_actions = init::init_project(ctx)?;
16 for a in &init_actions {
17 actions.push(format!("Created {a}"));
18 }
19
20 let ignore_actions = fix_claudeignore(ctx, results)?;
22 actions.extend(ignore_actions);
23
24 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 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}