3d389ac3e8
10 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 3d389ac3e8 |
feat: M3 — TUI interaction (periodic scan, countdown, change markers)
Replace the static-scan boot in the TUI with a reactive scan loop anchored on bubbletea.Tick and user-driven 'r' rescan. Scan loop (tea.Tick instead of goroutine — no leak): - Init returns a tick Cmd when --interval > 0; on each tick the model fires scanCmd and chains the next tick in the Update handler, producing a self-regulating interval loop that stops naturally on quit - 'r' key triggers an immediate rescan when the model is not already loading, for on-demand refresh - scanResultMsg handler records prevResult, computes status.Changed into a changed set (keyed by repo path), and updates lastScan - scanErrorMsg stores the error text on the model (displayed in the status bar) without stopping the loop Change detection in the view: - Repos whose state changed since the previous scan get a ▲ prefix (yellow bold) in the repo list, distinguishing them from the > cursor Status bar improvements: - Countdown "next: 23s" when --interval is active, computed from time.Since(lastScan); "scanning…" shown during async scans - Updated keybinding hints: r refresh, g/G home/end Navigation additions: - g/Home jumps to top, G/End jumps to bottom Styles: - Changed style (yellow bold foreground) for ▲ markers - Styles struct now includes all five variants Tests: - Home/End keys navigate to first/last repo - 'r' fires scanCmd when not loading - ▲ marker appears for changed repos - Interval tick fires scan when idle - Countdown "next:" shown in status bar Verified: go build, go vet, go test -race (12 packages), gofmt clean. |
|||
| 2656f29019 |
feat: M2 — TUI skeleton (bubbletea model/view/styles)
Ship a working `gitflow tui` command: static scan result rendered as a lipgloss-styled table, with keyboard navigation and a detail pane. TUI package (internal/tui): - Model struct: holds scan result, cursor, detail/help toggles, loading state, error display, and injected app/AI/config dependencies - Update: handles WindowSizeMsg (resize), KeyMsg for navigation (j/↑/k/↓, Home/End, Enter/Space for detail, ? for help, q/Ctrl-C for quit), and async scanResultMsg/scanErrorMsg for the M3 periodic scan loop - View: repo list table (STATUS/REPOSITORY/BRANCH/AHEAD-BEHIND/CHGS/STASH) with cursor highlighting and dimmed error rows, expandable detail pane showing files, and a status bar (scan summary + keybinding hints) - styles.go: lipgloss Styles (Header/Row/CursorRow/StatusBar) with dark/light theme palettes that mirror the CLI's ThemeMode - Loading and empty states; help overlay with keybinding reference Presenter API surface (needed by the TUI): - ShortPath exported (home → ~ collapsing, reused by TUI and CLI) - StatusSymbolFor exported (✓ ✗ ↑ ↓ ⇄ ◉ ▢ ! symbols, reused) - ShortPath is kept as a wrapper so internal formatter callers are unaffected CLI (cmd/gitflow): - newTUICmd: resolves config, builds AI provider, calls tui.New() for the initial scan, and runs the bubbletea Program - Wired into root command under `gitflow tui` with full flag set (--dir, --interval, --exclude, etc.) Dependencies: bubbletea v1.3.10, lipgloss v1.1.0, a stable x/term tree Testing: - Model logic tests: cursor navigation (arrow + j/k), bottom/top clamping, detail toggle (Enter/Space), ? help, q/Ctrl-C quit, view renders repo names/branch/status columns, detail pane shows file lists, help overlay shows keybindings, loading/empty states Verified: go build, go vet, go test -race (12 packages), gofmt clean, `gitflow tui --help` prints command usage. |
|||
| b83a4baebd |
refactor: M1 — extract shared HTTP client from AI package
Pull the private `client`/`postJSON` from `internal/ai` into a standalone `internal/httpclient` package so both the AI providers and the upcoming webhook senders can share the same bounded-reader, timeout-guarded JSON HTTP client without introducing a dependency cycle. Changes: - internal/httpclient: Client struct with PostJSON(ctx, url, headers, payload, out), functional options WithTimeout/WithTransport, a 4 MiB response cap, and a 60s default timeout - internal/ai: three providers (OpenAI, Ollama, Anthropic) now embed an `*httpclient.Client` (field renamed from `client` to `http`); the old `client.go` is deleted - All 11 test packages pass (ai tests are byte-for-byte unaffected) This zero-behaviour refactor unblocks the webhook package distributed in M5, which needs the exact same JSON-post-and-decode helper. |
|||
| 012fbf8ac5 |
feat: phase 6 — polish: completions, notifications, themes, rules, docs
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).
|
|||
| aee63bf321 |
feat: phase 5 — AI agent integration (OpenAI, Ollama, Anthropic)
Add the AI layer that turns scan results into suggested next actions. Provider model (internal/ai): - Provider interface with Name() and Suggest(ctx, ScanResult) returning []Suggestion (repo_path, action, message, command, priority 0-2) - NewProvider factory resolves the configured provider and enforces that cloud providers have their API key in the configured env var; Ollama needs no key - OpenAIProvider: Chat Completions with response_format json_object - OllamaProvider: local /api/chat with format:json for structured output - AnthropicProvider: Messages API with system prompt and key/version headers; all three fall back to provider-appropriate defaults for base_url and model - Shared client: 60s timeout, 4 MiB response cap, JSON encode/decode, HTTP error surfaces the upstream status and body Prompt design (BuildPrompt): - Renders the full repository status table (name/status/branch/ahead/ behind/changes) plus strict output rules: exact suggestion schema, allowed actions, smallest-safe-step guidance, no invented repositories, return [] when healthy Parsing (parseSuggestions): - Tolerates ```json fences and surrounding prose, clamps priorities to 0-2, caps the result, and rejects replies without a JSON array Suggestion display (presenter.Suggestions): - "AI SUGGESTIONS" table (repository/action/priority/message/command) rendered below the scan table with priority color-coded (red/yellow/ green); empty results say all repositories are healthy Guarded execution (--ai-execute, experimental): - RunConfirmed executes a suggestion's command inside its repository only after explicit per-command y/N confirmation, and only for actions on an allowlist (commit/push/pull/stash/checkout) so LLM output can never run arbitrary shell commands; cancellation aborts remaining suggestions CLI wiring: - scan: AI block after the table when --ai is set and format is not json (JSON streams stay machine-readable); failures degrade to warnings - watch: AI is queried only on the first frame and when something changed since the previous frame, to avoid hammering the provider every interval - New --ai-execute flag and provider validation in config (openai/ollama/ anthropic), included in the config dump Testing: - httptest-based provider tests verifying request shape (model, auth headers, path), response parsing, API error bodies, HTTP failures, and cancellation - Parse tests: plain/fenced/prose replies, empty arrays, garbage, truncated JSON, priority clamping - Execution tests: unsafe actions and empty commands never run, declined confirmations are skipped, confirmed commands execute in the repo dir, failing commands surface errors - Presenter suggestion table and empty-state tests Verified: go build, go vet, go test -race, gofmt clean; end-to-end smoke test against a local fake Ollama server (request shape confirmed, table + suggestions rendered) and the missing-API-key warning path. |
|||
| 0c9c07e8de |
feat: phase 4 — periodic scanning (watch mode) with change detection
Add the scheduling layer and the watch command so gitflow can rescan
repositories on an interval and surface state changes.
Scheduler (internal/scheduler):
- Scheduler runs a callback immediately and then on a time.Ticker until
the context is cancelled; interval <= 0 means a single run
- Graceful shutdown: cancellation is never reported as an error (checked
on the first run and after every run), so Ctrl-C / SIGTERM exit cleanly
- A non-cancellation run error stops the loop and is propagated
Change detection (pkg/status):
- Changed(prev, next) compares snapshots per repository path — status,
branch, detached flag, ahead/behind, stash count, and file counts —
and returns the repositories whose observable state differs, including
repositories that newly appear
Watch command (cmd/gitflow):
- newWatchCmd validates that --interval is positive, prompts for the
parent directory on a TTY when --dir is absent, and drives the
scheduler with ScanOnce
- Each frame clears the screen on a terminal (or prints a RFC3339 header
when output is piped, so watch doubles as a lightweight logger) and
renders through the presenter
- Footer shows changed repositories since the previous frame ("▲ name:
status") and the next scan time; JSON format emits one document per
frame for scripting
- SIGINT/SIGTERM handled via signalContext for a clean stop
Testing:
- Scheduler: single run, periodic repetition, stop-on-error, pre-cancelled
context, and cancellation-during-run (no error reported)
- status.Changed: unchanged repos ignored; status, file-count, and
newly-appeared repos detected
Verified: go build, go vet, go test -race, gofmt clean; manual watch
smoke test over a scratch directory with 1s interval — frames render,
SIGINT and SIGTERM both exit 0 with no orphaned processes.
|
|||
| c9ff44298c |
feat: phase 3 — presentation layer (table, JSON, compact)
Replace the interim renderers with a dedicated presenter package that
formats scan results in three ways.
Presenter (internal/presenter):
- Formatter interface with Present()/For() dispatch on format name:
table, json, compact
- TableFormatter: aligned tabwriter table with REPOSITORY / BRANCH /
STATUS / AHEAD-BEHIND / CHANGES / STASH columns, per-repo change
summaries like "2M 1U", error details inline, and a colored summary
line ("N repos | N clean | N need attention | N errors")
- JSONFormatter: indented document with scanned_at, parent_dir, repos,
and an aggregate summary for scripting; statuses render as string
labels ("modified") instead of raw integers
- CompactFormatter: one line per repo with color-coded status symbols
(✓ ✗ ↑ ↓ ⇄ ◉ ▢ !) plus branch, ahead/behind, and change counts
- Color handling: auto/always/never modes, TTY detection, and the
NO_COLOR convention (explicit --color=always still wins); paths have
$HOME collapsed to "~" in table and compact views
Domain model (pkg/status):
- JSON tags on RepoInfo/ScanResult/Summary for clean field names
- RepoStatus now marshals to its string label and unmarshals from both
string labels and numeric values, so JSON output round-trips
Configuration (internal/config):
- New --color flag (auto/always/never) validated in Load and included in
the config dump; keyed as "color" in viper
CLI (cmd/gitflow):
- scan now routes through presenter.Present with the resolved color mode;
the interim renderers are removed
Testing:
- Table content (columns, change summaries, error text, summary line) and
absence of escape codes with ColorNever
- ColorAlways emits ANSI codes even under NO_COLOR; auto stays clean on
non-terminal writers
- JSON decodes back into the domain types (string statuses round-trip)
- Compact symbols and counts; unknown formats rejected
- RepoStatus JSON round trip covers every status
Verified: go build, go vet, go test -race, gofmt clean; manual smoke of
table / compact / forced-color / JSON output against a scratch directory.
|
|||
| d52e0715f2 |
feat: phase 2 — CLI commands and configuration resolution
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. |
|||
| ae23019277 |
feat: phase 1 — repository discovery and status scanning
Implement the core data layer that everything else builds on: walking a parent directory for Git repositories and snapshotting each repository's state via porcelain commands. Domain model (pkg/status): - RepoStatus enum with zero value = StatusUnknown (invalid/unset), covering clean / modified / ahead / behind / diverged / detached / bare / error - RepoInfo snapshot: path, name, remote URL, branch, detached flag, staged / modified / untracked file lists, ahead/behind counts, stash count, error - ScanResult with computed Summary counters and NeedsAttention filter Git wrapper (internal/git): - Executor built with functional options (binary, timeout, env), defaults to "git" with a 10s per-command timeout so a hung repository can never stall a scan; context cancellation honoured via exec.CommandContext - Status() validates with rev-parse --is-bare-repository (bare repos are reported as StatusBare and skipped), then parses `git status --porcelain=v2 --branch -z` - Parsing uses the NUL-separated v2 format so paths with spaces, quotes, and tabs survive verbatim (no C-quoting to decode); rename records (type 2) and ahead/behind lines (# branch.ab) are handled, unknown tokens are ignored for forward compatibility - Best-effort remote URL (git config --get-regexp) and stash count Discovery (internal/scanner/discover.go): - WalkDir-based traversal that never descends into .git internals - Detects working trees via .git directory or .git pointer file (linked worktrees, submodule checkouts) and bare repos via a *.git directory carrying its own HEAD/objects/refs - WithExclude glob patterns (matched against full path and base name) and WithMaxDepth depth limiting as functional options - Walk errors (e.g. permission denied) are aggregated and returned alongside partial results via errors.Join; cancellation is propagated Scanner (internal/scanner/scanner.go): - Concurrent status scanning with a bounded worker pool (errgroup + SetLimit, default 8 workers) - Per-repo failures become StatusError entries instead of aborting the pass; a cancelled context aborts the whole scan Testing: - Table-driven parser tests with canned NUL-separated porcelain output - Integration tests against a real git binary (clean / modified / staged / stashed / detached / bare / non-repo) - Discovery tests over a fixture tree with nested repos, a bare repo, a linked worktree, and an exclusion target - Scanner tests for mixed success/failure, bounded concurrency, and cancellation; everything runs under -race Note: LastFetch from the original plan was dropped — the only reliable source is a reflog of the remote-tracking ref, which does not exist on fresh clones, so it would always be misleading. The model remains extensible if a fetch-history feature is wanted later. |
|||
| 9761adf3f9 |
feat: phase 0 — project scaffolding and build tooling
Bootstrap the gitflow module so subsequent phases build on a verified, linted foundation. - go.mod: initialize module gitea.oblak.solutions/dimitar/gitFlow at go 1.24 - cmd/gitflow/main.go: minimal entrypoint that prints the version string; this is a placeholder that Phase 2 (CLI & config) replaces with Cobra - internal/version: version metadata injectable at build time via ldflags, with a Makefile target wiring VERSION through - Makefile: build / test / fmt / vet / lint / install / clean targets with ldflags version injection - .golangci.yml: enable errcheck, govet, staticcheck, gosimple, ineffassign, unused, misspell, gofmt, goimports - .github/workflows/ci.yml: GitHub Actions CI running build + vet + tests (with -race) across Go 1.24/1.26, plus a golangci-lint job - .gitignore: build artifacts, test outputs, editor cruft, and local config overrides (never committed) Verified locally: go build, go vet, gofmt clean, binary prints version. |