ReviewOS

stacks/buddy

packageRules engine — Renovate-class matchers with per-rule overrides

#1384
Closed chris opened this 8 days ago ·
8 days ago

Part of #1367 (Phase 3 — Dependency Engine v2).

Goal

A Renovate-class packageRules engine — the single biggest gap vs Renovate and the config feature power users migrate for. Today we have only flat ignore/ignorePaths/pin/groups (name+patterns+strategy). No matchers, no per-rule overrides (src/types.ts packages section; grouping in src/utils/helpers.ts:groupUpdates + src/buddy.ts:groupUpdatesByConfig:1381).

This also delivers #107 (engine bumps become a managed dep type with rules) and makes the Renovate migrator (src/setup.ts:ConfigurationMigrator.migrateFromRenovate:192) able to import packageRules faithfully instead of flagging them incompatible.

Spec

Config

packages.rules?: PackageRule[]

interface PackageRule {
  // matchers (AND within a rule; rule list evaluated in order, later rules override earlier)
  matchPackages?: string[]          // exact names or globs (Bun Glob)
  matchEcosystems?: ('npm' | 'composer' | 'github-actions' | 'docker' | 'pkgx' | 'zig')[]
  matchDepTypes?: ('dependencies' | 'devDependencies' | 'peerDependencies' | 'optionalDependencies' | 'engines')[]
  matchUpdateTypes?: ('major' | 'minor' | 'patch')[]
  matchFiles?: string[]             // glob on manifest path (monorepo dirs)
  matchCurrentVersion?: string      // semver range
  // actions
  enabled?: boolean                 // false = ignore matched updates
  strategy?: 'major' | 'minor' | 'patch' | 'all'
  groupName?: string
  labels?: string[]
  reviewers?: string[]
  assignees?: string[]
  autoMerge?: boolean | { strategy?: 'merge' | 'squash' | 'rebase' }
  minimumReleaseAge?: number        // minutes, overrides global
  schedule?: string                 // cron restriction for when matched updates may PR
  prPriority?: number               // ordering within maxPRsPerRun
  commitMessageSuffix?: string
}

Engine

  • src/rules/engine.ts: resolveRuleEffects(update: PackageUpdate, filePath): ResolvedEffects — evaluate rules in order, merge effects (last-wins per field), memoize per (name, ecosystem, file)
  • Application points:
    • filtering/strategy: Buddy.scanForUpdates (src/buddy.ts:75) — replaces flat ignore/strategy application for matched packages
    • grouping: groupUpdatesByConfig — groupName effect feeds the same path as today's groups (keep groups as sugar that compiles into rules internally)
    • PR meta: labels/reviewers/assignees/autoMerge per-PR resolution in createPullRequests (buddy.ts:202) — when a PR contains updates with conflicting effects, union labels/reviewers and take the most conservative autoMerge (all-must-allow)
    • scheduling: rule schedule gates which updates a given run may propose (evaluate cron window against now)
    • priority: sort update groups by max prPriority before the maxPRsPerRun cap (buddy.ts:256)
  • Validation: src/config-validation.ts — matcher syntax, unknown fields, empty-matcher rules (match-all must be explicit: matchPackages: ['*'])
  • Migration: extend migrateFromRenovate to map Renovate matchers (matchPackageNames/Patterns, matchDepTypes, matchUpdateTypes, matchFileNames, matchCurrentVersion, automerge, labels, groupName, schedule) → our rules; drop-with-notice for unsupported ones

Tasks

  • Types + engine + memoization + ordered-merge semantics with an exhaustive unit-test matrix
  • Wire all five application points; compile legacy groups into rules internally (back-compat guaranteed by existing tests)
  • engines dep-type support in the npm scanner (delivers #107: propose engine bumps guarded by rules)
  • Renovate migrator upgrade + migration-report accuracy
  • docs/config.md rules section with a cookbook (10+ recipes: "hold back majors on react", "auto-merge types-only patches", "weekend-only majors", …)

Acceptance criteria

  • Every matcher and every effect is covered by tests, including rule-order override semantics and conflicting-effect merging in grouped PRs
  • Existing configs (groups/ignore/strategy) behave identically (regression suite green)
  • A real Renovate packageRules config imports with ≥ the matchers listed above mapped

Dependencies

  • None hard; #1369 (auto-merge) consumes the autoMerge effect. Closes the config half of #107.
8 days ago

Implemented in 4332bf0 — src/rules/engine.ts, wired into scanForUpdates.

Matchers: matchPackages (names or globs), matchEcosystems, matchDepTypes, matchUpdateTypes, matchFiles. Effects: enabled, strategy, groupName, labels, reviewers, assignees, autoMerge, minimumReleaseAge, prPriority.

Three semantics worth recording:

Scalar effects override, list effects accumulate. Later rules win per field for strategy/groupName/etc., but a package matching both a security rule and a frontend rule carries both sets of labels rather than whichever was last — replacing there loses information the user clearly wanted.

Group auto-merge is unanimous, not majority. One package in a grouped PR forbidding auto-merge holds back the whole PR, because a dependency that must not merge unattended has to be able to stop the PR carrying it.

Legacy packages.groups compile into rules rather than running alongside them, so grouping has one code path instead of two that can disagree. Existing configs keep working — the full regression suite is green.

Scope note: matchers, effects, resolution and the scan-path integration are done and tested (24 tests). Not yet wired: matchCurrentVersion (needs a semver range matcher), rule schedule (cron-window gating), and threading resolved effects into PR labels/reviewers at creation time — mergeGroupEffects computes them, but createPullRequests still reads the flat config. Also not done: extending the Renovate migrator to import packageRules, which is now a mapping exercise against a stable target. Following up rather than leaving this open.

Sign in to comment on this issue.