use std::path::Path;
use crate::files::context::{VAGUE_ISSUES_LINE, VAGUE_NOTES_LINE, VAGUE_STATUS_LINE};
use crate::files::integrity;
use crate::files::recovery;
use crate::workspace::Workspace;
include!("agent_files/io.rs");
const PLAN_XSD_SCHEMA: &str = include_str!("llm_output_extraction/plan.xsd");
const DEVELOPMENT_RESULT_XSD_SCHEMA: &str =
include_str!("llm_output_extraction/development_result.xsd");
const ISSUES_XSD_SCHEMA: &str = include_str!("llm_output_extraction/issues.xsd");
const FIX_RESULT_XSD_SCHEMA: &str = include_str!("llm_output_extraction/fix_result.xsd");
const COMMIT_MESSAGE_XSD_SCHEMA: &str = include_str!("llm_output_extraction/commit_message.xsd");
pub const GENERATED_FILES: &[&str] = &[
".agent/PLAN.md",
".agent/commit-message.txt",
".agent/checkpoint.json.tmp",
".git/ralph/no_agent_commit",
];
pub fn ensure_files_with_workspace(
workspace: &dyn Workspace,
isolation_mode: bool,
) -> std::io::Result<()> {
let agent_dir = Path::new(".agent");
if let recovery::RecoveryStatus::Unrecoverable(msg) =
recovery::auto_repair_with_workspace(workspace, agent_dir)?
{
return Err(std::io::Error::other(format!(
"Failed to repair .agent state: {msg}"
)));
}
integrity::check_filesystem_ready_with_workspace(workspace, agent_dir)?;
workspace.create_dir_all(&agent_dir.join("logs"))?;
workspace.create_dir_all(&agent_dir.join("tmp"))?;
let tmp_dir = agent_dir.join("tmp");
let _ = integrity::cleanup_stale_xml_files_with_workspace(workspace, &tmp_dir, false);
setup_xsd_schemas_with_workspace(workspace)?;
if !isolation_mode {
workspace.write_atomic(
&agent_dir.join("STATUS.md"),
&format!("{VAGUE_STATUS_LINE}\n"),
)?;
workspace.write_atomic(
&agent_dir.join("NOTES.md"),
&format!("{VAGUE_NOTES_LINE}\n"),
)?;
workspace.write_atomic(
&agent_dir.join("ISSUES.md"),
&format!("{VAGUE_ISSUES_LINE}\n"),
)?;
}
Ok(())
}
pub fn file_contains_marker_with_workspace(
workspace: &dyn Workspace,
path: &Path,
marker: &str,
) -> std::io::Result<bool> {
if !workspace.exists(path) {
return Ok(false);
}
let content = workspace.read(path)?;
Ok(content.lines().any(|line| line.contains(marker)))
}
pub fn delete_plan_file_with_workspace(workspace: &dyn Workspace) -> std::io::Result<()> {
let plan_path = Path::new(".agent/PLAN.md");
if workspace.exists(plan_path) {
workspace.remove(plan_path)?;
}
Ok(())
}
pub fn delete_commit_message_file_with_workspace(workspace: &dyn Workspace) -> std::io::Result<()> {
let msg_path = Path::new(".agent/commit-message.txt");
if workspace.exists(msg_path) {
workspace.remove(msg_path)?;
}
Ok(())
}
pub fn read_commit_message_file_with_workspace(
workspace: &dyn Workspace,
) -> std::io::Result<String> {
let msg_path = Path::new(".agent/commit-message.txt");
if workspace.exists(msg_path) {
if !super::integrity::verify_file_not_corrupted_with_workspace(workspace, msg_path)? {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
".agent/commit-message.txt appears corrupted",
));
}
}
let content = workspace.read(msg_path).map_err(|e| {
std::io::Error::new(
e.kind(),
format!("Failed to read .agent/commit-message.txt: {e}"),
)
})?;
let trimmed = content.trim();
if trimmed.is_empty() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
".agent/commit-message.txt is empty",
));
}
Ok(trimmed.to_string())
}
pub fn write_commit_message_file_with_workspace(
workspace: &dyn Workspace,
message: &str,
) -> std::io::Result<()> {
let msg_path = Path::new(".agent/commit-message.txt");
workspace.write_atomic(msg_path, message)
}
pub fn cleanup_generated_files_with_workspace(workspace: &dyn Workspace) {
let _ = GENERATED_FILES
.iter()
.map(|file| workspace.remove(Path::new(file)))
.collect::<Vec<_>>();
}
pub fn setup_xsd_schemas_with_workspace(workspace: &dyn Workspace) -> std::io::Result<()> {
let tmp_dir = Path::new(".agent/tmp");
workspace.create_dir_all(tmp_dir)?;
workspace.write(&tmp_dir.join("plan.xsd"), PLAN_XSD_SCHEMA)?;
workspace.write(
&tmp_dir.join("development_result.xsd"),
DEVELOPMENT_RESULT_XSD_SCHEMA,
)?;
workspace.write(&tmp_dir.join("issues.xsd"), ISSUES_XSD_SCHEMA)?;
workspace.write(&tmp_dir.join("fix_result.xsd"), FIX_RESULT_XSD_SCHEMA)?;
workspace.write(
&tmp_dir.join("commit_message.xsd"),
COMMIT_MESSAGE_XSD_SCHEMA,
)?;
Ok(())
}
#[cfg(test)]
mod tests {
#[cfg(feature = "test-utils")]
mod workspace_tests {
use super::super::*;
use crate::workspace::MemoryWorkspace;
#[test]
fn test_file_contains_marker_with_workspace() {
let workspace =
MemoryWorkspace::new_test().with_file("test.txt", "line1\nMARKER_TEST\nline3");
assert!(file_contains_marker_with_workspace(
&workspace,
Path::new("test.txt"),
"MARKER_TEST"
)
.unwrap());
assert!(!file_contains_marker_with_workspace(
&workspace,
Path::new("test.txt"),
"NONEXISTENT"
)
.unwrap());
}
#[test]
fn test_file_contains_marker_with_workspace_missing() {
let workspace = MemoryWorkspace::new_test();
let result = file_contains_marker_with_workspace(
&workspace,
Path::new("nonexistent.txt"),
"MARKER",
);
assert!(!result.unwrap());
}
#[test]
fn test_delete_plan_file_with_workspace() {
let workspace = MemoryWorkspace::new_test().with_file(".agent/PLAN.md", "test plan");
assert!(workspace.exists(Path::new(".agent/PLAN.md")));
delete_plan_file_with_workspace(&workspace).unwrap();
assert!(!workspace.exists(Path::new(".agent/PLAN.md")));
}
#[test]
fn test_delete_plan_file_with_workspace_nonexistent() {
let workspace = MemoryWorkspace::new_test();
delete_plan_file_with_workspace(&workspace).unwrap();
}
#[test]
fn test_read_commit_message_file_with_workspace() {
let workspace = MemoryWorkspace::new_test()
.with_file(".agent/commit-message.txt", "feat: test commit\n");
let msg = read_commit_message_file_with_workspace(&workspace).unwrap();
assert_eq!(msg, "feat: test commit");
}
#[test]
fn test_read_commit_message_file_with_workspace_empty() {
let workspace =
MemoryWorkspace::new_test().with_file(".agent/commit-message.txt", " \n");
assert!(read_commit_message_file_with_workspace(&workspace).is_err());
}
#[test]
fn test_write_commit_message_file_with_workspace() {
let workspace = MemoryWorkspace::new_test();
write_commit_message_file_with_workspace(&workspace, "feat: new feature").unwrap();
assert!(workspace.exists(Path::new(".agent/commit-message.txt")));
let content = workspace
.read(Path::new(".agent/commit-message.txt"))
.unwrap();
assert_eq!(content, "feat: new feature");
}
#[test]
fn test_delete_commit_message_file_with_workspace() {
let workspace =
MemoryWorkspace::new_test().with_file(".agent/commit-message.txt", "test message");
assert!(workspace.exists(Path::new(".agent/commit-message.txt")));
delete_commit_message_file_with_workspace(&workspace).unwrap();
assert!(!workspace.exists(Path::new(".agent/commit-message.txt")));
}
#[test]
fn test_cleanup_generated_files_with_workspace() {
let workspace = MemoryWorkspace::new_test()
.with_file(".agent/PLAN.md", "plan")
.with_file(".agent/commit-message.txt", "msg")
.with_file(".git/ralph/no_agent_commit", "");
cleanup_generated_files_with_workspace(&workspace);
assert!(!workspace.exists(Path::new(".agent/PLAN.md")));
assert!(!workspace.exists(Path::new(".agent/commit-message.txt")));
assert!(!workspace.exists(Path::new(".git/ralph/no_agent_commit")));
}
#[test]
fn test_setup_xsd_schemas_with_workspace() {
let workspace = MemoryWorkspace::new_test();
setup_xsd_schemas_with_workspace(&workspace).unwrap();
assert!(workspace.exists(Path::new(".agent/tmp/plan.xsd")));
assert!(workspace.exists(Path::new(".agent/tmp/development_result.xsd")));
assert!(workspace.exists(Path::new(".agent/tmp/issues.xsd")));
assert!(workspace.exists(Path::new(".agent/tmp/fix_result.xsd")));
assert!(workspace.exists(Path::new(".agent/tmp/commit_message.xsd")));
let plan_xsd = workspace.read(Path::new(".agent/tmp/plan.xsd")).unwrap();
assert!(plan_xsd.contains("xs:schema"));
assert!(plan_xsd.contains("ralph-plan"));
}
}
}