diff options
| author | soryu <soryu@soryu.co> | 2026-02-13 00:57:34 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-02-13 00:57:34 +0000 |
| commit | 639b4c6bc3b3964c00cdfc64c4f262c61ee22fc7 (patch) | |
| tree | 6ba4c1cd323f728453b95e19b9215e40974d1377 | |
| parent | 39df770ea4ae37f3f9e30c5be5274b741e7ecf7b (diff) | |
| download | soryu-639b4c6bc3b3964c00cdfc64c4f262c61ee22fc7.tar.gz soryu-639b4c6bc3b3964c00cdfc64c4f262c61ee22fc7.zip | |
Make sure directive tasks inherit worktrees
| -rw-r--r-- | makima/src/db/repository.rs | 29 | ||||
| -rw-r--r-- | makima/src/orchestration/directive.rs | 74 |
2 files changed, 95 insertions, 8 deletions
diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs index a79818f..51f49cd 100644 --- a/makima/src/db/repository.rs +++ b/makima/src/db/repository.rs @@ -5298,6 +5298,30 @@ pub async fn get_completed_step_tasks( .await } +/// Get the task ID of the most recently completed step for a directive. +/// Used as a fallback `continue_from_task_id` when dispatching new-generation steps +/// that have no explicit dependencies and no PR branch to continue from. +pub async fn get_last_completed_step_task_id( + pool: &PgPool, + directive_id: Uuid, +) -> Result<Option<Uuid>, sqlx::Error> { + let row: Option<(Uuid,)> = sqlx::query_as( + r#" + SELECT ds.task_id + FROM directive_steps ds + WHERE ds.directive_id = $1 + AND ds.status = 'completed' + AND ds.task_id IS NOT NULL + ORDER BY ds.updated_at DESC + LIMIT 1 + "#, + ) + .bind(directive_id) + .fetch_optional(pool) + .await?; + Ok(row.map(|r| r.0)) +} + // ============================================================================= // Directive Step CRUD // ============================================================================= @@ -5586,6 +5610,8 @@ pub struct StepForDispatch { pub repository_url: Option<String>, pub base_branch: Option<String>, pub memory_enabled: bool, + /// The directive's PR branch (if a PR has already been created from previous steps). + pub pr_branch: Option<String>, } /// Get ready steps that need task dispatch. @@ -5607,7 +5633,8 @@ pub async fn get_ready_steps_for_dispatch( d.title AS directive_title, d.repository_url, d.base_branch, - d.memory_enabled + d.memory_enabled, + d.pr_branch FROM directive_steps ds JOIN directives d ON d.id = ds.directive_id WHERE ds.status = 'ready' diff --git a/makima/src/orchestration/directive.rs b/makima/src/orchestration/directive.rs index d2fcbfd..344bdf5 100644 --- a/makima/src/orchestration/directive.rs +++ b/makima/src/orchestration/directive.rs @@ -101,7 +101,42 @@ impl DirectiveOrchestrator { // Resolve dependency steps to their task IDs for worktree continuation let dep_tasks = repository::get_step_dependency_tasks(&self.pool, &step.depends_on).await?; - let continue_from_task_id = dep_tasks.first().map(|d| d.task_id); + let mut continue_from_task_id = dep_tasks.first().map(|d| d.task_id); + + // If no dependency tasks resolved, try to continue from previous work: + // 1) Use the directive's PR branch as base (contains all previous merged work) + // 2) Fall back to the last completed step's task for worktree continuation + let effective_base_branch = if continue_from_task_id.is_none() { + if step.pr_branch.is_some() { + tracing::info!( + step_id = %step.step_id, + pr_branch = ?step.pr_branch, + "Step has no deps — using directive PR branch as base" + ); + step.pr_branch.as_deref() + } else { + // No PR branch yet — try to continue from the last completed step's worktree + match repository::get_last_completed_step_task_id( + &self.pool, + step.directive_id, + ) + .await + { + Ok(Some(task_id)) => { + tracing::info!( + step_id = %step.step_id, + continue_from = %task_id, + "Step has no deps, no PR branch — continuing from last completed task" + ); + continue_from_task_id = Some(task_id); + step.base_branch.as_deref() + } + _ => step.base_branch.as_deref(), + } + } + } else { + step.base_branch.as_deref() + }; let task_plan = step .task_plan @@ -180,7 +215,7 @@ impl DirectiveOrchestrator { format!("{}: {}", step.directive_title, step.step_name), plan, step.repository_url.as_deref(), - step.base_branch.as_deref(), + effective_base_branch, continue_from_task_id, ) .await @@ -746,13 +781,26 @@ fn build_planning_prompt( "EXISTING STEPS (generation {}):\n", generation - 1 )); + let mut last_completed_id: Option<Uuid> = None; for step in existing_steps { prompt.push_str(&format!( - "- {} [{}]: {}\n", - step.name, + "- [{}] {} (id: {}): {}\n", step.status, + step.name, + step.id, step.description.as_deref().unwrap_or("(no description)") )); + if step.status == "completed" { + last_completed_id = Some(step.id); + } + } + if let Some(last_id) = last_completed_id { + prompt.push_str(&format!( + "\nNew steps that build on previous work SHOULD use --depends-on \"{}\" (the last completed step) \ + so their worktree inherits all prior changes. Without this dependency, new steps start from a \ + fresh checkout and won't see any of the work done by previous steps.\n", + last_id + )); } prompt.push_str(&format!( "\nAdd new steps that build on or complement existing work. Use generation {}.\n\n", @@ -789,9 +837,21 @@ Or batch: makima directive batch-add-steps --json '[{{"name":"...","description":"...","taskPlan":"...","dependsOn":[],"orderIndex":0}}]' DEPENDENCY WORKTREE CONTINUATION: -When a step has dependsOn, it automatically continues from the first dependency's worktree (inheriting -committed and uncommitted changes). If there are multiple dependencies, the first provides the base worktree -and additional dependency branches are merged in before work starts. Use this for incremental work chains. +Each step runs in its own git worktree. How that worktree is initialised depends on dependsOn: +- With dependsOn: the step continues from the first dependency's worktree (inheriting all committed and + uncommitted changes). Additional dependencies are merged in as branches before work starts. +- Without dependsOn: the step starts from a FRESH worktree based on the base branch (or the PR branch if + a PR already exists from previous completions). + +Because of this, you MUST chain steps using dependsOn whenever one step's work builds on another's. +If step B modifies files created/changed by step A, step B MUST list step A in its dependsOn — otherwise +step B will start from a blank worktree and won't see step A's changes at all. + +Guidelines: +- For sequential work, create a linear chain: step1 → step2 → step3 (each depends on the previous). +- Only omit dependsOn for truly independent steps that can start from a fresh checkout. +- Parallel steps that share no files can omit mutual dependencies, but if they both build on a prior step + they should BOTH list that prior step in dependsOn. IMPORTANT: Each step's taskPlan must be self-contained. The executing instance won't have your planning context. "#, |
