supercode-harness 0.4.17

The optional native Supercode agent and tool harness
Documentation
//! BP-4 (catalog:91 "Synthetic context-injection blocks", design §1.4:
//! "harness-spliced reminders/nudges … the ambient nudge class is core"):
//! the injection REGISTRY behind `core.context_injections`.
//!
//! Before BP-4 the key gated exactly one thing — a static, caller-populated
//! [`ContextInjectionBlock`] list appended once at construction — so a
//! preset turning it on got nothing, because no preset (and nothing in the
//! product) ever populated the list. Neither parity preset set the key at
//! all, which made the gap invisible.
//!
//! The registry has three sources, spliced in this order:
//!
//! 1. **Built-in blocks** ([`builtin_blocks`]) — derived from the RESOLVED
//!    config, so a block only appears when the capability it talks about is
//!    actually armed. This is the "~25 block types" class cx's own
//!    `context/` library and cc's `<system-reminder>` blocks occupy: ambient
//!    statements about the harness the model is running inside, which no
//!    instruction file can know.
//! 2. **User blocks** — [`crate::Config::context_injection_blocks`], the
//!    pre-existing embedder-populated list, unchanged.
//! 3. **Spliced blocks** — [`crate::Agent::inject_context_block`], added
//!    mid-session (a hook's `additionalContext`, a frontend's nudge, an
//!    orchestrator's brief). This is the half that makes the mechanism a
//!    SEAM rather than a startup constant.
//!
//! Everything here is a no-op when `core.context_injections` is false (the
//! default): [`assemble`] returns an empty string and nothing is read.

use crate::config::{Config, ContextInjectionBlock};
use crate::modules::ModuleId;

/// The built-in ambient blocks armed by `config`, in a stable order.
///
/// Each block states something true about THIS resolved configuration that
/// the model cannot otherwise know, and each is gated on the capability it
/// describes — a config with none of them armed contributes no blocks at
/// all, so this is never boilerplate the model has to ignore.
pub fn builtin_blocks(config: &Config) -> Vec<ContextInjectionBlock> {
    let mut blocks = Vec::new();
    let active = |id: ModuleId| config.module_registry && config.module_activation.is_active(id);

    if active(ModuleId::Todos) {
        blocks.push(ContextInjectionBlock::new(
            "Task list",
            "A persistent task list is available through the plan/todo tool. Keep it current: \
             write the plan out before starting multi-step work, mark each step completed as \
             you finish it, and add work you discover along the way. The list survives \
             compaction, so it is the durable record of where this session is.",
        ));
    }
    if active(ModuleId::PlanMode) {
        blocks.push(ContextInjectionBlock::new(
            "Plan mode",
            "This session can enter a read-only planning mode. While it is active, do not edit \
             files, write files, or run state-changing commands — investigate, then present the \
             plan and wait for it to be accepted.",
        ));
    }
    if !config.permissions_protected_paths.is_empty() {
        blocks.push(ContextInjectionBlock::new(
            "Protected paths",
            format!(
                "Writes to these paths are never auto-approved and will stop for the user's \
                 decision: {}. Prefer a route that doesn't touch them.",
                config.permissions_protected_paths.join(", ")
            ),
        ));
    }
    if active(ModuleId::ToolsBackground) {
        blocks.push(ContextInjectionBlock::new(
            "Background work",
            "Long-running commands can be started in the background instead of blocking the \
             turn. Start them detached, keep working, and read their output when it matters — \
             never sit on a foreground command waiting for it to finish.",
        ));
    }
    blocks
}

/// Every block a system prompt should carry, in splice order: built-ins,
/// then the config's own list, then anything spliced in at runtime.
/// Empty (and free of any work) when `core.context_injections` is off.
pub fn blocks(config: &Config, spliced: &[ContextInjectionBlock]) -> Vec<ContextInjectionBlock> {
    if !config.context_injections {
        return Vec::new();
    }
    let mut out = builtin_blocks(config);
    out.extend(config.context_injection_blocks.iter().cloned());
    out.extend(spliced.iter().cloned());
    out
}

/// Render blocks as the `\n\n# {name}\n{content}` sections the assembly site
/// appends to the system prompt — the exact shape P4e's static list used, so
/// a config that only set `context_injection_blocks` renders identically.
pub fn render(blocks: &[ContextInjectionBlock]) -> String {
    let mut out = String::new();
    for block in blocks {
        out.push_str(&format!("\n\n# {}\n{}", block.name, block.content));
    }
    out
}

/// [`blocks`] + [`render`] — the whole injection contribution to a system
/// prompt.
pub fn assemble(config: &Config, spliced: &[ContextInjectionBlock]) -> String {
    render(&blocks(config, spliced))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::configfile::{resolve, ResolveOptions};

    fn resolved(preset: &str) -> Config {
        let toml = crate::presets::lookup(preset).unwrap();
        resolve(toml, None, &ResolveOptions { strict: true })
            .unwrap_or_else(|e| panic!("{preset} resolves: {e}"))
            .config
    }

    /// The gate still means what it said: off ⇒ nothing, not even built-ins.
    #[test]
    fn the_gate_off_contributes_nothing() {
        let mut config = resolved("cc-parity");
        config.context_injections = false;
        assert!(assemble(&config, &[]).is_empty());
    }

    /// Both parity presets arm the key AND get real built-in blocks out of
    /// it — the registry is not an empty seam under the presets.
    #[test]
    fn both_presets_arm_real_builtin_blocks() {
        for preset in ["cc-parity", "cx-parity"] {
            let config = resolved(preset);
            assert!(
                config.context_injections,
                "{preset} must set core.context_injections"
            );
            let blocks = blocks(&config, &[]);
            assert!(
                !blocks.is_empty(),
                "{preset}: the registry produced no blocks"
            );
            // Every preset arms todos and protected paths.
            let names: Vec<&str> = blocks.iter().map(|b| b.name.as_str()).collect();
            assert!(names.contains(&"Task list"), "{preset}: {names:?}");
            assert!(names.contains(&"Protected paths"), "{preset}: {names:?}");
        }
    }

    /// A block only appears when its capability is armed — cx-parity has
    /// `plan_mode` off, so it must not be told about plan mode.
    #[test]
    fn builtin_blocks_track_the_module_set() {
        let cc: Vec<String> = blocks(&resolved("cc-parity"), &[])
            .into_iter()
            .map(|b| b.name)
            .collect();
        let cx: Vec<String> = blocks(&resolved("cx-parity"), &[])
            .into_iter()
            .map(|b| b.name)
            .collect();
        assert!(cc.iter().any(|n| n == "Plan mode"));
        assert!(
            !cx.iter().any(|n| n == "Plan mode"),
            "cx-parity has plan_mode off"
        );
    }

    /// Built-ins, user blocks and spliced blocks all land, in that order.
    #[test]
    fn three_sources_splice_in_order() {
        let mut config = resolved("cc-parity");
        config.context_injection_blocks = vec![ContextInjectionBlock::new("User", "user body")];
        let spliced = [ContextInjectionBlock::new("Spliced", "spliced body")];
        let text = assemble(&config, &spliced);
        let builtin_at = text.find("# Task list").unwrap();
        let user_at = text.find("# User").unwrap();
        let spliced_at = text.find("# Spliced").unwrap();
        assert!(builtin_at < user_at && user_at < spliced_at);
        assert!(text.contains("spliced body"));
    }
}