foundry_mcp/core/
project.rs1use 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
11pub 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 filesystem::create_dir_all(&project_path)?;
19 filesystem::create_dir_all(project_path.join("specs"))?;
20
21 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
36pub 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
42pub fn project_exists(project_name: &str) -> Result<bool> {
44 let project_path = get_project_path(project_name)?;
45 Ok(project_path.exists())
46}
47
48pub 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 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 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(), }
99 })
100 .collect();
101
102 Ok(projects)
103}
104
105pub 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 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 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}