middleman is a local-first GitHub PR monitoring dashboard for a maintainer managing a small fixed set of repositories. It syncs PR data from GitHub into SQLite on a timer, serves a Svelte 5 SPA via an embedded Go HTTP server, and provides a focused workflow for triage, review, and merge without living in GitHub's notification UI.
CLI (middleman) → Config (TOML) → DB (SQLite)
↓ ↓
Sync Engine → GitHub API (go-github/v84)
↓ ↓
HTTP Server → REST API + Embedded SPA
- Server: Huma-based HTTP server on loopback (default 127.0.0.1:8091)
- Storage: SQLite with WAL mode (pure Go driver: modernc.org/sqlite)
- Sync: Periodic pull from GitHub API (configurable, default 5m)
- Frontend: Svelte 5 SPA embedded in the Go binary at build time
- Config: TOML at
~/.config/middleman/config.toml, GitHub token from env varMIDDLEMAN_GITHUB_TOKEN
cmd/middleman/- Go server entrypointinternal/config/- TOML config loading and validationinternal/db/- SQLite schema, connection, queries, typesinternal/github/- GitHub API client, normalization, sync engineinternal/server/- HTTP handlers and routinginternal/web/- Embedded frontend (dist/ copied at build time)frontend/- Svelte 5 SPA (Vite, TypeScript)
| Path | Purpose |
|---|---|
cmd/middleman/main.go |
CLI entry point, server startup, signal handling |
internal/config/config.go |
TOML config, validation, defaults |
internal/db/migrations/ |
Numbered SQL migrations for schema changes |
internal/db/db.go |
Database open, WAL, migration init |
internal/db/queries.go |
All CRUD operations |
internal/db/types.go |
DB model types |
internal/github/client.go |
GitHub API interface and live implementation |
internal/github/normalize.go |
Convert GitHub types to DB types |
internal/github/sync.go |
Periodic sync engine |
internal/server/server.go |
HTTP router, SPA serving |
internal/server/huma_routes.go |
Huma API registrations and handlers |
internal/server/api_types.go |
Shared API response types used by Huma |
internal/apiclient/generated/client.gen.go |
Generated Go API client from the checked-in OpenAPI spec |
frontend/src/App.svelte |
Root component, view routing |
frontend/src/app.css |
Design tokens, theme, global styles |
frontend/src/lib/stores/ |
Svelte 5 rune-based stores |
frontend/src/lib/components/ |
UI components (sidebar, detail, kanban) |
make build # Build binary with embedded frontend
make dev # Run Go server in dev mode
make frontend # Build frontend SPA only
make frontend-dev # Run Vite dev server (use alongside make dev)
make install # Build and install to ~/.local/bin or GOPATHFor development, run make dev and make frontend-dev in parallel. Vite proxies /api to the Go server on :8091.
make test # All Go tests
make test-short # Fast tests only
make lint # golangci-lint
make vet # go vetE2E tests are non-negotiable. Every major feature, bug fix, and refactor must include e2e tests that exercise the full stack (HTTP API with real SQLite). Even small changes merit e2e coverage when they touch API behavior, data flow between layers, or anything a user would notice if it broke. When in doubt, write the e2e test — the cost of a missing one is always higher than the cost of writing it.
- Always pass
-shuffle=onwhen invokinggo testdirectly (e.g.go test ./internal/db -run TestFoo -shuffle=on). Themake testandmake test-shorttargets already set it. Shuffled ordering catches hidden test-to-test coupling - Do not pass
-count=1togo test.-count=1is the default and specifying it wastes tokens and disables the build cache unnecessarily. Omit the flag. If a genuine need to bypass cache arises, confirm with the user first - Only pass
-count=NwhenN > 1(e.g.-count=10for flake hunting) - Table-driven tests for Go code
- Use
testifyconsistently in Go tests; preferrequirefor setup/preconditions andassertfor non-blocking checks - When a test function has more than 3 assertions, create a local helper with
assert := Assert.New(t)and use the helper methods for the rest of the checks - Do not use
t.Fatal,t.Fatalf,t.Error,t.Errorf,t.Fail, ort.FailNowin tests; use testify assertions instead - Prefer the generated Go API client in
internal/apiclientfor integration-style API tests - Use
openTestDB(t)helper for database tests - All tests use
t.TempDir()for temp directories - Tests should be fast and isolated
- Do not run tests with
-v(especiallygo test) — default output has enough signal to debug failures, and verbose output wastes tokens. Only use-vif the user asks for it or a failure genuinely needs the extra detail - For GraphQL query changes, follow
context/testing.mdand enableMIDDLEMAN_LIVE_GITHUB_TESTS=1to validate query shape against GitHub's live GraphQL API
- No CGO required — uses modernc.org/sqlite (pure Go)
- Frontend: Bun for Svelte build/test tooling, embedded via
internal/web/dist/
- Prefer stdlib over external dependencies
- Do the task requested, not the task imagined. Do not widen scope without explicitly confirming with the user first
- Use
humafor the web framework and OpenAPI generation - Regenerate API artifacts with
make api-generate; the Go client also supportsgo generate ./internal/apiclient/generated - Never use npm — use
bun install,bun run build,bun run dev, etc. for all frontend operations. Never runnpm installornpm run— this createspackage-lock.jsonwhich conflicts with the bun lockfile - Tests should be fast and isolated
- No emojis in code or output
- For database schema changes, follow
context/db-migrations.md;internal/db/migrations/is the source of truth for schema evolution. - For UI work, follow
context/ui-design-system.md; prefer extending shared UI primitives over adding one-off local badge/chip/button styling. - Datetimes are UTC across storage and API boundaries. Store timestamps in UTC, emit API timestamps as UTC RFC3339, and only convert to local time in the Svelte UI presentation layer.
- Commit every turn — always commit your work at the end of each turn, no exceptions
- Never amend commits — always create new commits for fixes, never use
--amend - Never change branches — don't create, switch, or delete branches without explicit permission
- Never bypass pre-commit hooks — all commits must go through a hook-enforced Git commit path. Do not use
jjor any other workflow to create, rewrite, or finalize commits in a way that skips the repository's Git hooks - Use conventional commit messages
- Run tests before committing when applicable
- Never push or pull unless explicitly asked
- PR descriptions should be concise: summarize what changed, not how or why in detail
- When a PR adds or changes visible UI, use the
capture-playwrightskill to capture a Playwright screenshot or short video and attach it withgh image - Do this before opening the PR so the description can include the visual artifact links
- No test plans, implementation details, or checklists in PR descriptions
- No marketing language (critical, robust, comprehensive, etc.)
- A bulleted summary of user-visible changes is sufficient