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/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 42cd8dbc1..c03e1e0d8 100644 --- a/dangerfile.ts +++ b/dangerfile.ts @@ -135,6 +135,31 @@ 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 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 = /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 ${adrPath}](${adrUrl}) for context.`, + ) +} + // --- 8. Big-file warning ---------------------------------------------------- // Files > 500 lines are review-hostile. Flag new ones. async function checkLargeFiles(): Promise {