sillok 0.7.0

A structured CLI chronicle for agentic daily work logs.
Documentation
use std::path::PathBuf;

mod sync_support;

use sync_support::{
    clone_remote, init_bare_remote, remote_set, run_failure_json, run_json, temp_store,
};

#[test]
fn sync_remote_set_and_show_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
    let (dir, store) = temp_store()?;
    let remote = init_bare_remote(dir.path())?;
    let set = remote_set(&store, &remote)?;
    assert_eq!(set["command"], "sync");
    assert_eq!(set["data"]["action"], "remote_set");
    assert_eq!(set["data"]["remote"]["branch"], "main");
    assert!(PathBuf::from(format!("{}.sync.json", store.display())).exists());

    let show = run_json(&store, &["sync", "remote", "show"])?;
    assert_eq!(show["data"]["action"], "remote_show");
    assert_eq!(show["data"]["remote"]["url"], remote.display().to_string());
    Ok(())
}

#[test]
fn sync_push_creates_remote_artifact() -> Result<(), Box<dyn std::error::Error>> {
    let (dir, store) = temp_store()?;
    let remote = init_bare_remote(dir.path())?;
    remote_set(&store, &remote)?;
    run_json(
        &store,
        &[
            "--tz",
            "UTC",
            "--at",
            "2026-05-13T08:00:00Z",
            "note",
            "sync push seed",
        ],
    )?;

    let pushed = run_json(&store, &["sync", "push"])?;
    assert_eq!(pushed["data"]["pushed"], true);
    assert!(pushed["data"]["commit"].is_string());

    let checkout = dir.path().join("checkout");
    clone_remote(&remote, &checkout)?;
    assert!(checkout.join("sillok.slk.zst").exists());
    Ok(())
}

#[test]
fn sync_pull_creates_missing_local_db() -> Result<(), Box<dyn std::error::Error>> {
    let (dir, source_store) = temp_store()?;
    let remote = init_bare_remote(dir.path())?;
    remote_set(&source_store, &remote)?;
    run_json(
        &source_store,
        &[
            "--tz",
            "UTC",
            "--at",
            "2026-05-13T09:00:00Z",
            "note",
            "pull me from remote",
        ],
    )?;
    run_json(&source_store, &["sync", "push"])?;

    let target_store = dir.path().join("target.db");
    remote_set(&target_store, &remote)?;
    let pulled = run_json(&target_store, &["sync", "pull"])?;
    assert_eq!(pulled["data"]["pulled"], true);
    assert!(target_store.exists());

    let day = run_json(
        &target_store,
        &["--tz", "UTC", "day", "--date", "2026-05-13"],
    )?;
    assert_eq!(day["data"]["records"][0]["text"], "pull me from remote");
    Ok(())
}

#[test]
fn sync_run_merges_diverged_archives() -> Result<(), Box<dyn std::error::Error>> {
    let (dir, left_store) = temp_store()?;
    let remote = init_bare_remote(dir.path())?;
    remote_set(&left_store, &remote)?;
    run_json(
        &left_store,
        &[
            "--tz",
            "UTC",
            "--at",
            "2026-05-13T08:00:00Z",
            "note",
            "base sync note",
        ],
    )?;
    run_json(&left_store, &["sync", "push"])?;

    let right_store = dir.path().join("right.db");
    remote_set(&right_store, &remote)?;
    run_json(&right_store, &["sync", "pull"])?;
    run_json(
        &left_store,
        &[
            "--tz",
            "UTC",
            "--at",
            "2026-05-13T09:00:00Z",
            "note",
            "left side note",
        ],
    )?;
    run_json(&left_store, &["sync", "push"])?;
    run_json(
        &right_store,
        &[
            "--tz",
            "UTC",
            "--at",
            "2026-05-13T10:00:00Z",
            "note",
            "right side note",
        ],
    )?;

    let synced = run_json(&right_store, &["sync", "run"])?;
    assert_eq!(synced["data"]["merged"], true);
    assert_eq!(synced["data"]["pulled"], true);
    assert_eq!(synced["data"]["pushed"], true);

    let verify_store = dir.path().join("verify.db");
    remote_set(&verify_store, &remote)?;
    run_json(&verify_store, &["sync", "pull"])?;
    let day = run_json(
        &verify_store,
        &["--tz", "UTC", "day", "--date", "2026-05-13"],
    )?;
    assert_eq!(day["data"]["records"].as_array().map(Vec::len), Some(3));
    assert_eq!(day["data"]["records"][1]["text"], "left side note");
    assert_eq!(day["data"]["records"][2]["text"], "right side note");
    Ok(())
}

#[test]
fn sync_archive_mismatch_preserves_local_db() -> Result<(), Box<dyn std::error::Error>> {
    let (dir, source_store) = temp_store()?;
    let remote = init_bare_remote(dir.path())?;
    remote_set(&source_store, &remote)?;
    run_json(
        &source_store,
        &[
            "--tz",
            "UTC",
            "--at",
            "2026-05-13T08:00:00Z",
            "note",
            "remote archive note",
        ],
    )?;
    run_json(&source_store, &["sync", "push"])?;

    let local_store = dir.path().join("local.db");
    remote_set(&local_store, &remote)?;
    run_json(
        &local_store,
        &[
            "--tz",
            "UTC",
            "--at",
            "2026-05-13T11:00:00Z",
            "note",
            "independent local note",
        ],
    )?;

    let failed = run_failure_json(&local_store, &["sync", "run"])?;
    assert_eq!(failed["error"]["code"], "sync_archive_mismatch");
    let day = run_json(
        &local_store,
        &["--tz", "UTC", "day", "--date", "2026-05-13"],
    )?;
    assert_eq!(day["data"]["records"].as_array().map(Vec::len), Some(1));
    assert_eq!(day["data"]["records"][0]["text"], "independent local note");
    Ok(())
}