arcature-cli 2026.1.1

Developer lifecycle CLI for Arcature applications.
Documentation
//! Release Plan data model (RV2.6).
//!
//! The Release Plan is the authoritative, machine-readable, deterministic
//! description of *what will be published* for one release transaction
//! (ADR-0005 Decision ยง6). It is produced by `arc release prepare` (RV2.7),
//! committed to git, and tied to an exact commit. The planner (`arc release
//! plan`, this phase) computes and displays the plan but mutates nothing
//! (ADR-0005 invariant 9).
//!
//! The conceptual TOML shape (ADR-0005 lines 249-269):
//!
//! ```toml
//! [transaction]
//! id = "2026-08-15.01"
//! commit = "<exact 40-char git sha>"
//!
//! [[publish]]
//! crate = "arcature-auth"
//! version = "2026.1.7"
//! order = 1
//! ```
//!
//! Exact fields are finalized in RV2.7; this model carries the information
//! the planner needs for review and the prepare step needs for
//! materialization.

use std::collections::BTreeMap;

use crate::release::change::ChangeKind;
use crate::release::version::Ybf;

/// One crate's entry in the Release Plan: its target version, the unit it
/// belongs to, the kind of change that drove the bump, and its position in
/// the topological publish order.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct PublishEntry {
    /// The crate name (e.g. `"arcature-auth"`).
    pub(crate) crate_name: String,
    /// The release unit this crate belongs to (e.g. `"core"`).
    pub(crate) unit: String,
    /// The crate's current version before this transaction.
    pub(crate) from: Ybf,
    /// The target version to publish.
    pub(crate) to: Ybf,
    /// Whether the change is None / Compatible / Breaking (from fragments).
    pub(crate) change_kind: ChangeKind,
    /// The topological publish order (1-based). Dependencies come before
    /// dependents.
    pub(crate) order: usize,
}

/// The complete Release Plan for one transaction.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ReleasePlan {
    /// A proposed transaction id (date-based, e.g. `"2026-08-15.01"`).
    /// Finalized by `arc release prepare` (RV2.7).
    pub(crate) transaction_id: String,
    /// The exact 40-character git sha the plan is pinned to. Read from the
    /// current HEAD; the prepare step commits the plan at this sha.
    pub(crate) commit: String,
    /// Per-crate publish entries, keyed by crate name and sorted
    /// deterministically (BTreeMap).
    pub(crate) entries: BTreeMap<String, PublishEntry>,
    /// The number of crates that will actually change version (change_kind
    /// != None). Crates with no fragment keep their current version and
    /// appear with `changed: false` for completeness but are not published.
    pub(crate) changed_count: usize,
}

impl ReleasePlan {
    /// The total number of publishable crates in the plan (changed or not).
    pub(crate) fn total_count(&self) -> usize {
        self.entries.len()
    }

    /// Whether every crate keeps its current version (no change fragments
    /// declare a bump for any unit). In that case the plan is a no-op.
    pub(crate) fn is_empty(&self) -> bool {
        self.changed_count == 0
    }
}