diff --git a/server/src/agents.rs b/server/src/agents.rs index c22ab23..d33fd4d 100644 --- a/server/src/agents.rs +++ b/server/src/agents.rs @@ -1283,6 +1283,12 @@ impl AgentPool { false }; + // Mergemaster slot is now free — trigger auto-assign so remaining + // items in 4_merge/ (or other stages) get picked up. The normal + // server-owned completion handler won't run because we already + // removed the agent entry above. + self.auto_assign_available_work(project_root).await; + Ok(MergeReport { story_id: story_id.to_string(), success: true, @@ -2797,57 +2803,17 @@ fn run_squash_merge( }); } - // ── Install frontend dependencies for quality gates ────────────── - let frontend_dir_for_install = merge_wt_path.join("frontend"); - if frontend_dir_for_install.exists() { - // Ensure frontend/dist/ exists so cargo clippy (RustEmbed) can compile - // even before `pnpm build` has run. - let dist_dir = frontend_dir_for_install.join("dist"); - std::fs::create_dir_all(&dist_dir) - .map_err(|e| format!("Failed to create frontend/dist: {e}"))?; - - all_output.push_str("=== pnpm install (merge worktree) ===\n"); - let pnpm_install = Command::new("pnpm") - .args(["install"]) - .current_dir(&frontend_dir_for_install) - .output() - .map_err(|e| format!("Failed to run pnpm install: {e}"))?; - - let install_out = format!( - "{}{}", - String::from_utf8_lossy(&pnpm_install.stdout), - String::from_utf8_lossy(&pnpm_install.stderr) - ); - all_output.push_str(&install_out); - all_output.push('\n'); - - if !pnpm_install.status.success() { - all_output.push_str("=== pnpm install FAILED — aborting merge ===\n"); - cleanup_merge_workspace(project_root, &merge_wt_path, &merge_branch); - return Ok(SquashMergeResult { - success: false, - had_conflicts, - conflicts_resolved, - conflict_details, - output: all_output, - gates_passed: false, - }); - } - } - // ── Install frontend dependencies for quality gates ────────── let frontend_dir = merge_wt_path.join("frontend"); if frontend_dir.exists() { // Ensure frontend/dist exists so RustEmbed (cargo clippy) doesn't fail // even before pnpm build runs. let dist_dir = frontend_dir.join("dist"); - if !dist_dir.exists() { - let _ = std::fs::create_dir_all(&dist_dir); - } + let _ = std::fs::create_dir_all(&dist_dir); all_output.push_str("=== pnpm install (merge worktree) ===\n"); let pnpm_install = Command::new("pnpm") - .args(["install", "--frozen-lockfile"]) + .args(["install"]) .current_dir(&frontend_dir) .output() .map_err(|e| format!("Failed to run pnpm install: {e}"))?; diff --git a/server/src/log_buffer.rs b/server/src/log_buffer.rs index f0810c8..473cb51 100644 --- a/server/src/log_buffer.rs +++ b/server/src/log_buffer.rs @@ -7,6 +7,9 @@ //! `get_server_logs` MCP tool. use std::collections::VecDeque; +use std::fs::OpenOptions; +use std::io::Write; +use std::path::PathBuf; use std::sync::{Mutex, OnceLock}; const CAPACITY: usize = 1000; @@ -68,12 +71,22 @@ impl LogEntry { pub struct LogBuffer { entries: Mutex>, + log_file: Mutex>, } impl LogBuffer { fn new() -> Self { Self { entries: Mutex::new(VecDeque::with_capacity(CAPACITY)), + log_file: Mutex::new(None), + } + } + + /// Set the persistent log file path. Call once at startup after the + /// project root is known. + pub fn set_log_file(&self, path: PathBuf) { + if let Ok(mut f) = self.log_file.lock() { + *f = Some(path); } } @@ -86,6 +99,15 @@ impl LogBuffer { message, }; eprintln!("{}", entry.colored_formatted()); + + // Append to persistent log file (best-effort). + if let Ok(guard) = self.log_file.lock() + && let Some(ref path) = *guard + && let Ok(mut file) = OpenOptions::new().create(true).append(true).open(path) + { + let _ = writeln!(file, "{}", entry.formatted()); + } + if let Ok(mut buf) = self.entries.lock() { if buf.len() >= CAPACITY { buf.pop_front(); @@ -188,6 +210,7 @@ mod tests { fn evicts_oldest_at_capacity() { let buf = LogBuffer { entries: Mutex::new(VecDeque::with_capacity(CAPACITY)), + log_file: Mutex::new(None), }; // Fill past capacity for i in 0..=CAPACITY { diff --git a/server/src/main.rs b/server/src/main.rs index 17d477f..f5d4773 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -100,6 +100,13 @@ async fn main() -> Result<(), std::io::Error> { } } + // Enable persistent server log file now that the project root is known. + if let Some(ref root) = *app_state.project_root.lock().unwrap() { + let log_dir = root.join(".story_kit").join("logs"); + let _ = std::fs::create_dir_all(&log_dir); + log_buffer::global().set_log_file(log_dir.join("server.log")); + } + let workflow = Arc::new(std::sync::Mutex::new(WorkflowState::default())); // Filesystem watcher: broadcast channel for work/ pipeline changes.