foundry_mcp/core/ops/
list_specs.rs1use anyhow::{Context, Result};
4
5use crate::core::foundry;
6use crate::types::responses::{FoundryResponse, ListSpecsResponse, SpecInfo};
7use crate::utils::response::{build_incomplete_response, build_success_response};
8
9#[derive(Debug, Clone)]
10pub struct Input {
11 pub project_name: String,
12}
13
14pub async fn run(input: Input) -> Result<FoundryResponse<ListSpecsResponse>> {
15 let foundry = foundry::get_default_foundry()?;
16
17 validate_project_exists(&foundry, &input.project_name).await?;
18
19 let specs = foundry
20 .list_specs(&input.project_name)
21 .await
22 .with_context(|| format!("Failed to list specs for project '{}'", input.project_name))?;
23
24 let spec_infos: Vec<SpecInfo> = specs
25 .into_iter()
26 .map(|spec_meta| SpecInfo {
27 name: spec_meta.name,
28 feature_name: spec_meta.feature_name,
29 created_at: spec_meta.created_at,
30 })
31 .collect();
32
33 let response_data = ListSpecsResponse {
34 project_name: input.project_name.clone(),
35 specs: spec_infos.clone(),
36 total_count: spec_infos.len(),
37 };
38
39 if response_data.specs.is_empty() {
40 let next_steps = vec![
41 "No specifications found for this project - ready for specification creation"
42 .to_string(),
43 format!(
44 "You can create your first specification: mcp_foundry_create_spec {} <feature_name>",
45 input.project_name
46 ),
47 "You can use 'mcp_foundry_load_project' to see full project context".to_string(),
48 ];
49
50 let workflow_hints = vec![
51 "You can start by creating specifications to track development features".to_string(),
52 "Each spec includes implementation notes and task lists for comprehensive planning"
53 .to_string(),
54 ];
55
56 Ok(build_incomplete_response(
57 response_data,
58 next_steps,
59 workflow_hints,
60 ))
61 } else {
62 let spec_count = response_data.specs.len();
63 let mut next_steps = vec![
64 format!("Found {} specification(s) in project", spec_count),
65 format!(
66 "You can load a specific spec: mcp_foundry_load_spec {} <spec_name>",
67 input.project_name
68 ),
69 ];
70
71 if spec_count <= 5 {
72 next_steps.push("Available specs:".to_string());
73 for spec in &response_data.specs {
74 next_steps.push(format!(" - {} ({})", spec.name, spec.feature_name));
75 }
76 }
77
78 next_steps.push(format!(
79 "You can create a new spec: mcp_foundry_create_spec {} <feature_name>",
80 input.project_name
81 ));
82
83 let workflow_hints = vec![
84 "Specifications are timestamped and organized by feature for easy navigation"
85 .to_string(),
86 format!("Total specs: {}", spec_count),
87 "You can load individual specs to see detailed implementation plans".to_string(),
88 "Specs include specification content, notes, and task lists for complete context"
89 .to_string(),
90 ];
91
92 Ok(build_success_response(
93 response_data,
94 next_steps,
95 workflow_hints,
96 ))
97 }
98}
99
100async fn validate_project_exists(
101 foundry: &foundry::Foundry<crate::core::backends::filesystem::FilesystemBackend>,
102 project_name: &str,
103) -> Result<()> {
104 if !foundry.project_exists(project_name).await? {
105 return Err(anyhow::anyhow!(
106 "Project '{}' not found. Use 'mcp_foundry_list_projects' to see available projects.",
107 project_name
108 ));
109 }
110 Ok(())
111}