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—groupNameeffect feeds the same path as today'sgroups(keepgroupsas 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
schedulegates which updates a given run may propose (evaluate cron window against now) - priority: sort update groups by max
prPrioritybefore themaxPRsPerRuncap (buddy.ts:256)
- filtering/strategy:
- Validation:
src/config-validation.ts— matcher syntax, unknown fields, empty-matcher rules (match-all must be explicit:matchPackages: ['*']) - Migration: extend
migrateFromRenovateto 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
groupsinto rules internally (back-compat guaranteed by existing tests) enginesdep-type support in the npm scanner (delivers #107: propose engine bumps guarded by rules)- Renovate migrator upgrade + migration-report accuracy
docs/config.mdrules 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
packageRulesconfig imports with ≥ the matchers listed above mapped