1use super::effect::{AppEffect, AppEffectHandler, AppEffectResult};
31use std::path::PathBuf;
32
33const PLAN_XSD_SCHEMA: &str = include_str!("../files/llm_output_extraction/plan.xsd");
35const DEVELOPMENT_RESULT_XSD_SCHEMA: &str =
36 include_str!("../files/llm_output_extraction/development_result.xsd");
37const ISSUES_XSD_SCHEMA: &str = include_str!("../files/llm_output_extraction/issues.xsd");
38const FIX_RESULT_XSD_SCHEMA: &str = include_str!("../files/llm_output_extraction/fix_result.xsd");
39const COMMIT_MESSAGE_XSD_SCHEMA: &str =
40 include_str!("../files/llm_output_extraction/commit_message.xsd");
41
42use crate::files::io::context::{VAGUE_ISSUES_LINE, VAGUE_NOTES_LINE, VAGUE_STATUS_LINE};
44
45pub fn handle_reset_start_commit<H: AppEffectHandler>(
67 handler: &mut H,
68 working_dir_override: Option<&PathBuf>,
69) -> Result<String, String> {
70 if let Some(dir) = working_dir_override {
72 match handler.execute(AppEffect::SetCurrentDir { path: dir.clone() }) {
73 AppEffectResult::Ok => {}
74 AppEffectResult::Error(e) => return Err(e),
75 other => return Err(format!("unexpected result from SetCurrentDir: {other:?}")),
76 }
77 }
78
79 match handler.execute(AppEffect::GitRequireRepo) {
81 AppEffectResult::Ok => {}
82 AppEffectResult::Error(e) => return Err(e),
83 other => return Err(format!("unexpected result from GitRequireRepo: {other:?}")),
84 }
85
86 let repo_root = match handler.execute(AppEffect::GitGetRepoRoot) {
88 AppEffectResult::Path(p) => p,
89 AppEffectResult::Error(e) => return Err(e),
90 other => return Err(format!("unexpected result from GitGetRepoRoot: {other:?}")),
91 };
92
93 if working_dir_override.is_none() {
95 match handler.execute(AppEffect::SetCurrentDir { path: repo_root }) {
96 AppEffectResult::Ok => {}
97 AppEffectResult::Error(e) => return Err(e),
98 other => return Err(format!("unexpected result from SetCurrentDir: {other:?}")),
99 }
100 }
101
102 match handler.execute(AppEffect::GitResetStartCommit) {
104 AppEffectResult::String(oid) => Ok(oid),
105 AppEffectResult::Error(e) => Err(e),
106 other => Err(format!(
107 "unexpected result from GitResetStartCommit: {other:?}"
108 )),
109 }
110}
111
112pub fn save_start_commit<H: AppEffectHandler>(handler: &mut H) -> Result<String, String> {
121 match handler.execute(AppEffect::GitSaveStartCommit) {
122 AppEffectResult::String(oid) => Ok(oid),
123 AppEffectResult::Error(e) => Err(e),
124 other => Err(format!(
125 "unexpected result from GitSaveStartCommit: {other:?}"
126 )),
127 }
128}
129
130pub fn is_on_main_branch<H: AppEffectHandler>(handler: &mut H) -> Result<bool, String> {
136 match handler.execute(AppEffect::GitIsMainBranch) {
137 AppEffectResult::Bool(b) => Ok(b),
138 AppEffectResult::Error(e) => Err(e),
139 other => Err(format!("unexpected result from GitIsMainBranch: {other:?}")),
140 }
141}
142
143pub fn get_head_oid<H: AppEffectHandler>(handler: &mut H) -> Result<String, String> {
149 match handler.execute(AppEffect::GitGetHeadOid) {
150 AppEffectResult::String(oid) => Ok(oid),
151 AppEffectResult::Error(e) => Err(e),
152 other => Err(format!("unexpected result from GitGetHeadOid: {other:?}")),
153 }
154}
155
156pub fn require_repo<H: AppEffectHandler>(handler: &mut H) -> Result<(), String> {
162 match handler.execute(AppEffect::GitRequireRepo) {
163 AppEffectResult::Ok => Ok(()),
164 AppEffectResult::Error(e) => Err(e),
165 other => Err(format!("unexpected result from GitRequireRepo: {other:?}")),
166 }
167}
168
169pub fn get_repo_root<H: AppEffectHandler>(handler: &mut H) -> Result<PathBuf, String> {
175 match handler.execute(AppEffect::GitGetRepoRoot) {
176 AppEffectResult::Path(p) => Ok(p),
177 AppEffectResult::Error(e) => Err(e),
178 other => Err(format!("unexpected result from GitGetRepoRoot: {other:?}")),
179 }
180}
181
182pub fn ensure_files_effectful<H: AppEffectHandler>(
206 handler: &mut H,
207 isolation_mode: bool,
208) -> Result<(), String> {
209 match handler.execute(AppEffect::CreateDir {
211 path: PathBuf::from(".agent/logs"),
212 }) {
213 AppEffectResult::Ok => {}
214 AppEffectResult::Error(e) => return Err(format!("Failed to create .agent/logs: {e}")),
215 other => return Err(format!("Unexpected result from CreateDir: {other:?}")),
216 }
217
218 match handler.execute(AppEffect::CreateDir {
220 path: PathBuf::from(".agent/tmp"),
221 }) {
222 AppEffectResult::Ok => {}
223 AppEffectResult::Error(e) => return Err(format!("Failed to create .agent/tmp: {e}")),
224 other => return Err(format!("Unexpected result from CreateDir: {other:?}")),
225 }
226
227 let schemas = [
229 (".agent/tmp/plan.xsd", PLAN_XSD_SCHEMA),
230 (
231 ".agent/tmp/development_result.xsd",
232 DEVELOPMENT_RESULT_XSD_SCHEMA,
233 ),
234 (".agent/tmp/issues.xsd", ISSUES_XSD_SCHEMA),
235 (".agent/tmp/fix_result.xsd", FIX_RESULT_XSD_SCHEMA),
236 (".agent/tmp/commit_message.xsd", COMMIT_MESSAGE_XSD_SCHEMA),
237 ];
238
239 for (path, content) in schemas {
240 match handler.execute(AppEffect::WriteFile {
241 path: PathBuf::from(path),
242 content: content.to_string(),
243 }) {
244 AppEffectResult::Ok => {}
245 AppEffectResult::Error(e) => return Err(format!("Failed to write {path}: {e}")),
246 other => return Err(format!("Unexpected result from WriteFile: {other:?}")),
247 }
248 }
249
250 if !isolation_mode {
252 let context_files = [
253 (".agent/STATUS.md", VAGUE_STATUS_LINE),
254 (".agent/NOTES.md", VAGUE_NOTES_LINE),
255 (".agent/ISSUES.md", VAGUE_ISSUES_LINE),
256 ];
257
258 for (path, line) in context_files {
259 let content = format!("{}\n", line.lines().next().unwrap_or_default().trim());
261 match handler.execute(AppEffect::WriteFile {
262 path: PathBuf::from(path),
263 content,
264 }) {
265 AppEffectResult::Ok => {}
266 AppEffectResult::Error(e) => return Err(format!("Failed to write {path}: {e}")),
267 other => return Err(format!("Unexpected result from WriteFile: {other:?}")),
268 }
269 }
270 }
271
272 Ok(())
273}
274
275pub fn reset_context_for_isolation_effectful<H: AppEffectHandler>(
294 handler: &mut H,
295) -> Result<(), String> {
296 let context_files = [
297 PathBuf::from(".agent/STATUS.md"),
298 PathBuf::from(".agent/NOTES.md"),
299 PathBuf::from(".agent/ISSUES.md"),
300 ];
301
302 for path in context_files {
303 let exists = match handler.execute(AppEffect::PathExists { path: path.clone() }) {
305 AppEffectResult::Bool(b) => b,
306 AppEffectResult::Error(e) => {
307 return Err(format!(
308 "Failed to check if {} exists: {}",
309 path.display(),
310 e
311 ))
312 }
313 other => {
314 return Err(format!(
315 "Unexpected result from PathExists for {}: {:?}",
316 path.display(),
317 other
318 ))
319 }
320 };
321
322 if exists {
324 match handler.execute(AppEffect::DeleteFile { path: path.clone() }) {
325 AppEffectResult::Ok => {}
326 AppEffectResult::Error(e) => {
327 return Err(format!("Failed to delete {}: {}", path.display(), e))
328 }
329 other => {
330 return Err(format!(
331 "Unexpected result from DeleteFile for {}: {:?}",
332 path.display(),
333 other
334 ))
335 }
336 }
337 }
338 }
339
340 Ok(())
341}
342
343pub fn check_prompt_exists_effectful<H: AppEffectHandler>(handler: &mut H) -> Result<bool, String> {
357 match handler.execute(AppEffect::PathExists {
358 path: PathBuf::from("PROMPT.md"),
359 }) {
360 AppEffectResult::Bool(exists) => Ok(exists),
361 AppEffectResult::Error(e) => Err(format!("Failed to check PROMPT.md: {}", e)),
362 other => Err(format!("Unexpected result from PathExists: {:?}", other)),
363 }
364}
365
366#[cfg(test)]
367mod tests;