use crate::config::PerRepoProviders;
use crate::prompt::{Error, dispatch, subagent};
use crate::template::GitRunner;
use crate::workspace::{self, agent_name};
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
pub(super) fn commit(
workspace_dir: &Path,
worktree: &Path,
agent_id: &str,
dispatch_sha: &str,
grant: &dispatch::Grant<'_>,
git: &dyn GitRunner,
) -> Result<String, Error> {
let target = grant.config_commit;
let soul_rel = format!("{}/{}.md", crate::prompt::SOULS_DIR, grant.role);
let soul =
workspace::show_control(workspace_dir, target, &soul_rel, git).map_err(|source| {
Error::ControlRead {
path: PathBuf::from(format!("{target}:{soul_rel}")),
source,
}
})?;
let tmp = scratch_worktree(agent_id);
let tmp_str = tmp.to_string_lossy().into_owned();
git.run(
worktree,
&["worktree", "add", "--detach", &tmp_str, dispatch_sha],
)
.map_err(|source| Error::Git {
op: "retarget scratch worktree",
source,
})?;
let minted = mint(&tmp, worktree, dispatch_sha, grant, &soul, git);
let _ = git.run(worktree, &["worktree", "remove", "--force", &tmp_str]);
minted
}
pub(super) fn granted(
workspace_dir: &Path,
target: &str,
role: &str,
git: &dyn GitRunner,
) -> Result<Vec<String>, Error> {
let file = crate::prompt::PER_REPO_PROVIDERS_FILE;
let raw = workspace::show_control(workspace_dir, target, file, git).map_err(|source| {
Error::ControlRead {
path: PathBuf::from(format!("{target}:{file}")),
source,
}
})?;
let providers = PerRepoProviders::parse(&raw, Path::new(&format!("{target}:{file}")))?;
Ok(providers
.roles
.get(role)
.map(|assignment| assignment.tools.clone())
.unwrap_or_default())
}
fn mint(
tmp: &Path,
access: &Path,
dispatch_sha: &str,
grant: &dispatch::Grant<'_>,
soul: &str,
git: &dyn GitRunner,
) -> Result<String, Error> {
let err = |op| move |source| Error::Git { op, source };
let name = agent_name::in_worktree(tmp);
dispatch::trim_to_context(tmp, grant, name.as_deref(), git)?;
std::fs::write(tmp.join(subagent::SOUL_FILE), soul)?;
git.run(tmp, &["add", "-A"]).map_err(err("retarget add"))?;
let tree = git
.run_capture(tmp, &["write-tree"])
.map_err(err("retarget write-tree"))?;
let subject = git
.run_capture(access, &["log", "-n", "1", "--format=%s", dispatch_sha])
.map_err(err("retarget dispatch subject"))?;
let sha = git
.run_capture(
tmp,
&[
"commit-tree",
tree.trim(),
"-p",
grant.config_commit,
"-m",
subject.trim(),
],
)
.map_err(err("retarget commit-tree"))?;
Ok(sha.trim().to_string())
}
fn scratch_worktree(agent_id: &str) -> PathBuf {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
std::env::temp_dir().join(format!("lernie-retarget-base-{agent_id}-{nanos}"))
}