mod agent;
mod attest;
mod brief;
mod cli;
mod declarations;
mod gate;
mod git;
mod report;
mod runner;
mod setup;
mod state;
mod trailer;
use cli::Mode;
use std::process::ExitCode;
fn fields(version: &str, what: &str) -> Result<Vec<u32>, String> {
version
.split('.')
.map(|f| {
f.parse::<u32>()
.map_err(|_| format!("{what} '{version}' is not a version like 0.2.0"))
})
.collect()
}
fn line_of(version: &[u32]) -> usize {
version.iter().position(|field| *field != 0).unwrap_or(0)
}
fn require_version(want: &str) -> Result<bool, String> {
let have = env!("CARGO_PKG_VERSION");
let (wanted, installed) = (
fields(want, "--require-version")?,
fields(have, "this binary's version")?,
);
let width = wanted.len().max(installed.len());
let padded = |mut v: Vec<u32>| {
v.resize(width, 0);
v
};
let (wanted, installed) = (padded(wanted), padded(installed));
let line = line_of(&wanted);
if wanted[..=line] != installed[..=line] {
report::incompatible(want, have);
return Ok(false);
}
if installed < wanted {
report::stale(want, have);
return Ok(false);
}
Ok(true)
}
fn reviewer_prompt(want: &str) -> Result<bool, String> {
let hook = declarations::read()?;
let declaration = declarations::find(&hook, want)?;
println!("{}", brief::system(declaration)?);
println!("──── and on stdin, opening a round ────\n");
println!(
"{}",
brief::opening("<the aim of the change, one flat line>")
);
Ok(true)
}
fn at_repo_root() {
if let Ok(root) = git::toplevel() {
let _ = std::env::set_current_dir(root);
}
}
fn main() -> ExitCode {
let args: Vec<String> = std::env::args().skip(1).collect();
at_repo_root();
if let [only] = args.as_slice() {
if only == "--version" || only == "-V" {
println!("git-agent-verdict {}", env!("CARGO_PKG_VERSION"));
return ExitCode::SUCCESS;
}
if only == "--help" || only == "-h" {
println!("{}", cli::USAGE);
return ExitCode::SUCCESS;
}
}
let mode = match cli::parse(args.clone().into_iter()) {
Ok(mode) => mode,
Err(detail) => {
eprintln!("git-agent-verdict: error: {detail}\n{}", cli::USAGE);
if !cli::agent_verb(&args) {
eprintln!("\n{}", setup::guide());
}
return ExitCode::from(2);
}
};
if declarations::listing_requested() {
match &mode {
Mode::Gate(inv) => {
declarations::emit_gate(inv);
return ExitCode::SUCCESS;
}
Mode::RequireVersion(_) => {}
_ => return ExitCode::SUCCESS,
}
}
let (label, outcome) = match &mode {
Mode::Gate(inv) => (inv.gate.as_str(), gate::check(inv)),
Mode::Attest(intent) => ("attest", attest::run(intent.as_deref())),
Mode::Reset(reason) => ("reset", attest::reset(reason)),
Mode::ReviewerPrompt(gate) => ("reviewer-prompt", reviewer_prompt(gate)),
Mode::RequireVersion(want) => ("require-version", require_version(want)),
Mode::RepoSetupGuide => ("repo-setup-guide", {
println!("{}", setup::guide());
Ok(true)
}),
};
match outcome {
Ok(true) => ExitCode::SUCCESS,
Ok(false) => ExitCode::FAILURE,
Err(detail) => {
eprintln!("git-agent-verdict: error: {label}: {detail}");
ExitCode::from(2)
}
}
}