ralph_workflow/app/
config_init.rs1use crate::agents::opencode_api::{CatalogLoader, RealCatalogLoader};
11use crate::agents::{
12 global_agents_config_path, validation as agent_validation, AgentRegistry, AgentRole,
13 ConfigSource,
14};
15use crate::cli::{
16 apply_args_to_config, handle_extended_help, handle_generate_completion, handle_init_global,
17 handle_init_legacy, handle_init_prompt, handle_list_work_guides, handle_smart_init, Args,
18};
19use crate::config::{loader, unified_config_path, Config, UnifiedConfig};
20use crate::git_helpers::get_repo_root;
21use crate::logger::Colors;
22use crate::logger::Logger;
23use std::path::PathBuf;
24
25pub struct ConfigInitResult {
27 pub config: Config,
29 pub registry: AgentRegistry,
31 pub config_path: PathBuf,
33 pub config_sources: Vec<ConfigSource>,
35}
36
37pub fn initialize_config(
58 args: &Args,
59 colors: Colors,
60 logger: &Logger,
61) -> anyhow::Result<Option<ConfigInitResult>> {
62 initialize_config_with_loader(args, colors, logger, &RealCatalogLoader)
63}
64
65pub fn initialize_config_with_loader<L: CatalogLoader>(
70 args: &Args,
71 colors: Colors,
72 logger: &Logger,
73 catalog_loader: &L,
74) -> anyhow::Result<Option<ConfigInitResult>> {
75 let (mut config, unified, warnings) = args
77 .config
78 .as_ref()
79 .map_or_else(loader::load_config, |config_path| {
80 loader::load_config_from_path(Some(config_path.as_path()))
81 });
82
83 for warning in warnings {
85 logger.warn(&warning);
86 }
87
88 let config_path = args
89 .config
90 .clone()
91 .or_else(unified_config_path)
92 .unwrap_or_else(|| PathBuf::from("~/.config/ralph-workflow.toml"));
93
94 config = config.with_commit_msg(args.commit_msg.clone());
96
97 apply_args_to_config(args, &mut config, colors);
99
100 if let Some(shell) = args.completion.generate_completion {
102 if handle_generate_completion(shell) {
103 return Ok(None);
104 }
105 }
106
107 if args.recovery.extended_help {
110 handle_extended_help();
111 if args.work_guide_list.list_work_guides {
112 println!();
113 handle_list_work_guides(colors);
114 }
115 return Ok(None);
116 }
117
118 if args.work_guide_list.list_work_guides && handle_list_work_guides(colors) {
120 return Ok(None);
121 }
122
123 if let Some(ref template_name) = args.init_prompt {
125 if handle_init_prompt(template_name, args.unified_init.force_init, colors)? {
126 return Ok(None);
127 }
128 }
129
130 if args.unified_init.init.is_some()
132 && handle_smart_init(
133 args.unified_init.init.as_deref(),
134 args.unified_init.force_init,
135 colors,
136 )?
137 {
138 return Ok(None);
139 }
140
141 if args.unified_init.init_config && handle_init_global(colors)? {
143 return Ok(None);
144 }
145
146 if args.unified_init.init_global && handle_init_global(colors)? {
148 return Ok(None);
149 }
150
151 if args.legacy_init.init_legacy {
153 let repo_root = get_repo_root().ok();
154 let legacy_path = repo_root.map_or_else(
155 || PathBuf::from(".agent/agents.toml"),
156 |root| root.join(".agent/agents.toml"),
157 );
158 if handle_init_legacy(colors, &legacy_path)? {
159 return Ok(None);
160 }
161 }
162
163 let (registry, config_sources) =
165 load_agent_registry(unified.as_ref(), config_path.as_path(), catalog_loader)?;
166
167 apply_default_agents(&mut config, ®istry);
169
170 Ok(Some(ConfigInitResult {
171 config,
172 registry,
173 config_path,
174 config_sources,
175 }))
176}
177
178fn load_agent_registry<L: CatalogLoader>(
179 unified: Option<&UnifiedConfig>,
180 config_path: &std::path::Path,
181 catalog_loader: &L,
182) -> anyhow::Result<(AgentRegistry, Vec<ConfigSource>)> {
183 let mut registry = AgentRegistry::new().map_err(|e| {
184 anyhow::anyhow!("Failed to load built-in default agents config (examples/agents.toml): {e}")
185 })?;
186
187 let mut sources = Vec::new();
188
189 if unified.is_none() {
192 if let Some(global_path) = global_agents_config_path() {
193 if global_path.exists() {
194 let loaded = registry.load_from_file(&global_path).map_err(|e| {
195 anyhow::anyhow!(
196 "Failed to load legacy global agent config {}: {}",
197 global_path.display(),
198 e
199 )
200 })?;
201 sources.push(ConfigSource {
202 path: global_path,
203 agents_loaded: loaded,
204 });
205 }
206 }
207
208 let repo_root = get_repo_root().ok();
209 let project_path = repo_root.map_or_else(
210 || PathBuf::from(".agent/agents.toml"),
211 |root| root.join(".agent/agents.toml"),
212 );
213 if project_path.exists() {
214 let loaded = registry.load_from_file(&project_path).map_err(|e| {
215 anyhow::anyhow!(
216 "Failed to load legacy per-repo agent config {}: {}",
217 project_path.display(),
218 e
219 )
220 })?;
221 sources.push(ConfigSource {
222 path: project_path,
223 agents_loaded: loaded,
224 });
225 }
226 }
227
228 if let Some(unified_cfg) = unified {
229 let loaded = registry.apply_unified_config(unified_cfg);
230 if loaded > 0 || unified_cfg.agent_chain.is_some() {
231 sources.push(ConfigSource {
232 path: config_path.to_path_buf(),
233 agents_loaded: loaded,
234 });
235 }
236 }
237
238 setup_opencode_catalog(&mut registry, unified, catalog_loader)?;
240
241 Ok((registry, sources))
242}
243
244fn setup_opencode_catalog<L: CatalogLoader>(
252 registry: &mut AgentRegistry,
253 unified: Option<&UnifiedConfig>,
254 catalog_loader: &L,
255) -> anyhow::Result<()> {
256 let fallback = unified
258 .and_then(|u| u.agent_chain.as_ref())
259 .cloned()
260 .unwrap_or_else(|| registry.fallback_config().clone());
261
262 let opencode_refs = agent_validation::get_opencode_refs(&fallback);
264 if opencode_refs.is_empty() {
265 return Ok(());
267 }
268
269 let catalog = catalog_loader.load().map_err(|e| {
271 anyhow::anyhow!(
272 "Failed to load OpenCode API catalog. \
273 This is required for the following agent references: {opencode_refs:?}. \
274 Error: {e}"
275 )
276 })?;
277
278 registry.set_opencode_catalog(catalog.clone());
280
281 agent_validation::validate_opencode_agents(&fallback, &catalog)
283 .map_err(|e| anyhow::anyhow!("{e}"))?;
284
285 Ok(())
286}
287
288fn apply_default_agents(config: &mut Config, registry: &AgentRegistry) {
293 if config.developer_agent.is_none() {
294 config.developer_agent = registry
295 .fallback_config()
296 .get_fallbacks(AgentRole::Developer)
297 .first()
298 .cloned();
299 }
300 if config.reviewer_agent.is_none() {
301 config.reviewer_agent = registry
302 .fallback_config()
303 .get_fallbacks(AgentRole::Reviewer)
304 .first()
305 .cloned();
306 }
307}