Wire the webhook dispatcher into configuration resolution and the watch
command so scan results are relayed to external services on every frame.
Config (internal/config):
- Config gains Webhooks []webhook.Config (yaml: "webhooks", mapstructure
tags for viper compatibility; duration fields use mapstructure:"-" and
are backfilled via fixWebhookDurations calling v.GetDuration)
- validateWebhook enforces: name non-empty, type in (slack|discord|
generic), URL starts with http/https, rate_limit >= 1s, retry
max_attempts <= 5, min_priority 0-2
- Dump includes the webhooks section
Watch command (cmd/gitflow):
- At startup, builds a webhook.Dispatcher from cfg.Webhooks
- After each scan frame, fires dispatchWebhooks in a goroutine so a slow
webhook never blocks the scan interval
- dispatchWebhooks constructs a webhook.Payload from the scan result,
computes changed repositories, and fans out via d.Dispatch; errors
are printed to stderr
Config tests:
- Webhooks parse from YAML (type, URL, on_change_only, rate_limit,
retry.backoff) with duration fixup verified
- Bad webhooks rejected: unknown type, empty URL, non-http URL,
sub-second rate_limit
Verified: go build, go vet, go test -race (13 packages), gofmt clean.