Skip to main content

claude_native/
init.rs

1use std::fs;
2
3use anyhow::Result;
4
5use crate::detection::*;
6use crate::scan::ProjectContext;
7
8/// Bootstrap a project with Claude Native configuration files.
9/// Only creates files that don't already exist.
10pub fn init_project(ctx: &ProjectContext) -> Result<Vec<String>> {
11    let mut created = Vec::new();
12    let root = &ctx.root;
13    let pt = ctx.project_type.as_ref();
14
15    if !root.join("CLAUDE.md").exists() && !root.join(".claude").join("CLAUDE.md").exists() {
16        let content = generate_claude_md(ctx, pt);
17        fs::write(root.join("CLAUDE.md"), content)?;
18        created.push("CLAUDE.md".into());
19    }
20
21    if !root.join("AGENTS.md").exists() {
22        let content = generate_agents_md(ctx, pt);
23        fs::write(root.join("AGENTS.md"), content)?;
24        created.push("AGENTS.md".into());
25    }
26
27    if !root.join(".claudeignore").exists() {
28        let content = generate_claudeignore(ctx, pt);
29        fs::write(root.join(".claudeignore"), content)?;
30        created.push(".claudeignore".into());
31    }
32
33    let claude_dir = root.join(".claude");
34    if !claude_dir.exists() {
35        fs::create_dir_all(&claude_dir)?;
36    }
37
38    let settings_path = claude_dir.join("settings.json");
39    if !settings_path.exists() {
40        let content = generate_settings(ctx, pt);
41        fs::write(&settings_path, content)?;
42        created.push(".claude/settings.json".into());
43    }
44
45    Ok(created)
46}
47
48pub fn detect_commands(_ctx: &ProjectContext, pt: Option<&ProjectType>) -> (&'static str, &'static str) {
49    match pt.map(|p| &p.primary) {
50        Some(PrimaryType::Mobile(MobileFramework::Flutter)) => ("flutter build", "flutter test"),
51        Some(PrimaryType::Mobile(MobileFramework::ReactNative)) => ("npm run build", "npm test"),
52        Some(PrimaryType::Mobile(MobileFramework::IosNative)) => ("swift build", "swift test"),
53        Some(PrimaryType::Mobile(MobileFramework::AndroidNative)) => ("./gradlew assembleDebug", "./gradlew test"),
54        Some(PrimaryType::Frontend(FrontendFramework::NextJs)) => ("npm run build", "npm test"),
55        Some(PrimaryType::Frontend(_)) => ("npm run build", "npm test"),
56        Some(PrimaryType::Backend(BackendFramework::Django)) => ("python manage.py check", "python manage.py test"),
57        Some(PrimaryType::Backend(BackendFramework::Rails)) => ("rails db:migrate", "rails test"),
58        Some(PrimaryType::Backend(BackendFramework::Express)) => ("npm run build", "npm test"),
59        Some(PrimaryType::Backend(BackendFramework::GoService)) => ("go build ./...", "go test ./..."),
60        Some(PrimaryType::Backend(BackendFramework::RustService)) => ("cargo build", "cargo test"),
61        Some(PrimaryType::Backend(BackendFramework::Phoenix)) => ("mix compile", "mix test"),
62        Some(PrimaryType::IaC(IaCTool::Terraform)) => ("terraform validate", "terraform test"),
63        Some(PrimaryType::IaC(IaCTool::Helm)) => ("helm lint .", "helm template . | kubeval"),
64        Some(PrimaryType::Serverless(_)) => ("sam build", "sam local invoke"),
65        Some(PrimaryType::ML) => ("pip install -e .", "pytest"),
66        Some(PrimaryType::DocSite(_)) => ("npm run build", "npm run build"),
67        Some(PrimaryType::GameDev(GameEngine::Godot)) => ("godot --export", "godot --test"),
68        Some(PrimaryType::GameDev(GameEngine::Bevy)) => ("cargo build", "cargo test"),
69        _ => detect_commands_from_manifests(),
70    }
71}
72
73fn detect_commands_from_manifests() -> (&'static str, &'static str) {
74    if std::path::Path::new("Cargo.toml").exists() { ("cargo build", "cargo test") }
75    else if std::path::Path::new("package.json").exists() { ("npm run build", "npm test") }
76    else if std::path::Path::new("go.mod").exists() { ("go build ./...", "go test ./...") }
77    else if std::path::Path::new("requirements.txt").exists() { ("pip install -e .", "pytest") }
78    else { ("make build", "make test") }
79}
80
81// ── Private helpers below ───────────────────────────────────────────
82
83fn generate_agents_md(ctx: &ProjectContext, pt: Option<&ProjectType>) -> String {
84    let project_name = ctx.root.file_name()
85        .and_then(|n| n.to_str())
86        .unwrap_or("Project");
87    let (build_cmd, test_cmd) = detect_commands(ctx, pt);
88
89    format!(
90        "# {project_name}\n\
91         \n\
92         > Universal AI agent instructions (AGENTS.md standard)\n\
93         \n\
94         ## Build & Test\n\
95         \n\
96         - Build: `{build_cmd}`\n\
97         - Test: `{test_cmd}`\n\
98         \n\
99         ## Guidelines\n\
100         \n\
101         - Follow existing code patterns and conventions\n\
102         - Write tests for new functionality\n\
103         - Keep functions focused and under 80 lines\n\
104         - Document non-obvious decisions with comments explaining \"why\"\n"
105    )
106}
107
108fn generate_claude_md(ctx: &ProjectContext, pt: Option<&ProjectType>) -> String {
109    let project_name = ctx.root.file_name()
110        .and_then(|n| n.to_str())
111        .unwrap_or("Project");
112
113    let (build_cmd, test_cmd) = detect_commands(ctx, pt);
114
115    format!(
116        "# {project_name}\n\
117         \n\
118         ## Build & Test\n\
119         \n\
120         ```\n\
121         {build_cmd}\n\
122         {test_cmd}\n\
123         ```\n\
124         \n\
125         ## Architecture\n\
126         \n\
127         <!-- Describe your project structure, key modules, and data flow here -->\n\
128         \n\
129         ## Code Patterns\n\
130         \n\
131         <!-- Document error handling, data access, and API conventions here -->\n"
132    )
133}
134
135fn generate_claudeignore(_ctx: &ProjectContext, pt: Option<&ProjectType>) -> String {
136    let mut lines = base_ignore_patterns();
137    if let Some(pt) = pt {
138        lines.extend(type_specific_ignores(&pt.primary));
139    }
140    lines.join("\n") + "\n"
141}
142
143fn base_ignore_patterns() -> Vec<&'static str> {
144    vec![
145        "# Dependencies", "node_modules/", ".venv/", "venv/", "vendor/", "",
146        "# Build artifacts", "dist/", "build/", "target/", "out/", "coverage/", "",
147        "# Lock files", "package-lock.json", "yarn.lock", "pnpm-lock.yaml",
148        "Cargo.lock", "Gemfile.lock", "poetry.lock", "go.sum", "",
149        "# Secrets", ".env", ".env.*", "!.env.example", "",
150        "# OS / IDE", ".DS_Store", ".vscode/", ".idea/", "",
151        "# Logs", "*.log",
152    ]
153}
154
155fn type_specific_ignores(primary: &PrimaryType) -> Vec<&'static str> {
156    match primary {
157        PrimaryType::Mobile(MobileFramework::Flutter) => vec![
158            "", "# Flutter", ".dart_tool/", "ios/Pods/",
159            "android/.gradle/", "android/build/", "*.g.dart", "*.freezed.dart",
160        ],
161        PrimaryType::Mobile(MobileFramework::ReactNative) => vec![
162            "", "# React Native", "ios/Pods/", "android/.gradle/", "android/build/", ".expo/",
163        ],
164        PrimaryType::Frontend(FrontendFramework::NextJs) => vec!["", "# Next.js", ".next/", ".vercel/"],
165        PrimaryType::IaC(IaCTool::Terraform) => vec!["", "# Terraform", ".terraform/", "*.tfstate", "*.tfstate.backup"],
166        PrimaryType::ML => vec!["", "# ML", "*.pkl", "*.h5", "*.pth", "*.onnx", "*.safetensors", "data/", "*.csv", "*.parquet"],
167        PrimaryType::GameDev(_) => vec!["", "# Game assets", "*.unity", "*.prefab", "*.tscn", "*.meta", "Library/", ".godot/", "*.png", "*.wav"],
168        _ => vec![],
169    }
170}
171
172fn generate_settings(_ctx: &ProjectContext, pt: Option<&ProjectType>) -> String {
173    let (build_cmd, test_cmd) = match pt.map(|p| &p.primary) {
174        Some(PrimaryType::Backend(BackendFramework::GoService)) => ("go", "go"),
175        Some(PrimaryType::Backend(BackendFramework::RustService)) | None => ("cargo", "cargo"),
176        Some(PrimaryType::Backend(BackendFramework::Django)) => ("python", "python"),
177        Some(PrimaryType::Mobile(MobileFramework::Flutter)) => ("flutter", "flutter"),
178        _ => ("npm", "npm"),
179    };
180
181    format!(
182        r#"{{
183  "permissions": {{
184    "allow": [
185      "Bash({build_cmd}:*)",
186      "Bash({test_cmd}:*)",
187      "Bash(git status:*)",
188      "Bash(git diff:*)",
189      "Bash(git log:*)"
190    ]
191  }}
192}}
193"#
194    )
195}