diff options
| -rw-r--r-- | makima/src/db/repository.rs | 19 | ||||
| -rw-r--r-- | makima/src/orchestration/directive.rs | 130 | ||||
| -rw-r--r-- | makima/src/server/handlers/directives.rs | 23 |
3 files changed, 153 insertions, 19 deletions
diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs index e288eba..d8168f6 100644 --- a/makima/src/db/repository.rs +++ b/makima/src/db/repository.rs @@ -5465,6 +5465,25 @@ pub async fn delete_directive_step( Ok(result.rows_affected() > 0) } +/// Delete all directive steps that have not started execution (pending, ready, failed, skipped). +/// Completed and running steps are preserved. +/// Returns the number of deleted steps. +pub async fn clear_pending_directive_steps( + pool: &PgPool, + directive_id: Uuid, +) -> Result<u64, sqlx::Error> { + let result = sqlx::query( + r#"DELETE FROM directive_steps + WHERE directive_id = $1 + AND status IN ('pending', 'ready', 'failed', 'skipped')"#, + ) + .bind(directive_id) + .execute(pool) + .await?; + + Ok(result.rows_affected()) +} + // ============================================================================= // Directive DAG Progression // ============================================================================= diff --git a/makima/src/orchestration/directive.rs b/makima/src/orchestration/directive.rs index bb573d4..ea8009d 100644 --- a/makima/src/orchestration/directive.rs +++ b/makima/src/orchestration/directive.rs @@ -699,33 +699,127 @@ fn build_planning_prompt( let mut prompt = String::new(); if !existing_steps.is_empty() { + // ── RE-PLANNING header ────────────────────────────────────── prompt.push_str(&format!( - "EXISTING STEPS (generation {}):\n", + "⚠️ RE-PLANNING: The GOAL has been updated — you must re-evaluate ALL existing steps.\n\ + Previous steps were planned for an earlier version of the goal. Some may no longer be \ + relevant. Review each step below and act according to the instructions per status category.\n\n\ + EXISTING STEPS (generation {}):\n", generation - 1 )); - let mut last_completed_id: Option<Uuid> = None; + + // Categorise steps by status + let mut completed: Vec<&crate::db::models::DirectiveStep> = Vec::new(); + let mut running: Vec<&crate::db::models::DirectiveStep> = Vec::new(); + let mut pending_ready: Vec<&crate::db::models::DirectiveStep> = Vec::new(); + let mut failed: Vec<&crate::db::models::DirectiveStep> = Vec::new(); + let mut skipped: Vec<&crate::db::models::DirectiveStep> = Vec::new(); + for step in existing_steps { - prompt.push_str(&format!( - "- [{}] {} (id: {}): {}\n", - step.status, - step.name, - step.id, - step.description.as_deref().unwrap_or("(no description)") - )); - if step.status == "completed" { + match step.status.as_str() { + "completed" => completed.push(step), + "running" => running.push(step), + "pending" | "ready" => pending_ready.push(step), + "failed" => failed.push(step), + "skipped" => skipped.push(step), + _ => pending_ready.push(step), + } + } + + // ── Completed steps ───────────────────────────────────────── + if !completed.is_empty() { + prompt.push_str("\n── COMPLETED steps (KEEP — work already done) ──\n"); + prompt.push_str("These steps have finished. Their work is committed and available.\n"); + prompt.push_str("Do NOT remove them. New steps can depend on them to inherit their changes.\n"); + let mut last_completed_id: Option<Uuid> = None; + for step in &completed { + prompt.push_str(&format!( + " ✅ {} (id: {}): {}\n", + step.name, + step.id, + step.description.as_deref().unwrap_or("(no description)") + )); 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 + )); + } + } + + // ── Running steps ─────────────────────────────────────────── + if !running.is_empty() { + prompt.push_str("\n── RUNNING steps (cannot remove — note if obsolete) ──\n"); + prompt.push_str("These steps are currently executing and cannot be removed or skipped.\n"); + prompt.push_str("If a running step is no longer relevant to the NEW goal, note it but do not attempt to remove it.\n"); + for step in &running { + prompt.push_str(&format!( + " 🔄 {} (id: {}): {}\n", + step.name, + step.id, + step.description.as_deref().unwrap_or("(no description)") + )); + } } - 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 - )); + + // ── Pending / Ready steps ─────────────────────────────────── + if !pending_ready.is_empty() { + prompt.push_str("\n── PENDING/READY steps (EVALUATE — remove if no longer relevant) ──\n"); + prompt.push_str("These steps have NOT started yet. Evaluate each against the NEW goal:\n"); + prompt.push_str(" • If still relevant → leave it in place (no action needed).\n"); + prompt.push_str(" • If NO LONGER relevant → remove it with: makima directive remove-step <step_id>\n"); + for step in &pending_ready { + prompt.push_str(&format!( + " ⏳ [{}] {} (id: {}): {}\n", + step.status, + step.name, + step.id, + step.description.as_deref().unwrap_or("(no description)") + )); + } + } + + // ── Failed steps ──────────────────────────────────────────── + if !failed.is_empty() { + prompt.push_str("\n── FAILED steps (EVALUATE — remove if no longer relevant) ──\n"); + prompt.push_str("These steps failed. Evaluate each against the NEW goal:\n"); + prompt.push_str(" • If still relevant → remove the failed step and re-add a corrected version.\n"); + prompt.push_str(" • If NO LONGER relevant → remove it with: makima directive remove-step <step_id>\n"); + for step in &failed { + prompt.push_str(&format!( + " ❌ {} (id: {}): {}\n", + step.name, + step.id, + step.description.as_deref().unwrap_or("(no description)") + )); + } } + + // ── Skipped steps ─────────────────────────────────────────── + if !skipped.is_empty() { + prompt.push_str("\n── SKIPPED steps (remove if no longer relevant) ──\n"); + for step in &skipped { + prompt.push_str(&format!( + " ⏭️ {} (id: {}): {}\n", + step.name, + step.id, + step.description.as_deref().unwrap_or("(no description)") + )); + } + } + + // ── Instructions ──────────────────────────────────────────── prompt.push_str(&format!( - "\nAdd new steps that build on or complement existing work. Use generation {}.\n\n", + "\n── ACTION PLAN ──\n\ + 1. First, remove any pending/ready/failed/skipped steps that are NOT relevant to the NEW goal:\n\ + \x20 makima directive remove-step <step_id>\n\ + 2. Then, add new steps for the updated goal. Use generation {}.\n\ + 3. New steps that build on completed work MUST use --depends-on to inherit the worktree.\n\ + 4. Ensure the new plan fully addresses the UPDATED goal.\n\n", generation )); } diff --git a/makima/src/server/handlers/directives.rs b/makima/src/server/handlers/directives.rs index 25b2dc4..929769c 100644 --- a/makima/src/server/handlers/directives.rs +++ b/makima/src/server/handlers/directives.rs @@ -824,7 +824,28 @@ pub async fn update_goal( }; match repository::update_directive_goal(pool, auth.owner_id, id, &req.goal).await { - Ok(Some(directive)) => Json(directive).into_response(), + Ok(Some(directive)) => { + // Clear non-started steps so replanning starts fresh + match repository::clear_pending_directive_steps(pool, id).await { + Ok(count) => { + if count > 0 { + tracing::info!( + directive_id = %id, + removed_steps = count, + "Cleared pending steps after goal update — replanning will generate new steps" + ); + } + } + Err(e) => { + tracing::warn!( + directive_id = %id, + error = %e, + "Failed to clear pending steps after goal update" + ); + } + } + Json(directive).into_response() + } Ok(None) => ( StatusCode::NOT_FOUND, Json(ApiError::new("NOT_FOUND", "Directive not found")), |
