readme tweaks

This commit is contained in:
Dhanji Prasanna
2025-10-10 14:08:37 +11:00
parent 2d959b3d63
commit 426a9b88a9
3 changed files with 185 additions and 57 deletions

View File

@@ -244,6 +244,55 @@ fn read_project_readme(workspace_dir: &Path) -> Option<String> {
None
}
/// Extract the main heading or title from README content
fn extract_readme_heading(readme_content: &str) -> Option<String> {
// Process the content line by line, skipping the prefix line if present
let lines_iter = readme_content.lines();
let mut content_lines = Vec::new();
for line in lines_iter {
// Skip the "📚 Project README (from ...):" line
if line.starts_with("📚 Project README") {
continue;
}
content_lines.push(line);
}
let content = content_lines.join("\n");
// Look for the first markdown heading
for line in content.lines() {
let trimmed = line.trim();
// Check for H1 heading (# Title)
if trimmed.starts_with("# ") {
let title = trimmed[2..].trim();
if !title.is_empty() {
// Return the full title (including any description after dash)
return Some(title.to_string());
}
}
// Skip other markdown headings for now (##, ###, etc.)
// We're only looking for the main H1 heading
}
// If no H1 heading found, look for the first non-empty, non-metadata line as a fallback
for line in content.lines().take(5) {
let trimmed = line.trim();
// Skip empty lines, other heading markers, and metadata
if !trimmed.is_empty() && !trimmed.starts_with("📚") && !trimmed.starts_with('#')
&& !trimmed.starts_with("==") && !trimmed.starts_with("--") {
// Limit length for display
return Some(if trimmed.len() > 100 {
format!("{}...", &trimmed[..97])
} else {
trimmed.to_string()
});
}
}
None
}
async fn run_interactive_retro(
config: Config,
show_prompt: bool,
@@ -278,7 +327,14 @@ async fn run_interactive_retro(
// Display message if README was loaded
if readme_content.is_some() {
tui.output("SYSTEM: PROJECT README LOADED INTO CONTEXT\n\n");
// Extract the first heading or title from the README
let readme_snippet = if let Some(ref content) = readme_content {
extract_readme_heading(content)
.unwrap_or_else(|| "PROJECT DOCUMENTATION LOADED".to_string())
} else {
"PROJECT DOCUMENTATION LOADED".to_string()
};
tui.output(&format!("SYSTEM: PROJECT README LOADED - {}\n\n", readme_snippet));
}
tui.output("SYSTEM: READY FOR INPUT\n\n");
tui.output("\n\n");
@@ -511,7 +567,18 @@ async fn run_interactive<W: UiWriter>(
// Display message if README was loaded
if readme_content.is_some() {
output.print("📚 Project README loaded into context");
// Extract the first heading or title from the README
let readme_snippet = if let Some(ref content) = readme_content {
extract_readme_heading(content)
.unwrap_or_else(|| "Project documentation loaded".to_string())
} else {
"Project documentation loaded".to_string()
};
output.print(&format!("📚 Project README loaded: {}", readme_snippet));
if readme_snippet.len() > 80 {
output.print(" (Full README available in context)");
}
output.print("");
}

View File

@@ -79,10 +79,10 @@ impl UiWriter for ConsoleUiWriter {
value.clone()
};
// Print with bold green formatting using ANSI escape codes
println!("\x1b[1;32m┌─ {} | {}\x1b[0m", tool_name, display_value);
println!("┌─\x1b[1;32m {} | {}\x1b[0m", tool_name, display_value);
} else {
// Print with bold green formatting using ANSI escape codes
println!("\x1b[1;32m┌─ {}\x1b[0m", tool_name);
println!("┌─\x1b[1;32m {}\x1b[0m", tool_name);
}
// Print any additional arguments (optional - can be removed if not wanted)

View File

@@ -1,7 +1,7 @@
pub mod error_handling;
pub mod project;
pub mod ui_writer;
pub mod task_result;
pub mod ui_writer;
pub use task_result::TaskResult;
#[cfg(test)]
@@ -231,7 +231,7 @@ impl StreamingToolParser {
pub struct ContextWindow {
pub used_tokens: u32,
pub total_tokens: u32,
pub cumulative_tokens: u32, // Track cumulative tokens across all interactions
pub cumulative_tokens: u32, // Track cumulative tokens across all interactions
pub conversation_history: Vec<Message>,
}
@@ -388,11 +388,19 @@ impl<W: UiWriter> Agent<W> {
Self::new_with_mode(config, ui_writer, false).await
}
pub async fn new_with_readme(config: Config, ui_writer: W, readme_content: Option<String>) -> Result<Self> {
pub async fn new_with_readme(
config: Config,
ui_writer: W,
readme_content: Option<String>,
) -> Result<Self> {
Self::new_with_mode_and_readme(config, ui_writer, false, readme_content).await
}
pub async fn new_autonomous_with_readme(config: Config, ui_writer: W, readme_content: Option<String>) -> Result<Self> {
pub async fn new_autonomous_with_readme(
config: Config,
ui_writer: W,
readme_content: Option<String>,
) -> Result<Self> {
Self::new_with_mode_and_readme(config, ui_writer, true, readme_content).await
}
@@ -404,7 +412,12 @@ impl<W: UiWriter> Agent<W> {
Self::new_with_mode_and_readme(config, ui_writer, is_autonomous, None).await
}
async fn new_with_mode_and_readme(config: Config, ui_writer: W, is_autonomous: bool, readme_content: Option<String>) -> Result<Self> {
async fn new_with_mode_and_readme(
config: Config,
ui_writer: W,
is_autonomous: bool,
readme_content: Option<String>,
) -> Result<Self> {
let mut providers = ProviderRegistry::new();
// Only register providers that are configured AND selected as the default provider
@@ -917,7 +930,8 @@ The tool will execute immediately and you'll receive the result (success or erro
request: CompletionRequest,
show_timing: bool,
) -> Result<TaskResult> {
self.stream_completion_with_tools(request, show_timing).await
self.stream_completion_with_tools(request, show_timing)
.await
}
/// Create tool definitions for native tool calling providers
@@ -1464,7 +1478,8 @@ The tool will execute immediately and you'll receive the result (success or erro
// Don't add the "=> " prefix in autonomous mode
// as it interferes with coach feedback parsing
if !self.is_autonomous {
full_response.push_str(&format!("\n\n=> {}", summary_str));
full_response
.push_str(&format!("\n\n=> {}", summary_str));
} else {
full_response.push_str(&format!("\n\n{}", summary_str));
}
@@ -1476,12 +1491,20 @@ The tool will execute immediately and you'll receive the result (success or erro
// Add timing if needed
let final_response = if show_timing {
format!("{}\n\n⏱️ {} | 💭 {}", full_response, Self::format_duration(total_execution_time), Self::format_duration(_ttft))
format!(
"{}\n\n⏱️ {} | 💭 {}",
full_response,
Self::format_duration(total_execution_time),
Self::format_duration(_ttft)
)
} else {
full_response
};
return Ok(TaskResult::new(final_response, self.context_window.clone()));
return Ok(TaskResult::new(
final_response,
self.context_window.clone(),
));
}
// Closure marker with timing
@@ -1576,12 +1599,14 @@ The tool will execute immediately and you'll receive the result (success or erro
// We need to check the parser's text buffer as well, since the LLM
// might have responded with text but no final_output tool call
let text_content = parser.get_text_content();
let has_text_response = !text_content.trim().is_empty() || !current_response.trim().is_empty();
let has_text_response = !text_content.trim().is_empty()
|| !current_response.trim().is_empty();
// If we have text in the parser buffer but not in current_response,
// we should add it to the response
if !text_content.trim().is_empty() && current_response.is_empty() {
current_response = filter_json_tool_calls(text_content).trim().to_string();
current_response =
filter_json_tool_calls(text_content).trim().to_string();
}
if !has_text_response && full_response.is_empty() {
@@ -1691,12 +1716,20 @@ The tool will execute immediately and you'll receive the result (success or erro
// Add timing if needed
let final_response = if show_timing {
format!("{}\n\n⏱️ {} | 💭 {}", full_response, Self::format_duration(total_execution_time), Self::format_duration(_ttft))
format!(
"{}\n\n⏱️ {} | 💭 {}",
full_response,
Self::format_duration(total_execution_time),
Self::format_duration(_ttft)
)
} else {
full_response
};
return Ok(TaskResult::new(final_response, self.context_window.clone()));
return Ok(TaskResult::new(
final_response,
self.context_window.clone(),
));
}
break; // Tool was executed, break to continue outer loop
}
@@ -1765,7 +1798,12 @@ The tool will execute immediately and you'll receive the result (success or erro
// Add timing if needed
let final_response = if show_timing {
format!("{}\n\n⏱️ {} | 💭 {}", full_response, Self::format_duration(total_execution_time), Self::format_duration(_ttft))
format!(
"{}\n\n⏱️ {} | 💭 {}",
full_response,
Self::format_duration(total_execution_time),
Self::format_duration(_ttft)
)
} else {
full_response
};
@@ -1781,7 +1819,12 @@ The tool will execute immediately and you'll receive the result (success or erro
// Add timing if needed
let final_response = if show_timing {
format!("{}\n\n⏱️ {} | 💭 {}", full_response, Self::format_duration(total_execution_time), Self::format_duration(_ttft))
format!(
"{}\n\n⏱️ {} | 💭 {}",
full_response,
Self::format_duration(total_execution_time),
Self::format_duration(_ttft)
)
} else {
full_response
};
@@ -1846,14 +1889,21 @@ The tool will execute immediately and you'll receive the result (success or erro
if let Some(file_path) = tool_call.args.get("file_path") {
if let Some(path_str) = file_path.as_str() {
// Extract optional start and end positions
let start_char = tool_call.args.get("start")
let start_char = tool_call
.args
.get("start")
.and_then(|v| v.as_u64())
.map(|n| n as usize);
let end_char = tool_call.args.get("end")
let end_char = tool_call
.args
.get("end")
.and_then(|v| v.as_u64())
.map(|n| n as usize);
debug!("Reading file: {}, start={:?}, end={:?}", path_str, start_char, end_char);
debug!(
"Reading file: {}, start={:?}, end={:?}",
path_str, start_char, end_char
);
match std::fs::read_to_string(path_str) {
Ok(content) => {
@@ -1863,13 +1913,24 @@ The tool will execute immediately and you'll receive the result (success or erro
// Validation
if start > content.len() {
return Ok(format!("❌ Start position {} exceeds file length {}", start, content.len()));
return Ok(format!(
"❌ Start position {} exceeds file length {}",
start,
content.len()
));
}
if end > content.len() {
return Ok(format!("❌ End position {} exceeds file length {}", end, content.len()));
return Ok(format!(
"❌ End position {} exceeds file length {}",
end,
content.len()
));
}
if start > end {
return Ok(format!("❌ Start position {} is greater than end position {}", start, end));
return Ok(format!(
"❌ Start position {} is greater than end position {}",
start, end
));
}
// Extract the requested portion
@@ -1884,7 +1945,10 @@ The tool will execute immediately and you'll receive the result (success or erro
start, end, line_count, total_lines, partial_content
))
} else {
Ok(format!("📄 File content ({} lines):\n{}", line_count, content))
Ok(format!(
"📄 File content ({} lines):\n{}",
line_count, content
))
}
}
Err(e) => Ok(format!("❌ Failed to read file '{}': {}", path_str, e)),
@@ -2057,8 +2121,8 @@ The tool will execute immediately and you'll receive the result (success or erro
let line_count = content.lines().count();
let char_count = content.len();
Ok(format!(
"✅ Successfully wrote {} lines ({} characters) to '{}'",
line_count, char_count, path
"✅ Successfully wrote {} lines ({} characters)",
line_count, char_count
))
}
Err(e) => Ok(format!("❌ Failed to write to file '{}': {}", path, e)),
@@ -2240,10 +2304,7 @@ The tool will execute immediately and you'll receive the result (success or erro
// Write the result back to the file
match std::fs::write(file_path, &result) {
Ok(()) => Ok(format!(
"✅ Successfully applied unified diff to '{}'",
file_path
)),
Ok(()) => Ok(format!("✅ Successfully applied unified diff")),
Err(e) => Ok(format!("❌ Failed to write to file '{}': {}", file_path, e)),
}
}