task_graph_mcp/resources/
workflows.rs1use crate::config::workflows::WorkflowsConfig;
7use anyhow::Result;
8use serde_json::{Value, json};
9
10pub fn list_workflows(workflows: &WorkflowsConfig) -> Result<Value> {
12 let mut workflow_list: Vec<Value> = Vec::new();
13
14 for (name, config) in &workflows.named_workflows {
15 let source = config.source_file.as_ref().map(|p| p.display().to_string());
16
17 workflow_list.push(json!({
18 "name": name,
19 "description": config.description,
20 "source_file": source,
21 "states": config.states.keys().collect::<Vec<_>>(),
22 "phases": config.phases.keys().collect::<Vec<_>>(),
23 }));
24 }
25
26 workflow_list.sort_by(|a, b| {
28 a.get("name")
29 .and_then(|v| v.as_str())
30 .unwrap_or("")
31 .cmp(b.get("name").and_then(|v| v.as_str()).unwrap_or(""))
32 });
33
34 let default_workflow = workflows.default_workflow_key.as_ref();
36
37 let mut overlay_list: Vec<Value> = workflows
39 .named_overlays
40 .iter()
41 .map(|(name, config)| {
42 json!({
43 "name": name,
44 "description": config.description,
45 })
46 })
47 .collect();
48
49 overlay_list.sort_by(|a, b| {
50 a.get("name")
51 .and_then(|v| v.as_str())
52 .unwrap_or("")
53 .cmp(b.get("name").and_then(|v| v.as_str()).unwrap_or(""))
54 });
55
56 Ok(json!({
57 "workflows": workflow_list,
58 "overlays": overlay_list,
59 "default_workflow": default_workflow,
60 "count": workflows.named_workflows.len(),
61 }))
62}
63
64pub fn get_workflow(workflows: &WorkflowsConfig, name: &str) -> Result<Value> {
66 let config = workflows
67 .named_workflows
68 .get(name)
69 .ok_or_else(|| anyhow::anyhow!("Workflow '{}' not found", name))?;
70
71 let source = config.source_file.as_ref().map(|p| p.display().to_string());
72
73 let states: Vec<Value> = config
75 .states
76 .iter()
77 .map(|(state_name, state)| {
78 json!({
79 "name": state_name,
80 "exits": state.exits,
81 "timed": state.timed,
82 "has_enter_prompt": state.prompts.enter.is_some(),
83 "has_exit_prompt": state.prompts.exit.is_some(),
84 })
85 })
86 .collect();
87
88 let phases: Vec<Value> = config
90 .phases
91 .iter()
92 .map(|(phase_name, phase)| {
93 json!({
94 "name": phase_name,
95 "has_enter_prompt": phase.prompts.enter.is_some(),
96 "has_exit_prompt": phase.prompts.exit.is_some(),
97 })
98 })
99 .collect();
100
101 Ok(json!({
102 "name": name,
103 "description": config.description,
104 "source_file": source,
105 "settings": {
106 "initial_state": config.settings.initial_state,
107 "disconnect_state": config.settings.disconnect_state,
108 "blocking_states": config.settings.blocking_states,
109 },
110 "states": states,
111 "phases": phases,
112 "combo_count": config.combos.len(),
113 }))
114}
115
116pub fn list_overlays(workflows: &WorkflowsConfig) -> Result<Value> {
118 let mut overlay_list: Vec<Value> = Vec::new();
119
120 for (name, config) in &workflows.named_overlays {
121 let source = config.source_file.as_ref().map(|p| p.display().to_string());
122
123 overlay_list.push(json!({
124 "name": name,
125 "description": config.description,
126 "source_file": source,
127 }));
128 }
129
130 overlay_list.sort_by(|a, b| {
132 a.get("name")
133 .and_then(|v| v.as_str())
134 .unwrap_or("")
135 .cmp(b.get("name").and_then(|v| v.as_str()).unwrap_or(""))
136 });
137
138 Ok(json!({
139 "overlays": overlay_list,
140 "count": workflows.named_overlays.len(),
141 }))
142}
143
144pub fn get_overlay(workflows: &WorkflowsConfig, name: &str) -> Result<Value> {
146 let config = workflows
147 .named_overlays
148 .get(name)
149 .ok_or_else(|| anyhow::anyhow!("Overlay '{}' not found", name))?;
150
151 let source = config.source_file.as_ref().map(|p| p.display().to_string());
152
153 let states: Vec<Value> = config
155 .states
156 .iter()
157 .map(|(state_name, state)| {
158 json!({
159 "name": state_name,
160 "exits": state.exits,
161 "timed": state.timed,
162 "has_enter_prompt": state.prompts.enter.is_some(),
163 "has_exit_prompt": state.prompts.exit.is_some(),
164 })
165 })
166 .collect();
167
168 let phases: Vec<Value> = config
170 .phases
171 .iter()
172 .map(|(phase_name, phase)| {
173 json!({
174 "name": phase_name,
175 "has_enter_prompt": phase.prompts.enter.is_some(),
176 "has_exit_prompt": phase.prompts.exit.is_some(),
177 })
178 })
179 .collect();
180
181 let gates: Vec<Value> = config
183 .gates
184 .iter()
185 .map(|(gate_key, gate_defs)| {
186 let defs: Vec<Value> = gate_defs
187 .iter()
188 .map(|g| {
189 json!({
190 "type": g.gate_type,
191 "enforcement": format!("{:?}", g.enforcement),
192 "description": g.description,
193 })
194 })
195 .collect();
196 json!({
197 "key": gate_key,
198 "definitions": defs,
199 })
200 })
201 .collect();
202
203 let roles: Vec<Value> = config
205 .roles
206 .iter()
207 .map(|(role_name, role)| {
208 json!({
209 "name": role_name,
210 "description": role.description,
211 "tags": role.tags,
212 "max_claims": role.max_claims,
213 "can_assign": role.can_assign,
214 "can_create_subtasks": role.can_create_subtasks,
215 })
216 })
217 .collect();
218
219 let advisories: Vec<Value> = config
221 .advisories
222 .iter()
223 .map(|(topic, advisory)| {
224 json!({
225 "topic": topic,
226 "level": advisory.level,
227 "phase": advisory.phase,
228 "role": advisory.role,
229 "domain": advisory.domain,
230 "content": advisory.content,
231 })
232 })
233 .collect();
234
235 let role_prompts: Value = config
237 .role_prompts
238 .iter()
239 .map(|(role_name, prompts)| (role_name.clone(), json!(prompts)))
240 .collect::<serde_json::Map<String, Value>>()
241 .into();
242
243 Ok(json!({
244 "name": name,
245 "description": config.description,
246 "source_file": source,
247 "states": states,
248 "phases": phases,
249 "gates": gates,
250 "roles": roles,
251 "advisories": advisories,
252 "role_prompts": role_prompts,
253 "combo_count": config.combos.len(),
254 }))
255}