use super::super::transcript;
use crate::config::Workflow;
use crate::prompt::Error;
use crate::prompt::tool::{context_file, frame_open};
use crate::template::GitRunner;
use crate::workspace;
use std::path::{Path, PathBuf};
pub(super) fn append(
content: &mut Vec<u8>,
conv_repo: &Path,
worktree: &Path,
conv_id: &str,
workflow: &Workflow,
git: &dyn GitRunner,
) -> Result<(), Error> {
let found = discovered(conv_repo, conv_id, workflow, git);
if found.is_empty() {
return Ok(());
}
let entries = transcript::tool_entries(worktree)?;
for (path, bytes) in found {
if framed(&entries, &path) {
continue;
}
if !content.ends_with(b"\n") {
content.push(b'\n');
}
content.extend_from_slice(&context_file(&path, &bytes, workflow.tool_output));
}
Ok(())
}
fn discovered(
conv_repo: &Path,
conv_id: &str,
workflow: &Workflow,
git: &dyn GitRunner,
) -> Vec<(PathBuf, Vec<u8>)> {
if workflow.context_files.is_empty() {
return Vec::new();
}
let cwd = workspace::cwd::effective(conv_repo, conv_id, git);
let cwd = std::fs::canonicalize(&cwd).unwrap_or(cwd);
let mut found = Vec::new();
for dir in path_set(&cwd, git) {
for name in &workflow.context_files {
let path = dir.join(name);
if let Ok(bytes) = std::fs::read(&path) {
found.push((path, bytes));
}
}
}
found
}
fn path_set(cwd: &Path, git: &dyn GitRunner) -> Vec<PathBuf> {
let Ok(top) = git.run_capture(cwd, &["rev-parse", "--show-toplevel"]) else {
return vec![cwd.to_path_buf()];
};
let top = PathBuf::from(&top);
let top = std::fs::canonicalize(&top).unwrap_or(top);
let Ok(rel) = cwd.strip_prefix(&top) else {
return vec![cwd.to_path_buf()];
};
let mut walked = top;
let mut dirs = vec![walked.clone()];
for segment in rel.components() {
walked = walked.join(segment);
dirs.push(walked.clone());
}
dirs
}
fn framed(entries: &[Vec<u8>], path: &Path) -> bool {
let quoted = serde_json::to_string(&frame_open(path)).expect("a string serializes");
let needle = quoted.trim_matches('"').as_bytes().to_vec();
entries
.iter()
.any(|entry| entry.windows(needle.len()).any(|w| w == needle))
}