bot-forge 1.0.2

Rust CLI for installing agent skills and developer tools from configurable forms.
Documentation
//! Validated plan execution and external command boundary.
//!
//! [`select`] performs user-facing selection over a frozen plan, [`preview_plan`] detects current
//! state, and [`execute`] performs the approved side effects. Execution consumes crate-level
//! cancellation and telemetry capabilities, delegates installation mechanics to typed backends,
//! and emits lifecycle facts through the neutral event capability before presentation.

mod apt_mirror;
pub(crate) mod command;
pub(crate) mod environment;
mod executor;
pub(crate) mod install;
pub(crate) mod managed_cargo;
pub(crate) mod managed_git;
pub(crate) mod policy;
pub(crate) mod retry;
pub(crate) mod runtime;
pub(crate) mod scheduler;
pub(crate) mod verifier;
mod windows_job;

use crate::events::{LifecycleEvent, emit as emit_lifecycle, is_active};
use crate::ui::{progress_completed, progress_started, progress_update};

#[cfg(test)]
pub(crate) static TEST_ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());

/// Ordered progress facts produced while one selected plan is executing.
pub(crate) enum ExecutionEvent {
    Started {
        step: usize,
        total: usize,
        component: String,
    },
    Progress {
        step: usize,
        total: usize,
        component: String,
        elapsed_secs: u64,
        recent: String,
    },
    Completed {
        step: usize,
        total: usize,
        component: String,
        elapsed_secs: u64,
    },
}

impl ExecutionEvent {
    /// Convert UI progress into the corresponding machine lifecycle event.
    pub(crate) fn lifecycle(&self) -> LifecycleEvent {
        match self {
            Self::Started { .. } => LifecycleEvent::Queued,
            Self::Progress {
                elapsed_secs,
                recent,
                ..
            } => LifecycleEvent::Progress {
                elapsed_ms: u128::from(*elapsed_secs) * 1000,
                message: recent.clone(),
            },
            Self::Completed { elapsed_secs, .. } => LifecycleEvent::Completed {
                elapsed_ms: u128::from(*elapsed_secs) * 1000,
            },
        }
    }
}

/// Consumer of execution progress events.
pub(crate) trait EventSink {
    /// Receive one ordered progress event.
    fn emit(&mut self, event: &ExecutionEvent);
}

/// Event sink that forwards machine events and renders human progress when JSONL is inactive.
pub(crate) struct UiEventSink;

impl EventSink for UiEventSink {
    fn emit(&mut self, event: &ExecutionEvent) {
        let component = match event {
            ExecutionEvent::Started { component, .. }
            | ExecutionEvent::Progress { component, .. }
            | ExecutionEvent::Completed { component, .. } => component,
        };
        emit_lifecycle(None, Some(component), None, event.lifecycle());
        if !is_active() {
            match event {
                ExecutionEvent::Started {
                    step,
                    total,
                    component,
                } => progress_started(*step, *total, component),
                ExecutionEvent::Progress {
                    step,
                    total,
                    component,
                    elapsed_secs,
                    recent,
                } => progress_update(*step, *total, component, *elapsed_secs, recent),
                ExecutionEvent::Completed {
                    step,
                    total,
                    component,
                    elapsed_secs,
                } => progress_completed(*step, *total, component, *elapsed_secs),
            }
        }
    }
}

pub use crate::execution::executor::{ExecutionSelection, PreparedExecution, execute, select};
pub use crate::execution::verifier::preview_plan;