summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--makima/src/db/repository.rs19
-rw-r--r--makima/src/orchestration/directive.rs130
-rw-r--r--makima/src/server/handlers/directives.rs23
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")),