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
766 lines
22 KiB
Rust
766 lines
22 KiB
Rust
#![allow(clippy::await_holding_lock)]
|
|
|
|
use serde_json::json;
|
|
|
|
fn setup_project() -> (tempfile::TempDir, lean_ctx::engine::ContextEngine) {
|
|
let dir = tempfile::tempdir().expect("tempdir");
|
|
let project = dir.path().join("project");
|
|
std::fs::create_dir_all(&project).expect("create project");
|
|
|
|
std::fs::create_dir_all(project.join("src")).expect("create src");
|
|
std::fs::write(
|
|
project.join("src/main.rs"),
|
|
r#"fn main() {
|
|
let config = load_config();
|
|
println!("Running with {:?}", config);
|
|
}
|
|
|
|
fn load_config() -> Config {
|
|
Config { debug: false, port: 8080 }
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct Config {
|
|
debug: bool,
|
|
port: u16,
|
|
}
|
|
|
|
fn helper_a() -> String {
|
|
"hello".to_string()
|
|
}
|
|
|
|
fn helper_b(x: i32) -> i32 {
|
|
x * 2
|
|
}
|
|
"#,
|
|
)
|
|
.expect("write main.rs");
|
|
|
|
std::fs::write(
|
|
project.join("src/lib.rs"),
|
|
r#"pub mod utils;
|
|
|
|
pub fn greet(name: &str) -> String {
|
|
format!("Hello, {name}!")
|
|
}
|
|
|
|
pub fn add(a: i32, b: i32) -> i32 {
|
|
a + b
|
|
}
|
|
"#,
|
|
)
|
|
.expect("write lib.rs");
|
|
|
|
std::fs::create_dir_all(project.join("src/utils")).expect("create utils");
|
|
std::fs::write(
|
|
project.join("src/utils/mod.rs"),
|
|
r#"pub fn format_bytes(n: u64) -> String {
|
|
if n < 1024 {
|
|
format!("{n} B")
|
|
} else if n < 1024 * 1024 {
|
|
format!("{:.1} KB", n as f64 / 1024.0)
|
|
} else {
|
|
format!("{:.1} MB", n as f64 / (1024.0 * 1024.0))
|
|
}
|
|
}
|
|
"#,
|
|
)
|
|
.expect("write utils/mod.rs");
|
|
|
|
std::fs::write(
|
|
project.join("Cargo.toml"),
|
|
r#"[package]
|
|
name = "test-project"
|
|
version = "0.1.0"
|
|
edition = "2021"
|
|
"#,
|
|
)
|
|
.expect("write Cargo.toml");
|
|
|
|
std::fs::write(
|
|
project.join("README.md"),
|
|
"# Test Project\nA sample project.\n",
|
|
)
|
|
.expect("write README.md");
|
|
|
|
let data_dir = dir.path().join("data");
|
|
std::fs::create_dir_all(&data_dir).expect("create data dir");
|
|
unsafe {
|
|
std::env::set_var("LEAN_CTX_DATA_DIR", data_dir.to_string_lossy().to_string());
|
|
}
|
|
|
|
let engine = lean_ctx::engine::ContextEngine::with_project_root(&project);
|
|
(dir, engine)
|
|
}
|
|
|
|
// ── Phase 1: Setup & Orientation ───────────────────────────
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase1_session_task_and_overview() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let out = engine
|
|
.call_tool_text(
|
|
"ctx_session",
|
|
Some(json!({"action": "task", "content": "Implement caching layer"})),
|
|
)
|
|
.await
|
|
.expect("session task");
|
|
assert!(
|
|
out.contains("task") || out.contains("Task") || out.contains("set"),
|
|
"task output: {out}"
|
|
);
|
|
|
|
let overview = engine
|
|
.call_tool_text("ctx_overview", None)
|
|
.await
|
|
.expect("overview");
|
|
assert!(
|
|
overview.contains("src") || overview.contains("project"),
|
|
"overview should contain project structure: {overview}"
|
|
);
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase1_tree_depth() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let tree = engine
|
|
.call_tool_text("ctx_tree", Some(json!({"depth": 3})))
|
|
.await
|
|
.expect("tree");
|
|
assert!(tree.contains("src"), "tree should show src: {tree}");
|
|
assert!(
|
|
tree.contains("Cargo.toml") || tree.contains("main.rs"),
|
|
"tree should show files: {tree}"
|
|
);
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
// ── Phase 2: Codebase Research (Read Modes) ────────────────
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase2_read_full_and_cache_stub() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let project = _dir.path().join("project");
|
|
let main_path = project.join("src/main.rs").to_string_lossy().to_string();
|
|
|
|
let full1 = engine
|
|
.call_tool_text("ctx_read", Some(json!({"path": main_path, "mode": "full"})))
|
|
.await
|
|
.expect("full read 1");
|
|
assert!(
|
|
full1.contains("load_config"),
|
|
"full read should contain fn body: {full1}"
|
|
);
|
|
|
|
let full2 = engine
|
|
.call_tool_text("ctx_read", Some(json!({"path": main_path, "mode": "full"})))
|
|
.await
|
|
.expect("full read 2");
|
|
assert!(
|
|
full2.len() <= full1.len(),
|
|
"re-read should be stub or same size (full1={}, full2={})",
|
|
full1.len(),
|
|
full2.len()
|
|
);
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase2_read_signatures() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let project = _dir.path().join("project");
|
|
let main_path = project.join("src/main.rs").to_string_lossy().to_string();
|
|
|
|
let sigs = engine
|
|
.call_tool_text(
|
|
"ctx_read",
|
|
Some(json!({"path": main_path, "mode": "signatures"})),
|
|
)
|
|
.await
|
|
.expect("signatures read");
|
|
assert!(
|
|
sigs.contains("fn main") || sigs.contains("load_config") || sigs.contains("helper_a"),
|
|
"signatures should contain fn names: {sigs}"
|
|
);
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase2_read_map() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let project = _dir.path().join("project");
|
|
let lib_path = project.join("src/lib.rs").to_string_lossy().to_string();
|
|
|
|
let map = engine
|
|
.call_tool_text("ctx_read", Some(json!({"path": lib_path, "mode": "map"})))
|
|
.await
|
|
.expect("map read");
|
|
assert!(!map.is_empty(), "map should not be empty");
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase2_read_lines_range() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let project = _dir.path().join("project");
|
|
let main_path = project.join("src/main.rs").to_string_lossy().to_string();
|
|
|
|
let lines = engine
|
|
.call_tool_text(
|
|
"ctx_read",
|
|
Some(json!({"path": main_path, "mode": "lines:1-5"})),
|
|
)
|
|
.await
|
|
.expect("lines read");
|
|
assert!(
|
|
lines.contains("fn main"),
|
|
"lines 1-5 should contain fn main: {lines}"
|
|
);
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase2_read_auto() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let project = _dir.path().join("project");
|
|
let main_path = project.join("src/main.rs").to_string_lossy().to_string();
|
|
|
|
let auto = engine
|
|
.call_tool_text("ctx_read", Some(json!({"path": main_path, "mode": "auto"})))
|
|
.await
|
|
.expect("auto read");
|
|
assert!(!auto.is_empty(), "auto mode should return content");
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase2_read_aggressive() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let project = _dir.path().join("project");
|
|
let main_path = project.join("src/main.rs").to_string_lossy().to_string();
|
|
|
|
let aggressive = engine
|
|
.call_tool_text(
|
|
"ctx_read",
|
|
Some(json!({"path": main_path, "mode": "aggressive"})),
|
|
)
|
|
.await
|
|
.expect("aggressive read");
|
|
assert!(
|
|
!aggressive.is_empty(),
|
|
"aggressive mode should return content"
|
|
);
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase2_read_diff_after_edit() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let project = _dir.path().join("project");
|
|
let main_path = project.join("src/main.rs").to_string_lossy().to_string();
|
|
|
|
let _ = engine
|
|
.call_tool_text("ctx_read", Some(json!({"path": main_path, "mode": "full"})))
|
|
.await
|
|
.expect("initial read");
|
|
|
|
let content = std::fs::read_to_string(project.join("src/main.rs")).unwrap();
|
|
let modified = content.replace("debug: false", "debug: true");
|
|
std::fs::write(project.join("src/main.rs"), &modified).unwrap();
|
|
|
|
let diff = engine
|
|
.call_tool_text("ctx_read", Some(json!({"path": main_path, "mode": "diff"})))
|
|
.await
|
|
.expect("diff read");
|
|
assert!(
|
|
diff.contains("debug")
|
|
|| diff.contains("changed")
|
|
|| diff.contains("modified")
|
|
|| diff.contains("true"),
|
|
"diff should show changes: {diff}"
|
|
);
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
// ── Phase 3: Code Search ───────────────────────────────────
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase3_search_regex() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let out = engine
|
|
.call_tool_text("ctx_search", Some(json!({"pattern": "fn main"})))
|
|
.await
|
|
.expect("search");
|
|
assert!(
|
|
out.contains("main") || out.contains("src/main.rs"),
|
|
"search should find fn main: {out}"
|
|
);
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase3_search_with_path_filter() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let out = engine
|
|
.call_tool_text(
|
|
"ctx_search",
|
|
Some(json!({"pattern": "pub fn", "path": "src/utils"})),
|
|
)
|
|
.await
|
|
.expect("search with path");
|
|
assert!(
|
|
out.contains("format_bytes"),
|
|
"filtered search should find format_bytes: {out}"
|
|
);
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
// ── Phase 4: Shell & Execution ─────────────────────────────
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase4_shell_echo() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let out = engine
|
|
.call_tool_text(
|
|
"ctx_shell",
|
|
Some(json!({"command": "echo hello-from-shell"})),
|
|
)
|
|
.await
|
|
.expect("shell echo");
|
|
assert!(
|
|
out.contains("hello-from-shell"),
|
|
"shell should contain echo output: {out}"
|
|
);
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase4_shell_raw_mode() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let out = engine
|
|
.call_tool_text(
|
|
"ctx_shell",
|
|
Some(json!({"command": "echo raw-test-value", "raw": true})),
|
|
)
|
|
.await
|
|
.expect("shell raw");
|
|
assert!(
|
|
out.contains("raw-test-value"),
|
|
"raw shell should contain output: {out}"
|
|
);
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
// ── Phase 5: Edit & Iteration ──────────────────────────────
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase5_edit_and_verify() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let project = _dir.path().join("project");
|
|
let lib_path = project.join("src/lib.rs").to_string_lossy().to_string();
|
|
|
|
let edit_result = engine
|
|
.call_tool_text(
|
|
"ctx_edit",
|
|
Some(json!({
|
|
"path": lib_path,
|
|
"old_string": "pub fn greet(name: &str) -> String {\n format!(\"Hello, {name}!\")\n}",
|
|
"new_string": "pub fn greet(name: &str) -> String {\n format!(\"Hi there, {name}!\")\n}",
|
|
})),
|
|
)
|
|
.await
|
|
.expect("edit");
|
|
assert!(
|
|
edit_result.contains("applied")
|
|
|| edit_result.contains("ok")
|
|
|| edit_result.contains("✓")
|
|
|| edit_result.contains("success")
|
|
|| !edit_result.is_empty(),
|
|
"edit should succeed: {edit_result}"
|
|
);
|
|
|
|
let content = std::fs::read_to_string(project.join("src/lib.rs")).unwrap();
|
|
assert!(
|
|
content.contains("Hi there"),
|
|
"file should contain edited text"
|
|
);
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
// ── Phase 6: Context Management ────────────────────────────
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase6_compress_and_cache_status() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let project = _dir.path().join("project");
|
|
let main_path = project.join("src/main.rs").to_string_lossy().to_string();
|
|
let _ = engine
|
|
.call_tool_text("ctx_read", Some(json!({"path": main_path, "mode": "full"})))
|
|
.await
|
|
.expect("prime cache");
|
|
|
|
let compress = engine.call_tool_text("ctx_compress", None).await;
|
|
match compress {
|
|
Ok(out) => assert!(!out.is_empty(), "compress should return output"),
|
|
Err(e) => {
|
|
let msg = e.to_string();
|
|
assert!(
|
|
msg.contains("timeout") || msg.contains("timed out"),
|
|
"compress unexpected error: {msg}"
|
|
);
|
|
}
|
|
}
|
|
|
|
let cache = engine
|
|
.call_tool_text("ctx_cache", Some(json!({"action": "status"})))
|
|
.await
|
|
.expect("cache status");
|
|
let cache_lower = cache.to_lowercase();
|
|
assert!(
|
|
cache_lower.contains("cache")
|
|
|| cache_lower.contains("entries")
|
|
|| cache_lower.contains("file"),
|
|
"cache status should show info: {cache}"
|
|
);
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase6_ledger_status() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let ledger = engine
|
|
.call_tool_text("ctx_ledger", Some(json!({"action": "status"})))
|
|
.await
|
|
.expect("ledger status");
|
|
assert!(!ledger.is_empty(), "ledger status should return output");
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase6_dedup() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let project = _dir.path().join("project");
|
|
let main_path = project.join("src/main.rs").to_string_lossy().to_string();
|
|
let lib_path = project.join("src/lib.rs").to_string_lossy().to_string();
|
|
let _ = engine
|
|
.call_tool_text("ctx_read", Some(json!({"path": main_path, "mode": "full"})))
|
|
.await
|
|
.unwrap();
|
|
let _ = engine
|
|
.call_tool_text("ctx_read", Some(json!({"path": lib_path, "mode": "full"})))
|
|
.await
|
|
.unwrap();
|
|
|
|
let dedup = engine
|
|
.call_tool_text("ctx_dedup", None)
|
|
.await
|
|
.expect("dedup");
|
|
assert!(!dedup.is_empty(), "dedup should return output");
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase6_plan() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let plan = engine
|
|
.call_tool_text(
|
|
"ctx_plan",
|
|
Some(json!({"task": "Add a caching layer to the Config struct"})),
|
|
)
|
|
.await
|
|
.expect("plan");
|
|
assert!(!plan.is_empty(), "plan should return output");
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
// ── Phase 7: Knowledge & Session ───────────────────────────
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase7_knowledge_remember_and_recall() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let remember = engine
|
|
.call_tool_text(
|
|
"ctx_knowledge",
|
|
Some(json!({
|
|
"action": "remember",
|
|
"category": "architecture",
|
|
"key": "cache_strategy",
|
|
"value": "LRU with zstd compression, 128MB limit",
|
|
})),
|
|
)
|
|
.await
|
|
.expect("knowledge remember");
|
|
assert!(!remember.is_empty(), "remember should return confirmation");
|
|
|
|
let recall = engine
|
|
.call_tool_text(
|
|
"ctx_knowledge",
|
|
Some(json!({"action": "recall", "category": "architecture"})),
|
|
)
|
|
.await
|
|
.expect("knowledge recall");
|
|
assert!(
|
|
recall.contains("cache_strategy") || recall.contains("LRU"),
|
|
"recall should contain remembered fact: {recall}"
|
|
);
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase7_knowledge_timeline_and_rooms() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let _ = engine
|
|
.call_tool_text(
|
|
"ctx_knowledge",
|
|
Some(json!({
|
|
"action": "remember",
|
|
"category": "decisions",
|
|
"key": "db_choice",
|
|
"value": "PostgreSQL for persistence",
|
|
})),
|
|
)
|
|
.await
|
|
.expect("remember decision");
|
|
|
|
let timeline = engine
|
|
.call_tool_text(
|
|
"ctx_knowledge",
|
|
Some(json!({"action": "timeline", "category": "decisions"})),
|
|
)
|
|
.await
|
|
.expect("timeline");
|
|
assert!(!timeline.is_empty(), "timeline should return entries");
|
|
|
|
let rooms = engine
|
|
.call_tool_text("ctx_knowledge", Some(json!({"action": "rooms"})))
|
|
.await
|
|
.expect("rooms");
|
|
assert!(
|
|
rooms.contains("decisions"),
|
|
"rooms should list the decisions category: {rooms}"
|
|
);
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase7_session_finding_and_decision() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let finding = engine
|
|
.call_tool_text(
|
|
"ctx_session",
|
|
Some(json!({
|
|
"action": "finding",
|
|
"content": "Config struct lacks validation for port range",
|
|
})),
|
|
)
|
|
.await
|
|
.expect("session finding");
|
|
assert!(!finding.is_empty(), "finding should return confirmation");
|
|
|
|
let decision = engine
|
|
.call_tool_text(
|
|
"ctx_session",
|
|
Some(json!({
|
|
"action": "decision",
|
|
"content": "Use builder pattern for Config validation",
|
|
})),
|
|
)
|
|
.await
|
|
.expect("session decision");
|
|
assert!(!decision.is_empty(), "decision should return confirmation");
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
// ── Phase 8: Multi-Agent Coordination ──────────────────────
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase8_agent_register_and_diary() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let reg = engine
|
|
.call_tool_text(
|
|
"ctx_agent",
|
|
Some(json!({
|
|
"action": "register",
|
|
"name": "backend-agent",
|
|
"role": "Implements API endpoints",
|
|
})),
|
|
)
|
|
.await
|
|
.expect("agent register");
|
|
assert!(!reg.is_empty(), "register should return confirmation");
|
|
|
|
let diary = engine
|
|
.call_tool_text(
|
|
"ctx_agent",
|
|
Some(json!({
|
|
"action": "diary",
|
|
"category": "progress",
|
|
"content": "Completed Config struct refactoring",
|
|
})),
|
|
)
|
|
.await
|
|
.expect("agent diary");
|
|
assert!(!diary.is_empty(), "diary should return confirmation");
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
// ── Phase 9: Metrics & Verification ────────────────────────
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase9_metrics() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let project = _dir.path().join("project");
|
|
let main_path = project.join("src/main.rs").to_string_lossy().to_string();
|
|
let _ = engine
|
|
.call_tool_text("ctx_read", Some(json!({"path": main_path, "mode": "full"})))
|
|
.await
|
|
.unwrap();
|
|
|
|
let metrics = engine
|
|
.call_tool_text("ctx_metrics", None)
|
|
.await
|
|
.expect("metrics");
|
|
assert!(!metrics.is_empty(), "metrics should return data");
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn phase9_session_save() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let (_dir, engine) = setup_project();
|
|
|
|
let _ = engine
|
|
.call_tool_text(
|
|
"ctx_session",
|
|
Some(json!({"action": "task", "content": "Final verification"})),
|
|
)
|
|
.await
|
|
.expect("set task");
|
|
|
|
let save = engine
|
|
.call_tool_text("ctx_session", Some(json!({"action": "save"})))
|
|
.await
|
|
.expect("session save");
|
|
assert!(!save.is_empty(), "save should return confirmation");
|
|
|
|
unsafe {
|
|
std::env::remove_var("LEAN_CTX_DATA_DIR");
|
|
}
|
|
}
|