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.
Close out the plan with the remaining polish items and a full README.
Shell completions (cmd/gitflow):
- New `completion [bash|zsh|fish|powershell]` command backed by cobra's
generators, wired into the root command
Desktop notifications (internal/notify):
- Send() dispatches to notify-send (Linux) with an osascript fallback
(macOS); Windows is a documented no-op for now; missing notifiers are
silent, never errors
- watch --notify sends a notification listing repositories whose state
changed since the previous frame
Color themes (internal/presenter):
- ThemeMode (dark/light) with ParseTheme; light uses bright ANSI variants
(90-97) that stay legible on light backgrounds; threaded through the
table, compact, and suggestions renderers; new --theme flag validated
and dumped by config
Custom rules (internal/rules):
- Rule{name, field (ahead|behind|stash|changes), op (==,!=,<,<=,>,>=),
value, label} with upfront validation in both Validate and Eval
- Config gains a `rules:` section (yaml/env only, no flag), validated at
load; matches render as a "FLAGS (custom rules)" section via a new
presenter.Flags renderer, shown in scan and watch frames
Docs:
- readme.md fully rewritten: features, install, usage, examples, flag
table, status classes, configuration reference, AI agent behavior and
--ai-execute guardrails, development layout, CI, and future work
- implementation.md gains an Implementation Progress section recording
every phase branch and the deviations from the original plan
Multi-platform:
- Verified cross-compilation for windows/amd64 and darwin/arm64; the
notify package is split behind build tags
Testing:
- rules: validation, operator semantics, Eval ordering, invalid-rule
errors
- presenter: ParseTheme, light-theme bright codes (and absence of
dark-theme codes), Flags rendering (empty = silent, matches render)
- config: rules loading from file, invalid-rule rejection, bad theme and
bad provider rejection
- notify: no-op behaviour when no notifier is installed (skipped when one
is, to avoid firing real notifications)
Verified: go build, go vet, go test -race (10 packages), gofmt clean,
windows/darwin cross-compile, completion generation, rules + light theme
smoke test, watch --notify graceful shutdown (exit 0, no orphans).
Add the Cobra-based command surface and the flag/env/config-file
resolution layer that all commands share.
Configuration (internal/config):
- Load() resolves settings with the documented precedence flags > env >
config file > defaults, via viper: GITFLOW_-prefixed env vars with
dot-to-underscore mapping, plus ~/.gitflow.yaml (or $GITFLOW_CONFIG)
- RegisterFlags/NewFlagSet own the flag definitions so every command and
the tests share a single source of truth
- Config/Validate/Dump cover dir, interval, format (table/json/compact),
exclude globs, max depth, worker count, and the AI block (enabled,
provider, model, api_key_env, base_url); ConfigFile records the loaded
path; Dump renders the effective config as human-readable YAML with the
interval as a duration string
App orchestration (internal/app):
- New() validates the configuration at the boundary (fail fast)
- ScanOnce() runs discovery then a concurrent status scan, warning on
stderr and continuing when discovery is only partially successful
(e.g. permission-denied subtrees), and bundles everything into a
ScanResult
CLI (cmd/gitflow):
- root command with scan / config / version subcommands
- scan: resolves config, prompts for the parent directory when stdin is a
TTY and --dir was not given (per the README), runs a single pass, and
renders the result — interim plain/JSON output until phase 3 lands the
presenter package
- config: prints the effective configuration
- version: prints the build version (ldflags-injectable)
- signalContext() wires SIGINT/SIGTERM into a cancellable context for
graceful shutdown
Testing:
- config: defaults, flag overrides, env overrides, flag-beats-env
precedence, config file loading (including duration and slice values),
GITFLOW_CONFIG path override, validation failures, and Dump output
- app: config validation on New, end-to-end ScanOnce over a real temp
repo, and missing-directory errors
Verified: go build, go vet, go test -race, gofmt clean; manual smoke of
`gitflow version`, `gitflow config`, and `gitflow scan -d <dir>` against a
scratch directory with a dirty repo.