1use mermaid_domain::{InstructionSource, LoadedInstructions};
16use std::path::{Path, PathBuf};
17use std::time::UNIX_EPOCH;
18
19use mermaid_model::constants::{INSTRUCTIONS_TRUNCATION_MARKER, MAX_INSTRUCTIONS_BYTES};
20
21pub const INSTRUCTION_FILENAMES: &[&str] = &["AGENTS.md", "MERMAID.md"];
26
27const MAX_WALK_DEPTH: usize = 32;
30
31#[derive(Debug, PartialEq, Eq)]
34pub enum ReloadOutcome {
35 Unchanged,
37 LoadedFirst { tokens: usize },
40 Reloaded {
42 old_tokens: usize,
43 new_tokens: usize,
44 },
45 Removed,
47}
48
49#[must_use]
59pub fn find_instruction_files(start: &Path) -> Vec<PathBuf> {
60 find_instruction_files_bounded(start, home_dir_boundary().as_deref())
61}
62
63fn home_dir_boundary() -> Option<PathBuf> {
71 let home = std::env::var_os("HOME");
72 #[cfg(windows)]
73 let resolved = pick_home_boundary(
74 home.as_deref(),
75 std::env::var_os("USERPROFILE").as_deref(),
76 std::env::var_os("HOMEDRIVE").as_deref(),
77 std::env::var_os("HOMEPATH").as_deref(),
78 );
79 #[cfg(not(windows))]
81 let resolved = pick_home_boundary(home.as_deref(), None, None, None);
82 resolved
83}
84
85fn pick_home_boundary(
91 home: Option<&std::ffi::OsStr>,
92 userprofile: Option<&std::ffi::OsStr>,
93 homedrive: Option<&std::ffi::OsStr>,
94 homepath: Option<&std::ffi::OsStr>,
95) -> Option<PathBuf> {
96 fn nonempty(v: Option<&std::ffi::OsStr>) -> Option<&std::ffi::OsStr> {
97 v.filter(|s| !s.is_empty())
98 }
99 if let Some(home) = nonempty(home) {
100 return Some(PathBuf::from(home));
101 }
102 if let Some(profile) = nonempty(userprofile) {
103 return Some(PathBuf::from(profile));
104 }
105 if let (Some(drive), Some(path)) = (nonempty(homedrive), nonempty(homepath)) {
106 let mut combined = drive.to_os_string();
107 combined.push(path);
108 return Some(PathBuf::from(combined));
109 }
110 None
111}
112
113fn find_instruction_files_bounded(start: &Path, home: Option<&Path>) -> Vec<PathBuf> {
117 let mut current = start.to_path_buf();
118 for _ in 0..MAX_WALK_DEPTH {
119 if home == Some(current.as_path()) {
124 return Vec::new();
125 }
126 let found: Vec<PathBuf> = INSTRUCTION_FILENAMES
127 .iter()
128 .map(|name| current.join(name))
129 .filter(|candidate| candidate.is_file())
130 .collect();
131 if !found.is_empty() {
132 return found;
133 }
134 if current.join(".git").exists() {
138 return Vec::new();
139 }
140 match current.parent() {
142 Some(parent) if parent != current => current = parent.to_path_buf(),
143 _ => return Vec::new(),
144 }
145 }
146 Vec::new()
147}
148
149#[must_use]
153pub fn load_from_path(path: &Path) -> Option<LoadedInstructions> {
154 load_from_paths(&[path.to_path_buf()])
155}
156
157#[must_use]
160pub fn load_from_paths(paths: &[PathBuf]) -> Option<LoadedInstructions> {
161 let mut sources = Vec::new();
162 let mut bodies = Vec::new();
163 let mut total_byte_len = 0usize;
164 let mut latest_mtime = UNIX_EPOCH;
165
166 for path in paths {
167 let Ok(metadata) = std::fs::metadata(path) else {
173 continue;
174 };
175 let Ok(mtime) = metadata.modified() else {
176 continue;
177 };
178 let true_len = metadata.len() as usize;
179 let Ok((bytes, _truncated)) =
185 mermaid_model::utils::read_file_capped(path, MAX_INSTRUCTIONS_BYTES.saturating_add(1))
186 else {
187 continue;
188 };
189 let raw = String::from_utf8_lossy(&bytes).into_owned();
190 total_byte_len = total_byte_len.saturating_add(true_len);
191 if mtime > latest_mtime {
192 latest_mtime = mtime;
193 }
194 sources.push(InstructionSource {
195 path: path.to_path_buf(),
196 mtime,
197 byte_len: true_len,
198 });
199 bodies.push((path.to_path_buf(), raw));
200 }
201 let primary = sources.first()?.path.clone();
202 let sections = label_instruction_bodies(bodies);
203 let byte_len = total_byte_len;
204 let (content, truncated) = combine_and_cap_sections(sections, MAX_INSTRUCTIONS_BYTES);
205 Some(LoadedInstructions {
206 path: primary,
207 content,
208 mtime: latest_mtime,
209 byte_len,
210 truncated,
211 sources,
212 })
213}
214
215#[must_use]
222pub fn refresh(
223 current: Option<LoadedInstructions>,
224 cwd: &Path,
225) -> (Option<LoadedInstructions>, ReloadOutcome) {
226 match current {
227 Some(prior) => {
228 let paths: Vec<PathBuf> = if prior.sources.is_empty() {
230 vec![prior.path.clone()]
231 } else {
232 prior
233 .sources
234 .iter()
235 .map(|source| source.path.clone())
236 .collect()
237 };
238 let changed = if prior.sources.is_empty() {
239 std::fs::metadata(&prior.path)
240 .and_then(|m| m.modified())
241 .map(|mtime| mtime != prior.mtime)
242 .unwrap_or(true)
243 } else {
244 prior.sources.iter().any(|source| {
245 std::fs::metadata(&source.path)
246 .and_then(|m| m.modified())
247 .map(|mtime| mtime != source.mtime)
248 .unwrap_or(true)
249 })
250 };
251 if !changed {
252 return (Some(prior), ReloadOutcome::Unchanged);
253 }
254 let old_tokens = prior.approx_tokens();
255 match load_from_paths(&paths) {
256 Some(reloaded) => {
257 let new_tokens = reloaded.approx_tokens();
258 (
259 Some(reloaded),
260 ReloadOutcome::Reloaded {
261 old_tokens,
262 new_tokens,
263 },
264 )
265 },
266 None => {
267 (None, ReloadOutcome::Removed)
270 },
271 }
272 },
273 None => {
274 match load_from_paths(&find_instruction_files(cwd)) {
277 Some(loaded) => {
278 let tokens = loaded.approx_tokens();
279 (Some(loaded), ReloadOutcome::LoadedFirst { tokens })
280 },
281 None => (None, ReloadOutcome::Unchanged),
282 }
283 },
284 }
285}
286
287#[must_use]
296pub fn load_project_context(
297 cwd: &Path,
298 mem_cfg: &mermaid_domain::MemoryConfig,
299) -> (
300 Option<LoadedInstructions>,
301 Option<mermaid_domain::LoadedMemory>,
302 Option<mermaid_domain::LoadedSkills>,
303) {
304 let (instructions, _) = refresh(None, cwd);
305 let (memory, _) = crate::app::memory::refresh(None, cwd, mem_cfg);
306 let skills = crate::app::skills::load(cwd);
307 (instructions, memory, skills)
308}
309
310const INSTRUCTION_SECTION_SEPARATOR: &str = "\n\n---\n\n";
312
313fn label_instruction_bodies(bodies: Vec<(PathBuf, String)>) -> Vec<String> {
319 bodies
320 .into_iter()
321 .map(|(path, body)| {
322 let name = path
323 .file_name()
324 .and_then(|name| name.to_str())
325 .unwrap_or("instructions");
326 format!("# Project Instructions: {name}\n\n{body}")
327 })
328 .collect()
329}
330
331fn combine_and_cap_sections(sections: Vec<String>, cap: usize) -> (String, bool) {
349 const SEP: &str = INSTRUCTION_SECTION_SEPARATOR;
350 let marker = INSTRUCTIONS_TRUNCATION_MARKER;
351
352 let full = sections.join(SEP);
353 if full.len() <= cap {
354 return (full, false);
355 }
356 let Some(winner) = sections.last() else {
359 return (String::new(), false);
360 };
361 if winner.len() >= cap {
365 let cut = winner.floor_char_boundary(cap);
366 let mut clipped = winner[..cut].to_string();
367 clipped.push_str(marker);
368 return (clipped, true);
369 }
370 let earlier_len = sections.len() - 1;
375 let earlier_full = sections[..earlier_len].join(SEP);
376 let avail = cap.saturating_sub(winner.len() + SEP.len());
377 let cut = earlier_full.floor_char_boundary(avail);
378 if cut == 0 {
379 let mut body = winner.clone();
382 body.push_str(marker);
383 return (body, true);
384 }
385 let mut body = String::with_capacity(cut + marker.len() + SEP.len() + winner.len());
386 body.push_str(&earlier_full[..cut]);
387 body.push_str(marker);
388 body.push_str(SEP);
389 body.push_str(winner.as_str());
390 (body, true)
391}
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396 use std::fs;
397 use std::sync::Mutex;
398
399 static FS_LOCK: Mutex<()> = Mutex::new(());
402
403 fn temp_dir(name: &str) -> PathBuf {
404 let p = std::env::temp_dir().join(format!("mermaid_instructions_test_{name}"));
405 let _ = fs::remove_dir_all(&p);
406 fs::create_dir_all(&p).expect("create temp dir");
407 p
408 }
409
410 #[test]
411 fn find_instruction_files_finds_in_cwd() {
412 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
413 let dir = temp_dir("cwd");
414 fs::write(dir.join("MERMAID.md"), "rules").unwrap();
415 let found = find_instruction_files(&dir);
416 assert_eq!(found, vec![dir.join("MERMAID.md")]);
417 let _ = fs::remove_dir_all(&dir);
418 }
419
420 #[test]
421 fn find_instruction_files_loads_both_in_precedence_order() {
422 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
423 let dir = temp_dir("both");
424 fs::write(dir.join("AGENTS.md"), "agent rules").unwrap();
425 fs::write(dir.join("MERMAID.md"), "mermaid rules").unwrap();
426 let found = find_instruction_files(&dir);
427 assert_eq!(found, vec![dir.join("AGENTS.md"), dir.join("MERMAID.md")]);
429 let loaded = load_from_paths(&found).expect("load combined");
430 assert!(loaded.content.contains("# Project Instructions: AGENTS.md"));
431 assert!(loaded.content.contains("agent rules"));
432 assert!(
433 loaded
434 .content
435 .contains("# Project Instructions: MERMAID.md")
436 );
437 assert!(loaded.content.contains("mermaid rules"));
438 assert!(
440 loaded.content.find("mermaid rules") > loaded.content.find("agent rules"),
441 "MERMAID.md must come last so its guidance overrides AGENTS.md"
442 );
443 assert_eq!(loaded.sources.len(), 2);
444 let _ = fs::remove_dir_all(&dir);
445 }
446
447 #[test]
448 fn find_instruction_files_walks_up_to_git_root() {
449 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
450 let root = temp_dir("walkup");
451 fs::create_dir(root.join(".git")).unwrap();
452 fs::write(root.join("MERMAID.md"), "root rules").unwrap();
453 let sub = root.join("subdir/deeper");
454 fs::create_dir_all(&sub).unwrap();
455 let found = find_instruction_files(&sub);
456 assert_eq!(found, vec![root.join("MERMAID.md")]);
457 let _ = fs::remove_dir_all(&root);
458 }
459
460 #[test]
461 fn find_instruction_files_stops_at_git_root_without_file() {
462 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
463 let root = temp_dir("git_no_md");
464 fs::create_dir(root.join(".git")).unwrap();
465 let parent = root.parent().unwrap();
468 let above_md = parent.join("MERMAID.md");
469 fs::write(&above_md, "outside").unwrap();
470 let sub = root.join("subdir");
471 fs::create_dir_all(&sub).unwrap();
472 let found = find_instruction_files(&sub);
473 assert!(found.is_empty(), "walk must stop at .git boundary");
474 let _ = fs::remove_dir_all(&root);
475 let _ = fs::remove_file(&above_md);
476 }
477
478 #[test]
479 fn find_instruction_files_returns_empty_if_absent() {
480 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
481 let dir = temp_dir("absent");
482 fs::create_dir(dir.join(".git")).unwrap();
485 let found = find_instruction_files(&dir);
486 assert!(found.is_empty());
487 let _ = fs::remove_dir_all(&dir);
488 }
489
490 #[test]
491 fn find_instruction_files_stops_at_home_boundary() {
492 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
497 let home = temp_dir("home_boundary");
498 fs::write(home.join("AGENTS.md"), "home rules").unwrap();
499 let child = home.join("project");
500 fs::create_dir_all(&child).unwrap();
501 let found = find_instruction_files_bounded(&child, Some(home.as_path()));
504 assert!(
505 found.is_empty(),
506 "walk must stop at $HOME and not load ~/AGENTS.md, got {found:?}"
507 );
508 let _ = fs::remove_dir_all(&home);
509 }
510
511 #[test]
512 fn single_file_instructions_get_labeled_header() {
513 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
517 let dir = temp_dir("single_header");
518 fs::write(dir.join("MERMAID.md"), "do the thing").unwrap();
519 let loaded = load_from_path(&dir.join("MERMAID.md")).expect("load");
520 assert!(
521 loaded
522 .content
523 .starts_with("# Project Instructions: MERMAID.md"),
524 "single-file instructions must carry a labeled header, got: {:?}",
525 loaded.content
526 );
527 assert!(loaded.content.contains("do the thing"));
528 let _ = fs::remove_dir_all(&dir);
529 }
530
531 #[test]
532 fn load_from_path_truncates_oversized_file() {
533 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
534 let dir = temp_dir("oversized");
535 let path = dir.join("MERMAID.md");
536 let big = "a".repeat(50_000);
538 fs::write(&path, &big).unwrap();
539 let loaded = load_from_path(&path).expect("load");
540 assert!(loaded.truncated);
541 assert_eq!(loaded.byte_len, 50_000); assert!(loaded.content.ends_with(INSTRUCTIONS_TRUNCATION_MARKER));
543 assert_eq!(
545 loaded.content.len(),
546 MAX_INSTRUCTIONS_BYTES + INSTRUCTIONS_TRUNCATION_MARKER.len()
547 );
548 let _ = fs::remove_dir_all(&dir);
549 }
550
551 #[test]
552 fn load_from_path_returns_none_when_missing() {
553 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
554 let dir = temp_dir("missing");
555 assert!(load_from_path(&dir.join("nope.md")).is_none());
556 let _ = fs::remove_dir_all(&dir);
557 }
558
559 #[test]
560 fn refresh_returns_unchanged_when_mtime_stable() {
561 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
562 let dir = temp_dir("stable");
563 let path = dir.join("MERMAID.md");
564 fs::write(&path, "v1").unwrap();
565 let prior = load_from_path(&path).unwrap();
566 let (after, outcome) = refresh(Some(prior.clone()), &dir);
567 assert_eq!(outcome, ReloadOutcome::Unchanged);
568 assert!(after.is_some());
569 let _ = fs::remove_dir_all(&dir);
570 }
571
572 #[test]
573 fn refresh_returns_reloaded_on_content_change() {
574 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
575 let dir = temp_dir("changed");
576 let path = dir.join("MERMAID.md");
577 fs::write(&path, "v1").unwrap();
578 let prior = load_from_path(&path).unwrap();
579 std::thread::sleep(std::time::Duration::from_millis(1100));
582 fs::write(&path, "v2 longer content here").unwrap();
583 let (after, outcome) = refresh(Some(prior), &dir);
584 assert!(matches!(outcome, ReloadOutcome::Reloaded { .. }));
585 let content = after.unwrap().content;
586 assert!(content.contains("# Project Instructions: MERMAID.md"));
587 assert!(content.contains("v2 longer content here"));
588 let _ = fs::remove_dir_all(&dir);
589 }
590
591 #[test]
592 fn refresh_returns_removed_when_file_deleted() {
593 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
594 let dir = temp_dir("removed");
595 let path = dir.join("MERMAID.md");
596 fs::write(&path, "v1").unwrap();
597 let prior = load_from_path(&path).unwrap();
598 fs::remove_file(&path).unwrap();
599 let (after, outcome) = refresh(Some(prior), &dir);
600 assert_eq!(outcome, ReloadOutcome::Removed);
601 assert!(after.is_none());
602 let _ = fs::remove_dir_all(&dir);
603 }
604
605 #[test]
606 fn refresh_returns_loaded_first_on_initial_discovery() {
607 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
608 let dir = temp_dir("first");
609 fs::create_dir(dir.join(".git")).unwrap();
611 fs::write(dir.join("MERMAID.md"), "fresh").unwrap();
613 let (after, outcome) = refresh(None, &dir);
614 assert!(matches!(outcome, ReloadOutcome::LoadedFirst { .. }));
615 let content = after.unwrap().content;
616 assert!(content.contains("# Project Instructions: MERMAID.md"));
617 assert!(content.contains("fresh"));
618 let _ = fs::remove_dir_all(&dir);
619 }
620
621 #[test]
622 fn load_project_context_loads_instructions_synchronously() {
623 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
627 let dir = temp_dir("project_context");
628 fs::create_dir(dir.join(".git")).unwrap();
629 fs::write(dir.join("MERMAID.md"), "sync-loaded instructions").unwrap();
630 let (instructions, _memory, _skills) =
631 load_project_context(&dir, &mermaid_domain::MemoryConfig::default());
632 let content = instructions
633 .expect("instructions must load synchronously")
634 .content;
635 assert!(content.contains("sync-loaded instructions"));
636 let _ = fs::remove_dir_all(&dir);
637 }
638
639 #[test]
640 fn oversized_agents_does_not_drop_mermaid_winner() {
641 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
646 let dir = temp_dir("agents_huge");
647 fs::write(dir.join("AGENTS.md"), "A".repeat(60_000)).unwrap();
648 fs::write(dir.join("MERMAID.md"), "MERMAID_WINS_SENTINEL").unwrap();
649 let loaded =
650 load_from_paths(&[dir.join("AGENTS.md"), dir.join("MERMAID.md")]).expect("load");
651 assert!(loaded.truncated, "combined body exceeds the cap");
652 assert!(
653 loaded.content.contains("MERMAID_WINS_SENTINEL"),
654 "MERMAID.md (the winner) must survive the cap, not be dropped"
655 );
656 assert!(
657 loaded
658 .content
659 .contains("# Project Instructions: MERMAID.md")
660 );
661 assert!(loaded.content.contains(INSTRUCTIONS_TRUNCATION_MARKER));
662 let marker_at = loaded.content.find(INSTRUCTIONS_TRUNCATION_MARKER).unwrap();
665 let winner_at = loaded.content.find("MERMAID_WINS_SENTINEL").unwrap();
666 assert!(
667 winner_at > marker_at,
668 "MERMAID.md must come after the truncated AGENTS.md"
669 );
670 assert!(
672 loaded.content.len() <= MAX_INSTRUCTIONS_BYTES + INSTRUCTIONS_TRUNCATION_MARKER.len()
673 );
674 let _ = fs::remove_dir_all(&dir);
675 }
676
677 #[test]
678 fn combine_and_cap_protects_the_last_section() {
679 let lower = format!("# Project Instructions: AGENTS.md\n\n{}", "L".repeat(200));
682 let winner = "# Project Instructions: MERMAID.md\n\nWIN".to_string();
683 let (body, truncated) = combine_and_cap_sections(vec![lower, winner], 100);
684 assert!(truncated);
685 assert!(body.contains("WIN"), "winner survives the cap");
686 assert!(body.contains(INSTRUCTIONS_TRUNCATION_MARKER));
687 let marker_at = body.find(INSTRUCTIONS_TRUNCATION_MARKER).unwrap();
688 assert!(
689 body.find("WIN").unwrap() > marker_at,
690 "winner stays last so it overrides on conflict"
691 );
692 assert!(body.len() <= 100 + INSTRUCTIONS_TRUNCATION_MARKER.len());
693 }
694
695 #[test]
696 fn combine_and_cap_clips_winner_when_it_alone_overflows() {
697 let lower = "# Project Instructions: AGENTS.md\n\nlower-content".to_string();
700 let winner = format!("# Project Instructions: MERMAID.md\n\n{}", "W".repeat(300));
701 let (body, truncated) = combine_and_cap_sections(vec![lower, winner], 100);
702 assert!(truncated);
703 assert!(body.ends_with(INSTRUCTIONS_TRUNCATION_MARKER));
704 assert_eq!(body.len(), 100 + INSTRUCTIONS_TRUNCATION_MARKER.len());
705 assert!(
706 !body.contains("lower-content"),
707 "no room for the lower-precedence section"
708 );
709 }
710
711 #[test]
712 fn combine_and_cap_passes_through_when_it_fits() {
713 let a = "# Project Instructions: AGENTS.md\n\naye".to_string();
714 let b = "# Project Instructions: MERMAID.md\n\nbee".to_string();
715 let (body, truncated) = combine_and_cap_sections(vec![a, b], 10_000);
716 assert!(!truncated);
717 assert!(body.contains("aye") && body.contains("bee"));
718 assert!(
719 body.find("bee").unwrap() > body.find("aye").unwrap(),
720 "highest-precedence section stays last"
721 );
722 }
723
724 #[test]
725 fn load_from_paths_tolerates_a_missing_file() {
726 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
730 let dir = temp_dir("partial_load");
731 fs::write(dir.join("AGENTS.md"), "agent rules").unwrap();
732 let missing = dir.join("MERMAID.md"); let loaded = load_from_paths(&[dir.join("AGENTS.md"), missing])
734 .expect("AGENTS.md must still load when MERMAID.md is absent");
735 assert!(loaded.content.contains("# Project Instructions: AGENTS.md"));
736 assert!(loaded.content.contains("agent rules"));
737 assert_eq!(loaded.sources.len(), 1, "only the present file is a source");
738 assert_eq!(loaded.path, dir.join("AGENTS.md"));
739 let _ = fs::remove_dir_all(&dir);
740 }
741
742 #[test]
743 fn load_from_paths_none_when_all_missing() {
744 let _lock = FS_LOCK.lock().unwrap_or_else(|e| e.into_inner());
745 let dir = temp_dir("all_missing");
746 assert!(
747 load_from_paths(&[dir.join("AGENTS.md"), dir.join("MERMAID.md")]).is_none(),
748 "no present files => None"
749 );
750 let _ = fs::remove_dir_all(&dir);
751 }
752
753 #[test]
754 fn pick_home_boundary_resolves_windows_home_vars() {
755 use std::ffi::OsStr;
759 assert_eq!(
761 pick_home_boundary(
762 Some(OsStr::new("/home/me")),
763 Some(OsStr::new("C:\\Users\\me")),
764 None,
765 None
766 ),
767 Some(PathBuf::from("/home/me"))
768 );
769 assert_eq!(
771 pick_home_boundary(None, Some(OsStr::new("C:\\Users\\me")), None, None),
772 Some(PathBuf::from("C:\\Users\\me"))
773 );
774 assert_eq!(
776 pick_home_boundary(
777 None,
778 None,
779 Some(OsStr::new("C:")),
780 Some(OsStr::new("\\Users\\me"))
781 ),
782 Some(PathBuf::from("C:\\Users\\me"))
783 );
784 assert_eq!(
786 pick_home_boundary(Some(OsStr::new("")), Some(OsStr::new("")), None, None),
787 None
788 );
789 assert_eq!(
791 pick_home_boundary(None, None, Some(OsStr::new("C:")), None),
792 None
793 );
794 assert_eq!(
795 pick_home_boundary(None, None, None, Some(OsStr::new("\\Users\\me"))),
796 None
797 );
798 }
799}