use crate::gate::{Check, GateSpec};
use crate::{Error, Result};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LegacyManifest {
pub name: String,
#[serde(rename = "agentId")]
pub agent_id: String,
#[serde(rename = "lintCmd", default)]
pub lint_cmd: String,
#[serde(rename = "formatCmd", default)]
pub format_cmd: String,
#[serde(rename = "testCmd", default)]
pub test_cmd: String,
#[serde(rename = "buildCmd", default)]
pub build_cmd: String,
}
impl LegacyManifest {
pub fn to_gate(&self) -> Option<GateSpec> {
let pairs = [
("format", &self.format_cmd),
("lint", &self.lint_cmd),
("test", &self.test_cmd),
("build", &self.build_cmd),
];
let checks: Vec<Check> = pairs
.into_iter()
.filter(|(_, cmd)| !cmd.trim().is_empty())
.map(|(name, cmd)| Check {
name: name.to_string(),
cmd: cmd.clone(),
required: true,
})
.collect();
if checks.is_empty() {
return None;
}
Some(GateSpec {
timeout_secs: None,
checks,
review: Default::default(),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GateSource {
ProjectConfig,
LegacyManifest,
}
pub fn parse_manifest(json: &str) -> Result<LegacyManifest> {
serde_json::from_str(json).map_err(|e| Error::Invalid(format!("manifest: {e}")))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn blank_commands_do_not_become_checks() {
let m = LegacyManifest {
name: "archon".into(),
agent_id: "agent-archon".into(),
lint_cmd: "cargo clippy".into(),
format_cmd: String::new(),
test_cmd: " ".into(),
build_cmd: "cargo build".into(),
};
let gate = m.to_gate().expect("has runnable checks");
let names: Vec<&str> = gate.checks.iter().map(|c| c.name.as_str()).collect();
assert_eq!(names, ["lint", "build"]);
}
#[test]
fn a_manifest_with_nothing_runnable_yields_no_gate() {
let m = LegacyManifest {
name: "x".into(),
agent_id: "agent-x".into(),
lint_cmd: String::new(),
format_cmd: String::new(),
test_cmd: String::new(),
build_cmd: String::new(),
};
assert!(m.to_gate().is_none());
}
}