lantern 0.2.2

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

use lantern::ingest::{IngestOptions, ingest_path, ingest_path_with};
use lantern::store::Store;
use tempfile::tempdir;

fn uri_list(report: &lantern::ingest::IngestReport) -> Vec<String> {
    let mut v: Vec<String> = report.ingested.iter().map(|s| s.uri.clone()).collect();
    v.sort();
    v
}

#[test]
fn honours_patterns_from_lantern_ignore() {
    let root = tempdir().unwrap();
    let store_dir = root.path().join("store");
    let mut store = Store::initialize(&store_dir).unwrap();

    let data = root.path().join("data");
    fs::create_dir_all(data.join("logs")).unwrap();
    fs::create_dir_all(data.join("docs")).unwrap();
    fs::write(data.join("docs/keep.md"), "# keep me").unwrap();
    fs::write(data.join("logs/trace.md"), "# noisy").unwrap();
    fs::write(data.join("scratch.md"), "# scratch").unwrap();
    fs::write(
        data.join(".lantern-ignore"),
        "# skip the log directory and top-level scratch\nlogs/\nscratch.md\n",
    )
    .unwrap();

    let report = ingest_path(&mut store, &data).unwrap();

    let paths: Vec<String> = report
        .ingested
        .iter()
        .map(|s| s.uri.clone())
        .collect::<Vec<_>>();
    assert_eq!(report.ingested.len(), 1, "unexpected ingest: {paths:?}");
    assert!(report.ingested[0].uri.ends_with("docs/keep.md"));
    assert!(
        report.ignored >= 2,
        "expected at least 2 ignored entries, got {} (uris: {:?})",
        report.ignored,
        paths
    );
}

#[test]
fn negation_pattern_unignores_file() {
    let root = tempdir().unwrap();
    let mut store = Store::initialize(&root.path().join("store")).unwrap();

    let data = root.path().join("data");
    fs::create_dir_all(&data).unwrap();
    fs::write(data.join("a.md"), "a").unwrap();
    fs::write(data.join("b.md"), "b").unwrap();
    fs::write(data.join("c.md"), "c").unwrap();
    fs::write(data.join(".lantern-ignore"), "*.md\n!b.md\n").unwrap();

    let report = ingest_path(&mut store, &data).unwrap();

    let uris = uri_list(&report);
    assert_eq!(uris.len(), 1, "unexpected ingest: {uris:?}");
    assert!(uris[0].ends_with("b.md"));
}

#[test]
fn defaults_apply_without_lantern_ignore() {
    let root = tempdir().unwrap();
    let mut store = Store::initialize(&root.path().join("store")).unwrap();

    let data = root.path().join("project");
    fs::create_dir_all(data.join("src")).unwrap();
    fs::create_dir_all(data.join("target/debug")).unwrap();
    fs::create_dir_all(data.join("node_modules/left-pad")).unwrap();
    fs::create_dir_all(data.join(".git")).unwrap();
    fs::write(data.join("src/main.rs"), "fn main() {}").unwrap();
    fs::write(data.join("target/debug/build.log"), "should be ignored").unwrap();
    fs::write(data.join("target/debug/build.rs"), "compiled code").unwrap();
    fs::write(
        data.join("node_modules/left-pad/index.js"),
        "module.exports = 1;",
    )
    .unwrap();
    fs::write(data.join(".git/HEAD"), "ref: refs/heads/main").unwrap();

    let report = ingest_path(&mut store, &data).unwrap();

    let uris = uri_list(&report);
    assert_eq!(
        uris.len(),
        1,
        "only src/main.rs should be ingested, got: {uris:?}"
    );
    assert!(uris[0].ends_with("src/main.rs"));
    assert!(report.ignored > 0);
}

#[test]
fn no_ignore_flag_bypasses_rules() {
    let root = tempdir().unwrap();
    let mut store = Store::initialize(&root.path().join("store")).unwrap();

    let data = root.path().join("project");
    fs::create_dir_all(data.join("target")).unwrap();
    fs::create_dir_all(data.join("src")).unwrap();
    fs::write(data.join("src/main.rs"), "fn main() {}").unwrap();
    fs::write(data.join("target/build.rs"), "compiled").unwrap();
    fs::write(data.join(".lantern-ignore"), "src/\n").unwrap();

    let report = ingest_path_with(&mut store, &data, &IngestOptions { no_ignore: true }).unwrap();

    let uris = uri_list(&report);
    assert_eq!(
        uris.len(),
        2,
        "both files should be ingested, got: {uris:?}"
    );
    assert_eq!(report.ignored, 0);
    assert!(uris.iter().any(|u| u.ends_with("src/main.rs")));
    assert!(uris.iter().any(|u| u.ends_with("target/build.rs")));
}

#[test]
fn single_file_target_is_checked_against_rules() {
    let root = tempdir().unwrap();
    let mut store = Store::initialize(&root.path().join("store")).unwrap();

    let data = root.path().join("project");
    fs::create_dir_all(&data).unwrap();
    let secret = data.join("secret.md");
    fs::write(&secret, "hush").unwrap();
    fs::write(data.join(".lantern-ignore"), "secret.md\n").unwrap();

    let report = ingest_path(&mut store, &secret).unwrap();
    assert!(report.ingested.is_empty());
    assert_eq!(report.ignored, 1);

    let bypass = ingest_path_with(&mut store, &secret, &IngestOptions { no_ignore: true }).unwrap();
    assert_eq!(bypass.ingested.len(), 1);
}