ascent-research 0.4.2

ascent-research — an incremental research workflow CLI for AI agents. Every session resumes; knowledge accretes across runs. Mixes HTTP, browser, and local file ingest into a durable per-session wiki + figure-rich HTML report.
Documentation
use serde_json::{Value, json};

use crate::output::Envelope;
use crate::session::{active, config, event::SessionEvent, log};

const CMD: &str = "research sources";

pub fn run(slug_arg: Option<&str>, show_rejected: bool) -> Envelope {
    let slug = match slug_arg {
        Some(s) => s.to_string(),
        None => match active::get_active() {
            Some(s) => s,
            None => {
                return Envelope::fail(
                    CMD,
                    "NO_ACTIVE_SESSION",
                    "no active session — pass <slug> or run `research new` first",
                );
            }
        },
    };

    if !config::exists(&slug) {
        return Envelope::fail(CMD, "SESSION_NOT_FOUND", format!("no session '{slug}'"))
            .with_context(json!({ "session": slug }));
    }

    let events = log::read_all(&slug).unwrap_or_default();
    let mut accepted: Vec<Value> = Vec::new();
    let mut rejected: Vec<Value> = Vec::new();
    for ev in &events {
        match ev {
            SessionEvent::SourceAccepted {
                timestamp,
                url,
                kind,
                executor,
                raw_path,
                bytes,
                trust_score,
                ..
            } => accepted.push(json!({
                "timestamp": timestamp,
                "url": url,
                "kind": kind,
                "executor": executor,
                "raw_path": raw_path,
                "bytes": bytes,
                "trust_score": trust_score,
            })),
            SessionEvent::SourceRejected {
                timestamp,
                url,
                kind,
                executor,
                reason,
                observed_url,
                observed_bytes,
                rejected_raw_path,
                ..
            } if show_rejected => rejected.push(json!({
                "timestamp": timestamp,
                "url": url,
                "kind": kind,
                "executor": executor,
                "reason": reason,
                "observed_url": observed_url,
                "observed_bytes": observed_bytes,
                "rejected_raw_path": rejected_raw_path,
            })),
            _ => {}
        }
    }

    let mut data = json!({ "accepted": accepted });
    if show_rejected {
        data["rejected"] = json!(rejected);
    }
    Envelope::ok(CMD, data).with_context(json!({ "session": slug }))
}