Skip to main content

atomcode_core/
init.rs

1//! Project initialization — generate .atomcode.md from project structure.
2
3use std::path::Path;
4
5/// Generate project instruction content by scanning the working directory.
6pub fn generate_project_instructions(working_dir: &Path) -> String {
7    let mut sections = Vec::new();
8    let project_name = working_dir
9        .file_name()
10        .map(|n| n.to_string_lossy().to_string())
11        .unwrap_or_else(|| "project".to_string());
12
13    sections.push(format!("# {} — Project Instructions\n", project_name));
14
15    // Detect tech stack
16    let (stack, build_cmds) = detect_project(working_dir);
17    if !stack.is_empty() {
18        sections.push(format!("**Tech Stack:** {}\n", stack.join(", ")));
19    }
20
21    if !build_cmds.is_empty() {
22        sections.push("## Build & Test\n".to_string());
23        for cmd in &build_cmds {
24            sections.push(format!("- `{}`", cmd));
25        }
26        sections.push(String::new());
27    }
28
29    sections.push("## Code Style\n".to_string());
30    sections.push("- Follow existing patterns in the codebase".to_string());
31    sections.push("- Write tests for new functionality".to_string());
32
33    // Add framework-specific hints
34    for hint in framework_hints(working_dir) {
35        sections.push(format!("- {}", hint));
36    }
37
38    sections.join("\n")
39}
40
41/// Detect project type and return (tech_stack, build_commands).
42fn detect_project(dir: &Path) -> (Vec<String>, Vec<String>) {
43    let mut stack = Vec::new();
44    let mut cmds = Vec::new();
45
46    if dir.join("Cargo.toml").exists() {
47        stack.push("Rust".to_string());
48        cmds.push("cargo build".to_string());
49        cmds.push("cargo test".to_string());
50        cmds.push("cargo clippy".to_string());
51    }
52    if dir.join("package.json").exists() {
53        stack.push("Node.js".to_string());
54        if dir.join("pnpm-lock.yaml").exists() {
55            cmds.push("pnpm install".to_string());
56            cmds.push("pnpm test".to_string());
57        } else if dir.join("yarn.lock").exists() {
58            cmds.push("yarn install".to_string());
59            cmds.push("yarn test".to_string());
60        } else {
61            cmds.push("npm install".to_string());
62            cmds.push("npm test".to_string());
63        }
64        // Detect framework
65        if let Ok(pkg) = std::fs::read_to_string(dir.join("package.json")) {
66            if pkg.contains("\"react\"") { stack.push("React".to_string()); }
67            if pkg.contains("\"vue\"") { stack.push("Vue".to_string()); }
68            if pkg.contains("\"next\"") { stack.push("Next.js".to_string()); }
69            if pkg.contains("\"typescript\"") { stack.push("TypeScript".to_string()); }
70        }
71    }
72    if dir.join("pom.xml").exists() {
73        stack.push("Java/Maven".to_string());
74        cmds.push("mvn compile".to_string());
75        cmds.push("mvn test".to_string());
76    }
77    if dir.join("build.gradle").exists() || dir.join("build.gradle.kts").exists() {
78        stack.push("Java/Gradle".to_string());
79        cmds.push("./gradlew build".to_string());
80        cmds.push("./gradlew test".to_string());
81    }
82    if dir.join("pyproject.toml").exists() || dir.join("setup.py").exists() {
83        stack.push("Python".to_string());
84        cmds.push("pip install -e .".to_string());
85        cmds.push("pytest".to_string());
86    }
87    if dir.join("requirements.txt").exists() && !stack.iter().any(|s| s == "Python") {
88        stack.push("Python".to_string());
89        cmds.push("pip install -r requirements.txt".to_string());
90        cmds.push("pytest".to_string());
91    }
92    if dir.join("go.mod").exists() {
93        stack.push("Go".to_string());
94        cmds.push("go build ./...".to_string());
95        cmds.push("go test ./...".to_string());
96    }
97
98    (stack, cmds)
99}
100
101/// Return framework-specific hints.
102fn framework_hints(dir: &Path) -> Vec<String> {
103    let mut hints = Vec::new();
104    if dir.join("Cargo.toml").exists() {
105        hints.push("Use `anyhow::Result` for error handling".to_string());
106        hints.push("Run `cargo fmt` before committing".to_string());
107    }
108    if dir.join(".eslintrc.json").exists() || dir.join(".eslintrc.js").exists() {
109        hints.push("Run linter before committing".to_string());
110    }
111    if dir.join("tsconfig.json").exists() {
112        hints.push("Ensure TypeScript strict mode compliance".to_string());
113    }
114    hints
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn test_rust_project() {
123        let tmp = tempfile::tempdir().unwrap();
124        std::fs::write(tmp.path().join("Cargo.toml"), "[package]\nname = \"test\"").unwrap();
125        let result = generate_project_instructions(tmp.path());
126        assert!(result.contains("Rust"));
127        assert!(result.contains("cargo build"));
128        assert!(result.contains("cargo test"));
129    }
130
131    #[test]
132    fn test_node_project() {
133        let tmp = tempfile::tempdir().unwrap();
134        std::fs::write(tmp.path().join("package.json"), r#"{"dependencies":{"react":"^18"}}"#).unwrap();
135        let result = generate_project_instructions(tmp.path());
136        assert!(result.contains("Node.js"));
137        assert!(result.contains("React"));
138        assert!(result.contains("npm install"));
139    }
140
141    #[test]
142    fn test_python_project() {
143        let tmp = tempfile::tempdir().unwrap();
144        std::fs::write(tmp.path().join("pyproject.toml"), "[project]\nname = \"test\"").unwrap();
145        let result = generate_project_instructions(tmp.path());
146        assert!(result.contains("Python"));
147        assert!(result.contains("pytest"));
148    }
149
150    #[test]
151    fn test_empty_project() {
152        let tmp = tempfile::tempdir().unwrap();
153        let result = generate_project_instructions(tmp.path());
154        assert!(result.contains("Project Instructions"));
155        assert!(result.contains("Follow existing patterns"));
156    }
157
158    #[test]
159    fn test_go_project() {
160        let tmp = tempfile::tempdir().unwrap();
161        std::fs::write(tmp.path().join("go.mod"), "module example.com/test").unwrap();
162        let result = generate_project_instructions(tmp.path());
163        assert!(result.contains("Go"));
164        assert!(result.contains("go test"));
165    }
166}