use std::sync::OnceLock;
use regex::Regex;
struct TicketPatterns {
gh_action: Regex,
azdo: Regex,
}
fn patterns() -> &'static TicketPatterns {
static PATTERNS: OnceLock<TicketPatterns> = OnceLock::new();
PATTERNS.get_or_init(|| {
TicketPatterns {
gh_action: Regex::new(r"(?i)\b(?:fix(?:es|ed)?|close[sd]?|resolve[sd]?)\s+#\d+\b")
.expect("gh_action pattern compiles"),
azdo: Regex::new(r"\bAB#\d+\b").expect("azdo pattern compiles"),
}
})
}
const NON_TICKET_PREFIXES: &[&str] = &[
"ADR", "DOC", "RFC", "SPEC", "ANSI", "ECMA", "IEC", "IEEE", "ISO", "ITU", "ASCII", "UTF", "AES", "CRC", "MD", "RSA", "SHA", "CAPEC", "CVE", "CWE", "GHSA", "RUSTSEC", "GCC", "LLVM",
];
struct ExtractPatterns {
azdo: Regex,
subject_prefix: Regex,
jira_anchored: Regex,
gh_bare: Regex,
jira_branch_segment: Regex,
gh_action_ref: Regex,
}
fn extract_patterns() -> &'static ExtractPatterns {
static EXTRACT: OnceLock<ExtractPatterns> = OnceLock::new();
EXTRACT.get_or_init(|| {
ExtractPatterns {
azdo: Regex::new(r"\bAB#\d+\b").expect("azdo extract pattern compiles"),
subject_prefix: Regex::new(
r"^(?:[a-z][a-z0-9]*(?:\([^)\n]*\))?!?:[ \t]*)?[\[(]?[ \t]*",
)
.expect("subject_prefix pattern compiles"),
jira_anchored: Regex::new(r"^([A-Z][A-Z0-9]*-\d+)(?:$|[\s:,.;)\]])")
.expect("jira_anchored pattern compiles"),
gh_bare: Regex::new(r"(?:^|\s)(#\d+)\b").expect("gh_bare extract pattern compiles"),
jira_branch_segment: Regex::new(r"^([A-Z][A-Z0-9]*-\d+)(?:$|[-_./\s])")
.expect("jira_branch_segment pattern compiles"),
gh_action_ref: Regex::new(
r"(?i)\b(?:fix(?:es|ed)?|close[sd]?|resolve[sd]?)\s+(#\d+)\b",
)
.expect("gh_action_ref pattern compiles"),
}
})
}
pub(crate) fn is_non_ticket_identifier(key: &str) -> bool {
key.split_once('-')
.is_some_and(|(prefix, _)| NON_TICKET_PREFIXES.contains(&prefix))
}
fn subject_ticket_key(subject: &str) -> Option<String> {
let p = extract_patterns();
let stripped = p.subject_prefix.replace(subject, "");
let key = p
.jira_anchored
.captures(stripped.as_ref())?
.get(1)?
.as_str()
.to_string();
if is_non_ticket_identifier(&key) {
None
} else {
Some(key)
}
}
pub fn is_ticketed(message: &str) -> bool {
let p = patterns();
p.gh_action.is_match(message)
|| p.azdo.is_match(message)
|| subject_ticket_key(message.lines().next().unwrap_or("")).is_some()
}
pub fn extract_ticket_id(message: &str) -> Option<String> {
let p = extract_patterns();
if let Some(m) = p.azdo.find(message) {
return Some(m.as_str().to_string());
}
if let Some(key) = subject_ticket_key(message.lines().next().unwrap_or("")) {
return Some(key);
}
if let Some(caps) = p.gh_bare.captures(message) {
if let Some(m) = caps.get(1) {
return Some(m.as_str().to_string());
}
}
None
}
pub fn branch_ticket_key(branch: &str) -> Option<String> {
let p = extract_patterns();
let trimmed = branch
.trim()
.strip_prefix("refs/heads/")
.unwrap_or(branch.trim());
for segment in trimmed.split('/') {
let Some(key) = p
.jira_branch_segment
.captures(segment)
.and_then(|c| c.get(1))
.map(|m| m.as_str().to_string())
else {
continue;
};
if !is_non_ticket_identifier(&key) {
return Some(key);
}
}
None
}
pub fn pr_body_ticket_key(body: &str) -> Option<String> {
extract_patterns()
.gh_action_ref
.captures(body)
.and_then(|c| c.get(1))
.map(|m| m.as_str().to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn patterns_compile() {
let _ = patterns();
}
#[test]
fn extract_patterns_compile() {
let _ = extract_patterns();
}
#[test]
fn extract_ticket_id_bb_2746() {
assert_eq!(
extract_ticket_id("BB-2746: refactor auth service"),
Some("BB-2746".to_string())
);
}
#[test]
fn extract_ticket_id_sre_3104() {
assert_eq!(
extract_ticket_id("SRE-3104: increase RDS connection timeout"),
Some("SRE-3104".to_string())
);
}
#[test]
fn extract_ticket_id_dre_405() {
assert_eq!(
extract_ticket_id("DRE-405 fix demand calculation"),
Some("DRE-405".to_string())
);
}
#[test]
fn extract_ticket_id_returns_none_for_plain_message() {
assert_eq!(extract_ticket_id("misc cleanup"), None);
assert_eq!(extract_ticket_id("update README"), None);
assert_eq!(extract_ticket_id("bump version to 1.2.3"), None);
}
#[test]
fn extract_ticket_id_github_bare_ref() {
assert_eq!(extract_ticket_id("fixes #99"), Some("#99".to_string()));
}
#[test]
fn extract_ticket_id_azdo_ref() {
assert_eq!(
extract_ticket_id("AB#42 implement feature"),
Some("AB#42".to_string())
);
}
#[test]
fn extract_ticket_id_azdo_preferred_over_jira() {
assert_eq!(
extract_ticket_id("AB#10 fixes PROJ-99"),
Some("AB#10".to_string())
);
}
#[test]
fn extract_ticket_id_jira_preferred_over_gh_bare() {
assert_eq!(
extract_ticket_id("ENG-7 closes #10"),
Some("ENG-7".to_string())
);
}
#[test]
fn extract_ticket_id_multiline_body() {
let msg = "Refactor module structure\n\nRelates to SRE-999.\n";
assert_eq!(extract_ticket_id(msg), None);
let anchored = "SRE-999 refactor module structure\n\nDetails here.\n";
assert_eq!(extract_ticket_id(anchored), Some("SRE-999".to_string()));
}
#[test]
fn adr_reference_is_not_a_ticket_key() {
assert_eq!(extract_ticket_id("docs(adr): ADR-0030 point 7"), None);
let msg = "fix(relay): verify the HMAC once\n\nPer ADR-0034 the relay spools durably.\nCloses #5089\nCloses #5175\n";
assert_eq!(extract_ticket_id(msg), Some("#5089".to_string()));
assert_eq!(extract_ticket_id("docs: DOC-67 §2 rewrite"), None);
assert_eq!(extract_ticket_id("RFC-2119 keyword sweep"), None);
assert_eq!(extract_ticket_id("SPEC-14 tightened"), None);
}
#[test]
fn non_ticket_prefixes_cover_the_measured_families() {
for key in [
"ADR-0029",
"DOC-67",
"RFC-2119",
"SPEC-14",
"ISO-8601",
"ECMA-48",
"IEEE-754",
"UTF-8",
"ASCII-7",
"SHA-256",
"MD-5",
"CRC-32",
"AES-256",
"RSA-4096",
"CVE-2024-3094",
"CWE-79",
"RUSTSEC-2026",
"GHSA-1234",
"GCC-11",
"LLVM-18",
] {
assert!(is_non_ticket_identifier(key), "key: {key:?}");
}
for key in [
"WI-1",
"MR-1",
"C-6",
"AC-1",
"MULTIREPO-01",
"CREDPANEL-01",
"PROJ-1234",
"ENG-123",
"GH-12",
] {
assert!(!is_non_ticket_identifier(key), "key: {key:?}");
}
assert!(!is_non_ticket_identifier("ADR"));
assert!(!is_non_ticket_identifier(""));
}
#[test]
fn body_prose_identifier_is_not_a_ticket_key() {
for body in [
"fix: stem handling\n\nA filename containing a multi-byte UTF-8 character breaks.\n",
"chore: pin the digest\n\nVerifies the artifact against the published SHA-256.\n",
"chore: bump anydoc\n\nAvoids reintroducing RUSTSEC-2026-0187.\n",
"build: probe the toolchain\n\nMirrors the AL2023/GCC-11 desync.\n",
"docs: rate table\n\nGPT-5 and Gemini rates differ.\n",
"fix: parse timestamps\n\nISO-8601 offsets were dropped.\n",
] {
assert_eq!(extract_ticket_id(body), None, "message: {body:?}");
}
}
#[test]
fn extract_ticket_id_subject_forms() {
for (msg, want) in [
("BB-2746: refactor auth service", "BB-2746"),
("DRE-405 fix demand calculation", "DRE-405"),
("fix(auth): PROJ-12 tighten token check", "PROJ-12"),
("[ENG-77] add endpoint", "ENG-77"),
("feat!: API-9 breaking rename", "API-9"),
("SRE-3104", "SRE-3104"),
] {
assert_eq!(extract_ticket_id(msg), Some(want.to_string()), "{msg:?}");
}
}
#[test]
fn hyphenated_tail_segment_is_not_a_ticket_key() {
assert_eq!(extract_ticket_id("SPEC-INSTALLER-01 detect target"), None);
assert_eq!(
extract_ticket_id("docs: SPEC-MPM-CUTOVER-03 decision"),
None
);
}
#[test]
fn jira_style_is_ticketed() {
assert!(is_ticketed("ENG-123: add feature"));
assert!(is_ticketed("PROJ-1 initial commit"));
assert!(is_ticketed("[ABC-4567] backport from upstream"));
}
#[test]
fn linear_style_is_ticketed() {
assert!(is_ticketed("FE-456 fix login"));
assert!(is_ticketed("API-9 add endpoint"));
}
#[test]
fn github_action_keyword_is_ticketed() {
assert!(is_ticketed("Fix race condition, fixes #123"));
assert!(is_ticketed("closes #45"));
assert!(is_ticketed("Resolves #7 by reworking auth"));
assert!(is_ticketed("CLOSED #99")); }
#[test]
fn bare_hash_alone_is_not_ticketed() {
assert!(!is_ticketed("Bug from #123 still present"));
assert!(!is_ticketed("#42 follow-up"));
assert!(!is_ticketed("some note about #42"));
assert_eq!(extract_ticket_id("#42 follow-up"), Some("#42".to_string()));
assert_eq!(
extract_ticket_id("Bug from #123 still present"),
Some("#123".to_string())
);
}
#[test]
fn closes_hash_is_ticketed() {
assert!(is_ticketed("closes #42"));
assert!(is_ticketed("fixes #123"));
assert!(is_ticketed("resolves #7"));
}
#[test]
fn jira_is_ticketed() {
assert!(is_ticketed("ENG-123: add feature"));
assert!(is_ticketed("PROJ-1 initial commit"));
}
#[test]
fn azdo_is_ticketed() {
assert!(is_ticketed("AB#1234 implement new feature"));
assert!(is_ticketed("Refactor module (AB#42)"));
}
#[test]
fn plain_message_is_not_ticketed() {
assert!(!is_ticketed("misc cleanup"));
assert!(!is_ticketed("update README"));
assert!(!is_ticketed("bump version to 1.2.3"));
assert!(!is_ticketed("set color to #abc123"));
assert!(!is_ticketed("eng-123 lowercase doesn't count"));
}
#[test]
fn multiline_body_with_ticket_is_ticketed() {
let body_only = "Refactor module structure\n\nMoves things around.\nRelates to PROJ-789.\n";
assert!(!is_ticketed(body_only));
let msg = "PROJ-789: refactor module structure\n\nMoves things around.\n";
assert!(is_ticketed(msg));
let msg2 = "First line no ticket\n\nSee #321 for context.";
assert!(!is_ticketed(msg2));
let msg3 = "First line no ticket\n\nCloses #321.";
assert!(is_ticketed(msg3));
}
#[test]
fn azdo_ab_ref_is_ticketed() {
assert!(is_ticketed("AB#1234 implement new feature"));
assert!(is_ticketed("Refactor module (AB#42)"));
assert!(is_ticketed("First line\n\nbody mentions AB#7 explicitly"));
}
#[test]
fn bare_hash_without_ab_prefix_is_not_azdo() {
let p = patterns();
assert!(!p.azdo.is_match("#1234 some work"));
assert!(!p.azdo.is_match("fixes #99"));
assert_eq!(subject_ticket_key("AB#1234"), None);
}
#[test]
fn jira_shaped_prose_is_not_ticketed() {
for msg in [
"fix: stem handling\n\nA filename containing a multi-byte UTF-8 character breaks.\n",
"docs: amend ADR-0034 point 7",
"chore: pin the digest\n\nVerifies the artifact against the published SHA-256.\n",
"chore: bump anydoc\n\nAvoids reintroducing RUSTSEC-2026-0187.\n",
"build: probe the toolchain\n\nMirrors the AL2023/GCC-11 desync.\n",
"docs: rate table\n\nGPT-5 and Gemini rates differ.\n",
"fix: parse timestamps\n\nISO-8601 offsets were dropped.\n",
"docs: manifest\n\nPer DOC-39 the manifest is an allowlist.\n",
"chore: triage\n\nResolves the HIGH-1 finding from the audit.\n",
"Backport from upstream (ABC-4567)",
"SPEC-INSTALLER-01 detect target",
"DOC-67 §2 rewrite",
"RFC-2119 keyword sweep",
] {
assert!(!is_ticketed(msg), "message: {msg:?}");
}
}
#[test]
fn subject_declared_key_is_still_ticketed() {
for msg in [
"BB-2746: refactor auth service",
"DRE-405 fix demand calculation",
"fix(auth): PROJ-12 tighten token check",
"[ENG-77] add endpoint",
"feat!: API-9 breaking rename",
"SRE-3104",
"ENG-7 refactor\n\nBody mentions UTF-8 and ADR-0034.\n",
] {
assert!(is_ticketed(msg), "message: {msg:?}");
assert!(extract_ticket_id(msg).is_some(), "message: {msg:?}");
}
assert!(is_ticketed("Rework the guard\n\nCloses #5735\n"));
assert!(is_ticketed(
"Refactor module\n\nbody mentions AB#7 explicitly"
));
}
#[test]
fn empty_message_is_not_ticketed() {
assert!(!is_ticketed(""));
assert!(!is_ticketed("\n\n"));
}
#[test]
fn branch_ticket_key_reads_the_key_a_branch_declares() {
for (branch, want) in [
("PROJ-123", "PROJ-123"),
("PROJ-123-add-thing", "PROJ-123"),
("feature/PROJ-123-thing", "PROJ-123"),
("feature/ENG-7_login", "ENG-7"),
("bobmatnyc/feature/API-9", "API-9"),
("refs/heads/feature/BB-2746-auth", "BB-2746"),
("refs/heads/SRE-3104", "SRE-3104"),
] {
assert_eq!(
branch_ticket_key(branch),
Some(want.to_string()),
"branch: {branch:?}"
);
}
}
#[test]
fn branch_ticket_key_rejects_documentation_prefixes() {
for branch in [
"fix/ADR-0029-followup",
"docs/DOC-67-rewrite",
"chore/RFC-2119-sweep",
"feat/SPEC-14-tightened",
"refs/heads/ADR-0034",
"feat/SPEC-INSTALLER-01-detect",
"fix/5734-harvest-branch-and-pr-refs",
"feat/pre-publish-sha-targeting-5740",
"main",
"",
] {
assert_eq!(branch_ticket_key(branch), None, "branch: {branch:?}");
}
}
#[test]
fn pr_body_ticket_key_needs_an_action_keyword() {
for (body, want) in [
("Closes #5734", "#5734"),
("Rework the guard.\n\nCloses #5734\n", "#5734"),
("fixes #99 and tidies up", "#99"),
("RESOLVED #7", "#7"),
("This closed #42 at last", "#42"),
] {
assert_eq!(
pr_body_ticket_key(body),
Some(want.to_string()),
"body: {body:?}"
);
}
for body in [
"Supersedes #123, see #456",
"Follow-up to #5199.",
"#5734 is the tracking issue",
"no reference at all",
"",
] {
assert_eq!(pr_body_ticket_key(body), None, "body: {body:?}");
}
}
#[test]
fn pr_body_ticket_key_never_reads_a_jira_shaped_token() {
for body in [
"Per DOC-39 the manifest is an allowlist.",
"Implements ADR-0024 and DOC-48.",
"fixes UTF-8 filename handling",
"closes RUSTSEC-2026-0187",
"resolves HIGH-1 from the audit",
"Fixes PROJ-123 in the tracker",
"SHA-256 digest pinned; ISO-8601 offsets kept",
] {
assert_eq!(pr_body_ticket_key(body), None, "body: {body:?}");
}
assert_eq!(
pr_body_ticket_key("Per ADR-0034 the relay spools durably.\n\nCloses #5089\n"),
Some("#5089".to_string())
);
}
}