omk 0.5.0

A Rust runtime for Kimi CLI. Turns prompts into proof-backed engineering runs with gates, worktrees, and replay.
Documentation
use anyhow::Result;
use chrono::Utc;
use serde_json::json;
use std::path::PathBuf;

use crate::runtime::goal::proof::GoalProof;
use crate::runtime::goal::state::{self, FileSystemGoalStateStore, GoalStateStore};
use crate::runtime::goal::types::{
    GoalControllerStep, GoalControllerStepKind, GoalRunUntilReadyOutcome,
};
use crate::runtime::goal::{evidence, proof};

#[derive(Debug, Clone)]
pub(crate) struct UntilReadyBlocker {
    pub(crate) reason: String,
    pub(crate) artifact_file: &'static str,
    pub(crate) human_decision_required: bool,
}

impl UntilReadyBlocker {
    pub(crate) fn policy(reason: impl Into<String>) -> Self {
        Self {
            reason: reason.into(),
            artifact_file: super::UNTIL_READY_BLOCKER_FILE,
            human_decision_required: false,
        }
    }

    pub(crate) fn human(reason: impl Into<String>, artifact_file: &'static str) -> Self {
        Self {
            reason: reason.into(),
            artifact_file,
            human_decision_required: true,
        }
    }
}

pub(crate) async fn finalize_until_ready_blocker(
    goal_id: &str,
    mut steps: Vec<GoalControllerStep>,
    blocker: UntilReadyBlocker,
) -> Result<GoalRunUntilReadyOutcome> {
    let mut state = crate::runtime::goal::resolve_goal(goal_id).await?;
    let mut proof = GoalProof::load(&state.state_dir).await?;
    let now = Utc::now();
    let relative_path = PathBuf::from(blocker.artifact_file);
    if let Some(parent) = relative_path.parent() {
        crate::runtime::config::ensure_private_dir(&state.state_dir.join(parent)).await?;
    }
    let artifact = json!({
        "status": "blocked",
        "reason": &blocker.reason,
        "delivery_policy": "local",
        "merge_policy": "manual",
        "github_mutation": false,
        "integrator_acceptance": "manual",
        "proof": state::GOAL_PROOF_FILE,
        "recorded_at": now,
    });
    proof::write_json_artifact(&state.state_dir.join(&relative_path), &artifact).await?;
    evidence::record_artifact_path_once(&mut state, "policy_blocker", relative_path.clone(), now);
    state.updated_at = now;
    FileSystemGoalStateStore::new().save(&state).await?;

    push_unique(&mut proof.known_gaps, blocker.reason.clone());
    if blocker.human_decision_required {
        push_unique(&mut proof.human_decisions_required, blocker.reason.clone());
    }
    proof.generated_at = now;
    proof.artifacts = state.artifacts.clone();
    proof::write_json_artifact(&state.state_dir.join(state::GOAL_PROOF_FILE), &proof).await?;

    steps.push(GoalControllerStep {
        kind: GoalControllerStepKind::Blocked,
        status: proof.status,
        summary: blocker.reason.clone(),
    });
    Ok(GoalRunUntilReadyOutcome {
        state,
        proof,
        steps,
        blocker: Some(blocker.reason),
        policy_evidence_path: Some(relative_path),
    })
}

pub(crate) fn push_unique(values: &mut Vec<String>, value: String) {
    if !values.iter().any(|existing| existing == &value) {
        values.push(value);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn policy_blocker_sets_expected_fields() {
        let b = UntilReadyBlocker::policy("some reason");
        assert_eq!(b.reason, "some reason");
        assert_eq!(b.artifact_file, super::super::UNTIL_READY_BLOCKER_FILE);
        assert!(!b.human_decision_required);
    }

    #[test]
    fn human_blocker_sets_expected_fields() {
        let b = UntilReadyBlocker::human("human reason", "custom.json");
        assert_eq!(b.reason, "human reason");
        assert_eq!(b.artifact_file, "custom.json");
        assert!(b.human_decision_required);
    }

    #[test]
    fn push_unique_appends_when_not_present() {
        let mut values = vec!["a".to_string(), "b".to_string()];
        push_unique(&mut values, "c".to_string());
        assert_eq!(values, vec!["a", "b", "c"]);
    }

    #[test]
    fn push_unique_skips_when_already_present() {
        let mut values = vec!["a".to_string(), "b".to_string()];
        push_unique(&mut values, "b".to_string());
        assert_eq!(values, vec!["a", "b"]);
    }

    #[test]
    fn push_unique_appends_to_empty_vec() {
        let mut values: Vec<String> = Vec::new();
        push_unique(&mut values, "first".to_string());
        assert_eq!(values, vec!["first"]);
    }
}