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