omk 0.5.0

A Rust runtime for Kimi CLI. Turns prompts into proof-backed engineering runs with gates, worktrees, and replay.
Documentation
mod builder;
mod types;
mod validation;

pub use builder::{plan_goal_delivery_slices, record_goal_delivery_slice_plan};
pub use types::{GoalDeliveryOverlapSerialization, GoalDeliverySlice, GoalDeliverySlicePlan};
pub use validation::{all_slices_done, ready_delivery_slices};

#[cfg(test)]
mod tests {
    use std::path::PathBuf;

    use super::*;
    use crate::runtime::goal::task_graph::{GoalTask, GoalTaskGraph, GoalTaskStatus};

    fn task(id: &str, status: GoalTaskStatus, dependencies: &[&str]) -> GoalTask {
        GoalTask {
            id: id.to_string(),
            title: format!("Task {id}"),
            description: format!("Task {id} description"),
            status,
            owner_role: None,
            completed_at: None,
            evidence: Vec::new(),
            retry_count: 0,
            max_retries: 0,
            lease_expires_at: None,
            dependencies: dependencies.iter().map(|d| d.to_string()).collect(),
            read_set: Vec::new(),
            write_set: vec!["project files".to_string()],
            risk: "low".to_string(),
            acceptance: vec![format!("Task {id} acceptance")],
        }
    }

    fn graph(tasks: Vec<GoalTask>) -> GoalTaskGraph {
        GoalTaskGraph {
            version: 1,
            goal_id: "goal-test".to_string(),
            generated_at: chrono::Utc::now(),
            tasks,
        }
    }

    #[tokio::test]
    async fn all_slices_done_returns_false_when_no_records() {
        let tmp = tempfile::tempdir().expect("tempdir");
        let graph = graph(vec![task("t1", GoalTaskStatus::Pending, &[])]);
        let task_graph_json = serde_json::json!({
            "version": 1,
            "goal_id": "goal-test",
            "generated_at": chrono::Utc::now(),
            "tasks": [
                {
                    "id": "t1",
                    "title": "Task t1",
                    "description": "desc",
                    "status": "pending",
                    "dependencies": [],
                    "read_set": [],
                    "write_set": ["project files"],
                    "risk": "low",
                    "acceptance": ["a"]
                }
            ]
        });
        tokio::fs::write(
            tmp.path()
                .join(crate::runtime::goal::state::GOAL_TASK_GRAPH_FILE),
            serde_json::to_vec_pretty(&task_graph_json).expect("json"),
        )
        .await
        .expect("write");

        let done = all_slices_done(tmp.path(), &graph)
            .await
            .expect("all_slices_done");
        assert!(!done, "no delivery records means not done");
    }

    #[tokio::test]
    async fn all_slices_done_returns_true_when_all_tasks_done() {
        let tmp = tempfile::tempdir().expect("tempdir");
        let graph = graph(vec![
            task("t1", GoalTaskStatus::Done, &[]),
            task("t2", GoalTaskStatus::Done, &["t1"]),
        ]);
        let task_graph_json = serde_json::json!({
            "version": 1,
            "goal_id": "goal-test",
            "generated_at": chrono::Utc::now(),
            "tasks": [
                {
                    "id": "t1",
                    "title": "Task t1",
                    "description": "desc",
                    "status": "done",
                    "dependencies": [],
                    "read_set": [],
                    "write_set": ["project files"],
                    "risk": "low",
                    "acceptance": ["a"],
                    "delivery": {
                        "slice_id": "t1",
                        "worktree_path": "/tmp/wt1",
                        "status": "delivered"
                    }
                },
                {
                    "id": "t2",
                    "title": "Task t2",
                    "description": "desc",
                    "status": "done",
                    "dependencies": ["t1"],
                    "read_set": [],
                    "write_set": ["project files"],
                    "risk": "low",
                    "acceptance": ["a"],
                    "delivery": {
                        "slice_id": "t2",
                        "worktree_path": "/tmp/wt2",
                        "status": "delivered"
                    }
                }
            ]
        });
        tokio::fs::write(
            tmp.path()
                .join(crate::runtime::goal::state::GOAL_TASK_GRAPH_FILE),
            serde_json::to_vec_pretty(&task_graph_json).expect("json"),
        )
        .await
        .expect("write");

        let done = all_slices_done(tmp.path(), &graph)
            .await
            .expect("all_slices_done");
        assert!(done, "all slice tasks are done");
    }

    #[tokio::test]
    async fn ready_delivery_slices_filters_done_and_blocked_dependencies() {
        let tmp = tempfile::tempdir().expect("tempdir");
        let graph = graph(vec![
            task("t1", GoalTaskStatus::Done, &[]),
            task("t2", GoalTaskStatus::Pending, &["t1"]),
            task("t3", GoalTaskStatus::Pending, &["t1"]),
        ]);
        let task_graph_json = serde_json::json!({
            "version": 1,
            "goal_id": "goal-test",
            "generated_at": chrono::Utc::now(),
            "tasks": [
                {
                    "id": "t1",
                    "title": "Task t1",
                    "description": "desc",
                    "status": "done",
                    "dependencies": [],
                    "read_set": [],
                    "write_set": ["project files"],
                    "risk": "low",
                    "acceptance": ["a"],
                    "delivery": { "slice_id": "t1", "worktree_path": "/tmp/wt1", "status": "delivered" }
                },
                {
                    "id": "t2",
                    "title": "Task t2",
                    "description": "desc",
                    "status": "pending",
                    "dependencies": ["t1"],
                    "read_set": [],
                    "write_set": ["project files"],
                    "risk": "low",
                    "acceptance": ["a"],
                    "delivery": { "slice_id": "t2", "worktree_path": "/tmp/wt2", "status": "planned", "dependencies": ["t1"] }
                },
                {
                    "id": "t3",
                    "title": "Task t3",
                    "description": "desc",
                    "status": "pending",
                    "dependencies": ["t1"],
                    "read_set": [],
                    "write_set": ["project files"],
                    "risk": "low",
                    "acceptance": ["a"],
                    "delivery": { "slice_id": "t3", "worktree_path": "/tmp/wt3", "status": "planned", "dependencies": ["t1", "t2"] }
                }
            ]
        });
        tokio::fs::write(
            tmp.path()
                .join(crate::runtime::goal::state::GOAL_TASK_GRAPH_FILE),
            serde_json::to_vec_pretty(&task_graph_json).expect("json"),
        )
        .await
        .expect("write");

        let ready = ready_delivery_slices(tmp.path(), &graph)
            .await
            .expect("ready");
        assert_eq!(ready.len(), 1, "only t2 is ready (t3 blocked on t2)");
        assert_eq!(ready[0].task_id, "t2");
        assert_eq!(ready[0].worktree_path, PathBuf::from("/tmp/wt2"));
    }
}