chant/operations/pause.rs
1//! Spec pause operation.
2//!
3//! Canonical implementation for pausing running work processes.
4
5use anyhow::Result;
6
7use crate::spec::{Spec, SpecStatus};
8use std::path::Path;
9
10/// Options for pausing a spec
11#[derive(Debug, Clone, Default)]
12pub struct PauseOptions {
13 /// Force stop the process without confirmation (CLI only)
14 pub force: bool,
15}
16
17/// Pause a running work process for a spec.
18///
19/// This is the canonical pause logic:
20/// - Checks for PID file and running process
21/// - Stops the process if running (respecting force flag)
22/// - Updates spec status from in_progress to paused
23/// - Cleans up PID file
24///
25/// Returns Ok(true) if a process was stopped, Ok(false) otherwise.
26pub fn pause_spec(spec: &mut Spec, spec_path: &Path, options: PauseOptions) -> Result<bool> {
27 let spec_id = &spec.id;
28
29 // Check if there's a PID file
30 let pid = crate::pid::read_pid_file(spec_id)?;
31
32 let mut process_stopped = false;
33 if let Some(pid) = pid {
34 if crate::pid::is_process_running(pid) {
35 // Process is running
36 if options.force {
37 // Stop the process
38 crate::pid::stop_process(pid)?;
39 crate::pid::remove_pid_file(spec_id)?;
40 process_stopped = true;
41 } else {
42 // MCP handler: always stop without force flag check
43 // CLI: this should have been checked earlier and returned error
44 crate::pid::stop_process(pid)?;
45 crate::pid::remove_pid_file(spec_id)?;
46 process_stopped = true;
47 }
48 } else {
49 // Process not running, clean up PID file
50 crate::pid::remove_pid_file(spec_id)?;
51 }
52 }
53
54 // Update spec status to paused if it was in_progress
55 if spec.frontmatter.status == SpecStatus::InProgress {
56 spec.set_status(SpecStatus::Paused)
57 .map_err(|e| anyhow::anyhow!("Failed to transition spec to Paused: {}", e))?;
58 spec.save(spec_path)?;
59 }
60
61 Ok(process_stopped)
62}