Skip to main content

aether_cli/agent/new_agent_wizard/
draft_agent_entry.rs

1use aether_project::{AgentEntry, Settings};
2use std::{
3    fs::{create_dir_all, read_to_string, write},
4    path::{Path, PathBuf},
5};
6
7use super::new_agent_step::{NewAgentMode, PromptFile};
8use crate::error::CliError;
9
10pub struct DraftAgentEntry {
11    pub entry: AgentEntry,
12    pub system_md_content: String,
13    pub system_md_edited: bool,
14    pub workspace_mcp_configs: Vec<String>,
15}
16
17impl DraftAgentEntry {
18    pub fn slug(&self) -> String {
19        self.entry.name.to_lowercase().replace(' ', "-")
20    }
21
22    pub fn generated_paths(&self, mode: &NewAgentMode) -> GeneratedPaths {
23        let filename = format!("{}.md", self.slug().to_uppercase());
24        match mode {
25            NewAgentMode::ScaffoldProject => GeneratedPaths {
26                system_md: PathBuf::from(format!(".aether/{filename}")),
27                mcp_json: PathBuf::from(".aether/mcp.json"),
28            },
29            NewAgentMode::AddAgentToExistingProject => {
30                let slug = self.slug();
31                GeneratedPaths {
32                    system_md: PathBuf::from(format!(".aether/agents/{slug}/{filename}")),
33                    mcp_json: PathBuf::from(format!(".aether/agents/{slug}/mcp.json")),
34                }
35            }
36        }
37    }
38
39    pub fn to_agent_entry(&self, mode: &NewAgentMode, inherited_prompts: &[String]) -> AgentEntry {
40        let paths = self.generated_paths(mode);
41
42        let mut prompts = vec![paths.system_md.to_string_lossy().to_string()];
43        match mode {
44            NewAgentMode::ScaffoldProject => {
45                prompts.extend(self.entry.prompts.iter().cloned());
46            }
47            NewAgentMode::AddAgentToExistingProject => {
48                for name in &self.entry.prompts {
49                    if !inherited_prompts.iter().any(|d| d == name) {
50                        prompts.push(name.clone());
51                    }
52                }
53            }
54        }
55
56        let mut mcp_servers =
57            if self.entry.mcp_servers.is_empty() { vec![] } else { vec![paths.mcp_json.to_string_lossy().to_string()] };
58        mcp_servers.extend(self.workspace_mcp_configs.iter().cloned());
59
60        AgentEntry { prompts, mcp_servers, ..self.entry.clone() }
61    }
62
63    pub fn to_settings(&self, mode: &NewAgentMode, existing: Option<&str>) -> Settings {
64        match mode {
65            NewAgentMode::ScaffoldProject => {
66                let entry = self.to_agent_entry(mode, &[]);
67                Settings { prompts: vec![], mcp_servers: vec![], agents: vec![entry] }
68            }
69            NewAgentMode::AddAgentToExistingProject => {
70                let inherited = inherited_prompts_from_existing(existing);
71                let entry = self.to_agent_entry(mode, &inherited);
72
73                let mut settings: Settings = existing.and_then(|s| serde_json::from_str(s).ok()).unwrap_or_default();
74                settings.agents.push(entry);
75                settings
76            }
77        }
78    }
79
80    pub fn to_mcp_json(&self) -> String {
81        use mcp_utils::client::config::{RawMcpConfig, RawMcpServerConfig};
82        use std::collections::BTreeMap;
83
84        let servers = self
85            .entry
86            .mcp_servers
87            .iter()
88            .map(|name| {
89                let args = if name == "skills" { vec!["--dir".into(), "$HOME/.aether".into()] } else { vec![] };
90                (name.clone(), RawMcpServerConfig::InMemory { args, input: None })
91            })
92            .collect::<BTreeMap<_, _>>();
93
94        let config = RawMcpConfig { servers };
95        serde_json::to_string_pretty(&config).expect("mcp serialization cannot fail")
96    }
97}
98
99pub struct GeneratedPaths {
100    pub system_md: PathBuf,
101    pub mcp_json: PathBuf,
102}
103
104fn inherited_prompts_from_existing(existing: Option<&str>) -> Vec<String> {
105    existing
106        .and_then(|s| serde_json::from_str::<Settings>(s).ok())
107        .map(|s| s.prompts.into_iter().filter(|p| PromptFile::all().iter().any(|d| d.filename() == p)).collect())
108        .unwrap_or_default()
109}
110
111pub fn build_system_md(draft: &DraftAgentEntry) -> String {
112    format!(
113        "# {name}
114
115{description}
116
117## System Env
118
119Working directory: !`pwd`\\
120Platform: !`uname -s`\\
121Today's date: !`date +%Y-%m-%d`\\
122Git branch: !`git rev-parse --abbrev-ref HEAD`
123",
124        name = draft.entry.name,
125        description = draft.entry.description,
126    )
127}
128
129pub fn build_agents_md(draft: &DraftAgentEntry) -> String {
130    format!("# {}\n\n{}\n\nYou are an expert coding assistant.\n", draft.entry.name, draft.entry.description)
131}
132
133pub fn scaffold(project_root: &Path, draft: &DraftAgentEntry) -> Result<(), CliError> {
134    create_dir_all(project_root).map_err(CliError::IoError)?;
135
136    let paths = draft.generated_paths(&NewAgentMode::ScaffoldProject);
137    write_if_absent(&project_root.join(&paths.system_md), &draft.system_md_content)?;
138    write_if_absent(&project_root.join(".aether/mcp.json"), &draft.to_mcp_json())?;
139    if draft.entry.prompts.iter().any(|n| n == PromptFile::Agents.filename()) {
140        write_if_absent(&project_root.join("AGENTS.md"), &build_agents_md(draft))?;
141    }
142    let settings = draft.to_settings(&NewAgentMode::ScaffoldProject, None);
143    let json = serde_json::to_string_pretty(&settings).expect("settings serialization cannot fail");
144    write_if_absent(&project_root.join(".aether/settings.json"), &json)?;
145
146    Ok(())
147}
148
149pub fn add_agent(settings_path: &Path, draft: &DraftAgentEntry) -> Result<(), CliError> {
150    let content = read_to_string(settings_path).map_err(CliError::IoError)?;
151    let slug_dir = settings_path.parent().unwrap().join("agents").join(draft.slug());
152    create_dir_all(&slug_dir).map_err(CliError::IoError)?;
153
154    let filename = format!("{}.md", draft.slug().to_uppercase());
155    write(slug_dir.join(filename), &draft.system_md_content).map_err(CliError::IoError)?;
156
157    if !draft.entry.mcp_servers.is_empty() {
158        write(slug_dir.join("mcp.json"), draft.to_mcp_json()).map_err(CliError::IoError)?;
159    }
160
161    let settings = draft.to_settings(&NewAgentMode::AddAgentToExistingProject, Some(&content));
162    let json = serde_json::to_string_pretty(&settings).expect("settings serialization cannot fail");
163    write(settings_path, json).map_err(CliError::IoError)?;
164
165    Ok(())
166}
167
168fn write_if_absent(path: &Path, content: &str) -> Result<(), CliError> {
169    if path.exists() {
170        return Ok(());
171    }
172    if let Some(parent) = path.parent() {
173        std::fs::create_dir_all(parent).map_err(CliError::IoError)?;
174    }
175    std::fs::write(path, content).map_err(CliError::IoError)?;
176    Ok(())
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182    use llm::ReasoningEffort;
183    use mcp_utils::client::config::RawMcpConfig;
184
185    fn default_draft() -> DraftAgentEntry {
186        let mut draft = DraftAgentEntry {
187            entry: AgentEntry {
188                name: "Default".to_string(),
189                description: "Default coding agent".to_string(),
190                user_invocable: true,
191                agent_invocable: true,
192                model: "anthropic:claude-sonnet-4-5".to_string(),
193                prompts: vec!["AGENTS.md".to_string()],
194                mcp_servers: vec!["coding".to_string(), "skills".to_string(), "tasks".to_string()],
195                ..AgentEntry::default()
196            },
197            system_md_content: String::new(),
198            system_md_edited: false,
199            workspace_mcp_configs: vec![],
200        };
201        draft.system_md_content = build_system_md(&draft);
202        draft
203    }
204
205    #[test]
206    fn scaffold_writes_all_files() {
207        let dir = tempfile::tempdir().unwrap();
208        scaffold(dir.path(), &default_draft()).unwrap();
209
210        assert!(dir.path().join(".aether/settings.json").exists());
211        assert!(dir.path().join(".aether/mcp.json").exists());
212        assert!(dir.path().join(".aether/DEFAULT.md").exists());
213        assert!(dir.path().join("AGENTS.md").exists());
214    }
215
216    #[test]
217    fn scaffold_settings_json_is_valid() {
218        let dir = tempfile::tempdir().unwrap();
219        scaffold(dir.path(), &default_draft()).unwrap();
220
221        let catalog = aether_project::load_agent_catalog(dir.path()).unwrap();
222        assert_eq!(catalog.all().len(), 1);
223        assert_eq!(catalog.all()[0].name, "Default");
224    }
225
226    #[test]
227    fn scaffold_mcp_json_is_valid() {
228        let dir = tempfile::tempdir().unwrap();
229        scaffold(dir.path(), &default_draft()).unwrap();
230
231        let mcp_path = dir.path().join(".aether/mcp.json");
232        let raw = RawMcpConfig::from_json_file(&mcp_path).unwrap();
233        assert_eq!(raw.servers.len(), 3);
234        assert!(raw.servers.contains_key("coding"));
235        assert!(raw.servers.contains_key("skills"));
236        assert!(raw.servers.contains_key("tasks"));
237    }
238
239    #[test]
240    fn scaffold_skips_existing_files() {
241        let dir = tempfile::tempdir().unwrap();
242        let agents_path = dir.path().join("AGENTS.md");
243        std::fs::write(&agents_path, "My custom prompt").unwrap();
244
245        scaffold(dir.path(), &default_draft()).unwrap();
246
247        let content = std::fs::read_to_string(&agents_path).unwrap();
248        assert_eq!(content, "My custom prompt");
249    }
250
251    #[test]
252    fn scaffold_creates_parent_dirs() {
253        let dir = tempfile::tempdir().unwrap();
254        let nested = dir.path().join("deep/nested/project");
255        scaffold(&nested, &default_draft()).unwrap();
256
257        assert!(nested.join(".aether/settings.json").exists());
258        assert!(nested.join(".aether/mcp.json").exists());
259        assert!(nested.join(".aether/DEFAULT.md").exists());
260        assert!(nested.join("AGENTS.md").exists());
261    }
262
263    #[test]
264    fn scaffold_is_idempotent() {
265        let dir = tempfile::tempdir().unwrap();
266        let draft = default_draft();
267        scaffold(dir.path(), &draft).unwrap();
268        scaffold(dir.path(), &draft).unwrap();
269        assert!(dir.path().join(".aether/settings.json").exists());
270    }
271
272    #[test]
273    fn scaffold_system_md_matches_draft_content() {
274        let dir = tempfile::tempdir().unwrap();
275        let draft = default_draft();
276        scaffold(dir.path(), &draft).unwrap();
277
278        let content = std::fs::read_to_string(dir.path().join(".aether/DEFAULT.md")).unwrap();
279        assert_eq!(content, draft.system_md_content);
280    }
281
282    #[test]
283    fn generated_settings_reference_aether_paths() {
284        let dir = tempfile::tempdir().unwrap();
285        scaffold(dir.path(), &default_draft()).unwrap();
286
287        let content = std::fs::read_to_string(dir.path().join(".aether/settings.json")).unwrap();
288        let settings: Settings = serde_json::from_str(&content).unwrap();
289
290        assert!(settings.prompts.is_empty());
291        assert!(settings.mcp_servers.is_empty());
292
293        assert_eq!(settings.agents.len(), 1);
294        assert!(settings.agents[0].prompts.contains(&".aether/DEFAULT.md".to_string()));
295        assert!(settings.agents[0].prompts.contains(&"AGENTS.md".to_string()));
296        assert!(settings.agents[0].mcp_servers.contains(&".aether/mcp.json".to_string()));
297    }
298
299    #[test]
300    fn scaffold_without_agents_md() {
301        let dir = tempfile::tempdir().unwrap();
302        let mut draft = default_draft();
303        draft.entry.prompts = vec![];
304        scaffold(dir.path(), &draft).unwrap();
305
306        assert!(!dir.path().join("AGENTS.md").exists());
307
308        let content = std::fs::read_to_string(dir.path().join(".aether/settings.json")).unwrap();
309        let settings: Settings = serde_json::from_str(&content).unwrap();
310        assert!(!settings.prompts.contains(&"AGENTS.md".to_string()));
311    }
312
313    #[test]
314    fn scaffold_includes_reasoning_effort() {
315        let dir = tempfile::tempdir().unwrap();
316        let mut draft = default_draft();
317        draft.entry.reasoning_effort = Some(ReasoningEffort::High);
318        scaffold(dir.path(), &draft).unwrap();
319
320        let catalog = aether_project::load_agent_catalog(dir.path()).unwrap();
321        assert_eq!(catalog.all()[0].reasoning_effort, Some(ReasoningEffort::High));
322    }
323
324    #[test]
325    fn scaffold_omits_reasoning_effort_when_none() {
326        let dir = tempfile::tempdir().unwrap();
327        scaffold(dir.path(), &default_draft()).unwrap();
328
329        let content = std::fs::read_to_string(dir.path().join(".aether/settings.json")).unwrap();
330        assert!(!content.contains("reasoningEffort"));
331    }
332
333    #[test]
334    fn scaffold_custom_servers() {
335        let dir = tempfile::tempdir().unwrap();
336        let mut draft = default_draft();
337        draft.entry.mcp_servers = vec!["coding".to_string(), "lsp".to_string()];
338        scaffold(dir.path(), &draft).unwrap();
339
340        let raw = RawMcpConfig::from_json_file(dir.path().join(".aether/mcp.json")).unwrap();
341        assert_eq!(raw.servers.len(), 2);
342        assert!(raw.servers.contains_key("coding"));
343        assert!(raw.servers.contains_key("lsp"));
344        assert!(!raw.servers.contains_key("tasks"));
345    }
346
347    #[test]
348    fn scaffold_no_servers_no_mcp_json_ref() {
349        let dir = tempfile::tempdir().unwrap();
350        let mut draft = default_draft();
351        draft.entry.mcp_servers = vec![];
352        scaffold(dir.path(), &draft).unwrap();
353
354        let content = std::fs::read_to_string(dir.path().join(".aether/settings.json")).unwrap();
355        let settings: Settings = serde_json::from_str(&content).unwrap();
356        assert!(settings.mcp_servers.is_empty());
357    }
358
359    fn researcher_draft() -> DraftAgentEntry {
360        let mut draft = default_draft();
361        draft.entry.name = "Researcher".to_string();
362        draft.entry.description = "Research agent".to_string();
363        draft.entry.mcp_servers = vec![];
364        draft.workspace_mcp_configs = vec![];
365        draft.system_md_content = build_system_md(&draft);
366        draft
367    }
368
369    #[test]
370    fn add_agent_appends_to_existing_settings() {
371        let dir = tempfile::tempdir().unwrap();
372        scaffold(dir.path(), &default_draft()).unwrap();
373
374        let settings_path = dir.path().join(".aether/settings.json");
375        add_agent(&settings_path, &researcher_draft()).unwrap();
376
377        let catalog = aether_project::load_agent_catalog(dir.path()).unwrap();
378        assert_eq!(catalog.all().len(), 2);
379        assert_eq!(catalog.all()[0].name, "Default");
380        assert_eq!(catalog.all()[1].name, "Researcher");
381    }
382
383    #[test]
384    fn add_agent_writes_per_agent_system_md() {
385        let dir = tempfile::tempdir().unwrap();
386        scaffold(dir.path(), &default_draft()).unwrap();
387
388        let settings_path = dir.path().join(".aether/settings.json");
389        let mut new_draft = researcher_draft();
390        new_draft.entry.prompts = vec![];
391        let expected_per_agent = new_draft.system_md_content.clone();
392        add_agent(&settings_path, &new_draft).unwrap();
393
394        let agent_md = dir.path().join(".aether/agents/researcher/RESEARCHER.md");
395        assert!(agent_md.exists());
396        assert_eq!(std::fs::read_to_string(agent_md).unwrap(), expected_per_agent);
397
398        let shared_md = dir.path().join(".aether/DEFAULT.md");
399        assert_eq!(std::fs::read_to_string(&shared_md).unwrap(), default_draft().system_md_content);
400    }
401
402    #[test]
403    fn add_agent_does_not_rewrite_shared_files() {
404        let dir = tempfile::tempdir().unwrap();
405        scaffold(dir.path(), &default_draft()).unwrap();
406
407        let shared_system = dir.path().join(".aether/DEFAULT.md");
408        std::fs::write(&shared_system, "custom shared prompt").unwrap();
409        let shared_mcp = dir.path().join(".aether/mcp.json");
410        let original_mcp = std::fs::read_to_string(&shared_mcp).unwrap();
411        let agents_md = dir.path().join("AGENTS.md");
412        std::fs::write(&agents_md, "custom agents md").unwrap();
413
414        let settings_path = dir.path().join(".aether/settings.json");
415        let mut new_draft = researcher_draft();
416        new_draft.entry.mcp_servers = vec!["coding".to_string()];
417        add_agent(&settings_path, &new_draft).unwrap();
418
419        assert_eq!(std::fs::read_to_string(&shared_system).unwrap(), "custom shared prompt");
420        assert_eq!(std::fs::read_to_string(&shared_mcp).unwrap(), original_mcp);
421        assert_eq!(std::fs::read_to_string(&agents_md).unwrap(), "custom agents md");
422    }
423
424    #[test]
425    fn add_agent_writes_per_agent_mcp_json() {
426        let dir = tempfile::tempdir().unwrap();
427        scaffold(dir.path(), &default_draft()).unwrap();
428
429        let settings_path = dir.path().join(".aether/settings.json");
430        let mut new_draft = researcher_draft();
431        new_draft.entry.prompts = vec![];
432        new_draft.entry.mcp_servers = vec!["coding".to_string(), "lsp".to_string()];
433        add_agent(&settings_path, &new_draft).unwrap();
434
435        let agent_mcp = dir.path().join(".aether/agents/researcher/mcp.json");
436        assert!(agent_mcp.exists());
437
438        let raw = RawMcpConfig::from_json_file(&agent_mcp).unwrap();
439        assert_eq!(raw.servers.len(), 2);
440        assert!(raw.servers.contains_key("coding"));
441        assert!(raw.servers.contains_key("lsp"));
442    }
443
444    #[test]
445    fn add_agent_agent_entry_references_local_assets() {
446        let dir = tempfile::tempdir().unwrap();
447        scaffold(dir.path(), &default_draft()).unwrap();
448
449        let settings_path = dir.path().join(".aether/settings.json");
450        let mut new_draft = researcher_draft();
451        new_draft.entry.user_invocable = false;
452        new_draft.entry.prompts = vec![];
453        new_draft.entry.mcp_servers = vec!["coding".to_string()];
454        add_agent(&settings_path, &new_draft).unwrap();
455
456        let content = std::fs::read_to_string(&settings_path).unwrap();
457        let settings: Settings = serde_json::from_str(&content).unwrap();
458        let researcher = &settings.agents[1];
459
460        assert_eq!(researcher.name, "Researcher");
461        assert!(!researcher.user_invocable);
462        assert!(researcher.agent_invocable);
463        assert!(researcher.prompts.contains(&".aether/agents/researcher/RESEARCHER.md".to_string()));
464        assert!(researcher.mcp_servers.contains(&".aether/agents/researcher/mcp.json".to_string()));
465    }
466
467    #[test]
468    fn generated_paths_scaffold() {
469        let draft = default_draft();
470        let paths = draft.generated_paths(&NewAgentMode::ScaffoldProject);
471        assert_eq!(paths.system_md, PathBuf::from(".aether/DEFAULT.md"));
472        assert_eq!(paths.mcp_json, PathBuf::from(".aether/mcp.json"));
473    }
474
475    #[test]
476    fn generated_paths_add_agent() {
477        let draft = default_draft();
478        let paths = draft.generated_paths(&NewAgentMode::AddAgentToExistingProject);
479        assert_eq!(paths.system_md, PathBuf::from(".aether/agents/default/DEFAULT.md"));
480        assert_eq!(paths.mcp_json, PathBuf::from(".aether/agents/default/mcp.json"));
481    }
482
483    #[test]
484    fn slug_from_name() {
485        let mut draft = default_draft();
486        draft.entry.name = "Codebase Explorer".to_string();
487        assert_eq!(draft.slug(), "codebase-explorer");
488    }
489
490    #[test]
491    fn build_system_md_uses_name_description_and_bash_block() {
492        let mut draft = default_draft();
493        draft.entry.name = "Researcher".to_string();
494        draft.entry.description = "Digs through the codebase".to_string();
495        let body = build_system_md(&draft);
496        assert!(body.starts_with("# Researcher\n"));
497        assert!(body.contains("Digs through the codebase"));
498        assert!(body.contains("## System Env"));
499        assert!(body.contains("Working directory: !`pwd`\\"));
500        assert!(body.contains("Platform: !`uname -s`\\"));
501        assert!(body.contains("Today's date: !`date +%Y-%m-%d`\\"));
502        assert!(body.contains("Git branch: !`git rev-parse --abbrev-ref HEAD`"));
503    }
504
505    #[test]
506    fn build_settings_json_scaffold_emits_all_selected_prompts() {
507        let mut draft = default_draft();
508        draft.entry.prompts = vec!["AGENTS.md".into(), "CLAUDE.md".into()];
509        let settings = draft.to_settings(&NewAgentMode::ScaffoldProject, None);
510
511        assert!(settings.prompts.is_empty());
512        assert!(settings.agents[0].prompts.contains(&".aether/DEFAULT.md".to_string()));
513        assert!(settings.agents[0].prompts.contains(&"AGENTS.md".to_string()));
514        assert!(settings.agents[0].prompts.contains(&"CLAUDE.md".to_string()));
515    }
516
517    #[test]
518    fn build_settings_json_add_agent_skips_inherited_prompts() {
519        let existing = serde_json::to_string_pretty(&Settings {
520            prompts: vec!["AGENTS.md".into()],
521            mcp_servers: vec![],
522            agents: vec![default_draft().to_agent_entry(&NewAgentMode::ScaffoldProject, &[])],
523        })
524        .unwrap();
525
526        let mut new_draft = researcher_draft();
527        new_draft.entry.prompts = vec!["AGENTS.md".into(), "CLAUDE.md".into()];
528        let settings = new_draft.to_settings(&NewAgentMode::AddAgentToExistingProject, Some(&existing));
529
530        let researcher = &settings.agents[1];
531        assert_eq!(researcher.name, "Researcher");
532        assert!(
533            !researcher.prompts.contains(&"AGENTS.md".to_string()),
534            "AGENTS.md is inherited from top-level prompts"
535        );
536        assert!(researcher.prompts.contains(&"CLAUDE.md".to_string()));
537    }
538
539    #[test]
540    fn scaffold_writes_agents_md_when_selected() {
541        let dir = tempfile::tempdir().unwrap();
542        let mut draft = default_draft();
543        draft.entry.prompts = vec!["AGENTS.md".into()];
544        scaffold(dir.path(), &draft).unwrap();
545        assert!(dir.path().join("AGENTS.md").exists());
546    }
547
548    #[test]
549    fn scaffold_includes_workspace_mcp_configs() {
550        let dir = tempfile::tempdir().unwrap();
551        std::fs::write(dir.path().join("mcp.json"), r#"{"servers":{}}"#).unwrap();
552
553        let mut draft = default_draft();
554        draft.workspace_mcp_configs = vec!["mcp.json".to_string()];
555        scaffold(dir.path(), &draft).unwrap();
556
557        let content = std::fs::read_to_string(dir.path().join(".aether/settings.json")).unwrap();
558        let settings: Settings = serde_json::from_str(&content).unwrap();
559
560        assert!(settings.mcp_servers.is_empty());
561        assert!(settings.agents[0].mcp_servers.contains(&"mcp.json".to_string()));
562    }
563
564    #[test]
565    fn add_agent_includes_workspace_mcp_configs() {
566        let dir = tempfile::tempdir().unwrap();
567        scaffold(dir.path(), &default_draft()).unwrap();
568
569        let settings_path = dir.path().join(".aether/settings.json");
570        let mut new_draft = researcher_draft();
571        new_draft.entry.mcp_servers = vec!["coding".to_string()];
572        new_draft.workspace_mcp_configs = vec![".mcp.json".to_string()];
573        add_agent(&settings_path, &new_draft).unwrap();
574
575        let content = std::fs::read_to_string(&settings_path).unwrap();
576        let settings: Settings = serde_json::from_str(&content).unwrap();
577        let researcher = &settings.agents[1];
578
579        assert!(researcher.mcp_servers.contains(&".mcp.json".to_string()));
580    }
581
582    #[test]
583    fn scaffold_never_writes_claude_or_gemini_md() {
584        let dir = tempfile::tempdir().unwrap();
585        let mut draft = default_draft();
586        draft.entry.prompts = vec!["AGENTS.md".into(), "CLAUDE.md".into(), "GEMINI.md".into()];
587        scaffold(dir.path(), &draft).unwrap();
588
589        assert!(dir.path().join("AGENTS.md").exists());
590        assert!(!dir.path().join("CLAUDE.md").exists());
591        assert!(!dir.path().join("GEMINI.md").exists());
592    }
593}