1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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()
}
}