use std::collections::BTreeSet;
use serde::{Deserialize, Serialize};
use crate::extensions::backlog_state::{display_title, GitHubBacklogCache, GitHubBacklogItem};
const PRIMARY_SOURCE_ORDER: &[&str] = &["locality_focus_assignment"];
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum IssueReferenceKind {
GithubIssue,
CcdId,
Ambiguous,
}
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub(crate) struct IssueReference {
pub kind: IssueReferenceKind,
pub number: u64,
pub source: &'static str,
pub raw_reference: String,
pub context: String,
}
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub(crate) struct ResolvedBacklogItem {
pub backlog_provider: String,
pub ccd_id: u64,
pub github_issue_number: u64,
pub title: String,
pub url: String,
pub closed: bool,
pub out_of_queue: bool,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum ContinuityResolutionStatus {
ResolvedOpen,
ResolvedClosed,
Unresolved,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum ContinuityMatchedVia {
Cache,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct ResolvedIssueReference {
pub reference: IssueReference,
pub resolution_status: ContinuityResolutionStatus,
pub matched_via: ContinuityMatchedVia,
pub title: Option<String>,
pub url: Option<String>,
pub cache_item: Option<ResolvedBacklogItem>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[cfg_attr(not(any(feature = "extension-backlog", test)), allow(dead_code))]
pub(crate) struct ContinuityReferenceQuery {
pub source: String,
pub kind: IssueReferenceKind,
pub number: u64,
pub raw_reference: String,
pub context: String,
pub primary: bool,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[cfg_attr(not(any(feature = "extension-backlog", test)), allow(dead_code))]
pub(crate) struct TransientContinuityReferenceResolution {
pub source: String,
pub kind: IssueReferenceKind,
pub number: u64,
pub raw_reference: String,
pub context: String,
pub resolution_status: ContinuityResolutionStatus,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub(crate) struct IssueReferenceReport {
pub references: Vec<IssueReference>,
pub matches: Vec<ResolvedIssueReference>,
}
impl IssueReferenceReport {
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn has_primary_reference(&self) -> bool {
self.primary_source().is_some()
}
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn primary_references_all_matched(&self) -> bool {
let Some(primary_source) = self.primary_source() else {
return false;
};
if self.primary_target_is_ambiguous() {
return false;
}
let primary_references = self
.references
.iter()
.filter(|reference| reference.source == primary_source)
.count();
let primary_matches = self
.matches
.iter()
.filter(|resolved| resolved.reference.source == primary_source)
.count();
primary_references == primary_matches
}
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn reference_evidence_lines(&self) -> Vec<String> {
self.references
.iter()
.map(|reference| {
format!(
"{} references {} via `{}`.",
source_label(reference),
reference.display(),
reference.raw_reference
)
})
.collect()
}
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn matched_reference_count(&self) -> usize {
self.matches.len()
}
pub(crate) fn has_closed_matches(&self) -> bool {
self.matches.iter().any(|resolved| {
resolved.resolution_status == ContinuityResolutionStatus::ResolvedClosed
})
}
pub(crate) fn has_out_of_queue_matches(&self) -> bool {
self.matches.iter().any(|resolved| {
resolved
.cache_item
.as_ref()
.map(|item| !item.closed && item.out_of_queue)
.unwrap_or(false)
})
}
pub(crate) fn unique_closed_items(&self) -> Vec<ResolvedBacklogItem> {
let mut seen = BTreeSet::new();
let mut items = Vec::new();
for resolved in &self.matches {
let Some(item) = resolved.cache_item.as_ref() else {
continue;
};
if !item.closed {
continue;
}
let key = (item.ccd_id, item.github_issue_number);
if seen.insert(key) {
items.push(item.clone());
}
}
items
}
pub(crate) fn start_alert_message(&self, cache_status: &str) -> String {
let cache_label = if cache_status == "stale" {
"stale cached work queue"
} else {
"cached work queue"
};
let closed_items = self.unique_closed_items();
if closed_items.len() == 1 {
let item = &closed_items[0];
return format!(
"workspace-local handoff, work stream, or project-focus assignment references closed work item {}; the {cache_label} already marks it closed, so retarget the next-session handoff or work stream before relying on it",
item.display_label()
);
}
format!(
"workspace-local handoff, work stream, or project-focus assignments reference {} closed work items; the {cache_label} already marks them closed, so retarget the next-session handoff or work stream before relying on them",
closed_items.len()
)
}
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn closed_evidence_lines(&self) -> Vec<String> {
let mut evidence = Vec::new();
for resolved in &self.matches {
if resolved.resolution_status != ContinuityResolutionStatus::ResolvedClosed {
continue;
}
if let Some(item) = resolved.cache_item.as_ref() {
evidence.push(format!(
"{} references {} via `{}`; {} marks {} as closed.",
source_label(&resolved.reference),
resolved.reference.display(),
resolved.reference.raw_reference,
matched_via_label(resolved.matched_via),
item.display_label()
));
} else {
let title = resolved.title.as_deref().unwrap_or("unlabeled work item");
evidence.push(format!(
"{} references {} via `{}`; {} marks `{}` as closed.",
source_label(&resolved.reference),
resolved.reference.display(),
resolved.reference.raw_reference,
matched_via_label(resolved.matched_via),
title
));
}
}
evidence
}
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn active_evidence_lines(&self) -> Vec<String> {
let mut evidence = Vec::new();
for resolved in &self.matches {
if resolved.resolution_status != ContinuityResolutionStatus::ResolvedOpen {
continue;
}
let item_label = if let Some(item) = resolved.cache_item.as_ref() {
item.display_label()
} else {
format!(
"`{}`",
resolved.title.as_deref().unwrap_or("open work item")
)
};
if resolved
.cache_item
.as_ref()
.map(|item| item.out_of_queue)
.unwrap_or(false)
{
evidence.push(format!(
"{} still maps to open but out-of-queue work item {} via {}.",
source_label(&resolved.reference),
item_label,
matched_via_label(resolved.matched_via)
));
} else {
evidence.push(format!(
"{} still maps to active work item {} via {}.",
source_label(&resolved.reference),
item_label,
matched_via_label(resolved.matched_via)
));
}
}
evidence
}
#[cfg(any(feature = "extension-backlog", test))]
pub(crate) fn out_of_queue_evidence_lines(&self) -> Vec<String> {
let mut evidence = Vec::new();
for resolved in &self.matches {
let Some(item) = resolved.cache_item.as_ref() else {
continue;
};
if item.closed || !item.out_of_queue {
continue;
}
evidence.push(format!(
"{} references {} via `{}`; {} resolves it to {} that is no longer queue-scoped.",
source_label(&resolved.reference),
resolved.reference.display(),
resolved.reference.raw_reference,
matched_via_label(resolved.matched_via),
item.out_of_queue_label()
));
}
evidence
}
pub(crate) fn out_of_queue_start_alert_message(&self) -> String {
let count = self
.matches
.iter()
.filter(|resolved| {
resolved
.cache_item
.as_ref()
.map(|item| !item.closed && item.out_of_queue)
.unwrap_or(false)
})
.count();
if count == 1 {
if let Some(item) = self.matches.iter().find_map(|resolved| {
resolved
.cache_item
.as_ref()
.filter(|item| !item.closed && item.out_of_queue)
}) {
return format!(
"workspace-local handoff, work stream, or project-focus assignment still references {} that is no longer queue-scoped by CCD priority labels; keep continuity if intentional or relabel/retarget it before relying on backlog selection",
item.out_of_queue_label()
);
}
return "workspace-local handoff, work stream, or project-focus assignment still references an open work item that is no longer queue-scoped by CCD priority labels; keep continuity if intentional or relabel/retarget it before relying on backlog selection".to_owned();
}
format!(
"workspace-local handoff, work stream, or project-focus assignments still reference {count} open work items that are no longer queue-scoped by CCD priority labels; keep continuity if intentional or relabel/retarget them before relying on backlog selection"
)
}
fn primary_source(&self) -> Option<&'static str> {
PRIMARY_SOURCE_ORDER.iter().copied().find(|source| {
self.references
.iter()
.any(|reference| reference.source == *source)
})
}
fn primary_target_is_ambiguous(&self) -> bool {
let Some(primary_source) = self.primary_source() else {
return false;
};
let references = self
.references
.iter()
.filter(|reference| reference.source == primary_source)
.collect::<Vec<_>>();
select_primary_reference(&references).is_none() && !references.is_empty()
}
}
impl IssueReference {
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn display(&self) -> String {
match self.kind {
IssueReferenceKind::GithubIssue => format!("GH#{}", self.number),
IssueReferenceKind::CcdId => format!("ccd#{}", self.number),
IssueReferenceKind::Ambiguous => format!("issue #{}", self.number),
}
}
}
impl ResolvedBacklogItem {
fn provider_is_github(&self) -> bool {
self.backlog_provider == "github-issues" && self.github_issue_number != 0
}
fn display_reference(&self) -> String {
let ccd_ref = (self.ccd_id != 0).then(|| format!("ccd#{}", self.ccd_id));
let github_ref = self
.provider_is_github()
.then(|| format!("GH#{}", self.github_issue_number));
match (ccd_ref, github_ref) {
(Some(ccd_ref), Some(github_ref)) if ccd_ref != github_ref => {
format!("{ccd_ref} / {github_ref}")
}
(Some(ccd_ref), _) => ccd_ref,
(None, Some(github_ref)) => github_ref,
(None, None) => "work item".to_owned(),
}
}
fn display_label(&self) -> String {
format!("`{}` `{}`", self.display_reference(), self.title)
}
fn out_of_queue_label(&self) -> String {
if self.provider_is_github() {
format!(
"open GitHub issue `GH#{}` `{}`",
self.github_issue_number, self.title
)
} else {
format!("open work item {}", self.display_label())
}
}
}
pub(crate) fn resolve_issue_references(
branch: &str,
handoff_title: &str,
immediate_actions: &[String],
backlog_cache: Option<&GitHubBacklogCache>,
) -> IssueReferenceReport {
let mut references = Vec::new();
references.extend(collect_branch_references(branch));
references.extend(collect_text_references("handoff_title", handoff_title));
for action in immediate_actions {
references.extend(collect_text_references("immediate_action", action));
}
dedupe_references(&mut references);
let Some(cache) = backlog_cache else {
return IssueReferenceReport {
references,
matches: Vec::new(),
};
};
let matches = references
.iter()
.filter_map(|reference| resolve_reference(reference, &cache.items))
.collect();
IssueReferenceReport {
references,
matches,
}
}
fn dedupe_references(references: &mut Vec<IssueReference>) {
let mut seen = BTreeSet::new();
references.retain(|reference| {
seen.insert((
reference.kind,
reference.number,
reference.source,
reference.raw_reference.clone(),
reference.context.clone(),
))
});
}
fn collect_branch_references(branch: &str) -> Vec<IssueReference> {
let mut references = Vec::new();
for segment in branch.split('/') {
if let Some(number) = strip_prefixed_number(segment, "issue-") {
references.push(IssueReference {
kind: IssueReferenceKind::GithubIssue,
number,
source: "branch",
raw_reference: format!("issue-{number}"),
context: branch.to_owned(),
});
continue;
}
if let Some(number) = strip_prefixed_number(segment, "gh-") {
references.push(IssueReference {
kind: IssueReferenceKind::GithubIssue,
number,
source: "branch",
raw_reference: format!("gh-{number}"),
context: branch.to_owned(),
});
continue;
}
if let Some(number) = strip_prefixed_number(segment, "ccd-") {
references.push(IssueReference {
kind: IssueReferenceKind::Ambiguous,
number,
source: "branch",
raw_reference: format!("ccd-{number}"),
context: branch.to_owned(),
});
continue;
}
if let Some(number) = leading_branch_number(segment) {
references.push(IssueReference {
kind: IssueReferenceKind::GithubIssue,
number,
source: "branch",
raw_reference: segment.to_owned(),
context: branch.to_owned(),
});
}
}
references
}
fn strip_prefixed_number(segment: &str, prefix: &str) -> Option<u64> {
let suffix = segment.strip_prefix(prefix)?;
let digits = suffix
.chars()
.take_while(|ch| ch.is_ascii_digit())
.collect::<String>();
if digits.is_empty() {
return None;
}
let remainder = &suffix[digits.len()..];
if remainder.is_empty()
|| remainder.starts_with('-')
|| remainder.starts_with('_')
|| remainder.starts_with('/')
{
return digits.parse().ok();
}
None
}
fn leading_branch_number(segment: &str) -> Option<u64> {
let digits = segment
.chars()
.take_while(|ch| ch.is_ascii_digit())
.collect::<String>();
if digits.is_empty() {
return None;
}
let remainder = &segment[digits.len()..];
if remainder.is_empty() || remainder.starts_with('-') || remainder.starts_with('_') {
return digits.parse().ok();
}
None
}
fn collect_text_references(source: &'static str, text: &str) -> Vec<IssueReference> {
let bytes = text.as_bytes();
let mut references = Vec::new();
let mut index = 0usize;
while index < bytes.len() {
if bytes[index] != b'#' {
index += 1;
continue;
}
let digit_start = index + 1;
let digit_end = consume_digits(bytes, digit_start);
if digit_end == digit_start {
index += 1;
continue;
}
let prefix_start = contiguous_ascii_alpha_start(bytes, index);
let prefix = text[prefix_start..index].to_ascii_lowercase();
let previous = if prefix_start == 0 {
None
} else {
Some(bytes[prefix_start - 1])
};
let kind = if prefix == "ccd" {
Some(IssueReferenceKind::CcdId)
} else if prefix == "gh" {
Some(IssueReferenceKind::GithubIssue)
} else if previous.is_some_and(|byte| byte.is_ascii_alphanumeric()) {
None
} else {
Some(IssueReferenceKind::GithubIssue)
};
let Some(kind) = kind else {
index = digit_end;
continue;
};
let raw_start = if matches!(
kind,
IssueReferenceKind::CcdId | IssueReferenceKind::GithubIssue
) && (prefix == "ccd" || prefix == "gh")
{
prefix_start
} else {
index
};
if let Ok(number) = text[digit_start..digit_end].parse() {
references.push(IssueReference {
kind,
number,
source,
raw_reference: text[raw_start..digit_end].to_owned(),
context: text.trim().to_owned(),
});
}
index = digit_end;
}
references
}
fn contiguous_ascii_alpha_start(bytes: &[u8], end: usize) -> usize {
let mut start = end;
while start > 0 && bytes[start - 1].is_ascii_alphabetic() {
start -= 1;
}
start
}
fn consume_digits(bytes: &[u8], start: usize) -> usize {
let mut end = start;
while end < bytes.len() && bytes[end].is_ascii_digit() {
end += 1;
}
end
}
fn resolve_reference(
reference: &IssueReference,
items: &[GitHubBacklogItem],
) -> Option<ResolvedIssueReference> {
let matched = match reference.kind {
IssueReferenceKind::GithubIssue => items
.iter()
.filter(|item| item.github_issue_number == reference.number)
.collect::<Vec<_>>(),
IssueReferenceKind::CcdId => items
.iter()
.filter(|item| item.ccd_id == reference.number)
.collect::<Vec<_>>(),
IssueReferenceKind::Ambiguous => {
let github_matches = items
.iter()
.filter(|item| item.github_issue_number == reference.number)
.collect::<Vec<_>>();
let ccd_matches = items
.iter()
.filter(|item| item.ccd_id == reference.number)
.collect::<Vec<_>>();
match (github_matches.is_empty(), ccd_matches.is_empty()) {
(true, true) => Vec::new(),
(false, true) => github_matches,
(true, false) => ccd_matches,
(false, false) => {
let mut merged = github_matches;
for item in ccd_matches {
if !merged.iter().any(|existing| {
existing.ccd_id == item.ccd_id
&& existing.github_issue_number == item.github_issue_number
}) {
merged.push(item);
}
}
let has_closed = merged.iter().any(|item| !item.is_active());
let has_open = merged.iter().any(|item| item.is_active());
if has_closed && has_open {
Vec::new()
} else {
merged
}
}
}
}
};
if matched.is_empty() {
return None;
}
if matched.len() != 1 {
return None;
}
let item = matched[0];
let cache_item = ResolvedBacklogItem {
backlog_provider: item.backlog_ref.provider.clone(),
ccd_id: item.ccd_id,
github_issue_number: item.github_issue_number,
title: display_title(&item.title),
url: item.url.clone(),
closed: !item.is_active(),
out_of_queue: item.is_active() && !item.is_queue_scoped(),
};
Some(ResolvedIssueReference {
reference: reference.clone(),
resolution_status: if cache_item.closed {
ContinuityResolutionStatus::ResolvedClosed
} else {
ContinuityResolutionStatus::ResolvedOpen
},
matched_via: ContinuityMatchedVia::Cache,
title: Some(cache_item.title.clone()),
url: Some(cache_item.url.clone()),
cache_item: Some(cache_item),
})
}
#[cfg_attr(not(test), allow(dead_code))]
fn source_label(reference: &IssueReference) -> String {
match reference.source {
"locality_focus_assignment" => {
format!(
"Project-focus assignment for work stream `{}`",
reference.context
)
}
"branch" => format!("Work stream `{}`", reference.context),
"handoff_title" => format!("Handoff title `{}`", reference.context),
"immediate_action" => format!("Immediate action `{}`", reference.context),
_ => format!("Reference `{}`", reference.context),
}
}
fn select_primary_reference<'a>(
references: &'a [&'a IssueReference],
) -> Option<&'a IssueReference> {
let first = *references.first()?;
if references
.iter()
.all(|reference| same_reference_target(reference, first))
{
Some(first)
} else {
None
}
}
fn same_reference_target(left: &IssueReference, right: &IssueReference) -> bool {
left.kind == right.kind && left.number == right.number
}
#[cfg_attr(not(test), allow(dead_code))]
fn matched_via_label(matched_via: ContinuityMatchedVia) -> &'static str {
match matched_via {
ContinuityMatchedVia::Cache => "the workspace-local work queue cache",
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::extensions::backlog_state::GitHubBacklogCache;
fn test_item(
ccd_id: u64,
github_issue_number: u64,
title: &str,
github_state: &str,
) -> GitHubBacklogItem {
GitHubBacklogItem {
backlog_ref: crate::extensions::backlog_state::BacklogRef {
provider: "github-issues".to_owned(),
kind: "issue".to_owned(),
id: github_issue_number.to_string(),
url: format!("https://example.com/issues/{github_issue_number}"),
},
ccd_id,
github_issue_number,
github_state: github_state.to_owned(),
title: title.to_owned(),
url: format!("https://example.com/issues/{github_issue_number}"),
kind: "item".to_owned(),
section: "tests".to_owned(),
status: if github_state.eq_ignore_ascii_case("closed") {
"done".to_owned()
} else {
"ready".to_owned()
},
effort: "medium".to_owned(),
impact: "high".to_owned(),
module: None,
priority_rank: None,
claimed_by: None,
depends_on: Vec::new(),
roadmap_epic: None,
spec_refs: Vec::new(),
summary: title.to_owned(),
acceptance_criteria: Vec::new(),
related_specs: Vec::new(),
operator_notes: Vec::new(),
labels: Vec::new(),
assignees: Vec::new(),
queue_state: crate::extensions::backlog_state::GitHubQueueState::QueueCandidate,
priority_label: None,
metadata_status: crate::extensions::backlog_state::MetadataStatus::Enriched,
metadata_error: None,
upstream_claim: crate::extensions::backlog_state::UpstreamClaimState::Unclaimed,
dispatch_state: crate::extensions::backlog_state::GitHubDispatchState::Ready,
}
}
fn local_markdown_item(
ccd_id: u64,
github_issue_number: u64,
title: &str,
github_state: &str,
out_of_queue: bool,
) -> GitHubBacklogItem {
GitHubBacklogItem {
backlog_ref: crate::extensions::backlog_state::BacklogRef {
provider: "local-markdown".to_owned(),
kind: "item".to_owned(),
id: ccd_id.to_string(),
url: format!("file:///tmp/local-markdown-{ccd_id}"),
},
ccd_id,
github_issue_number,
github_state: github_state.to_owned(),
title: title.to_owned(),
url: format!("file:///tmp/local-markdown-{ccd_id}"),
kind: "item".to_owned(),
section: "tests".to_owned(),
status: if github_state == "closed" {
"done".to_owned()
} else {
"ready".to_owned()
},
effort: String::new(),
impact: String::new(),
module: None,
priority_rank: Some(ccd_id),
claimed_by: None,
depends_on: Vec::new(),
roadmap_epic: None,
spec_refs: Vec::new(),
summary: title.to_owned(),
acceptance_criteria: Vec::new(),
related_specs: Vec::new(),
operator_notes: Vec::new(),
labels: Vec::new(),
assignees: Vec::new(),
queue_state: if out_of_queue {
crate::extensions::backlog_state::GitHubQueueState::OutOfQueue
} else {
crate::extensions::backlog_state::GitHubQueueState::QueueCandidate
},
priority_label: None,
metadata_status: crate::extensions::backlog_state::MetadataStatus::Absent,
metadata_error: None,
upstream_claim: crate::extensions::backlog_state::UpstreamClaimState::Unclaimed,
dispatch_state: if out_of_queue {
crate::extensions::backlog_state::GitHubDispatchState::OutOfQueue
} else {
crate::extensions::backlog_state::GitHubDispatchState::Ready
},
}
}
#[test]
fn parses_text_and_branch_issue_references() {
let references = resolve_issue_references(
"codex/ccd-46-retry-backoff",
"Next Session: Verify #122 follow-up",
&["Review ccd#46 before merge.".to_owned()],
None,
);
assert!(references
.references
.iter()
.any(|reference| reference.source == "branch"
&& reference.kind == IssueReferenceKind::Ambiguous
&& reference.number == 46));
assert!(references
.references
.iter()
.any(|reference| reference.source == "handoff_title"
&& reference.kind == IssueReferenceKind::GithubIssue
&& reference.number == 122));
assert!(references
.references
.iter()
.any(|reference| reference.source == "immediate_action"
&& reference.kind == IssueReferenceKind::CcdId
&& reference.number == 46));
}
#[test]
fn ambiguous_branch_reference_is_ignored_when_cache_conflicts() {
let cache = GitHubBacklogCache::new(
"example/ccd-guide",
1,
vec![
test_item(46, 400, "[ccd#46] local id", "open"),
test_item(99, 46, "github issue 46", "closed"),
],
);
let report = resolve_issue_references(
"codex/ccd-46-retry-backoff",
"Next Session: follow-up",
&[],
Some(&cache),
);
assert!(report.matches.is_empty());
}
#[test]
fn closed_evidence_uses_cache_matches() {
let cache = GitHubBacklogCache::new(
"example/ccd-guide",
1,
vec![test_item(
71,
46,
"[ccd#71] retry backoff cleanup",
"closed",
)],
);
let report = resolve_issue_references(
"main",
"Next Session: follow-up",
&["Review GH#46 before branching.".to_owned()],
Some(&cache),
);
assert!(report.has_closed_matches());
assert!(report.closed_evidence_lines()[0].contains("GH#46"));
}
#[test]
fn handoff_and_branch_references_are_observed_without_assignment() {
let cache = GitHubBacklogCache::new(
"example/ccd-guide",
1,
vec![test_item(46, 122, "[ccd#46] retry backoff cleanup", "open")],
);
let report = resolve_issue_references(
"codex/122-follow-up",
"Next Session: Review GH#122 follow-up",
&["Inspect ccd#46 before merge.".to_owned()],
Some(&cache),
);
assert!(!report.has_primary_reference());
assert!(report
.references
.iter()
.any(|reference| reference.source == "handoff_title"));
assert!(report
.references
.iter()
.any(|reference| reference.source == "branch"));
}
#[test]
fn local_markdown_closed_messages_do_not_emit_phantom_github_refs() {
let cache = GitHubBacklogCache::new(
"example/ccd-guide",
1,
vec![local_markdown_item(
71,
171,
"[ccd#71] local markdown item",
"closed",
false,
)],
);
let report =
resolve_issue_references("main", "Next Session: inspect ccd#71", &[], Some(&cache));
assert!(report.has_closed_matches());
assert!(report.closed_evidence_lines()[0].contains("ccd#71"));
assert!(!report.closed_evidence_lines()[0].contains("GH#171"));
}
#[test]
fn local_markdown_out_of_queue_messages_do_not_emit_phantom_github_refs() {
let cache = GitHubBacklogCache::new(
"example/ccd-guide",
1,
vec![local_markdown_item(
71,
171,
"[ccd#71] local markdown item",
"open",
true,
)],
);
let report =
resolve_issue_references("main", "Next Session: inspect ccd#71", &[], Some(&cache));
assert!(report.has_out_of_queue_matches());
assert!(report.out_of_queue_evidence_lines()[0].contains("ccd#71"));
assert!(!report.out_of_queue_evidence_lines()[0].contains("GH#171"));
assert!(report.out_of_queue_start_alert_message().contains("ccd#71"));
assert!(!report.out_of_queue_start_alert_message().contains("GH#171"));
assert!(!report
.out_of_queue_start_alert_message()
.contains("GitHub issue"));
}
}