foundry_mcp/cli/commands/
delete_spec.rs1use crate::cli::args::DeleteSpecArgs;
4use crate::core::{project, spec};
5use crate::types::responses::{DeleteSpecResponse, FoundryResponse, ValidationStatus};
6use anyhow::{Context, Result};
7use std::fs;
8
9pub async fn execute(args: DeleteSpecArgs) -> Result<FoundryResponse<DeleteSpecResponse>> {
10 validate_args(&args)?;
12
13 validate_project_exists(&args.project_name)?;
15
16 if !spec::spec_exists(&args.project_name, &args.spec_name)? {
18 return Err(anyhow::anyhow!(
19 "Spec '{}' not found in project '{}'. Use 'foundry load-project {}' to see available specs.",
20 args.spec_name,
21 args.project_name,
22 args.project_name
23 ));
24 }
25
26 let spec_path = spec::get_spec_path(&args.project_name, &args.spec_name)?;
28 let files_to_delete = get_spec_files(&spec_path)?;
29
30 if args.confirm.to_lowercase() != "true" {
32 return Err(anyhow::anyhow!(
33 "Deletion not confirmed. Set --confirm true to proceed with deleting spec '{}' and all its files. Got: '{}'",
34 args.spec_name,
35 args.confirm
36 ));
37 }
38
39 spec::delete_spec(&args.project_name, &args.spec_name)
41 .with_context(|| format!("Failed to delete spec '{}'", args.spec_name))?;
42
43 let response_data = DeleteSpecResponse {
44 project_name: args.project_name.clone(),
45 spec_name: args.spec_name.clone(),
46 spec_path: spec_path.to_string_lossy().to_string(),
47 files_deleted: files_to_delete,
48 };
49
50 Ok(FoundryResponse {
51 data: response_data,
52 next_steps: generate_next_steps(&args),
53 validation_status: ValidationStatus::Complete,
54 workflow_hints: generate_workflow_hints(&args),
55 })
56}
57
58fn validate_args(args: &DeleteSpecArgs) -> Result<()> {
60 if args.project_name.trim().is_empty() {
61 return Err(anyhow::anyhow!("Project name cannot be empty"));
62 }
63
64 if args.spec_name.trim().is_empty() {
65 return Err(anyhow::anyhow!("Spec name cannot be empty"));
66 }
67
68 if !args.spec_name.contains('_') {
70 return Err(anyhow::anyhow!(
71 "Invalid spec name format '{}'. Expected format: YYYYMMDD_HHMMSS_feature_name",
72 args.spec_name
73 ));
74 }
75
76 Ok(())
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 get_spec_files(spec_path: &std::path::Path) -> Result<Vec<String>> {
92 let mut files = Vec::new();
93
94 if !spec_path.exists() {
95 return Ok(files);
96 }
97
98 let expected_files = ["spec.md", "task-list.md", "notes.md"];
100
101 for file_name in &expected_files {
102 let file_path = spec_path.join(file_name);
103 if file_path.exists() {
104 files.push(file_path.to_string_lossy().to_string());
105 }
106 }
107
108 if let Ok(entries) = fs::read_dir(spec_path) {
110 for entry in entries.flatten() {
111 let path = entry.path();
112 if path.is_file() {
113 let file_name = path
114 .file_name()
115 .and_then(|name| name.to_str())
116 .unwrap_or("unknown");
117
118 if !expected_files.contains(&file_name) {
120 files.push(path.to_string_lossy().to_string());
121 }
122 }
123 }
124 }
125
126 Ok(files)
127}
128
129fn generate_next_steps(args: &DeleteSpecArgs) -> Vec<String> {
131 vec![
132 format!(
133 "Successfully deleted spec '{}' from project '{}'",
134 args.spec_name, args.project_name
135 ),
136 "All spec files have been permanently removed".to_string(),
137 format!(
138 "You can view remaining specs: foundry load-project {}",
139 args.project_name
140 ),
141 format!(
142 "You can create a new spec: foundry create-spec {} <feature_name>",
143 args.project_name
144 ),
145 "Deletion cannot be undone - you might consider backing up important specs before deletion"
146 .to_string(),
147 ]
148}
149
150fn generate_workflow_hints(args: &DeleteSpecArgs) -> Vec<String> {
152 vec![
153 format!("Deleted spec: {}", args.spec_name),
154 "This action cannot be undone".to_string(),
155 "All associated files (spec.md, task-list.md, notes.md) have been removed".to_string(),
156 "You can use 'foundry list-projects' to see project status after deletion".to_string(),
157 "You might consider archiving completed specs rather than deleting for future reference"
158 .to_string(),
159 "You can create new specs to continue feature development".to_string(),
160 ]
161}