cartulary 0.3.0-alpha.1

The knowledge layer of your project — decisions, issues, docs, all in one place.
Documentation
use crate::domain::model::check::Violations;
use crate::domain::model::issue::companion::{
    CompanionContent, CompanionIdentifier, IssueCompanions,
};
use crate::domain::model::issue::{Issue, IssueCollection};
use crate::domain::model::record_ref::IssueRef;

/// Port: persistence for issues. Reads (`list`, `find_by_id`) merge the
/// writable home with any read-only union sources; writes (`save`) only
/// hit the writable home. Each returned `Issue` carries its `origin` so
/// callers can tell `Local` from `Union { source }` without a second
/// lookup. Bulk traversal with per-file error reporting lives behind the
/// separate [`crate::domain::usecases::issue::IssueScanner`] port so the
/// two concerns can be wired independently.
pub trait IssueRepository {
    fn save(&self, issue: &Issue) -> anyhow::Result<()>;
    fn list(&self) -> anyhow::Result<IssueCollection>;
    fn find_by_id(&self, id: &IssueRef) -> anyhow::Result<Option<Issue>>;
    /// Enumerate every companion file in the issue's directory as pure
    /// metadata: canonical kinds (`Plan` / `ImplementationNotes` /
    /// `DesignDecision`) are recognised by filename, anything else is
    /// reported as `Other`. No content is loaded here. A missing
    /// directory yields an empty collection (not an error).
    fn issue_companions(&self, id: &IssueRef) -> anyhow::Result<IssueCompanions>;
    /// Load the content of a single companion. Canonical companions
    /// surface as [`Text`], everything else as [`Binary`]. Returns
    /// `Ok(None)` when the identifier does not resolve to a file in
    /// the issue's directory.
    ///
    /// [`Text`]: CompanionContent::Text
    /// [`Binary`]: CompanionContent::Binary
    fn read_companion(
        &self,
        id: &IssueRef,
        identifier: &CompanionIdentifier,
    ) -> anyhow::Result<Option<CompanionContent>>;
    /// Return the configured ID prefix for issues (e.g. `"ISSUE-"`), if any.
    /// Used by `check_issues` to detect prefix mismatches.
    fn configured_id_prefix(&self) -> Option<&str> {
        None
    }
}

/// A validated result for a single issue file, combining parse errors
/// and semantic violations.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IssueCheckResult {
    pub path: std::path::PathBuf,
    pub violations: Violations,
}

impl IssueCheckResult {
    pub fn has_errors(&self) -> bool {
        self.violations.has_errors()
    }
}