talon-core 0.4.2

Core retrieval engine for Talon: hybrid search (BM25 + semantic + reranker), indexing, and graph-aware ranking over markdown corpora.
Documentation
use rusqlite::{Connection, params};

use crate::graph::build::{GraphBuildInput, clean_source_reference, rebuild_graph};
use crate::graph::load_graph_snapshot;
use crate::indexing::migrations::run_migrations;

#[test]
fn rebuild_graph_filters_unresolved_and_inactive_links() -> Result<(), Box<dyn std::error::Error>> {
    let mut conn = Connection::open_in_memory()?;
    run_migrations(&mut conn)?;
    insert_note(&conn, 1, "Graph/Source.md", 1)?;
    insert_note(&conn, 2, "Graph/Target.md", 1)?;
    insert_note(&conn, 3, "Graph/Inactive.md", 0)?;
    insert_link(&conn, "Graph/Source.md", "Graph/Target.md")?;
    insert_link(&conn, "Graph/Source.md", "Graph/Missing.md")?;
    insert_link(&conn, "Graph/Source.md", "Graph/Inactive.md")?;

    let stats = rebuild_graph(&mut conn, &GraphBuildInput)?;
    assert_eq!(stats.node_count, 2);
    assert_eq!(stats.edge_count, 1);

    let snapshot = load_graph_snapshot(&conn)?;
    assert!(snapshot.nodes.contains_key("Graph/Source.md"));
    assert!(!snapshot.nodes.contains_key("Graph/Inactive.md"));
    assert_eq!(snapshot.edges.len(), 1);
    assert_eq!(snapshot.edges[0].to_path, "Graph/Target.md");
    Ok(())
}

#[test]
fn rebuild_graph_persists_source_map_and_structural_detection()
-> Result<(), Box<dyn std::error::Error>> {
    let mut conn = Connection::open_in_memory()?;
    run_migrations(&mut conn)?;
    insert_note(&conn, 1, "Graph/Index.md", 1)?;
    insert_frontmatter(&conn, 1, "sources", "[[Book One|book]]")?;
    insert_frontmatter(&conn, 1, "type", "index")?;

    let stats = rebuild_graph(&mut conn, &GraphBuildInput)?;
    assert_eq!(stats.source_count, 1);

    let snapshot = load_graph_snapshot(&conn)?;
    let Some(node) = snapshot.nodes.get("Graph/Index.md") else {
        panic!("missing graph node");
    };
    assert!(node.structural);
    assert_eq!(node.note_type.as_deref(), Some("index"));
    assert_eq!(node.sources, vec!["Graph/Book One.md"]);
    let Some(citations) = snapshot.source_citations.get("Graph/Book One.md") else {
        panic!("missing source citations");
    };
    assert_eq!(citations.len(), 1);
    Ok(())
}

#[test]
fn source_reference_cleanup_handles_obsidian_links_and_urls() {
    assert_eq!(
        clean_source_reference("Notes/Current.md", "[[Book#Chapter|label]]"),
        "Notes/Book.md"
    );
    assert_eq!(
        clean_source_reference("Notes/Current.md", "https://example.com/a"),
        "https://example.com/a"
    );
}

fn insert_note(conn: &Connection, id: i64, path: &str, active: u8) -> Result<(), rusqlite::Error> {
    conn.execute(
        "INSERT INTO notes (
           id, vault_path, title, tags, aliases, content, frontmatter,
           mtime_ms, size_bytes, hash, docid, active, scope
         ) VALUES (?1, ?2, ?3, '[]', '[]', '', '', 0, 0, ?4, ?5, ?6, '')",
        params![
            id,
            path,
            path,
            format!("hash-{id}"),
            format!("docid-{id}"),
            active
        ],
    )?;
    Ok(())
}

fn insert_link(conn: &Connection, from: &str, to: &str) -> Result<(), rusqlite::Error> {
    conn.execute(
        "INSERT INTO links (from_path, to_path, raw_target, heading, alias)
         VALUES (?1, ?2, ?2, NULL, NULL)",
        params![from, to],
    )?;
    Ok(())
}

fn insert_frontmatter(
    conn: &Connection,
    note_id: i64,
    field: &str,
    value: &str,
) -> Result<(), rusqlite::Error> {
    conn.execute(
        "INSERT INTO note_frontmatter_fields (note_id, field, value, value_type, value_norm)
         VALUES (?1, ?2, ?3, 'string', ?3)",
        params![note_id, field, value],
    )?;
    Ok(())
}