lantern 0.2.3

Local-first, provenance-aware semantic search for agent activity
Documentation
use std::fs;
use std::process::Command;

use tempfile::tempdir;

fn lantern() -> Command {
    Command::new(env!("CARGO_BIN_EXE_lantern"))
}

fn run(mut cmd: Command) -> (String, String, bool) {
    let output = cmd.output().expect("failed to run lantern");
    (
        String::from_utf8_lossy(&output.stdout).into_owned(),
        String::from_utf8_lossy(&output.stderr).into_owned(),
        output.status.success(),
    )
}

#[test]
fn query_subcommand_produces_summary_output() {
    let root = tempdir().unwrap();
    let store = root.path().join("store");
    fs::create_dir_all(root.path().join("data")).unwrap();
    let note = root.path().join("data/note.md");
    fs::write(&note, "needle floating in a wider haystack of text").unwrap();

    let (_, _, ok) = run({
        let mut c = lantern();
        c.args(["init", "--path"]).arg(&store);
        c
    });
    assert!(ok, "init failed");

    let (_, _, ok) = run({
        let mut c = lantern();
        c.arg("ingest").arg(&note).arg("--store").arg(&store);
        c
    });
    assert!(ok, "ingest failed");

    let (stdout, _, ok) = run({
        let mut c = lantern();
        c.arg("query").arg("needle").arg("--store").arg(&store);
        c
    });
    assert!(ok, "query failed");
    assert!(
        stdout.contains("query: \"needle\""),
        "summary header missing: {stdout}"
    );
    assert!(stdout.contains("hits: 1"), "hit count missing: {stdout}");
    assert!(stdout.contains("note.md"), "source path missing: {stdout}");
}

#[test]
fn query_help_advertises_the_higher_default_limit() {
    let (stdout, _, ok) = run({
        let mut c = lantern();
        c.args(["query", "--help"]);
        c
    });
    assert!(ok, "query --help failed");
    assert!(
        stdout.contains("[default: 20]"),
        "expected a default of 20 in help output: {stdout}"
    );
}

#[test]
fn query_accepts_the_same_filters_as_search() {
    let root = tempdir().unwrap();
    let store = root.path().join("store");
    let data = root.path().join("data");
    fs::create_dir_all(&data).unwrap();
    fs::write(data.join("a.md"), "needle in markdown").unwrap();
    fs::write(data.join("b.txt"), "needle in plain text").unwrap();

    let (_, _, ok) = run({
        let mut c = lantern();
        c.args(["init", "--path"]).arg(&store);
        c
    });
    assert!(ok);
    let (_, _, ok) = run({
        let mut c = lantern();
        c.arg("ingest").arg(&data).arg("--store").arg(&store);
        c
    });
    assert!(ok);

    let (stdout, _, ok) = run({
        let mut c = lantern();
        c.arg("query")
            .arg("needle")
            .arg("--store")
            .arg(&store)
            .args(["--kind", "text/markdown"]);
        c
    });
    assert!(ok);
    assert!(stdout.contains("hits: 1"), "kind filter missing: {stdout}");
    assert!(stdout.contains("a.md"));
    assert!(!stdout.contains("b.txt"));
}

#[test]
fn query_surfaces_jsonl_metadata_in_summary_output() {
    let root = tempdir().unwrap();
    let store = root.path().join("store");
    let (_, _, ok) = run({
        let mut c = lantern();
        c.args(["init", "--path"]).arg(&store);
        c
    });
    assert!(ok);

    let payload = concat!(
        "{\"role\":\"assistant\",\"session_id\":\"sess-7\",",
        "\"turn_id\":\"turn-9\",\"tool_name\":\"search\",",
        "\"timestamp\":1700000003,\"content\":\"needle in a transcript\"}\n"
    );
    let transcript = root.path().join("data/session.jsonl");
    fs::create_dir_all(transcript.parent().unwrap()).unwrap();
    fs::write(&transcript, payload).unwrap();
    let (_, _, ok) = run({
        let mut c = lantern();
        c.arg("ingest").arg(&transcript).arg("--store").arg(&store);
        c
    });
    assert!(ok, "jsonl ingest failed");

    let (stdout, _, ok) = run({
        let mut c = lantern();
        c.arg("query").arg("needle").arg("--store").arg(&store);
        c
    });
    assert!(ok, "query failed");
    assert!(
        stdout.contains("session=sess-7"),
        "session metadata missing: {stdout}"
    );
    assert!(
        stdout.contains("turn=turn-9"),
        "turn metadata missing: {stdout}"
    );
    assert!(
        stdout.contains("tool=search"),
        "tool metadata missing: {stdout}"
    );
}