use super::{Error, Fx, Outcome};
use crate::config::Workflow;
use crate::config::version::Version;
use crate::prompt::resolve::{ConfigSource, workflow_source};
use crate::template::{GitRunner, RealGit};
use crate::workspace::{self, workflow_mark};
use std::path::{Path, PathBuf};
const REASON: &str =
"a workflow switch marks a running agent to resolve another workflow (ARCH §6)";
const VERSION_FILE: &str = "version";
#[derive(clap::Args, Debug)]
pub struct Args {
pub workspace: PathBuf,
pub agent: String,
#[arg(long, conflicts_with = "clear")]
pub config: Option<String>,
#[arg(long)]
pub clear: bool,
}
pub fn run(args: Args, _fx: &mut Fx) -> Result<Outcome, Error> {
let e = |source: &dyn std::fmt::Display| Error::new("workflow", source);
crate::name::require_agent_id(&args.agent).map_err(|s| e(&s))?;
let git = RealGit::new();
workspace::require(&args.workspace).map_err(|s| e(&s))?;
workspace::require_agent(&args.workspace, &args.agent, REASON, &git).map_err(|s| e(&s))?;
if args.clear {
workflow_mark::clear(&args.workspace, &args.agent, &git).map_err(|s| e(&s))?;
eprintln!(
"litany: workflow mark cleared for [{}] — its followed config's workflow \
governs from its next step (ARCH §6)",
args.agent,
);
return Ok(Outcome::Quiet);
}
let Some(name) = args.config.as_deref() else {
return show(&args.workspace, &args.agent, &git)
.map(Outcome::Line)
.map_err(|s| e(&s));
};
let target = preflight(&args.workspace, name, &git).map_err(|s| e(&s))?;
workflow_mark::write(&args.workspace, &args.agent, &target, &git).map_err(|s| e(&s))?;
eprintln!(
"litany: [{}] marked to run {}'s workflow ({}) — it governs from the agent's next \
step boundary on, and stands until re-marked or cleared (ARCH §6)",
args.agent,
workspace::config_ref(name),
&target[..target.len().min(12)],
);
Ok(Outcome::Quiet)
}
fn show(ws: &Path, agent: &str, git: &dyn GitRunner) -> Result<String, String> {
let rev = workspace::agent_ref(agent);
let followed =
workspace::current_config::current_config(ws, &rev, git).map_err(|s| s.to_string())?;
let source =
workflow_source::source_of(ws, &ConfigSource::Agent(agent), followed.commit(), git);
let commit = source.commit();
let origin = match &source {
workflow_source::Source::Marked { holder, .. } if holder == agent => {
format!("marked on [{holder}]")
}
workflow_source::Source::Marked { holder, .. } => {
format!("marked on ancestor [{holder}]")
}
workflow_source::Source::Followed { .. } => match followed.held() {
None => "followed from its governing lineage".to_string(),
Some(tips) => {
format!("followed from its fork commit — {tips} diverged config lineages")
}
},
};
let lineage = match lineage_at(ws, commit, git)? {
Some(name) => format!(" ({name})"),
None => String::new(),
};
Ok(format!(
"{agent} runs {}:{}{lineage} — {origin}",
&commit[..commit.len().min(12)],
crate::prompt::WORKFLOW_FILE,
))
}
fn lineage_at(ws: &Path, commit: &str, git: &dyn GitRunner) -> Result<Option<String>, String> {
let out = git
.run_capture(
&workspace::repo_git(ws),
&[
"for-each-ref",
"--format=%(refname:short)",
"--points-at",
commit,
"refs/heads/config/",
],
)
.map_err(|s| s.to_string())?;
Ok(out
.lines()
.map(str::trim)
.find(|l| !l.is_empty())
.map(str::to_string))
}
fn preflight(ws: &Path, name: &str, git: &dyn GitRunner) -> Result<String, String> {
workspace::require_lineage(ws, name, git).map_err(|s| s.to_string())?;
let spec = format!("{}^{{commit}}", workspace::config_ref(name));
let target = git
.run_capture(&workspace::repo_git(ws), &["rev-parse", &spec])
.map_err(|s| s.to_string())?
.trim()
.to_string();
let origin = |path: &str| PathBuf::from(format!("{target}:{path}"));
let version = control(ws, &target, VERSION_FILE, git)?;
Version::parse(&version, &origin(VERSION_FILE)).map_err(|s| s.to_string())?;
let workflow = control(ws, &target, crate::prompt::WORKFLOW_FILE, git)?;
Workflow::parse(&workflow, &origin(crate::prompt::WORKFLOW_FILE)).map_err(|s| s.to_string())?;
Ok(target)
}
fn control(ws: &Path, commit: &str, path: &str, git: &dyn GitRunner) -> Result<String, String> {
workspace::show_control(ws, commit, path, git).map_err(|s| format!("{commit}:{path}: {s}"))
}