foundry_mcp/cli/commands/
load_spec.rs1use crate::cli::args::LoadSpecArgs;
4use crate::core::{filesystem, project, spec};
5use crate::types::responses::{
6 FoundryResponse, LoadSpecResponse, SpecContent, SpecInfo, ValidationStatus,
7};
8use anyhow::{Context, Result};
9
10pub async fn execute(args: LoadSpecArgs) -> Result<FoundryResponse<LoadSpecResponse>> {
11 validate_project_exists(&args.project_name)?;
13
14 let project_summary = load_project_summary(&args.project_name)?;
16
17 match args.spec_name {
19 None => {
20 let specs = spec::list_specs(&args.project_name)?;
22 let available_specs: Vec<SpecInfo> = specs
23 .into_iter()
24 .map(|spec_meta| SpecInfo {
25 name: spec_meta.name,
26 feature_name: spec_meta.feature_name,
27 created_at: spec_meta.created_at,
28 })
29 .collect();
30
31 let response_data = LoadSpecResponse {
32 project_name: args.project_name.clone(),
33 project_summary,
34 spec_name: None,
35 created_at: None,
36 spec_content: None,
37 available_specs: available_specs.clone(),
38 };
39
40 Ok(FoundryResponse {
41 data: response_data,
42 next_steps: generate_listing_next_steps(&args.project_name, &available_specs),
43 validation_status: if available_specs.is_empty() {
44 ValidationStatus::Incomplete
45 } else {
46 ValidationStatus::Complete
47 },
48 workflow_hints: generate_listing_workflow_hints(&available_specs),
49 })
50 }
51 Some(spec_name) => {
52 let spec_data = spec::load_spec(&args.project_name, &spec_name)
54 .with_context(|| format!("Failed to load spec '{}'", spec_name))?;
55
56 let spec_content = SpecContent {
57 content: spec_data.content,
58 };
59
60 let response_data = LoadSpecResponse {
61 project_name: args.project_name.clone(),
62 project_summary,
63 spec_name: Some(spec_data.name.clone()),
64 created_at: Some(spec_data.created_at.clone()),
65 spec_content: Some(spec_content),
66 available_specs: Vec::new(), };
68
69 Ok(FoundryResponse {
70 data: response_data,
71 next_steps: generate_spec_next_steps(&args.project_name, &spec_data.name),
72 validation_status: ValidationStatus::Complete,
73 workflow_hints: generate_spec_workflow_hints(&spec_data.name),
74 })
75 }
76 }
77}
78
79fn validate_project_exists(project_name: &str) -> Result<()> {
81 if !project::project_exists(project_name)? {
82 return Err(anyhow::anyhow!(
83 "Project '{}' not found. Use 'foundry list-projects' to see available projects.",
84 project_name
85 ));
86 }
87 Ok(())
88}
89
90fn load_project_summary(project_name: &str) -> Result<String> {
92 let project_path = project::get_project_path(project_name)?;
93 let summary_path = project_path.join("summary.md");
94
95 Ok(filesystem::read_file(summary_path).unwrap_or_else(|_| {
96 "No project summary available. Consider updating the project summary for better context.".to_string()
97 }))
98}
99
100fn generate_listing_next_steps(project_name: &str, available_specs: &[SpecInfo]) -> Vec<String> {
102 if available_specs.is_empty() {
103 vec![
104 "No specifications found for this project - ready for specification creation"
105 .to_string(),
106 format!(
107 "You can create your first specification: foundry create-spec {} <feature_name>",
108 project_name
109 ),
110 "You can use 'foundry load-project' to see full project context".to_string(),
111 ]
112 } else {
113 let mut steps = vec![
114 format!(
115 "Found {} specification(s) in project",
116 available_specs.len()
117 ),
118 format!(
119 "You can load a specific spec: foundry load-spec {} <spec_name>",
120 project_name
121 ),
122 ];
123
124 if available_specs.len() <= 5 {
125 steps.push("Available specs:".to_string());
126 for spec in available_specs {
127 steps.push(format!(" - {} ({})", spec.name, spec.feature_name));
128 }
129 }
130
131 steps.push(format!(
132 "You can create a new spec: foundry create-spec {} <feature_name>",
133 project_name
134 ));
135
136 steps
137 }
138}
139
140fn generate_spec_next_steps(project_name: &str, spec_name: &str) -> Vec<String> {
142 vec![
143 format!("Spec '{}' loaded successfully", spec_name),
144 "You can review the specification content and tasks for implementation guidance"
145 .to_string(),
146 "You can use the project summary for additional context".to_string(),
147 format!(
148 "You can create a new spec: foundry create-spec {} <feature_name>",
149 project_name
150 ),
151 format!("You can list all specs: foundry load-spec {}", project_name),
152 ]
153}
154
155fn generate_listing_workflow_hints(available_specs: &[SpecInfo]) -> Vec<String> {
157 let mut hints = vec![
158 "You can use the project summary for context about all specifications".to_string(),
159 "Specifications are timestamped and organized by feature for easy navigation".to_string(),
160 ];
161
162 if available_specs.is_empty() {
163 hints.push(
164 "You can start by creating specifications to track development features".to_string(),
165 );
166 hints.push(
167 "Each spec includes implementation notes and task lists for comprehensive planning"
168 .to_string(),
169 );
170 } else {
171 hints.push(format!("Total specs: {}", available_specs.len()));
172 hints
173 .push("You can load individual specs to see detailed implementation plans".to_string());
174 hints.push(
175 "Specs include specification content, notes, and task lists for complete context"
176 .to_string(),
177 );
178 }
179
180 hints
181}
182
183fn generate_spec_workflow_hints(spec_name: &str) -> Vec<String> {
185 vec![
186 format!("Loaded spec: {}", spec_name),
187 "You must update task-list.md as work progresses".to_string(),
188 "You can add notes for design decisions and implementation details".to_string(),
189 "Spec content provides detailed feature requirements and acceptance criteria".to_string(),
190 "You can use the project summary for broader context during implementation".to_string(),
191 ]
192}