ralph/agent/
repoprompt.rs1use crate::config;
20use crate::contracts::AgentConfig;
21use clap::ValueEnum;
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
25pub enum RepoPromptMode {
26 #[value(name = "tools")]
27 Tools,
28 #[value(name = "plan")]
29 Plan,
30 #[value(name = "off")]
31 Off,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct RepopromptFlags {
37 pub plan_required: bool,
38 pub tool_injection: bool,
39}
40
41pub(crate) fn repoprompt_flags_from_mode(mode: RepoPromptMode) -> RepopromptFlags {
43 match mode {
44 RepoPromptMode::Tools => RepopromptFlags {
45 plan_required: false,
46 tool_injection: true,
47 },
48 RepoPromptMode::Plan => RepopromptFlags {
49 plan_required: true,
50 tool_injection: true,
51 },
52 RepoPromptMode::Off => RepopromptFlags {
53 plan_required: false,
54 tool_injection: false,
55 },
56 }
57}
58
59pub(crate) fn resolve_repoprompt_flags_from_agent_config(agent: &AgentConfig) -> RepopromptFlags {
61 let plan_required = agent.repoprompt_plan_required.unwrap_or(false);
62 let tool_injection = agent.repoprompt_tool_injection.unwrap_or(false);
63 RepopromptFlags {
64 plan_required,
65 tool_injection,
66 }
67}
68
69pub fn resolve_repoprompt_flags(
71 repo_prompt: Option<RepoPromptMode>,
72 resolved: &config::Resolved,
73) -> RepopromptFlags {
74 if let Some(mode) = repo_prompt {
75 return repoprompt_flags_from_mode(mode);
76 }
77 resolve_repoprompt_flags_from_agent_config(&resolved.config.agent)
78}
79
80pub fn resolve_rp_required(
82 repo_prompt: Option<RepoPromptMode>,
83 resolved: &config::Resolved,
84) -> bool {
85 resolve_repoprompt_flags(repo_prompt, resolved).tool_injection
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use crate::contracts::{
92 AgentConfig, ClaudePermissionMode, Config, GitRevertMode, NotificationConfig, QueueConfig,
93 RunnerRetryConfig,
94 };
95 use tempfile::TempDir;
96
97 fn resolved_with_defaults() -> config::Resolved {
98 let dir = TempDir::new().expect("temp dir");
99 let repo_root = dir.path().to_path_buf();
100
101 let cfg = Config {
102 agent: AgentConfig {
103 runner: None,
104 model: None,
105 reasoning_effort: None,
106 iterations: None,
107 followup_reasoning_effort: None,
108 codex_bin: Some("codex".to_string()),
109 opencode_bin: Some("opencode".to_string()),
110 gemini_bin: Some("gemini".to_string()),
111 claude_bin: Some("claude".to_string()),
112 cursor_bin: Some("agent".to_string()),
113 kimi_bin: Some("kimi".to_string()),
114 pi_bin: Some("pi".to_string()),
115 phases: Some(2),
116 claude_permission_mode: Some(ClaudePermissionMode::BypassPermissions),
117 runner_cli: None,
118 phase_overrides: None,
119 instruction_files: None,
120 repoprompt_plan_required: None,
121 repoprompt_tool_injection: None,
122 ci_gate: Some(crate::contracts::CiGateConfig {
123 enabled: Some(true),
124 argv: Some(vec!["make".to_string(), "ci".to_string()]),
125 }),
126 git_revert_mode: Some(GitRevertMode::Ask),
127 git_commit_push_enabled: Some(true),
128 notification: NotificationConfig::default(),
129 webhook: crate::contracts::WebhookConfig::default(),
130 runner_retry: RunnerRetryConfig::default(),
131 session_timeout_hours: None,
132 scan_prompt_version: None,
133 },
134 queue: QueueConfig::default(),
135 ..Config::default()
136 };
137
138 config::Resolved {
139 config: cfg,
140 repo_root: repo_root.clone(),
141 queue_path: repo_root.join(".ralph/queue.json"),
142 done_path: repo_root.join(".ralph/done.json"),
143 id_prefix: "RQ".to_string(),
144 id_width: 4,
145 global_config_path: None,
146 project_config_path: Some(repo_root.join(".ralph/config.json")),
147 }
148 }
149
150 #[test]
151 fn resolve_rp_required_cli_plan_overrides_config() {
152 let resolved = resolved_with_defaults();
153 assert!(resolve_rp_required(Some(RepoPromptMode::Plan), &resolved));
154 }
155
156 #[test]
157 fn resolve_rp_required_cli_off_overrides_config() {
158 let resolved = resolved_with_defaults();
159 assert!(!resolve_rp_required(Some(RepoPromptMode::Off), &resolved));
160 }
161
162 #[test]
163 fn resolve_rp_required_uses_config_when_cli_not_set() {
164 let mut resolved = resolved_with_defaults();
165 resolved.config.agent.repoprompt_tool_injection = Some(true);
166 assert!(resolve_rp_required(None, &resolved));
167
168 resolved.config.agent.repoprompt_tool_injection = Some(false);
169 assert!(!resolve_rp_required(None, &resolved));
170 }
171
172 #[test]
173 fn resolve_repoprompt_flags_defaults_false_when_unset() {
174 let resolved = resolved_with_defaults();
175 let flags = resolve_repoprompt_flags(None, &resolved);
176 assert!(!flags.plan_required);
177 assert!(!flags.tool_injection);
178 }
179
180 #[test]
181 fn resolve_repoprompt_flags_uses_config_fields() {
182 let mut resolved = resolved_with_defaults();
183 resolved.config.agent.repoprompt_plan_required = Some(true);
184 resolved.config.agent.repoprompt_tool_injection = Some(false);
185
186 let flags = resolve_repoprompt_flags(None, &resolved);
187 assert!(flags.plan_required);
188 assert!(!flags.tool_injection);
189 }
190}