foundry_mcp/cli/commands/
load_spec.rs

1//! Implementation of the load_spec command
2
3use 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
12    validate_project_exists(&args.project_name)?;
13
14    // Load project summary for context
15    let project_summary = load_project_summary(&args.project_name)?;
16
17    // Handle two cases: list specs or load specific spec
18    match args.spec_name {
19        None => {
20            // List available specs
21            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            // Load specific spec
53            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(), // Empty when loading specific spec
67            };
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
79/// Validate that project exists
80fn 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
90/// Load project summary for context
91fn 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
100/// Generate next steps for spec listing
101fn 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
140/// Generate next steps for specific spec loading
141fn 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
155/// Generate workflow hints for spec listing
156fn 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
183/// Generate workflow hints for specific spec loading
184fn 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}