GitOps configuration
.pullpulse.yml
Set routing, quiet hours and agent handling in your repository. PullPulse validates every change and reports problems as a GitHub check.
Complete .pullpulse.yml example
Start from the defaults, then keep the sections your team needs.
version: 1
defaults:
channel: "#eng-reviews"
strategy: message
mention: reviewers
holdUntil: readyAndChecksGreen
threadReplies: true
aiBrief: true
notifyAuthorOnReady: true
density: card
releases: false
weeklyNotes: false
escalateAfter: "8h"
quietHours:
tz: Europe/Berlin
from: "19:00"
to: "08:00"
quietDays: [6, 7]
rules:
- name: "critical workloads need a review thread"
when:
workload:
criticality: critical
route:
channel: "#system-review"
strategy: message
mention: reviewers
aiBrief: true
- name: "migrations go to the database channel"
when:
paths: ["**/migrations/**", "**/*.sql"]
route:
channel: "#db-review"
mention: reviewers
escalateAfter: "4h"
- name: "agent PRs wait for green CI"
when:
authorKind: agent
route:
strategy: rollup
mention: none
holdUntil: checksGreen
digest: hourly
reminders:
enabled: true
firstNudgeAfter: "4h"
escalateAfter: "24h"
staleAfter: "3d"
maxPerReviewerPerDay: 6
escalateToOwners: true
ai:
enabled: true
provider: workers-ai
brief: true
reviewerSuggestions: true
suggestAttention: true
pulseReport: false
maxDiffBytes: 180000
digests:
enabled: true
cadence: daily
at: "09:30"
tz: Europe/Berlin
channel: "#eng-reviews"
includeAgentQueue: true
agents:
enabled: true
extraLogins: [our-internal-bot]
holdUntil: readyAndChecksGreen
rollup: true
ignoreRepos: ["acme/legacy-*"]
ignorePaths: ["**/*.md", "**/fixtures/**"]
system:
workloads:
- name: sessions
paths: ["src/auth/**"]
criticality: critical
hotPath: true
intent: "Validate the session before returning private workspace data."
Rules are first-match-wins, evaluated top to bottom, with the workspace defaults underneath. /pullpulse explain <pr> prints which rule matched and why. Set continue: true on a rule to keep evaluating after it matches.
Durations match ^(\d+)(ms|s|m|h|d|w)$ — 30m, 4h, 2d. Times of day are 24-hour HH:MM in the accompanying tz. Channels are #name or a Slack channel id. Path and repo patterns are globs, where ** crosses directory boundaries.
version
| key | type | default | notes |
|---|
| version | 1 | required | |
defaults
| key | type | default | notes |
|---|
| defaults | object | — | section |
| defaults.channel | string | — | matches ^(#[a-z0-9][a-z0-9._-]{0,79}|[CGD][A-Z0-9]{6,})$ |
| defaults.strategy | message | rollup | digest | mute | "message" | |
| defaults.mention | none | author | reviewers | all | "reviewers" | |
| defaults.holdUntil | none | checksGreen | checksComplete | readyForReview | readyAndChecksGreen | "readyAndChecksGreen" | |
| defaults.threadReplies | boolean | true | |
| defaults.aiBrief | boolean | true | |
| defaults.notifyAuthorOnReady | boolean | true | |
| defaults.density | card | compact | board | "card" | |
| defaults.releases | boolean | false | |
| defaults.weeklyNotes | boolean | false | |
| defaults.escalateAfter | string | — | |
| defaults.quietHours | object | — | section |
| defaults.quietHours.tz | string | "UTC" | |
| defaults.quietHours.from | string | required | matches ^([01]\d|2[0-3]):[0-5]\d$ |
| defaults.quietHours.to | string | required | matches ^([01]\d|2[0-3]):[0-5]\d$ |
| defaults.quietHours.quietDays | integer[] | [6,7] | up to 7 entries |
rules
| key | type | default | notes |
|---|
| rules | object[] | [] | up to 200 entries |
| rules[].name | string | required | |
| rules[].when | object | required | section |
| rules[].when.repos | string[] | — | at least one entry |
| rules[].when.events | pr.opened | pr.reopened | pr.ready_for_review | pr.converted_to_draft | pr.synchronized | pr.edited | pr.labeled | pr.unlabeled | pr.review_requested | pr.review_request_removed | pr.assigned | pr.merged | pr.closed | review.submitted | review.dismissed | comment.created | comment.edited | checks.updated | config.updated[] | — | at least one entry |
| rules[].when.authorKind | human | agent | bot | human | agent | bot[] | — | |
| rules[].when.agents | claude-code | github-copilot | openai-codex | devin | cursor | jules | aider | gemini-cli | unknown-agent[] | — | at least one entry |
| rules[].when.authors | string[] | — | at least one entry |
| rules[].when.labels | string[] | — | at least one entry |
| rules[].when.notLabels | string[] | — | at least one entry |
| rules[].when.baseRef | string[] | — | at least one entry |
| rules[].when.headRef | string[] | — | at least one entry |
| rules[].when.paths | string[] | — | at least one entry |
| rules[].when.draft | boolean | — | |
| rules[].when.workload | object | — | section |
| rules[].when.workload.names | string[] | — | up to 64 entries |
| rules[].when.workload.criticality | critical | standard | — | |
| rules[].when.workload.hotPath | true | — | |
| rules[].when.complexityAtLeast | trivial | low | medium | high | — | |
| rules[].when.complexityBelow | trivial | low | medium | high | — | |
| rules[].when.attention | string[] | — | at least one entry |
| rules[].when.riskAtLeast | integer | — | 0–100 |
| rules[].when.riskBelow | integer | — | 0–100 |
| rules[].when.sizeAtLeast | integer | — | 0–9007199254740991 |
| rules[].when.sizeBelow | integer | — | 1–9007199254740991 |
| rules[].when.titleMatches | string | — | |
| rules[].when.checks | green | red | pending | none[] | — | at least one entry |
| rules[].route | object | required | section |
| rules[].route.channel | string | — | matches ^(#[a-z0-9][a-z0-9._-]{0,79}|[CGD][A-Z0-9]{6,})$ |
| rules[].route.strategy | message | rollup | digest | mute | — | |
| rules[].route.mention | none | author | reviewers | all | — | |
| rules[].route.holdUntil | none | checksGreen | checksComplete | readyForReview | readyAndChecksGreen | — | |
| rules[].route.digest | hourly | daily | weekly | — | |
| rules[].route.escalateAfter | string | — | |
| rules[].route.threadReplies | boolean | — | |
| rules[].route.aiBrief | boolean | — | |
| rules[].route.notifyAuthorOnReady | boolean | — | |
| rules[].route.density | card | compact | board | — | |
| rules[].route.releases | boolean | — | |
| rules[].route.weeklyNotes | boolean | — | |
| rules[].continue | boolean | false | |
reminders
| key | type | default | notes |
|---|
| reminders | object | — | section |
| reminders.enabled | boolean | true | |
| reminders.firstNudgeAfter | string | "4h" | |
| reminders.escalateAfter | string | "24h" | |
| reminders.staleAfter | string | "3d" | |
| reminders.maxPerReviewerPerDay | integer | 6 | 1–50 |
| reminders.escalateToOwners | boolean | true | |
ai
| key | type | default | notes |
|---|
| ai | object | — | section |
| ai.enabled | boolean | true | |
| ai.provider | workers-ai | openai | anthropic | "workers-ai" | |
| ai.model | string | — | |
| ai.brief | boolean | true | |
| ai.reviewerSuggestions | boolean | true | |
| ai.suggestAttention | boolean | true | |
| ai.riskAdjustment | boolean | — | |
| ai.pulseReport | boolean | false | |
| ai.maxDiffBytes | integer | 180000 | 1000–2000000 |
digests
| key | type | default | notes |
|---|
| digests | object | — | section |
| digests.enabled | boolean | true | |
| digests.cadence | hourly | daily | weekly | "daily" | |
| digests.at | string | "09:30" | matches ^([01]\d|2[0-3]):[0-5]\d$ |
| digests.tz | string | "UTC" | |
| digests.channel | string | — | matches ^(#[a-z0-9][a-z0-9._-]{0,79}|[CGD][A-Z0-9]{6,})$ |
| digests.includeAgentQueue | boolean | true | |
agents
| key | type | default | notes |
|---|
| agents | object | — | section |
| agents.enabled | boolean | true | |
| agents.extraLogins | string[] | [] | |
| agents.minComplexityToNotify | trivial | low | medium | high | — | Retired: ignored with a migration notice. Use explicit workload or path rules. |
| agents.minRiskToNotify | integer | — | Retired: ignored with a migration notice. Use explicit workload or path rules. · 0–100 |
| agents.holdUntil | none | checksGreen | checksComplete | readyForReview | readyAndChecksGreen | "readyAndChecksGreen" | |
| agents.rollup | boolean | true | |
attention
| key | type | default | notes |
|---|
| attention | object | — | section |
| attention.paths | object | {} | |
map
| key | type | default | notes |
|---|
| map | object | — | section |
| map.enabled | boolean | true | |
| map.publish | slack | slack-and-github | — | |
| map.animation | boolean | — | |
| map.components | object | {} | |
system
| key | type | default | notes |
|---|
| system | object | — | section |
| system.workloads | object[] | required | up to 64 entries |
| system.workloads[].name | string | required | |
| system.workloads[].paths | string[] | required | up to 16 entries |
| system.workloads[].criticality | critical | standard | unknown | "unknown" | |
| system.workloads[].hotPath | boolean | — | |
| system.workloads[].intent | string | — | |
| key | type | default | notes |
|---|
| githubComment | boolean | — | |
ignoreRepos
| key | type | default | notes |
|---|
| ignoreRepos | string[] | [] | |
ignorePaths
| key | type | default | notes |
|---|
| ignorePaths | string[] | [] | |