use std::path::Path;
pub const PLAYBOOK_VERSION: &str = "1.0.0";
pub const PLAYBOOK: &str = include_str!("playbook/v1_0_0.md");
pub const BLOCK: &str = include_str!("playbook/block_v1_0_0.md");
pub const BLOCK_WORD_LIMIT: usize = 300;
pub fn playbook_hash() -> String {
format!(
"sha256:{}",
exocortex_wire::signing::content_digest_hex(PLAYBOOK.as_bytes())
)
}
pub fn block_hash() -> String {
format!(
"sha256:{}",
exocortex_wire::signing::content_digest_hex(BLOCK.as_bytes())
)
}
pub fn install(data_dir: &Path) -> std::io::Result<Option<String>> {
let versioned_name = format!("playbook-v{PLAYBOOK_VERSION}.md");
let versioned = data_dir.join(&versioned_name);
let current = data_dir.join("playbook.md");
let installed_marker = data_dir.join("version.txt");
let already_current = installed_marker.exists()
&& std::fs::read_to_string(&installed_marker)
.map(|v| v.contains(&format!("playbook={PLAYBOOK_VERSION}")))
.unwrap_or(false);
std::fs::create_dir_all(data_dir)?;
std::fs::write(&versioned, PLAYBOOK)?;
if current.symlink_metadata().is_ok() {
let _ = std::fs::remove_file(¤t);
}
#[cfg(unix)]
std::os::unix::fs::symlink(&versioned_name, ¤t)?;
#[cfg(not(unix))]
std::fs::write(¤t, PLAYBOOK)?;
let version_row = format!(
"client={} playbook={}\n",
env!("CARGO_PKG_VERSION"),
PLAYBOOK_VERSION
);
std::fs::write(&installed_marker, version_row)?;
if already_current {
Ok(None)
} else {
Ok(Some(format!(
"[exocortex] playbook v{PLAYBOOK_VERSION} installed at {} — reference it from your harness instructions.",
current.display()
)))
}
}
pub fn block_word_count() -> usize {
BLOCK.split_whitespace().count()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn playbook_title_carries_version() {
assert!(PLAYBOOK.starts_with(&format!("# Exocortex Agent Playbook v{PLAYBOOK_VERSION}")));
}
#[test]
fn block_within_word_bound() {
assert!(
block_word_count() <= BLOCK_WORD_LIMIT,
"instruction block is {} words; bound is {}",
block_word_count(),
BLOCK_WORD_LIMIT
);
}
#[test]
fn generated_markers_present() {
assert!(PLAYBOOK.contains("<!-- gen:kinds"));
assert!(PLAYBOOK.contains("<!-- /gen:kinds -->"));
assert!(PLAYBOOK.contains("<!-- gen:rejects"));
assert!(PLAYBOOK.contains("<!-- /gen:rejects -->"));
}
#[test]
fn install_is_idempotent() {
let dir = std::env::temp_dir().join(format!("exo-pb-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
let first = install(&dir).unwrap();
assert!(first.is_some(), "first run installs and notifies");
let second = install(&dir).unwrap();
assert!(second.is_none(), "second run is quiet");
assert!(dir.join("playbook.md").exists());
assert!(dir
.join(format!("playbook-v{PLAYBOOK_VERSION}.md"))
.exists());
let _ = std::fs::remove_dir_all(&dir);
}
}