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>(
71 handler: &mut H,
72 working_dir_override: Option<&PathBuf>,
73) -> Result<String, String> {
74 if let Some(dir) = working_dir_override {
76 match handler.execute(AppEffect::SetCurrentDir { path: dir.clone() }) {
77 AppEffectResult::Ok => {}
78 AppEffectResult::Error(e) => return Err(e),
79 other => return Err(format!("unexpected result from SetCurrentDir: {other:?}")),
80 }
81 }
82
83 match handler.execute(AppEffect::GitRequireRepo) {
85 AppEffectResult::Ok => {}
86 AppEffectResult::Error(e) => return Err(e),
87 other => return Err(format!("unexpected result from GitRequireRepo: {other:?}")),
88 }
89
90 let repo_root = match handler.execute(AppEffect::GitGetRepoRoot) {
92 AppEffectResult::Path(p) => p,
93 AppEffectResult::Error(e) => return Err(e),
94 other => return Err(format!("unexpected result from GitGetRepoRoot: {other:?}")),
95 };
96
97 if working_dir_override.is_none() {
99 match handler.execute(AppEffect::SetCurrentDir { path: repo_root }) {
100 AppEffectResult::Ok => {}
101 AppEffectResult::Error(e) => return Err(e),
102 other => return Err(format!("unexpected result from SetCurrentDir: {other:?}")),
103 }
104 }
105
106 match handler.execute(AppEffect::GitResetStartCommit) {
108 AppEffectResult::String(oid) => Ok(oid),
109 AppEffectResult::Error(e) => Err(e),
110 other => Err(format!(
111 "unexpected result from GitResetStartCommit: {other:?}"
112 )),
113 }
114}
115
116pub fn save_start_commit<H: AppEffectHandler>(handler: &mut H) -> Result<String, String> {
129 match handler.execute(AppEffect::GitSaveStartCommit) {
130 AppEffectResult::String(oid) => Ok(oid),
131 AppEffectResult::Error(e) => Err(e),
132 other => Err(format!(
133 "unexpected result from GitSaveStartCommit: {other:?}"
134 )),
135 }
136}
137
138pub fn is_on_main_branch<H: AppEffectHandler>(handler: &mut H) -> Result<bool, String> {
148 match handler.execute(AppEffect::GitIsMainBranch) {
149 AppEffectResult::Bool(b) => Ok(b),
150 AppEffectResult::Error(e) => Err(e),
151 other => Err(format!("unexpected result from GitIsMainBranch: {other:?}")),
152 }
153}
154
155pub fn get_head_oid<H: AppEffectHandler>(handler: &mut H) -> Result<String, String> {
165 match handler.execute(AppEffect::GitGetHeadOid) {
166 AppEffectResult::String(oid) => Ok(oid),
167 AppEffectResult::Error(e) => Err(e),
168 other => Err(format!("unexpected result from GitGetHeadOid: {other:?}")),
169 }
170}
171
172pub fn require_repo<H: AppEffectHandler>(handler: &mut H) -> Result<(), String> {
182 match handler.execute(AppEffect::GitRequireRepo) {
183 AppEffectResult::Ok => Ok(()),
184 AppEffectResult::Error(e) => Err(e),
185 other => Err(format!("unexpected result from GitRequireRepo: {other:?}")),
186 }
187}
188
189pub fn get_repo_root<H: AppEffectHandler>(handler: &mut H) -> Result<PathBuf, String> {
199 match handler.execute(AppEffect::GitGetRepoRoot) {
200 AppEffectResult::Path(p) => Ok(p),
201 AppEffectResult::Error(e) => Err(e),
202 other => Err(format!("unexpected result from GitGetRepoRoot: {other:?}")),
203 }
204}
205
206pub fn ensure_files_effectful<H: AppEffectHandler>(
234 handler: &mut H,
235 isolation_mode: bool,
236) -> Result<(), String> {
237 match handler.execute(AppEffect::CreateDir {
239 path: PathBuf::from(".agent/logs"),
240 }) {
241 AppEffectResult::Ok => {}
242 AppEffectResult::Error(e) => return Err(format!("Failed to create .agent/logs: {e}")),
243 other => return Err(format!("Unexpected result from CreateDir: {other:?}")),
244 }
245
246 match handler.execute(AppEffect::CreateDir {
248 path: PathBuf::from(".agent/tmp"),
249 }) {
250 AppEffectResult::Ok => {}
251 AppEffectResult::Error(e) => return Err(format!("Failed to create .agent/tmp: {e}")),
252 other => return Err(format!("Unexpected result from CreateDir: {other:?}")),
253 }
254
255 let schemas = [
257 (".agent/tmp/plan.xsd", PLAN_XSD_SCHEMA),
258 (
259 ".agent/tmp/development_result.xsd",
260 DEVELOPMENT_RESULT_XSD_SCHEMA,
261 ),
262 (".agent/tmp/issues.xsd", ISSUES_XSD_SCHEMA),
263 (".agent/tmp/fix_result.xsd", FIX_RESULT_XSD_SCHEMA),
264 (".agent/tmp/commit_message.xsd", COMMIT_MESSAGE_XSD_SCHEMA),
265 ];
266
267 for (path, content) in schemas {
268 match handler.execute(AppEffect::WriteFile {
269 path: PathBuf::from(path),
270 content: content.to_string(),
271 }) {
272 AppEffectResult::Ok => {}
273 AppEffectResult::Error(e) => return Err(format!("Failed to write {path}: {e}")),
274 other => return Err(format!("Unexpected result from WriteFile: {other:?}")),
275 }
276 }
277
278 if !isolation_mode {
280 let context_files = [
281 (".agent/STATUS.md", VAGUE_STATUS_LINE),
282 (".agent/NOTES.md", VAGUE_NOTES_LINE),
283 (".agent/ISSUES.md", VAGUE_ISSUES_LINE),
284 ];
285
286 for (path, line) in context_files {
287 let content = format!("{}\n", line.lines().next().unwrap_or_default().trim());
289 match handler.execute(AppEffect::WriteFile {
290 path: PathBuf::from(path),
291 content,
292 }) {
293 AppEffectResult::Ok => {}
294 AppEffectResult::Error(e) => return Err(format!("Failed to write {path}: {e}")),
295 other => return Err(format!("Unexpected result from WriteFile: {other:?}")),
296 }
297 }
298 }
299
300 Ok(())
301}
302
303pub fn reset_context_for_isolation_effectful<H: AppEffectHandler>(
326 handler: &mut H,
327) -> Result<(), String> {
328 let context_files = [
329 PathBuf::from(".agent/STATUS.md"),
330 PathBuf::from(".agent/NOTES.md"),
331 PathBuf::from(".agent/ISSUES.md"),
332 ];
333
334 for path in context_files {
335 let exists = match handler.execute(AppEffect::PathExists { path: path.clone() }) {
337 AppEffectResult::Bool(b) => b,
338 AppEffectResult::Error(e) => {
339 return Err(format!(
340 "Failed to check if {} exists: {}",
341 path.display(),
342 e
343 ))
344 }
345 other => {
346 return Err(format!(
347 "Unexpected result from PathExists for {}: {:?}",
348 path.display(),
349 other
350 ))
351 }
352 };
353
354 if exists {
356 match handler.execute(AppEffect::DeleteFile { path: path.clone() }) {
357 AppEffectResult::Ok => {}
358 AppEffectResult::Error(e) => {
359 return Err(format!("Failed to delete {}: {}", path.display(), e))
360 }
361 other => {
362 return Err(format!(
363 "Unexpected result from DeleteFile for {}: {:?}",
364 path.display(),
365 other
366 ))
367 }
368 }
369 }
370 }
371
372 Ok(())
373}
374
375pub fn check_prompt_exists_effectful<H: AppEffectHandler>(handler: &mut H) -> Result<bool, String> {
393 match handler.execute(AppEffect::PathExists {
394 path: PathBuf::from("PROMPT.md"),
395 }) {
396 AppEffectResult::Bool(exists) => Ok(exists),
397 AppEffectResult::Error(e) => Err(format!("Failed to check PROMPT.md: {e}")),
398 other => Err(format!("Unexpected result from PathExists: {other:?}")),
399 }
400}
401
402#[cfg(test)]
403mod tests;