Skip to main content

plan_issue/
cli.rs

1use std::path::PathBuf;
2
3use clap::{Parser, ValueEnum};
4
5use crate::ValidationError;
6use crate::commands::Command;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
9pub enum OutputFormat {
10    Text,
11    Json,
12}
13
14#[derive(Debug, Clone, Parser)]
15#[command(
16    version,
17    long_version = nils_build_info::long_version(env!("CARGO_PKG_VERSION")),
18    about = "Rust implementation of the plan-issue orchestration workflow.",
19    long_about = "Run live or local plan-issue orchestration flows with a typed command contract and deterministic runtime workspace.",
20    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.",
21    disable_help_subcommand = true
22)]
23pub struct Cli {
24    /// Pass-through repository target for issue/PR/MR operations.
25    #[arg(long, global = true, value_name = "owner/repo")]
26    pub repo: Option<String>,
27
28    /// Print write actions without mutating GitHub state.
29    #[arg(long, global = true)]
30    pub dry_run: bool,
31
32    /// Bypass markdown payload guard for GitHub body/comment writes.
33    #[arg(short = 'f', long, global = true)]
34    pub force: bool,
35
36    /// Hidden alias for `--format json` (kept for backwards compatibility).
37    #[arg(long, global = true, hide = true, conflicts_with = "format")]
38    pub json: bool,
39
40    /// Output format.
41    #[arg(long, global = true, value_enum)]
42    pub format: Option<OutputFormat>,
43
44    /// Runtime workspace root. Overrides $PLAN_ISSUE_HOME and the
45    /// $XDG_STATE_HOME/plan-issue default. Artefacts land under
46    /// `<state-dir>/out/plan-issue-delivery/...`.
47    #[arg(long, global = true, value_name = "PATH")]
48    pub state_dir: Option<PathBuf>,
49
50    #[command(subcommand)]
51    pub command: Command,
52}
53
54impl Cli {
55    pub fn resolve_output_format(&self) -> Result<OutputFormat, ValidationError> {
56        if self.json || matches!(self.format, Some(OutputFormat::Json)) {
57            return Ok(OutputFormat::Json);
58        }
59
60        Ok(OutputFormat::Text)
61    }
62
63    pub fn validate(&self) -> Result<(), ValidationError> {
64        self.command.validate(self.dry_run)
65    }
66}