rusty-beads 0.1.0

Git-backed graph issue tracker for AI coding agents - a Rust implementation with context store, dependency tracking, and semantic compaction
Documentation
//! Issue type definitions.

use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;

/// The category of work an issue represents.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum IssueType {
    /// Defect or malfunction.
    Bug,
    /// New capability or enhancement.
    Feature,
    /// General work item (default).
    #[default]
    Task,
    /// Large initiative with multiple steps.
    Epic,
    /// Routine maintenance.
    Chore,
    /// Ephemeral communication.
    Message,
    /// Template for issue hierarchies.
    Molecule,
    /// Async coordination mechanism.
    Gate,
    /// Agent identity bead.
    Agent,
    /// Agent role definition.
    Role,
    /// Rig identity (multi-repo workspace).
    Rig,
    /// Cross-project tracking.
    Convoy,
    /// Operational state change record.
    Event,
    /// Merge queue entry.
    MergeRequest,
    /// Exclusive access slot.
    Slot,
}

impl IssueType {
    /// All valid issue types.
    pub fn all() -> &'static [IssueType] {
        &[
            IssueType::Bug,
            IssueType::Feature,
            IssueType::Task,
            IssueType::Epic,
            IssueType::Chore,
            IssueType::Message,
            IssueType::Molecule,
            IssueType::Gate,
            IssueType::Agent,
            IssueType::Role,
            IssueType::Rig,
            IssueType::Convoy,
            IssueType::Event,
            IssueType::MergeRequest,
            IssueType::Slot,
        ]
    }

    /// Returns the string representation for database storage.
    pub fn as_str(&self) -> &'static str {
        match self {
            IssueType::Bug => "bug",
            IssueType::Feature => "feature",
            IssueType::Task => "task",
            IssueType::Epic => "epic",
            IssueType::Chore => "chore",
            IssueType::Message => "message",
            IssueType::Molecule => "molecule",
            IssueType::Gate => "gate",
            IssueType::Agent => "agent",
            IssueType::Role => "role",
            IssueType::Rig => "rig",
            IssueType::Convoy => "convoy",
            IssueType::Event => "event",
            IssueType::MergeRequest => "merge_request",
            IssueType::Slot => "slot",
        }
    }

    /// Returns true if this type represents a container for other issues.
    pub fn is_container(&self) -> bool {
        matches!(self, IssueType::Epic | IssueType::Molecule | IssueType::Convoy)
    }

    /// Returns true if this type represents agent-related work.
    pub fn is_agent_type(&self) -> bool {
        matches!(self, IssueType::Agent | IssueType::Role | IssueType::Rig)
    }
}

impl fmt::Display for IssueType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.as_str())
    }
}

impl FromStr for IssueType {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "bug" => Ok(IssueType::Bug),
            "feature" => Ok(IssueType::Feature),
            "task" => Ok(IssueType::Task),
            "epic" => Ok(IssueType::Epic),
            "chore" => Ok(IssueType::Chore),
            "message" => Ok(IssueType::Message),
            "molecule" => Ok(IssueType::Molecule),
            "gate" => Ok(IssueType::Gate),
            "agent" => Ok(IssueType::Agent),
            "role" => Ok(IssueType::Role),
            "rig" => Ok(IssueType::Rig),
            "convoy" => Ok(IssueType::Convoy),
            "event" => Ok(IssueType::Event),
            "merge_request" | "merge-request" | "mergerequest" => Ok(IssueType::MergeRequest),
            "slot" => Ok(IssueType::Slot),
            _ => Err(format!("unknown issue type: {}", s)),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_issue_type_roundtrip() {
        for issue_type in IssueType::all() {
            let s = issue_type.as_str();
            let parsed: IssueType = s.parse().unwrap();
            assert_eq!(*issue_type, parsed);
        }
    }
}