1use std::path::Path;
4
5pub 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 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 for hint in framework_hints(working_dir) {
35 sections.push(format!("- {}", hint));
36 }
37
38 sections.join("\n")
39}
40
41fn 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 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
101fn 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}