33//! Dispatch specs to AI coding agents for automated implementation.
44
55use colored:: Colorize ;
6+ use leanspec_core:: SpecLoader ;
67use std:: error:: Error ;
7- use std:: process:: { Command , Stdio } ;
88use std:: fs;
9- use leanspec_core :: SpecLoader ;
9+ use std :: process :: { Command , Stdio } ;
1010
1111/// Supported AI agents
1212const SUPPORTED_AGENTS : & [ & str ] = & [ "claude" , "copilot" , "aider" , "gemini" , "cursor" , "continue" ] ;
@@ -36,7 +36,12 @@ pub fn run(
3636
3737fn show_help ( ) {
3838 println ! ( ) ;
39- println ! ( "{}" , "LeanSpec Agent - Dispatch specs to AI coding agents" . cyan( ) . bold( ) ) ;
39+ println ! (
40+ "{}" ,
41+ "LeanSpec Agent - Dispatch specs to AI coding agents"
42+ . cyan( )
43+ . bold( )
44+ ) ;
4045 println ! ( ) ;
4146 println ! ( "{}" , "Usage:" . bold( ) ) ;
4247 println ! ( " lean-spec agent run <spec> [--agent <type>] Dispatch spec to AI agent" ) ;
@@ -68,61 +73,66 @@ fn run_agent(
6873 dry_run : bool ,
6974) -> Result < ( ) , Box < dyn Error > > {
7075 let spec_ids = specs. ok_or ( "At least one spec is required for 'run' action" ) ?;
71-
76+
7277 if spec_ids. is_empty ( ) {
7378 return Err ( "At least one spec is required" . into ( ) ) ;
7479 }
75-
80+
7681 let agent_name = agent. unwrap_or_else ( || "claude" . to_string ( ) ) ;
77-
82+
7883 // Validate agent
7984 if !SUPPORTED_AGENTS . contains ( & agent_name. as_str ( ) ) {
8085 return Err ( format ! (
8186 "Unknown agent: {}. Supported: {}" ,
8287 agent_name,
8388 SUPPORTED_AGENTS . join( ", " )
84- ) . into ( ) ) ;
89+ )
90+ . into ( ) ) ;
8591 }
86-
92+
8793 // Check if agent is available
8894 let agent_command = get_agent_command ( & agent_name) ;
8995 if !is_command_available ( & agent_command) && !dry_run {
9096 return Err ( format ! (
9197 "Agent not found: {}. Make sure {} is installed and in your PATH." ,
9298 agent_name, agent_command
93- ) . into ( ) ) ;
99+ )
100+ . into ( ) ) ;
94101 }
95-
102+
96103 println ! ( ) ;
97- println ! ( "{}" , format!( "🤖 Dispatching to {} agent" , agent_name. cyan( ) ) . green( ) ) ;
104+ println ! (
105+ "{}" ,
106+ format!( "🤖 Dispatching to {} agent" , agent_name. cyan( ) ) . green( )
107+ ) ;
98108 println ! ( ) ;
99-
109+
100110 // Load specs
101111 let loader = SpecLoader :: new ( specs_dir) ;
102112 let all_specs = loader. load_all ( ) ?;
103-
113+
104114 // Find matching specs
105115 let mut found_specs = Vec :: new ( ) ;
106116 for spec_id in & spec_ids {
107117 let matching: Vec < _ > = all_specs
108118 . iter ( )
109119 . filter ( |s| s. path . contains ( spec_id) || s. name ( ) . contains ( spec_id) )
110120 . collect ( ) ;
111-
121+
112122 if matching. is_empty ( ) {
113123 return Err ( format ! ( "Spec not found: {}" , spec_id) . into ( ) ) ;
114124 }
115-
125+
116126 found_specs. extend ( matching) ;
117127 }
118-
128+
119129 println ! ( "{}" , "Specs to process:" . bold( ) ) ;
120130 for spec in & found_specs {
121131 let status_icon = spec. frontmatter . status_emoji ( ) ;
122132 println ! ( " • {} {}" , spec. name( ) , status_icon) ;
123133 }
124134 println ! ( ) ;
125-
135+
126136 if dry_run {
127137 println ! ( "{}" , "Dry run mode - no actions will be taken" . yellow( ) ) ;
128138 println ! ( ) ;
@@ -139,41 +149,44 @@ fn run_agent(
139149 }
140150 return Ok ( ( ) ) ;
141151 }
142-
152+
143153 // Process each spec
144154 for spec in & found_specs {
145155 println ! ( "{}" , format!( "Processing: {}" , spec. name( ) ) . bold( ) ) ;
146-
156+
147157 // Update status to in-progress (if not disabled)
148158 if !no_status_update {
149159 println ! ( " {} Updated status to in-progress" , "✓" . green( ) ) ;
150160 }
151-
161+
152162 // Create worktree for parallel development
153163 if parallel {
154- println ! ( " {} Creating worktree (not implemented in Rust CLI yet)" , "⚠" . yellow( ) ) ;
164+ println ! (
165+ " {} Creating worktree (not implemented in Rust CLI yet)" ,
166+ "⚠" . yellow( )
167+ ) ;
155168 }
156-
169+
157170 // Load spec content
158171 let readme_path = spec. file_path . clone ( ) ;
159172 let content = fs:: read_to_string ( & readme_path) . unwrap_or_default ( ) ;
160-
173+
161174 // Launch agent
162175 println ! ( " {} Launching {}..." , "→" . cyan( ) , agent_name) ;
163-
176+
164177 let result = launch_agent ( & agent_name, spec. name ( ) , & content) ;
165-
178+
166179 match result {
167180 Ok ( _) => println ! ( " {} Agent session started" , "✓" . green( ) ) ,
168181 Err ( e) => println ! ( " {} Failed to launch agent: {}" , "✗" . red( ) , e) ,
169182 }
170-
183+
171184 println ! ( ) ;
172185 }
173-
186+
174187 println ! ( "{}" , "✨ Agent dispatch complete" . green( ) ) ;
175188 println ! ( "Use {} to check progress" , "lean-spec agent status" . cyan( ) ) ;
176-
189+
177190 Ok ( ( ) )
178191}
179192
@@ -185,7 +198,7 @@ fn list_agents(output_format: &str) -> Result<(), Box<dyn Error>> {
185198 available : bool ,
186199 agent_type : String ,
187200 }
188-
201+
189202 let agents: Vec < AgentInfo > = SUPPORTED_AGENTS
190203 . iter ( )
191204 . map ( |& name| {
@@ -198,17 +211,17 @@ fn list_agents(output_format: &str) -> Result<(), Box<dyn Error>> {
198211 }
199212 } )
200213 . collect ( ) ;
201-
214+
202215 if output_format == "json" {
203216 println ! ( "{}" , serde_json:: to_string_pretty( & agents) ?) ;
204217 return Ok ( ( ) ) ;
205218 }
206-
219+
207220 println ! ( ) ;
208221 println ! ( "{}" , "=== Available AI Agents ===" . green( ) . bold( ) ) ;
209222 println ! ( ) ;
210223 println ! ( "{}" , "CLI-based (local):" . bold( ) ) ;
211-
224+
212225 for agent in & agents {
213226 let status = if agent. available {
214227 "✓" . green ( )
@@ -217,52 +230,63 @@ fn list_agents(output_format: &str) -> Result<(), Box<dyn Error>> {
217230 } ;
218231 println ! ( " {} {} ({})" , status, agent. name, agent. command. dimmed( ) ) ;
219232 }
220-
233+
221234 println ! ( ) ;
222235 println ! ( "Set default: {}" , "lean-spec agent config <agent>" . cyan( ) ) ;
223- println ! ( "Run agent: {}" , "lean-spec agent run <spec> --agent <agent>" . cyan( ) ) ;
236+ println ! (
237+ "Run agent: {}" ,
238+ "lean-spec agent run <spec> --agent <agent>" . cyan( )
239+ ) ;
224240 println ! ( ) ;
225-
241+
226242 Ok ( ( ) )
227243}
228244
229245fn show_status ( specs : Option < Vec < String > > , output_format : & str ) -> Result < ( ) , Box < dyn Error > > {
230246 // In a real implementation, this would track active sessions
231247 // For now, just report that there are no active sessions
232-
248+
233249 if output_format == "json" {
234250 println ! ( "{{}}" ) ;
235251 return Ok ( ( ) ) ;
236252 }
237-
253+
238254 println ! ( ) ;
239-
255+
240256 if let Some ( spec_ids) = specs {
241257 for spec_id in spec_ids {
242- println ! ( "{}" , format!( "No active session for spec: {}" , spec_id) . yellow( ) ) ;
258+ println ! (
259+ "{}" ,
260+ format!( "No active session for spec: {}" , spec_id) . yellow( )
261+ ) ;
243262 }
244263 } else {
245264 println ! ( "{}" , "No active agent sessions" . dimmed( ) ) ;
246265 }
247-
266+
248267 println ! ( ) ;
249-
268+
250269 Ok ( ( ) )
251270}
252271
253272fn configure_default_agent ( agent : Option < String > ) -> Result < ( ) , Box < dyn Error > > {
254273 let agent_name = agent. ok_or ( "Agent name required for 'config' action" ) ?;
255-
274+
256275 if !SUPPORTED_AGENTS . contains ( & agent_name. as_str ( ) ) {
257276 return Err ( format ! (
258277 "Unknown agent: {}. Supported: {}" ,
259278 agent_name,
260279 SUPPORTED_AGENTS . join( ", " )
261- ) . into ( ) ) ;
280+ )
281+ . into ( ) ) ;
262282 }
263-
264- println ! ( "{} Default agent set to: {}" , "✓" . green( ) , agent_name. cyan( ) ) ;
265-
283+
284+ println ! (
285+ "{} Default agent set to: {}" ,
286+ "✓" . green( ) ,
287+ agent_name. cyan( )
288+ ) ;
289+
266290 Ok ( ( ) )
267291}
268292
@@ -290,7 +314,7 @@ fn is_command_available(command: &str) -> bool {
290314 . map ( |s| s. success ( ) )
291315 . unwrap_or ( false )
292316 }
293-
317+
294318 #[ cfg( not( target_os = "windows" ) ) ]
295319 {
296320 Command :: new ( "which" )
@@ -305,20 +329,23 @@ fn is_command_available(command: &str) -> bool {
305329
306330fn launch_agent ( agent : & str , _spec_name : & str , content : & str ) -> Result < ( ) , Box < dyn Error > > {
307331 let command = get_agent_command ( agent) ;
308-
332+
309333 // Build context template
310334 let context = format ! (
311335 "Implement the following LeanSpec specification:\n \n ---\n {}\n ---\n \n \
312336 Please follow the spec's design, plan, and test sections. \
313337 Update the spec status to 'complete' when done.",
314338 content. chars( ) . take( 2000 ) . collect:: <String >( )
315339 ) ;
316-
340+
317341 // Launch agent based on type
318342 match agent {
319343 "claude" => {
320344 println ! ( " {} Context prepared for Claude" , "✓" . green( ) ) ;
321- println ! ( " {} Copy the spec content and paste into Claude" , "ℹ" . cyan( ) ) ;
345+ println ! (
346+ " {} Copy the spec content and paste into Claude" ,
347+ "ℹ" . cyan( )
348+ ) ;
322349 }
323350 "aider" => {
324351 // Aider takes --message flag
@@ -333,9 +360,13 @@ fn launch_agent(agent: &str, _spec_name: &str, content: &str) -> Result<(), Box<
333360 . spawn ( ) ?;
334361 }
335362 _ => {
336- println ! ( " {} Launch {} manually with the spec content" , "ℹ" . cyan( ) , agent) ;
363+ println ! (
364+ " {} Launch {} manually with the spec content" ,
365+ "ℹ" . cyan( ) ,
366+ agent
367+ ) ;
337368 }
338369 }
339-
370+
340371 Ok ( ( ) )
341372}
0 commit comments