1use std::fs;
2
3use anyhow::Result;
4
5use crate::detection::*;
6use crate::scan::ProjectContext;
7
8pub 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
81fn 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}