Skip to main content

ito_core/ralph/
readiness.rs

1use super::runner::{RalphOptions, run_ralph_with_readiness};
2use crate::errors::{CoreError, CoreResult};
3use crate::harness::Harness;
4use crate::implementation_readiness::{
5    ReadinessPhase, ReadinessRequest, evaluate_readiness, render_readiness_text,
6};
7use ito_config::{ConfigContext, load_cascading_project_config};
8use ito_domain::changes::ChangeRepository;
9use ito_domain::modules::ModuleRepository;
10use ito_domain::tasks::TaskRepository;
11use std::path::{Path, PathBuf};
12
13/// Effective checkout and `.ito` state directory for a Ralph invocation.
14#[derive(Debug, Clone)]
15pub struct ResolvedCwd {
16    /// Directory where the harness and Git commands execute.
17    pub path: PathBuf,
18    /// `.ito` directory used for state writes.
19    pub ito_path: PathBuf,
20}
21
22/// Readiness boundary used by Ralph before a change-scoped operation.
23pub trait RalphReadinessGate {
24    /// Reject before any iteration, task update, Git automation, or state write.
25    fn require(&self, ito_path: &Path, change_id: &str, checkout: &ResolvedCwd) -> CoreResult<()>;
26}
27
28#[derive(Debug, Default)]
29pub(super) struct SystemRalphReadinessGate;
30
31impl RalphReadinessGate for SystemRalphReadinessGate {
32    fn require(&self, ito_path: &Path, change_id: &str, checkout: &ResolvedCwd) -> CoreResult<()> {
33        let repository_root = ito_path.parent().unwrap_or_else(|| Path::new("."));
34        let loaded = load_cascading_project_config(
35            repository_root,
36            ito_path,
37            &ConfigContext::from_process_env(),
38        );
39        let config = serde_json::from_value(loaded.merged).map_err(|error| {
40            CoreError::Validation(format!(
41                "Cannot evaluate Ralph implementation readiness because Ito configuration is invalid: {error}"
42            ))
43        })?;
44        let request = ReadinessRequest::new(change_id, ReadinessPhase::Execute, repository_root)
45            .with_current_checkout(&checkout.path);
46        let report = evaluate_readiness(&request, &config);
47        if report.ready {
48            return Ok(());
49        }
50        Err(CoreError::Validation(format!(
51            "Ralph did not start because implementation readiness failed before any iteration or state mutation.\n\n{}",
52            render_readiness_text(&report)
53        )))
54    }
55}
56
57/// Run Ralph with the system main-first readiness boundary.
58pub fn run_ralph(
59    ito_path: &Path,
60    change_repo: &(impl ChangeRepository + ?Sized),
61    task_repo: &dyn TaskRepository,
62    module_repo: &(impl ModuleRepository + ?Sized),
63    opts: RalphOptions,
64    harness: &mut dyn Harness,
65) -> CoreResult<()> {
66    run_ralph_with_readiness(
67        ito_path,
68        change_repo,
69        task_repo,
70        module_repo,
71        opts,
72        harness,
73        &SystemRalphReadinessGate,
74    )
75}