1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//! Workflow — a reusable sequence of steps captured from sessions.
//!
//! Workflows embed `KnowledgeBase` via `#[serde(flatten)]` so YAML stays flat.
use serde::{Deserialize, Serialize};
use crate::knowledge::KnowledgeBase;
use crate::schedule::Capability;
/// A MUR workflow — a captured, reusable sequence of steps.
///
/// YAML files in `~/.mur/workflows/` are the source of truth.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Workflow {
/// Shared knowledge fields (flattened into YAML)
#[serde(flatten)]
pub base: KnowledgeBase,
/// Ordered steps in this workflow
#[serde(default)]
pub steps: Vec<Step>,
/// Variables/parameters for this workflow
#[serde(default)]
pub variables: Vec<Variable>,
/// Session IDs this workflow was extracted from
#[serde(default)]
pub source_sessions: Vec<String>,
/// Natural-language trigger description (e.g. "when deploying to production")
#[serde(default)]
pub trigger: String,
/// Tools this workflow uses (e.g. ["cargo", "docker"])
#[serde(default)]
pub tools: Vec<String>,
/// Published version number (incremented on each publish)
#[serde(default)]
pub published_version: u32,
/// Permission level required to run this workflow
#[serde(default)]
pub permission: Permission,
/// Cron schedule expression (e.g. "0 * * * *" for hourly)
#[serde(default, skip_serializing_if = "Option::is_none")]
pub schedule: Option<String>,
/// Unique workflow ID (assigned by Commander, optional for CLI)
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
/// Notification preferences (Commander feature)
#[serde(default, skip_serializing_if = "Option::is_none")]
pub notify: Option<NotifyConfig>,
/// Capabilities required to run this workflow
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub requires: Vec<Capability>,
}
// Allow `workflow.name`, `workflow.content`, etc. via auto-deref.
impl std::ops::Deref for Workflow {
type Target = KnowledgeBase;
fn deref(&self) -> &KnowledgeBase {
&self.base
}
}
impl std::ops::DerefMut for Workflow {
fn deref_mut(&mut self) -> &mut KnowledgeBase {
&mut self.base
}
}
/// A single step in a workflow.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Step {
/// Execution order (1-based)
pub order: u32,
/// Human-readable description of what this step does
pub description: String,
/// Shell command to execute (if any)
#[serde(default)]
pub command: Option<String>,
/// Tool to use (e.g. "cargo", "npm")
#[serde(default)]
pub tool: Option<String>,
/// Whether this step requires user approval before executing
#[serde(default)]
pub needs_approval: bool,
/// What to do if this step fails
#[serde(default)]
pub on_failure: FailureAction,
/// Commander extension: pause execution for manual inspection
#[serde(default, skip_serializing_if = "Option::is_none")]
pub breakpoint: Option<bool>,
/// Commander extension: retry configuration
#[serde(default, skip_serializing_if = "Option::is_none")]
pub retry: Option<RetryConfig>,
/// Commander extension: step timeout in seconds
#[serde(default, skip_serializing_if = "Option::is_none")]
pub timeout_secs: Option<u64>,
}
pub use crate::skill::manifest::{FailureAction, RetryConfig, VarType, Variable};
/// Notification level for workflow events.
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum NotifyLevel {
#[default]
Silent,
Normal,
Alert,
}
/// Notification configuration for workflow execution results.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NotifyConfig {
#[serde(default)]
pub on_success: NotifyLevel,
#[serde(default = "default_alert")]
pub on_failure: NotifyLevel,
#[serde(default = "default_normal")]
pub on_anomaly: NotifyLevel,
}
fn default_alert() -> NotifyLevel {
NotifyLevel::Alert
}
fn default_normal() -> NotifyLevel {
NotifyLevel::Normal
}
impl Default for NotifyConfig {
fn default() -> Self {
Self {
on_success: NotifyLevel::Silent,
on_failure: NotifyLevel::Alert,
on_anomaly: NotifyLevel::Normal,
}
}
}
/// Permission level for workflow execution.
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum Permission {
/// Read-only access
#[default]
Read,
/// Read and write access
Write,
/// Execute only (no read/write of intermediate state)
#[serde(rename = "execute_only")]
ExecuteOnly,
}