shepherd-compiler 6.6.0

Pure, deterministic Shepherd content compiler and prompt-budget engine.
//! Harness-specific emission over pure typed content.

use alloc::{
    collections::BTreeSet,
    format,
    string::{String, ToString},
    vec,
    vec::Vec,
};
use core::fmt::Write;

use sha2::{Digest, Sha256};

use crate::{
    BudgetClass, CompileError, CompileInput, EmittedFile, EmittedKind, EmittedRole, EmittedTree,
    HarnessProfile, ModelResolution, Portability, RoleInput, SkillInput, TOKENIZER_VERSION,
    TargetHarness, measure_text, validate_budget,
};

const REGULAR_MODE: u32 = 0o644;

const EXECUTABLE_MODE: u32 = 0o755;

const MAX_RESOURCE_BYTES: usize = 64 * 1024;

const MAX_SKILL_RESOURCE_BYTES: usize = 256 * 1024;

const PLANTER_CAPABILITIES: [&str; 7] = [
    "read",
    "search",
    "shell",
    "write",
    "skill-load",
    "tool-discovery",
    "ask-operator",
];

const PLANTER_WRITE_SCOPE: &str =
    "the bound run's mesh.md and seed.md only; native verification and seed-pointer commands";

pub fn compile(
    input: &CompileInput,
    profile: &HarnessProfile,
) -> Result<EmittedTree, CompileError> {
    validate_input(input)?;

    let mut roles = input.roles.iter().collect::<Vec<_>>();
    roles.sort_by(|left, right| left.role.cmp(&right.role));
    let mut skills = input.skills.iter().collect::<Vec<_>>();
    skills.sort_by(|left, right| left.name.cmp(&right.name));
    let mut emitted_roles = resolve_roles(&roles, &skills, profile)?;

    let mut files = match profile.target {
        TargetHarness::Claude => emit_claude(&roles, &emitted_roles, &skills)?,
        TargetHarness::Codex => emit_codex(&roles, &emitted_roles, &skills, profile)?,
        TargetHarness::Pi => emit_pi(&roles, &emitted_roles, &skills)?,
    };
    files.sort_by(|left, right| left.path.cmp(&right.path));
    for pair in files.windows(2) {
        if pair[0].path == pair[1].path {
            return Err(CompileError::Invalid(format!(
                "duplicate emitted path `{}` for target `{}`",
                pair[0].path,
                profile.target.as_str()
            )));
        }
    }

    bind_startup_skill_digests(&mut emitted_roles, &files, profile.target)?;

    let skill_bundle = files
        .iter()
        .filter(|file| {
            file.kind == EmittedKind::Skill
                && (file.path.ends_with("/SKILL.md") || file.path.contains("/references/"))
        })
        .map(|file| file.content.as_str())
        .collect::<String>();
    validate_budget(
        &format!("{} skill entry set", profile.target.as_str()),
        BudgetClass::HarnessSkillSet,
        &skill_bundle,
    )?;

    let digest = tree_digest(&files);
    Ok(EmittedTree {
        target: profile.target,
        roles: emitted_roles,
        files,
        digest,
        tokenizer_version: TOKENIZER_VERSION,
    })
}

mod digest;
mod emit;
mod resolve;
mod validate;

use digest::*;
use emit::*;
use resolve::*;
use validate::*;