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
View File
@@ -18,6 +18,7 @@ limitations under the License.
package cmd
import (
"errors"
"fmt"
"os"
@@ -96,6 +97,15 @@ func main() {
// Read configuration
config, err := utils.ReadConfig(repo.LocalConfigFile)
if err != nil {
// A bad "matching" value or an invalid regex keyword is a real
// misconfiguration, not a missing-file situation: fail loudly
// instead of silently falling back to fuzzy matching.
if errors.Is(err, utils.ErrInvalidMatchingConfig) {
utils.Critical("Invalid matching configuration", map[string]interface{}{
"error": err.Error(),
})
os.Exit(1)
}
utils.Error("Unable to find config file. Using defaults and flags.", map[string]interface{}{
"file": repo.LocalConfigFile,
})
@@ -140,6 +150,7 @@ func main() {
repo.GitRepo.Commits,
repo.GitRepo.Tags,
repo.Config.Wording,
repo.Config.Matcher,
repo.Config.Blacklist,
repo.Semver,
params.varExisting || repo.Config.Force.Existing,