govctl 0.17.0

Project governance CLI for RFC, ADR, and Work Item management
use super::{OwnedEditAction, execute};
use crate::cmd;
use crate::config::Config;
use crate::diagnostic::{DiagnosticResult, Diagnostics};
use crate::model::{ClauseKind, RfcPhase, WorkItemStatus};
use crate::write::{BumpLevel, WriteOp};
use crate::{
    FinalizeStatus, GetOutputFormat, ListOutputFormat, ListTarget, OutputFormat, RenderTarget,
    ShowOutputFormat, TraceOutputFormat,
};
use std::path::PathBuf;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Scope {
    Global,
    Collection {
        target: ListTarget,
    },
    Artifact {
        artifact: cmd::edit::ArtifactType,
        id: String,
    },
    Target {
        artifact: cmd::edit::ArtifactType,
        id: String,
        target: cmd::edit::engine::ResolvedTarget,
    },
}

#[derive(Debug, Clone)]
pub enum BuiltinOp {
    Init {
        force: bool,
    },
    InitSkills {
        force: bool,
        format: crate::SkillFormat,
        dir: Option<std::path::PathBuf>,
    },
    Check,
    Status,
    RenderGlobal {
        target: RenderTarget,
        dry_run: bool,
        force: bool,
    },
    Migrate,
    Verify {
        guard_ids: Vec<String>,
        work: Option<String>,
    },
    Search {
        query: Vec<String>,
        types: Vec<ListTarget>,
        tags: Vec<String>,
        limit: Option<usize>,
        output: OutputFormat,
        reindex: bool,
    },
    Describe {
        context: bool,
    },
    Completions {
        shell: clap_complete::Shell,
    },
    SelfUpdate {
        check: bool,
    },
    #[cfg(feature = "tui")]
    Tui,
    ReleaseCut {
        version: String,
        date: Option<String>,
    },
    ReleaseUndo {
        expected_version: String,
    },
    TagNew {
        tag: String,
    },
    TagDelete {
        tag: String,
    },
    TagList {
        output: crate::OutputFormat,
    },
    LoopStart {
        loop_id: Option<String>,
        work_ids: Vec<String>,
    },
    LoopList {
        filter: Option<String>,
        limit: Option<usize>,
        output: crate::OutputFormat,
    },
    LoopShow {
        loop_id: String,
    },
    LoopResume {
        loop_id: String,
    },
    LoopReplan {
        loop_id: String,
    },
    LoopAdd {
        loop_id: String,
        field: String,
        value: String,
    },
    LoopRemove {
        loop_id: String,
        field: String,
        value: String,
    },
    LoopRun {
        loop_id: String,
        target_work_ids: Vec<String>,
    },
    ConformanceList {
        filter: Option<String>,
        limit: Option<usize>,
        output: Option<ListOutputFormat>,
        tags: Vec<String>,
    },
    ConformanceGet {
        id: String,
        field: Option<String>,
        output: Option<GetOutputFormat>,
    },
    ConformanceShow {
        id: String,
        output: ShowOutputFormat,
        history: bool,
    },
    ConformanceNew {
        title: String,
        path: String,
        selector: String,
        requirements: Vec<String>,
        guards: Vec<String>,
        id: Option<String>,
    },
    ConformanceEdit {
        id: String,
        path: String,
        action: OwnedEditAction,
    },
    ConformanceDelete {
        id: String,
        force: bool,
    },
    ConformanceTrace {
        target: Option<String>,
        output: Option<TraceOutputFormat>,
    },
}

impl BuiltinOp {
    fn is_lock_free(&self) -> bool {
        match self {
            Self::Check { .. }
            | Self::Status
            | Self::Verify { .. }
            | Self::Describe { .. }
            | Self::Completions { .. }
            | Self::SelfUpdate { .. }
            | Self::TagList { .. }
            | Self::LoopList { .. }
            | Self::LoopShow { .. }
            | Self::LoopResume { .. } => true,
            Self::ConformanceList { .. }
            | Self::ConformanceGet { .. }
            | Self::ConformanceShow { .. }
            | Self::ConformanceTrace { .. } => true,
            // [[RFC-0002:C-SEARCH-COMMAND]]: search may sync `.govctl/`
            // derived local state but must not mutate governed artifacts or
            // rendered docs; [[RFC-0004:C-DEFINITIONS]] keeps that outside the
            // gov-root write-lock class.
            Self::Search { .. } => true,
            #[cfg(feature = "tui")]
            Self::Tui => true,
            _ => false,
        }
    }
}

#[derive(Debug, Clone)]
pub enum CreateOp {
    Rfc {
        title: String,
        id: Option<String>,
    },
    Clause {
        clause_id: String,
        title: String,
        section: String,
        kind: ClauseKind,
    },
    Adr {
        title: String,
    },
    Work {
        title: String,
        active: bool,
    },
    Guard {
        title: String,
    },
}

#[derive(Debug, Clone)]
pub enum EditOp {
    Field { action: OwnedEditAction },
}

#[derive(Debug, Clone)]
pub enum LifecycleOp {
    Bump {
        level: Option<BumpLevel>,
        summary: Option<String>,
        changes: Vec<String>,
    },
    Finalize {
        status: FinalizeStatus,
    },
    Advance {
        phase: RfcPhase,
    },
    Deprecate {
        force: bool,
    },
    Supersede {
        by: String,
        force: bool,
    },
    AcceptAdr {
        force: bool,
    },
    RejectAdr,
    MoveWork {
        file_or_id: PathBuf,
        status: WorkItemStatus,
    },
}

#[derive(Debug, Clone)]
pub enum Op {
    Builtin(BuiltinOp),
    Create(CreateOp),
    List {
        filter: Option<String>,
        limit: Option<usize>,
        output: Option<ListOutputFormat>,
        /// Tags to filter by (artifact must have ALL specified tags) — [[RFC-0002:C-CRUD-VERBS]]
        tags: Vec<String>,
    },
    Get {
        output: Option<GetOutputFormat>,
    },
    Show {
        output: ShowOutputFormat,
        history: bool,
    },
    Edit(EditOp),
    Lifecycle(LifecycleOp),
    Delete {
        force: bool,
    },
    RenderArtifact {
        dry_run: bool,
    },
}

impl Op {
    fn is_lock_free(&self) -> bool {
        match self {
            Self::Builtin(builtin) => builtin.is_lock_free(),
            Self::Get { .. } | Self::List { .. } | Self::Show { .. } => true,
            _ => false,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LockDisposition {
    None,
    GovRootExclusive,
}

#[derive(Debug, Clone)]
pub struct CommandPlan {
    pub scope: Scope,
    pub op: Op,
}

impl CommandPlan {
    pub(crate) fn new(scope: Scope, op: Op) -> Self {
        Self { scope, op }
    }

    pub fn lock_disposition(&self) -> LockDisposition {
        if self.op.is_lock_free() {
            LockDisposition::None
        } else {
            LockDisposition::GovRootExclusive
        }
    }

    pub fn execute(&self, config: &Config, op: WriteOp) -> DiagnosticResult<Diagnostics> {
        execute::execute_plan(self, config, op)
    }
}