use super::{GitRunner, Path, PathBuf, agent_ref, repo_git};
pub fn agent_exists(workspace: &Path, agent_id: &str, git: &dyn GitRunner) -> bool {
let refspec = format!("refs/heads/{}", agent_ref(agent_id));
git.run(
&repo_git(workspace),
&["rev-parse", "--verify", "--quiet", &refspec],
)
.is_ok()
}
#[derive(Debug, thiserror::Error)]
#[error(
"no agent {id:?} in this workspace — {reason}; check the id against the workspace's \
`agents/*` refs, or start an agent with `lernie prompt` / `lernie dispatch`"
)]
pub struct UnknownAgent {
id: String,
reason: &'static str,
}
pub fn require_agent(
workspace: &Path,
agent_id: &str,
reason: &'static str,
git: &dyn GitRunner,
) -> Result<(), UnknownAgent> {
if agent_exists(workspace, agent_id, git) {
return Ok(());
}
Err(UnknownAgent {
id: agent_id.to_owned(),
reason,
})
}
#[derive(Debug, thiserror::Error)]
#[error(
"no ref or commit {rev:?} in this workspace — {reason}; any ref is a legal fork point \
(ARCH §2.3): a config branch, an agent branch, or any commit of either — read them \
with `git -C <workspace>/repo.git log --all --oneline`"
)]
pub struct UnknownRef {
rev: String,
reason: &'static str,
}
pub fn require_ref(
workspace: &Path,
rev: &str,
reason: &'static str,
git: &dyn GitRunner,
) -> Result<(), UnknownRef> {
let spec = format!("{rev}^{{commit}}");
if git
.run(
&repo_git(workspace),
&["rev-parse", "--verify", "--quiet", &spec],
)
.is_ok()
{
return Ok(());
}
Err(UnknownRef {
rev: rev.to_owned(),
reason,
})
}
#[derive(Debug, thiserror::Error)]
pub enum UnknownLineage {
#[error("no config lineage {name:?} in this workspace — existing lineages: {pool}")]
NoSuch { name: String, pool: String },
#[error("list config lineages: {0}")]
Git(#[source] std::io::Error),
}
pub fn require_lineage(
workspace: &Path,
name: &str,
git: &dyn GitRunner,
) -> Result<(), UnknownLineage> {
let names = super::config_names(workspace, git).map_err(UnknownLineage::Git)?;
if names.iter().any(|n| n == name) {
return Ok(());
}
Err(UnknownLineage::NoSuch {
name: name.to_owned(),
pool: crate::name::pool(&names),
})
}
#[derive(Debug, thiserror::Error)]
pub enum LayoutError {
#[error(
"{0} uses the retired per-conversation layout (a `root/` primary worktree with \
control files at the repo root); the current layout is one repo per workspace — \
`<workspace>/repo.git` (bare) with `config/*` branches and `agents/*` refs \
(ARCH §2.2). Pre-v1 clean break (§10): no migration — create a fresh workspace \
with `lernie new` and re-author its config"
)]
OldLayout(PathBuf),
#[error("{0} is not a workspace (no repo.git) — create one with `lernie new` (ARCH §2.2)")]
NotAWorkspace(PathBuf),
}
pub fn require(workspace: &Path) -> Result<(), LayoutError> {
if repo_git(workspace).is_dir() {
return Ok(());
}
if workspace.join("root").join(".git").exists() {
return Err(LayoutError::OldLayout(workspace.to_path_buf()));
}
Err(LayoutError::NotAWorkspace(workspace.to_path_buf()))
}