feat(matching): add an opt-in regex keyword matching mode

Fuzzy matching compares each keyword with every word of the commit
message. A word matches if it contains the keyword's letters in order.
Thus "feat" matches "fix(delegation):" and "major" matches
"deps(majors):", which gives false minor and major versions.

The new top-level config key "matching" accepts "fuzzy" (the default)
or "regex". In regex mode every wording keyword is a Go regular
expression. The tool compiles all of them when it loads the config and
runs them against the full commit message, so "(?m)^" anchors a
pattern to the start of a line. An invalid value, an invalid pattern,
or any other config parse error in regex mode stops the tool with a
non-zero exit code. It never falls back to fuzzy matching.

Fuzzy mode does not change. The blacklist check moves into one helper
that both modes use. A replay of 15 repositories gives the same
versions as before. This change improves version precision for the
repositories that opt in.

Claude-Session: https://claude.ai/code/session_01Qq1gbqQek1Zjtofo4VDRdW
This commit is contained in:
2026-09-25 19:35:48 +01:00
parent 9617b281a9
commit e90191de4c
9 changed files with 800 additions and 20 deletions
+23 -10
View File
@@ -166,6 +166,27 @@ func ParseExistingSemver(tagName string, currentSemver SemVer, prefixes []string
return semanticVersion
}
// isBlacklisted reports whether contentStr contains any blacklist term
// (case-insensitive substring match), logging the same way every caller used
// to log it inline. Both matching modes call this so blacklist semantics stay
// identical between fuzzy and regex matching.
func isBlacklisted(contentStr string, blacklist []string) bool {
if len(blacklist) == 0 {
return false
}
lowerContent := strings.ToLower(contentStr)
for _, blacklistTerm := range blacklist {
if strings.Contains(lowerContent, strings.ToLower(blacklistTerm)) {
Debug("Blacklisted term detected, ignoring commit", map[string]interface{}{
"content": contentStr,
"blacklist_term": blacklistTerm,
})
return true
}
}
return false
}
// CheckMatches checks if any of the targets match the content
func CheckMatches(content []string, targets []string, blacklist []string) bool {
contentStr := strings.Join(content, " ")
@@ -186,16 +207,8 @@ func CheckMatches(content []string, targets []string, blacklist []string) bool {
}
// If we have a match, check against blacklist
if hasMatch && len(blacklist) > 0 {
for _, blacklistTerm := range blacklist {
if strings.Contains(strings.ToLower(contentStr), strings.ToLower(blacklistTerm)) {
Debug("Blacklisted term detected, ignoring commit", map[string]interface{}{
"content": contentStr,
"blacklist_term": blacklistTerm,
})
return false
}
}
if hasMatch && isBlacklisted(contentStr, blacklist) {
return false
}
return hasMatch