1use std::collections::HashMap;
2use std::path::Path;
3use std::process::Command;
4
5use serde::Serialize;
6
7use crate::error::ZigError;
8use crate::prompt;
9use crate::run;
10
11fn build_system_prompt(zag_help: &str, zag_orch: &str) -> String {
14 let vars = HashMap::from([
15 ("zug_format_spec", prompt::templates::CONFIG_SIDECAR),
16 ("zag_help", zag_help),
17 ("zag_orch", zag_orch),
18 ]);
19 prompt::render(prompt::templates::CREATE, &vars)
20}
21
22fn get_zag_help() -> String {
24 Command::new("zag")
25 .arg("--help-agent")
26 .output()
27 .ok()
28 .filter(|o| o.status.success())
29 .and_then(|o| String::from_utf8(o.stdout).ok())
30 .unwrap_or_else(|| "(zag --help-agent not available — zag may not be installed)".into())
31}
32
33fn get_zag_orch_reference() -> String {
35 Command::new("zag")
36 .args(["man", "orchestration"])
37 .output()
38 .ok()
39 .filter(|o| o.status.success())
40 .and_then(|o| String::from_utf8(o.stdout).ok())
41 .unwrap_or_else(|| {
42 "(zag man orchestration not available — zag may not be installed)".into()
43 })
44}
45
46fn pattern_guidance(pattern: &str) -> &'static str {
48 match pattern {
49 "sequential" => {
50 "\
51 Use the Sequential Pipeline pattern: steps run in order, each feeding \
52 its output to the next via inject_context. Structure it as a linear \
53 depends_on chain."
54 }
55 "fan-out" => {
56 "\
57 Use the Fan-Out / Gather pattern: multiple independent steps run in \
58 parallel and a final step synthesizes their results. The gathering step \
59 should depends_on all parallel steps with inject_context = true."
60 }
61 "generator-critic" => {
62 "\
63 Use the Generator / Critic pattern: a generation step is followed by a \
64 critique step that scores the output, looping back via the next field \
65 until a quality threshold is met. Use saves to extract score and \
66 feedback variables, and a condition to control the loop."
67 }
68 "coordinator-dispatcher" => {
69 "\
70 Use the Coordinator / Dispatcher pattern: an initial classification \
71 step routes to specialized handler steps via condition expressions. \
72 Use json + saves to extract the classification, then condition guards \
73 on each handler step."
74 }
75 "hierarchical-decomposition" => {
76 "\
77 Use the Hierarchical Decomposition pattern: a planning step breaks the \
78 problem into sub-tasks, multiple worker steps handle each sub-task in \
79 parallel, and a synthesis step combines the results."
80 }
81 "human-in-the-loop" => {
82 "\
83 Use the Human-in-the-Loop pattern: incorporate human approval gates \
84 between automated steps. Use condition expressions on approval \
85 variables that are set by human review."
86 }
87 "inter-agent-communication" => {
88 "\
89 Use the Inter-Agent Communication pattern: agents collaborate via \
90 shared variables. Design the vars section carefully so agents can \
91 read and write to common state."
92 }
93 _ => "",
94 }
95}
96
97#[derive(Debug, Clone, Serialize)]
100pub struct CreateParams {
101 pub system_prompt: String,
102 pub initial_prompt: String,
103 pub output_path: String,
104 pub session_name: String,
105 pub session_tag: String,
106}
107
108pub fn prepare_create(
112 name: Option<&str>,
113 output: Option<&str>,
114 pattern: Option<&str>,
115) -> Result<CreateParams, ZigError> {
116 let zag_help = get_zag_help();
117 let zag_orch = get_zag_orch_reference();
118 let system_prompt = build_system_prompt(&zag_help, &zag_orch);
119
120 let output_path = if let Some(o) = output {
121 o.to_string()
122 } else {
123 let global_dir = crate::paths::ensure_global_workflows_dir()?;
124 let filename = name
125 .map(|n| format!("{n}.zug"))
126 .unwrap_or_else(|| "workflow.zug".to_string());
127 global_dir.join(&filename).to_string_lossy().to_string()
128 };
129
130 let mut initial_prompt = if let Some(n) = name {
131 format!(
132 "I want to create a workflow called \"{n}\". \
133 The output will be saved to {output_path}. \
134 Please help me design it — start by asking what process I want to automate."
135 )
136 } else {
137 format!(
138 "I want to create a new workflow. \
139 The output will be saved to {output_path}. \
140 Please help me design it — start by asking what process I want to automate."
141 )
142 };
143
144 if let Some(p) = pattern {
145 let guidance = pattern_guidance(p);
146 if !guidance.is_empty() {
147 initial_prompt.push(' ');
148 initial_prompt.push_str(guidance);
149 }
150 }
151
152 Ok(CreateParams {
153 system_prompt,
154 initial_prompt,
155 output_path,
156 session_name: "zig-create".to_string(),
157 session_tag: "zig-workflow-creation".to_string(),
158 })
159}
160
161pub fn run_create(
166 name: Option<&str>,
167 output: Option<&str>,
168 pattern: Option<&str>,
169) -> Result<(), ZigError> {
170 run::check_zag()?;
171
172 let params = prepare_create(name, output, pattern)?;
173
174 let status = Command::new("zag")
175 .args(["run", ¶ms.initial_prompt])
176 .args(["--system-prompt", ¶ms.system_prompt])
177 .args(["--name", ¶ms.session_name])
178 .args(["--tag", ¶ms.session_tag])
179 .status()
180 .map_err(|e| ZigError::Zag(format!("failed to launch zag: {e}")))?;
181
182 if !status.success() {
183 return Err(ZigError::Zag(format!("zag exited with status {status}")));
184 }
185
186 if Path::new(¶ms.output_path).exists() {
188 match crate::workflow::parser::parse_file(Path::new(¶ms.output_path)) {
189 Ok(workflow) => {
190 if let Err(errors) = crate::workflow::validate::validate(&workflow) {
191 eprintln!("Warning: generated workflow has validation issues:");
192 for e in &errors {
193 eprintln!(" - {e}");
194 }
195 }
196 }
197 Err(e) => {
198 eprintln!("Warning: could not parse generated file: {e}");
199 }
200 }
201 }
202
203 Ok(())
204}
205
206#[cfg(test)]
207#[path = "create_tests.rs"]
208mod tests;