minion_engine/cli/
init_templates.rs1pub struct Template {
3 pub name: &'static str,
4 pub description: &'static str,
5 pub content: &'static str,
6}
7
8pub const TEMPLATES: &[Template] = &[
9 Template {
10 name: "blank",
11 description: "Minimal workflow skeleton",
12 content: BLANK,
13 },
14 Template {
15 name: "fix-issue",
16 description: "Fetch a GitHub issue, plan and implement a fix, open a PR",
17 content: FIX_ISSUE,
18 },
19 Template {
20 name: "code-review",
21 description: "Run an AI-powered code review on a branch or PR",
22 content: CODE_REVIEW,
23 },
24 Template {
25 name: "security-audit",
26 description: "Perform a security audit on the codebase",
27 content: SECURITY_AUDIT,
28 },
29];
30
31pub fn get(name: &str) -> Option<&'static Template> {
32 TEMPLATES.iter().find(|t| t.name == name)
33}
34
35pub fn names() -> Vec<&'static str> {
36 TEMPLATES.iter().map(|t| t.name).collect()
37}
38
39const BLANK: &str = r#"name: {name}
42version: 1
43description: "Short description of what this workflow does"
44
45# Optional: global config applied to all steps
46config:
47 global:
48 timeout: 300s
49 cmd:
50 fail_on_error: true
51
52steps:
53 - name: hello
54 type: cmd
55 run: "echo 'Hello, {{ target }}!'"
56"#;
57
58const FIX_ISSUE: &str = r#"name: {name}
59version: 1
60description: "Fetch a GitHub issue, plan & implement a fix, open a PR"
61
62config:
63 global:
64 timeout: 600s
65 agent:
66 model: claude-sonnet-4-20250514
67 permissions: skip
68 cmd:
69 fail_on_error: true
70
71scopes:
72 lint_loop:
73 steps:
74 - name: lint
75 type: cmd
76 run: "cargo clippy --fix --allow-dirty 2>&1 || true"
77 - name: check_lint
78 type: gate
79 condition: "{{ steps.lint.exit_code == 0 }}"
80 on_pass: break
81 outputs: "{{ steps.lint.stdout }}"
82
83steps:
84 - name: fetch_issue
85 type: cmd
86 run: "gh issue view {{ target }} --json title,body,labels"
87
88 - name: find_files
89 type: cmd
90 run: "git ls-files | head -50"
91
92 - name: plan
93 type: agent
94 prompt: |
95 You are a senior engineer. Analyze this GitHub issue and plan a fix.
96 Issue: {{ steps.fetch_issue.stdout }}
97 Files: {{ steps.find_files.stdout }}
98 Provide a concise implementation plan.
99
100 - name: implement
101 type: agent
102 prompt: |
103 Implement the fix described in this plan:
104 {{ steps.plan.response }}
105 Make the minimal changes needed. Do not add unrelated improvements.
106
107 - name: lint_fix
108 type: repeat
109 scope: lint_loop
110 max_iterations: 3
111
112 - name: create_branch
113 type: cmd
114 run: "git checkout -b fix/issue-{{ target }}"
115
116 - name: commit
117 type: cmd
118 run: "git add -A && git commit -m 'fix: resolve issue #{{ target }}'"
119
120 - name: open_pr
121 type: cmd
122 run: |
123 gh pr create \
124 --title "fix: resolve issue #{{ target }}" \
125 --body "Closes #{{ target }}" \
126 --head "fix/issue-{{ target }}"
127"#;
128
129const CODE_REVIEW: &str = r#"name: {name}
130version: 1
131description: "AI-powered code review on a branch or PR"
132
133config:
134 global:
135 timeout: 300s
136 agent:
137 model: claude-sonnet-4-20250514
138 permissions: skip
139
140steps:
141 - name: get_diff
142 type: cmd
143 run: "git diff main...HEAD"
144
145 - name: get_files
146 type: cmd
147 run: "git diff --name-only main...HEAD"
148
149 - name: review
150 type: agent
151 prompt: |
152 You are a senior engineer performing a code review.
153
154 Changed files:
155 {{ steps.get_files.stdout }}
156
157 Diff:
158 {{ steps.get_diff.stdout }}
159
160 Review for:
161 1. Logic errors and bugs
162 2. Security vulnerabilities
163 3. Performance issues
164 4. Code quality and readability
165 5. Missing tests
166
167 Be specific. Reference line numbers where possible.
168
169 - name: summary
170 type: cmd
171 run: |
172 echo "=== Code Review Complete ==="
173 echo "{{ steps.review.response }}"
174"#;
175
176const SECURITY_AUDIT: &str = r#"name: {name}
177version: 1
178description: "Security audit of the codebase"
179
180config:
181 global:
182 timeout: 600s
183 agent:
184 model: claude-sonnet-4-20250514
185 permissions: skip
186 cmd:
187 fail_on_error: false
188
189steps:
190 - name: list_files
191 type: cmd
192 run: "git ls-files | grep -E '\\.(rs|py|js|ts|go|java|rb|php)$' | head -100"
193
194 - name: check_deps
195 type: cmd
196 run: "cargo audit 2>&1 || echo 'cargo audit not available'"
197
198 - name: find_secrets
199 type: cmd
200 run: |
201 grep -rn \
202 -e 'password\s*=' \
203 -e 'secret\s*=' \
204 -e 'api_key\s*=' \
205 -e 'token\s*=' \
206 --include='*.rs' --include='*.toml' . 2>/dev/null | head -20 || echo "No obvious secrets found"
207
208 - name: audit
209 type: agent
210 prompt: |
211 You are a security engineer performing a security audit.
212
213 Project files:
214 {{ steps.list_files.stdout }}
215
216 Dependency audit:
217 {{ steps.check_deps.stdout }}
218
219 Potential secrets scan:
220 {{ steps.find_secrets.stdout }}
221
222 Identify:
223 1. Hardcoded secrets or credentials
224 2. Injection vulnerabilities (SQL, command, path traversal)
225 3. Insecure dependencies
226 4. Missing input validation
227 5. Improper error handling that leaks info
228
229 Provide a severity-ranked list of findings with remediation steps.
230
231 - name: report
232 type: cmd
233 run: |
234 echo "=== Security Audit Report ==="
235 echo "{{ steps.audit.response }}"
236"#;
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 #[test]
243 fn all_template_names_are_unique() {
244 let mut names = names();
245 let original_len = names.len();
246 names.dedup();
247 assert_eq!(names.len(), original_len, "template names must be unique");
248 }
249
250 #[test]
251 fn get_blank_template() {
252 let t = get("blank").expect("blank template should exist");
253 assert_eq!(t.name, "blank");
254 assert!(!t.content.is_empty());
255 }
256
257 #[test]
258 fn get_nonexistent_template_returns_none() {
259 assert!(get("does-not-exist").is_none());
260 }
261
262 #[test]
263 fn all_templates_contain_name_placeholder() {
264 for t in TEMPLATES {
265 assert!(
266 t.content.contains("{name}"),
267 "template '{}' must contain {{name}} placeholder",
268 t.name
269 );
270 }
271 }
272
273 #[test]
274 fn init_creates_valid_yaml_for_each_template() {
275 use crate::workflow::parser;
276 use crate::workflow::validator;
277
278 for t in TEMPLATES {
279 let content = t.content.replace("{name}", "test-workflow");
280 let wf = parser::parse_str(&content)
281 .unwrap_or_else(|e| panic!("template '{}' failed to parse: {e}", t.name));
282 let errors = validator::validate(&wf);
283 assert!(
284 errors.is_empty(),
285 "template '{}' has validation errors: {:?}",
286 t.name,
287 errors
288 );
289 }
290 }
291}