use std::path::Path;
pub fn generate_project_instructions(working_dir: &Path) -> String {
let mut sections = Vec::new();
let project_name = working_dir
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_else(|| "project".to_string());
sections.push(format!("# {} — Project Instructions\n", project_name));
let (stack, build_cmds) = detect_project(working_dir);
if !stack.is_empty() {
sections.push(format!("**Tech Stack:** {}\n", stack.join(", ")));
}
if !build_cmds.is_empty() {
sections.push("## Build & Test\n".to_string());
for cmd in &build_cmds {
sections.push(format!("- `{}`", cmd));
}
sections.push(String::new());
}
sections.push("## Code Style\n".to_string());
sections.push("- Follow existing patterns in the codebase".to_string());
sections.push("- Write tests for new functionality".to_string());
for hint in framework_hints(working_dir) {
sections.push(format!("- {}", hint));
}
sections.join("\n")
}
fn detect_project(dir: &Path) -> (Vec<String>, Vec<String>) {
let mut stack = Vec::new();
let mut cmds = Vec::new();
if dir.join("Cargo.toml").exists() {
stack.push("Rust".to_string());
cmds.push("cargo build".to_string());
cmds.push("cargo test".to_string());
cmds.push("cargo clippy".to_string());
}
if dir.join("package.json").exists() {
stack.push("Node.js".to_string());
if dir.join("pnpm-lock.yaml").exists() {
cmds.push("pnpm install".to_string());
cmds.push("pnpm test".to_string());
} else if dir.join("yarn.lock").exists() {
cmds.push("yarn install".to_string());
cmds.push("yarn test".to_string());
} else {
cmds.push("npm install".to_string());
cmds.push("npm test".to_string());
}
if let Ok(pkg) = std::fs::read_to_string(dir.join("package.json")) {
if pkg.contains("\"react\"") { stack.push("React".to_string()); }
if pkg.contains("\"vue\"") { stack.push("Vue".to_string()); }
if pkg.contains("\"next\"") { stack.push("Next.js".to_string()); }
if pkg.contains("\"typescript\"") { stack.push("TypeScript".to_string()); }
}
}
if dir.join("pom.xml").exists() {
stack.push("Java/Maven".to_string());
cmds.push("mvn compile".to_string());
cmds.push("mvn test".to_string());
}
if dir.join("build.gradle").exists() || dir.join("build.gradle.kts").exists() {
stack.push("Java/Gradle".to_string());
cmds.push("./gradlew build".to_string());
cmds.push("./gradlew test".to_string());
}
if dir.join("pyproject.toml").exists() || dir.join("setup.py").exists() {
stack.push("Python".to_string());
cmds.push("pip install -e .".to_string());
cmds.push("pytest".to_string());
}
if dir.join("requirements.txt").exists() && !stack.iter().any(|s| s == "Python") {
stack.push("Python".to_string());
cmds.push("pip install -r requirements.txt".to_string());
cmds.push("pytest".to_string());
}
if dir.join("go.mod").exists() {
stack.push("Go".to_string());
cmds.push("go build ./...".to_string());
cmds.push("go test ./...".to_string());
}
(stack, cmds)
}
fn framework_hints(dir: &Path) -> Vec<String> {
let mut hints = Vec::new();
if dir.join("Cargo.toml").exists() {
hints.push("Use `anyhow::Result` for error handling".to_string());
hints.push("Run `cargo fmt` before committing".to_string());
}
if dir.join(".eslintrc.json").exists() || dir.join(".eslintrc.js").exists() {
hints.push("Run linter before committing".to_string());
}
if dir.join("tsconfig.json").exists() {
hints.push("Ensure TypeScript strict mode compliance".to_string());
}
hints
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rust_project() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join("Cargo.toml"), "[package]\nname = \"test\"").unwrap();
let result = generate_project_instructions(tmp.path());
assert!(result.contains("Rust"));
assert!(result.contains("cargo build"));
assert!(result.contains("cargo test"));
}
#[test]
fn test_node_project() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join("package.json"), r#"{"dependencies":{"react":"^18"}}"#).unwrap();
let result = generate_project_instructions(tmp.path());
assert!(result.contains("Node.js"));
assert!(result.contains("React"));
assert!(result.contains("npm install"));
}
#[test]
fn test_python_project() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join("pyproject.toml"), "[project]\nname = \"test\"").unwrap();
let result = generate_project_instructions(tmp.path());
assert!(result.contains("Python"));
assert!(result.contains("pytest"));
}
#[test]
fn test_empty_project() {
let tmp = tempfile::tempdir().unwrap();
let result = generate_project_instructions(tmp.path());
assert!(result.contains("Project Instructions"));
assert!(result.contains("Follow existing patterns"));
}
#[test]
fn test_go_project() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join("go.mod"), "module example.com/test").unwrap();
let result = generate_project_instructions(tmp.path());
assert!(result.contains("Go"));
assert!(result.contains("go test"));
}
}