foundry_mcp/core/
project.rs

1//! Project management core logic
2
3use anyhow::Result;
4use chrono::Utc;
5use std::fs;
6use std::path::PathBuf;
7
8use crate::core::filesystem;
9use crate::types::project::{Project, ProjectConfig, ProjectMetadata};
10
11/// Create a new project structure
12pub fn create_project(config: ProjectConfig) -> Result<Project> {
13    let foundry_dir = filesystem::foundry_dir()?;
14    let project_path = foundry_dir.join(&config.name);
15    let created_at = Utc::now().to_rfc3339();
16
17    // Create project directory structure
18    filesystem::create_dir_all(&project_path)?;
19    filesystem::create_dir_all(project_path.join("specs"))?;
20
21    // Write project files
22    filesystem::write_file_atomic(project_path.join("vision.md"), &config.vision)?;
23    filesystem::write_file_atomic(project_path.join("tech-stack.md"), &config.tech_stack)?;
24    filesystem::write_file_atomic(project_path.join("summary.md"), &config.summary)?;
25
26    Ok(Project {
27        name: config.name,
28        created_at,
29        path: project_path,
30        vision: Some(config.vision),
31        tech_stack: Some(config.tech_stack),
32        summary: Some(config.summary),
33    })
34}
35
36/// Get project directory path
37pub fn get_project_path(project_name: &str) -> Result<PathBuf> {
38    let foundry_dir = filesystem::foundry_dir()?;
39    Ok(foundry_dir.join(project_name))
40}
41
42/// Check if a project exists
43pub fn project_exists(project_name: &str) -> Result<bool> {
44    let project_path = get_project_path(project_name)?;
45    Ok(project_path.exists())
46}
47
48/// List all projects
49pub fn list_projects() -> Result<Vec<ProjectMetadata>> {
50    let foundry_dir = filesystem::foundry_dir()?;
51
52    if !foundry_dir.exists() {
53        return Ok(Vec::new());
54    }
55
56    let projects: Vec<ProjectMetadata> = fs::read_dir(foundry_dir)?
57        .filter_map(|entry| {
58            let entry = entry.ok()?;
59            if entry.file_type().map(|t| t.is_dir()).unwrap_or(false) {
60                Some(entry)
61            } else {
62                None
63            }
64        })
65        .map(|entry| {
66            let project_name = entry.file_name().to_string_lossy().to_string();
67            let project_path = entry.path();
68
69            // Count specs using fold
70            let specs_dir = project_path.join("specs");
71            let spec_count = if specs_dir.exists() {
72                fs::read_dir(specs_dir)
73                    .ok()
74                    .map(|dir| {
75                        dir.filter_map(|e| e.ok())
76                            .filter(|e| e.file_type().map(|t| t.is_dir()).unwrap_or(false))
77                            .fold(0, |acc, _| acc + 1)
78                    })
79                    .unwrap_or(0)
80            } else {
81                0
82            };
83
84            // Get creation time (use directory creation time as fallback)
85            let created_at = entry
86                .metadata()
87                .ok()
88                .and_then(|m| m.created().ok())
89                .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
90                .map(|d| d.as_secs())
91                .unwrap_or(0);
92
93            ProjectMetadata {
94                name: project_name,
95                created_at: created_at.to_string(),
96                spec_count,
97                last_modified: created_at.to_string(), // TODO: Use actual last modified time
98            }
99        })
100        .collect();
101
102    Ok(projects)
103}
104
105/// Load project by name
106pub fn load_project(project_name: &str) -> Result<Project> {
107    let project_path = get_project_path(project_name)?;
108
109    if !project_path.exists() {
110        return Err(anyhow::anyhow!("Project '{}' not found", project_name));
111    }
112
113    // Read project files
114    let vision = filesystem::read_file(project_path.join("vision.md")).ok();
115    let tech_stack = filesystem::read_file(project_path.join("tech-stack.md")).ok();
116    let summary = filesystem::read_file(project_path.join("summary.md")).ok();
117
118    // Get creation time from directory metadata
119    let created_at = fs::metadata(&project_path)?
120        .created()?
121        .duration_since(std::time::UNIX_EPOCH)?
122        .as_secs()
123        .to_string();
124
125    Ok(Project {
126        name: project_name.to_string(),
127        created_at,
128        path: project_path,
129        vision,
130        tech_stack,
131        summary,
132    })
133}