unlost 0.17.1

Unlost - Local-first code memory for a workspace.
Documentation
use chrono::{SecondsFormat, TimeZone};

fn fmt_ts_utc(ts_ms: i64) -> String {
    chrono::Utc
        .timestamp_millis_opt(ts_ms)
        .single()
        .map(|dt| dt.to_rfc3339_opts(SecondsFormat::Secs, true))
        .unwrap_or_else(|| ts_ms.to_string())
}

pub async fn run(
    path: String,
    limit: usize,
    emotion: Option<crate::cli::EmotionType>,
    provider: Option<crate::cli::ProviderType>,
    since: Option<String>,
    until: Option<String>,
    filter: Option<String>,
) -> anyhow::Result<()> {
    let ws = crate::workspace::get_or_create_workspace_paths(std::path::Path::new(&path))?;

    let emotion_label = emotion.map(|e| match e {
        crate::cli::EmotionType::Joy => "joy",
        crate::cli::EmotionType::Anger => "anger",
        crate::cli::EmotionType::Frustration => "frustration",
        crate::cli::EmotionType::Sad => "sad",
        crate::cli::EmotionType::Confused => "confused",
        crate::cli::EmotionType::Neutral => "neutral",
    });
    let provider_label = provider.map(|p| match p {
        crate::cli::ProviderType::Openai => "openai",
        crate::cli::ProviderType::Anthropic => "anthropic",
        crate::cli::ProviderType::Opencode => "opencode",
    });
    let since_ms = match since {
        Some(ref s) => crate::util::parse_time_filter(s)?,
        None => None,
    };
    let until_ms = match until {
        Some(ref u) => crate::util::parse_time_filter(u)?,
        None => None,
    };

    match crate::storage::scan_capsules_lancedb_recent(
        &ws,
        limit,
        filter.as_deref(),
        emotion_label.as_deref(),
        provider_label.as_deref(),
        since_ms,
        until_ms,
    )
    .await
    {
        Ok(rows) if !rows.is_empty() => {
            println!("workspace: {}", ws.id);
            for hit in rows {
                let cap = hit.capsule;
                let meta = hit.meta;
                println!("---");
                println!("chunked_at: {}", fmt_ts_utc(hit.ts_ms));
                if let Some(ref session) = meta.agent_session_id {
                    println!("session:   {}", session);
                }
                if let Some(ref sha) = hit.head_sha {
                    println!("head_sha:  {}", sha);
                }
                if let Some(ref sha) = hit.commit_sha {
                    println!("commit_sha: {}", sha);
                }
                println!(
                    "conn:      {} seq: {} status: {}",
                    hit.conn_id, hit.exchange_seq, meta.http_status
                );
                if let Some(e) = hit.user_emotion {
                    println!(
                        "user_mood:  {} (conf={:.2} val={:.2} int={:.2})",
                        e.label, e.confidence, e.valence, e.intensity
                    );
                }
                if let Some(e) = hit.assistant_emotion {
                    println!(
                        "asst_mood:  {} (conf={:.2} val={:.2} int={:.2})",
                        e.label, e.confidence, e.valence, e.intensity
                    );
                }
                println!("source:    {}", meta.source);
                println!("category:  {}", cap.category);
                println!("upstream:  {}", meta.upstream_host);
                println!("path:      {}", meta.request_path);
                if let Some(ref u) = meta.usage {
                    let model = u.model_id.as_deref().unwrap_or("-");
                    let provider = u.provider_id.as_deref().unwrap_or("-");
                    let cost = u
                        .cost
                        .map(|c| format!("{c:.6}"))
                        .unwrap_or_else(|| "-".to_string());
                    let tokens = u
                        .tokens_total()
                        .map(|t| t.to_string())
                        .unwrap_or_else(|| "-".to_string());
                    let input = u
                        .tokens_input
                        .map(|t| t.to_string())
                        .unwrap_or_else(|| "-".to_string());
                    let output = u
                        .tokens_output
                        .map(|t| t.to_string())
                        .unwrap_or_else(|| "-".to_string());
                    let reasoning = u
                        .tokens_reasoning
                        .map(|t| t.to_string())
                        .unwrap_or_else(|| "-".to_string());
                    let cache_r = u
                        .tokens_cache_read
                        .map(|t| t.to_string())
                        .unwrap_or_else(|| "-".to_string());
                    let cache_w = u
                        .tokens_cache_write
                        .map(|t| t.to_string())
                        .unwrap_or_else(|| "-".to_string());
                    println!("model:     {provider}/{model}");
                    println!("cost:      {cost}");
                    println!(
                        "tokens:    total={tokens} in={input} out={output} reason={reasoning} cache_r={cache_r} cache_w={cache_w}"
                    );
                }
                if !cap.intent.trim().is_empty() {
                    println!("intent:    {}", cap.intent);
                }
                if !cap.decision.trim().is_empty() {
                    println!("decision:  {}", cap.decision);
                }
                if !cap.rationale.trim().is_empty() {
                    println!("rationale: {}", cap.rationale);
                }
                if !cap.next_steps.is_empty() {
                    println!("next:      {:?}", cap.next_steps);
                }
                println!("symbols:   {:?}", cap.symbols);
                if !cap.questions.is_empty() {
                    for (i, q) in cap.questions.iter().enumerate() {
                        println!("question[{}]: {}", i, q);
                    }
                }
                if let Some(ptr) = meta.source_pointer.as_deref() {
                    if let Some(label) = crate::workspace::resolve_source_label(ptr) {
                        println!("source_ref: {label}");
                    }
                    println!("source_uri: {ptr}");
                }
                if let Some(ref te) = hit.turn_eval {
                    println!(
                        "turn_eval: {} intensity={:.2} state={:?}",
                        te.version, te.trajectory_intensity, te.trajectory_state
                    );
                    println!(
                        "  tune:     rep={:.2} nov={:.2} sem={:.2} eff={:.2} align={:.2} \
                         hall={:.2} stall={:.2} inst={:.2} churn={:.2} fluency={:.2}",
                        te.repetition,
                        te.novelty_collapse,
                        te.semantic_stall,
                        te.effort_spike,
                        te.alignment_debt,
                        te.path_hallucination,
                        te.grounding_stall,
                        te.instruction_staticness,
                        te.logic_churn,
                        te.fluency
                    );
                    println!(
                        "  coach:    clarity={:.2} freshness={:.2} verify={:.2} \
                         progress={:.2} scope={:.2} cost_accel={:.2}",
                        te.clarity,
                        te.context_freshness,
                        te.verification_rigor,
                        te.decision_progress,
                        te.scope_discipline,
                        te.cost_acceleration
                    );
                    if !te.flags.is_empty() {
                        println!("  flags:    {:?}", te.flags);
                    }
                    if !te.outcome_hint.is_empty() && te.outcome_hint != "unclear" {
                        println!("  outcome:  {}", te.outcome_hint);
                    }
                    if !te.evidence.is_empty() {
                        for ev in &te.evidence {
                            println!("  evidence: {}", ev);
                        }
                    }
                }
                println!();
            }
            Ok(())
        }
        Ok(_) => {
            println!("workspace: {}", ws.id);
            println!("no rows found");
            Ok(())
        }
        Err(e) => {
            tracing::warn!(error = ?e, "inspect failed");
            println!("inspect failed: {e}");
            Ok(())
        }
    }
}