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
+11 -5
View File
@@ -9,12 +9,19 @@ func CalculateSemver(
commits []CommitDetails,
tags []TagDetails,
wording Wording,
matcher KeywordMatcher,
blacklist []string,
initialSemver SemVer,
respectExisting bool,
strictMode bool,
tagPrefixes []string,
) SemVer {
// Default-preserving: callers that don't select a matcher get today's
// fuzzy behaviour rather than a nil-pointer panic.
if matcher == nil {
matcher = FuzzyMatcher{}
}
semver := initialSemver
startIndex := 0
@@ -57,11 +64,10 @@ func CalculateSemver(
}
// Check for keyword matches
commitSlice := strings.Fields(commit.Message)
matchPatch := CheckMatches(commitSlice, wording.Patch, blacklist)
matchMinor := CheckMatches(commitSlice, wording.Minor, blacklist)
matchMajor := CheckMatches(commitSlice, wording.Major, blacklist)
matchReleaseCandidate := CheckMatches(commitSlice, wording.Release, blacklist)
matchPatch := matcher.Matches(commit.Message, wording.Patch, blacklist)
matchMinor := matcher.Matches(commit.Message, wording.Minor, blacklist)
matchMajor := matcher.Matches(commit.Message, wording.Major, blacklist)
matchReleaseCandidate := matcher.Matches(commit.Message, wording.Release, blacklist)
// Apply version changes based on matches
if matchMajor {