109 lines
4.0 KiB
Rust
109 lines
4.0 KiB
Rust
use std::sync::mpsc::Sender;
|
|
|
|
use anyhow::Result;
|
|
use serde_json::{json, Value};
|
|
|
|
use crate::app::AppEvent;
|
|
use crate::model::{ExecutionResponse, Plan, PlanStep, SessionSource, TaskConfig};
|
|
use crate::process;
|
|
use crate::prompt;
|
|
use crate::storage::toon;
|
|
|
|
pub fn implement(
|
|
repo_root: &std::path::Path,
|
|
config: &TaskConfig,
|
|
plan: &Plan,
|
|
step: &PlanStep,
|
|
event_tx: &Sender<AppEvent>,
|
|
) -> Result<ExecutionResponse> {
|
|
let goal_md = toon::read_markdown(&config.goal_file)?;
|
|
let standards_md = toon::read_markdown(&config.standards_file)?;
|
|
let context = build_execution_context(plan, step);
|
|
let prompt = format!(
|
|
concat!(
|
|
"You are the autonomous execution worker for a Rust TUI-first controller.\n",
|
|
"Execution mode only. Do not ask the user questions.\n",
|
|
"Complete the active step with the smallest correct change set.\n\n",
|
|
"Efficiency rules:\n",
|
|
"- Inspect only files likely relevant to this step.\n",
|
|
"- Avoid repository-wide searches unless the focused path is exhausted.\n",
|
|
"- Prefer targeted verification and targeted tests over broad full-suite runs.\n",
|
|
"- Keep output terse. Use short summaries and short notes.\n",
|
|
"- If the requested change is already present, return done.\n",
|
|
"- If the goal is genuinely ambiguous, set needs_goal_clarification=true.\n\n",
|
|
"Return empty arrays for verification_commands, test_commands, or notes when not needed.\n\n",
|
|
"Goal summary:\n{goal}\n\n",
|
|
"Standards summary:\n{standards}\n\n",
|
|
"Execution context:\n{context}\n"
|
|
),
|
|
goal = prompt::compact_markdown(&goal_md, 8, 1200),
|
|
standards = prompt::compact_markdown(&standards_md, 10, 1200),
|
|
context = serde_json::to_string_pretty(&context)?,
|
|
);
|
|
|
|
let schema = json!({
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"required": ["status", "summary", "verification_commands", "test_commands", "notes", "needs_goal_clarification"],
|
|
"properties": {
|
|
"status": { "type": "string", "enum": ["done", "blocked", "needs-replan"] },
|
|
"summary": { "type": "string" },
|
|
"verification_commands": { "type": "array", "items": { "type": "string" } },
|
|
"test_commands": { "type": "array", "items": { "type": "string" } },
|
|
"notes": { "type": "array", "items": { "type": "string" } },
|
|
"needs_goal_clarification": { "type": "boolean" }
|
|
}
|
|
});
|
|
let raw = process::run_codex_with_schema(
|
|
repo_root,
|
|
&prompt,
|
|
&schema,
|
|
event_tx,
|
|
SessionSource::Executor,
|
|
Some(step.id.clone()),
|
|
)?;
|
|
Ok(serde_json::from_str(&raw)?)
|
|
}
|
|
|
|
fn build_execution_context(plan: &Plan, step: &PlanStep) -> Value {
|
|
let dependency_steps = step
|
|
.dependencies
|
|
.iter()
|
|
.filter_map(|dependency| {
|
|
plan.steps
|
|
.iter()
|
|
.find(|candidate| &candidate.id == dependency)
|
|
.map(|candidate| {
|
|
json!({
|
|
"id": candidate.id,
|
|
"title": prompt::truncate_text(&candidate.title, 100),
|
|
"status": candidate.status,
|
|
})
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let next_steps = plan
|
|
.steps
|
|
.iter()
|
|
.filter(|candidate| candidate.id != step.id)
|
|
.filter(|candidate| !candidate.status.is_done())
|
|
.take(3)
|
|
.map(|candidate| {
|
|
json!({
|
|
"id": candidate.id,
|
|
"title": prompt::truncate_text(&candidate.title, 100),
|
|
"dependencies": prompt::compact_string_vec(&candidate.dependencies, 4, 60),
|
|
"status": candidate.status,
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
json!({
|
|
"goal_summary": prompt::truncate_text(&plan.goal_summary, 200),
|
|
"active_step": prompt::compact_step(step),
|
|
"dependency_steps": dependency_steps,
|
|
"next_pending_steps": next_steps,
|
|
})
|
|
}
|