kanbus 0.14.0

High-performance CLI and web console for the Kanbus issue tracker. Includes kanbus (CLI) and kanbus-console (web UI server).
Documentation
use std::fs;
use std::path::PathBuf;
use std::process::Command;

use cucumber::{given, then, when};

use kanbus::agents_management::{kanbus_section_text, project_management_text};

use crate::step_definitions::initialization_steps::KanbusWorld;

const README_STUB: &str = "This is a sample project that uses Kanbus.";

fn repo_root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..")
}

fn example_dir(name: &str) -> PathBuf {
    let slug = name.trim().to_lowercase().replace(' ', "-");
    repo_root().join("examples").join(slug)
}

fn build_kbs_binary() -> PathBuf {
    let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    let target_dir = std::env::var("CARGO_TARGET_DIR")
        .map(PathBuf::from)
        .unwrap_or_else(|_| manifest_dir.join("target"));
    let binary_path = target_dir.join("debug").join("kbs");
    if binary_path.exists() {
        return binary_path;
    }

    let status = Command::new("cargo")
        .args(["build", "--bin", "kbs"])
        .current_dir(&manifest_dir)
        .env("CARGO_TARGET_DIR", &target_dir)
        .status()
        .expect("build kbs binary");
    if !status.success() {
        panic!("failed to build kbs binary");
    }
    binary_path
}

#[given(expr = "the {string} example project does not exist")]
fn given_example_missing(_world: &mut KanbusWorld, name: String) {
    let path = example_dir(&name);
    if path.exists() {
        fs::remove_dir_all(&path).expect("remove example dir");
    }
}

#[when(expr = "I create the {string} example project")]
fn when_create_example_project(_world: &mut KanbusWorld, name: String) {
    let path = example_dir(&name);
    fs::create_dir_all(&path).expect("create example dir");
    let status = Command::new("git")
        .args(["init"])
        .current_dir(&path)
        .status()
        .expect("git init failed");
    if !status.success() {
        panic!("git init failed");
    }
}

#[when(expr = "I run \"kanbus init\" in the {string} example project")]
fn when_run_kanbus_init_in_example(_world: &mut KanbusWorld, name: String) {
    let path = example_dir(&name);
    let binary_path = build_kbs_binary();
    let status = Command::new(binary_path)
        .args(["init"])
        .current_dir(&path)
        .env("KANBUS_NO_DAEMON", "1")
        .status()
        .expect("kanbus init failed");
    if !status.success() {
        panic!("kanbus init failed");
    }
}

#[when(expr = "I run \"kanbus setup agents\" in the {string} example project")]
fn when_run_kanbus_setup_agents_in_example(_world: &mut KanbusWorld, name: String) {
    let path = example_dir(&name);
    let binary_path = build_kbs_binary();
    let status = Command::new(binary_path)
        .args(["setup", "agents"])
        .current_dir(&path)
        .env("KANBUS_NO_DAEMON", "1")
        .status()
        .expect("kanbus setup agents failed");
    if !status.success() {
        panic!("kanbus setup agents failed");
    }
}

#[when(expr = "I add a README stub to the {string} example project")]
fn when_add_readme_stub(_world: &mut KanbusWorld, name: String) {
    let path = example_dir(&name);
    let readme = path.join("README.md");
    fs::write(readme, format!("{README_STUB}\n")).expect("write README");
}

#[then(expr = "the {string} example project should contain a README stub")]
fn then_example_contains_readme(_world: &mut KanbusWorld, name: String) {
    let path = example_dir(&name);
    let readme = path.join("README.md");
    assert!(readme.exists());
    let content = fs::read_to_string(readme).expect("read README");
    assert_eq!(content.trim(), README_STUB);
}

#[then(expr = "the {string} example project should contain .kanbus.yml")]
fn then_example_contains_config(_world: &mut KanbusWorld, name: String) {
    let path = example_dir(&name);
    assert!(path.join(".kanbus.yml").exists());
}

#[then(expr = "the {string} example project should contain the project directory")]
fn then_example_contains_project_dir(_world: &mut KanbusWorld, name: String) {
    let path = example_dir(&name);
    assert!(path.join("project").exists());
}

#[then(expr = "the {string} example project should contain AGENTS.md with Kanbus instructions")]
fn then_example_contains_agents(_world: &mut KanbusWorld, name: String) {
    let path = example_dir(&name);
    let agents = path.join("AGENTS.md");
    assert!(agents.exists());
    let content = fs::read_to_string(agents).expect("read AGENTS.md");
    assert!(content.contains(kanbus_section_text().trim()));
}

#[then(expr = "the {string} example project should contain CONTRIBUTING_AGENT.md")]
fn then_example_contains_instructions(_world: &mut KanbusWorld, name: String) {
    let path = example_dir(&name);
    let instructions = path.join("CONTRIBUTING_AGENT.md");
    assert!(instructions.exists());
    let content = fs::read_to_string(instructions).expect("read instructions");
    let expected = project_management_text(&path).expect("instructions text");
    assert_eq!(content.trim(), expected.trim());
}