use super::effect::{AppEffect, AppEffectHandler, AppEffectResult};
use std::path::PathBuf;
use thiserror::Error;
const PLAN_XSD_SCHEMA: &str = include_str!("../files/llm_output_extraction/plan.xsd");
const DEVELOPMENT_RESULT_XSD_SCHEMA: &str =
include_str!("../files/llm_output_extraction/development_result.xsd");
const ISSUES_XSD_SCHEMA: &str = include_str!("../files/llm_output_extraction/issues.xsd");
const FIX_RESULT_XSD_SCHEMA: &str = include_str!("../files/llm_output_extraction/fix_result.xsd");
const COMMIT_MESSAGE_XSD_SCHEMA: &str =
include_str!("../files/llm_output_extraction/commit_message.xsd");
use crate::files::context::{VAGUE_ISSUES_LINE, VAGUE_NOTES_LINE, VAGUE_STATUS_LINE};
#[derive(Debug, Error)]
pub enum AppEffectError {
#[error("effect {effect:?} handler failed: {message}")]
Handler { effect: AppEffect, message: String },
#[error("effect {effect:?} returned unexpected result: {result:?}")]
UnexpectedResult {
effect: AppEffect,
result: AppEffectResult,
},
}
fn execute_expect_ok<H: AppEffectHandler>(
handler: &mut H,
effect: AppEffect,
) -> Result<(), AppEffectError> {
match handler.execute(effect.clone()) {
AppEffectResult::Ok => Ok(()),
AppEffectResult::Error(message) => Err(AppEffectError::Handler { effect, message }),
other => Err(AppEffectError::UnexpectedResult {
effect,
result: other,
}),
}
}
fn execute_expect_string<H: AppEffectHandler>(
handler: &mut H,
effect: AppEffect,
) -> Result<String, AppEffectError> {
match handler.execute(effect.clone()) {
AppEffectResult::String(value) => Ok(value),
AppEffectResult::Error(message) => Err(AppEffectError::Handler { effect, message }),
other => Err(AppEffectError::UnexpectedResult {
effect,
result: other,
}),
}
}
fn execute_expect_path<H: AppEffectHandler>(
handler: &mut H,
effect: AppEffect,
) -> Result<PathBuf, AppEffectError> {
match handler.execute(effect.clone()) {
AppEffectResult::Path(path) => Ok(path),
AppEffectResult::Error(message) => Err(AppEffectError::Handler { effect, message }),
other => Err(AppEffectError::UnexpectedResult {
effect,
result: other,
}),
}
}
fn execute_expect_bool<H: AppEffectHandler>(
handler: &mut H,
effect: AppEffect,
) -> Result<bool, AppEffectError> {
match handler.execute(effect.clone()) {
AppEffectResult::Bool(value) => Ok(value),
AppEffectResult::Error(message) => Err(AppEffectError::Handler { effect, message }),
other => Err(AppEffectError::UnexpectedResult {
effect,
result: other,
}),
}
}
pub fn handle_reset_start_commit<H: AppEffectHandler>(
handler: &mut H,
working_dir_override: Option<&PathBuf>,
) -> Result<String, AppEffectError> {
if let Some(dir) = working_dir_override {
execute_expect_ok(handler, AppEffect::SetCurrentDir { path: dir.clone() })?;
}
execute_expect_ok(handler, AppEffect::GitRequireRepo)?;
let repo_root = execute_expect_path(handler, AppEffect::GitGetRepoRoot)?;
if working_dir_override.is_none() {
execute_expect_ok(
handler,
AppEffect::SetCurrentDir {
path: repo_root.clone(),
},
)?;
}
execute_expect_string(handler, AppEffect::GitResetStartCommit)
}
pub fn save_start_commit<H: AppEffectHandler>(handler: &mut H) -> Result<String, AppEffectError> {
execute_expect_string(handler, AppEffect::GitSaveStartCommit)
}
pub fn is_on_main_branch<H: AppEffectHandler>(handler: &mut H) -> Result<bool, AppEffectError> {
execute_expect_bool(handler, AppEffect::GitIsMainBranch)
}
pub fn get_head_oid<H: AppEffectHandler>(handler: &mut H) -> Result<String, AppEffectError> {
execute_expect_string(handler, AppEffect::GitGetHeadOid)
}
pub fn require_repo<H: AppEffectHandler>(handler: &mut H) -> Result<(), AppEffectError> {
execute_expect_ok(handler, AppEffect::GitRequireRepo)
}
pub fn get_repo_root<H: AppEffectHandler>(handler: &mut H) -> Result<PathBuf, AppEffectError> {
execute_expect_path(handler, AppEffect::GitGetRepoRoot)
}
pub fn ensure_files_effectful<H: AppEffectHandler>(
handler: &mut H,
isolation_mode: bool,
) -> Result<(), AppEffectError> {
execute_expect_ok(
handler,
AppEffect::CreateDir {
path: PathBuf::from(".agent/logs"),
},
)?;
execute_expect_ok(
handler,
AppEffect::CreateDir {
path: PathBuf::from(".agent/tmp"),
},
)?;
let schemas = [
(".agent/tmp/plan.xsd", PLAN_XSD_SCHEMA),
(
".agent/tmp/development_result.xsd",
DEVELOPMENT_RESULT_XSD_SCHEMA,
),
(".agent/tmp/issues.xsd", ISSUES_XSD_SCHEMA),
(".agent/tmp/fix_result.xsd", FIX_RESULT_XSD_SCHEMA),
(".agent/tmp/commit_message.xsd", COMMIT_MESSAGE_XSD_SCHEMA),
];
schemas
.iter()
.map(|(path, content)| {
execute_expect_ok(
handler,
AppEffect::WriteFile {
path: PathBuf::from(*path),
content: content.to_string(),
},
)
})
.collect::<Result<Vec<_>, _>>()?;
if !isolation_mode {
let context_files = [
(".agent/STATUS.md", VAGUE_STATUS_LINE),
(".agent/NOTES.md", VAGUE_NOTES_LINE),
(".agent/ISSUES.md", VAGUE_ISSUES_LINE),
];
context_files
.iter()
.map(|(path, line)| {
let content = format!("{}\n", line.lines().next().unwrap_or_default().trim());
execute_expect_ok(
handler,
AppEffect::WriteFile {
path: PathBuf::from(*path),
content,
},
)
})
.collect::<Result<Vec<_>, _>>()?;
}
Ok(())
}
pub fn reset_context_for_isolation_effectful<H: AppEffectHandler>(
handler: &mut H,
) -> Result<(), AppEffectError> {
let context_files = [
PathBuf::from(".agent/STATUS.md"),
PathBuf::from(".agent/NOTES.md"),
PathBuf::from(".agent/ISSUES.md"),
];
context_files
.iter()
.map(|path| -> Result<(), AppEffectError> {
let exists =
execute_expect_bool(handler, AppEffect::PathExists { path: path.clone() })?;
if exists {
execute_expect_ok(handler, AppEffect::DeleteFile { path: path.clone() })?;
}
Ok(())
})
.collect::<Result<Vec<_>, _>>()?;
Ok(())
}
pub fn check_prompt_exists_effectful<H: AppEffectHandler>(
handler: &mut H,
) -> Result<bool, AppEffectError> {
execute_expect_bool(
handler,
AppEffect::PathExists {
path: PathBuf::from("PROMPT.md"),
},
)
}
#[cfg(test)]
mod tests;