Part of #1367 (Phase 3 — Dependency Engine v2).
Problem
The README markets a "Plugin Ecosystem" with Slack/Discord/Jira integrations — but the plugin system is setup-only: PluginManager lives in src/setup.ts:400-700, only the setup_complete event ever fires (bin/cli.ts:444), PluginManager is never referenced in src/buddy.ts, and JSON-loaded custom plugins (.buddy/plugins/*.json) cannot carry executable handlers since SetupHook.handler is a function type. IntegrationPoint/AuthConfig types (setup.ts:126-138) are declared and unused. No notifications exist for PR creation, update runs, failures, or security findings — the events users actually care about.
Spec
1. Runtime event bus
src/events/bus.ts — typed events emitted from the real code paths:
| Event | Emitted from |
|---|---|
scan.completed (updates found, counts by type) | Buddy.scanForUpdates |
pr.created / pr.updated / pr.closed | createPullRequests, auto-close paths |
pr.merged (auto-merge) | #1369 |
security.advisories (new vulnerable deps) | annotateSecurityAdvisories |
run.failed (any command failure w/ error summary) | CLI top-level handler |
dashboard.updated | createOrUpdateDashboard |
review.completed / fixci.completed | #1375 / #1381 when they land |
gate.failed | #1382 |
2. Notification sinks (declarative, JSON-safe — fixes the handler problem)
notifications?: {
slack?: { webhookEnv?: string, events?: EventName[], channel?: string }
discord?: { webhookEnv?: string, events?: EventName[] }
jira?: { baseUrlEnv?, tokenEnv?, projectKey?, events?: EventName[], createTicketsFor?: ('security.advisories' | 'run.failed')[] }
webhooks?: { url: string, secretEnv?: string, events?: EventName[] }[] // generic: HMAC-signed JSON POST
}- Rich formatting: Slack blocks / Discord embeds with PR links, update tables, severity colors (reuse existing formatting knowledge from
src/utils/security-format.ts) - Generic webhook = the extensibility story (replaces un-executable JSON "plugins" for runtime): HMAC-SHA256 signature header (
x-buddy-bot-signature), retry w/ backoff viasrc/utils/http.ts, event payload versioned - Secrets only ever referenced by env-var name in config (consistent with #1386 pattern)
- Failure isolation: sink errors are logged, never fail the run (matching current swallow-errors hook behavior)
3. Migration & cleanup
- Keep setup-time plugin discovery (auto-detect
SLACK_WEBHOOK_URLetc. → offer to writenotificationsconfig) - Deprecate
.buddy/plugins/*.jsonruntime pretensions honestly: they remain setup-lifecycle hooks only; docs updated - Delete or implement
IntegrationPoint/AuthConfigdead types
Tasks
- Event bus + emissions at the sites listed (thread through
Buddyconstructor) - Slack/Discord/Jira/generic-webhook sinks with per-event templates + fixtures (recorded payload snapshots)
- HMAC signing + retry/backoff + failure isolation tests
- Config + validation + setup-wizard integration (Step 2 discovery now writes real config)
docs/integrations.mdrewrite matching reality; README claims audit- Payload schema doc for webhook consumers (versioned)
Acceptance criteria
pr.createdfires a correctly-formatted Slack block message in fixture tests;security.advisoriescan open a Jira ticket- A failing sink never fails the bot run
- Generic webhook receives signed, versioned JSON for every subscribed event
- README's integration claims are true