use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
pub struct BuiltinFiles {
pub files: HashMap<&'static str, &'static str>,
}
pub fn get_generate_builtin() -> BuiltinFiles {
let mut files = HashMap::new();
files.insert("main.7", include_str!("../builtins/generate/main.7"));
files.insert(
".7/harnesses.toml",
include_str!("../builtins/generate/.7/harnesses.toml"),
);
files.insert(
"prompts/gather-requirements.md",
include_str!("../builtins/generate/prompts/gather-requirements.md"),
);
files.insert(
"scripts/has-unanswered-questions.sh",
include_str!("../builtins/generate/scripts/has-unanswered-questions.sh"),
);
BuiltinFiles { files }
}
pub fn list_builtins() -> Vec<&'static str> {
vec!["generate"]
}
pub fn extract_builtin_to_temp(name: &str) -> Result<PathBuf, String> {
let builtin = match name {
"generate" => get_generate_builtin(),
_ => return Err(format!("Unknown builtin: @{}", name)),
};
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let unique = COUNTER.fetch_add(1, Ordering::Relaxed);
let temp_dir = std::env::temp_dir().join(format!("o7-builtin-{}-{}-{}", name, std::process::id(), unique));
fs::create_dir_all(&temp_dir)
.map_err(|e| format!("Failed to create temp dir: {}", e))?;
for (path, content) in &builtin.files {
let file_path = temp_dir.join(path);
if let Some(parent) = file_path.parent() {
fs::create_dir_all(parent)
.map_err(|e| format!("Failed to create dir: {}", e))?;
}
fs::write(&file_path, content)
.map_err(|e| format!("Failed to write file: {}", e))?;
if path.ends_with(".sh") {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let perms = fs::Permissions::from_mode(0o755);
fs::set_permissions(&file_path, perms)
.map_err(|e| format!("Failed to set permissions: {}", e))?;
}
}
}
Ok(temp_dir.join("main.7"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_list_builtins() {
let builtins = list_builtins();
assert!(builtins.contains(&"generate"));
}
#[test]
fn test_extract_generate_builtin() {
let path = extract_builtin_to_temp("generate").unwrap();
assert!(path.exists());
assert!(path.ends_with("main.7"));
let content = std::fs::read_to_string(&path).unwrap();
assert!(content.contains("version 1"));
assert!(content.contains("workflow main"));
if let Some(parent) = path.parent() {
let _ = std::fs::remove_dir_all(parent);
}
}
#[test]
fn test_extract_unknown_builtin() {
let result = extract_builtin_to_temp("nonexistent");
assert!(result.is_err());
assert!(result.unwrap_err().contains("Unknown builtin"));
}
#[test]
fn test_resolve_workflow_path_builtin() {
let (path, project_root) = resolve_workflow_path("@generate").unwrap();
assert!(path.exists());
assert!(path.ends_with("main.7"));
assert!(project_root.exists());
let _ = std::fs::remove_dir_all(&project_root);
}
#[test]
fn test_resolve_workflow_path_unknown_builtin() {
let result = resolve_workflow_path("@nonexistent");
assert!(result.is_err());
}
#[test]
fn test_harnesses_toml_is_extracted() {
let path = extract_builtin_to_temp("generate").unwrap();
let harnesses_path = path.parent().unwrap().join(".7/harnesses.toml");
assert!(harnesses_path.exists(), "harnesses.toml should be extracted");
let content = std::fs::read_to_string(&harnesses_path).unwrap();
assert!(content.contains("[harness.check-script]"), "Should define check-script harness");
assert!(content.contains("[harness.claude-code]"), "Should define claude-code harness");
let config = crate::harness::config::load_harness_config(
path.parent().unwrap().to_str().unwrap()
).unwrap();
assert!(config.harness.contains_key("check-script"));
assert!(config.harness.contains_key("claude-code"));
assert!(config.harness["check-script"].prompt_slot.is_none());
if let Some(parent) = path.parent() {
let _ = std::fs::remove_dir_all(parent);
}
}
#[test]
fn test_check_script_is_executable() {
let path = extract_builtin_to_temp("generate").unwrap();
let script_path = path.parent().unwrap().join("scripts/has-unanswered-questions.sh");
assert!(script_path.exists());
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let perms = std::fs::metadata(&script_path).unwrap().permissions();
assert!(perms.mode() & 0o111 != 0, "Script should be executable");
}
if let Some(parent) = path.parent() {
let _ = std::fs::remove_dir_all(parent);
}
}
}
pub fn resolve_workflow_path(input: &str) -> Result<(PathBuf, PathBuf), String> {
if input.starts_with('@') {
let builtin_name = &input[1..];
let main_path = extract_builtin_to_temp(builtin_name)?;
let project_root = main_path.parent()
.ok_or_else(|| "Invalid builtin path".to_string())?
.to_path_buf();
Ok((main_path, project_root))
} else {
let path = PathBuf::from(input).canonicalize()
.map_err(|e| format!("File not found: {}: {}", input, e))?;
let project_root = path.parent()
.ok_or_else(|| "Cannot determine project root".to_string())?
.to_path_buf();
Ok((path, project_root))
}
}