supercode-harness 0.4.20

The optional native Supercode agent and tool harness
Documentation
//! UNI-7 acceptance: repo-family discovery scope (dev/03) and the coverage
//! receipt (dev/04), driven through the real `HarnessCatalog::discover_page`.
//!
//! Fixture per the SUP-54 decision: a main checkout, two sibling worktrees
//! (`.git` pointer files into `<main>/.git/worktrees/<name>`), and one
//! unrelated repository — plus a same-origin CLONE for the tie-breaker rule.

use std::fs;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};

use supercode_harness::{DiscoveryQuery, HarnessCatalog, HarnessHomes, HarnessId};

fn temp_dir(label: &str) -> PathBuf {
    let nonce = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_nanos();
    let path = std::env::temp_dir().join(format!(
        "supercode-uni7-{label}-{}-{nonce}",
        std::process::id()
    ));
    fs::create_dir_all(&path).unwrap();
    path
}

/// A minimal main checkout: `.git/` directory with a config declaring origin.
fn make_main_repo(root: &Path, origin: &str) {
    let git = root.join(".git");
    fs::create_dir_all(git.join("worktrees")).unwrap();
    fs::write(
        git.join("config"),
        format!("[core]\n\trepositoryformatversion = 0\n[remote \"origin\"]\n\turl = {origin}\n\tfetch = +refs/heads/*:refs/remotes/origin/*\n"),
    )
    .unwrap();
}

/// A linked worktree: `.git` FILE pointing into the main repo's worktrees dir.
fn make_worktree(root: &Path, main: &Path, name: &str) {
    fs::create_dir_all(root).unwrap();
    let slot = main.join(".git/worktrees").join(name);
    fs::create_dir_all(&slot).unwrap();
    fs::write(root.join(".git"), format!("gitdir: {}\n", slot.display())).unwrap();
}

/// One claude-code session whose recorded cwd is `cwd`.
fn write_session(home: &Path, id: &str, cwd: &Path, at_ms: u64) {
    let project = home.join("projects/p");
    fs::create_dir_all(&project).unwrap();
    let line = serde_json::json!({
        "type": "user",
        "sessionId": id,
        "cwd": cwd.to_string_lossy(),
        "timestamp": format!("2026-08-30T00:00:{:02}.000Z", at_ms / 1000 % 60),
        "message": {"role": "user", "content": [{"type": "text", "text": format!("hello from {id}")}]}
    });
    fs::write(project.join(format!("{id}.jsonl")), format!("{line}\n")).unwrap();
    let stamp = SystemTime::UNIX_EPOCH + std::time::Duration::from_millis(at_ms);
    let file = fs::File::options()
        .append(true)
        .open(project.join(format!("{id}.jsonl")))
        .unwrap();
    file.set_modified(stamp).unwrap();
}

struct World {
    home: PathBuf,
    main: PathBuf,
    worktree_a: PathBuf,
    worktree_b: PathBuf,
    clone: PathBuf,
    unrelated: PathBuf,
}

fn build_world() -> World {
    let base = temp_dir("family");
    let main = base.join("repo-main");
    let worktree_a = base.join("repo-wt-a");
    let worktree_b = base.join("repo-wt-b");
    let clone = base.join("repo-clone");
    let unrelated = base.join("other-repo");
    fs::create_dir_all(&main).unwrap();
    make_main_repo(&main, "git@example.com:volter/product.git");
    make_worktree(&worktree_a, &main, "wt-a");
    make_worktree(&worktree_b, &main, "wt-b");
    fs::create_dir_all(&clone).unwrap();
    make_main_repo(&clone, "git@example.com:volter/product.git");
    fs::create_dir_all(&unrelated).unwrap();
    make_main_repo(&unrelated, "git@example.com:volter/other.git");

    let home = base.join("claude-home");
    write_session(&home, "s-main", &main, 5_000);
    write_session(&home, "s-wt-a", &worktree_a, 4_000);
    write_session(&home, "s-wt-b", &worktree_b, 3_000);
    write_session(&home, "s-clone", &clone, 2_000);
    write_session(&home, "s-unrelated", &unrelated, 1_000);
    World {
        home,
        main,
        worktree_a,
        worktree_b,
        clone,
        unrelated,
    }
}

fn family_query(world: &World, scope: &Path) -> DiscoveryQuery {
    DiscoveryQuery {
        harnesses: vec![HarnessId::new(HarnessId::CLAUDE_CODE)],
        homes: HarnessHomes {
            claude_code: world.home.clone(),
            ..HarnessHomes::default()
        },
        workspace_family: Some(scope.to_path_buf()),
        ..DiscoveryQuery::default()
    }
}

#[test]
fn family_scope_joins_main_and_worktrees_without_admitting_unrelated_repos() {
    let world = build_world();
    // Queried FROM a worktree (the kaizen shape): the family must contain the
    // main checkout, both worktrees, and the same-origin clone (tie-breaker),
    // and must exclude the unrelated repository.
    let page = HarnessCatalog::new()
        .discover_page(&family_query(&world, &world.worktree_a))
        .unwrap();
    let ids: Vec<&str> = page
        .sessions
        .iter()
        .map(|s| s.locator.session_id.as_str())
        .collect();
    assert_eq!(ids, ["s-main", "s-wt-a", "s-wt-b", "s-clone"], "{ids:?}");

    // Queried from the unrelated repo: only its own session.
    let page = HarnessCatalog::new()
        .discover_page(&family_query(&world, &world.unrelated))
        .unwrap();
    let ids: Vec<&str> = page
        .sessions
        .iter()
        .map(|s| s.locator.session_id.as_str())
        .collect();
    assert_eq!(ids, ["s-unrelated"]);

    // Exact-cwd matching stays available and narrower.
    let mut exact = family_query(&world, &world.main);
    exact.workspace_family = None;
    exact.workspace = Some(world.worktree_b.clone());
    let page = HarnessCatalog::new().discover_page(&exact).unwrap();
    let ids: Vec<&str> = page
        .sessions
        .iter()
        .map(|s| s.locator.session_id.as_str())
        .collect();
    assert_eq!(ids, ["s-wt-b"]);
}

#[test]
fn receipt_reports_window_bounds_counts_and_truncation() {
    let world = build_world();
    let mut query = family_query(&world, &world.main);
    query.updated_after_ms = Some(2_500);
    query.limit = Some(2);
    let page = HarnessCatalog::new().discover_page(&query).unwrap();

    // Window admits s-main(5000), s-wt-a(4000), s-wt-b(3000); the limit caps
    // the page at two, and the receipt must say so explicitly.
    assert_eq!(page.sessions.len(), 2);
    let receipt = &page.receipt;
    assert_eq!(receipt.requested_after_ms, Some(2_500));
    assert_eq!(receipt.requested_before_ms, None);
    assert_eq!(receipt.requested_limit, Some(2));
    assert_eq!(receipt.returned, 2);
    assert_eq!(receipt.total_matched, 3);
    assert!(receipt.truncated);
    assert!(page.next_cursor.is_some());
    assert_eq!(receipt.newest_returned_ms, Some(5_000));
    assert_eq!(receipt.oldest_returned_ms, Some(4_000));

    // Resuming from the cursor drains the remainder and the receipt closes.
    query.cursor = page.next_cursor.clone();
    let rest = HarnessCatalog::new().discover_page(&query).unwrap();
    assert_eq!(rest.receipt.returned, 1);
    assert!(!rest.receipt.truncated);
    assert!(rest.next_cursor.is_none());
    assert_eq!(rest.receipt.oldest_returned_ms, Some(3_000));
}