1use crate::agents::opencode_api::{CatalogLoader, RealCatalogLoader};
17use crate::agents::{
18 global_agents_config_path, validation as agent_validation, AgentRegistry, AgentRole,
19 ConfigSource,
20};
21use crate::cli::{
22 apply_args_to_config, handle_extended_help, handle_generate_completion,
23 handle_init_global_with, handle_init_legacy, handle_init_prompt_with, handle_list_work_guides,
24 handle_smart_init_with, Args,
25};
26use crate::config::{
27 loader, unified_config_path, Config, ConfigEnvironment, RealConfigEnvironment, UnifiedConfig,
28};
29use crate::git_helpers::get_repo_root;
30use crate::logger::Colors;
31use crate::logger::Logger;
32use std::path::PathBuf;
33
34pub struct ConfigInitResult {
36 pub config: Config,
38 pub registry: AgentRegistry,
40 pub config_path: PathBuf,
42 pub config_sources: Vec<ConfigSource>,
44}
45
46pub fn initialize_config(
67 args: &Args,
68 colors: Colors,
69 logger: &Logger,
70) -> anyhow::Result<Option<ConfigInitResult>> {
71 initialize_config_with(
72 args,
73 colors,
74 logger,
75 &RealCatalogLoader,
76 &RealConfigEnvironment,
77 )
78}
79
80#[deprecated(since = "0.6.0", note = "Use initialize_config_with instead")]
85pub fn initialize_config_with_loader<L: CatalogLoader>(
86 args: &Args,
87 colors: Colors,
88 logger: &Logger,
89 catalog_loader: &L,
90) -> anyhow::Result<Option<ConfigInitResult>> {
91 initialize_config_with(args, colors, logger, catalog_loader, &RealConfigEnvironment)
92}
93
94pub fn initialize_config_with<L: CatalogLoader, P: ConfigEnvironment>(
113 args: &Args,
114 colors: Colors,
115 logger: &Logger,
116 catalog_loader: &L,
117 path_resolver: &P,
118) -> anyhow::Result<Option<ConfigInitResult>> {
119 let (mut config, unified, warnings) =
122 loader::load_config_from_path_with_env(args.config.as_deref(), path_resolver);
123
124 for warning in warnings {
126 logger.warn(&warning);
127 }
128
129 let config_path = args
130 .config
131 .clone()
132 .or_else(unified_config_path)
133 .unwrap_or_else(|| PathBuf::from("~/.config/ralph-workflow.toml"));
134
135 config = config.with_commit_msg(args.commit_msg.clone());
137
138 apply_args_to_config(args, &mut config, colors);
140
141 if let Some(shell) = args.completion.generate_completion {
143 if handle_generate_completion(shell) {
144 return Ok(None);
145 }
146 }
147
148 if args.recovery.extended_help {
151 handle_extended_help();
152 if args.work_guide_list.list_work_guides {
153 println!();
154 handle_list_work_guides(colors);
155 }
156 return Ok(None);
157 }
158
159 if args.work_guide_list.list_work_guides && handle_list_work_guides(colors) {
161 return Ok(None);
162 }
163
164 if let Some(ref template_name) = args.init_prompt {
166 if handle_init_prompt_with(
167 template_name,
168 args.unified_init.force_init,
169 colors,
170 path_resolver,
171 )? {
172 return Ok(None);
173 }
174 }
175
176 if args.unified_init.init.is_some()
178 && handle_smart_init_with(
179 args.unified_init.init.as_deref(),
180 args.unified_init.force_init,
181 colors,
182 path_resolver,
183 )?
184 {
185 return Ok(None);
186 }
187
188 if args.unified_init.init_config && handle_init_global_with(colors, path_resolver)? {
190 return Ok(None);
191 }
192
193 if args.unified_init.init_global && handle_init_global_with(colors, path_resolver)? {
195 return Ok(None);
196 }
197
198 if args.legacy_init.init_legacy {
200 let repo_root = get_repo_root().ok();
201 let legacy_path = repo_root.map_or_else(
202 || PathBuf::from(".agent/agents.toml"),
203 |root| root.join(".agent/agents.toml"),
204 );
205 if handle_init_legacy(colors, &legacy_path)? {
206 return Ok(None);
207 }
208 }
209
210 let (registry, config_sources) =
212 load_agent_registry(unified.as_ref(), config_path.as_path(), catalog_loader)?;
213
214 apply_default_agents(&mut config, ®istry);
216
217 Ok(Some(ConfigInitResult {
218 config,
219 registry,
220 config_path,
221 config_sources,
222 }))
223}
224
225fn load_agent_registry<L: CatalogLoader>(
226 unified: Option<&UnifiedConfig>,
227 config_path: &std::path::Path,
228 catalog_loader: &L,
229) -> anyhow::Result<(AgentRegistry, Vec<ConfigSource>)> {
230 let mut registry = AgentRegistry::new().map_err(|e| {
231 anyhow::anyhow!("Failed to load built-in default agents config (examples/agents.toml): {e}")
232 })?;
233
234 let mut sources = Vec::new();
235
236 if unified.is_none() {
239 if let Some(global_path) = global_agents_config_path() {
240 if global_path.exists() {
241 let loaded = registry.load_from_file(&global_path).map_err(|e| {
242 anyhow::anyhow!(
243 "Failed to load legacy global agent config {}: {}",
244 global_path.display(),
245 e
246 )
247 })?;
248 sources.push(ConfigSource {
249 path: global_path,
250 agents_loaded: loaded,
251 });
252 }
253 }
254
255 let repo_root = get_repo_root().ok();
256 let project_path = repo_root.map_or_else(
257 || PathBuf::from(".agent/agents.toml"),
258 |root| root.join(".agent/agents.toml"),
259 );
260 if project_path.exists() {
261 let loaded = registry.load_from_file(&project_path).map_err(|e| {
262 anyhow::anyhow!(
263 "Failed to load legacy per-repo agent config {}: {}",
264 project_path.display(),
265 e
266 )
267 })?;
268 sources.push(ConfigSource {
269 path: project_path,
270 agents_loaded: loaded,
271 });
272 }
273 }
274
275 if let Some(unified_cfg) = unified {
276 let loaded = registry.apply_unified_config(unified_cfg);
277 if loaded > 0 || unified_cfg.agent_chain.is_some() {
278 sources.push(ConfigSource {
279 path: config_path.to_path_buf(),
280 agents_loaded: loaded,
281 });
282 }
283 }
284
285 setup_opencode_catalog(&mut registry, unified, catalog_loader)?;
287
288 Ok((registry, sources))
289}
290
291fn setup_opencode_catalog<L: CatalogLoader>(
299 registry: &mut AgentRegistry,
300 unified: Option<&UnifiedConfig>,
301 catalog_loader: &L,
302) -> anyhow::Result<()> {
303 let fallback = unified
305 .and_then(|u| u.agent_chain.as_ref())
306 .cloned()
307 .unwrap_or_else(|| registry.fallback_config().clone());
308
309 let opencode_refs = agent_validation::get_opencode_refs(&fallback);
311 if opencode_refs.is_empty() {
312 return Ok(());
314 }
315
316 let catalog = catalog_loader.load().map_err(|e| {
318 anyhow::anyhow!(
319 "Failed to load OpenCode API catalog. \
320 This is required for the following agent references: {opencode_refs:?}. \
321 Error: {e}"
322 )
323 })?;
324
325 registry.set_opencode_catalog(catalog.clone());
327
328 agent_validation::validate_opencode_agents(&fallback, &catalog)
330 .map_err(|e| anyhow::anyhow!("{e}"))?;
331
332 Ok(())
333}
334
335fn apply_default_agents(config: &mut Config, registry: &AgentRegistry) {
340 if config.developer_agent.is_none() {
341 config.developer_agent = registry
342 .fallback_config()
343 .get_fallbacks(AgentRole::Developer)
344 .first()
345 .cloned();
346 }
347 if config.reviewer_agent.is_none() {
348 config.reviewer_agent = registry
349 .fallback_config()
350 .get_fallbacks(AgentRole::Reviewer)
351 .first()
352 .cloned();
353 }
354}