tokensave 5.1.2

Code intelligence tool that builds a semantic knowledge graph from Rust, Go, Java, Scala, TypeScript, Python, C, C++, Kotlin, C#, Swift, and many more codebases
use tempfile::TempDir;
use tokensave::tokensave::TokenSave;

async fn make_project() -> (TempDir, TokenSave) {
    let tmp = TempDir::new().unwrap();
    std::fs::write(tmp.path().join("a.rs"), "pub fn hello() {}").unwrap();
    let cg = TokenSave::init(tmp.path()).await.unwrap();
    (tmp, cg)
}

#[tokio::test]
async fn record_decision_persists_and_recalls() {
    let (_tmp, cg) = make_project().await;

    let id = cg
        .record_decision(
            "use JWT for auth",
            Some("session tokens flagged by legal"),
            &["src/auth.rs".to_string()],
            &["security".to_string(), "decision".to_string()],
        )
        .await
        .unwrap();
    assert!(id > 0);

    let hits = cg.session_recall(Some("JWT"), None, 10).await.unwrap();
    assert_eq!(hits.len(), 1);
    assert_eq!(hits[0].text, "use JWT for auth");
    assert_eq!(
        hits[0].reason.as_deref(),
        Some("session tokens flagged by legal")
    );
    assert_eq!(hits[0].files, vec!["src/auth.rs"]);
    assert_eq!(hits[0].tags, vec!["security", "decision"]);
}

#[tokio::test]
async fn session_recall_orders_newest_first_when_no_query() {
    let (_tmp, cg) = make_project().await;

    cg.record_decision("first", None, &[], &[]).await.unwrap();
    // current_timestamp() is second-granularity, so we need a >1s gap to guarantee
    // the two decisions have distinct created_at values for a deterministic ordering.
    tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
    cg.record_decision("second", None, &[], &[]).await.unwrap();

    let hits = cg.session_recall(None, None, 10).await.unwrap();
    assert_eq!(hits.len(), 2);
    assert_eq!(hits[0].text, "second");
    assert_eq!(hits[1].text, "first");
}

#[tokio::test]
async fn record_code_area_upserts_touch_count() {
    let (_tmp, cg) = make_project().await;

    cg.record_code_area("src/auth.rs", Some("OAuth provider"))
        .await
        .unwrap();
    cg.record_code_area("src/auth.rs", None).await.unwrap();
    cg.record_code_area("src/auth.rs", None).await.unwrap();

    let areas = cg.list_code_areas(10).await.unwrap();
    assert_eq!(areas.len(), 1);
    assert_eq!(areas[0].path, "src/auth.rs");
    assert_eq!(areas[0].touch_count, 3);
    assert_eq!(areas[0].description.as_deref(), Some("OAuth provider"));
}

#[tokio::test]
async fn session_recall_filters_by_since() {
    let (_tmp, cg) = make_project().await;
    cg.record_decision("old decision", None, &[], &[])
        .await
        .unwrap();
    // Force a > 1s gap so created_at values differ deterministically.
    tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
    let cutoff = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_secs() as i64;
    // Force a > 1s gap on the new side too, otherwise the new record could share
    // its created_at with `cutoff` (second-granularity).
    tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
    cg.record_decision("new decision", None, &[], &[])
        .await
        .unwrap();

    let hits = cg.session_recall(None, Some(cutoff), 10).await.unwrap();
    assert_eq!(hits.len(), 1);
    assert_eq!(hits[0].text, "new decision");
}