supercode-harness 0.4.13

The optional native Supercode agent and tool harness
Documentation
//! P5-11 (COMPOSABLE-HARNESS-DESIGN.md §2 modules 28/29 `lsp`/`formatters`):
//! end-to-end seam-composition tests driven through `Agent::send`, proving
//! the shared D-5 write-path observer chain (`checkpoint -> formatters ->
//! lsp`, installed by `crate::agent::build_tool_context`) actually composes
//! in the required order, that C10 diff-back surfaces the FORMATTED
//! content in the tool result the model sees, and that `checkpoint`'s pre-
//! image capture + restore correctness survive `formatters`/`lsp` also
//! being on.
//!
//! **LIVE-AGENT-TEST SAFETY.** Every test drives `Agent` through a
//! scripted MOCK `Provider` (`checkpoint_engine.rs`'s own idiom) and a
//! tiny local stub formatter (`sh`/`tr`, harmless) / stub LSP server
//! (a local Python script speaking minimal LSP over stdio) — never a real
//! model endpoint, never a real language-server download.

use async_trait::async_trait;
use supercode_harness::formatters::FormatterSpec;
use supercode_harness::lsp::LspServerSpec;
use supercode_harness::{
    Agent, ChatMessage, ChatRequest, Config, FunctionCall, Provider, Role, ToolCall, Usage,
};

fn tool_call(id: &str, name: &str, args: serde_json::Value) -> ToolCall {
    ToolCall {
        id: id.to_string(),
        kind: "function".to_string(),
        function: FunctionCall {
            name: name.to_string(),
            arguments: args.to_string(),
        },
    }
}

fn assistant_with_call(call: ToolCall) -> ChatMessage {
    ChatMessage {
        role: Role::Assistant,
        content: None,
        content_parts: None,
        tool_calls: Some(vec![call]),
        tool_call_id: None,
        name: None,
        metadata: Default::default(),
    }
}

fn tmp(tag: &str) -> std::path::PathBuf {
    let dir = std::env::temp_dir().join(format!(
        "supercode-lsp-fmt-seam-{tag}-{}-{}",
        std::process::id(),
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_nanos()
    ));
    std::fs::create_dir_all(&dir).unwrap();
    dir
}

fn python() -> Option<String> {
    for candidate in ["python3", "python"] {
        if std::process::Command::new(candidate)
            .arg("--version")
            .output()
            .is_ok()
        {
            return Some(candidate.to_string());
        }
    }
    None
}

/// Same stub server idiom as `crates/harness/src/lsp.rs`'s own unit tests
/// (duplicated here since integration tests can't reach that module's
/// private `#[cfg(test)]` items): flags a diagnostic iff the document text
/// contains the UPPERCASE marker `TODO` — deliberately case-sensitive, so
/// whether it fires proves whether this write's diagnostics ran BEFORE or
/// AFTER the uppercasing formatter.
const STUB_SERVER_PY: &str = r#"
import sys, json

def read_message():
    headers = {}
    while True:
        line = sys.stdin.buffer.readline()
        if not line:
            return None
        line = line.decode("utf-8", "replace").rstrip("\r\n")
        if line == "":
            break
        if ":" in line:
            k, v = line.split(":", 1)
            headers[k.strip()] = v.strip()
    length = int(headers.get("Content-Length", "0"))
    body = sys.stdin.buffer.read(length)
    return json.loads(body.decode("utf-8"))

def write_message(obj):
    body = json.dumps(obj).encode("utf-8")
    sys.stdout.buffer.write(("Content-Length: %d\r\n\r\n" % len(body)).encode("utf-8"))
    sys.stdout.buffer.write(body)
    sys.stdout.buffer.flush()

def diagnostics_for(text):
    if "TODO" in text:
        return [{
            "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 4}},
            "severity": 1,
            "message": "uppercase TODO marker present",
        }]
    return []

while True:
    msg = read_message()
    if msg is None:
        break
    method = msg.get("method")
    if method == "initialize":
        write_message({"jsonrpc": "2.0", "id": msg["id"], "result": {"capabilities": {}}})
    elif method == "initialized":
        pass
    elif method in ("textDocument/didOpen", "textDocument/didChange"):
        params = msg["params"]
        if method == "textDocument/didOpen":
            uri = params["textDocument"]["uri"]
            text = params["textDocument"]["text"]
        else:
            uri = params["textDocument"]["uri"]
            text = params["contentChanges"][0]["text"]
        write_message({
            "jsonrpc": "2.0",
            "method": "textDocument/publishDiagnostics",
            "params": {"uri": uri, "diagnostics": diagnostics_for(text)},
        })
    elif method == "shutdown":
        write_message({"jsonrpc": "2.0", "id": msg["id"], "result": None})
    elif method == "exit":
        break
"#;

struct ScriptedWrite {
    calls: std::sync::atomic::AtomicUsize,
    file_name: String,
    content: String,
}

#[async_trait]
impl Provider for ScriptedWrite {
    async fn complete(
        &self,
        req: &ChatRequest,
        _on_delta: &(dyn for<'a> Fn(&'a str) + Send + Sync),
    ) -> supercode_harness::Result<(ChatMessage, Usage)> {
        match self.calls.fetch_add(1, std::sync::atomic::Ordering::SeqCst) {
            0 => Ok((
                assistant_with_call(tool_call(
                    "call_1",
                    "write_file",
                    serde_json::json!({"path": self.file_name, "content": self.content}),
                )),
                Usage::default(),
            )),
            1 => Ok((ChatMessage::assistant("done"), Usage::default())),
            n => panic!("unexpected provider call {n}: {:?}", req.messages.last()),
        }
    }
}

fn last_tool_result(agent: &Agent) -> String {
    agent
        .history()
        .iter()
        .rev()
        .find(|m| m.role == Role::Tool)
        .and_then(|m| m.content.clone())
        .expect("a tool-result message must exist")
}

fn upper_formatter_spec() -> (String, FormatterSpec) {
    (
        "upper".to_string(),
        FormatterSpec {
            command: "sh".to_string(),
            args: vec!["-c".to_string(), "tr 'a-z' 'A-Z'".to_string()],
            extensions: vec![".rs".to_string()],
        },
    )
}

fn stub_lsp_spec(project: &std::path::Path) -> (String, LspServerSpec) {
    let script = project.join("stub_lsp.py");
    std::fs::write(&script, STUB_SERVER_PY).unwrap();
    (
        "stub".to_string(),
        LspServerSpec {
            command: python().expect("python3/python required for this test"),
            args: vec![script.to_string_lossy().into_owned()],
            extensions: vec![".rs".to_string()],
        },
    )
}

/// THE central ordering proof: the model writes lowercase `todo`
/// (`lsp`'s stub server only flags UPPERCASE `TODO`), the formatter
/// uppercases the whole file, and the stub server's diagnostics — which
/// must reflect the FINAL formatted file, not the model's pre-format draft
/// — DO fire. If the chain ever ran `lsp` before `formatters`, this test
/// would see NO diagnostics annotation and fail.
#[tokio::test]
async fn formatter_runs_before_lsp_so_diagnostics_see_the_formatted_file() {
    let Some(_py) = python() else {
        eprintln!("skipping: no python3/python on PATH");
        return;
    };
    let project = tmp("order-project");
    let shadow = tmp("order-shadow");
    let (fmt_name, fmt_spec) = upper_formatter_spec();
    let (lsp_name, lsp_spec) = stub_lsp_spec(&project);

    let config = Config::builder()
        .cwd(project.clone())
        .checkpoint_enabled(true)
        .checkpoint_dir(shadow.clone())
        .formatters_enabled(true)
        .formatters(vec![(fmt_name, fmt_spec)])
        .formatters_diff_back(true)
        .lsp_enabled(true)
        .lsp_servers(vec![(lsp_name, lsp_spec)])
        .lsp_timeout_secs(5)
        .build();

    let mut agent = Agent::with_provider(
        config,
        Box::new(ScriptedWrite {
            calls: std::sync::atomic::AtomicUsize::new(0),
            file_name: "f.rs".to_string(),
            content: "// todo: fix this\nfn main() {}\n".to_string(),
        }),
    );
    let reply = agent.send("write f.rs").await.unwrap();
    assert_eq!(reply, "done");

    let result = last_tool_result(&agent);
    // C10 diff-back: the formatter's change is annotated, and it shows the
    // FORMATTED (uppercase) content, not the model's raw lowercase input.
    assert!(
        result.contains("upper"),
        "formatter name in result: {result}"
    );
    assert!(
        result.contains("FN MAIN"),
        "diff-back must show the FORMATTED content: {result}"
    );
    // The lsp diagnostics annotation must ALSO be present, and it only
    // fires on the case-sensitive `TODO` marker the formatter produced —
    // proving lsp ran AFTER formatters on the FINAL file.
    assert!(
        result.contains("stub") && result.contains("uppercase TODO marker present"),
        "lsp diagnostics must reflect the POST-format file: {result}"
    );

    let on_disk = std::fs::read_to_string(project.join("f.rs")).unwrap();
    assert_eq!(on_disk, "// TODO: FIX THIS\nFN MAIN() {}\n");

    // checkpoint composition: the captured PRE-image must be the model's
    // ORIGINAL (pre-format) content, never the formatted one — restore
    // must undo back to exactly what the model wrote, not the
    // formatter's output.
    let observer = agent.checkpoint_observer().expect("checkpoint is enabled");
    let id = observer
        .current()
        .expect("a checkpoint is open for this turn");
    let manifest = observer.store().manifest(&id).unwrap();
    assert_eq!(manifest.files.len(), 1);
    // The file didn't exist before this write (`write_file` created it),
    // so its pre-image is "did not exist" (`blob: None`) — restoring must
    // DELETE it, giving back the pre-write (empty-project) state exactly,
    // regardless of the formatter's post-write rewrite.
    assert!(
        manifest.files[0].blob.is_none(),
        "the file didn't exist before this write; checkpoint must record 'did not exist', \
         not the formatter's output"
    );
    observer.store().restore(&id, &project, &[]).unwrap();
    assert!(
        !project.join("f.rs").exists(),
        "restore must undo the whole turn (including the formatter's rewrite), \
         giving back the true pre-write state"
    );

    if let Some(lsp) = agent.lsp_manager() {
        lsp.shutdown_all().await;
    }
    std::fs::remove_dir_all(&project).ok();
    std::fs::remove_dir_all(&shadow).ok();
}

/// `diff_back = false`: the formatter still reformats the file (composition
/// with `lsp` is unaffected — diagnostics still fire on the final file),
/// but the RESULT text carries no formatter annotation (the C10-unsafe,
/// non-default mode).
#[tokio::test]
async fn diff_back_false_still_composes_with_lsp_but_omits_the_formatter_annotation() {
    let Some(_py) = python() else {
        eprintln!("skipping: no python3/python on PATH");
        return;
    };
    let project = tmp("diffback-off-project");
    let (fmt_name, fmt_spec) = upper_formatter_spec();
    let (lsp_name, lsp_spec) = stub_lsp_spec(&project);

    let config = Config::builder()
        .cwd(project.clone())
        .formatters_enabled(true)
        .formatters(vec![(fmt_name, fmt_spec)])
        .formatters_diff_back(false)
        .lsp_enabled(true)
        .lsp_servers(vec![(lsp_name, lsp_spec)])
        .lsp_timeout_secs(5)
        .build();

    let mut agent = Agent::with_provider(
        config,
        Box::new(ScriptedWrite {
            calls: std::sync::atomic::AtomicUsize::new(0),
            file_name: "f.rs".to_string(),
            content: "// todo\n".to_string(),
        }),
    );
    agent.send("write f.rs").await.unwrap();

    let result = last_tool_result(&agent);
    assert!(
        !result.contains("Formatter `upper`"),
        "diff_back=false must omit the formatter annotation: {result}"
    );
    assert!(
        result.contains("uppercase TODO marker present"),
        "lsp diagnostics must still fire on the (silently) reformatted file: {result}"
    );
    let on_disk = std::fs::read_to_string(project.join("f.rs")).unwrap();
    assert_eq!(on_disk, "// TODO\n", "the file must still be reformatted");

    if let Some(lsp) = agent.lsp_manager() {
        lsp.shutdown_all().await;
    }
    std::fs::remove_dir_all(&project).ok();
}

/// Default-off byte identity for BOTH modules together: with
/// `lsp_enabled`/`formatters_enabled` both `false` (even with servers/
/// formatters CONFIGURED — proving the gate is `enabled`, not "list is
/// non-empty"), the write path is untouched: no process spawned, no
/// annotation appended.
#[tokio::test]
async fn both_off_by_default_is_a_true_noop_even_with_servers_configured() {
    let project = tmp("both-off-project");
    let (fmt_name, fmt_spec) = upper_formatter_spec();
    let lsp_spec = (
        "stub".to_string(),
        LspServerSpec {
            command: "does-not-exist-and-must-never-be-spawned".to_string(),
            args: vec![],
            extensions: vec![".rs".to_string()],
        },
    );
    let config = Config::builder()
        .cwd(project.clone())
        .formatters(vec![(fmt_name, fmt_spec)]) // configured, but formatters_enabled defaults false
        .lsp_servers(vec![lsp_spec]) // configured, but lsp_enabled defaults false
        .build();
    assert!(!config.formatters_enabled);
    assert!(!config.lsp_enabled);

    let mut agent = Agent::with_provider(
        config,
        Box::new(ScriptedWrite {
            calls: std::sync::atomic::AtomicUsize::new(0),
            file_name: "f.rs".to_string(),
            content: "// todo\n".to_string(),
        }),
    );
    agent.send("write f.rs").await.unwrap();

    let result = last_tool_result(&agent);
    assert_eq!(result, "Wrote 8 bytes to f.rs");
    let on_disk = std::fs::read_to_string(project.join("f.rs")).unwrap();
    assert_eq!(on_disk, "// todo\n", "no formatter must ever have run");
    assert!(agent.lsp_manager().is_none());

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