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
80pub fn initialize_config_with<L: CatalogLoader, P: ConfigEnvironment>(
99 args: &Args,
100 colors: Colors,
101 logger: &Logger,
102 catalog_loader: &L,
103 path_resolver: &P,
104) -> anyhow::Result<Option<ConfigInitResult>> {
105 let (mut config, unified, warnings) =
108 loader::load_config_from_path_with_env(args.config.as_deref(), path_resolver);
109
110 for warning in warnings {
112 logger.warn(&warning);
113 }
114
115 let config_path = args
116 .config
117 .clone()
118 .or_else(unified_config_path)
119 .unwrap_or_else(|| PathBuf::from("~/.config/ralph-workflow.toml"));
120
121 apply_args_to_config(args, &mut config, colors);
123
124 if let Some(shell) = args.completion.generate_completion {
126 if handle_generate_completion(shell) {
127 return Ok(None);
128 }
129 }
130
131 if args.recovery.extended_help {
134 handle_extended_help();
135 if args.work_guide_list.list_work_guides {
136 println!();
137 handle_list_work_guides(colors);
138 }
139 return Ok(None);
140 }
141
142 if args.work_guide_list.list_work_guides && handle_list_work_guides(colors) {
144 return Ok(None);
145 }
146
147 if let Some(ref template_name) = args.init_prompt {
149 if handle_init_prompt_with(
150 template_name,
151 args.unified_init.force_init,
152 colors,
153 path_resolver,
154 )? {
155 return Ok(None);
156 }
157 }
158
159 if args.unified_init.init.is_some()
161 && handle_smart_init_with(
162 args.unified_init.init.as_deref(),
163 args.unified_init.force_init,
164 colors,
165 path_resolver,
166 )?
167 {
168 return Ok(None);
169 }
170
171 if args.unified_init.init_config && handle_init_global_with(colors, path_resolver)? {
173 return Ok(None);
174 }
175
176 if args.unified_init.init_global && handle_init_global_with(colors, path_resolver)? {
178 return Ok(None);
179 }
180
181 if args.legacy_init.init_legacy {
183 let repo_root = get_repo_root().ok();
184 let legacy_path = repo_root.map_or_else(
185 || PathBuf::from(".agent/agents.toml"),
186 |root| root.join(".agent/agents.toml"),
187 );
188 if handle_init_legacy(colors, &legacy_path)? {
189 return Ok(None);
190 }
191 }
192
193 let (registry, config_sources) =
195 load_agent_registry(unified.as_ref(), config_path.as_path(), catalog_loader)?;
196
197 apply_default_agents(&mut config, ®istry);
199
200 Ok(Some(ConfigInitResult {
201 config,
202 registry,
203 config_path,
204 config_sources,
205 }))
206}
207
208fn load_agent_registry<L: CatalogLoader>(
209 unified: Option<&UnifiedConfig>,
210 config_path: &std::path::Path,
211 catalog_loader: &L,
212) -> anyhow::Result<(AgentRegistry, Vec<ConfigSource>)> {
213 let mut registry = AgentRegistry::new().map_err(|e| {
214 anyhow::anyhow!("Failed to load built-in default agents config (examples/agents.toml): {e}")
215 })?;
216
217 let mut sources = Vec::new();
218
219 if unified.is_none() {
222 if let Some(global_path) = global_agents_config_path() {
223 if global_path.exists() {
224 let loaded = registry.load_from_file(&global_path).map_err(|e| {
225 anyhow::anyhow!(
226 "Failed to load legacy global agent config {}: {}",
227 global_path.display(),
228 e
229 )
230 })?;
231 sources.push(ConfigSource {
232 path: global_path,
233 agents_loaded: loaded,
234 });
235 }
236 }
237
238 let repo_root = get_repo_root().ok();
239 let project_path = repo_root.map_or_else(
240 || PathBuf::from(".agent/agents.toml"),
241 |root| root.join(".agent/agents.toml"),
242 );
243 if project_path.exists() {
244 let loaded = registry.load_from_file(&project_path).map_err(|e| {
245 anyhow::anyhow!(
246 "Failed to load legacy per-repo agent config {}: {}",
247 project_path.display(),
248 e
249 )
250 })?;
251 sources.push(ConfigSource {
252 path: project_path,
253 agents_loaded: loaded,
254 });
255 }
256 }
257
258 if let Some(unified_cfg) = unified {
259 let loaded = registry.apply_unified_config(unified_cfg);
260 if loaded > 0 || unified_cfg.agent_chain.is_some() {
261 sources.push(ConfigSource {
262 path: config_path.to_path_buf(),
263 agents_loaded: loaded,
264 });
265 }
266 }
267
268 setup_opencode_catalog(&mut registry, unified, catalog_loader)?;
270
271 Ok((registry, sources))
272}
273
274fn setup_opencode_catalog<L: CatalogLoader>(
282 registry: &mut AgentRegistry,
283 unified: Option<&UnifiedConfig>,
284 catalog_loader: &L,
285) -> anyhow::Result<()> {
286 let fallback = unified
288 .and_then(|u| u.agent_chain.as_ref())
289 .cloned()
290 .unwrap_or_else(|| registry.fallback_config().clone());
291
292 let opencode_refs = agent_validation::get_opencode_refs(&fallback);
294 if opencode_refs.is_empty() {
295 return Ok(());
297 }
298
299 let catalog = catalog_loader.load().map_err(|e| {
301 anyhow::anyhow!(
302 "Failed to load OpenCode API catalog. \
303 This is required for the following agent references: {opencode_refs:?}. \
304 Error: {e}"
305 )
306 })?;
307
308 registry.set_opencode_catalog(catalog.clone());
310
311 agent_validation::validate_opencode_agents(&fallback, &catalog)
313 .map_err(|e| anyhow::anyhow!("{e}"))?;
314
315 Ok(())
316}
317
318fn apply_default_agents(config: &mut Config, registry: &AgentRegistry) {
323 if config.developer_agent.is_none() {
324 config.developer_agent = registry
325 .fallback_config()
326 .get_fallbacks(AgentRole::Developer)
327 .first()
328 .cloned();
329 }
330 if config.reviewer_agent.is_none() {
331 config.reviewer_agent = registry
332 .fallback_config()
333 .get_fallbacks(AgentRole::Reviewer)
334 .first()
335 .cloned();
336 }
337}