use ignore::WalkBuilder;
use std::path::Path;
const MAX_FILE_BYTES: usize = 32_000;
const MAX_TREE_ENTRIES: usize = 200;
pub struct WorkspaceContext {
pub file_tree: String,
pub git_status: Option<String>,
pub file_contents: Vec<(String, String)>, }
impl WorkspaceContext {
pub fn build(workspace: &Path) -> Self {
let file_tree = build_tree(workspace);
let git_status = get_git_status(workspace);
Self {
file_tree,
git_status,
file_contents: Vec::new(),
}
}
pub fn add_file(&mut self, path: &str, content: &str) {
let truncated = if content.len() > MAX_FILE_BYTES {
format!(
"{}…[truncated at {}KB]",
&content[..MAX_FILE_BYTES],
MAX_FILE_BYTES / 1024
)
} else {
content.to_string()
};
self.file_contents.push((path.to_string(), truncated));
}
pub fn to_context_string(&self) -> String {
let mut out = String::new();
out.push_str("## Workspace File Tree\n```\n");
out.push_str(&self.file_tree);
out.push_str("\n```\n\n");
if let Some(ref status) = self.git_status {
out.push_str("## Git Status\n```\n");
out.push_str(status);
out.push_str("\n```\n\n");
}
if !self.file_contents.is_empty() {
out.push_str("## Referenced Files\n\n");
for (path, content) in &self.file_contents {
out.push_str(&format!("### {}\n```\n{}\n```\n\n", path, content));
}
}
out
}
}
fn build_tree(workspace: &Path) -> String {
let mut entries = Vec::new();
let walker = WalkBuilder::new(workspace)
.hidden(false)
.ignore(true)
.git_ignore(true)
.max_depth(Some(4))
.build();
for entry in walker.flatten() {
let rel = entry
.path()
.strip_prefix(workspace)
.unwrap_or(entry.path())
.display()
.to_string();
if rel.is_empty() {
continue;
}
let suffix = if entry.path().is_dir() { "/" } else { "" };
entries.push(format!("{}{}", rel, suffix));
if entries.len() >= MAX_TREE_ENTRIES {
entries.push(format!("[...and more]"));
break;
}
}
entries.sort();
entries.join("\n")
}
fn get_git_status(workspace: &Path) -> Option<String> {
let output = std::process::Command::new("git")
.args(["status", "--short"])
.current_dir(workspace)
.output()
.ok()?;
if !output.status.success() {
return None;
}
let s = String::from_utf8_lossy(&output.stdout).to_string();
if s.trim().is_empty() {
None
} else {
Some(s)
}
}