kaizen-cli 0.1.35

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 kaizen::core::event::{Event, EventKind, EventSource, SessionRecord, SessionStatus};
use kaizen::metrics::types::{FileFact, RepoSnapshotRecord};
use serde_json::json;

pub fn now_ms() -> u64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default()
        .as_millis() as u64
}

pub fn session(id: &str, workspace: &str, now: u64) -> SessionRecord {
    SessionRecord {
        id: id.into(),
        agent: "codex".into(),
        model: Some("gpt".into()),
        workspace: workspace.into(),
        started_at_ms: now - 2_000,
        ended_at_ms: Some(now),
        status: SessionStatus::Done,
        trace_path: String::new(),
        start_commit: None,
        end_commit: None,
        branch: None,
        dirty_start: None,
        dirty_end: None,
        repo_binding_source: None,
        prompt_fingerprint: None,
        parent_session_id: None,
        agent_version: None,
        os: None,
        arch: None,
        repo_file_count: None,
        repo_total_loc: None,
    }
}

pub fn snapshot(workspace: &str, now: u64) -> RepoSnapshotRecord {
    RepoSnapshotRecord {
        id: "snap".into(),
        workspace: workspace.into(),
        head_commit: None,
        dirty_fingerprint: "clean".into(),
        analyzer_version: "test".into(),
        indexed_at_ms: now,
        dirty: false,
        graph_path: String::new(),
    }
}

pub fn facts() -> Vec<FileFact> {
    vec![
        fact("src/a.rs", 10, 3, 2),
        fact("src/b.rs", 5, 9, 4),
        fact("src/c.rs", 1, 1, 1),
    ]
}

fn fact(path: &str, complexity: u32, churn: u32, authors: u32) -> FileFact {
    FileFact {
        snapshot_id: "snap".into(),
        path: path.into(),
        language: "rust".into(),
        bytes: 1,
        loc: 1,
        sloc: 1,
        complexity_total: complexity,
        max_fn_complexity: complexity,
        symbol_count: 1,
        import_count: 0,
        fan_in: 0,
        fan_out: 0,
        churn_30d: churn,
        churn_90d: churn,
        authors_90d: authors,
        last_changed_ms: None,
    }
}

pub fn events(session_id: &str, now: u64) -> Vec<Event> {
    [
        (0, now - 900, EventKind::ToolCall, "bash", "a", "src/a.rs"),
        (1, now - 800, EventKind::ToolResult, "bash", "a", "src/a.rs"),
        (2, now - 700, EventKind::ToolCall, "bash", "b", "src/a.rs"),
        (3, now - 500, EventKind::ToolResult, "bash", "b", "src/a.rs"),
        (
            4,
            now - 400,
            EventKind::ToolCall,
            "read_file",
            "c",
            "src/b.rs",
        ),
        (
            5,
            now - 100,
            EventKind::ToolResult,
            "read_file",
            "c",
            "src/b.rs",
        ),
    ]
    .into_iter()
    .map(|(seq, ts, kind, tool, call, path)| event(session_id, seq, ts, kind, tool, call, path))
    .collect()
}

fn event(
    session_id: &str,
    seq: u64,
    ts_ms: u64,
    kind: EventKind,
    tool: &str,
    call_id: &str,
    path: &str,
) -> Event {
    Event {
        session_id: session_id.into(),
        seq,
        ts_ms,
        ts_exact: true,
        kind,
        source: EventSource::Tail,
        tool: Some(tool.into()),
        tool_call_id: Some(call_id.into()),
        tokens_in: Some(seq as u32 + 1),
        tokens_out: Some(2),
        reasoning_tokens: Some(1),
        cost_usd_e6: Some(1),
        stop_reason: None,
        latency_ms: None,
        ttft_ms: None,
        retry_count: None,
        context_used_tokens: None,
        context_max_tokens: None,
        cache_creation_tokens: None,
        cache_read_tokens: None,
        system_prompt_tokens: None,
        payload: json!({ "path": path }),
    }
}