Architecture overview
Pullminder is a Turborepo pnpm monorepo with five apps:
| App | Stack | Purpose |
|---|---|---|
| api | Go, chi router | REST API and webhook handler |
| dashboard | React 19, Vite, TanStack Router/Query, Tailwind CSS 4 | Web UI for teams |
| cli | Go, Cobra | Offline analysis and platform commands |
| marketing | Astro | Public website at pullminder.com |
| docs | Astro Starlight | Documentation site |
API internals
Section titled “API internals”The API server is a single Go binary built around the chi router. Business logic lives in internal/ packages:
| Package | Responsibility |
|---|---|
api | HTTP handlers and middleware |
auth | GitHub OAuth, sessions, CSRF |
billing | Subscription management, Viva Payments |
config | Environment and feature flags |
crypto | AES-256-GCM field encryption, HMAC signing |
evaluation | Rule engine, risk scoring |
github | GitHub API client, webhook parsing |
notify | Email and Slack notifications |
observe | Logging, tracing, metrics |
queue | Asynq task definitions and handlers |
registry | Rule pack fetching and caching |
risk | Risk model and score calculation |
session | Session store (PostgreSQL-backed) |
store | Database queries via pgx |
Data layer
Section titled “Data layer”PostgreSQL 16.8 is the primary data store. The schema is managed through 78+ sequential migrations that run automatically when the API server starts. All queries use the pgx driver directly — there is no ORM.
Redis 7.4 powers the task queue via Asynq. Four task types flow through the queue:
| Task | Description |
|---|---|
analyze_pr | Fetch diff, run analyzers, post results |
send_notification | Deliver email or Slack message |
fetch_coverage | Pull coverage data from CI provider |
baseline_scan | Full-repo scan on initial install |
Queue priorities control worker allocation:
| Priority | Workers |
|---|---|
critical | 3 |
default | 6 |
low | 1 |
Dashboard
Section titled “Dashboard”The dashboard is a React 19 SPA using TanStack Router for file-based routing and TanStack Query for server state. Styling uses Tailwind CSS 4. Charts are rendered with Recharts.
The CLI is a standalone Go binary built with Cobra. It runs the same analyzers as the platform but operates entirely offline against local diffs. Platform commands (sync, login) connect to the API.
Deployment
Section titled “Deployment”The API and worker run as Docker containers on Hetzner behind a Caddy reverse proxy. The dashboard, marketing site, and docs deploy to Cloudflare Pages with automatic builds from the main branch.
Observability
Section titled “Observability”- Logging — structured JSON via slog
- Error tracking — GlitchTip (Sentry-compatible)
- Tracing — OpenTelemetry
- Metrics — Prometheus
/metricsendpoint (internal, token-authenticated)
Security
Section titled “Security”- Field encryption — AES-256-GCM with rotatable keys for sensitive data at rest
- Badge signing — HMAC to prevent badge URL tampering
- CSRF — double-submit cookie pattern
- Sessions — HttpOnly, Secure, SameSite cookies backed by PostgreSQL
Data flow
Section titled “Data flow”GitHub webhook | v API server ──> PostgreSQL | v Redis queue | v Worker | ├── analyze PR ├── post comment on GitHub └── set commit status | v Dashboard reads from PostgreSQL- A pull request is opened or updated on GitHub.
- GitHub sends a webhook to the API server.
- The API validates the webhook signature, persists the event, and enqueues an
analyze_prtask. - The worker picks up the task, fetches the diff from GitHub, runs it through the rule engine, calculates a risk score, and stores the results.
- The worker posts a comment on the PR and sets a commit status check.
- The dashboard queries the API (backed by PostgreSQL) to display results to the team.