use super::*;
pub(super) fn bind_startup_skill_digests(
roles: &mut [EmittedRole],
files: &[EmittedFile],
target: TargetHarness,
) -> Result<(), CompileError> {
let target_prefix = if target == TargetHarness::Codex {
".agents/skills"
} else {
"skills"
};
for role in roles {
let skill = required_startup_skill(role)?;
let prefix = format!("{target_prefix}/{skill}/");
let bundle = files
.iter()
.filter(|file| file.path.starts_with(&prefix))
.collect::<Vec<_>>();
if bundle.is_empty() {
return Err(CompileError::Invalid(format!(
"startup skill `{skill}` emitted no target files"
)));
}
role.startup_skill_sha256 = Some(startup_bundle_digest(&bundle, target));
}
Ok(())
}
pub(super) fn update_tree_digest(digest: &mut Sha256, file: &EmittedFile) {
digest.update(file.path.as_bytes());
digest.update([0]);
digest.update(file.mode.to_be_bytes());
digest.update([0]);
digest.update(file.content.as_bytes());
digest.update([0, 0]);
}
pub(super) fn tree_digest(files: &[EmittedFile]) -> String {
let mut digest = Sha256::new();
for file in files {
update_tree_digest(&mut digest, file);
}
hex(&digest.finalize())
}
pub(super) fn startup_bundle_digest(files: &[&EmittedFile], target: TargetHarness) -> String {
let mut digest = Sha256::new();
for file in files {
let path = if target == TargetHarness::Codex {
file.path
.strip_prefix(".agents/")
.expect("Codex startup bundle lives below .agents")
} else {
&file.path
};
digest.update(path.as_bytes());
digest.update([0]);
digest.update(file.mode.to_be_bytes());
digest.update([0]);
digest.update(file.content.as_bytes());
digest.update([0, 0]);
}
hex(&digest.finalize())
}
pub(super) fn sha256(bytes: &[u8]) -> String {
hex(&Sha256::digest(bytes))
}
pub(super) fn hex(bytes: &[u8]) -> String {
let mut output = String::with_capacity(bytes.len() * 2);
for byte in bytes {
write!(output, "{byte:02x}").expect("writing to String cannot fail");
}
output
}