omk 0.5.0

A Rust runtime for Kimi CLI. Turns prompts into proof-backed engineering runs with gates, worktrees, and replay.
Documentation
use super::*;
use crate::llm::client::MockLlmClient;
use crate::llm::types::{GoalKind, Slice};

#[tokio::test]
async fn test_planner_classify_mock() {
    let classification_json = r#"{
        "kind": "greenfield",
        "confidence": 0.95,
        "reasoning": "Building new feature",
        "is_testable": true,
        "suggested_refinement": null
    }"#;

    let client = Arc::new(MockLlmClient::new(vec![classification_json.to_string()]));
    let planner = LlmPlanner::new(client, TokenBudget::new(1000));

    let result = planner.classify("Add user authentication").await.unwrap();
    assert_eq!(result.kind, GoalKind::Greenfield);
    assert!((result.confidence - 0.95).abs() < f32::EPSILON);
    assert!(result.is_testable);
}

#[tokio::test]
async fn test_planner_decompose_mock() {
    let plan_json = r#"{
        "kind": "rewrite",
        "complexity_score": 7,
        "complexity_reasoning": "Significant changes",
        "slices": [
            {
                "id": "slice-1",
                "description": "Refactor module A",
                "write_set": ["src/a.rs"],
                "estimated_difficulty": "hard"
            },
            {
                "id": "slice-2",
                "description": "Refactor module B",
                "write_set": ["src/b.rs"],
                "estimated_difficulty": "medium"
            }
        ],
        "dependencies": [[0, 1]],
        "acceptance_criteria": ["All tests pass"]
    }"#;

    let client = Arc::new(MockLlmClient::new(vec![plan_json.to_string()]));
    let planner = LlmPlanner::new(client, TokenBudget::new(1000));

    let result = planner
        .decompose(
            "Refactor core modules",
            &RepoContext {
                primary_language: Some("Rust".to_string()),
                file_count: 20,
                top_level_files: vec!["src".to_string()],
                has_tests: true,
                has_ci: true,
            },
        )
        .await
        .unwrap();

    assert_eq!(result.kind, GoalKind::Rewrite);
    assert_eq!(result.slices.len(), 2);
    assert_eq!(result.dependencies, vec![(0, 1)]);
    assert_eq!(
        result.slices[0].estimated_difficulty,
        super::super::types::Difficulty::Hard
    );
}

#[tokio::test]
async fn test_mock_planner_classify() {
    let planner = MockPlanner::new().with_classification(GoalClassification {
        kind: GoalKind::Audit,
        confidence: 0.8,
        reasoning: "Looks like audit".to_string(),
        is_testable: false,
        suggested_refinement: None,
    });

    let result = planner.classify("anything").await.unwrap();
    assert_eq!(result.kind, GoalKind::Audit);
}

#[tokio::test]
async fn test_mock_planner_decompose() {
    let planner = MockPlanner::new().with_plan(Plan {
        goal_text: "Test".to_string(),
        kind: GoalKind::Repair,
        complexity: Complexity {
            score: 3,
            reasoning: "Easy fix".to_string(),
            estimated_hours: Some(1.0),
        },
        slices: vec![Slice {
            id: "s1".to_string(),
            description: "Fix it".to_string(),
            write_set: vec!["src/lib.rs".to_string()],
            estimated_difficulty: super::super::types::Difficulty::Easy,
        }],
        dependencies: vec![],
        acceptance_criteria: vec!["Tests pass".to_string()],
        estimated_tokens: 100,
    });

    let result = planner
        .decompose(
            "Fix bug",
            &RepoContext {
                primary_language: None,
                file_count: 0,
                top_level_files: vec![],
                has_tests: false,
                has_ci: false,
            },
        )
        .await
        .unwrap();

    assert_eq!(result.kind, GoalKind::Repair);
    assert_eq!(result.slices.len(), 1);
}

#[tokio::test]
async fn test_planner_generate_criteria_mock() {
    let criteria_json = r#"["User can login", "Session persists"]"#;
    let client = Arc::new(MockLlmClient::new(vec![criteria_json.to_string()]));
    let planner = LlmPlanner::new(client, TokenBudget::new(1000));

    let plan = Plan {
        goal_text: "Add auth".to_string(),
        kind: GoalKind::Greenfield,
        complexity: Complexity {
            score: 5,
            reasoning: "Medium".to_string(),
            estimated_hours: None,
        },
        slices: vec![],
        dependencies: vec![],
        acceptance_criteria: vec![],
        estimated_tokens: 0,
    };

    let result = planner.generate_criteria("Add auth", &plan).await.unwrap();
    assert_eq!(result.len(), 2);
    assert_eq!(result[0], "User can login");
}

#[tokio::test]
async fn test_planner_estimate_complexity_mock() {
    let complexity_json = r#"{"score": 8, "reasoning": "Hard", "estimated_hours": 12.0}"#;
    let client = Arc::new(MockLlmClient::new(vec![complexity_json.to_string()]));
    let planner = LlmPlanner::new(client, TokenBudget::new(1000));

    let plan = Plan {
        goal_text: "Refactor core".to_string(),
        kind: GoalKind::Rewrite,
        complexity: Complexity {
            score: 5,
            reasoning: "Medium".to_string(),
            estimated_hours: None,
        },
        slices: vec![],
        dependencies: vec![],
        acceptance_criteria: vec![],
        estimated_tokens: 0,
    };

    let result = planner
        .estimate_complexity("Refactor core", &plan)
        .await
        .unwrap();
    assert_eq!(result.score, 8);
    assert_eq!(result.reasoning, "Hard");
    assert!((result.estimated_hours.unwrap() - 12.0).abs() < f32::EPSILON);
}

#[tokio::test]
async fn test_mock_planner_generate_criteria() {
    let planner = MockPlanner::new().with_criteria(vec!["Test passes".to_string()]);
    let plan = Plan {
        goal_text: "Test".to_string(),
        kind: GoalKind::Greenfield,
        complexity: Complexity {
            score: 1,
            reasoning: "Trivial".to_string(),
            estimated_hours: None,
        },
        slices: vec![],
        dependencies: vec![],
        acceptance_criteria: vec![],
        estimated_tokens: 0,
    };
    let result = planner.generate_criteria("Test", &plan).await.unwrap();
    assert_eq!(result, vec!["Test passes"]);
}

#[tokio::test]
async fn test_mock_planner_estimate_complexity() {
    let planner = MockPlanner::new().with_complexity(Complexity {
        score: 9,
        reasoning: "Complex".to_string(),
        estimated_hours: Some(20.0),
    });
    let plan = Plan {
        goal_text: "Test".to_string(),
        kind: GoalKind::Greenfield,
        complexity: Complexity {
            score: 1,
            reasoning: "Trivial".to_string(),
            estimated_hours: None,
        },
        slices: vec![],
        dependencies: vec![],
        acceptance_criteria: vec![],
        estimated_tokens: 0,
    };
    let result = planner.estimate_complexity("Test", &plan).await.unwrap();
    assert_eq!(result.score, 9);
}