KeiSeiKit-1.0/_primitives/_rust/kei-decision/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

163 lines
5.7 KiB
Rust

//! kei-decision CLI entry point — clap parse + dispatch only.
//!
//! Each subcommand has a thin `run_*` function declared inline below. Heavy
//! logic lives in the library modules (`parser`, `classifier`, `ranker`,
//! `emitter`, `executor`, `ledger`, `sleep_link`, `graph`).
use clap::Parser;
use kei_decision::cli::{Cli, Cmd};
use kei_decision::{
classify, emit_task_toml, execute_action, merge_graphs, parse_master_report, pre_fork_ledger,
rank_actions, ParseError, RankedAction,
};
use serde::Serialize;
use std::path::PathBuf;
use std::process::ExitCode;
fn main() -> ExitCode {
let cli = Cli::parse();
match cli.cmd {
Cmd::Parse { master } => run_parse(master),
Cmd::Rank { master, limit, markdown } => run_rank(master, limit, markdown),
Cmd::Plan { master, out } => run_plan(master, out),
Cmd::Execute { master, dry_run, limit, no_ledger } => {
run_execute(master, dry_run, limit, no_ledger)
}
Cmd::Link { research_dir, graph_out } => run_link(research_dir, graph_out),
}
}
fn run_parse(master: PathBuf) -> ExitCode {
match parse_master_report(&master) {
Ok(actions) => {
let kinds: Vec<_> = actions.iter().map(classify).collect();
let with_kind: Vec<_> = actions.iter().zip(kinds.iter()).map(|(a, k)| WithKind { raw: a, kind: *k }).collect();
print_json_or_err(&with_kind)
}
Err(e) => parse_err_to_exit(&e),
}
}
fn run_rank(master: PathBuf, limit: Option<usize>, markdown: bool) -> ExitCode {
let ranked = match rank_pipeline(&master, limit) {
Ok(r) => r,
Err(e) => return parse_err_to_exit(&e),
};
if markdown { print_markdown_table(&ranked); ExitCode::SUCCESS }
else { print_json_or_err(&ranked) }
}
fn run_plan(master: PathBuf, out: PathBuf) -> ExitCode {
let ranked = match rank_pipeline(&master, None) {
Ok(r) => r,
Err(e) => return parse_err_to_exit(&e),
};
let mut paths = Vec::new();
for action in &ranked {
match emit_task_toml(action, &out, &master) {
Ok(emit) => paths.push(emit),
Err(e) => return generic_err("plan", &e),
}
}
print_json_or_err(&paths)
}
fn run_execute(master: PathBuf, dry_run: bool, limit: Option<usize>, no_ledger: bool) -> ExitCode {
let ranked = match rank_pipeline(&master, limit) {
Ok(r) => r,
Err(e) => return parse_err_to_exit(&e),
};
let tmp = std::env::temp_dir().join("kei-decision-execute");
let mut report: Vec<serde_json::Value> = Vec::new();
for action in &ranked {
let emit = match emit_task_toml(action, &tmp, &master) {
Ok(e) => e,
Err(e) => return generic_err("execute/emit", &e),
};
if dry_run {
report.push(serde_json::json!({ "action_id": action.raw.id, "task_path": emit.path, "executed": false }));
continue;
}
if let Some(code) = execute_one(action, &emit.path, no_ledger, &mut report) {
return code;
}
}
print_json_or_err(&report)
}
fn execute_one(
action: &RankedAction,
task_path: &std::path::Path,
no_ledger: bool,
report: &mut Vec<serde_json::Value>,
) -> Option<ExitCode> {
let exec_out = match execute_action(&action.raw.id, task_path) {
Ok(o) => o,
Err(e) => { eprintln!("kei-decision execute: {}", e); return Some(ExitCode::from(3)); }
};
if !no_ledger {
// best-effort: log the ledger pre-fork attempt; failure is non-fatal here.
let spec_sha_stub = format!("planned-{}", action.raw.id);
let _ = pre_fork_ledger(&exec_out.agent_id, &exec_out.branch, &spec_sha_stub);
}
report.push(serde_json::to_value(&exec_out).unwrap_or(serde_json::Value::Null));
None
}
fn run_link(research_dir: PathBuf, graph_out: Option<PathBuf>) -> ExitCode {
let out = graph_out.unwrap_or_else(default_graph_path);
match merge_graphs(&research_dir, &out) {
Ok(o) => print_json_or_err(&o),
Err(e) => generic_err("link", &e),
}
}
fn default_graph_path() -> PathBuf {
let home = std::env::var("HOME").unwrap_or_else(|_| ".".into());
PathBuf::from(home).join("Projects/KnowledgeVault/knowledge/research-graph.json")
}
fn rank_pipeline(master: &std::path::Path, limit: Option<usize>) -> Result<Vec<RankedAction>, ParseError> {
let raws = parse_master_report(master)?;
let kinds: Vec<_> = raws.iter().map(classify).collect();
let mut ranked = rank_actions(raws, kinds);
if let Some(n) = limit { ranked.truncate(n); }
Ok(ranked)
}
#[derive(Serialize)]
struct WithKind<'a> {
#[serde(flatten)]
raw: &'a kei_decision::RawAction,
kind: kei_decision::ActionKind,
}
fn print_json_or_err<T: Serialize>(v: &T) -> ExitCode {
match serde_json::to_string_pretty(v) {
Ok(s) => { println!("{s}"); ExitCode::SUCCESS }
Err(e) => { eprintln!("kei-decision serialize: {e}"); ExitCode::from(1) }
}
}
fn print_markdown_table(ranked: &[RankedAction]) {
println!("| Rank | # | Action | Kind | Severity | Effort | Score |");
println!("|------|---|--------|------|----------|--------|-------|");
for r in ranked {
println!("| {} | {} | {} | {:?} | {} | {} | {:.2} |",
r.rank, r.raw.id, r.raw.title, r.kind, r.raw.severity, r.raw.effort, r.score);
}
}
fn parse_err_to_exit(e: &ParseError) -> ExitCode {
eprintln!("kei-decision parse: {e}");
match e {
ParseError::FileNotFound(_) => ExitCode::from(1),
ParseError::NoActionsFound => ExitCode::from(2),
ParseError::Io(_) => ExitCode::from(1),
}
}
fn generic_err(stage: &str, e: &impl std::fmt::Display) -> ExitCode {
eprintln!("kei-decision {stage}: {e}");
ExitCode::from(1)
}