chant/operations/cancel.rs
1//! Cancel operation for specs.
2//!
3//! Provides the canonical implementation for canceling specs.
4
5use anyhow::Result;
6use std::path::Path;
7
8use crate::spec::{Spec, SpecStatus};
9
10/// Options for the cancel operation.
11#[derive(Debug, Clone, Default)]
12pub struct CancelOptions {
13 /// Whether to proceed even if the spec is already cancelled.
14 pub force: bool,
15}
16
17/// Cancel a spec by setting its status to Cancelled.
18///
19/// This operation:
20/// - Sets the spec status to Cancelled using the state machine
21/// - Saves the updated spec to disk
22///
23/// # Arguments
24/// * `specs_dir` - Path to the specs directory
25/// * `spec_id` - ID of the spec to cancel
26/// * `options` - Cancel operation options
27///
28/// # Returns
29/// * `Ok(())` if the spec was successfully cancelled
30/// * `Err(_)` if the spec doesn't exist, is already cancelled (without force), or can't be transitioned
31pub fn cancel_spec(specs_dir: &Path, spec_id: &str, options: &CancelOptions) -> Result<Spec> {
32 use crate::spec;
33
34 // Resolve and load the spec
35 let mut spec = spec::resolve_spec(specs_dir, spec_id)?;
36
37 // Check if already cancelled
38 if spec.frontmatter.status == SpecStatus::Cancelled && !options.force {
39 anyhow::bail!("Spec '{}' is already cancelled", spec.id);
40 }
41
42 // Set status to cancelled using state machine
43 spec.set_status(SpecStatus::Cancelled)
44 .map_err(|e| anyhow::anyhow!("Failed to transition spec to Cancelled: {}", e))?;
45
46 // Save the spec
47 let spec_path = specs_dir.join(format!("{}.md", spec.id));
48 spec.save(&spec_path)?;
49
50 Ok(spec)
51}