From general-developer
Dangerfile best practices — PR automation, rule authoring, CI integration, actionable messaging, and keeping rules maintainable.
How this skill is triggered — by the user, by Claude, or both
Slash command
/general-developer:dangerfileThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Danger runs during CI and reads pull request metadata (title, description, changed files, diff, reviews) to enforce team conventions automatically. It posts a comment on the PR with results — flagging violations, leaving warnings, or praising good practice.
Danger runs during CI and reads pull request metadata (title, description, changed files, diff, reviews) to enforce team conventions automatically. It posts a comment on the PR with results — flagging violations, leaving warnings, or praising good practice.
Danger shifts code review from "did you remember to…?" checklists to automated, consistent enforcement.
# JavaScript / TypeScript projects
npm install --save-dev danger
// danger.config.ts (optional runner config)
import { danger, warn, fail, message } from 'danger'
# .github/workflows/danger.yml
name: Danger
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
danger:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
- run: npx danger ci
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
| Function | Severity | PR effect |
|---|---|---|
fail(msg) | Error | Marks CI check as failed |
warn(msg) | Warning | CI passes, yellow flag |
message(msg) | Info | Neutral comment |
markdown(md) | Info | Renders full Markdown block |
Always prefer the lowest severity that still gets attention. Reserve fail for genuine blockers.
// Dangerfile.ts
import { danger, fail, warn } from 'danger'
const prBody = danger.github.pr.body
// Require a non-empty description
if (!prBody || prBody.trim().length < 20) {
fail('Please provide a meaningful PR description (at least 20 characters).')
}
// Require a checklist or section marker
if (!prBody.includes('## ') && !prBody.includes('- [ ]')) {
warn('Consider adding a summary section or checklist to the PR description.')
}
const addedLines = danger.github.pr.additions
const deletedLines = danger.github.pr.deletions
const totalChanges = addedLines + deletedLines
if (totalChanges > 600) {
warn(
`This PR is large (${totalChanges} lines changed). ` +
'Consider splitting it into smaller, focused PRs for easier review.'
)
}
const modifiedFiles = danger.git.modified_files
const createdFiles = danger.git.created_files
const allChangedFiles = [...modifiedFiles, ...createdFiles]
// Warn if lockfile changed without package.json
const lockfileChanged = allChangedFiles.some((f) => f.includes('package-lock.json') || f.includes('yarn.lock') || f.includes('pnpm-lock.yaml'))
const packageChanged = allChangedFiles.some((f) => f === 'package.json')
if (lockfileChanged && !packageChanged) {
warn('Lockfile changed without a corresponding `package.json` change — was this intentional?')
}
// Require tests alongside source changes
const sourceChanged = allChangedFiles.some((f) => f.startsWith('src/') && !f.includes('.test.') && !f.includes('.spec.'))
const testsChanged = allChangedFiles.some((f) => f.includes('.test.') || f.includes('.spec.') || f.startsWith('tests/'))
if (sourceChanged && !testsChanged) {
warn('Source files changed without any test changes. Did you mean to add or update tests?')
}
const changelogUpdated = allChangedFiles.some((f) => f.toLowerCase() === 'changelog.md')
if (!changelogUpdated) {
message('No `CHANGELOG.md` update detected. If this is a user-facing change, consider documenting it.')
}
const reviewers = danger.github.requested_reviewers.users.map((u) => u.login)
if (reviewers.length === 0) {
warn('No reviewers assigned. Please add at least one reviewer before merging.')
}
const sensitiveFiles = [
'.env',
'.env.production',
'config/secrets.yml',
'infrastructure/',
]
const sensitiveChanged = allChangedFiles.filter((f) =>
sensitiveFiles.some((s) => f.startsWith(s) || f === s)
)
if (sensitiveChanged.length > 0) {
fail(
`Sensitive files changed: \`${sensitiveChanged.join('`, `')}\`. ` +
'Ensure no secrets are committed and get a security review.'
)
}
const migrationsChanged = allChangedFiles.filter((f) => f.includes('/migrations/'))
if (migrationsChanged.length > 0) {
message(
`Database migrations included in this PR:\n${migrationsChanged.map((f) => `- \`${f}\``).join('\n')}\n\n` +
'Ensure migrations are reversible and tested against a backup.'
)
}
Keep Dangerfile.ts readable by extracting each rule into a named function.
// Dangerfile.ts
import { danger, fail, warn, message } from 'danger'
checkPrDescription()
checkPrSize()
checkTestCoverage()
checkLockfileConsistency()
checkSensitiveFiles()
// ─── Rules ───────────────────────────────────────────────
function checkPrDescription() {
const body = danger.github.pr.body ?? ''
if (body.trim().length < 20) {
fail('Please provide a meaningful PR description.')
}
}
function checkPrSize() {
const total = danger.github.pr.additions + danger.github.pr.deletions
if (total > 600) {
warn(`Large PR (${total} lines). Consider splitting it.`)
}
}
function checkTestCoverage() {
const changed = [...danger.git.modified_files, ...danger.git.created_files]
const srcChanged = changed.some((f) => f.startsWith('src/') && !/\.(test|spec)\./.test(f))
const testChanged = changed.some((f) => /\.(test|spec)\./.test(f) || f.startsWith('tests/'))
if (srcChanged && !testChanged) {
warn('Source changed without test changes.')
}
}
function checkLockfileConsistency() {
const changed = [...danger.git.modified_files, ...danger.git.created_files]
const lockfile = changed.some((f) => /lock\.(json|yaml)$/.test(f))
const packageJson = changed.includes('package.json')
if (lockfile && !packageJson) {
warn('Lockfile changed without `package.json` change.')
}
}
function checkSensitiveFiles() {
const sensitive = ['.env', 'secrets', 'credentials']
const changed = [...danger.git.modified_files, ...danger.git.created_files]
const flagged = changed.filter((f) => sensitive.some((s) => f.includes(s)))
if (flagged.length > 0) {
fail(`Sensitive files modified: ${flagged.map((f) => `\`${f}\``).join(', ')}`)
}
}
// Bad
warn('Tests missing.')
// Good
warn(
'Source files in `src/services/` were modified but no test files changed. ' +
'Add or update tests in `tests/services/` to maintain coverage. ' +
'See [contributing guide](./CONTRIBUTING.md#testing).'
)
warn by default; only promote to fail after the team has adjustedfail — treating every rule as a hard blocker causes alert fatigue; use warn for advisory rules and fail only for genuine blockers (secrets, broken builds)GITHUB_TOKEN not scoped correctly — Danger needs read access to PR metadata and write access to post comments; use the built-in GITHUB_TOKEN with pull-requests: write permissionpull_request events--dangerfile flag for monorepos — a single root Dangerfile checking all packages becomes unwieldy; use --dangerfile packages/foo/Dangerfile.ts per package or a shared rule librarynpx claudepluginhub messeb/skills --plugin general-developerReviews staged or working changes for correctness, security, and performance before creating a PR. Produces a checklist and updates project rules.
Provides prompt-injection defense rules for GitHub issues and pull requests, plus optional workflow conventions for issue triage and CI safety.
Generic CI environment rules for GitHub Actions workflows. Use when operating in CI — covers security, CI monitoring, comment formatting, and investigating session logs from other runs.