Skip to main content

chant/operations/
update.rs

1//! Spec update operation.
2//!
3//! Canonical implementation for updating spec fields with validation.
4
5use anyhow::Result;
6use std::path::Path;
7
8use crate::spec::{Spec, SpecStatus, TransitionBuilder};
9
10/// Options for spec update
11#[derive(Debug, Clone, Default)]
12pub struct UpdateOptions {
13    /// New status (validated via state machine)
14    pub status: Option<SpecStatus>,
15    /// Dependencies to set
16    pub depends_on: Option<Vec<String>>,
17    /// Labels to set
18    pub labels: Option<Vec<String>>,
19    /// Target files to set
20    pub target_files: Option<Vec<String>>,
21    /// Model to set
22    pub model: Option<String>,
23    /// Output text to append to body
24    pub output: Option<String>,
25    /// Force status transition (bypass validation)
26    pub force: bool,
27}
28
29/// Update spec fields with validation.
30///
31/// This is the canonical update logic used by both CLI and MCP.
32/// Status transitions are validated via the state machine.
33pub fn update_spec(spec: &mut Spec, spec_path: &Path, options: UpdateOptions) -> Result<()> {
34    let mut updated = false;
35
36    // Update status if provided (use TransitionBuilder with optional force)
37    if let Some(new_status) = options.status {
38        let mut builder = TransitionBuilder::new(spec);
39        if options.force {
40            builder = builder.force();
41        }
42        builder.to(new_status)?;
43        updated = true;
44    }
45
46    // Update depends_on if provided
47    if let Some(depends_on) = options.depends_on {
48        spec.frontmatter.depends_on = Some(depends_on);
49        updated = true;
50    }
51
52    // Update labels if provided
53    if let Some(labels) = options.labels {
54        spec.frontmatter.labels = Some(labels);
55        updated = true;
56    }
57
58    // Update target_files if provided
59    if let Some(target_files) = options.target_files {
60        spec.frontmatter.target_files = Some(target_files);
61        updated = true;
62    }
63
64    // Update model if provided
65    if let Some(model) = options.model {
66        spec.frontmatter.model = Some(model);
67        updated = true;
68    }
69
70    // Append output if provided
71    if let Some(output) = options.output {
72        if !output.is_empty() {
73            if !spec.body.ends_with('\n') && !spec.body.is_empty() {
74                spec.body.push('\n');
75            }
76            spec.body.push_str("\n## Output\n\n");
77            spec.body.push_str(&output);
78            spec.body.push('\n');
79            updated = true;
80        }
81    }
82
83    if !updated {
84        anyhow::bail!("No updates specified");
85    }
86
87    // Save the spec
88    spec.save(spec_path)?;
89
90    Ok(())
91}