use super::server::LeanCtxServer;
#[derive(Clone, Debug, Default)]
pub(super) struct StartupContext {
pub(super) project_root: Option<String>,
pub(super) shell_cwd: Option<String>,
}
pub fn create_server() -> LeanCtxServer {
LeanCtxServer::new()
}
pub(super) fn has_project_marker(dir: &std::path::Path) -> bool {
crate::core::pathutil::has_project_marker(dir)
}
pub(super) fn is_suspicious_root(dir: &std::path::Path) -> bool {
crate::core::pathutil::is_agent_config_dir(dir)
}
pub(super) fn canonicalize_path(path: &std::path::Path) -> String {
crate::core::pathutil::safe_canonicalize_or_self(path)
.to_string_lossy()
.to_string()
}
pub(super) fn detect_startup_context(
explicit_project_root: Option<&str>,
startup_cwd: Option<&std::path::Path>,
) -> StartupContext {
let shell_cwd = startup_cwd.map(canonicalize_path);
let project_root = explicit_project_root
.map(|root| canonicalize_path(std::path::Path::new(root)))
.or_else(|| {
startup_cwd
.and_then(maybe_derive_project_root_from_absolute)
.map(|p| canonicalize_path(&p))
});
let shell_cwd = match (shell_cwd, project_root.as_ref()) {
(Some(cwd), Some(root))
if std::path::Path::new(&cwd).starts_with(std::path::Path::new(root)) =>
{
Some(cwd)
}
(_, Some(root)) => Some(root.clone()),
(cwd, None) => cwd,
};
StartupContext {
project_root,
shell_cwd,
}
}
pub(super) fn maybe_derive_project_root_from_absolute(
abs: &std::path::Path,
) -> Option<std::path::PathBuf> {
let mut cur = if abs.is_dir() {
abs.to_path_buf()
} else {
abs.parent()?.to_path_buf()
};
let mut best: Option<std::path::PathBuf> = None;
loop {
if has_project_marker(&cur) {
best = Some(crate::core::pathutil::safe_canonicalize_or_self(&cur));
}
if !cur.pop() {
break;
}
}
best
}
pub(crate) fn auto_consolidate_knowledge(project_root: &str) {
let _ = crate::tools::ctx_knowledge::consolidate_project_knowledge_with(
project_root,
&crate::core::consolidation_engine::ConsolidateOptions::incremental_auto(),
);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn derive_root_promotes_to_outermost_marker_in_monorepo() {
let tmp = tempfile::tempdir().unwrap();
let repo = tmp.path().join("repo");
let sub = repo.join("modules").join("payroll");
std::fs::create_dir_all(&sub).unwrap();
std::fs::create_dir(repo.join(".git")).unwrap();
std::fs::write(repo.join("package.json"), "{}").unwrap();
std::fs::write(sub.join("package.json"), "{}").unwrap();
let result = maybe_derive_project_root_from_absolute(&sub);
assert!(result.is_some(), "should find a root");
let root = result.unwrap();
let canonical_repo = crate::core::pathutil::safe_canonicalize_or_self(&repo);
assert_eq!(
root, canonical_repo,
"should return repo root, not sub-package"
);
}
#[test]
fn derive_root_returns_nearest_when_no_outer_marker() {
let tmp = tempfile::tempdir().unwrap();
let pkg = tmp.path().join("standalone");
std::fs::create_dir_all(&pkg).unwrap();
std::fs::write(pkg.join("package.json"), "{}").unwrap();
let result = maybe_derive_project_root_from_absolute(&pkg);
assert!(result.is_some());
let root = result.unwrap();
let canonical = crate::core::pathutil::safe_canonicalize_or_self(&pkg);
assert_eq!(root, canonical);
}
}