use std::path::Path; use lean_ctx::core::knowledge::ProjectKnowledge; use lean_ctx::core::memory_policy::MemoryPolicy; #[test] fn ctx_knowledge_recall_is_budgeted_and_deterministic() { let _g = lean_ctx::core::data_dir::test_env_lock(); let tmp = tempfile::tempdir().expect("tempdir"); let data_dir = tmp.path().join("data"); std::fs::create_dir_all(&data_dir).expect("create data dir"); // TODO: Audit that the environment access only happens in single-threaded code. unsafe { std::env::set_var("LEAN_CTX_DATA_DIR", data_dir.to_string_lossy().to_string()) }; let project_root = tmp.path().join("proj"); std::fs::create_dir_all(&project_root).expect("create project root"); let project_root_str = project_root.to_string_lossy().to_string(); let policy = MemoryPolicy::default(); let mut knowledge = ProjectKnowledge::load_or_create(&project_root_str); for i in 0..50 { knowledge.remember( "architecture", &format!("k{i:02}"), &format!("v{i:02}"), "s1", 0.8, &policy, ); } knowledge.save().expect("save knowledge"); let out1 = lean_ctx::tools::ctx_knowledge::handle( &project_root_str, "recall", Some("architecture"), None, None, None, "s1", None, None, None, None, None, ); let out2 = lean_ctx::tools::ctx_knowledge::handle( &project_root_str, "recall", Some("architecture"), None, None, None, "s1", None, None, None, None, None, ); assert_eq!(out1, out2, "recall output must be deterministic"); assert!( out1.contains("showing 10/50"), "recall header must indicate truncation" ); let fact_lines = out1 .lines() .filter(|l| l.starts_with(" [architecture/")) .count(); assert!(fact_lines <= 10, "must not exceed recall budget"); // TODO: Audit that the environment access only happens in single-threaded code. unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR") }; } #[test] fn ctx_knowledge_export_is_file_backed_not_json_stdout() { let _g = lean_ctx::core::data_dir::test_env_lock(); let tmp = tempfile::tempdir().expect("tempdir"); let data_dir = tmp.path().join("data"); std::fs::create_dir_all(&data_dir).expect("create data dir"); // TODO: Audit that the environment access only happens in single-threaded code. unsafe { std::env::set_var("LEAN_CTX_DATA_DIR", data_dir.to_string_lossy().to_string()) }; let project_root = tmp.path().join("proj"); std::fs::create_dir_all(&project_root).expect("create project root"); let project_root_str = project_root.to_string_lossy().to_string(); let policy = MemoryPolicy::default(); let mut knowledge = ProjectKnowledge::load_or_create(&project_root_str); knowledge.remember("arch", "db", "MySQL", "s1", 0.8, &policy); knowledge.save().expect("save knowledge"); let out = lean_ctx::tools::ctx_knowledge::handle( &project_root_str, "export", None, None, None, None, "s1", None, None, None, None, None, ); assert!( out.starts_with("Export saved: "), "export must return a compact confirmation" ); assert!( !out.trim_start().starts_with('{'), "export must not print full JSON to stdout" ); let path_str = out .strip_prefix("Export saved: ") .and_then(|s| s.split_whitespace().next()) .expect("extract export path"); assert!(Path::new(path_str).exists(), "export file must exist"); // TODO: Audit that the environment access only happens in single-threaded code. unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR") }; } #[test] fn ctx_knowledge_feedback_persists_and_affects_quality_score() { let _g = lean_ctx::core::data_dir::test_env_lock(); let tmp = tempfile::tempdir().expect("tempdir"); let data_dir = tmp.path().join("data"); std::fs::create_dir_all(&data_dir).expect("create data dir"); // TODO: Audit that the environment access only happens in single-threaded code. unsafe { std::env::set_var("LEAN_CTX_DATA_DIR", data_dir.to_string_lossy().to_string()) }; let project_root = tmp.path().join("proj"); std::fs::create_dir_all(&project_root).expect("create project root"); let project_root_str = project_root.to_string_lossy().to_string(); let policy = MemoryPolicy::default(); let mut knowledge = ProjectKnowledge::load_or_create(&project_root_str); knowledge.remember("arch", "db", "MySQL", "s1", 0.8, &policy); knowledge.save().expect("save knowledge"); let before = ProjectKnowledge::load_or_create(&project_root_str); let before_fact = before .facts .iter() .find(|f| f.is_current() && f.category == "arch" && f.key == "db") .expect("fact exists"); let before_score = before_fact.quality_score(); let out = lean_ctx::tools::ctx_knowledge::handle( &project_root_str, "feedback", Some("arch"), Some("db"), Some("up"), None, "s2", None, None, None, None, None, ); assert!( out.contains("Feedback recorded"), "feedback output must confirm recording: {out}" ); let after = ProjectKnowledge::load_or_create(&project_root_str); let after_fact = after .facts .iter() .find(|f| f.is_current() && f.category == "arch" && f.key == "db") .expect("fact exists"); assert_eq!(after_fact.feedback_up, 1); assert_eq!(after_fact.feedback_down, 0); assert!(after_fact.last_feedback.is_some()); assert!( after_fact.quality_score() > before_score, "quality score should increase after positive feedback" ); // TODO: Audit that the environment access only happens in single-threaded code. unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR") }; } #[test] fn ctx_knowledge_relations_persist_and_render() { let _g = lean_ctx::core::data_dir::test_env_lock(); let tmp = tempfile::tempdir().expect("tempdir"); let data_dir = tmp.path().join("data"); std::fs::create_dir_all(&data_dir).expect("create data dir"); // TODO: Audit that the environment access only happens in single-threaded code. unsafe { std::env::set_var("LEAN_CTX_DATA_DIR", data_dir.to_string_lossy().to_string()) }; let project_root = tmp.path().join("proj"); std::fs::create_dir_all(&project_root).expect("create project root"); let project_root_str = project_root.to_string_lossy().to_string(); let policy = MemoryPolicy::default(); let mut knowledge = ProjectKnowledge::load_or_create(&project_root_str); knowledge.remember("arch", "db", "MySQL", "s1", 0.9, &policy); knowledge.remember("arch", "cache", "Redis", "s1", 0.9, &policy); knowledge.save().expect("save knowledge"); let out = lean_ctx::tools::ctx_knowledge::handle( &project_root_str, "relate", Some("arch"), Some("db"), Some("depends_on"), Some("arch/cache"), "s2", None, None, None, None, None, ); assert!(out.contains("Relation"), "relate must confirm: {out}"); let loaded = ProjectKnowledge::load_or_create(&project_root_str); let graph = lean_ctx::core::knowledge_relations::KnowledgeRelationGraph::load(&loaded.project_hash) .expect("relations graph should exist"); assert!( graph.edges.iter().any(|e| { e.from.category == "arch" && e.from.key == "db" && e.to.category == "arch" && e.to.key == "cache" && e.kind == lean_ctx::core::knowledge_relations::KnowledgeEdgeKind::DependsOn }), "expected depends_on edge in relations graph" ); let list = lean_ctx::tools::ctx_knowledge::handle( &project_root_str, "relations", Some("arch"), Some("db"), None, Some("out"), "s2", None, None, None, None, None, ); assert!( list.contains("depends_on") && list.contains("arch/cache"), "relations must include the edge: {list}" ); let diagram = lean_ctx::tools::ctx_knowledge::handle( &project_root_str, "relations_diagram", Some("arch"), Some("db"), None, Some("out"), "s2", None, None, None, None, None, ); assert!( diagram.contains("graph TD") && diagram.contains("depends_on"), "relations_diagram must return mermaid: {diagram}" ); let out2 = lean_ctx::tools::ctx_knowledge::handle( &project_root_str, "unrelate", Some("arch"), Some("db"), Some("depends_on"), Some("arch/cache"), "s2", None, None, None, None, None, ); assert!( out2.contains("removed"), "unrelate must confirm removal: {out2}" ); // TODO: Audit that the environment access only happens in single-threaded code. unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR") }; } #[test] fn ctx_knowledge_lifecycle_report_covers_all_layers() { let _g = lean_ctx::core::data_dir::test_env_lock(); let tmp = tempfile::tempdir().expect("tempdir"); let data_dir = tmp.path().join("data"); std::fs::create_dir_all(&data_dir).expect("create data dir"); // TODO: Audit that the environment access only happens in single-threaded code. unsafe { std::env::set_var("LEAN_CTX_DATA_DIR", data_dir.to_string_lossy().to_string()) }; let project_root = tmp.path().join("proj"); std::fs::create_dir_all(&project_root).expect("create project root"); let project_root_str = project_root.to_string_lossy().to_string(); let policy = MemoryPolicy::default(); let mut knowledge = ProjectKnowledge::load_or_create(&project_root_str); knowledge.remember("architecture", "auth", "JWT RS256", "s1", 0.9, &policy); knowledge.save().expect("save knowledge"); let out = lean_ctx::tools::ctx_knowledge::handle( &project_root_str, "lifecycle_report", None, None, None, None, "s1", None, None, None, None, None, ); for layer in [ "knowledge", "archives", "episodic", "procedural", "embeddings", ] { assert!( out.contains(layer), "lifecycle report must mention layer '{layer}': {out}" ); } assert!( out.contains("1 active"), "knowledge layer must show real counts: {out}" ); assert!( out.contains("Layer boundaries"), "report must document layer boundaries: {out}" ); // TODO: Audit that the environment access only happens in single-threaded code. unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR") }; } #[test] fn ctx_knowledge_recall_as_of_time_travels() { let _g = lean_ctx::core::data_dir::test_env_lock(); let tmp = tempfile::tempdir().expect("tempdir"); let data_dir = tmp.path().join("data"); std::fs::create_dir_all(&data_dir).expect("create data dir"); // TODO: Audit that the environment access only happens in single-threaded code. unsafe { std::env::set_var("LEAN_CTX_DATA_DIR", data_dir.to_string_lossy().to_string()) }; let project_root = tmp.path().join("proj"); std::fs::create_dir_all(&project_root).expect("create project root"); let project_root_str = project_root.to_string_lossy().to_string(); let policy = MemoryPolicy::default(); let mut knowledge = ProjectKnowledge::load_or_create(&project_root_str); knowledge.remember("arch", "db", "PostgreSQL", "s1", 0.95, &policy); knowledge.facts[0].confirmation_count = 3; let between = chrono::Utc::now(); std::thread::sleep(std::time::Duration::from_millis(10)); knowledge.remember("arch", "db", "MySQL", "s2", 0.9, &policy); knowledge.save().expect("save knowledge"); let past = lean_ctx::tools::ctx_knowledge::handle( &project_root_str, "recall", None, None, None, Some("db"), "s1", None, None, None, None, Some(&between.to_rfc3339()), ); assert!( past.contains("PostgreSQL") && !past.contains("MySQL"), "as_of recall must return the value valid at that time: {past}" ); let invalid = lean_ctx::tools::ctx_knowledge::handle( &project_root_str, "recall", None, None, None, Some("db"), "s1", None, None, None, None, Some("not-a-date"), ); assert!( invalid.contains("invalid as_of"), "invalid as_of must produce a clear error: {invalid}" ); // TODO: Audit that the environment access only happens in single-threaded code. unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR") }; }