From 6468bf877915a8b908ebe8532e8d576c1435a843 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Tue, 17 Mar 2026 15:08:59 +1100 Subject: [PATCH 1/4] feat: auto-trigger code review when adding repos with existing commits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When creating a new project with a repo or adding a repo to an existing project, automatically kick off an auto code review if the branch already has commits (e.g. from an existing PR). Adds a shared helper `maybe_trigger_auto_review_for_new_repo` that: - For local branches, checks for commits since base via the worktree and only triggers a review if commits exist - For remote branches, optimistically triggers the review and lets `trigger_auto_review` resolve the HEAD via the workspace The helper is called from three code paths: - `create_project` (lib.rs) — after worktree + prerun actions setup - `add_project_repo` Tauri command (lib.rs) — after worktree/remote clone setup, for both local and remote branches - `add_project_repo` MCP tool (project_mcp.rs) — after worktree + prerun actions setup --- apps/staged/src-tauri/src/lib.rs | 102 ++++++++++++++++++++++- apps/staged/src-tauri/src/project_mcp.rs | 22 +++-- 2 files changed, 113 insertions(+), 11 deletions(-) diff --git a/apps/staged/src-tauri/src/lib.rs b/apps/staged/src-tauri/src/lib.rs index 338d165d..10278a4c 100644 --- a/apps/staged/src-tauri/src/lib.rs +++ b/apps/staged/src-tauri/src/lib.rs @@ -209,6 +209,66 @@ fn confirm_reset_store( // Project commands // ============================================================================= +/// Check whether a newly added repo already has commits on its branch and, if +/// so, kick off an automatic code review. +/// +/// For **local** branches the check is done by inspecting the worktree on disk. +/// For **remote** branches we optimistically trigger the review and let +/// `trigger_auto_review` resolve the HEAD via the workspace. +/// +/// This is fire-and-forget — errors are logged but never propagated. +pub(crate) async fn maybe_trigger_auto_review_for_new_repo( + store: &Arc, + app_handle: &tauri::AppHandle, + branch_id: &str, + worktree_path: Option<&str>, +) { + // For local branches, check whether there are any commits on the branch + // relative to its base before spinning up a review session. + if let Some(path) = worktree_path { + let branch = match store.get_branch(branch_id) { + Ok(Some(b)) => b, + _ => return, + }; + let worktree = std::path::PathBuf::from(path); + match git::get_commits_since_base(&worktree, &branch.base_branch) { + Ok(commits) if commits.is_empty() => { + log::info!( + "[auto_review] branch {branch_id} has no commits yet — skipping auto review" + ); + return; + } + Err(e) => { + log::warn!("[auto_review] failed to check commits for branch {branch_id}: {e}"); + return; + } + Ok(_) => { /* has commits — fall through to trigger */ } + } + } + + let registry = app_handle.state::>(); + match session_commands::trigger_auto_review( + Arc::clone(store), + Arc::clone(®istry), + app_handle.clone(), + branch_id.to_string(), + None, + ) + .await + { + Ok(resp) => { + log::info!( + "[auto_review] triggered for new repo on branch {branch_id}: session={}, review={}", + resp.session_id, + resp.artifact_id, + ); + } + Err(e) => { + log::warn!("[auto_review] failed to trigger for branch {branch_id}: {e}"); + } + } +} + #[tauri::command] fn list_projects( store: tauri::State<'_, Mutex>>>, @@ -346,10 +406,11 @@ fn create_project( }) .await; - match worktree_result { + let worktree_path = match worktree_result { Ok(Ok(path)) => { log::info!("[create_project] worktree ready at {path}"); let _ = app_handle.emit("project-setup-progress", project_id.clone()); + path } Ok(Err(e)) => { log::warn!("[create_project] worktree setup failed: {e}"); @@ -359,7 +420,7 @@ fn create_project( log::warn!("[create_project] worktree task panicked: {e}"); return; } - } + }; let executor = app_handle.state::>(); let act_registry = app_handle.state::>(); @@ -380,6 +441,16 @@ fn create_project( log::warn!("[create_project] prerun actions failed: {e}"); } } + + // If the repo already has commits on this branch, kick off + // an automatic code review so the user gets immediate feedback. + maybe_trigger_auto_review_for_new_repo( + &store_bg, + &app_handle, + &branch_id, + Some(&worktree_path), + ) + .await; }); } } else if project.location == store::ProjectLocation::Remote { @@ -472,10 +543,11 @@ async fn add_project_repo( }) .await; - match worktree_result { + let worktree_path = match worktree_result { Ok(Ok(path)) => { log::info!("[add_project_repo] worktree ready at {path}"); let _ = app_handle.emit("project-setup-progress", project_id.clone()); + path } Ok(Err(e)) => { log::warn!("[add_project_repo] worktree setup failed: {e}"); @@ -485,7 +557,7 @@ async fn add_project_repo( log::warn!("[add_project_repo] worktree task panicked: {e}"); return; } - } + }; let executor = app_handle.state::>(); let act_registry = app_handle.state::>(); @@ -506,6 +578,16 @@ async fn add_project_repo( log::warn!("[add_project_repo] prerun actions failed: {e}"); } } + + // If the repo already has commits on this branch, kick off + // an automatic code review so the user gets immediate feedback. + maybe_trigger_auto_review_for_new_repo( + &store, + &app_handle, + &branch.id, + Some(&worktree_path), + ) + .await; } else { // Remote branch: clone the repo into the running workspace, // fetch the base branch, and create the feature branch. @@ -521,9 +603,21 @@ async fn add_project_repo( "[add_project_repo] remote repo clone failed for branch '{}': {e}", branch.branch_name ); + let _ = app_handle.emit("project-setup-progress", project_id); + return; } } let _ = app_handle.emit("project-setup-progress", project_id); + + // If the repo already has commits on this branch, kick off + // an automatic code review so the user gets immediate feedback. + maybe_trigger_auto_review_for_new_repo( + &store, + &app_handle, + &branch.id, + None, // remote — trigger_auto_review resolves HEAD via workspace + ) + .await; } } }); diff --git a/apps/staged/src-tauri/src/project_mcp.rs b/apps/staged/src-tauri/src/project_mcp.rs index a82b0d1d..fb4297f3 100644 --- a/apps/staged/src-tauri/src/project_mcp.rs +++ b/apps/staged/src-tauri/src/project_mcp.rs @@ -637,16 +637,14 @@ impl ProjectToolsHandler { }) .await; - match worktree_result { - Ok(Ok(worktree_path)) => { - log::debug!( - "[project_mcp] add_project_repo: worktree ready at {}", - worktree_path - ); + let worktree_path = match worktree_result { + Ok(Ok(path)) => { + log::debug!("[project_mcp] add_project_repo: worktree ready at {}", path); // Notify UI that the worktree is ready so branch state updates let _ = self .app_handle .emit("project-setup-progress", self.project_id.clone()); + path } Ok(Err(e)) => { log::warn!( @@ -665,7 +663,7 @@ impl ProjectToolsHandler { "Added repository {github_repo} to project (worktree task error: {e})" ); } - } + }; // Run detect_actions + prerun actions if we have an executor if let (Some(executor), Some(act_registry)) = @@ -705,6 +703,16 @@ impl ProjectToolsHandler { "[project_mcp] add_project_repo: no action executor available, skipping prerun actions" ); } + + // If the repo already has commits on this branch, kick off + // an automatic code review so the user gets immediate feedback. + crate::maybe_trigger_auto_review_for_new_repo( + &self.store, + &self.app_handle, + &branch.id, + Some(&worktree_path), + ) + .await; } format!("Added repository {github_repo} to project") From 5e6a933493f92557da93f3659e65b8afa8f1a73e Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Tue, 17 Mar 2026 15:32:21 +1100 Subject: [PATCH 2/4] fix: trigger auto-review for remote projects in create_project Previously the create_project path set branch_id to None for remote projects, which meant the if-let block containing the auto-review trigger was never entered. Remote projects created with existing commits (e.g. from a PR) would not receive an automatic code review. Restructure the match to always return the branch ID alongside a boolean indicating local vs remote. Local branches follow the existing worktree + prerun-actions + auto-review path. Remote branches now spawn a task that optimistically triggers auto-review via the workspace, matching the behavior already present in the add_project_repo command. --- apps/staged/src-tauri/src/lib.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/apps/staged/src-tauri/src/lib.rs b/apps/staged/src-tauri/src/lib.rs index 10278a4c..0dfedfff 100644 --- a/apps/staged/src-tauri/src/lib.rs +++ b/apps/staged/src-tauri/src/lib.rs @@ -358,7 +358,7 @@ fn create_project( format!("origin/{detected_base}") }; - let branch_id = match project.location { + let (branch_id, is_local) = match project.location { store::ProjectLocation::Local => { let mut branch = store::Branch::new(&project.id, &inferred_branch_name, &effective_base) @@ -367,7 +367,7 @@ fn create_project( branch = branch.with_pr(pr); } store.create_branch(&branch).map_err(|e| e.to_string())?; - Some(branch.id) + (branch.id, true) } store::ProjectLocation::Remote => { let workspace_name = branches::infer_workspace_name(&inferred_branch_name); @@ -388,12 +388,12 @@ fn create_project( workspace_name, project.id ); - None // remote branches don't need worktree setup + (branch.id, false) } }; - // Spawn background worktree + prerun-actions setup for local branches. - if let Some(branch_id) = branch_id { + if is_local { + // Spawn background worktree + prerun-actions setup for local branches. let project_id = project.id.clone(); let store_bg = Arc::clone(&store); tauri::async_runtime::spawn(async move { @@ -452,6 +452,14 @@ fn create_project( ) .await; }); + } else { + // Remote branches: optimistically trigger auto-review (the + // workspace will resolve HEAD). No worktree setup needed. + let store_bg = Arc::clone(&store); + tauri::async_runtime::spawn(async move { + maybe_trigger_auto_review_for_new_repo(&store_bg, &app_handle, &branch_id, None) + .await; + }); } } else if project.location == store::ProjectLocation::Remote { log::info!( From e1a80bb1ebdc119c0f2252f00f8e23ddf4bfb5e9 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Tue, 17 Mar 2026 16:03:04 +1100 Subject: [PATCH 3/4] fix: fetch remote branch when setting up workspace for existing PRs When creating a remote project from an existing PR, the workspace was checking out the local branch from origin/{base_ref} (e.g. origin/main) instead of origin/{branch_name}. This meant the local branch had zero commits ahead of the base, so the branch card showed no commits. Fix both code paths that set up the branch in a remote workspace: - clone_repo_into_workspace: After fetching the base branch, also attempt to fetch the feature branch from origin. If it exists, create the local branch from origin/{branch_name} instead of origin/{base_ref}. - start_workspace: After ws_start completes (which clones at the base ref), fetch the feature branch from origin. If it exists, use 'checkout -B' to create the local branch from the remote tracking ref, picking up all existing PR commits. --- apps/staged/src-tauri/src/branches.rs | 53 ++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/apps/staged/src-tauri/src/branches.rs b/apps/staged/src-tauri/src/branches.rs index 94ef5cca..b51c0e5b 100644 --- a/apps/staged/src-tauri/src/branches.rs +++ b/apps/staged/src-tauri/src/branches.rs @@ -337,10 +337,31 @@ async fn clone_repo_into_workspace( "Failed to fetch base branch '{base_ref}' for '{repo_slug}' in workspace '{ws_name}': {e}" ))?; + // If the branch already exists on the remote (e.g. from an existing PR), + // fetch it and start from there so we pick up its commits. Otherwise fall + // back to starting from the base branch. + let has_remote_branch = if branch_name != base_ref { + run_workspace_git_async( + ws_name, + Some(repo_subpath), + &["fetch", "origin", branch_name], + ) + .await + .is_ok() + } else { + false + }; + + let start_point = if has_remote_branch { + format!("origin/{branch_name}") + } else { + format!("origin/{base_ref}") + }; + run_workspace_git_async( ws_name, Some(repo_subpath), - &["checkout", "-B", branch_name, &format!("origin/{base_ref}")], + &["checkout", "-B", branch_name, &start_point], ) .await .map_err(|e| { @@ -1124,15 +1145,35 @@ pub async fn start_workspace( ws_name, ws_start_started_at.elapsed().as_millis() ); - // Create the feature branch inside the workspace so work happens - // on `branch_name` rather than the detached base ref. - if let Err(e) = run_workspace_git_async( + // If the branch already exists on the remote (e.g. from an + // existing PR), fetch it and start from there so we pick up its + // commits. Otherwise create a fresh branch from the base ref. + let has_remote_branch = run_workspace_git_async( ws_name, repo_subpath.as_deref(), - &["checkout", "-b", &branch.branch_name], + &["fetch", "origin", &branch.branch_name], ) .await - { + .is_ok(); + + let remote_ref = format!("origin/{}", branch.branch_name); + let checkout_result = if has_remote_branch { + run_workspace_git_async( + ws_name, + repo_subpath.as_deref(), + &["checkout", "-B", &branch.branch_name, &remote_ref], + ) + .await + } else { + run_workspace_git_async( + ws_name, + repo_subpath.as_deref(), + &["checkout", "-b", &branch.branch_name], + ) + .await + }; + + if let Err(e) = checkout_result { log::warn!( "failed to create branch '{}' in workspace '{}': {e}", branch.branch_name, From 794115cf950f00d4ad212350a05bb20104c421e3 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Tue, 17 Mar 2026 17:11:43 +1100 Subject: [PATCH 4/4] fix: defer remote auto-review until workspace is running MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The auto-review for remote branches in create_project was firing immediately after creating the branch record, before the frontend called start_workspace. The workspace wasn't running yet, so the `sq blox acp` process would exit immediately with: ACP init failed: server shut down unexpectedly Move the remote auto-review trigger from create_project into start_workspace, which is where the workspace actually becomes ready and the branch is checked out. The trigger fires on both success paths (primary ws_start and secondary repo clone into an already-running workspace). To avoid spurious reviews on workspace restarts, the trigger only fires on the first start (Starting → Running), not on restarts (Stopped → Running). Also update the doc comment on maybe_trigger_auto_review_for_new_repo to clarify that callers are responsible for ensuring the workspace is running before calling it for remote branches. --- apps/staged/src-tauri/src/branches.rs | 44 +++++++++++++++++++++++++++ apps/staged/src-tauri/src/lib.rs | 19 ++++++------ 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/apps/staged/src-tauri/src/branches.rs b/apps/staged/src-tauri/src/branches.rs index b51c0e5b..6afd3a9e 100644 --- a/apps/staged/src-tauri/src/branches.rs +++ b/apps/staged/src-tauri/src/branches.rs @@ -1047,6 +1047,7 @@ pub async fn create_remote_branch( #[tauri::command(rename_all = "camelCase")] pub async fn start_workspace( store: tauri::State<'_, Mutex>>>, + app_handle: AppHandle, branch_id: String, ) -> Result<(), String> { let store = get_store(&store)?; @@ -1056,6 +1057,12 @@ pub async fn start_workspace( .map_err(|e| e.to_string())? .ok_or_else(|| format!("Branch not found: {branch_id}"))?; + // Track whether this is the first workspace start (Starting → Running) + // vs a restart (Stopped → Running). We only trigger auto-review on the + // first start so that existing branches don't get a spurious review + // every time the workspace restarts. + let is_first_start = branch.workspace_status == Some(store::WorkspaceStatus::Starting); + let project = store .get_project(&branch.project_id) .map_err(|e| e.to_string())? @@ -1109,6 +1116,24 @@ pub async fn start_workspace( store .update_branch_workspace_status(&branch_id, &store::WorkspaceStatus::Running) .map_err(|e| e.to_string())?; + + // Trigger auto-review for the newly cloned secondary repo + // if this is the first start for this branch. + if is_first_start { + let store_bg = Arc::clone(&store); + let app_handle_bg = app_handle.clone(); + let branch_id_bg = branch_id.clone(); + tauri::async_runtime::spawn(async move { + crate::maybe_trigger_auto_review_for_new_repo( + &store_bg, + &app_handle_bg, + &branch_id_bg, + None, + ) + .await; + }); + } + return Ok(()); } } @@ -1180,6 +1205,25 @@ pub async fn start_workspace( ws_name ); } + + // If this is the first workspace start for a new branch, check + // whether the branch already has commits (e.g. from an existing + // PR) and kick off an automatic code review. + if is_first_start { + let store_bg = Arc::clone(&store); + let app_handle_bg = app_handle.clone(); + let branch_id_bg = branch_id.clone(); + tauri::async_runtime::spawn(async move { + crate::maybe_trigger_auto_review_for_new_repo( + &store_bg, + &app_handle_bg, + &branch_id_bg, + None, + ) + .await; + }); + } + Ok(()) } Err(blox::BloxError::NotAuthenticated) => { diff --git a/apps/staged/src-tauri/src/lib.rs b/apps/staged/src-tauri/src/lib.rs index 0dfedfff..5540b8ee 100644 --- a/apps/staged/src-tauri/src/lib.rs +++ b/apps/staged/src-tauri/src/lib.rs @@ -212,9 +212,12 @@ fn confirm_reset_store( /// Check whether a newly added repo already has commits on its branch and, if /// so, kick off an automatic code review. /// -/// For **local** branches the check is done by inspecting the worktree on disk. -/// For **remote** branches we optimistically trigger the review and let -/// `trigger_auto_review` resolve the HEAD via the workspace. +/// For **local** branches (`worktree_path: Some`) the check is done by +/// inspecting the worktree on disk before triggering. +/// For **remote** branches (`worktree_path: None`) the review is triggered +/// unconditionally — the caller is responsible for ensuring the workspace is +/// running before calling this (e.g. after `start_workspace` or +/// `setup_remote_repo_clone` completes). /// /// This is fire-and-forget — errors are logged but never propagated. pub(crate) async fn maybe_trigger_auto_review_for_new_repo( @@ -453,13 +456,9 @@ fn create_project( .await; }); } else { - // Remote branches: optimistically trigger auto-review (the - // workspace will resolve HEAD). No worktree setup needed. - let store_bg = Arc::clone(&store); - tauri::async_runtime::spawn(async move { - maybe_trigger_auto_review_for_new_repo(&store_bg, &app_handle, &branch_id, None) - .await; - }); + // Remote branches: auto-review is deferred until `start_workspace` + // completes — the workspace isn't running yet at this point, so + // attempting to trigger a review here would fail. } } else if project.location == store::ProjectLocation::Remote { log::info!(