use crate::{
core, doctor, heatmap, hook_handlers, report, setup, shell, status, token_report, uninstall,
};
mod analytics;
mod help;
mod lifecycle;
mod network;
mod server;
pub(crate) mod suggest;
#[allow(clippy::wildcard_imports)]
use analytics::*;
#[allow(clippy::wildcard_imports)]
use help::*;
#[allow(clippy::wildcard_imports)]
use lifecycle::*;
#[allow(clippy::wildcard_imports)]
use network::*;
#[allow(clippy::wildcard_imports)]
use server::*;
pub fn run() {
let mut args: Vec<String> = std::env::args().collect();
if args.get(1).is_some_and(|a| a == "(deleted)") {
args.remove(1);
}
if !is_server_mode(&args) {
restore_sigpipe_default();
}
let enters_mcp = args.len() == 1 || args.get(1).is_some_and(|a| a == "mcp");
if !enters_mcp {
crate::core::logging::init_logging();
}
if args.len() > 1 {
let rest = args[2..].to_vec();
match args[1].as_str() {
"-c" | "exec" => handle_exec(&args, &rest),
"-t" | "--track" => handle_track(&args),
"badge" => {
crate::cli::badge_cmd::cmd_badge(&rest);
return;
}
"shell" | "--shell" => {
shell::interactive();
return;
}
"gain" => {
cmd_gain(&rest);
return;
}
"spend" => {
cmd_spend(&rest);
return;
}
"savings" => {
cmd_savings(&rest);
return;
}
"learning" => {
cmd_learning(&rest);
return;
}
"conformance" | "selftest" => {
cmd_conformance(&rest);
return;
}
"health" => {
let code = crate::cli::health_cmd::cmd_health(&rest);
if code != 0 {
std::process::exit(code);
}
return;
}
"quality-lab" | "quality_lab" => {
let code = crate::cli::quality_lab_cmd::cmd_quality_lab(&rest);
if code != 0 {
std::process::exit(code);
}
return;
}
"billing" => {
cmd_billing(&rest);
return;
}
"finops" => {
cmd_finops(&rest);
return;
}
"roi" => {
super::cmd_roi(&rest);
return;
}
"output-savings" | "output_savings" => {
super::cmd_output_savings(&rest);
return;
}
"value-report" | "value_report" => {
super::cmd_value_report(&rest);
return;
}
"evidence-export" => {
super::cmd_evidence_export(&rest);
return;
}
"shadow" => {
super::cmd_shadow(&rest);
return;
}
"triage" => {
super::cmd_triage(&rest);
return;
}
"scenario" => {
if let Err(error) = crate::cli::scenario_cmd::cmd_scenario_from_cli(&rest) {
eprintln!("scenario: {error}");
std::process::exit(2);
}
return;
}
"measure" => {
super::cmd_measure(&rest);
return;
}
"token-report" | "report-tokens" => {
let code = token_report::run_cli(&rest);
if code != 0 {
std::process::exit(code);
}
return;
}
"pack" => {
crate::cli::cmd_pack(&rest);
return;
}
"policy" => {
crate::cli::cmd_policy(&rest);
return;
}
"plugin" | "plugins" => {
crate::cli::plugin_cmd::cmd_plugin(&rest);
return;
}
"addon" | "addons" => {
let code = crate::cli::addon_cmd::cmd_addon(&rest);
if code != 0 {
std::process::exit(code);
}
return;
}
"embeddings" => {
crate::cli::embeddings_cmd::cmd_embeddings(&rest);
return;
}
"model" => {
crate::cli::model_cmd::cmd_model(&rest);
return;
}
"enable-gpu" | "gpu" => {
core::updater::enable_gpu(&rest);
return;
}
"rules" => {
crate::cli::rules_cmd::cmd_rules(&rest);
return;
}
"proof" => {
crate::cli::cmd_proof(&rest);
return;
}
"prove" => {
crate::cli::cmd_prove(&rest);
return;
}
"snapshot" => {
crate::cli::cmd_snapshot(&rest);
return;
}
"verify" => {
crate::cli::cmd_verify(&rest);
return;
}
"eval" => {
crate::cli::eval_cmd::cmd_eval(&rest);
return;
}
"verify-cache" | "cache-selftest" => {
let code = crate::cli::verify_cache_cmd::cmd_verify_cache(&rest);
if code != 0 {
std::process::exit(code);
}
return;
}
"visualize" => {
super::cmd_visualize(&rest);
return;
}
"audit" => {
match rest.first().map(String::as_str) {
Some("evidence") => crate::cli::audit_report::cmd_evidence(&rest[1..]),
Some("determinism") => crate::cli::audit_report::cmd_determinism(&rest[1..]),
_ => println!("{}", crate::cli::audit_report::generate_report()),
}
return;
}
"compliance" => {
crate::cli::cmd_compliance(&rest);
return;
}
"agent" => {
crate::cli::cmd_agent(&rest);
return;
}
"instructions" => {
crate::cli::cmd_instructions(&rest);
return;
}
"index" => {
crate::cli::cmd_index(&rest);
return;
}
"semantic-search" | "search-code" => {
crate::cli::cmd_semantic_search(&rest);
core::stats::flush();
return;
}
"explore" => {
crate::cli::explore_cmd::cmd_explore(&rest);
core::stats::flush();
return;
}
"repomap" | "repo-map" => {
crate::cli::cmd_repomap(&rest);
core::stats::flush();
return;
}
"cep" => {
println!("{}", core::stats::format_cep_report());
return;
}
"demo" => {
super::cmd_demo(&rest);
return;
}
"dashboard" => {
cmd_dashboard(&rest);
return;
}
"team" => {
cmd_team(&rest);
return;
}
"provider" => {
cmd_provider(&rest);
return;
}
"serve" => {
cmd_serve(&rest);
return;
}
"watch" => {
cmd_watch(&rest);
return;
}
"proxy" => {
cmd_proxy(&rest);
return;
}
"daemon" => {
cmd_daemon(&rest);
return;
}
"init" => {
super::cmd_init(&rest);
return;
}
"setup" => {
handle_setup(&rest);
return;
}
"onboard" => {
handle_onboard(&rest);
return;
}
"install" => {
handle_install(&rest);
return;
}
"bootstrap" => {
handle_bootstrap(&rest);
return;
}
"wrap" => {
crate::cli::wrap_cmd::cmd_wrap(&rest);
return;
}
"unwrap" => {
crate::cli::wrap_cmd::cmd_unwrap(&rest);
return;
}
"status" => {
let code = status::run_cli(&rest);
if code != 0 {
std::process::exit(code);
}
return;
}
"cognitive" => {
crate::cli::cognitive::run();
return;
}
"read" => {
super::cmd_read(&rest);
core::tool_lifecycle::flush_all();
return;
}
"call" => {
super::cmd_call(&rest);
return;
}
"diff" => {
super::cmd_diff(&rest);
core::tool_lifecycle::flush_all();
return;
}
"grep" => {
super::cmd_grep(&rest);
core::tool_lifecycle::flush_all();
return;
}
"glob" => {
super::cmd_glob(&rest);
core::stats::flush();
return;
}
"find" => {
super::cmd_find(&rest);
core::tool_lifecycle::flush_all();
return;
}
"ls" => {
super::cmd_ls(&rest);
core::tool_lifecycle::flush_all();
return;
}
"deps" => {
super::cmd_deps(&rest);
core::tool_lifecycle::flush_all();
return;
}
"discover" => {
super::cmd_discover(&rest);
return;
}
"ghost" => {
super::cmd_ghost(&rest);
return;
}
"filter" => {
super::cmd_filter(&rest);
return;
}
"heatmap" => {
heatmap::cmd_heatmap(&rest);
return;
}
"graph" => {
cmd_graph(&rest);
return;
}
"smells" => {
cmd_smells(&rest);
return;
}
"session" => {
super::cmd_session_action(&rest);
return;
}
"ledger" => {
super::cmd_ledger(&rest);
return;
}
"ocla" => {
super::cmd_ocla(&rest);
return;
}
"control" | "context-control" => {
super::cmd_control(&rest);
return;
}
"plan" | "context-plan" => {
super::cmd_plan(&rest);
return;
}
"compile" | "context-compile" => {
super::cmd_compile(&rest);
return;
}
"import" => {
crate::cli::import_cmd::cmd_import(&rest);
}
"checkpoints" => {
crate::cli::checkpoint_cmd::cmd_checkpoints(&rest);
}
"knowledge" => {
super::cmd_knowledge(&rest);
return;
}
"skillify" => {
super::cmd_skillify(&rest);
return;
}
"summary" => {
super::cmd_summary(&rest);
return;
}
"overview" => {
super::cmd_overview(&rest);
return;
}
"compress" => {
super::cmd_compress(&rest);
return;
}
"wrapped" => {
eprintln!("'lean-ctx wrapped' has been removed. Use: lean-ctx gain --wrapped");
std::process::exit(1);
}
"sessions" | "session-store" => {
super::cmd_sessions(&rest);
return;
}
"benchmark" => {
if rest.is_empty()
|| rest.first().is_some_and(|arg| {
matches!(
arg.as_str(),
"--real"
| "--format"
| "--json"
| "--output"
| "-o"
| "--share"
| "--help"
| "-h"
)
})
{
super::cmd_benchmark_real(&rest);
} else {
super::cmd_benchmark(&rest);
}
return;
}
"compact" => {
cmd_compact(&rest);
return;
}
"profile" => {
super::cmd_profile(&rest);
return;
}
"tools" => {
if rest.first().map(String::as_str) == Some("health") {
super::cmd_tools_health(&rest[1..]);
return;
}
let mut forwarded = vec!["tools".to_string()];
forwarded.extend(rest.iter().cloned());
super::cmd_profile(&forwarded);
return;
}
"config" => {
super::cmd_config(&rest);
return;
}
"allow" => {
super::cmd_allow(&rest);
return;
}
"security" => {
super::cmd_security(&rest);
return;
}
"yolo" => {
super::cmd_yolo(&rest);
return;
}
"secure" | "lockdown" => {
super::cmd_secure(&rest);
return;
}
"trust" => {
super::cmd_trust(&rest);
return;
}
"untrust" => {
super::cmd_untrust(&rest);
return;
}
"stats" => {
super::cmd_stats(&rest);
return;
}
"introspect" => {
super::cmd_introspect(&rest);
return;
}
"cache" => {
super::cmd_cache(&rest);
return;
}
"theme" => {
super::cmd_theme(&rest);
return;
}
"enterprise" => {
super::cmd_enterprise(&rest);
return;
}
"tee" => {
super::cmd_tee(&rest);
return;
}
"terse" | "compression" => {
super::cmd_compression(&rest);
return;
}
"slow-log" => {
super::cmd_slow_log(&rest);
return;
}
"debug-log" => {
super::cmd_debug_log(&rest);
return;
}
"editor-signal" => {
let file = rest
.iter()
.position(|a| a == "--file")
.and_then(|i| rest.get(i + 1));
if let Some(path) = file {
if let Err(e) = core::editor_signal::record_focus(path) {
eprintln!("editor-signal: {e}");
std::process::exit(1);
}
} else {
eprintln!("usage: lean-ctx editor-signal --file <path>");
std::process::exit(2);
}
return;
}
"editor-session" => {
handle_editor_session(&rest);
return;
}
"update" | "--self-update" => {
core::updater::run(&rest);
return;
}
"restart" => {
cmd_restart();
return;
}
"stop" => {
cmd_stop();
return;
}
"dev-install" => {
cmd_dev_install();
return;
}
"codesign-setup" => {
cmd_codesign_setup();
return;
}
"doctor" => {
let code = doctor::run_cli(&rest);
if code != 0 {
std::process::exit(code);
}
return;
}
"harden" => {
super::harden::run(&rest);
return;
}
"export-rules" => {
super::export_rules::run(&rest);
return;
}
"completions" => {
super::completions::run_completions(&rest);
return;
}
"__complete" => {
#[allow(non_snake_case)]
super::completions::run___complete(&rest);
return;
}
"gotchas" | "bugs" => {
super::cloud::cmd_gotchas(&rest);
return;
}
"learn" => {
super::cmd_learn(&rest);
return;
}
"buddy" | "pet" => {
super::cloud::cmd_buddy(&rest);
return;
}
"hook" => {
hook_handlers::mark_hook_environment();
core::agent_runtime_env::capture();
let action = rest.first().map_or("help", std::string::String::as_str);
if !matches!(action, "rewrite" | "redirect" | "deny" | "vibe-pre-tool") {
hook_handlers::arm_watchdog(std::time::Duration::from_secs(5));
}
match action {
"rewrite" => hook_handlers::handle_rewrite(),
"redirect" => hook_handlers::handle_redirect(),
"deny" => hook_handlers::handle_deny(),
"read-dedup" => hook_handlers::handle_read_dedup(),
"observe" => hook_handlers::handle_observe(),
"post-commit" => hook_handlers::handle_post_commit(),
"copilot" => hook_handlers::handle_copilot(),
"codex-pretooluse" => hook_handlers::handle_codex_pretooluse(),
"codex-session-start" => hook_handlers::handle_codex_session_start(),
"rewrite-inline" => hook_handlers::handle_rewrite_inline(),
"vibe-pre-tool" => hook_handlers::handle_vibe_pre_tool(),
_ => {
eprintln!(
"Usage: lean-ctx hook <rewrite|redirect|deny|read-dedup|observe|post-commit|copilot|codex-pretooluse|codex-session-start|rewrite-inline|vibe-pre-tool>"
);
eprintln!(
" Internal commands used by agent hooks (Claude, Cursor, Copilot, etc.)"
);
std::process::exit(1);
}
}
return;
}
"report-issue" | "report" => {
report::run(&rest);
return;
}
"uninstall" => {
if rest.iter().any(|a| a == "--help" || a == "-h") {
uninstall::print_help();
return;
}
let dry_run = rest.iter().any(|a| a == "--dry-run");
let keep_config = rest.iter().any(|a| a == "--keep-config");
let keep_binary = rest.iter().any(|a| a == "--keep-binary");
uninstall::run(dry_run, keep_config, keep_binary);
return;
}
"raw" | "bypass" => handle_raw(&args, &rest),
"safety-levels" | "safety" => {
println!("{}", core::compression_safety::format_safety_table());
return;
}
"cheat" | "cheatsheet" | "cheat-sheet" => {
super::cmd_cheatsheet();
return;
}
"login" => {
super::cloud::cmd_login(&rest);
return;
}
"register" => {
super::cloud::cmd_register(&rest);
return;
}
"forgot-password" => {
super::cloud::cmd_forgot_password(&rest);
return;
}
"sync" => {
super::cloud::cmd_sync(&rest);
return;
}
"contribute" => {
super::cloud::cmd_contribute();
return;
}
"telemetry" => {
super::telemetry_cmd::cmd_telemetry(&rest);
return;
}
"cloud" => {
super::cloud::cmd_cloud(&rest);
return;
}
"upgrade" => {
super::cloud::cmd_upgrade();
return;
}
"--version" | "-V" => {
println!("{}", core::integrity::origin_line());
return;
}
"help" => {
let want_all = rest
.iter()
.any(|a| matches!(a.as_str(), "all" | "full" | "--all" | "-a"));
if want_all {
print_help();
} else {
print_help_concise();
}
return;
}
"--help" | "-h" => {
if rest
.iter()
.any(|a| matches!(a.as_str(), "all" | "full" | "--all" | "-a"))
{
print_help();
} else {
print_help_concise();
}
return;
}
"mcp" => {}
_ => {
let unknown = &args[1];
eprintln!("lean-ctx: unknown command '{unknown}'");
if let Some(suggestion) = suggest::closest_command(unknown) {
eprintln!(" did you mean '{suggestion}'?");
}
eprintln!(" run 'lean-ctx help' for the full command list");
std::process::exit(1);
}
}
}
if args.len() == 1 && std::io::IsTerminal::is_terminal(&std::io::stdin()) {
print_quickstart();
return;
}
if let Err(e) = run_mcp_server() {
tracing::error!("lean-ctx: {e}");
std::process::exit(1);
}
}
fn handle_editor_session(args: &[String]) {
let value_for = |flag: &str| {
args.iter()
.position(|argument| argument == flag)
.and_then(|index| args.get(index + 1))
};
let fields = (
value_for("--event"),
value_for("--source"),
value_for("--workspace"),
value_for("--session-id"),
);
let (Some(event), Some(source), Some(workspace), Some(session_id)) = fields else {
eprintln!(
"usage: lean-ctx editor-session --event <open|heartbeat|close> \
--source <editor> --workspace <path> --session-id <id>"
);
std::process::exit(2);
};
if let Err(error) = crate::core::agents::AgentRegistry::record_logical_session_presence(
event, source, workspace, session_id,
) {
eprintln!("editor-session: {error}");
std::process::exit(1);
}
}
fn print_setup_help() {
println!("Usage: lean-ctx setup [options]");
println!();
println!("Guided setup: shell hook, agent hooks/rules, MCP registrations.");
println!("Interactive by default; runs non-interactively without a TTY.");
println!();
println!("Options:");
println!(" --non-interactive No prompts; apply defaults");
println!(" --yes, -y Assume yes for all prompts");
println!(" --fix Repair an existing installation");
println!(" --json Machine-readable report (implies non-interactive)");
println!(" --no-auto-approve Skip auto-approve configuration");
println!(" --skip-rules Do not write agent rules files");
println!(" --help, -h Show this help (never runs setup)");
println!();
println!("See also: lean-ctx onboard (one-command setup), lean-ctx doctor");
}
fn is_server_mode(args: &[String]) -> bool {
args.len() == 1
|| args.get(1).is_some_and(|a| {
matches!(
a.as_str(),
"mcp" | "daemon" | "proxy" | "serve" | "watch" | "dashboard" | "gateway"
)
})
}
fn extract_and_apply_env_prefix(cmd: &str) -> String {
let mut rest = cmd.trim_start();
let mut last_rest = rest;
loop {
if let Some(eq_pos) = rest.find('=') {
let key = &rest[..eq_pos];
if !key.is_empty()
&& key
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || byte == b'_')
{
let after_eq = &rest[eq_pos + 1..];
if let Some(space_pos) = after_eq.find(' ') {
let value = &after_eq[..space_pos];
if key.starts_with("LEAN_CTX_") {
unsafe { std::env::set_var(key, value) };
}
rest = after_eq[space_pos..].trim_start();
last_rest = rest;
continue;
}
}
}
break;
}
last_rest.to_string()
}
fn handle_exec(args: &[String], rest: &[String]) -> ! {
let raw = rest.first().is_some_and(|a| a == "--raw");
let cmd_args = if raw { &args[3..] } else { &args[2..] };
let command = if cmd_args.len() == 1 {
cmd_args[0].clone()
} else {
shell::join_command(cmd_args)
};
let _stripped_command = extract_and_apply_env_prefix(&command);
core::agent_runtime_env::capture();
if crate::shell::reentry::should_pass_through() {
passthrough(&command);
}
if raw {
core::runtime_flags::enable_raw();
} else {
core::runtime_flags::enable_compress();
}
let code = shell::exec(&command);
core::tool_lifecycle::flush_all();
std::process::exit(code);
}
fn handle_track(args: &[String]) -> ! {
let cmd_args = &args[2..];
let code = if cmd_args.len() > 1 {
shell::exec_argv(cmd_args)
} else {
let command = cmd_args[0].clone();
if crate::shell::reentry::should_pass_through() {
passthrough(&command);
}
shell::exec(&command)
};
core::tool_lifecycle::flush_all();
std::process::exit(code);
}
fn handle_setup(rest: &[String]) {
if rest.iter().any(|a| a == "--help" || a == "-h") {
print_setup_help();
return;
}
const KNOWN: &[&str] = &[
"--non-interactive",
"--yes",
"-y",
"--fix",
"--json",
"--no-auto-approve",
"--skip-rules",
"--no-agent-aliases",
];
if let Some(unknown) = rest
.iter()
.find(|a| a.starts_with('-') && !KNOWN.contains(&a.as_str()))
{
eprintln!("setup: unknown flag '{unknown}'\n");
print_setup_help();
std::process::exit(2);
}
let non_interactive = rest.iter().any(|a| a == "--non-interactive");
let yes = rest.iter().any(|a| a == "--yes" || a == "-y");
let fix = rest.iter().any(|a| a == "--fix");
let json = rest.iter().any(|a| a == "--json");
let no_auto_approve = rest.iter().any(|a| a == "--no-auto-approve");
let skip_rules = rest.iter().any(|a| a == "--skip-rules");
let no_agent_aliases = rest.iter().any(|a| a == "--no-agent-aliases");
if no_agent_aliases {
let _ = crate::core::config::setter::set_by_key("skip_agent_aliases", "true");
}
if non_interactive || fix || json || yes {
let opts = setup::SetupOptions {
non_interactive,
yes,
fix,
json,
no_auto_approve,
skip_rules,
..Default::default()
};
run_setup_options(opts, json);
} else {
setup::run_setup();
}
}
fn handle_onboard(rest: &[String]) {
if rest.iter().any(|a| a == "--help" || a == "-h") {
println!("Usage: lean-ctx onboard [--no-agent-aliases]");
println!("Connect your AI tools with one command: detects installed");
println!("agents, installs hooks/rules/MCP registrations, verifies.");
println!();
println!(" --no-agent-aliases Do not install claude/codex/gemini shell aliases");
println!();
println!("Fine-grained control: lean-ctx setup --help");
return;
}
if rest.iter().any(|a| a == "--no-agent-aliases") {
let _ = crate::core::config::setter::set_by_key("skip_agent_aliases", "true");
}
setup::run_onboard();
}
fn handle_install(rest: &[String]) {
let repair = rest.iter().any(|a| a == "--repair" || a == "--fix");
let json = rest.iter().any(|a| a == "--json");
if !repair {
setup::run_setup();
return;
}
run_repair_setup(json);
}
fn handle_bootstrap(rest: &[String]) {
let json = rest.iter().any(|a| a == "--json");
run_repair_setup(json);
}
fn run_repair_setup(json: bool) {
let opts = setup::SetupOptions {
non_interactive: true,
yes: true,
fix: true,
json,
..Default::default()
};
run_setup_options(opts, json);
}
fn handle_raw(args: &[String], rest: &[String]) -> ! {
if rest.is_empty() {
eprintln!("Usage: lean-ctx raw \"command\"");
eprintln!(
"Runs the command with output passed through unchanged (no compression). \
The shell allowlist still applies."
);
std::process::exit(1);
}
let command = if rest.len() == 1 {
rest[0].clone()
} else {
shell::join_command(&args[2..])
};
core::runtime_flags::enable_raw();
let code = shell::exec(&command);
std::process::exit(code);
}
fn run_setup_options(opts: setup::SetupOptions, json: bool) {
match setup::run_setup_with_options(opts) {
Ok(report) => {
if json {
println!(
"{}",
serde_json::to_string_pretty(&report).unwrap_or_else(|_| "{}".to_string())
);
}
if !report.success {
std::process::exit(1);
}
}
Err(e) => {
eprintln!("{e}");
std::process::exit(1);
}
}
}
#[cfg(unix)]
fn restore_sigpipe_default() {
unsafe {
libc::signal(libc::SIGPIPE, libc::SIG_DFL);
}
}
#[cfg(not(unix))]
fn restore_sigpipe_default() {}
fn passthrough(command: &str) -> ! {
let (shell, flag) = shell::shell_and_flag();
let mut cmd = std::process::Command::new(&shell);
cmd.arg(&flag).arg(command);
shell::reentry::mark_child(&mut cmd);
shell::platform::apply_utf8_locale(&mut cmd);
let status = cmd.status().map_or(127, |s| s.code().unwrap_or(1));
std::process::exit(status);
}
pub(super) fn run_async<F: std::future::Future>(future: F) -> F::Output {
match tokio::runtime::Runtime::new() {
Ok(rt) => rt.block_on(future),
Err(e) => {
eprintln!("lean-ctx: failed to create async runtime: {e}");
std::process::exit(1);
}
}
}
#[cfg(test)]
mod tests {
use super::{
capability_banner, concise_help_text, is_server_mode, quickstart_text,
resolve_worker_threads,
};
use serial_test::serial;
fn args_of(parts: &[&str]) -> Vec<String> {
parts.iter().map(|s| (*s).to_string()).collect()
}
#[test]
fn server_modes_keep_ignored_sigpipe() {
for mode in ["mcp", "daemon", "proxy", "serve", "watch", "dashboard"] {
assert!(
is_server_mode(&args_of(&["lean-ctx", mode])),
"{mode} must count as server mode"
);
}
assert!(is_server_mode(&args_of(&["lean-ctx"])));
}
#[test]
fn cli_modes_restore_default_sigpipe() {
for mode in ["doctor", "-c", "status", "ls", "grep", "gain", "help"] {
assert!(
!is_server_mode(&args_of(&["lean-ctx", mode])),
"{mode} must count as CLI mode (SIGPIPE default)"
);
}
}
#[test]
fn quickstart_is_short_and_points_to_setup() {
let q = quickstart_text();
assert!(q.contains("lean-ctx wrap"), "quickstart must point to wrap");
assert!(q.contains("lean-ctx help"), "quickstart must point to help");
assert!(
q.lines().count() <= 16,
"quickstart should be short; got {} lines",
q.lines().count()
);
assert!(
!q.contains("COMMANDS:"),
"quickstart must not inline the full command reference"
);
}
#[test]
fn concise_help_is_short_and_points_to_full() {
let h = concise_help_text();
assert!(h.contains("lean-ctx wrap"), "must lead with wrap");
assert!(
h.contains("lean-ctx help all"),
"must point to full reference"
);
assert!(
h.contains("lean-ctx tools"),
"must surface the tools profile command"
);
assert!(
h.lines().count() <= 40,
"concise help should stay short; got {} lines",
h.lines().count()
);
assert!(
!h.contains("SHELL HOOK PATTERNS"),
"concise help must not inline the full pattern catalog"
);
}
#[test]
fn capability_banner_tool_count_matches_registry() {
let n = crate::server::registry::tool_count();
let banner = capability_banner();
assert!(
banner.contains(&format!("{n} MCP tools")),
"banner must show the live registry count ({n}); got: {banner}"
);
}
#[test]
#[serial]
fn worker_threads_default_clamps_low() {
let _env_lock = crate::core::data_dir::test_env_lock();
crate::test_env::remove_var("LEAN_CTX_WORKER_THREADS");
assert_eq!(resolve_worker_threads(1), 1);
}
#[test]
#[serial]
fn worker_threads_default_clamps_high() {
let _env_lock = crate::core::data_dir::test_env_lock();
crate::test_env::remove_var("LEAN_CTX_WORKER_THREADS");
assert_eq!(resolve_worker_threads(32), 4);
}
#[test]
#[serial]
fn worker_threads_default_passthrough() {
let _env_lock = crate::core::data_dir::test_env_lock();
crate::test_env::remove_var("LEAN_CTX_WORKER_THREADS");
assert_eq!(resolve_worker_threads(3), 3);
}
#[test]
#[serial]
fn worker_threads_env_override() {
let _env_lock = crate::core::data_dir::test_env_lock();
crate::test_env::set_var("LEAN_CTX_WORKER_THREADS", "12");
assert_eq!(resolve_worker_threads(2), 12);
crate::test_env::remove_var("LEAN_CTX_WORKER_THREADS");
}
#[test]
#[serial]
fn worker_threads_env_invalid_falls_back() {
let _env_lock = crate::core::data_dir::test_env_lock();
crate::test_env::set_var("LEAN_CTX_WORKER_THREADS", "not_a_number");
assert_eq!(resolve_worker_threads(3), 3);
crate::test_env::remove_var("LEAN_CTX_WORKER_THREADS");
}
}
#[cfg(test)]
mod env_prefix_tests {
use super::extract_and_apply_env_prefix;
use serial_test::serial;
#[test]
#[serial]
fn extracts_lean_ctx_disabled() {
let _env_lock = crate::core::data_dir::test_env_lock();
crate::test_env::remove_var("LEAN_CTX_DISABLED");
let result = extract_and_apply_env_prefix("LEAN_CTX_DISABLED=1 cargo test --lib");
assert_eq!(result, "cargo test --lib");
assert_eq!(std::env::var("LEAN_CTX_DISABLED").unwrap(), "1");
crate::test_env::remove_var("LEAN_CTX_DISABLED");
}
#[test]
#[serial]
fn ignores_non_lean_ctx_vars() {
let _env_lock = crate::core::data_dir::test_env_lock();
crate::test_env::remove_var("FOO");
let result = extract_and_apply_env_prefix("FOO=bar cargo test --lib");
assert_eq!(result, "cargo test --lib");
assert!(
std::env::var("FOO").is_err(),
"FOO must not be set in process env"
);
}
#[test]
#[serial]
fn extracts_multiple_lean_ctx_vars() {
let _env_lock = crate::core::data_dir::test_env_lock();
crate::test_env::remove_var("LEAN_CTX_DISABLED");
crate::test_env::remove_var("LEAN_CTX_ACTIVE");
let result =
extract_and_apply_env_prefix("LEAN_CTX_DISABLED=1 LEAN_CTX_ACTIVE=1 cargo test --lib");
assert_eq!(result, "cargo test --lib");
assert_eq!(std::env::var("LEAN_CTX_DISABLED").unwrap(), "1");
assert_eq!(std::env::var("LEAN_CTX_ACTIVE").unwrap(), "1");
crate::test_env::remove_var("LEAN_CTX_DISABLED");
crate::test_env::remove_var("LEAN_CTX_ACTIVE");
}
#[test]
#[serial]
fn no_prefix_returns_unchanged() {
let _env_lock = crate::core::data_dir::test_env_lock();
let result = extract_and_apply_env_prefix("cargo test --lib");
assert_eq!(result, "cargo test --lib");
}
#[test]
#[serial]
fn mixed_vars_extracts_only_lean_ctx() {
let _env_lock = crate::core::data_dir::test_env_lock();
crate::test_env::remove_var("FOO");
crate::test_env::remove_var("LEAN_CTX_DISABLED");
let result = extract_and_apply_env_prefix("FOO=bar LEAN_CTX_DISABLED=1 cargo test --lib");
assert_eq!(result, "cargo test --lib");
assert_eq!(std::env::var("LEAN_CTX_DISABLED").unwrap(), "1");
assert!(std::env::var("FOO").is_err());
crate::test_env::remove_var("LEAN_CTX_DISABLED");
}
}