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