nils-plan-issue 1.12.0

CLI crate for nils-plan-issue in the nils-cli workspace.
Documentation
use std::path::PathBuf;

use clap::{Parser, ValueEnum};

use crate::ValidationError;
use crate::commands::Command;

#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum OutputFormat {
    Text,
    Json,
}

#[derive(Debug, Clone, Parser)]
#[command(
    version,
    long_version = nils_build_info::long_version(env!("CARGO_PKG_VERSION")),
    about = "Rust implementation of the plan-issue orchestration workflow.",
    long_about = "Run live or local plan-issue orchestration flows with a typed command contract and deterministic runtime workspace.",
    after_help = "EXAMPLES:\n  plan-issue start-plan --issue 123 --repo owner/repo\n  plan-issue-local build-task-spec --plan docs/plans/example/example-plan.md --sprint 1\n  plan-issue status-plan --issue 123 --format json\n\nENVIRONMENT:\n  PLAN_ISSUE_HOME  Runtime workspace override.\n  XDG_STATE_HOME   Base state directory when PLAN_ISSUE_HOME is unset.\n  HOME             Fallback base state directory.\n\nEXIT CODES:\n  0   success\n  1   runtime error\n  64  command-line usage error\n  65  invalid input data\n\nUSAGE PATHS:\n  - plan-issue: live GitHub-backed orchestration\n  - plan-issue-local: local-first rehearsal and dry-run flow\n\nUNSUPPORTED IN PLAN-ISSUE-LOCAL:\n  - Any --issue path that requires live GitHub reads/writes (for example: status-plan/ready-plan with --issue, close-plan with --issue-only, cleanup-worktrees).\n\nUSE INSTEAD:\n  - plan-issue <command> ...        (live GitHub path)\n  - --body-file + --dry-run flows   (local rehearsal path where supported)\n\nRUNTIME WORKSPACE:\n  - Pass --state-dir <PATH> to override the workspace root, or export PLAN_ISSUE_HOME.\n  - Default: ${XDG_STATE_HOME:-$HOME/.local/state}/plan-issue.\n\nBoth binaries share the same typed command contract.",
    disable_help_subcommand = true
)]
pub struct Cli {
    /// Pass-through repository target for issue/PR/MR operations.
    #[arg(long, global = true, value_name = "owner/repo")]
    pub repo: Option<String>,

    /// Print write actions without mutating GitHub state.
    #[arg(long, global = true)]
    pub dry_run: bool,

    /// Bypass markdown payload guard for GitHub body/comment writes.
    #[arg(short = 'f', long, global = true)]
    pub force: bool,

    /// Hidden alias for `--format json` (kept for backwards compatibility).
    #[arg(long, global = true, hide = true, conflicts_with = "format")]
    pub json: bool,

    /// Output format.
    #[arg(long, global = true, value_enum)]
    pub format: Option<OutputFormat>,

    /// Runtime workspace root. Overrides $PLAN_ISSUE_HOME and the
    /// $XDG_STATE_HOME/plan-issue default. Artefacts land under
    /// `<state-dir>/out/plan-issue-delivery/...`.
    #[arg(long, global = true, value_name = "PATH")]
    pub state_dir: Option<PathBuf>,

    #[command(subcommand)]
    pub command: Command,
}

impl Cli {
    pub fn resolve_output_format(&self) -> Result<OutputFormat, ValidationError> {
        if self.json || matches!(self.format, Some(OutputFormat::Json)) {
            return Ok(OutputFormat::Json);
        }

        Ok(OutputFormat::Text)
    }

    pub fn validate(&self) -> Result<(), ValidationError> {
        self.command.validate(self.dry_run)
    }
}