o7 0.1.1

O7 workflow DSL runner
Documentation
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;

/// Embedded builtin workflow files (compiled into the binary).
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(
        "refs/llms-dsl.txt",
        include_str!("../builtins/generate/refs/llms-dsl.txt"),
    );
    files.insert(
        "scripts/has-unanswered-questions.sh",
        include_str!("../builtins/generate/scripts/has-unanswered-questions.sh"),
    );
    BuiltinFiles { files }
}

/// List all available builtin names.
pub fn list_builtins() -> Vec<&'static str> {
    vec!["generate"]
}

/// Extract a builtin to a temporary directory and return its path.
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 a unique suffix per invocation to avoid collisions between concurrent calls
    // (e.g. parallel tests or multiple concurrent runs in the same process).
    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))?;

        // Make scripts executable
        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"));
        // Verify the file content is valid
        let content = std::fs::read_to_string(&path).unwrap();
        assert!(content.contains("version 1"));
        assert!(content.contains("workflow main"));
        // Clean up
        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());
        // Clean up
        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"
        );
        // Verify it loads as valid config
        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"));
        // check-script should have no prompt_slot
        assert!(config.harness["check-script"].prompt_slot.is_none());
        // Clean up
        if let Some(parent) = path.parent() {
            let _ = std::fs::remove_dir_all(parent);
        }
    }

    #[test]
    fn test_llms_dsl_ref_is_extracted() {
        let path = extract_builtin_to_temp("generate").unwrap();
        let ref_path = path.parent().unwrap().join("refs/llms-dsl.txt");
        assert!(ref_path.exists(), "llms-dsl.txt should be extracted");

        let extracted = std::fs::read_to_string(&ref_path).unwrap();
        let builtin = get_generate_builtin();
        let embedded = builtin.files.get("refs/llms-dsl.txt").unwrap();
        assert_eq!(extracted, *embedded);

        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");
        }

        // Clean up
        if let Some(parent) = path.parent() {
            let _ = std::fs::remove_dir_all(parent);
        }
    }
}

/// Resolve a workflow path that might be a builtin (@name) or a regular file path.
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))
    }
}