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 DEVELOPMENT_CONTINUATION_RESULT_XSD_SCHEMA: &str =
38 include_str!("../files/llm_output_extraction/development_continuation_result.xsd");
39const ISSUES_XSD_SCHEMA: &str = include_str!("../files/llm_output_extraction/issues.xsd");
40const FIX_RESULT_XSD_SCHEMA: &str = include_str!("../files/llm_output_extraction/fix_result.xsd");
41const COMMIT_MESSAGE_XSD_SCHEMA: &str =
42 include_str!("../files/llm_output_extraction/commit_message.xsd");
43
44use crate::files::io::context::{VAGUE_ISSUES_LINE, VAGUE_NOTES_LINE, VAGUE_STATUS_LINE};
46
47pub fn handle_reset_start_commit<H: AppEffectHandler>(
73 handler: &mut H,
74 working_dir_override: Option<&PathBuf>,
75) -> Result<String, String> {
76 if let Some(dir) = working_dir_override {
78 match handler.execute(AppEffect::SetCurrentDir { path: dir.clone() }) {
79 AppEffectResult::Ok => {}
80 AppEffectResult::Error(e) => return Err(e),
81 other => return Err(format!("unexpected result from SetCurrentDir: {other:?}")),
82 }
83 }
84
85 match handler.execute(AppEffect::GitRequireRepo) {
87 AppEffectResult::Ok => {}
88 AppEffectResult::Error(e) => return Err(e),
89 other => return Err(format!("unexpected result from GitRequireRepo: {other:?}")),
90 }
91
92 let repo_root = match handler.execute(AppEffect::GitGetRepoRoot) {
94 AppEffectResult::Path(p) => p,
95 AppEffectResult::Error(e) => return Err(e),
96 other => return Err(format!("unexpected result from GitGetRepoRoot: {other:?}")),
97 };
98
99 if working_dir_override.is_none() {
101 match handler.execute(AppEffect::SetCurrentDir { path: repo_root }) {
102 AppEffectResult::Ok => {}
103 AppEffectResult::Error(e) => return Err(e),
104 other => return Err(format!("unexpected result from SetCurrentDir: {other:?}")),
105 }
106 }
107
108 match handler.execute(AppEffect::GitResetStartCommit) {
110 AppEffectResult::String(oid) => Ok(oid),
111 AppEffectResult::Error(e) => Err(e),
112 other => Err(format!(
113 "unexpected result from GitResetStartCommit: {other:?}"
114 )),
115 }
116}
117
118pub fn save_start_commit<H: AppEffectHandler>(handler: &mut H) -> Result<String, String> {
131 match handler.execute(AppEffect::GitSaveStartCommit) {
132 AppEffectResult::String(oid) => Ok(oid),
133 AppEffectResult::Error(e) => Err(e),
134 other => Err(format!(
135 "unexpected result from GitSaveStartCommit: {other:?}"
136 )),
137 }
138}
139
140pub fn is_on_main_branch<H: AppEffectHandler>(handler: &mut H) -> Result<bool, String> {
150 match handler.execute(AppEffect::GitIsMainBranch) {
151 AppEffectResult::Bool(b) => Ok(b),
152 AppEffectResult::Error(e) => Err(e),
153 other => Err(format!("unexpected result from GitIsMainBranch: {other:?}")),
154 }
155}
156
157pub fn get_head_oid<H: AppEffectHandler>(handler: &mut H) -> Result<String, String> {
167 match handler.execute(AppEffect::GitGetHeadOid) {
168 AppEffectResult::String(oid) => Ok(oid),
169 AppEffectResult::Error(e) => Err(e),
170 other => Err(format!("unexpected result from GitGetHeadOid: {other:?}")),
171 }
172}
173
174pub fn require_repo<H: AppEffectHandler>(handler: &mut H) -> Result<(), String> {
184 match handler.execute(AppEffect::GitRequireRepo) {
185 AppEffectResult::Ok => Ok(()),
186 AppEffectResult::Error(e) => Err(e),
187 other => Err(format!("unexpected result from GitRequireRepo: {other:?}")),
188 }
189}
190
191pub fn get_repo_root<H: AppEffectHandler>(handler: &mut H) -> Result<PathBuf, String> {
201 match handler.execute(AppEffect::GitGetRepoRoot) {
202 AppEffectResult::Path(p) => Ok(p),
203 AppEffectResult::Error(e) => Err(e),
204 other => Err(format!("unexpected result from GitGetRepoRoot: {other:?}")),
205 }
206}
207
208pub fn ensure_files_effectful<H: AppEffectHandler>(
236 handler: &mut H,
237 isolation_mode: bool,
238) -> Result<(), String> {
239 match handler.execute(AppEffect::CreateDir {
241 path: PathBuf::from(".agent/logs"),
242 }) {
243 AppEffectResult::Ok => {}
244 AppEffectResult::Error(e) => return Err(format!("Failed to create .agent/logs: {e}")),
245 other => return Err(format!("Unexpected result from CreateDir: {other:?}")),
246 }
247
248 match handler.execute(AppEffect::CreateDir {
250 path: PathBuf::from(".agent/tmp"),
251 }) {
252 AppEffectResult::Ok => {}
253 AppEffectResult::Error(e) => return Err(format!("Failed to create .agent/tmp: {e}")),
254 other => return Err(format!("Unexpected result from CreateDir: {other:?}")),
255 }
256
257 let schemas = [
259 (".agent/tmp/plan.xsd", PLAN_XSD_SCHEMA),
260 (
261 ".agent/tmp/development_result.xsd",
262 DEVELOPMENT_RESULT_XSD_SCHEMA,
263 ),
264 (
265 ".agent/tmp/development_continuation_result.xsd",
266 DEVELOPMENT_CONTINUATION_RESULT_XSD_SCHEMA,
267 ),
268 (".agent/tmp/issues.xsd", ISSUES_XSD_SCHEMA),
269 (".agent/tmp/fix_result.xsd", FIX_RESULT_XSD_SCHEMA),
270 (".agent/tmp/commit_message.xsd", COMMIT_MESSAGE_XSD_SCHEMA),
271 ];
272
273 for (path, content) in schemas {
274 match handler.execute(AppEffect::WriteFile {
275 path: PathBuf::from(path),
276 content: content.to_string(),
277 }) {
278 AppEffectResult::Ok => {}
279 AppEffectResult::Error(e) => return Err(format!("Failed to write {path}: {e}")),
280 other => return Err(format!("Unexpected result from WriteFile: {other:?}")),
281 }
282 }
283
284 if !isolation_mode {
286 let context_files = [
287 (".agent/STATUS.md", VAGUE_STATUS_LINE),
288 (".agent/NOTES.md", VAGUE_NOTES_LINE),
289 (".agent/ISSUES.md", VAGUE_ISSUES_LINE),
290 ];
291
292 for (path, line) in context_files {
293 let content = format!("{}\n", line.lines().next().unwrap_or_default().trim());
295 match handler.execute(AppEffect::WriteFile {
296 path: PathBuf::from(path),
297 content,
298 }) {
299 AppEffectResult::Ok => {}
300 AppEffectResult::Error(e) => return Err(format!("Failed to write {path}: {e}")),
301 other => return Err(format!("Unexpected result from WriteFile: {other:?}")),
302 }
303 }
304 }
305
306 Ok(())
307}
308
309pub fn reset_context_for_isolation_effectful<H: AppEffectHandler>(
332 handler: &mut H,
333) -> Result<(), String> {
334 let context_files = [
335 PathBuf::from(".agent/STATUS.md"),
336 PathBuf::from(".agent/NOTES.md"),
337 PathBuf::from(".agent/ISSUES.md"),
338 ];
339
340 for path in context_files {
341 let exists = match handler.execute(AppEffect::PathExists { path: path.clone() }) {
343 AppEffectResult::Bool(b) => b,
344 AppEffectResult::Error(e) => {
345 return Err(format!(
346 "Failed to check if {} exists: {}",
347 path.display(),
348 e
349 ))
350 }
351 other => {
352 return Err(format!(
353 "Unexpected result from PathExists for {}: {:?}",
354 path.display(),
355 other
356 ))
357 }
358 };
359
360 if exists {
362 match handler.execute(AppEffect::DeleteFile { path: path.clone() }) {
363 AppEffectResult::Ok => {}
364 AppEffectResult::Error(e) => {
365 return Err(format!("Failed to delete {}: {}", path.display(), e))
366 }
367 other => {
368 return Err(format!(
369 "Unexpected result from DeleteFile for {}: {:?}",
370 path.display(),
371 other
372 ))
373 }
374 }
375 }
376 }
377
378 Ok(())
379}
380
381pub fn check_prompt_exists_effectful<H: AppEffectHandler>(handler: &mut H) -> Result<bool, String> {
399 match handler.execute(AppEffect::PathExists {
400 path: PathBuf::from("PROMPT.md"),
401 }) {
402 AppEffectResult::Bool(exists) => Ok(exists),
403 AppEffectResult::Error(e) => Err(format!("Failed to check PROMPT.md: {e}")),
404 other => Err(format!("Unexpected result from PathExists: {other:?}")),
405 }
406}
407
408#[cfg(test)]
409mod tests;