Skip to content

feat(merge-report): add exclude-paths, exclude-message-patterns, subject-match - #41

Merged
jfallows merged 1 commit into
developfrom
claude/gitflow-merge-report-66igkl
Jul 21, 2026
Merged

feat(merge-report): add exclude-paths, exclude-message-patterns, subject-match#41
jfallows merged 1 commit into
developfrom
claude/gitflow-merge-report-66igkl

Conversation

@jfallows

Copy link
Copy Markdown
Contributor

Summary

Closes #40.

The first real merge-report run (aklivity/zilla-plus, support/1.xdevelop) surfaced 86 raw git cherry candidates; manually auditing them found only 1 genuine gap. This adds three filtering layers, applied cheapest-first, before falling through to the existing exclude-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 of classification.ts's existing DEFAULT_CLASSIFICATION_PATTERNS (built for "does this belong in the customer changelog," which treats examples//docs/ as noise) — that would have silently hidden the one real gap found in zilla-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 least subject-match-min-overlap (default 0.3) before trusting it. Calibrated against real data: of 35 confirmed real matches in zilla-plus, 34 sit at 85–100% file overlap; the one outlier (independently confirmed correct) sits at 50% — 0.3 is 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/commitDate today. 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: export matchesAny (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-overlap
  • src/merge-report.ts: cheapest-first filtering pipeline (isExcluded)
  • src/merge-report-config.ts: wires the four new options, with the safe exclude-paths default
  • src/merge-report-cli.ts / src/merge-report-action.ts / merge-report/action.yml: new inputs
  • README.md: rewritten "How it detects a gap" section describing the full pipeline

Test plan

  • npm run typecheck passes
  • npm 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 the subjectMatch: false toggle disabling the whole mechanism even with a perfect match
  • npm run build succeeds for all three entrypoints
  • Note: npm run lint still fails the same pre-existing way it does on develop (missing eslint.config.js) — unrelated to this change.

Generated by Claude Code

…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.
@jfallows
jfallows merged commit a364d32 into develop Jul 21, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

merge-report: layered false-positive filtering (path/message excludes + subject-normalization matching)

2 participants