omk 0.5.0

A Rust runtime for Kimi CLI. Turns prompts into proof-backed engineering runs with gates, worktrees, and replay.
Documentation
use assert_cmd::Command;
use predicates::prelude::*;
use serde_json::{json, Value};
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command as StdCommand;

fn isolated_env() -> (tempfile::TempDir, Vec<(&'static str, PathBuf)>) {
    omk::test_helpers::isolated_xdg_env()
}

fn omk_cmd(envs: &[(&'static str, PathBuf)]) -> Command {
    let mut cmd = Command::cargo_bin("omk").unwrap();
    for (key, value) in envs {
        cmd.env(key, value);
    }
    cmd
}

fn xdg_state(envs: &[(&'static str, PathBuf)]) -> PathBuf {
    envs.iter()
        .find_map(|(key, value)| (*key == "XDG_STATE_HOME").then(|| value.clone()))
        .expect("missing XDG_STATE_HOME")
}

fn goal_dirs(envs: &[(&'static str, PathBuf)]) -> Vec<PathBuf> {
    let goals_dir = xdg_state(envs).join("omk").join("goals");
    let mut dirs: Vec<_> = fs::read_dir(goals_dir)
        .expect("missing goals dir")
        .map(|entry| entry.expect("failed to read goal entry").path())
        .filter(|path| path.is_dir())
        .collect();
    dirs.sort();
    dirs
}

fn git(project_dir: &Path, args: &[&str]) {
    let output = StdCommand::new("git")
        .arg("-C")
        .arg(project_dir)
        .args(args)
        .output()
        .expect("git command failed to start");
    assert!(
        output.status.success(),
        "git {:?} failed: stdout={} stderr={}",
        args,
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );
}

fn write_gate_config(project_dir: &Path) {
    let omk_dir = project_dir.join(".omk");
    fs::create_dir_all(&omk_dir).expect("failed to create .omk dir");
    fs::write(
        omk_dir.join("gates.toml"),
        r#"
[[gates]]
name = "smoke"
command = "/bin/sh"
args = ["-c", "echo smoke-ok"]
required = true
"#,
    )
    .expect("failed to write gates.toml");
}

fn init_project_with_goal(envs: &[(&'static str, PathBuf)]) -> (tempfile::TempDir, PathBuf) {
    let project = tempfile::tempdir().expect("project tempdir");
    git(project.path(), &["init"]);
    git(project.path(), &["config", "user.email", "omk@example.com"]);
    git(project.path(), &["config", "user.name", "OMK Test"]);
    fs::write(project.path().join("README.md"), "# fixture\n").expect("write readme");
    write_gate_config(project.path());
    git(project.path(), &["add", "."]);
    git(project.path(), &["commit", "-m", "baseline"]);
    git(
        project.path(),
        &["checkout", "-b", "codex/goal-open-pr-fixture"],
    );

    let mut plan = omk_cmd(envs);
    plan.current_dir(project.path())
        .args([
            "goal",
            "plan",
            "Render a GitHub PR from goal proof evidence",
        ])
        .assert()
        .success();

    let goal_dir = goal_dirs(envs)
        .into_iter()
        .next()
        .expect("goal dir should exist");
    inject_delivery_metadata(&goal_dir.join("task-graph.json"));
    fs::write(project.path().join("src-change.txt"), "changed\n").expect("write changed file");

    let mut verify = omk_cmd(envs);
    verify
        .current_dir(project.path())
        .args(["goal", "verify", "latest"])
        .assert()
        .success();

    (project, goal_dir)
}

fn inject_delivery_metadata(task_graph_path: &Path) {
    let mut task_graph: Value =
        serde_json::from_slice(&fs::read(task_graph_path).expect("read task graph"))
            .expect("task graph json");
    let task = task_graph["tasks"]
        .as_array_mut()
        .expect("task graph tasks should be an array")
        .iter_mut()
        .find(|task| task["id"] == "goal-agent-execute")
        .expect("generated graph should include agent execution task");
    task["delivery"] = json!({
        "slice_id": "goal-agent-execute",
        "owner": "codex",
        "branch": "codex/goal-open-pr-fixture",
        "worktree_path": "../oh-my-kimi-goal-open-pr",
        "pr_url": "https://github.com/ekhodzitsky/oh-my-kimi/pull/456",
        "write_scope": [
            "src/cli/goal/mod.rs",
            "src/cli/goal/commands/mod.rs",
            "src/runtime/goal/open_pr.rs",
            "tests/goal_open_pr_test.rs"
        ],
        "verification_summary": "cargo test --test goal_open_pr_test passed"
    });
    fs::write(
        task_graph_path,
        serde_json::to_vec_pretty(&task_graph).expect("task graph json"),
    )
    .expect("rewrite task graph");
}

#[test]
fn goal_help_lists_open_pr_command() {
    let (_tmp, envs) = isolated_env();

    omk_cmd(&envs)
        .args(["goal", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains("open-pr"));
}

#[test]
fn goal_open_pr_markdown_dry_run_renders_goal_proof_and_delivery_metadata() {
    let (_tmp, envs) = isolated_env();
    let (_project, goal_dir) = init_project_with_goal(&envs);
    let goal_id = goal_dir
        .file_name()
        .and_then(|name| name.to_str())
        .expect("goal id");

    let output = omk_cmd(&envs)
        .args([
            "goal",
            "open-pr",
            goal_id,
            "--dry-run",
            "--format",
            "markdown",
        ])
        .output()
        .expect("omk goal open-pr failed");

    assert!(
        output.status.success(),
        "open-pr failed: stdout={} stderr={}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );
    let stdout = String::from_utf8(output.stdout).expect("stdout utf8");
    assert!(stdout.contains("Title: Goal proof: Render a GitHub PR from goal proof evidence"));
    assert!(stdout.contains("## Goal"));
    assert!(stdout.contains(goal_id));
    assert!(stdout.contains("## Task Summary"));
    assert!(stdout.contains("## Delivery Metadata"));
    assert!(stdout.contains("slice: goal-agent-execute"));
    assert!(stdout.contains("owner: codex"));
    assert!(stdout.contains("branch: codex/goal-open-pr-fixture"));
    assert!(stdout.contains("worktree: ../oh-my-kimi-goal-open-pr"));
    assert!(stdout.contains("pr: https://github.com/ekhodzitsky/oh-my-kimi/pull/456"));
    assert!(stdout.contains("src/runtime/goal/open_pr.rs"));
    assert!(stdout.contains("## Proof Summary"));
    assert!(stdout.contains("Proof path:"));
    assert!(stdout.contains("proof.json"));
    assert!(stdout.contains("## Verification Wall"));
    assert!(stdout.contains("smoke"));
    assert!(!stdout.contains("smoke-ok"));
    assert!(stdout.contains("## Release Candidate Notes"));
    assert!(stdout.contains("merge recommendation"));
    assert!(stdout.contains("## Review Evidence"));
    assert!(stdout.contains("## Known Gaps"));
    assert!(stdout.contains("## Changed Files"));
    assert!(stdout.contains("src-change.txt"));
    assert!(stdout.contains("## Artifacts"));
}

#[test]
fn goal_open_pr_json_dry_run_is_valid_json() {
    let (_tmp, envs) = isolated_env();
    let (_project, _goal_dir) = init_project_with_goal(&envs);

    let output = omk_cmd(&envs)
        .args(["goal", "open-pr", "latest", "--dry-run", "--format", "json"])
        .output()
        .expect("omk goal open-pr failed");

    assert!(
        output.status.success(),
        "open-pr json failed: stdout={} stderr={}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );
    let json: Value = serde_json::from_slice(&output.stdout).expect("open-pr JSON should parse");
    assert_eq!(json["dry_run"], true);
    assert_eq!(
        json["title"],
        "Goal proof: Render a GitHub PR from goal proof evidence"
    );
    assert!(json["body"]
        .as_str()
        .expect("body string")
        .contains("## Verification Wall"));
}

#[test]
fn goal_open_pr_draft_dry_run_marks_draft_metadata() {
    let (_tmp, envs) = isolated_env();
    let (_project, _goal_dir) = init_project_with_goal(&envs);

    let output = omk_cmd(&envs)
        .args([
            "goal",
            "open-pr",
            "latest",
            "--dry-run",
            "--draft",
            "--format",
            "json",
        ])
        .output()
        .expect("omk goal open-pr failed");

    assert!(
        output.status.success(),
        "open-pr draft json failed: stdout={} stderr={}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );
    let json: Value = serde_json::from_slice(&output.stdout).expect("open-pr JSON should parse");
    assert_eq!(json["draft"], true);
    assert!(json["body"]
        .as_str()
        .expect("body string")
        .contains("- Draft: `true`"));
}

#[test]
fn goal_open_pr_default_local_policy_renders_without_dry_run() {
    let (_tmp, envs) = isolated_env();
    let (_project, _goal_dir) = init_project_with_goal(&envs);

    let output = omk_cmd(&envs)
        .args(["goal", "open-pr", "latest"])
        .output()
        .expect("omk goal open-pr failed");

    assert!(
        output.status.success(),
        "open-pr failed: stdout={} stderr={}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );
    let stdout = String::from_utf8(output.stdout).expect("stdout utf8");
    assert!(stdout.contains("Title: Goal proof: Render a GitHub PR from goal proof evidence"));
}

#[test]
fn goal_open_pr_auto_pr_dry_run_renders_without_mutation() {
    let (_tmp, envs) = isolated_env();
    let (_project, _goal_dir) = init_project_with_goal(&envs);

    let output = omk_cmd(&envs)
        .args([
            "goal",
            "open-pr",
            "latest",
            "--policy",
            "auto-pr",
            "--dry-run",
            "--format",
            "json",
        ])
        .output()
        .expect("omk goal open-pr failed");

    assert!(
        output.status.success(),
        "open-pr auto-pr dry-run failed: stdout={} stderr={}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );
    let json: Value = serde_json::from_slice(&output.stdout).expect("open-pr JSON should parse");
    assert_eq!(json["dry_run"], true);
    assert_eq!(
        json["title"],
        "Goal proof: Render a GitHub PR from goal proof evidence"
    );
}

#[test]
fn goal_open_pr_local_policy_dry_run_false_is_same_as_default() {
    let (_tmp, envs) = isolated_env();
    let (_project, _goal_dir) = init_project_with_goal(&envs);

    let output = omk_cmd(&envs)
        .args(["goal", "open-pr", "latest", "--policy", "local"])
        .output()
        .expect("omk goal open-pr failed");

    assert!(
        output.status.success(),
        "open-pr local policy failed: stdout={} stderr={}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );
    let stdout = String::from_utf8(output.stdout).expect("stdout utf8");
    assert!(stdout.contains("Title: Goal proof: Render a GitHub PR from goal proof evidence"));
}

#[test]
fn goal_open_pr_auto_pr_without_dry_run_fails_when_gh_not_authenticated() {
    let (_tmp, envs) = isolated_env();
    let (_project, _goal_dir) = init_project_with_goal(&envs);

    let output = omk_cmd(&envs)
        .args(["goal", "open-pr", "latest", "--policy", "auto-pr"])
        .output()
        .expect("omk goal open-pr failed");

    assert!(
        !output.status.success(),
        "open-pr auto-pr without dry-run should fail when gh is not authenticated"
    );
    let stderr = String::from_utf8_lossy(&output.stderr);
    let helpful = stderr.contains("gh")
        || stderr.contains("failed")
        || stderr.contains("dry-run")
        || stderr.contains("dry_run");
    assert!(
        helpful,
        "stderr should contain a helpful message, got: {}",
        stderr
    );
}

#[test]
fn goal_open_pr_fails_when_proof_has_no_evidence() {
    let (_tmp, envs) = isolated_env();

    omk_cmd(&envs)
        .args(["goal", "plan", "Prepare a missing evidence PR"])
        .assert()
        .success();

    omk_cmd(&envs)
        .args(["goal", "open-pr", "latest", "--dry-run"])
        .assert()
        .failure()
        .stderr(predicate::str::contains("proof evidence is missing"))
        .stderr(predicate::str::contains("Next: omk goal execute latest"));
}