1use anyhow::Result;
19use clap::{Args, Subcommand};
20
21use crate::{
22 agent, commands::prompt as prompt_cmd, commands::task as task_cmd, config, promptflow,
23};
24
25pub fn handle_prompt(args: PromptArgs) -> Result<()> {
26 let resolved = config::resolve_from_cwd()?;
27
28 match args.command {
29 PromptCommand::Worker(p) => {
30 let repoprompt_flags = agent::resolve_repoprompt_flags(p.repo_prompt, &resolved);
31
32 let mode = if p.single {
33 prompt_cmd::WorkerMode::Single
34 } else if let Some(phase) = p.phase {
35 match phase {
36 promptflow::RunPhase::Phase1 => prompt_cmd::WorkerMode::Phase1,
37 promptflow::RunPhase::Phase2 => prompt_cmd::WorkerMode::Phase2,
38 promptflow::RunPhase::Phase3 => prompt_cmd::WorkerMode::Phase3,
39 }
40 } else {
41 if resolved.config.agent.phases.unwrap_or(2) > 1 {
45 prompt_cmd::WorkerMode::Phase1
46 } else {
47 prompt_cmd::WorkerMode::Single
48 }
49 };
50
51 let prompt = prompt_cmd::build_worker_prompt(
52 &resolved,
53 prompt_cmd::WorkerPromptOptions {
54 task_id: p.task_id,
55 mode,
56 repoprompt_plan_required: repoprompt_flags.plan_required,
57 repoprompt_tool_injection: repoprompt_flags.tool_injection,
58 iterations: p.iterations,
59 iteration_index: p.iteration_index,
60 plan_file: p.plan_file,
61 plan_text: p.plan_text,
62 explain: p.explain,
63 },
64 )?;
65 print!("{prompt}");
66 }
67 PromptCommand::Scan(p) => {
68 let rp_required = agent::resolve_rp_required(p.repo_prompt, &resolved);
69 let prompt = prompt_cmd::build_scan_prompt(
70 &resolved,
71 prompt_cmd::ScanPromptOptions {
72 focus: p.focus,
73 mode: p.mode,
74 repoprompt_tool_injection: rp_required,
75 explain: p.explain,
76 },
77 )?;
78 print!("{prompt}");
79 }
80 PromptCommand::TaskBuilder(p) => {
81 let rp_required = agent::resolve_rp_required(p.repo_prompt, &resolved);
82
83 let request = if let Some(r) = p.request {
85 r
86 } else {
87 task_cmd::read_request_from_args_or_stdin(&[])? };
90
91 let prompt = prompt_cmd::build_task_builder_prompt(
92 &resolved,
93 prompt_cmd::TaskBuilderPromptOptions {
94 request,
95 hint_tags: p.tags,
96 hint_scope: p.scope,
97 repoprompt_tool_injection: rp_required,
98 explain: p.explain,
99 },
100 )?;
101 print!("{prompt}");
102 }
103 PromptCommand::List => {
104 prompt_cmd::list_prompts(&resolved.repo_root)?;
105 }
106 PromptCommand::Show(p) => {
107 prompt_cmd::show_prompt(&resolved.repo_root, &p.name, p.raw)?;
108 }
109 PromptCommand::Export(p) => {
110 prompt_cmd::export_prompts(&resolved.repo_root, p.name.as_deref(), p.force)?;
111 }
112 PromptCommand::Sync(p) => {
113 prompt_cmd::sync_prompts(&resolved.repo_root, p.dry_run, p.force)?;
114 }
115 PromptCommand::Diff(p) => {
116 prompt_cmd::diff_prompt(&resolved.repo_root, &p.name)?;
117 }
118 }
119
120 Ok(())
121}
122
123fn parse_phase(s: &str) -> anyhow::Result<promptflow::RunPhase> {
124 match s {
125 "1" => Ok(promptflow::RunPhase::Phase1),
126 "2" => Ok(promptflow::RunPhase::Phase2),
127 "3" => Ok(promptflow::RunPhase::Phase3),
128 _ => anyhow::bail!("invalid phase '{s}', expected 1, 2, or 3"),
129 }
130}
131
132#[derive(Args)]
133#[command(
134 about = "Manage and inspect prompt templates",
135 after_long_help = "Commands to view, export, and sync prompt templates.\n\nPreview compiled prompts (what the agent sees):\n ralph prompt worker --phase 1 --repo-prompt plan\n ralph prompt worker --single\n ralph prompt scan --focus \"risk audit\" --repo-prompt off\n ralph prompt task-builder --request \"Add tests\"\n\nList and view raw templates:\n ralph prompt list\n ralph prompt show worker --raw\n ralph prompt diff worker\n\nExport and sync templates:\n ralph prompt export --all\n ralph prompt export worker\n ralph prompt sync --dry-run\n ralph prompt sync --force\n"
136)]
137pub struct PromptArgs {
138 #[command(subcommand)]
139 pub command: PromptCommand,
140}
141
142#[derive(Subcommand)]
143pub enum PromptCommand {
144 Worker(PromptWorkerArgs),
146 Scan(PromptScanArgs),
148 TaskBuilder(PromptTaskBuilderArgs),
150 List,
152 Show(PromptShowArgs),
154 Export(PromptExportArgs),
156 Sync(PromptSyncArgs),
158 Diff(PromptDiffArgs),
160}
161
162#[derive(Args)]
163pub struct PromptWorkerArgs {
164 #[arg(long, conflicts_with = "phase")]
166 pub single: bool,
167
168 #[arg(long, value_parser = parse_phase)]
170 pub phase: Option<promptflow::RunPhase>,
171
172 #[arg(long)]
174 pub task_id: Option<String>,
175
176 #[arg(long)]
178 pub plan_file: Option<std::path::PathBuf>,
179
180 #[arg(long)]
182 pub plan_text: Option<String>,
183
184 #[arg(long, default_value_t = 1)]
186 pub iterations: u8,
187
188 #[arg(long, default_value_t = 1)]
190 pub iteration_index: u8,
191
192 #[arg(long = "repo-prompt", value_enum, value_name = "MODE")]
194 pub repo_prompt: Option<agent::RepoPromptMode>,
195
196 #[arg(long)]
198 pub explain: bool,
199}
200
201#[derive(Args)]
202pub struct PromptScanArgs {
203 #[arg(long, default_value = "")]
205 pub focus: String,
206
207 #[arg(short = 'm', long, value_enum, default_value_t = super::scan::ScanMode::Maintenance)]
210 pub mode: super::scan::ScanMode,
211
212 #[arg(long = "repo-prompt", value_enum, value_name = "MODE")]
214 pub repo_prompt: Option<agent::RepoPromptMode>,
215
216 #[arg(long)]
218 pub explain: bool,
219}
220
221#[derive(Args)]
222pub struct PromptTaskBuilderArgs {
223 #[arg(long)]
225 pub request: Option<String>,
226
227 #[arg(long, default_value = "")]
229 pub tags: String,
230
231 #[arg(long, default_value = "")]
233 pub scope: String,
234
235 #[arg(long = "repo-prompt", value_enum, value_name = "MODE")]
237 pub repo_prompt: Option<agent::RepoPromptMode>,
238
239 #[arg(long)]
241 pub explain: bool,
242}
243
244#[derive(Args)]
245pub struct PromptShowArgs {
246 pub name: String,
248
249 #[arg(long)]
251 pub raw: bool,
252}
253
254#[derive(Args)]
255pub struct PromptExportArgs {
256 pub name: Option<String>,
258
259 #[arg(long)]
261 pub all: bool,
262
263 #[arg(long)]
265 pub force: bool,
266}
267
268#[derive(Args)]
269pub struct PromptSyncArgs {
270 #[arg(long)]
272 pub dry_run: bool,
273
274 #[arg(long)]
276 pub force: bool,
277}
278
279#[derive(Args)]
280pub struct PromptDiffArgs {
281 pub name: String,
283}