KeiSeiKit-1.0/_primitives/_rust/kei-spawn/src/main.rs
Parfii-bot a4e667de10 KeiSeiKit-public — clean state
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.
2026-05-01 12:09:03 +08:00

174 lines
5.3 KiB
Rust

//! kei-spawn — CLI dispatcher.
//!
//! Four subcommands:
//! - `spawn <task.toml>` — prepare invocation + ledger fork, emit JSON
//! - `drive <task.toml>` — spawn + attempt driver invocation (v0.1: stub,
//! returns exit 64 NotImplemented after emitting SpawnOutput JSON)
//! - `verify <agent-id> <worktree>` — run verify pipeline, update ledger
//! - `list-pending` — forward `kei-ledger list --status running`
//!
//! Exit codes:
//! 0 success (spawn, verify-clean, list-pending)
//! 1 generic failure (any Err from the pipeline)
//! 2 verify-failed (capabilities failed but pipeline ran)
//! 64 drive NotImplemented (v0.1 stub path)
use clap::{Parser, Subcommand};
use std::path::PathBuf;
use std::process::ExitCode;
use kei_spawn::{
drive_with, ledger_sh, not_implemented_message, spawn_from_task, spawn_with_pipeline,
verify_agent, DriveError, ManualDriver, PipelineChain, SpawnOutput,
};
use serde::Serialize;
#[derive(Parser)]
#[command(
name = "kei-spawn",
version,
about = "Automation envelope: prepare + ledger fork + verify (RULE 0.13-compliant)"
)]
struct Cli {
#[command(subcommand)]
cmd: Cmd,
}
#[derive(Subcommand)]
enum Cmd {
/// Prepare an Agent-tool invocation + register ledger row.
Spawn {
/// Path to task.toml.
task: PathBuf,
/// kit root (default: cwd).
#[arg(long)]
kit_root: Option<PathBuf>,
/// Also derive downstream handoff chain from role's `[pipeline]`
/// section + scaffold stub task files for each step.
#[arg(long)]
pipeline: bool,
},
/// Spawn + invoke driver (v0.1: stub — emits SpawnOutput then exit 64).
Drive {
/// Path to task.toml.
task: PathBuf,
/// kit root (default: cwd).
#[arg(long)]
kit_root: Option<PathBuf>,
},
/// Run verify pipeline + update ledger status.
Verify {
/// agent-id previously emitted by `kei-spawn spawn`.
agent_id: String,
/// Worktree path reported by the Claude harness on agent return.
worktree: PathBuf,
#[arg(long)]
kit_root: Option<PathBuf>,
},
/// Show all running ledger rows.
ListPending,
}
fn main() -> ExitCode {
let cli = Cli::parse();
match cli.cmd {
Cmd::Spawn { task, kit_root, pipeline } => run_spawn(task, kit_root, pipeline),
Cmd::Drive { task, kit_root } => run_drive(task, kit_root),
Cmd::Verify { agent_id, worktree, kit_root } => {
run_verify(agent_id, worktree, kit_root)
}
Cmd::ListPending => run_list_pending(),
}
}
#[derive(Serialize)]
struct SpawnWithPipelineJson<'a> {
#[serde(flatten)]
spawn: &'a SpawnOutput,
pipeline: &'a PipelineChain,
}
fn run_spawn(task: PathBuf, kit_root: Option<PathBuf>, pipeline: bool) -> ExitCode {
let kit = kit_root_or_cwd(kit_root);
if pipeline {
match spawn_with_pipeline(&task, &kit) {
Ok((out, chain)) => emit_json(&SpawnWithPipelineJson {
spawn: &out,
pipeline: &chain,
}),
Err(e) => err("spawn --pipeline", e),
}
} else {
match spawn_from_task(&task, &kit) {
Ok(out) => emit_json(&out),
Err(e) => err("spawn", e),
}
}
}
fn run_drive(task: PathBuf, kit_root: Option<PathBuf>) -> ExitCode {
let kit = kit_root_or_cwd(kit_root);
let out = match spawn_from_task(&task, &kit) {
Ok(o) => o,
Err(e) => return err("drive", e),
};
// Always emit SpawnOutput JSON first so callers can pipe it regardless
// of the driver outcome. Drive-only failure modes come via stderr.
if emit_json(&out) != ExitCode::SUCCESS {
return ExitCode::from(1);
}
dispatch_driver(&out)
}
fn dispatch_driver(out: &SpawnOutput) -> ExitCode {
let driver = ManualDriver;
match drive_with(&driver, &out.prompt, &out.subagent_type, out.isolation.as_deref()) {
Ok(_) => ExitCode::SUCCESS,
Err(DriveError::NotImplemented { .. }) => {
eprintln!("kei-spawn drive: {}", not_implemented_message());
ExitCode::from(64)
}
Err(e) => err("drive", e),
}
}
fn run_verify(agent_id: String, worktree: PathBuf, kit_root: Option<PathBuf>) -> ExitCode {
let kit = kit_root_or_cwd(kit_root);
match verify_agent(&agent_id, &worktree, &kit) {
Ok(out) => {
let code = if out.is_clean { ExitCode::SUCCESS } else { ExitCode::from(2) };
let _ = emit_json(&out);
code
}
Err(e) => err("verify", e),
}
}
fn run_list_pending() -> ExitCode {
match ledger_sh::list_running() {
Ok(s) => {
print!("{s}");
ExitCode::SUCCESS
}
Err(e) => err("list-pending", e),
}
}
fn emit_json<T: serde::Serialize>(v: &T) -> ExitCode {
match serde_json::to_string_pretty(v) {
Ok(s) => {
println!("{s}");
ExitCode::SUCCESS
}
Err(e) => err("serialize json", e),
}
}
fn kit_root_or_cwd(arg: Option<PathBuf>) -> PathBuf {
arg.unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")))
}
fn err(stage: &str, e: impl std::fmt::Display) -> ExitCode {
eprintln!("kei-spawn {stage}: {e}");
ExitCode::from(1)
}