supercode-core 0.2.1

A lightweight, fully-customizable AI coding agent SDK in Rust. Talks to any model via OpenRouter or any OpenAI-compatible endpoint.
Documentation
//! Guarantor-path test closing the same coverage-gap class TR-2/TR-3/TR-4/
//! TR-6/TR-10's own `tr*_guarantor.rs` files close for their reductions: a
//! LIVE `Agent` (recorder + `ReductionPolicy`, TR-7's `summarize_cleared_turns`
//! ON with an injected deterministic fake `SpanSummarizer`) clears AND
//! summarizes a span; the sidecar and reduction log are persisted, then
//! **reloaded from disk** (never the live `Agent`/in-memory `Session`);
//! `verify_log` and `invert` both run OFFLINE against that reloaded state.
//!
//! What this specifically proves, that `reduce_summaries.rs`'s in-memory
//! `project_messages` unit tests structurally cannot: TR-7's audit fields
//! (`SpanSummary`) and the summarized placeholder survive a real
//! `SidecarWriter` -> disk -> `SessionStore` round trip, and — the crux of
//! B10/TR-7 dev/02 — the RELOADED SIDECAR itself never contains one byte of
//! the LLM summary text (it only ever exists in the projected view's
//! placeholder), and `invert` restores the original cleared turns byte-exact
//! straight from that reloaded sidecar.
//!
//! No real model call anywhere: the injected summarizer is a small
//! deterministic in-process fake.

use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};

use async_trait::async_trait;
use supercode::reduce::summarize::SpanSummarizer;
use supercode::reduce::{invert, project_messages, verify_log, ReductionKind, ReductionPolicy};
use supercode::session::Session;
use supercode::sidecar::SidecarWriter;
use supercode::store::SessionStore;
use supercode::{Agent, ChatMessage, ChatRequest, Config, Provider, Usage};

fn temp_dir(tag: &str) -> PathBuf {
    static N: AtomicUsize = AtomicUsize::new(0);
    let dir = std::env::temp_dir().join(format!(
        "supercode-tr7-guarantor-{tag}-{}-{}",
        std::process::id(),
        N.fetch_add(1, Ordering::SeqCst)
    ));
    std::fs::create_dir_all(&dir).unwrap();
    dir
}

/// A deterministic all-ASCII filler string of exactly `len` bytes — padding
/// so the cleared span comfortably clears TR-7's default cost-guard floor
/// (`expected_summary_bytes(400) * summary_cost_floor_multiple(4)` = 1600
/// bytes) without needing to touch those knobs.
fn filler(len: usize) -> String {
    (0..len).map(|i| (b'a' + (i % 26) as u8) as char).collect()
}

/// Marker text unique enough that finding it anywhere outside the projected
/// view's own placeholder is unambiguous proof of a leak.
const SUMMARY_MARKER: &str = "SUMMARY-MARKER-tr7-guarantor-8f2b";

/// The one injected side-call this whole test uses — a small, deterministic,
/// in-process fake. Never a real model call.
struct FakeSummarizer;
impl SpanSummarizer for FakeSummarizer {
    fn summarize(&self, span_text: &str) -> supercode::Result<String> {
        Ok(format!(
            "{SUMMARY_MARKER}: span covered {} chars of transcript",
            span_text.len()
        ))
    }
    fn model_id(&self) -> &str {
        "fake-tr7-guarantor-model-v1"
    }
}

/// Every turn: a plain text reply (no tool calls), padded so each turn's
/// serialized bytes add up quickly toward the cost-guard floor.
struct PlainRepliesPadded {
    calls: AtomicUsize,
}
#[async_trait]
impl Provider for PlainRepliesPadded {
    async fn complete(
        &self,
        _req: &ChatRequest,
        _on_delta: &(dyn for<'a> Fn(&'a str) + Send + Sync),
    ) -> supercode::Result<(ChatMessage, Usage)> {
        let n = self.calls.fetch_add(1, Ordering::SeqCst);
        Ok((
            ChatMessage::assistant(format!("reply {n}: {}", filler(150))),
            Usage::default(),
        ))
    }
}

/// dev/02 + dev/04 (guarantor-path closure): a live agent run with a
/// recorder + `ReductionPolicy` (summaries ON, fake summarizer injected)
/// establishes a summarized `TurnsCleared` reduction; `verify_log` and
/// `invert` then run OFFLINE against the sidecar and reduction log reloaded
/// from disk. Both must pass clean; `invert` must restore the full original
/// cleared turns byte-exact, and the reloaded sidecar must carry ZERO bytes
/// of the summary text.
#[tokio::test]
async fn summarized_span_survives_live_agent_disk_reload_offline_verify_and_invert() {
    let dir = temp_dir("dev02-dev04");
    let store_dir = dir.join("store");
    let store = SessionStore::open(&store_dir).unwrap();
    let name = "tr7-dev02-dev04";
    let sidecar_path = store.sidecar_path(name);

    let config = Config::builder()
        .cwd(dir.clone())
        .compact_after_messages(8)
        .build();
    let mut agent = Agent::with_parts(
        config,
        Box::new(PlainRepliesPadded {
            calls: AtomicUsize::new(0),
        }),
        supercode::tools::ToolRegistry::new(),
    );

    let empty_session = Session::from_claude_code_str("").unwrap();
    let writer = SidecarWriter::create(&sidecar_path, &empty_session).unwrap();
    agent.set_recorder(writer);

    // Cost-guard knobs deliberately low: this test's job is exercising the
    // summarize -> record -> persist -> disk-reload -> verify/invert
    // pipeline end to end, not re-proving the floor's exact boundary
    // (`reduce_summaries.rs`'s dev/05 already covers that precisely) — a
    // trivially-cleared floor here just keeps the fixture's turn count
    // small.
    let policy = ReductionPolicy {
        summarize_cleared_turns: true,
        expected_summary_bytes: 10,
        summary_cost_floor_multiple: 2,
        ..ReductionPolicy::default()
    };
    agent.set_reduction_policy(policy.clone());
    agent.set_span_summarizer(FakeSummarizer);

    // 6 turns (mirrors `tr12_cap_supersession.rs`'s own turn count): enough
    // for `compact_after_messages(8)` to trigger and stay established.
    for i in 0..6 {
        let reply = agent
            .send(format!("turn {i}: {}", filler(150)))
            .await
            .unwrap();
        assert!(
            reply.starts_with("reply"),
            "unexpected reply at turn {i}: {reply}"
        );
    }

    let log_live = agent.reduction_log().clone();
    let cleared = log_live
        .reductions
        .iter()
        .find(|r| matches!(r.kind, ReductionKind::TurnsCleared { .. }))
        .expect("a TurnsCleared reduction must have been established");
    let (first, last, audit) = match &cleared.kind {
        ReductionKind::TurnsCleared {
            first,
            last,
            summary,
        } => (
            *first,
            *last,
            summary
                .as_ref()
                .expect("TR-7 dev/04: a summarized span must carry SpanSummary audit metadata"),
        ),
        _ => unreachable!(),
    };
    assert_eq!(audit.model_id, "fake-tr7-guarantor-model-v1");
    assert_eq!(
        audit.prompt_version,
        supercode::reduce::summarize::PROMPT_VERSION
    );
    assert!(
        cleared.placeholder.contains(SUMMARY_MARKER),
        "the live view's placeholder must carry the LLM summary text: {}",
        cleared.placeholder
    );
    assert!(
        cleared
            .placeholder
            .contains(&format!("expand_reduction(\"{}\")", cleared.id)),
        "the honesty banner must name this reduction's own id: {}",
        cleared.placeholder
    );

    // Persist the reduction log the way the CLI does.
    store.save_reduction_log(name, &log_live).unwrap();

    // --- OFFLINE from here: fresh reads from disk, no live Agent/Session ---
    let sidecar_jsonl = store
        .load_sidecar(name)
        .unwrap()
        .expect("sidecar must exist on disk");
    let sidecar = Session::from_sidecar_str(&sidecar_jsonl).unwrap();
    let log_reloaded = store
        .load_reduction_log(name)
        .unwrap()
        .expect("reduction log must exist on disk");

    assert_eq!(
        log_reloaded, log_live,
        "the reloaded-from-disk log must be identical to the live agent's log"
    );

    // THE CRUX (B10 / TR-7 dev/02): the reloaded SIDECAR itself — the
    // full-fidelity record `invert`/`expand_reduction` restore from — must
    // never contain one byte of the LLM summary text. It only ever exists in
    // the projected view's placeholder, never in the sidecar.
    let sidecar_text = serde_json::to_string(&sidecar.messages).unwrap();
    assert!(
        !sidecar_text.contains(SUMMARY_MARKER),
        "the reloaded sidecar must carry ZERO summary text: {sidecar_text}"
    );
    // Cross-check directly against the cleared range's own original content.
    for m in &sidecar.messages[first..=last] {
        let content = m.content.as_deref().unwrap_or("");
        assert!(
            !content.contains(SUMMARY_MARKER),
            "a cleared-range original message in the sidecar must never carry summary text: \
             {content}"
        );
    }

    // The exact primitive `cli/main.rs`'s `show-reductions`/`convert`/
    // `inspect` all call before doing anything else with a reduced session —
    // anchored on sidecar bytes alone, so it passes identically regardless of
    // TR-7's summary being present.
    verify_log(&log_reloaded, &sidecar)
        .expect("verify_log must pass clean against the reloaded-from-disk sidecar");

    // Re-projecting from the reloaded sidecar with its own log must not
    // invent new reductions and must keep reproducing the SAME (summarized)
    // placeholder verbatim — the established `TurnsCleared` range is a
    // singleton, reapplied forever, independent of whether a summarizer is
    // even installed on this second, offline pass.
    let (final_view, reprojected_log) = project_messages(&sidecar.messages, &policy, &log_reloaded);
    assert_eq!(
        reprojected_log, log_reloaded,
        "re-projecting from the reloaded sidecar with its own log must not invent new reductions"
    );
    let view_placeholder = final_view
        .iter()
        .find_map(|m| {
            supercode::reduce::reduction_id(m).and_then(|id| (id == cleared.id).then_some(m))
        })
        .and_then(|m| m.content.as_deref())
        .expect("the reprojected view must still carry the summarized placeholder");
    assert!(
        view_placeholder.contains(SUMMARY_MARKER),
        "{view_placeholder}"
    );

    let inverted = invert(&final_view, &log_reloaded, &sidecar)
        .expect("invert must pass clean against the reloaded-from-disk sidecar");
    assert_eq!(inverted.len(), sidecar.messages.len());

    // Byte-exact restore of the ORIGINAL cleared turns, from the
    // disk-reloaded sidecar — and no summary text anywhere in the result.
    for (i, (restored, original)) in inverted
        .iter()
        .zip(&sidecar.messages)
        .enumerate()
        .filter(|(i, _)| *i >= first && *i <= last)
    {
        assert_eq!(
            restored.content, original.content,
            "invert must restore cleared-range message {i} byte-exact from the reloaded sidecar"
        );
        let content = restored.content.as_deref().unwrap_or("");
        assert!(
            !content.contains(SUMMARY_MARKER),
            "invert must never leak summary text into a restored original: {content}"
        );
    }

    std::fs::remove_dir_all(&dir).ok();
}