Skip to main content

driven/cli/
init.rs

1//! Init command - initialize driven in a project
2
3use crate::{DrivenConfig, Result};
4use dialoguer::{Confirm, MultiSelect, Select, theme::ColorfulTheme};
5use std::path::Path;
6
7/// Init command handler
8#[derive(Debug)]
9pub struct InitCommand;
10
11impl InitCommand {
12    /// Run the init command
13    pub fn run(project_root: &Path, interactive: bool) -> Result<()> {
14        if interactive {
15            Self::run_interactive(project_root)
16        } else {
17            Self::run_default(project_root)
18        }
19    }
20
21    fn run_interactive(project_root: &Path) -> Result<()> {
22        let theme = ColorfulTheme::default();
23
24        println!();
25        println!("🚀 Welcome to Driven - AI-Assisted Development Orchestrator");
26        println!();
27
28        // Select editors
29        let editor_items = &[
30            "Cursor (.cursor/rules/)",
31            "GitHub Copilot (.github/copilot-instructions.md)",
32            "Windsurf (.windsurf/rules/)",
33            "Claude (CLAUDE.md)",
34            "Aider (.aider.conf.yml)",
35            "Cline (.clinerules)",
36        ];
37
38        let selected = MultiSelect::with_theme(&theme)
39            .with_prompt("Which AI editors do you use?")
40            .items(editor_items)
41            .defaults(&[true, true, false, false, false, false])
42            .interact()
43            .map_err(|e| crate::DrivenError::Cli(e.to_string()))?;
44
45        let mut config = DrivenConfig::default();
46        config.editors.cursor = selected.contains(&0);
47        config.editors.copilot = selected.contains(&1);
48        config.editors.windsurf = selected.contains(&2);
49        config.editors.claude = selected.contains(&3);
50        config.editors.aider = selected.contains(&4);
51        config.editors.cline = selected.contains(&5);
52
53        // Select template
54        let template_items = &[
55            "Rust Workspace",
56            "TypeScript/JavaScript",
57            "Full-Stack (Rust + TypeScript)",
58            "CLI Tool",
59            "Library",
60            "Custom (start empty)",
61        ];
62
63        let template_idx = Select::with_theme(&theme)
64            .with_prompt("What type of project is this?")
65            .items(template_items)
66            .default(0)
67            .interact()
68            .map_err(|e| crate::DrivenError::Cli(e.to_string()))?;
69
70        config.templates.project = Some(match template_idx {
71            0 => "rust-workspace".to_string(),
72            1 => "typescript-monorepo".to_string(),
73            2 => "fullstack".to_string(),
74            3 => "cli-tool".to_string(),
75            4 => "library".to_string(),
76            _ => "custom".to_string(),
77        });
78
79        // Enable auto-sync?
80        config.sync.watch = Confirm::with_theme(&theme)
81            .with_prompt("Enable auto-sync when rules change?")
82            .default(true)
83            .interact()
84            .map_err(|e| crate::DrivenError::Cli(e.to_string()))?;
85
86        // Create config
87        Self::create_config(project_root, &config)?;
88
89        // Create initial rules
90        Self::create_initial_rules(project_root, &config)?;
91
92        super::print_success("Driven initialized successfully!");
93        println!();
94
95        // Show next steps
96        println!("📋 Next steps:");
97        println!("   1. Edit .driven/rules.drv to customize your AI rules");
98        println!("   2. Run 'driven sync' to propagate rules to all editors");
99        println!("   3. Run 'driven analyze' to generate context from your codebase");
100        println!();
101
102        Ok(())
103    }
104
105    fn run_default(project_root: &Path) -> Result<()> {
106        let config = DrivenConfig::default();
107        Self::create_config(project_root, &config)?;
108        Self::create_initial_rules(project_root, &config)?;
109
110        super::print_success("Driven initialized with default settings");
111        super::print_info("Run 'driven init -i' for interactive setup");
112
113        Ok(())
114    }
115
116    fn create_config(project_root: &Path, config: &DrivenConfig) -> Result<()> {
117        let config_dir = project_root.join(".driven");
118        std::fs::create_dir_all(&config_dir).map_err(|e| {
119            crate::DrivenError::Config(format!("Failed to create .driven directory: {}", e))
120        })?;
121
122        let config_path = config_dir.join("config.toml");
123        let toml = toml::to_string_pretty(config).map_err(|e| {
124            crate::DrivenError::Config(format!("Failed to serialize config: {}", e))
125        })?;
126
127        std::fs::write(&config_path, toml)
128            .map_err(|e| crate::DrivenError::Config(format!("Failed to write config: {}", e)))?;
129
130        Ok(())
131    }
132
133    fn create_initial_rules(project_root: &Path, config: &DrivenConfig) -> Result<()> {
134        let rules_path = project_root.join(&config.sync.source_of_truth);
135
136        // Ensure parent directory exists
137        if let Some(parent) = rules_path.parent() {
138            std::fs::create_dir_all(parent).map_err(|e| {
139                crate::DrivenError::Config(format!("Failed to create rules directory: {}", e))
140            })?;
141        }
142
143        // Create initial markdown rules (can be converted to binary later)
144        let initial_rules = r#"# AI Development Rules
145
146## Persona
147
148You are an expert software engineer with deep knowledge of the project's tech stack.
149
150### Traits
151- Precise and detail-oriented
152- Proactive about edge cases
153- Explains reasoning clearly
154
155### Principles
156- Write idiomatic code
157- Prefer explicit over implicit
158- Test thoroughly
159
160## Standards
161
162### Style
163- Follow project's existing code style
164- Use consistent formatting
165
166### Naming
167- Use descriptive, meaningful names
168- Follow language conventions
169
170### Error Handling
171- Handle all error cases explicitly
172- Provide helpful error messages
173
174### Testing
175- Write tests for all new features
176- Maintain existing test coverage
177
178## Context
179
180### Focus
181- src/
182- tests/
183
184### Exclude
185- target/
186- node_modules/
187- .git/
188"#;
189
190        let rules_md_path = rules_path.with_extension("md");
191        std::fs::write(&rules_md_path, initial_rules)
192            .map_err(|e| crate::DrivenError::Config(format!("Failed to write rules: {}", e)))?;
193
194        Ok(())
195    }
196}