feat(merge-report): add exclude-paths, exclude-message-patterns, subject-match - #41
Merged
Merged
Conversation
…ect-match Adds three filtering layers, applied cheapest-first before falling through to exclude-labels/the ignore-list: - exclude-paths: drop a candidate only if every changed path matches a configured glob (default: .github/**, CHANGELOG.md, .gitflow-changelog*.yml -- safe across any repo, deliberately not the existing feature/noise classification used for upstream fold-in, which treats examples/docs as noise and would hide a real doc-only gap). - exclude-message-patterns: opt-in regexes for a repo's own literal, machine-generated commit messages (e.g. a release workflow's fixed "Prepare release " template) -- not a general commit-message heuristic. - subject-match (on by default): the dominant real-world false positive isn't release noise, it's a backport PR renumbered on the target branch with enough incidental diff drift to change the patch-id despite being the same fix. Strip trailing "(#NNN)"/"(backport...)" from the subject, look for an identical normalized subject already on the target, and require the two commits' changed-file sets to overlap (Jaccard) by at least subject-match-min-overlap (default 0.3) before trusting it -- calibrated against real zilla-plus data (34 of 35 confirmed matches sit at 85-100% overlap, the one outlier at 50%). Confirmed against aklivity/zilla-plus's real support/1.x -> develop history: 86 raw git cherry candidates -> 9 (8 one-off residue for the ignore-list, 1 real gap correctly still visible), with zero new ignore- list entries needed for the recurring renumbered-backport shape. None of this is cached, consistent with git cherry/commitDate today -- all local git, no API cost, and "no subject-match yet" isn't safe to cache without watermark-level care since a real match can appear once a backport actually lands. Closes #40.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #40.
The first real
merge-reportrun (aklivity/zilla-plus,support/1.x→develop) surfaced 86 rawgit cherrycandidates; manually auditing them found only 1 genuine gap. This adds three filtering layers, applied cheapest-first, before falling through to the existingexclude-labels/ignore-list:exclude-paths— a candidate is dropped only if every changed path matches a configured glob (one file outside the set still shows up). Default.github/**,CHANGELOG.md,.gitflow-changelog*.yml— safe across any consuming repo. Deliberately not a reuse ofclassification.ts's existingDEFAULT_CLASSIFICATION_PATTERNS(built for "does this belong in the customer changelog," which treatsexamples//docs/as noise) — that would have silently hidden the one real gap found inzilla-plus, since it's docs-only.exclude-message-patterns— opt-in regexes, no built-in default, scoped to a repo's own literal, machine-generated commit messages (e.g. a release workflow's fixed"Prepare release "template) — not a general commit-message heuristic.subject-match(on by default) — the dominant real-world false positive isn't release noise, it's a backport PR renumbered on the target branch: the same fix, cherry-picked with a new PR number and enough incidental diff drift (unrelated codebase divergence between the branches) to change the patch-id despite being the same change. Strips trailing(#NNN)/(backport...)from the subject, searches the target branch for an identical normalized subject, and requires the two commits' changed-file sets to overlap (Jaccard) by at leastsubject-match-min-overlap(default0.3) before trusting it. Calibrated against real data: of 35 confirmed real matches inzilla-plus, 34 sit at 85–100% file overlap; the one outlier (independently confirmed correct) sits at 50% —0.3is comfortably below that floor.Net effect on the real data that motivated this: 86 → 9 (8 genuine one-off residue for the ignore-list, 1 real gap correctly still visible) — with zero new ignore-list entries needed for the recurring renumbered-backport shape, which is the actual point: without this, the ignore-list would grow roughly one entry per backport PR, forever.
Nothing here is cached — consistent with
git cherry/commitDatetoday. Everything is local git (no GitHub API cost), and caching "no subject-match yet" isn't safe without the same watermark discipline the events cache uses (a real match can appear on a later run once a backport actually lands).Changes
src/classification.ts: exportmatchesAny(was already private, now reused instead of duplicated)src/git.ts:findCommitsContainingSubject()src/subject-match.ts: new —normalizeSubject(),fileOverlap()(pure functions)src/repo-config.ts:exclude-paths,exclude-message-patterns,subject-match,subject-match-min-overlapsrc/merge-report.ts: cheapest-first filtering pipeline (isExcluded)src/merge-report-config.ts: wires the four new options, with the safeexclude-pathsdefaultsrc/merge-report-cli.ts/src/merge-report-action.ts/merge-report/action.yml: new inputsREADME.md: rewritten "How it detects a gap" section describing the full pipelineTest plan
npm run typecheckpassesnpm test— 216 tests passing across 19 files (up from 195/18), including:test/subject-match.test.ts:normalizeSubject(single/chained PR-number stripping, backport-annotation stripping, combined, no-op, doesn't strip a non-trailing#NNN),fileOverlap(identical/disjoint/partial/dedup)test/git-cherry.test.ts:findCommitsContainingSubject(finds, empty, treats the query as a literal substring)test/merge-report.test.ts: exclude-paths (all-must-match vs. one-file-outside-the-set stays visible), exclude-message-patterns, subject-match resolving a renamed backport above threshold, staying visible below threshold, and thesubjectMatch: falsetoggle disabling the whole mechanism even with a perfect matchnpm run buildsucceeds for all three entrypointsnpm run lintstill fails the same pre-existing way it does ondevelop(missingeslint.config.js) — unrelated to this change.Generated by Claude Code