Refine planner mode UI, logging, and history tracking

- Display coach feedback content (up to 25 lines) instead of just length
- Write GIT COMMIT entry to history before actual commit for better a...
- Implement single-line status updates during LLM processing with too...
- Display non-tool LLM text responses in planner UI
- Redirect all logs to <workspace>/logs directory instead of codepath
- Preserve TODO file in planner mode for history (prevent deletion)

Completed files:
- completed_requirements_2025-12-09_16-16-51.md
- completed_todo_2025-12-09_16-16-51.md
This commit is contained in:
Jochen
2025-12-09 16:17:53 +11:00
parent ff8b3e7c7b
commit 633da0d8a6
10 changed files with 310 additions and 71 deletions

View File

@@ -11,8 +11,6 @@ use std::fs::{self, OpenOptions};
use std::io::Write;
use std::path::Path;
use crate::prompts;
/// Format a timestamp for planner_history.txt entries
/// Format: YYYY-MM-DD HH:MM:SS (ISO 8601 for readability)
pub fn format_timestamp() -> String {

View File

@@ -196,12 +196,19 @@ pub fn extract_summary(response: &str) -> Option<String> {
/// Write the codebase report to logs directory
fn write_code_report(report: &str) -> Result<()> {
// Ensure logs directory exists
fs::create_dir_all("logs")?;
// Get logs directory from workspace path or current dir
let logs_dir = if let Ok(workspace_path) = std::env::var("G3_WORKSPACE_PATH") {
std::path::PathBuf::from(workspace_path).join("logs")
} else {
std::env::current_dir().unwrap_or_default().join("logs")
};
// Ensure logs directory exists
fs::create_dir_all(&logs_dir)?;
// Generate timestamp in same format as tool_calls log
let timestamp = Local::now().format("%Y%m%d_%H%M%S").to_string();
let filename = format!("logs/code_report_{}.log", timestamp);
let filename = logs_dir.join(format!("code_report_{}.log", timestamp));
// Write the report to file
let mut file = OpenOptions::new()
@@ -218,12 +225,19 @@ fn write_code_report(report: &str) -> Result<()> {
/// Write the discovery commands to logs directory
fn write_discovery_commands(commands: &[String]) -> Result<()> {
// Get logs directory from workspace path or current dir
let logs_dir = if let Ok(workspace_path) = std::env::var("G3_WORKSPACE_PATH") {
std::path::PathBuf::from(workspace_path).join("logs")
} else {
std::env::current_dir().unwrap_or_default().join("logs")
};
// Ensure logs directory exists
fs::create_dir_all("logs")?;
fs::create_dir_all(&logs_dir)?;
// Generate timestamp in same format as tool_calls log
let timestamp = Local::now().format("%Y%m%d_%H%M%S").to_string();
let filename = format!("logs/discovery_commands_{}.log", timestamp);
let filename = logs_dir.join(format!("discovery_commands_{}.log", timestamp));
// Write the commands to file
let mut file = OpenOptions::new()

View File

@@ -182,8 +182,33 @@ pub async fn generate_commit_message(
}
/// A simple UiWriter implementation for planner output
/// Uses single-line status updates during LLM processing
#[derive(Clone)]
pub struct PlannerUiWriter;
pub struct PlannerUiWriter {
tool_count: std::sync::Arc<std::sync::atomic::AtomicUsize>,
}
impl Default for PlannerUiWriter {
fn default() -> Self {
Self::new()
}
}
impl PlannerUiWriter {
pub fn new() -> Self {
Self {
tool_count: std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0)),
}
}
/// Clear the current line and print a status message
fn print_status_line(&self, message: &str) {
use std::io::Write;
// Use carriage return to overwrite previous line, pad to 80 chars to clear old content
print!("\r{:<80}", message);
std::io::stdout().flush().ok();
}
}
impl g3_core::ui_writer::UiWriter for PlannerUiWriter {
fn print(&self, message: &str) {
@@ -209,7 +234,11 @@ impl g3_core::ui_writer::UiWriter for PlannerUiWriter {
}
fn print_tool_header(&self, tool_name: &str) {
println!("🔧 {}", tool_name);
// Increment tool count and show on single line
let count = self.tool_count.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1;
// Clear the "Thinking..." line and print tool header on new line
print!("\r{:<80}\n", ""); // Clear status line
println!("🔧 [{}] {}", count, tool_name);
}
fn print_tool_arg(&self, _key: &str, _value: &str) {}
@@ -218,9 +247,25 @@ impl g3_core::ui_writer::UiWriter for PlannerUiWriter {
fn print_tool_output_line(&self, _line: &str) {}
fn print_tool_output_summary(&self, _hidden_count: usize) {}
fn print_tool_timing(&self, _duration_str: &str) {}
fn print_agent_prompt(&self) {}
fn print_agent_response(&self, _content: &str) {}
fn notify_sse_received(&self) {}
fn print_agent_prompt(&self) {
// Clear any status line before agent response
print!("\r{:<80}\n", "");
}
fn print_agent_response(&self, content: &str) {
// Display non-tool text messages from LLM
if !content.trim().is_empty() {
print!("{}", content);
use std::io::Write;
std::io::stdout().flush().ok();
}
}
fn notify_sse_received(&self) {
// Show "Thinking..." status on single line
self.print_status_line("💭 Thinking...");
}
fn flush(&self) {
use std::io::Write;
@@ -254,7 +299,7 @@ pub async fn call_refinement_llm_with_tools(
// Create agent with planner config
let planner_config = config.for_planner()?;
let ui_writer = PlannerUiWriter;
let ui_writer = PlannerUiWriter::new();
// Create project pointing to codepath as workspace
let workspace = std::path::PathBuf::from(codepath);

View File

@@ -11,7 +11,6 @@ use std::path::{Path, PathBuf};
use crate::git;
use crate::history;
use crate::llm;
use crate::prompts;
use crate::state::{
ApprovalChoice, BranchConfirmChoice, CompletionChoice, DirtyFilesChoice,
PlannerState, RecoveryChoice, RecoveryInfo,
@@ -482,14 +481,14 @@ pub fn stage_and_commit(
return Ok(());
}
// Log commit to history BEFORE making the commit (provides audit trail even if commit fails)
history::write_git_commit(&config.plan_dir(), summary)?;
// Make commit
print_msg("📝 Making git commit...");
let _commit_sha = git::commit(&config.codepath, summary, description)?;
print_msg("✅ Commit successful");
// Log commit to history
history::write_git_commit(&config.plan_dir(), summary)?;
Ok(())
}
@@ -588,6 +587,9 @@ pub async fn run_coach_player_loop(
// Set environment variable for custom todo path
std::env::set_var("G3_TODO_PATH", planner_config.todo_path().display().to_string());
// Set environment variable for workspace path (used for logs)
std::env::set_var("G3_WORKSPACE_PATH", planner_config.codepath.display().to_string());
let mut turn = 1;
let mut coach_feedback = String::new();
@@ -598,7 +600,7 @@ pub async fn run_coach_player_loop(
print_msg("🎯 Player: Implementing requirements...");
let player_config = g3_config.for_player()?;
let ui_writer = llm::PlannerUiWriter;
let ui_writer = llm::PlannerUiWriter::new();
let mut player_agent = Agent::new_autonomous_with_readme_and_quiet(
player_config,
ui_writer,
@@ -633,7 +635,7 @@ pub async fn run_coach_player_loop(
print_msg("🎓 Coach: Reviewing implementation...");
let coach_config = g3_config.for_coach()?;
let coach_ui_writer = llm::PlannerUiWriter;
let coach_ui_writer = llm::PlannerUiWriter::new();
let mut coach_agent = Agent::new_autonomous_with_readme_and_quiet(
coach_config,
coach_ui_writer,
@@ -657,7 +659,19 @@ pub async fn run_coach_player_loop(
return Ok(());
}
coach_feedback = result.response;
print_msg(&format!("📝 Coach feedback: {} chars", coach_feedback.len()));
// Display first 25 lines of coach feedback
let lines: Vec<&str> = coach_feedback.lines().collect();
let display_lines = if lines.len() > 25 {
let mut truncated: Vec<&str> = lines[..25].to_vec();
truncated.push("...");
truncated
} else {
lines
};
print_msg(&format!("📝 Coach feedback ({} chars):", coach_feedback.len()));
for line in display_lines {
print_msg(&format!(" {}", line));
}
}
Err(e) => {
print_msg(&format!("⚠️ Coach error: {}", e));