26382a7ac6
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
JetBrains Plugin / Actionlint (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (rust) (push) Has been cancelled
JetBrains Plugin / Validation (push) Has been cancelled
JetBrains Plugin / Build (push) Has been cancelled
JetBrains Plugin / Test (push) Has been cancelled
Security Check / Security Scan (push) Has been cancelled
430 lines
13 KiB
Rust
430 lines
13 KiB
Rust
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") };
|
|
}
|