Skip to main content

foundry_mcp/core/ops/
delete_spec.rs

1//! Core op for deleting a spec (tool-agnostic)
2
3use anyhow::{Context, Result};
4
5use crate::core::foundry;
6use crate::types::responses::{DeleteSpecResponse, FoundryResponse, ValidationStatus};
7
8#[derive(Debug, Clone)]
9pub struct Input {
10    pub project_name: String,
11    pub spec_name: String,
12    pub confirm: String,
13}
14
15pub async fn run(input: Input) -> Result<FoundryResponse<DeleteSpecResponse>> {
16    let foundry = foundry::get_default_foundry()?;
17
18    validate_args(&input)?;
19    validate_project_exists(&foundry, &input.project_name).await?;
20
21    // Check if spec exists by trying to load it
22    let response_data = match foundry
23        .load_spec(&input.project_name, &input.spec_name)
24        .await
25    {
26        Ok(spec) => {
27            let files_to_delete = vec![
28                format!("{}/spec.md", spec.name),
29                format!("{}/task-list.md", spec.name),
30                format!("{}/notes.md", spec.name),
31            ];
32
33            if input.confirm.to_lowercase() != "true" {
34                return Err(anyhow::anyhow!(
35                    "Deletion not confirmed. Set --confirm true to proceed with deleting spec '{}' and all its files. Got: '{}'",
36                    input.spec_name,
37                    input.confirm
38                ));
39            }
40
41            foundry
42                .delete_spec(&input.project_name, &input.spec_name)
43                .await
44                .with_context(|| format!("Failed to delete spec '{}'", input.spec_name))?;
45
46            DeleteSpecResponse {
47                project_name: input.project_name.clone(),
48                spec_name: input.spec_name.clone(),
49                spec_path: format!(
50                    "~/.foundry/{}/specs/{}",
51                    input.project_name, input.spec_name
52                ),
53                files_deleted: files_to_delete,
54            }
55        }
56        Err(_) => {
57            return Err(anyhow::anyhow!(
58                "Spec '{}' not found in project '{}'. Use 'mcp_foundry_load_project {}' to see available specs.",
59                input.spec_name,
60                input.project_name,
61                input.project_name
62            ));
63        }
64    };
65
66    Ok(FoundryResponse {
67        data: response_data,
68        next_steps: generate_next_steps(&input),
69        validation_status: ValidationStatus::Complete,
70        workflow_hints: generate_workflow_hints(&input),
71    })
72}
73
74fn validate_args(input: &Input) -> Result<()> {
75    if input.project_name.trim().is_empty() {
76        return Err(anyhow::anyhow!("Project name cannot be empty"));
77    }
78    if input.spec_name.trim().is_empty() {
79        return Err(anyhow::anyhow!("Spec name cannot be empty"));
80    }
81    if !input.spec_name.contains('_') {
82        return Err(anyhow::anyhow!(
83            "Invalid spec name format '{}'. Expected format: YYYYMMDD_HHMMSS_feature_name",
84            input.spec_name
85        ));
86    }
87    Ok(())
88}
89
90async fn validate_project_exists(
91    foundry: &foundry::Foundry<crate::core::backends::filesystem::FilesystemBackend>,
92    project_name: &str,
93) -> Result<()> {
94    if !foundry.project_exists(project_name).await? {
95        return Err(anyhow::anyhow!(
96            "Project '{}' not found. Use 'mcp_foundry_list_projects' to see available projects.",
97            project_name
98        ));
99    }
100    Ok(())
101}
102
103fn generate_next_steps(input: &Input) -> Vec<String> {
104    vec![
105        format!(
106            "Successfully deleted spec '{}' from project '{}'",
107            input.spec_name, input.project_name
108        ),
109        "All spec files have been permanently removed".to_string(),
110        format!(
111            "You can view remaining specs: mcp_foundry_load_project {}",
112            input.project_name
113        ),
114        format!(
115            "You can create a new spec: mcp_foundry_create_spec {} <feature_name>",
116            input.project_name
117        ),
118        "Deletion cannot be undone - you might consider backing up important specs before deletion"
119            .to_string(),
120    ]
121}
122
123fn generate_workflow_hints(input: &Input) -> Vec<String> {
124    vec![
125        format!("Deleted spec: {}", input.spec_name),
126        "This action cannot be undone".to_string(),
127        "All associated files (spec.md, task-list.md, notes.md) have been removed".to_string(),
128        "You can use 'mcp_foundry_list_projects' to see project status after deletion".to_string(),
129        "You might consider archiving completed specs rather than deleting for future reference"
130            .to_string(),
131        "You can create new specs to continue feature development".to_string(),
132    ]
133}