use std::path::Path;
use std::process::{Command, Output};
fn godmode_bin() -> std::path::PathBuf {
if let Ok(path) = std::env::var("CARGO_BIN_EXE_godmode") {
return std::path::PathBuf::from(path);
}
let manifest = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
manifest
.parent()
.expect("crate parent")
.parent()
.expect("workspace root")
.join("target/debug/godmode")
}
fn run_godmode(dir: &Path, args: &[&str]) -> Output {
Command::new(godmode_bin())
.args(args)
.current_dir(dir)
.output()
.expect("run godmode")
}
fn scratch(name: &str) -> tempfile::TempDir {
let dir = tempfile::TempDir::new().expect("temp dir");
let init = Command::new("git")
.args(["init", "-q"])
.current_dir(dir.path())
.status()
.expect("git init");
assert!(init.success(), "git init must succeed for the scratch repo");
let commit = Command::new("git")
.args(["commit", "-q", "--allow-empty", "-m", name])
.current_dir(dir.path())
.status()
.expect("git commit");
assert!(
commit.success(),
"git commit must succeed for the scratch repo"
);
dir
}
fn trace_body(dir: &Path) -> String {
std::fs::read_to_string(dir.join(".ctx/godmode/traces/trace.jsonl"))
.expect("trace log must exist after an emit")
}
#[test]
fn skill_start_accepts_an_argument_that_looks_like_a_flag() {
let dir = scratch("hyphen-args");
let out = run_godmode(
dir.path(),
&[
"trace",
"emit",
"skill-start",
"--skill",
"demo",
"--helper",
"helpers/demo.nu",
"--args",
"--json",
"--args",
"-x",
],
);
assert!(
out.status.success(),
"emit must succeed: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(trace_body(dir.path()).contains("demo"));
}
#[test]
fn skill_start_prints_a_trace_id_the_close_call_accepts() {
let dir = scratch("round-trip");
let start = run_godmode(
dir.path(),
&[
"trace",
"emit",
"skill-start",
"--skill",
"demo",
"--helper",
"h.nu",
],
);
assert!(start.status.success());
let tid = String::from_utf8_lossy(&start.stdout).trim().to_string();
assert!(!tid.is_empty(), "skill-start must print a trace id");
assert!(
tid.starts_with("demo#"),
"id must lead with the skill: {tid}"
);
let close = run_godmode(
dir.path(),
&["trace", "emit", "skill-complete", "--trace-id", &tid],
);
assert!(
close.status.success(),
"closing with a minted id must succeed: {}",
String::from_utf8_lossy(&close.stderr)
);
assert!(trace_body(dir.path()).contains(&tid));
}
#[test]
fn skill_error_records_the_exit_code_and_stderr_tail() {
let dir = scratch("error");
let out = run_godmode(
dir.path(),
&[
"trace",
"emit",
"skill-error",
"--trace-id",
"demo#12345.0#h",
"--exit-code",
"7",
"--stderr-tail",
"boom",
],
);
assert!(out.status.success());
let body = trace_body(dir.path());
assert!(body.contains("boom"), "stderr tail must be recorded");
assert!(
body.contains("\"exit_code\":7"),
"exit code must be recorded"
);
}
#[test]
fn agent_start_records_the_slot_and_crate() {
let dir = scratch("slot");
let out = run_godmode(
dir.path(),
&[
"trace",
"emit",
"agent-start",
"--agent-id",
"explore",
"--slot",
"1",
"--crate",
"godmode-core",
],
);
assert!(out.status.success());
let body = trace_body(dir.path());
assert!(body.contains("explore"));
assert!(body.contains("\"slot\":\"1\""), "slot must be recorded");
assert!(body.contains("godmode-core"), "crate must be recorded");
}
#[test]
fn a_hyphen_leading_value_is_rejected_for_an_identifier_flag() {
let dir = scratch("hyphen-slot");
let out = run_godmode(
dir.path(),
&[
"trace",
"emit",
"agent-start",
"--agent-id",
"a",
"--slot",
"-1",
],
);
assert!(
!out.status.success(),
"a hyphen-leading slot must fail loudly rather than be swallowed"
);
}
#[test]
fn an_unparseable_trace_id_still_closes_without_failing() {
let dir = scratch("junk-id");
let out = run_godmode(
dir.path(),
&["trace", "emit", "skill-complete", "--trace-id", "garbage"],
);
assert!(
out.status.success(),
"a junk id must not fail the caller: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(trace_body(dir.path()).contains("unknown"));
}
#[test]
fn emitted_events_are_then_queryable_in_the_same_process_sequence() {
let dir = scratch("seam");
for args in [
vec![
"trace",
"emit",
"agent-start",
"--agent-id",
"explore",
"--slot",
"1",
],
vec![
"trace",
"emit",
"agent-start",
"--agent-id",
"explore",
"--slot",
"1",
],
] {
assert!(run_godmode(dir.path(), &args).status.success());
}
let out = run_godmode(dir.path(), &["trace", "--json", "summary"]);
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("\"explore\""),
"the unresolved agent must surface in summary: {stdout}"
);
}