kaizen-cli 0.1.42

Distributable agent observability: real-time-tailable sessions, agile-style retros, and repo-level improvement (Cursor, Claude Code, Codex). SQLite, redact before any sync you enable.
Documentation
use anyhow::{Context, Result};
use rusqlite::{Connection, OpenFlags};

use super::{db_path, db_path_for_write, sql};

pub(super) fn open_write(workspace: &std::path::Path) -> Result<Option<Connection>> {
    let Some(path) = db_path_for_write(workspace)? else {
        return Ok(None);
    };
    create_parent(&path)?;
    let conn = open(&path)?;
    migrate(&conn)?;
    Ok(Some(conn))
}

pub(super) fn open_read() -> Result<Option<Connection>> {
    let Some(path) = db_path() else {
        return Ok(None);
    };
    if !path.exists() {
        return Ok(None);
    }
    open_read_path(&path).map(Some)
}

fn open_read_path(path: &std::path::Path) -> Result<Connection> {
    let flags = OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_NO_MUTEX;
    let conn = Connection::open_with_flags(path, flags)
        .with_context(|| format!("open machine registry: {}", path.display()))?;
    conn.execute_batch("PRAGMA query_only=ON; PRAGMA busy_timeout=5000;")?;
    Ok(conn)
}

fn create_parent(path: &std::path::Path) -> Result<()> {
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)?;
    }
    Ok(())
}

fn open(path: &std::path::Path) -> Result<Connection> {
    let conn = Connection::open(path)
        .with_context(|| format!("open machine registry: {}", path.display()))?;
    conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;")?;
    Ok(conn)
}

fn migrate(conn: &Connection) -> Result<()> {
    sql::MIGRATIONS.iter().try_for_each(|statement| {
        conn.execute_batch(statement)
            .with_context(|| format!("machine registry migration: {statement}"))
    })
}