From 4c09aa13a50064a4145ef53021490d303e46bc5e Mon Sep 17 00:00:00 2001 From: soryu Date: Fri, 30 Jan 2026 03:28:40 +0000 Subject: Add auto_merge_local option for local-only contracts When local_only=true on a contract, all completion actions are skipped. This adds a new option auto_merge_local that, when enabled along with local_only, will automatically merge completed task changes to the master/main branch locally (without pushing or creating PRs). Changes: - Add auto_merge_local column to contracts table (migration) - Add auto_merge_local field to Contract model and summary - Update CreateContractRequest and UpdateContractRequest structs - Update contract repository create/update functions - Add auto_merge_local to WebSocket protocol StartTask command - Pass auto_merge_local through spawn_task and run_task functions - Modify task manager completion logic: if local_only=true AND auto_merge_local=true, execute 'merge' completion action locally - Update all server handlers to retrieve and pass auto_merge_local - Add TypeScript types to frontend components Co-Authored-By: Claude Opus 4.5 --- makima/src/db/models.rs | 18 ++++++++++++++++++ makima/src/db/repository.rs | 19 ++++++++++++------- 2 files changed, 30 insertions(+), 7 deletions(-) (limited to 'makima/src/db') diff --git a/makima/src/db/models.rs b/makima/src/db/models.rs index 4411747..a6b5b05 100644 --- a/makima/src/db/models.rs +++ b/makima/src/db/models.rs @@ -1448,6 +1448,11 @@ pub struct Contract { /// allowing users to manually handle code changes via patch files or other means. #[serde(default)] pub local_only: bool, + /// Whether to auto-merge to target branch locally when local_only mode is enabled. + /// When both local_only and auto_merge_local are true, completed task changes will be + /// automatically merged to the master/main branch locally (without pushing or creating PRs). + #[serde(default)] + pub auto_merge_local: bool, /// Whether to spawn a red team task to monitor work tasks. /// When enabled, a parallel task monitors outputs and can alert /// the supervisor about potential issues. @@ -1641,6 +1646,9 @@ pub struct ContractSummary { /// When true, tasks do not auto-execute completion actions and work stays in worktrees. #[serde(default)] pub local_only: bool, + /// When true with local_only, automatically merge completed tasks to target branch locally. + #[serde(default)] + pub auto_merge_local: bool, /// Whether red team monitoring is enabled for this contract. #[serde(default)] pub red_team_enabled: bool, @@ -1710,6 +1718,11 @@ pub struct CreateContractRequest { /// allowing users to manually handle code changes via patch files or other means. #[serde(default)] pub local_only: Option, + /// Enable auto-merge to target branch locally when local_only mode is enabled. + /// When both local_only and auto_merge_local are true, completed task changes will be + /// automatically merged to the master/main branch locally (without pushing or creating PRs). + #[serde(default)] + pub auto_merge_local: Option, /// Enable red team monitoring for this contract. /// When enabled, a parallel task monitors work task outputs /// and can alert the supervisor about potential issues. @@ -1745,6 +1758,11 @@ pub struct UpdateContractRequest { /// allowing users to manually handle code changes via patch files or other means. #[serde(default)] pub local_only: Option, + /// Enable or disable auto-merge to target branch locally when local_only mode is enabled. + /// When both local_only and auto_merge_local are true, completed task changes will be + /// automatically merged to the master/main branch locally (without pushing or creating PRs). + #[serde(default)] + pub auto_merge_local: Option, /// Version for optimistic locking pub version: Option, } diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs index 9a1bf2d..9fc2c84 100644 --- a/makima/src/db/repository.rs +++ b/makima/src/db/repository.rs @@ -2462,6 +2462,7 @@ pub async fn create_contract_for_owner( let autonomous_loop = req.autonomous_loop.unwrap_or(false); let phase_guard = req.phase_guard.unwrap_or(false); let local_only = req.local_only.unwrap_or(false); + let auto_merge_local = req.auto_merge_local.unwrap_or(false); let red_team_enabled = req.red_team_enabled.unwrap_or(false); // Serialize phase_config to JSON @@ -2469,8 +2470,8 @@ pub async fn create_contract_for_owner( sqlx::query_as::<_, Contract>( r#" - INSERT INTO contracts (owner_id, name, description, contract_type, phase, autonomous_loop, phase_guard, local_only, red_team_enabled, red_team_prompt, phase_config) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) + INSERT INTO contracts (owner_id, name, description, contract_type, phase, autonomous_loop, phase_guard, local_only, auto_merge_local, red_team_enabled, red_team_prompt, phase_config) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING * "#, ) @@ -2482,6 +2483,7 @@ pub async fn create_contract_for_owner( .bind(autonomous_loop) .bind(phase_guard) .bind(local_only) + .bind(auto_merge_local) .bind(red_team_enabled) .bind(&req.red_team_prompt) .bind(phase_config_json) @@ -2517,7 +2519,7 @@ pub async fn list_contracts_for_owner( r#" SELECT c.id, c.name, c.description, c.contract_type, c.phase, c.status, - c.supervisor_task_id, c.local_only, c.red_team_enabled, c.version, c.created_at, + c.supervisor_task_id, c.local_only, c.auto_merge_local, c.red_team_enabled, c.version, c.created_at, (SELECT COUNT(*) FROM files WHERE contract_id = c.id) as file_count, (SELECT COUNT(*) FROM tasks WHERE contract_id = c.id) as task_count, (SELECT COUNT(*) FROM contract_repositories WHERE contract_id = c.id) as repository_count @@ -2541,7 +2543,7 @@ pub async fn get_contract_summary_for_owner( r#" SELECT c.id, c.name, c.description, c.contract_type, c.phase, c.status, - c.supervisor_task_id, c.local_only, c.red_team_enabled, c.version, c.created_at, + c.supervisor_task_id, c.local_only, c.auto_merge_local, c.red_team_enabled, c.version, c.created_at, (SELECT COUNT(*) FROM files WHERE contract_id = c.id) as file_count, (SELECT COUNT(*) FROM tasks WHERE contract_id = c.id) as task_count, (SELECT COUNT(*) FROM contract_repositories WHERE contract_id = c.id) as repository_count @@ -2586,14 +2588,15 @@ pub async fn update_contract_for_owner( let autonomous_loop = req.autonomous_loop.unwrap_or(existing.autonomous_loop); let phase_guard = req.phase_guard.unwrap_or(existing.phase_guard); let local_only = req.local_only.unwrap_or(existing.local_only); + let auto_merge_local = req.auto_merge_local.unwrap_or(existing.auto_merge_local); let result = if req.version.is_some() { sqlx::query_as::<_, Contract>( r#" UPDATE contracts SET name = $3, description = $4, phase = $5, status = $6, - supervisor_task_id = $7, autonomous_loop = $8, phase_guard = $9, local_only = $10, version = version + 1, updated_at = NOW() - WHERE id = $1 AND owner_id = $2 AND version = $11 + supervisor_task_id = $7, autonomous_loop = $8, phase_guard = $9, local_only = $10, auto_merge_local = $11, version = version + 1, updated_at = NOW() + WHERE id = $1 AND owner_id = $2 AND version = $12 RETURNING * "#, ) @@ -2607,6 +2610,7 @@ pub async fn update_contract_for_owner( .bind(autonomous_loop) .bind(phase_guard) .bind(local_only) + .bind(auto_merge_local) .bind(req.version.unwrap()) .fetch_optional(pool) .await? @@ -2615,7 +2619,7 @@ pub async fn update_contract_for_owner( r#" UPDATE contracts SET name = $3, description = $4, phase = $5, status = $6, - supervisor_task_id = $7, autonomous_loop = $8, phase_guard = $9, local_only = $10, version = version + 1, updated_at = NOW() + supervisor_task_id = $7, autonomous_loop = $8, phase_guard = $9, local_only = $10, auto_merge_local = $11, version = version + 1, updated_at = NOW() WHERE id = $1 AND owner_id = $2 RETURNING * "#, @@ -2630,6 +2634,7 @@ pub async fn update_contract_for_owner( .bind(autonomous_loop) .bind(phase_guard) .bind(local_only) + .bind(auto_merge_local) .fetch_optional(pool) .await? }; -- cgit v1.2.3