Single-commit clean baseline after security scrub of niche-tells, project codenames, internal jargon, and contributor-email leaks. Contents: - 100 Rust crates (_primitives/_rust/) - 37 agent manifests (_manifests/) + generated specs (_generated/) - 67 user-invocable skills (skills/) - 33 hooks (hooks/) - Composition blocks (_blocks/) - Documentation (docs/, README.md) - TS adapter packages (_ts_packages/) - Assembler (_assembler/) - Roles (_roles/) - Templates (_templates/) - Forgejo CI (.forgejo/) Author: Denis Parfionovich <info@greendragon.info> License: see LICENSE.
216 lines
5.6 KiB
Rust
216 lines
5.6 KiB
Rust
//! spawn_smoke — integration tests for kei-spawn library API.
|
|
//!
|
|
//! These tests set `KEI_SPAWN_LEDGER_NOOP=1` so the ledger subprocess is a
|
|
//! no-op — we exercise the compose + prepare_agent + output shape path
|
|
//! without depending on a real `kei-ledger` binary being on PATH.
|
|
//!
|
|
//! Fixtures follow the same pattern as kei-agent-runtime's tests: write a
|
|
//! minimal `_roles/` + `_capabilities/` tree into a tempdir, a task.toml
|
|
//! referencing the role, then call `spawn_from_task` and assert the JSON
|
|
//! shape + on-disk artefacts.
|
|
|
|
use kei_spawn::{spawn_from_task, verify_agent};
|
|
use std::path::Path;
|
|
use tempfile::TempDir;
|
|
|
|
fn write_capability(root: &Path, cat: &str, slug: &str, body: &str) {
|
|
let dir = root.join("_capabilities").join(cat).join(slug);
|
|
std::fs::create_dir_all(&dir).unwrap();
|
|
std::fs::write(dir.join("text.md"), body).unwrap();
|
|
}
|
|
|
|
fn write_role(root: &Path, name: &str, toml: &str) {
|
|
std::fs::create_dir_all(root.join("_roles")).unwrap();
|
|
std::fs::write(root.join("_roles").join(format!("{name}.toml")), toml).unwrap();
|
|
}
|
|
|
|
fn write_task(root: &Path, toml: &str) -> std::path::PathBuf {
|
|
let path = root.join("task.toml");
|
|
std::fs::write(&path, toml).unwrap();
|
|
path
|
|
}
|
|
|
|
fn minimal_kit(root: &Path) {
|
|
write_capability(root, "policy", "no-git-ops", "## Never git.\n");
|
|
write_capability(root, "output", "report-format", "## Report fields.\n");
|
|
write_role(
|
|
root,
|
|
"edit-local",
|
|
r#"
|
|
[role]
|
|
name = "edit-local"
|
|
spawnable = true
|
|
claude-subagent-type = "code-implementer"
|
|
|
|
[capabilities]
|
|
required = ["policy::no-git-ops", "output::report-format"]
|
|
"#,
|
|
);
|
|
}
|
|
|
|
fn set_noop() {
|
|
std::env::set_var("KEI_SPAWN_LEDGER_NOOP", "1");
|
|
}
|
|
|
|
#[test]
|
|
fn spawn_happy_path_emits_full_output() {
|
|
set_noop();
|
|
let tmp = TempDir::new().unwrap();
|
|
let root = tmp.path();
|
|
minimal_kit(root);
|
|
|
|
let task_path = write_task(
|
|
root,
|
|
r#"
|
|
[task]
|
|
role = "edit-local"
|
|
|
|
[body]
|
|
text = "Port kei-forge templating to pure Rust."
|
|
"#,
|
|
);
|
|
|
|
let out = spawn_from_task(&task_path, root).expect("spawn should succeed");
|
|
assert!(out.agent_id.starts_with("ag-edit-local-"), "id: {}", out.agent_id);
|
|
assert_eq!(out.role, "edit-local");
|
|
assert_eq!(out.subagent_type, "code-implementer");
|
|
assert_eq!(out.isolation.as_deref(), Some("worktree"));
|
|
assert!(out.prompt.contains("Port kei-forge"));
|
|
assert!(out.prompt.contains("Never git"));
|
|
assert_eq!(out.spec_sha.len(), 64, "sha256 hex = 64 chars: {}", out.spec_sha);
|
|
assert!(out.branch.starts_with("agent/"));
|
|
assert!(out.branch.contains(&out.agent_id));
|
|
assert!(out.next_step.contains("code-implementer"));
|
|
assert!(out.prompt_path.is_file());
|
|
assert!(out.task_path.is_file());
|
|
assert!(!out.dna.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn spawn_preserves_explicit_agent_id() {
|
|
set_noop();
|
|
let tmp = TempDir::new().unwrap();
|
|
let root = tmp.path();
|
|
minimal_kit(root);
|
|
|
|
let task_path = write_task(
|
|
root,
|
|
r#"
|
|
[task]
|
|
role = "edit-local"
|
|
agent-id = "ag-edit-local-explicit-12345"
|
|
|
|
[body]
|
|
text = "Explicit id test."
|
|
"#,
|
|
);
|
|
|
|
let out = spawn_from_task(&task_path, root).expect("spawn should succeed");
|
|
assert_eq!(out.agent_id, "ag-edit-local-explicit-12345");
|
|
assert_eq!(out.branch, "agent/ag-edit-local-explicit-12345");
|
|
}
|
|
|
|
#[test]
|
|
fn spawn_rejects_unknown_role() {
|
|
set_noop();
|
|
let tmp = TempDir::new().unwrap();
|
|
let root = tmp.path();
|
|
|
|
let task_path = write_task(
|
|
root,
|
|
r#"
|
|
[task]
|
|
role = "does-not-exist"
|
|
|
|
[body]
|
|
text = "x"
|
|
"#,
|
|
);
|
|
|
|
let err = spawn_from_task(&task_path, root).expect_err("unknown role must fail");
|
|
let msg = format!("{err:#}");
|
|
assert!(
|
|
msg.contains("role") || msg.contains("does-not-exist"),
|
|
"error should reference the role: {msg}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn spawn_refuses_non_spawnable_role() {
|
|
set_noop();
|
|
let tmp = TempDir::new().unwrap();
|
|
let root = tmp.path();
|
|
|
|
write_role(
|
|
root,
|
|
"git-ops",
|
|
r#"
|
|
[role]
|
|
name = "git-ops"
|
|
spawnable = false
|
|
|
|
[capabilities]
|
|
required = []
|
|
"#,
|
|
);
|
|
|
|
let task_path = write_task(
|
|
root,
|
|
r#"
|
|
[task]
|
|
role = "git-ops"
|
|
|
|
[body]
|
|
text = "should refuse"
|
|
"#,
|
|
);
|
|
|
|
let err = spawn_from_task(&task_path, root).expect_err("git-ops must be refused");
|
|
let msg = format!("{err:#}");
|
|
assert!(msg.contains("RULE 0.13"), "refusal must cite RULE 0.13: {msg}");
|
|
}
|
|
|
|
#[test]
|
|
fn verify_fails_when_task_missing() {
|
|
set_noop();
|
|
let tmp = TempDir::new().unwrap();
|
|
let root = tmp.path();
|
|
minimal_kit(root);
|
|
|
|
let worktree = tmp.path().join("wt");
|
|
std::fs::create_dir_all(&worktree).unwrap();
|
|
|
|
let err = verify_agent("ag-does-not-exist", &worktree, root)
|
|
.expect_err("missing task.toml must fail");
|
|
let msg = format!("{err:#}");
|
|
assert!(msg.contains("task.toml not found"), "msg: {msg}");
|
|
}
|
|
|
|
#[test]
|
|
fn spawn_then_verify_end_to_end() {
|
|
set_noop();
|
|
let tmp = TempDir::new().unwrap();
|
|
let root = tmp.path();
|
|
minimal_kit(root);
|
|
|
|
let task_path = write_task(
|
|
root,
|
|
r#"
|
|
[task]
|
|
role = "edit-local"
|
|
|
|
[body]
|
|
text = "Round-trip test."
|
|
"#,
|
|
);
|
|
|
|
let spawned = spawn_from_task(&task_path, root).expect("spawn");
|
|
// worktree doesn't need real content — the two capabilities in
|
|
// minimal_kit have no verify implementations, so the report is clean.
|
|
let worktree = tmp.path().join("wt");
|
|
std::fs::create_dir_all(&worktree).unwrap();
|
|
|
|
let verified = verify_agent(&spawned.agent_id, &worktree, root).expect("verify");
|
|
assert_eq!(verified.agent_id, spawned.agent_id);
|
|
assert!(verified.is_clean, "failed: {:?}", verified.failed);
|
|
}
|