miyabi_cli/commands/
init.rs

1//! Init command - Initialize new Miyabi project
2
3use crate::error::{CliError, Result};
4use colored::Colorize;
5use std::fs;
6use std::path::{Path, PathBuf};
7
8pub struct InitCommand {
9    pub name: String,
10    #[allow(dead_code)] // Reserved for GitHub repo creation (public vs private)
11    pub private: bool,
12}
13
14impl InitCommand {
15    pub fn new(name: String, private: bool) -> Self {
16        Self { name, private }
17    }
18
19    pub async fn execute(&self) -> Result<()> {
20        println!("{}", "🚀 Initializing new Miyabi project...".cyan().bold());
21
22        // Validate project name
23        self.validate_project_name()?;
24
25        // Create project directory
26        let project_dir = self.create_project_directory()?;
27
28        // Initialize git repository
29        self.init_git_repository(&project_dir)?;
30
31        // Create basic structure
32        self.create_project_structure(&project_dir)?;
33
34        // Create configuration files
35        self.create_config_files(&project_dir)?;
36
37        println!();
38        println!("{}", "✅ Project initialized successfully!".green().bold());
39        println!();
40        println!("Next steps:");
41        println!("  cd {}", self.name);
42        println!("  export GITHUB_TOKEN=ghp_xxx");
43        println!("  miyabi status");
44
45        Ok(())
46    }
47
48    fn validate_project_name(&self) -> Result<()> {
49        // Check if name is valid
50        if self.name.is_empty() {
51            return Err(CliError::InvalidProjectName(
52                "Project name cannot be empty".to_string(),
53            ));
54        }
55
56        // Check if name contains invalid characters
57        if !self
58            .name
59            .chars()
60            .all(|c| c.is_alphanumeric() || c == '-' || c == '_')
61        {
62            return Err(CliError::InvalidProjectName(
63                "Project name can only contain alphanumeric characters, hyphens, and underscores"
64                    .to_string(),
65            ));
66        }
67
68        Ok(())
69    }
70
71    fn create_project_directory(&self) -> Result<PathBuf> {
72        let project_dir = PathBuf::from(&self.name);
73
74        // Check if directory already exists
75        if project_dir.exists() {
76            return Err(CliError::ProjectExists(self.name.clone()));
77        }
78
79        // Create directory
80        fs::create_dir(&project_dir)?;
81        println!("  Created directory: {}", project_dir.display());
82
83        Ok(project_dir)
84    }
85
86    fn init_git_repository(&self, project_dir: &Path) -> Result<()> {
87        use std::process::Command;
88
89        // Initialize git repository
90        let output = Command::new("git")
91            .args(["init"])
92            .current_dir(project_dir)
93            .output()?;
94
95        if !output.status.success() {
96            return Err(CliError::Io(std::io::Error::other(
97                "Failed to initialize git repository",
98            )));
99        }
100
101        println!("  Initialized git repository");
102        Ok(())
103    }
104
105    fn create_project_structure(&self, project_dir: &Path) -> Result<()> {
106        // Create standard directories
107        let dirs = vec![
108            ".github/workflows",
109            ".claude/agents/specs/coding",
110            ".claude/agents/specs/business",
111            ".claude/agents/prompts/coding",
112            ".claude/agents/prompts/business",
113            ".claude/commands",
114            ".claude/prompts",
115            ".claude/templates",
116            "docs",
117            "scripts",
118            "logs",
119            "reports",
120        ];
121
122        for dir in dirs {
123            let dir_path = project_dir.join(dir);
124            fs::create_dir_all(&dir_path)?;
125        }
126
127        // Create CLAUDE.md (project context file)
128        self.create_claude_md(project_dir)?;
129
130        // Create essential .claude files
131        self.create_claude_files(project_dir)?;
132
133        println!("  Created project structure");
134        Ok(())
135    }
136
137    fn create_claude_md(&self, project_dir: &Path) -> Result<()> {
138        let claude_md = format!(
139            r#"# Claude Code プロジェクト設定
140
141このファイルは、Claude Codeが自動的に参照するプロジェクトコンテキストファイルです。
142
143## プロジェクト概要
144
145**{}** - Miyabi自律型開発プロジェクト
146
147## アーキテクチャ
148
149### コアコンポーネント
150
1511. **Agent System** - 自律実行Agent(Miyabi Framework)
1522. **GitHub OS Integration** - GitHubをOSとして活用
1533. **Label System** - 53ラベル体系による状態管理
154
155### ディレクトリ構造
156
157```
158{}/
159├── .claude/                    # Claude Code設定
160│   ├── agents/                # Agent仕様・プロンプト
161│   ├── commands/              # カスタムコマンド
162│   └── prompts/               # 実行プロンプト
163├── .github/                   # GitHub設定
164│   └── workflows/             # GitHub Actions
165├── docs/                      # ドキュメント
166├── scripts/                   # 自動化スクリプト
167├── logs/                      # ログファイル
168└── reports/                   # レポート出力
169```
170
171## 開発ガイドライン
172
173### コミット規約
174- Conventional Commits準拠
175- `feat:`, `fix:`, `chore:`, `docs:`, etc.
176
177### セキュリティ
178- トークンは環境変数
179- `.miyabi.yml`は`.gitignore`に追加済み
180
181## 環境変数
182
183```bash
184GITHUB_TOKEN=ghp_xxx        # GitHubアクセストークン
185ANTHROPIC_API_KEY=sk-xxx    # Anthropic APIキー(Agent実行時)
186```
187
188## 実行例
189
190```bash
191# ステータス確認
192miyabi status
193
194# Agent実行
195miyabi agent coordinator --issue 1
196
197# テスト実行
198cargo test --all
199
200# Linter実行
201cargo clippy --all-targets
202```
203
204---
205
206**このファイルはClaude Codeが自動参照します。プロジェクトのコンテキストとして常に最新に保ってください。**
207"#,
208            self.name, self.name
209        );
210
211        fs::write(project_dir.join("CLAUDE.md"), claude_md)?;
212        Ok(())
213    }
214
215    fn create_claude_files(&self, project_dir: &Path) -> Result<()> {
216        // Create .claude/README.md
217        let claude_readme = r#"# .claude Directory
218
219Claude Code設定ディレクトリ - プロジェクト固有の設定とプロンプト
220
221## 構造
222
223- `agents/` - Agent仕様とプロンプト
224  - `specs/coding/` - コーディング系Agent仕様
225  - `specs/business/` - ビジネス系Agent仕様
226  - `prompts/coding/` - 実行プロンプト
227- `commands/` - カスタムスラッシュコマンド
228- `prompts/` - 汎用プロンプト
229- `templates/` - テンプレートファイル
230
231## カスタムコマンド
232
233`.claude/commands/` 配下に `*.md` ファイルを作成することで、
234カスタムスラッシュコマンドを定義できます。
235
236例: `.claude/commands/test.md` → `/test` コマンド
237
238## Agent仕様
239
240Agent仕様ファイル(`.claude/agents/specs/`)で、各Agentの役割・権限・エスカレーション条件を定義します。
241"#;
242        fs::write(project_dir.join(".claude/README.md"), claude_readme)?;
243
244        // Create .claude/QUICK_START.md
245        let quick_start = format!(
246            r#"# {} - Quick Start Guide
247
248## 🚀 3分で始めるMiyabi
249
250### 1. 環境変数設定
251
252```bash
253export GITHUB_TOKEN=ghp_xxx
254export ANTHROPIC_API_KEY=sk-xxx
255```
256
257### 2. ステータス確認
258
259```bash
260miyabi status
261```
262
263### 3. Issue作成
264
265GitHubでIssueを作成し、以下のラベルを付与:
266- `type:feature` または `type:bug`
267- `priority:P1-High`
268
269### 4. Agent実行
270
271```bash
272miyabi agent coordinator --issue 1
273```
274
275## 📚 詳細ドキュメント
276
277- [CLAUDE.md](../CLAUDE.md) - プロジェクトコンテキスト
278- [.claude/README.md](./README.md) - .claudeディレクトリ説明
279
280---
281
282**Miyabi** - Beauty in Autonomous Development 🌸
283"#,
284            self.name
285        );
286        fs::write(project_dir.join(".claude/QUICK_START.md"), quick_start)?;
287
288        Ok(())
289    }
290
291    fn create_config_files(&self, project_dir: &Path) -> Result<()> {
292        // Create .miyabi.yml
293        let miyabi_config = format!(
294            r#"# Miyabi Configuration
295project_name: {}
296version: "0.1.0"
297
298# GitHub settings (use environment variables for sensitive data)
299# github_token: ${{{{ GITHUB_TOKEN }}}}
300
301# Agent settings
302agents:
303  enabled: true
304  use_worktree: true
305  worktree_base_path: ".worktrees"
306
307# Logging
308logging:
309  level: info
310  directory: "./logs"
311
312# Reporting
313reporting:
314  directory: "./reports"
315"#,
316            self.name
317        );
318
319        fs::write(project_dir.join(".miyabi.yml"), miyabi_config)?;
320
321        // Create .gitignore
322        let gitignore = r#"# Miyabi
323.miyabi.yml
324.worktrees/
325logs/
326reports/
327*.log
328
329# Environment
330.env
331.env.local
332
333# Dependencies
334node_modules/
335target/
336
337# IDE
338.vscode/
339.idea/
340*.swp
341*.swo
342"#;
343
344        fs::write(project_dir.join(".gitignore"), gitignore)?;
345
346        // Create README.md
347        let readme = format!(
348            r#"# {}
349
350Miyabi autonomous development project.
351
352## Setup
353
3541. Set GitHub token:
355   ```bash
356   export GITHUB_TOKEN=ghp_xxx
357   ```
358
3592. Check status:
360   ```bash
361   miyabi status
362   ```
363
3643. Run agent:
365   ```bash
366   miyabi agent coordinator --issue 1
367   ```
368
369## Documentation
370
371- See `docs/` directory for detailed documentation
372- See `.claude/agents/specs/` for agent specifications
373"#,
374            self.name
375        );
376
377        fs::write(project_dir.join("README.md"), readme)?;
378
379        println!("  Created configuration files");
380        Ok(())
381    }
382}
383
384#[cfg(test)]
385mod tests {
386    use super::*;
387
388    #[test]
389    fn test_validate_project_name() {
390        let valid_cmd = InitCommand::new("my-project".to_string(), false);
391        assert!(valid_cmd.validate_project_name().is_ok());
392
393        let valid_cmd = InitCommand::new("my_project_123".to_string(), false);
394        assert!(valid_cmd.validate_project_name().is_ok());
395
396        let invalid_cmd = InitCommand::new("".to_string(), false);
397        assert!(invalid_cmd.validate_project_name().is_err());
398
399        let invalid_cmd = InitCommand::new("my project".to_string(), false);
400        assert!(invalid_cmd.validate_project_name().is_err());
401
402        let invalid_cmd = InitCommand::new("my@project".to_string(), false);
403        assert!(invalid_cmd.validate_project_name().is_err());
404    }
405}