tix-tracker 0.12.0

A terminal issue tracker: markdown + YAML frontmatter issues with a kanban TUI and CLI
Documentation
#[allow(clippy::wildcard_imports)]
use super::*;

/// The `tix analytics` subcommands (mission `burndown-scope-analytics-01M3C9J1`, WP05).
///
/// A thin adapter over the shared analytics DTOs. The daily fold, the unit conversion
/// and the projection live in `crate::analytics`, and the versioned chart document is
/// WP04's (`crate::commands::export::chart_document`), which every verb here calls rather
/// than re-composing. This enum owns only the spelling of the surface: the scope, the
/// period and the mode/unit are always explicit, and no flag here can change an issue, a
/// cycle date, a milestone or a snapshot.
#[derive(Debug, Clone, Subcommand)]
pub enum AnalyticsCommand {
    /// Print the daily remaining/completed series for one cycle or milestone
    ///
    /// A tab-separated table with a `date` column followed by the point's own fields —
    /// `scope_total`, `remaining`, `completed`, `scope_added`, `completions` — and the
    /// day's `coverage`. A value the record cannot support is `-`, never a zero, and a
    /// day with no recorded history is `unknown` or `coverage_limited`. The scope,
    /// period, mode, unit, formula version and coverage tally print above the header
    /// row, and the findings render to stderr with their remedies.
    Burndown {
        #[command(flatten)]
        args: AnalyticsScopeArgs,
    },
    /// Print the read-only end-date forecast over one cycle or milestone
    ///
    /// The planning end date is the scope's own (`--to` overrides it) and `--as-of` is
    /// the caller's day, never a clock. A forecast the record cannot answer prints its
    /// `status` (`insufficient_data` or `unknown`) with `-` for every value it could not
    /// derive — no completion date is ever invented, and no planning date or issue
    /// changes.
    Projection {
        #[command(flatten)]
        args: AnalyticsScopeArgs,
        /// The forecast origin, a `YYYY-MM-DD` calendar date
        #[arg(long, value_name = "YYYY-MM-DD")]
        as_of: String,
    },
    /// Write the deterministic local chart document (WP04's target)
    ///
    /// The document goes to stdout, or to `--output` through the atomic export writer the
    /// other local targets use. `--mode` is required: the document states the mode it was
    /// folded in, so a default would be a guess about which arithmetic produced the
    /// numbers. `--as-of` asks the same document for a forecast. The remote
    /// `gitlab`/`linear` namespaces are never consulted and cannot collide with a chart
    /// document.
    Export {
        #[command(flatten)]
        args: AnalyticsScopeArgs,
        /// Ask the document for a forecast at this origin, a `YYYY-MM-DD` calendar date
        #[arg(long, value_name = "YYYY-MM-DD")]
        as_of: Option<String>,
        /// Write the document to this path instead of stdout
        #[arg(long, value_name = "PATH")]
        output: Option<String>,
    },
}

/// The scope, period, mode and unit flags every `tix analytics` verb shares.
///
/// The period is the scope's own planning data unless a bound is named here, and the mode
/// is explicit rather than inferred: an estimate valuation is only drawn through the
/// project's configured unit conversion, so a mode the record cannot support is refused
/// instead of silently falling back to a count.
#[derive(Debug, Clone, Args)]
pub struct AnalyticsScopeArgs {
    /// Scope kind: `cycle` or `milestone`
    #[arg(long, value_name = "cycle|milestone")]
    pub scope: String,
    /// The scope's planning identity: a cycle name or stable id, or a milestone name
    #[arg(long, value_name = "ID")]
    pub id: String,
    /// The series' inclusive start, `YYYY-MM-DD`; absent means the scope's own start
    #[arg(long, value_name = "YYYY-MM-DD")]
    pub from: Option<String>,
    /// The series' inclusive end, `YYYY-MM-DD`; absent means the scope's own planning end
    #[arg(long, value_name = "YYYY-MM-DD")]
    pub to: Option<String>,
    /// How the series counts: `count` (issue counts, the default) or `estimate`
    ///
    /// `estimate` requires `--unit` and a unit the project declares a conversion for;
    /// a missing or incompatible unit is refused with the shared remedy, never guessed.
    #[arg(long, value_name = "count|estimate")]
    pub mode: Option<String>,
    /// The unit estimate mode counts in; count mode ignores it (an issue count is unitless)
    #[arg(long, value_name = "UNIT")]
    pub unit: Option<String>,
}