Skip to main content

tix/cli/
cli_health.rs

1#[allow(clippy::wildcard_imports)]
2use super::*;
3
4/// The `tix triage` tree (`contracts/cli-surface.md`, WP05 T023–T025).
5///
6/// `list` and `status` are read-only queue views; the four decision verbs take a
7/// single issue; `batch` is the selection-shaped twin of `tix bulk`, preview by
8/// default and writing only under an explicit `--apply`.
9///
10/// ## Why the reason flag is `Option` on `accept` and the rest
11///
12/// `contracts/decision-history.md` splits it: decline, duplicate and defer
13/// *require* a reason, accept records one when supplied. A required clap argument
14/// on three of the four would make the fourth's absence look like an oversight,
15/// and — more importantly — the enforcement belongs to the service, which refuses
16/// `missing_reason` before any write on every surface (NFR-003). The flag is
17/// therefore optional everywhere and the *rule* is the service's, so the CLI,
18/// TUI and MCP cannot end up with three different ideas of which decision needs
19/// one. `tix triage decline 1` with no `--reason` still fails, at the service,
20/// with the contract's own kind.
21/// `tix health`'s actions.
22#[derive(Subcommand)]
23pub enum HealthAction {
24    /// Record an issue's health state (append-only; needs --rationale)
25    ///
26    /// Writes the frontmatter value and **one** History entry, together or not at
27    /// all. Any of the four states is accepted; anything else is refused by name.
28    Set {
29        /// The issue, by number or slug
30        issue: String,
31        /// One of `unknown`, `on_track`, `needs_attention`, `at_risk`
32        #[arg(long)]
33        state: String,
34        /// Who is making the change (defaults to the configured git identity)
35        #[arg(long)]
36        actor: Option<String>,
37        /// When, as `YYYY-MM-DD` (defaults to today)
38        #[arg(long)]
39        date: Option<String>,
40        /// Why — required: a health change nobody can explain is not recorded
41        #[arg(long)]
42        rationale: Option<String>,
43    },
44    /// Show one issue's health: its own value, its derived rollup, its diagnostics
45    Show {
46        /// The issue, by number or slug
47        issue: String,
48    },
49    /// Show a scope's derived health, with its contributors and policy version
50    Rollup {
51        /// `issue` | `project` | `epic` | `cycle` | `milestone`
52        #[arg(long)]
53        scope: String,
54        /// The scope's stable id (an issue number for `--scope issue`)
55        #[arg(long)]
56        id: String,
57    },
58    /// Layer an explicit override over a derived scope's health
59    ///
60    /// An override is a deliberate act about a whole scope, so it needs `--confirm`:
61    /// it does not replace what the children say, it is recorded *over* it.
62    Override {
63        /// `project` | `epic` | `cycle` | `milestone`
64        #[arg(long)]
65        scope: String,
66        /// The scope's stable id
67        #[arg(long)]
68        id: String,
69        /// One of `unknown`, `on_track`, `needs_attention`, `at_risk`
70        #[arg(long)]
71        state: String,
72        /// Who is making the change (defaults to the configured git identity)
73        #[arg(long)]
74        actor: Option<String>,
75        /// When, as `YYYY-MM-DD` (defaults to today)
76        #[arg(long)]
77        date: Option<String>,
78        /// Why — required
79        #[arg(long)]
80        rationale: Option<String>,
81        /// Confirm an override of derived health
82        #[arg(long)]
83        confirm: bool,
84    },
85    /// The append-only health History entries about a scope
86    History {
87        /// A scope: `issue:42`, `cycle:2026-10`, `milestone:m1`, …
88        scope: String,
89    },
90    /// Every scope's health, with what contributes to each
91    Board,
92}