aether_cli/agent/new_agent_wizard/
draft_agent_entry.rs1use 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}