diff --git a/src/headless.rs b/src/headless.rs index ebc5683..cf96b31 100644 --- a/src/headless.rs +++ b/src/headless.rs @@ -215,10 +215,24 @@ fn run_once(configs: &[config::AgentConfig]) -> Result<(), String> { let running = Arc::new(AtomicBool::new(true)); setup_ctrlc(running.clone()); let agents = spawn_and_wait(configs, &running); - if let Some(a) = agents.first() { - write_state(&agents); - println!("{}", strip_dir_listing(&a.output)); + if agents.is_empty() { return Ok(()); } + write_state(&agents); + + let output = &agents[0].output; + let clean = strip_dir_listing(output); + println!("{clean}"); + + // If the agent proposed @agents, chain them + let next = extract_agent_configs(clean); + if !next.is_empty() { + eprintln!("\n AI proposed {} agent(s):", next.len()); + for c in &next { eprintln!(" @{} {}", c.name, c.task); } + + let _ = std::fs::remove_dir_all(STATE_DIR); + create_state_dir(); + return run_with_configs(next); } + Ok(()) } @@ -408,9 +422,7 @@ fn save_session(cycle: usize, agents: &[Agent], decision: &str, indices: &[usize let json = serde_json::to_string_pretty(&session).unwrap_or_default(); atomic_write(&filename, json.as_bytes()); - if !decision.is_empty() { - save_to_aigpt_memory(decision, &saved_agents); - } + // aigpt memory is NOT auto-saved. Use `aishell remember` for important decisions. // Update unified command history update_cmd_history(&saved_agents, timestamp); @@ -632,6 +644,77 @@ pub fn decision() { } } +/// Run review preset, get commit message, and commit. +pub fn commit() { + use std::process::Command; + + // Check if there are changes + let status = Command::new("git").args(["status", "--short"]).output(); + match status { + Ok(o) if o.stdout.is_empty() => { + eprintln!("nothing to commit"); + return; + } + Err(e) => { eprintln!("git error: {e}"); return; } + _ => {} + } + + // Run commit-msg agent + eprintln!(" generating commit message..."); + let cwd = std::env::current_dir() + .map(|p| p.display().to_string()) + .unwrap_or_else(|_| ".".to_string()); + let configs = vec![config::AgentConfig { + name: "commit-msg".into(), + task: "Suggest a commit message for the current git changes. Output ONLY the message, nothing else. Conventional commits format. One line.".into(), + cwd, + }]; + + let running = Arc::new(AtomicBool::new(true)); + setup_ctrlc(running.clone()); + let agents = spawn_and_wait(&configs, &running); + if agents.is_empty() { return; } + + let msg = strip_dir_listing(&agents[0].output).trim().to_string(); + let msg = msg.trim_matches('`').trim().to_string(); + if msg.is_empty() { + eprintln!(" failed to generate message"); + return; + } + + eprintln!(" message: {msg}"); + eprintln!(" committing..."); + + let _ = Command::new("git").args(["add", "-A"]).status(); + match Command::new("git").args(["commit", "-m", &msg]).status() { + Ok(s) if s.success() => eprintln!(" ✓ committed"), + Ok(s) => eprintln!(" ✗ git commit failed: {}", s.code().unwrap_or(-1)), + Err(e) => eprintln!(" ✗ {e}"), + } +} + +/// Save the latest decision to aigpt memory (explicit, not automatic). +pub fn remember() { + let dec_path = format!("{STATE_DIR}/decision.json"); + match std::fs::read_to_string(&dec_path) { + Ok(content) => { + if let Ok(d) = serde_json::from_str::(&content) { + let decision = d["decision"].as_str().unwrap_or(""); + let agents = d["agents"].as_array() + .map(|a| a.to_vec()) + .unwrap_or_default(); + if decision.is_empty() { + eprintln!("No decision to remember."); + return; + } + save_to_aigpt_memory(decision, &agents); + eprintln!("saved to aigpt memory"); + } + } + Err(_) => eprintln!("No decision. Run a cycle first."), + } +} + pub fn signal_next(save: Vec) { let save_val = if save.is_empty() { serde_json::json!(null) diff --git a/src/main.rs b/src/main.rs index ee4d37a..4bf1407 100644 --- a/src/main.rs +++ b/src/main.rs @@ -92,7 +92,9 @@ fn main() { aishell::headless::log(&id); } Some("decision") => aishell::headless::decision(), + Some("commit") => aishell::headless::commit(), Some("context") => aishell::headless::context(), + Some("remember") => aishell::headless::remember(), Some("history") => { let sub = env::args().nth(2); match sub.as_deref() { @@ -134,10 +136,12 @@ fn print_help() { println!(" aishell run Run single agent"); println!(" aishell run -p Preset: daily, review, improve"); println!(" aishell run -f Custom config file"); + println!(" aishell commit Git commit with AI-suggested message"); println!(" aishell status [-v] Show agent status"); println!(" aishell log Show agent output"); println!(" aishell decision Show AI integration result"); println!(" aishell context Full project context (for AI)"); + println!(" aishell remember Save latest decision to AI memory"); println!(" aishell history [id] Show past sessions"); println!(" aishell history cmd [grep] Command history (filterable)"); println!(" aishell history clean Remove old sessions (keep 10)");