Skip to main content

aether_cli/agent/new_agent_wizard/
draft_agent_entry.rs

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