From 24d5cb0bc6ed82bac0070133396c9b9bf8079668 Mon Sep 17 00:00:00 2001 From: Lucas Santana Date: Thu, 21 May 2026 01:24:34 -0300 Subject: [PATCH 1/2] chore(process): add feature-removal sweep checklist + dangerfile guard (#908) --- .github/PULL_REQUEST_TEMPLATE.md | 17 +++++++++++++++++ dangerfile.ts | 20 ++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..a8d9605f5 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,17 @@ +## Description + + +## Checklist +- [ ] Tests pass locally +- [ ] CHANGELOG.md updated (if user-facing) +- [ ] TypeScript builds cleanly + +## Feature-removal sweep +**Skip this section if this PR doesn't remove a route, handler, endpoint, model, or toggle.** + +When removing a feature, ensure no orphan code is left behind: +- [ ] Orphan Prisma models removed (or depended-upon by other models) +- [ ] Broken or stale test files removed +- [ ] Unused type aliases removed +- [ ] Imports/exports cleaned up +- [ ] ADRs and CONTEXT.md cross-references updated diff --git a/dangerfile.ts b/dangerfile.ts index 42cd8dbc1..635a521cd 100644 --- a/dangerfile.ts +++ b/dangerfile.ts @@ -135,6 +135,26 @@ if (!validPrefixes.test(headRef) && !headRef.startsWith('worktree-')) { ) } + +// --- 9. Feature-removal sweep guard ----------------------------------------- +// When a commit message indicates feature/route/model removal, +// flag if the PR body doesn't mention the sweep checklist. +const removalPattern = /^(remove|delete|retire|drop|deprecate).*\b(route|handler|endpoint|model|toggle|feature)\b/i +const prBody = pr.body || '' +const hasSweepChecklistInBody = prBody.includes('Feature-removal sweep') +const hasRemovalCommit = danger.git.commits.some((c) => + removalPattern.test(c.message), +) + +if (hasRemovalCommit && !hasSweepChecklistInBody) { + warn( + `This PR appears to remove a feature or route (detected in commit message). ` + + `Please fill in the **Feature-removal sweep** checklist in the PR template ` + + `to ensure no orphan code (models, tests, types, imports) is left behind. ` + + `[See docs/decisions/2026-05-19-retire-per-guild-feature-toggles.md](${pr.html_url}) for context.`, + ) +} + // --- 8. Big-file warning ---------------------------------------------------- // Files > 500 lines are review-hostile. Flag new ones. async function checkLargeFiles(): Promise { From c0a6634257440651c4e4207550055f1de49cae0a Mon Sep 17 00:00:00 2001 From: Lucas Santana Date: Thu, 21 May 2026 02:31:46 -0300 Subject: [PATCH 2/2] fix(dangerfile): address CodeRabbit review on #913 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three findings from CodeRabbit on the original PR: 1. removalPattern was anchored to `^` — would miss removal-verbs anywhere except commit-message start. Split into two case-insensitive word-boundary regexes (removalVerb + removalTarget) and require both to match the same commit message. 2. hasSweepChecklistInBody used case-sensitive includes() — would miss the "feature-removal sweep" or "Feature-removal Sweep" capitalisation variants. Switched to case-insensitive regex. 3. ADR link in the warn() interpolated pr.html_url (the PR URL) but labelled it as the ADR file. Now builds the correct repo blob URL from pr.base {repo.full_name, ref} + relative ADR path. Also: add the CHANGELOG [Unreleased] entry for the sweep checklist PR (#908). --- CHANGELOG.md | 1 + dangerfile.ts | 15 ++++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ad032259..da3a8551a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Internal - fix(ci): group $GITHUB_STEP_SUMMARY redirects in madge workflow to satisfy shellcheck SC2129 (#905) - test(shared): add `coverageThreshold` gate to `packages/shared/jest.config.cjs` (no-regression floor at current baseline -2%, statements=19/branches=16/functions=15/lines=18) (#909) +- chore(process): add Feature-removal sweep checklist to PR template + advisory dangerfile guard that flags PRs with removal-pattern commits missing the sweep (#908) ## [2.11.0] - 2026-05-15 diff --git a/dangerfile.ts b/dangerfile.ts index 635a521cd..c03e1e0d8 100644 --- a/dangerfile.ts +++ b/dangerfile.ts @@ -139,19 +139,24 @@ if (!validPrefixes.test(headRef) && !headRef.startsWith('worktree-')) { // --- 9. Feature-removal sweep guard ----------------------------------------- // When a commit message indicates feature/route/model removal, // flag if the PR body doesn't mention the sweep checklist. -const removalPattern = /^(remove|delete|retire|drop|deprecate).*\b(route|handler|endpoint|model|toggle|feature)\b/i +const removalVerb = /\b(remove|delete|retire|drop|deprecate)\b/i +const removalTarget = /\b(route|handler|endpoint|model|toggle|feature)\b/i const prBody = pr.body || '' -const hasSweepChecklistInBody = prBody.includes('Feature-removal sweep') -const hasRemovalCommit = danger.git.commits.some((c) => - removalPattern.test(c.message), +const hasSweepChecklistInBody = /feature-removal sweep/i.test(prBody) +const hasRemovalCommit = danger.git.commits.some( + (c) => removalVerb.test(c.message) && removalTarget.test(c.message), ) if (hasRemovalCommit && !hasSweepChecklistInBody) { + const baseRepo = pr.base.repo.full_name + const baseRef = pr.base.ref + const adrPath = 'docs/decisions/2026-05-19-retire-per-guild-feature-toggles.md' + const adrUrl = `https://github.com/${baseRepo}/blob/${baseRef}/${adrPath}` warn( `This PR appears to remove a feature or route (detected in commit message). ` + `Please fill in the **Feature-removal sweep** checklist in the PR template ` + `to ensure no orphan code (models, tests, types, imports) is left behind. ` + - `[See docs/decisions/2026-05-19-retire-per-guild-feature-toggles.md](${pr.html_url}) for context.`, + `[See ${adrPath}](${adrUrl}) for context.`, ) }