diff options
Diffstat (limited to 'makima/src/orchestration/directive.rs')
| -rw-r--r-- | makima/src/orchestration/directive.rs | 217 |
1 files changed, 217 insertions, 0 deletions
diff --git a/makima/src/orchestration/directive.rs b/makima/src/orchestration/directive.rs index 9113fd4..6e0d83d 100644 --- a/makima/src/orchestration/directive.rs +++ b/makima/src/orchestration/directive.rs @@ -1232,3 +1232,220 @@ IMPORTANT: You MUST run `makima directive update` with either `--pr-url` or `--s base_branch = base_branch, ) } + +/// Build a specialized planning prompt for picking up open orders. +/// +/// This prompt instructs the planner to evaluate available orders, select an +/// adequate number based on priority and directive capacity, and create steps +/// to fulfil them. +pub fn build_order_pickup_prompt( + directive: &crate::db::models::Directive, + existing_steps: &[crate::db::models::DirectiveStep], + orders: &[crate::db::models::Order], + generation: i32, + goal_history: &[crate::db::models::DirectiveGoalHistory], +) -> String { + let mut prompt = String::new(); + + // ── Directive context ────────────────────────────────────────── + prompt.push_str(&format!( + "You are planning work for directive \"{title}\".\n\n\ + GOAL: {goal}\n\ + {repo_section}\n", + title = directive.title, + goal = directive.goal, + repo_section = match &directive.repository_url { + Some(url) => format!("REPOSITORY: {}\n", url), + None => String::new(), + }, + )); + + // ── Goal history (if any) ───────────────────────────────────── + if !goal_history.is_empty() { + prompt.push_str("-- GOAL CHANGES --\n"); + for (i, entry) in goal_history.iter().enumerate() { + if i == 0 { + prompt.push_str(&format!( + "PREVIOUS GOAL (replaced at {}):\n{}\n\n", + entry.created_at.format("%Y-%m-%d %H:%M:%S UTC"), + entry.goal + )); + } else { + prompt.push_str(&format!( + "OLDER GOAL (version from {}):\n{}\n\n", + entry.created_at.format("%Y-%m-%d %H:%M:%S UTC"), + entry.goal + )); + } + } + } + + // ── Orders being picked up ─────────────────────────────────── + prompt.push_str("== ORDERS AVAILABLE FOR PICKUP ==\n"); + prompt.push_str("The following open orders have been linked to this directive. \ + Review them and create steps to fulfil them.\n\n"); + for (i, order) in orders.iter().enumerate() { + prompt.push_str(&format!( + " {}. [{}] [{}] {} (id: {})\n", + i + 1, + order.priority, + order.order_type, + order.title, + order.id, + )); + if let Some(ref desc) = order.description { + prompt.push_str(&format!(" Description: {}\n", desc)); + } + } + prompt.push('\n'); + + // ── Existing steps ─────────────────────────────────────────── + if !existing_steps.is_empty() { + 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(); + + for step in existing_steps { + match step.status.as_str() { + "completed" => completed.push(step), + "running" => running.push(step), + "pending" | "ready" => pending_ready.push(step), + "failed" => failed.push(step), + _ => pending_ready.push(step), + } + } + + prompt.push_str("== EXISTING STEPS ==\n"); + + if !completed.is_empty() { + prompt.push_str("\n── COMPLETED steps (work already done) ──\n"); + let mut last_completed_id: Option<uuid::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 \"{}\" \ + so their worktree inherits all prior changes.\n", + last_id + )); + } + } + + if !running.is_empty() { + prompt.push_str("\n── RUNNING steps (in progress) ──\n"); + for step in &running { + prompt.push_str(&format!( + " 🔄 {} (id: {}): {}\n", + step.name, + step.id, + step.description.as_deref().unwrap_or("(no description)") + )); + } + } + + if !pending_ready.is_empty() { + prompt.push_str("\n── PENDING/READY steps (not yet started) ──\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)") + )); + } + } + + if !failed.is_empty() { + prompt.push_str("\n── FAILED steps ──\n"); + for step in &failed { + prompt.push_str(&format!( + " ❌ {} (id: {}): {}\n", + step.name, + step.id, + step.description.as_deref().unwrap_or("(no description)") + )); + } + } + + // Determine whether to create fresh steps or combine with existing + let all_terminal = existing_steps + .iter() + .all(|s| matches!(s.status.as_str(), "completed" | "failed" | "skipped")); + + if all_terminal { + prompt.push_str( + "\nAll existing steps are in terminal state (completed/failed/skipped). \ + Create FRESH steps from the orders above.\n\n", + ); + } else if !pending_ready.is_empty() || !running.is_empty() { + prompt.push_str( + "\nThere are existing active/pending steps. Evaluate whether to KEEP them \ + and ADD new steps from the orders, creating a combined plan. \ + Do not duplicate work already covered by existing steps.\n\n", + ); + } + } + + // ── Order selection guidance ───────────────────────────────── + prompt.push_str(&format!( + "== ORDER SELECTION GUIDANCE ==\n\ + You do NOT need to pick up ALL orders. Select an ADEQUATE number based on:\n\ + - Priority: prefer critical and high priority orders first\n\ + - Directive scope: consider the directive's current goal and capacity\n\ + - Avoid overloading: don't assign too many orders to a single directive\n\ + - The orders are already linked to this directive — focus on creating steps\n\n\ + If some orders are not relevant to this directive's goal or would overload it, \ + you may leave them for a future pickup cycle.\n\n" + )); + + // ── Step creation instructions ─────────────────────────────── + prompt.push_str(&format!( + r#"== STEP CREATION INSTRUCTIONS == +For each order (or group of related orders), create one or more steps: +- name: Short imperative title (e.g., "Add user authentication middleware") +- description: What to do and acceptance criteria +- taskPlan: Full instructions for the Claude instance (include file paths, patterns to follow) +- dependsOn: UUIDs of steps this depends on (use IDs from previous add-step responses) +- orderIndex: Execution phase number. Steps only start after ALL steps with a lower orderIndex complete. + Steps with the same orderIndex run in parallel. Use ascending values (0, 1, 2, ...) to create sequential phases. + +Submit steps using generation {generation}: + makima directive add-step "Step Name" --description "..." --task-plan "..." --generation {generation} + (Use --depends-on "uuid1,uuid2" for dependencies) + +Or batch: + makima directive batch-add-steps --json '[{{"name":"...","description":"...","taskPlan":"...","dependsOn":[],"orderIndex":0,"generation":{generation}}}]' + +DEPENDENCY WORKTREE CONTINUATION: +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. +"#, + generation = generation, + )); + + prompt +} |
