lean_ctx_server/tools/
startup.rs1use super::server::LeanCtxServer;
3
4#[derive(Clone, Debug, Default)]
5pub(super) struct StartupContext {
6 pub(super) project_root: Option<String>,
7 pub(super) shell_cwd: Option<String>,
8}
9
10pub fn create_server() -> LeanCtxServer {
12 LeanCtxServer::new()
13}
14
15pub(super) fn has_project_marker(dir: &std::path::Path) -> bool {
16 lean_ctx::core::pathutil::has_project_marker(dir)
17}
18
19pub(crate) use lean_ctx::core::pathutil::is_suspicious_root;
20
21pub(super) fn canonicalize_path(path: &std::path::Path) -> String {
22 lean_ctx::core::pathutil::safe_canonicalize_or_self(path)
23 .to_string_lossy()
24 .to_string()
25}
26
27pub(super) fn detect_startup_context(
28 explicit_project_root: Option<&str>,
29 startup_cwd: Option<&std::path::Path>,
30) -> StartupContext {
31 let shell_cwd = startup_cwd.map(canonicalize_path);
32 let project_root = explicit_project_root
33 .map(|root| canonicalize_path(std::path::Path::new(root)))
34 .or_else(|| {
35 startup_cwd
36 .and_then(maybe_derive_project_root_from_absolute)
37 .map(|p| canonicalize_path(&p))
38 });
39
40 let shell_cwd = match (shell_cwd, project_root.as_ref()) {
41 (Some(cwd), Some(root))
42 if std::path::Path::new(&cwd).starts_with(std::path::Path::new(root)) =>
43 {
44 Some(cwd)
45 }
46 (_, Some(root)) => Some(root.clone()),
47 (cwd, None) => cwd,
48 };
49
50 StartupContext {
51 project_root,
52 shell_cwd,
53 }
54}
55
56pub(super) fn maybe_derive_project_root_from_absolute(
57 abs: &std::path::Path,
58) -> Option<std::path::PathBuf> {
59 let mut cur = if abs.is_dir() {
60 abs.to_path_buf()
61 } else {
62 abs.parent()?.to_path_buf()
63 };
64 let mut best: Option<std::path::PathBuf> = None;
69 loop {
70 if has_project_marker(&cur) {
71 best = Some(lean_ctx::core::pathutil::safe_canonicalize_or_self(&cur));
72 }
73 if !cur.pop() {
74 break;
75 }
76 }
77 best
78}
79
80pub(crate) fn auto_consolidate_knowledge(project_root: &str) {
86 let _ = crate::tools::ctx_knowledge::consolidate_project_knowledge_with(
87 project_root,
88 &lean_ctx::core::consolidation_engine::ConsolidateOptions::incremental_auto(),
89 );
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn derive_root_promotes_to_outermost_marker_in_monorepo() {
98 let tmp = tempfile::tempdir().unwrap();
102 let repo = tmp.path().join("repo");
103 let sub = repo.join("modules").join("payroll");
104 std::fs::create_dir_all(&sub).unwrap();
105 std::fs::create_dir(repo.join(".git")).unwrap();
107 std::fs::write(repo.join("package.json"), "{}").unwrap();
108 std::fs::write(sub.join("package.json"), "{}").unwrap();
110
111 let result = maybe_derive_project_root_from_absolute(&sub);
112 assert!(result.is_some(), "should find a root");
113 let root = result.unwrap();
114 let canonical_repo = lean_ctx::core::pathutil::safe_canonicalize_or_self(&repo);
115 assert_eq!(
116 root, canonical_repo,
117 "should return repo root, not sub-package"
118 );
119 }
120
121 #[test]
122 fn derive_root_returns_nearest_when_no_outer_marker() {
123 let tmp = tempfile::tempdir().unwrap();
126 let pkg = tmp.path().join("standalone");
127 std::fs::create_dir_all(&pkg).unwrap();
128 std::fs::write(pkg.join("package.json"), "{}").unwrap();
129
130 let result = maybe_derive_project_root_from_absolute(&pkg);
131 assert!(result.is_some());
132 let root = result.unwrap();
133 let canonical = lean_ctx::core::pathutil::safe_canonicalize_or_self(&pkg);
134 assert_eq!(root, canonical);
135 }
136}