ralph/commands/tutorial/
mod.rs1mod phases;
13mod prompter;
14mod sandbox;
15
16pub use prompter::{
17 DialoguerTutorialPrompter, ScriptedResponse, ScriptedTutorialPrompter, TutorialPrompter,
18};
19pub use sandbox::TutorialSandbox;
20
21use anyhow::Result;
22
23pub struct TutorialOptions {
25 pub interactive: bool,
27 pub keep_sandbox: bool,
29}
30
31pub fn run_tutorial(opts: TutorialOptions) -> Result<()> {
33 if opts.interactive {
34 run_tutorial_interactive(opts.keep_sandbox)
35 } else {
36 run_tutorial_non_interactive(opts.keep_sandbox)
37 }
38}
39
40fn run_tutorial_interactive(keep_sandbox: bool) -> Result<()> {
42 let prompter = DialoguerTutorialPrompter;
43
44 phases::phase_welcome(&prompter)?;
45 let sandbox = phases::phase_setup(&prompter)?;
46 phases::phase_init(&prompter, &sandbox)?;
47 let task_id = phases::phase_create_task(&prompter, &sandbox)?;
48 phases::phase_dry_run(&prompter, &sandbox, &task_id)?;
49 phases::phase_review(&prompter, &sandbox)?;
50 phases::phase_cleanup(&prompter, sandbox, keep_sandbox)?;
51
52 Ok(())
53}
54
55pub fn run_tutorial_with_prompter(
57 prompter: &dyn TutorialPrompter,
58 keep_sandbox: bool,
59) -> Result<()> {
60 phases::phase_welcome(prompter)?;
61 let sandbox = phases::phase_setup(prompter)?;
62 phases::phase_init(prompter, &sandbox)?;
63 let task_id = phases::phase_create_task(prompter, &sandbox)?;
64 phases::phase_dry_run(prompter, &sandbox, &task_id)?;
65 phases::phase_review(prompter, &sandbox)?;
66 phases::phase_cleanup(prompter, sandbox, keep_sandbox)?;
67
68 Ok(())
69}
70
71fn run_tutorial_non_interactive(keep_sandbox: bool) -> Result<()> {
73 log::info!("Running tutorial in non-interactive mode");
74
75 let sandbox = sandbox::TutorialSandbox::create()?;
76 log::info!("Created sandbox at: {}", sandbox.path.display());
77
78 let original_dir = std::env::current_dir()?;
80 std::env::set_current_dir(&sandbox.path)?;
81
82 let resolved = crate::config::resolve_from_cwd()?;
83 crate::commands::init::run_init(
84 &resolved,
85 crate::commands::init::InitOptions {
86 force: false,
87 force_lock: false,
88 interactive: false,
89 update_readme: false,
90 },
91 )?;
92
93 let task_id = "RQ-0001";
95 let task = crate::contracts::Task {
96 id: task_id.to_string(),
97 title: "Tutorial task".to_string(),
98 description: Some("A sample tutorial task".to_string()),
99 status: crate::contracts::TaskStatus::Todo,
100 priority: crate::contracts::TaskPriority::Medium,
101 tags: vec!["tutorial".to_string()],
102 scope: vec!["src/lib.rs".to_string()],
103 plan: vec!["Add a farewell function".to_string()],
104 request: Some("Tutorial task".to_string()),
105 created_at: Some(crate::timeutil::now_utc_rfc3339_or_fallback()),
106 updated_at: Some(crate::timeutil::now_utc_rfc3339_or_fallback()),
107 ..Default::default()
108 };
109 let queue = crate::contracts::QueueFile {
110 version: 1,
111 tasks: vec![task],
112 };
113 crate::queue::save_queue(&sandbox.path.join(".ralph/queue.jsonc"), &queue)?;
114
115 std::env::set_current_dir(&original_dir)?;
116
117 log::info!("Tutorial completed successfully");
118
119 if keep_sandbox {
120 let path = sandbox.preserve();
121 log::info!("Sandbox preserved at: {}", path.display());
122 }
123
124 Ok(())
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn tutorial_options_creation() {
133 let opts = TutorialOptions {
134 interactive: true,
135 keep_sandbox: false,
136 };
137 assert!(opts.interactive);
138 assert!(!opts.keep_sandbox);
139 }
140}