1mod checks;
4mod common;
5mod deprecations;
6mod fix;
7mod integrations;
8mod migrate;
9mod workspace_scope;
10
11#[allow(clippy::wildcard_imports)]
12use checks::*;
13#[allow(clippy::wildcard_imports)]
14use common::*;
15
16pub(super) const GREEN: &str = "\x1b[32m";
17
18pub(super) const RED: &str = "\x1b[31m";
19
20pub(super) const BOLD: &str = "\x1b[1m";
21
22pub(super) const RST: &str = "\x1b[0m";
23
24pub(super) const DIM: &str = "\x1b[2m";
25
26pub(super) const WHITE: &str = "\x1b[97m";
27
28pub(super) const YELLOW: &str = "\x1b[33m";
29
30pub(super) struct Outcome {
31 pub ok: bool,
32 pub line: String,
33}
34
35pub fn run() {
37 let mut passed = 0u32;
38 let total = 10u32;
39
40 println!("{BOLD}{WHITE}lean-ctx doctor{RST} {DIM}diagnostics{RST}\n");
41
42 let path_bin = resolve_lean_ctx_binary();
44 let also_in_path_dirs = path_in_path_env();
45 let bin_ok = path_bin.is_some() || also_in_path_dirs;
46 if bin_ok {
47 passed += 1;
48 }
49 let bin_line = if let Some(p) = path_bin {
50 format!("{BOLD}lean-ctx in PATH{RST} {WHITE}{}{RST}", p.display())
51 } else if also_in_path_dirs {
52 format!(
53 "{BOLD}lean-ctx in PATH{RST} {YELLOW}found via PATH walk (not resolved by `command -v`){RST}"
54 )
55 } else {
56 format!("{BOLD}lean-ctx in PATH{RST} {RED}not found{RST}")
57 };
58 print_check(&Outcome {
59 ok: bin_ok,
60 line: bin_line,
61 });
62
63 let ver = if bin_ok {
65 lean_ctx_version_from_path()
66 } else {
67 Outcome {
68 ok: false,
69 line: format!("{BOLD}lean-ctx version{RST} {RED}skipped (binary not in PATH){RST}"),
70 }
71 };
72 if ver.ok {
73 passed += 1;
74 }
75 print_check(&ver);
76
77 let lean_dir = crate::core::data_dir::lean_ctx_data_dir().ok();
79 let dir_outcome = match &lean_dir {
80 Some(p) if p.is_dir() => {
81 passed += 1;
82 Outcome {
83 ok: true,
84 line: format!(
85 "{BOLD}data dir{RST} {GREEN}exists{RST} {DIM}{}{RST}",
86 p.display()
87 ),
88 }
89 }
90 Some(p) => Outcome {
91 ok: false,
92 line: format!(
93 "{BOLD}data dir{RST} {RED}missing or not a directory{RST} {DIM}{}{RST}",
94 p.display()
95 ),
96 },
97 None => Outcome {
98 ok: false,
99 line: format!("{BOLD}data dir{RST} {RED}could not resolve data directory{RST}"),
100 },
101 };
102 print_check(&dir_outcome);
103
104 let stats_path = lean_dir.as_ref().map(|d| d.join("stats.json"));
106 let stats_outcome = match stats_path.as_ref().and_then(|p| std::fs::metadata(p).ok()) {
107 Some(m) if m.is_file() => {
108 passed += 1;
109 let size = m.len();
110 let path_display = if let Some(p) = stats_path.as_ref() {
111 p.display().to_string()
112 } else {
113 String::new()
114 };
115 Outcome {
116 ok: true,
117 line: format!(
118 "{BOLD}stats.json{RST} {GREEN}exists{RST} {WHITE}{size} bytes{RST} {DIM}{path_display}{RST}",
119 ),
120 }
121 }
122 Some(_m) => {
123 let path_display = if let Some(p) = stats_path.as_ref() {
124 p.display().to_string()
125 } else {
126 String::new()
127 };
128 Outcome {
129 ok: false,
130 line: format!(
131 "{BOLD}stats.json{RST} {RED}not a file{RST} {DIM}{path_display}{RST}",
132 ),
133 }
134 }
135 None => {
136 passed += 1;
137 Outcome {
138 ok: true,
139 line: match &stats_path {
140 Some(p) => format!(
141 "{BOLD}stats.json{RST} {YELLOW}not yet created{RST} {DIM}(will appear after first use) {}{RST}",
142 p.display()
143 ),
144 None => format!("{BOLD}stats.json{RST} {RED}could not resolve path{RST}"),
145 },
146 }
147 }
148 };
149 print_check(&stats_outcome);
150
151 let split_dirs = crate::core::data_dir::all_data_dirs_with_stats();
152 if split_dirs.len() >= 2 {
153 let dirs_str = split_dirs
154 .iter()
155 .map(|d| d.display().to_string())
156 .collect::<Vec<_>>()
157 .join(", ");
158 print_check(&Outcome {
159 ok: false,
160 line: format!(
161 "{BOLD}data dir split{RST} {RED}stats.json found in {count} locations{RST}: {dirs_str} {DIM}(run: lean-ctx setup to auto-merge){RST}",
162 count = split_dirs.len(),
163 ),
164 });
165 }
166
167 let config_path = lean_dir.as_ref().map(|d| d.join("config.toml"));
169 let config_outcome = match &config_path {
170 Some(p) => match std::fs::metadata(p) {
171 Ok(m) if m.is_file() => {
172 passed += 1;
173 Outcome {
174 ok: true,
175 line: format!(
176 "{BOLD}config.toml{RST} {GREEN}exists{RST} {DIM}{}{RST}",
177 p.display()
178 ),
179 }
180 }
181 Ok(_) => Outcome {
182 ok: false,
183 line: format!(
184 "{BOLD}config.toml{RST} {RED}exists but is not a regular file{RST} {DIM}{}{RST}",
185 p.display()
186 ),
187 },
188 Err(_) => {
189 passed += 1;
190 Outcome {
191 ok: true,
192 line: format!(
193 "{BOLD}config.toml{RST} {YELLOW}not found, using defaults{RST} {DIM}(expected at {}){RST}",
194 p.display()
195 ),
196 }
197 }
198 },
199 None => Outcome {
200 ok: false,
201 line: format!("{BOLD}config.toml{RST} {RED}could not resolve path{RST}"),
202 },
203 };
204 print_check(&config_outcome);
205
206 let allowlist_outcome = shell_allowlist_outcome();
208 if allowlist_outcome.ok {
209 passed += 1;
210 }
211 print_check(&allowlist_outcome);
212
213 let path_jail = path_jail_outcome();
215 if path_jail.ok {
216 passed += 1;
217 }
218 print_check(&path_jail);
219
220 let passthrough_outcome = compact_format_passthrough_outcome();
222 if passthrough_outcome.ok {
223 passed += 1;
224 }
225 print_check(&passthrough_outcome);
226
227 let perm_inherit_outcome = permission_inheritance_outcome();
229 if perm_inherit_outcome.ok {
230 passed += 1;
231 }
232 print_check(&perm_inherit_outcome);
233
234 let proxy_outcome = proxy_upstream_outcome();
236 if proxy_outcome.ok {
237 passed += 1;
238 }
239 print_check(&proxy_outcome);
240
241 let aliases = shell_aliases_outcome();
243 if aliases.ok {
244 passed += 1;
245 }
246 print_check(&aliases);
247
248 let mcp = mcp_config_outcome();
250 if mcp.ok {
251 passed += 1;
252 }
253 print_check(&mcp);
254
255 let workspace_scope = workspace_scope::workspace_scope_outcome(mcp.ok);
257 if let Some(ref ws) = workspace_scope {
258 if ws.ok {
259 passed += 1;
260 }
261 print_check(ws);
262 }
263
264 let skill = skill_files_outcome();
266 if skill.ok {
267 passed += 1;
268 }
269 print_check(&skill);
270
271 let port = port_3333_outcome();
273 if port.ok {
274 passed += 1;
275 }
276 print_check(&port);
277
278 #[cfg(unix)]
280 let daemon_outcome = {
281 let autostart = crate::daemon_autostart::is_installed();
282 let autostart_tag = if autostart {
285 match crate::daemon_autostart::service_file_path() {
286 Some(p) => format!(" {DIM}[autostart: on — {}]{RST}", p.display()),
287 None => format!(" {DIM}[autostart: on]{RST}"),
288 }
289 } else {
290 String::new()
291 };
292 if crate::daemon::is_daemon_running() {
293 let pid_path = crate::daemon::daemon_pid_path();
294 let pid_str = std::fs::read_to_string(&pid_path).unwrap_or_default();
295 Outcome {
296 ok: true,
297 line: format!(
298 "{BOLD}Daemon{RST} {GREEN}running (PID {}){RST}{autostart_tag}",
299 pid_str.trim()
300 ),
301 }
302 } else {
303 let hint = if autostart {
304 format!("{DIM}(autostart enabled, will restart){RST}")
305 } else {
306 format!("{DIM}(run: lean-ctx daemon start or: lean-ctx daemon enable){RST}")
307 };
308 Outcome {
309 ok: true,
310 line: format!("{BOLD}Daemon{RST} {YELLOW}not running{RST} {hint}"),
311 }
312 }
313 };
314 #[cfg(not(unix))]
315 let daemon_outcome = Outcome {
316 ok: true,
317 line: format!("{BOLD}Daemon{RST} {DIM}not supported on this platform{RST}"),
318 };
319 if daemon_outcome.ok {
320 passed += 1;
321 }
322 print_check(&daemon_outcome);
323
324 #[cfg(target_os = "linux")]
326 {
327 if let Ok(o) = std::process::Command::new("systemctl")
328 .args(["--user", "is-active", "lean-ctx-daemon.service"])
329 .output()
330 {
331 let state = String::from_utf8_lossy(&o.stdout).trim().to_string();
332 if state != "active" {
333 println!(
334 " {DIM} systemd unit state: {YELLOW}{state}{RST}{DIM} (expected: active){RST}"
335 );
336 }
337 }
338 let username = std::env::var("USER")
339 .or_else(|_| std::env::var("LOGNAME"))
340 .unwrap_or_else(|_| "$(whoami)".to_string());
341 if let Ok(o) = std::process::Command::new("loginctl")
342 .args(["show-user", &username, "-p", "Linger", "--value"])
343 .output()
344 {
345 let val = String::from_utf8_lossy(&o.stdout).trim().to_string();
346 if val != "yes" {
347 println!(
348 " {YELLOW}⚠{RST} Linger not enabled — daemon won't start at boot without login"
349 );
350 println!(" {DIM}Fix: loginctl enable-linger {username}{RST}");
351 }
352 }
353 }
354 if let Some(log_path) = crate::core::startup_guard::crash_loop_log_path(
355 crate::core::startup_guard::MCP_PROCESS_NAME,
356 ) {
357 if log_path.exists() {
358 if let Ok(contents) = std::fs::read_to_string(&log_path) {
359 let lines: Vec<&str> = contents.lines().collect();
360 if lines.len() >= 5 {
361 println!(
362 " {YELLOW}⚠{RST} Crash-loop log: {} recent restarts {DIM}({}){RST}",
363 lines.len(),
364 log_path.display()
365 );
366 }
367 }
368 }
369 }
370
371 let provider_outcome = provider_outcome();
373 print_check(&provider_outcome);
374
375 let bridge_outcomes = mcp_bridge_outcomes();
377 for bridge_check in &bridge_outcomes {
378 print_check(bridge_check);
379 }
380
381 let plan_outcomes = plan_mode_outcomes();
383 for plan_check in &plan_outcomes {
384 print_check(plan_check);
385 }
386
387 let session_outcome = session_state_outcome();
389 if session_outcome.ok {
390 passed += 1;
391 }
392 print_check(&session_outcome);
393
394 let docker_outcomes = docker_env_outcomes();
396 for docker_check in &docker_outcomes {
397 if docker_check.ok {
398 passed += 1;
399 }
400 print_check(docker_check);
401 }
402
403 let pi = pi_outcome();
405 if let Some(ref pi_check) = pi {
406 if pi_check.ok {
407 passed += 1;
408 }
409 print_check(pi_check);
410 }
411
412 let integrity = crate::core::integrity::check();
414 let integrity_ok = integrity.seed_ok && integrity.origin_ok;
415 if integrity_ok {
416 passed += 1;
417 }
418 let integrity_line = if integrity_ok {
419 format!(
420 "{BOLD}Build origin{RST} {GREEN}official{RST} {DIM}{}{RST}",
421 integrity.repo
422 )
423 } else {
424 format!(
425 "{BOLD}Build origin{RST} {RED}MODIFIED REDISTRIBUTION{RST} {YELLOW}pkg={}, repo={}{RST}",
426 integrity.pkg_name, integrity.repo
427 )
428 };
429 print_check(&Outcome {
430 ok: integrity_ok,
431 line: integrity_line,
432 });
433
434 let cache_safety = cache_safety_outcome();
436 if cache_safety.ok {
437 passed += 1;
438 }
439 print_check(&cache_safety);
440
441 let claude_truncation = claude_truncation_outcome();
443 if let Some(ref ct) = claude_truncation {
444 if ct.ok {
445 passed += 1;
446 }
447 print_check(ct);
448 }
449
450 let bm25_health = bm25_cache_health_outcome();
452 if bm25_health.ok {
453 passed += 1;
454 }
455 print_check(&bm25_health);
456
457 let semantic_index = semantic_index_outcome();
460 if let Some(ref check) = semantic_index {
461 if check.ok {
462 passed += 1;
463 }
464 print_check(check);
465 }
466
467 let archive_footprint = archive_footprint_outcome();
469 if archive_footprint.ok {
470 passed += 1;
471 }
472 print_check(&archive_footprint);
473
474 let mem_profile = memory_profile_outcome();
476 passed += 1;
477 print_check(&mem_profile);
478
479 let mem_cleanup = memory_cleanup_outcome();
481 passed += 1;
482 print_check(&mem_cleanup);
483
484 let ram_outcome = ram_guardian_outcome();
486 if ram_outcome.ok {
487 passed += 1;
488 }
489 print_check(&ram_outcome);
490
491 let cap_warnings = capacity_warnings();
493 for cw in &cap_warnings {
494 if cw.ok {
495 passed += 1;
496 }
497 print_check(cw);
498 }
499
500 let proxy_health = proxy_health_outcome();
502 if proxy_health.ok {
503 passed += 1;
504 }
505 print_check(&proxy_health);
506
507 let stale_env = stale_proxy_env_outcome();
509 if let Some(ref check) = stale_env {
510 if check.ok {
511 passed += 1;
512 }
513 print_check(check);
514 }
515
516 let subscription_conflict = proxy_subscription_conflict_outcome();
518 if let Some(ref check) = subscription_conflict {
519 if check.ok {
520 passed += 1;
521 }
522 print_check(check);
523 }
524
525 let deprecation_check = deprecations::deprecations_outcome();
528 if deprecation_check.ok {
529 passed += 1;
530 }
531 print_check(&deprecation_check);
532
533 println!("\n {BOLD}{WHITE}LSP (optional — for ctx_refactor):{RST}");
535 let lsp_outcomes = lsp_server_outcomes();
536 for lsp_check in &lsp_outcomes {
537 print_check(lsp_check);
538 }
539
540 let mut effective_total = total + 10; effective_total += 1; effective_total += 1; effective_total += 1; effective_total += 1; effective_total += 1; effective_total += cap_warnings.len() as u32;
547 effective_total += docker_outcomes.len() as u32;
548 if pi.is_some() {
549 effective_total += 1;
550 }
551 if claude_truncation.is_some() {
552 effective_total += 1;
553 }
554 if stale_env.is_some() {
555 effective_total += 1;
556 }
557 if subscription_conflict.is_some() {
558 effective_total += 1;
559 }
560 if workspace_scope.is_some() {
561 effective_total += 1;
562 }
563 if semantic_index.is_some() {
564 effective_total += 1;
565 }
566 let cfg = crate::core::config::Config::load();
568 let shadow_line = if cfg.shadow_mode {
569 format!("{BOLD}Shadow mode{RST} {GREEN}active{RST} {DIM}(native tools intercepted → ctx_*){RST}")
570 } else {
571 format!("{BOLD}Shadow mode{RST} {DIM}disabled{RST} {DIM}(enable: lean-ctx config set shadow_mode true){RST}")
572 };
573 println!(" {shadow_line}");
574
575 let tool_profile = crate::core::tool_profiles::ToolProfile::from_config(&cfg);
579 println!(
580 " {BOLD}Tool profile{RST} {WHITE}{tool_profile}{RST} {DIM}{} + ctx_call gateway{RST}",
581 tool_profile.description()
582 );
583
584 let cep = &crate::core::stats::load().cep;
588 let hit_ratio = if cep.total_cache_reads > 0 {
589 (cep.total_cache_hits as f64 / cep.total_cache_reads as f64) * 100.0
590 } else {
591 0.0
592 };
593 println!(
594 " {BOLD}Session cache{RST} {WHITE}{} sessions{RST} {DIM}{}/{} reads cached ({hit_ratio:.0}% hit) · prove: lean-ctx verify-cache{RST}",
595 cep.sessions, cep.total_cache_hits, cep.total_cache_reads
596 );
597
598 let needs_attention = effective_total.saturating_sub(passed);
599 println!();
600 println!(" {BOLD}{WHITE}Summary:{RST} {GREEN}{passed}{RST}{DIM}/{effective_total}{RST} checks passed");
601 if needs_attention > 0 {
602 println!(
603 " {YELLOW}{needs_attention} check(s) need attention.{RST} Auto-repair what's fixable: {BOLD}lean-ctx doctor --fix{RST}"
604 );
605 } else {
606 println!(" {GREEN}Everything looks good.{RST}");
607 }
608 println!(" {DIM}LSP servers are optional enhancements (not counted in score){RST}");
609 println!(" {DIM}{}{RST}", crate::core::integrity::origin_line());
610}
611
612pub fn run_compact() {
613 let (passed, total) = compact_score();
614 print_compact_status(passed, total);
615}
616
617pub fn run_cli(args: &[String]) -> i32 {
618 let (sub, rest) = match args.first().map(String::as_str) {
619 Some("integrations") => ("integrations", &args[1..]),
620 _ => ("", args),
621 };
622
623 let fix = rest.iter().any(|a| a == "--fix");
624 let json = rest.iter().any(|a| a == "--json");
625 let migrate_check = rest.iter().any(|a| a == "--migrate-check");
626 let help = rest.iter().any(|a| a == "--help" || a == "-h");
627
628 if help {
629 println!("Usage:");
630 println!(" lean-ctx doctor");
631 println!(" lean-ctx doctor integrations [--json]");
632 println!(" lean-ctx doctor --fix [--json]");
633 println!(" lean-ctx doctor --migrate-check [--json]");
634 return 0;
635 }
636
637 if migrate_check {
638 return migrate::run_migrate_check(json);
639 }
640
641 if sub == "integrations" {
642 if fix {
643 let _ = fix::run_fix(&fix::DoctorFixOptions { json: false });
644 }
645 return integrations::run_integrations(&integrations::IntegrationsOptions { json });
646 }
647
648 if !fix {
649 run();
650 return 0;
651 }
652
653 match fix::run_fix(&fix::DoctorFixOptions { json }) {
654 Ok(code) => code,
655 Err(e) => {
656 tracing::error!("doctor --fix failed: {e}");
657 2
658 }
659 }
660}
661
662pub fn compact_score() -> (u32, u32) {
663 let mut passed = 0u32;
664 let total = 6u32;
665
666 if resolve_lean_ctx_binary().is_some() || path_in_path_env() {
667 passed += 1;
668 }
669 let lean_dir = crate::core::data_dir::lean_ctx_data_dir().ok();
670 if lean_dir.as_ref().is_some_and(|p| p.is_dir()) {
671 passed += 1;
672 }
673 if lean_dir
674 .as_ref()
675 .map(|d| d.join("stats.json"))
676 .and_then(|p| std::fs::metadata(p).ok())
677 .is_some_and(|m| m.is_file())
678 {
679 passed += 1;
680 }
681 if shell_aliases_outcome().ok {
682 passed += 1;
683 }
684 if mcp_config_outcome().ok {
685 passed += 1;
686 }
687 if skill_files_outcome().ok {
688 passed += 1;
689 }
690
691 (passed, total)
692}
693
694pub(super) fn print_compact_status(passed: u32, total: u32) {
695 let status = if passed == total {
696 format!("{GREEN}✓ All {total} checks passed{RST}")
697 } else {
698 format!("{YELLOW}{passed}/{total} passed{RST} — run {BOLD}lean-ctx doctor{RST} for details")
699 };
700 println!(" {status}");
701}
702
703#[cfg(test)]
704mod tests {
705 use super::is_active_shell_impl;
706
707 fn make_capacity_check(name: &str, current: usize, limit: usize) -> Option<(bool, String)> {
711 if limit == 0 {
712 return None;
713 }
714 let pct = (current as f64 / limit as f64 * 100.0) as u32;
715 if pct > 100 {
716 Some((true, format!("{name}: {current}/{limit} ({pct}%)")))
717 } else if pct >= 80 {
718 Some((false, format!("{name}: {current}/{limit} ({pct}%)")))
719 } else {
720 None
721 }
722 }
723
724 #[test]
725 fn capacity_below_80_no_warning() {
726 assert!(make_capacity_check("facts", 100, 200).is_none());
727 assert!(make_capacity_check("facts", 159, 200).is_none());
728 }
729
730 #[test]
731 fn capacity_at_80_yellow_warning() {
732 let result = make_capacity_check("facts", 160, 200);
733 assert!(result.is_some());
734 let (critical, msg) = result.unwrap();
735 assert!(!critical);
736 assert!(msg.contains("160/200"));
737 assert!(msg.contains("80%"));
738 }
739
740 #[test]
741 fn capacity_at_92_yellow_warning() {
742 let result = make_capacity_check("facts", 185, 200);
743 assert!(result.is_some());
744 let (critical, msg) = result.unwrap();
745 assert!(!critical);
746 assert!(msg.contains("185/200"));
747 assert!(msg.contains("92%"));
748 }
749
750 #[test]
751 fn capacity_at_95_is_warning_not_critical() {
752 let result = make_capacity_check("facts", 190, 200);
753 assert!(result.is_some());
754 let (critical, msg) = result.unwrap();
755 assert!(!critical, "95% is full-but-healthy, not over cap");
756 assert!(msg.contains("190/200"));
757 assert!(msg.contains("95%"));
758 }
759
760 #[test]
761 fn capacity_at_100_is_warning_not_critical() {
762 let result = make_capacity_check("facts", 200, 200);
764 assert!(result.is_some());
765 let (critical, _) = result.unwrap();
766 assert!(!critical);
767 }
768
769 #[test]
770 fn capacity_over_100_is_critical() {
771 let result = make_capacity_check("facts", 206, 200);
774 assert!(result.is_some());
775 let (critical, msg) = result.unwrap();
776 assert!(critical);
777 assert!(msg.contains("206/200"));
778 assert!(msg.contains("103%"));
779 }
780
781 #[test]
782 fn capacity_zero_limit_skipped() {
783 assert!(make_capacity_check("facts", 50, 0).is_none());
784 }
785
786 #[test]
787 fn bashrc_active_on_non_windows_when_shell_empty() {
788 assert!(is_active_shell_impl("~/.bashrc", "", false, false));
789 }
790
791 #[test]
792 fn bashrc_not_active_on_windows_when_shell_empty() {
793 assert!(!is_active_shell_impl("~/.bashrc", "", true, false));
794 }
795
796 #[test]
797 fn bashrc_active_when_shell_contains_bash_on_linux() {
798 assert!(is_active_shell_impl(
799 "~/.bashrc",
800 "/usr/bin/bash",
801 false,
802 false
803 ));
804 }
805
806 #[test]
807 fn bashrc_not_active_on_windows_even_with_bash_in_shell_env() {
808 std::env::remove_var("BASH_VERSION");
811 assert!(!is_active_shell_impl(
812 "~/.bashrc",
813 "C:\\\\Program Files\\\\Git\\\\bin\\\\bash.exe",
814 true,
815 false,
816 ));
817 }
818
819 #[test]
820 fn bashrc_not_active_on_windows_powershell_even_with_bash_in_shell() {
821 assert!(!is_active_shell_impl(
822 "~/.bashrc",
823 "C:\\\\Program Files\\\\Git\\\\bin\\\\bash.exe",
824 true,
825 true,
826 ));
827 }
828
829 #[test]
830 fn bashrc_not_active_on_windows_powershell_with_empty_shell() {
831 assert!(!is_active_shell_impl("~/.bashrc", "", true, true));
832 }
833
834 #[test]
835 fn zshrc_unaffected_by_powershell_flag() {
836 assert!(is_active_shell_impl("~/.zshrc", "/bin/zsh", false, false));
837 assert!(is_active_shell_impl("~/.zshrc", "/bin/zsh", true, true));
838 }
839
840 #[test]
841 fn bashrc_not_active_on_windows_without_powershell_detection() {
842 std::env::remove_var("BASH_VERSION");
845 assert!(!is_active_shell_impl(
846 "~/.bashrc",
847 "/usr/bin/bash",
848 true,
849 false,
850 ));
851 }
852
853 #[test]
854 fn bashrc_active_on_linux() {
855 assert!(is_active_shell_impl("~/.bashrc", "/bin/bash", false, false));
856 assert!(is_active_shell_impl("~/.bashrc", "", false, false));
857 }
858}