chant/mcp/tools/
lifecycle.rs1use anyhow::Result;
4use serde_json::Value;
5
6use crate::operations;
7use crate::spec::{load_all_specs, resolve_spec, SpecStatus};
8
9use super::super::handlers::mcp_ensure_initialized;
10use super::super::response::{mcp_error_response, mcp_text_response};
11
12pub fn tool_chant_finalize(arguments: Option<&Value>) -> Result<Value> {
13 let specs_dir = match mcp_ensure_initialized() {
14 Ok(dir) => dir,
15 Err(err_response) => return Ok(err_response),
16 };
17
18 let args = arguments.ok_or_else(|| anyhow::anyhow!("Missing arguments"))?;
19
20 let id = args
21 .get("id")
22 .and_then(|v| v.as_str())
23 .ok_or_else(|| anyhow::anyhow!("Missing required parameter: id"))?;
24
25 let force = args.get("force").and_then(|v| v.as_bool()).unwrap_or(false);
26
27 let mut spec = match resolve_spec(&specs_dir, id) {
28 Ok(s) => s,
29 Err(e) => {
30 return Ok(mcp_error_response(e.to_string()));
31 }
32 };
33
34 let spec_id = spec.id.clone();
35
36 match spec.frontmatter.status {
38 SpecStatus::Completed | SpecStatus::InProgress | SpecStatus::Failed => {
39 }
41 _ => {
42 return Ok(mcp_error_response(format!("Spec '{}' must be in_progress, completed, or failed to finalize. Current status: {:?}", spec_id, spec.frontmatter.status)));
43 }
44 }
45
46 let unchecked = spec.count_unchecked_checkboxes();
48 if unchecked > 0 {
49 return Ok(mcp_error_response(format!("Spec '{}' has {} unchecked acceptance criteria. All criteria must be checked before finalization.", spec_id, unchecked)));
50 }
51
52 let config = match crate::config::Config::load() {
54 Ok(c) => c,
55 Err(e) => {
56 return Ok(mcp_error_response(format!("Failed to load config: {}", e)));
57 }
58 };
59
60 let all_specs = match load_all_specs(&specs_dir) {
61 Ok(specs) => specs,
62 Err(e) => {
63 return Ok(mcp_error_response(format!("Failed to load specs: {}", e)));
64 }
65 };
66
67 let spec_repo = crate::repository::spec_repository::FileSpecRepository::new(specs_dir.clone());
69 let options = crate::operations::finalize::FinalizeOptions {
70 allow_no_commits: false,
71 commits: None, force,
73 };
74
75 match crate::operations::finalize::finalize_spec(
76 &mut spec, &spec_repo, &config, &all_specs, options,
77 ) {
78 Ok(_) => Ok(mcp_text_response(format!("Finalized spec: {}", spec_id))),
79 Err(e) => Ok(mcp_error_response(format!(
80 "Failed to finalize spec: {}",
81 e
82 ))),
83 }
84}
85
86pub fn tool_chant_reset(arguments: Option<&Value>) -> Result<Value> {
87 let specs_dir = match mcp_ensure_initialized() {
88 Ok(dir) => dir,
89 Err(err_response) => return Ok(err_response),
90 };
91
92 let args = arguments.ok_or_else(|| anyhow::anyhow!("Missing arguments"))?;
93
94 let id = args
95 .get("id")
96 .and_then(|v| v.as_str())
97 .ok_or_else(|| anyhow::anyhow!("Missing required parameter: id"))?;
98
99 let mut spec = match resolve_spec(&specs_dir, id) {
100 Ok(s) => s,
101 Err(e) => {
102 return Ok(mcp_error_response(e.to_string()));
103 }
104 };
105
106 let spec_id = spec.id.clone();
107 let spec_path = specs_dir.join(format!("{}.md", spec.id));
108
109 let options = crate::operations::reset::ResetOptions::default();
111
112 match crate::operations::reset::reset_spec(&mut spec, &spec_path, options) {
113 Ok(_) => Ok(mcp_text_response(format!(
114 "Reset spec '{}' to pending",
115 spec_id
116 ))),
117 Err(e) => Ok(mcp_error_response(format!("Failed to reset spec: {}", e))),
118 }
119}
120
121pub fn tool_chant_cancel(arguments: Option<&Value>) -> Result<Value> {
122 let specs_dir = match mcp_ensure_initialized() {
123 Ok(dir) => dir,
124 Err(err_response) => return Ok(err_response),
125 };
126
127 let args = arguments.ok_or_else(|| anyhow::anyhow!("Missing arguments"))?;
128
129 let id = args
130 .get("id")
131 .and_then(|v| v.as_str())
132 .ok_or_else(|| anyhow::anyhow!("Missing required parameter: id"))?;
133
134 let options = operations::CancelOptions::default();
135
136 match operations::cancel_spec(&specs_dir, id, &options) {
137 Ok(spec) => Ok(mcp_text_response(format!("Cancelled spec: {}", spec.id))),
138 Err(e) => Ok(mcp_error_response(e.to_string())),
139 }
140}
141
142pub fn tool_chant_archive(arguments: Option<&Value>) -> Result<Value> {
143 let specs_dir = match mcp_ensure_initialized() {
144 Ok(dir) => dir,
145 Err(err_response) => return Ok(err_response),
146 };
147
148 let args = arguments.ok_or_else(|| anyhow::anyhow!("Missing arguments"))?;
149
150 let id = args
151 .get("id")
152 .and_then(|v| v.as_str())
153 .ok_or_else(|| anyhow::anyhow!("Missing required parameter: id"))?;
154
155 let options = operations::ArchiveOptions::default();
156
157 match operations::archive_spec(&specs_dir, id, &options) {
158 Ok(dest_path) => Ok(mcp_text_response(format!(
159 "Archived spec: {} -> {}",
160 id,
161 dest_path.display()
162 ))),
163 Err(e) => Ok(mcp_error_response(e.to_string())),
164 }
165}