Imported from z3spinner/go-stop (
AGENTS.md). Install upstream withnpx skills add z3spinner/go-stop. Copyright stays with the author.
AGENTS.md — Go-Stop (repository root)
Lightweight local ride-sharing notice board. A Go (Gin) API + PostgreSQL backend serving a compiled SvelteKit SPA. No accounts; phone number is the only identity and the (unverified) delete credential. AGPL-3.0.
Nested
AGENTS.mdfiles give detailed, location-specific conventions. Read the one closest to the code you're touching:
internal/AGENTS.md— clean-architecture rules & layer wiringinternal/usecase/AGENTS.md— use-case pattern & unit testsinternal/boundaries/AGENTS.md— Gin handlers, OpenAPI annotations, portsinternal/infrastructure/postgres/AGENTS.md— sqlc, migrations, reposfrontend/AGENTS.md— SvelteKit, shadcn-svelte, generated API client, i18ne2e/AGENTS.md— Playwright end-to-end tests
Architecture (Clean Architecture — dependencies point inward only)
domain ← usecase ← boundaries ← infrastructure (Go: ./internal)
▲
main.go (composition root: builds repos → use cases → handlers → routes)
web/build ← frontend/ (SvelteKit SPA, built and served as static files by the Go server)
The Go module is github.com/z3spinner/go-stop. main.go is the only wiring
point: it constructs concrete infrastructure, injects it into use cases, hands
those to handlers, and registers routes under /api. Any non-/api path falls
back to web/build/index.html (SPA routing), with per-ride Open Graph tags
injected server-side for link previews (og.go).
Repo map
| Path | What |
|---|---|
main.go, og.go, main_serve_test.go |
Composition root, SPA+OG handler, serve tests |
internal/domain |
Pure entities/value objects (no deps, no tags) |
internal/usecase |
Business logic; one NewXxx/Execute struct per file |
internal/boundaries |
Gin handlers + repository/notifier interfaces (ports) |
internal/infrastructure |
postgres (pgx + sqlc), vapid, webpush — the adapters |
internal/version |
Build SHA, injected at build time (do not commit build.go) |
cmd/migratedb |
golang-migrate CLI (up/down/force/drop/version) |
frontend/ |
SvelteKit + TypeScript + shadcn-svelte; builds to ../web/build |
e2e/ |
Playwright specs (run against the built app on :8080) |
docs/ |
Generated OpenAPI (docs.go, swagger.json/yaml) + design notes |
scripts/seed.sh |
Seeds the dev DB through the running app's API |
web/build |
Build artifact (gitignored) |
Commands (see Makefile)
| Task | Command |
|---|---|
| Run dev stack (DB + API + Vite) | docker compose up --build → app on :5173 (proxies /api→:8080) |
| Run Go + Vite locally (no Docker) | make dev |
| Unit tests (fast, no DB) | make test-unit |
| Integration tests (isolated stack) | make test-integration (alias: make test) |
| E2E tests (isolated stack) | make test-e2e |
| Unit + integration + e2e (stacks in parallel) | make test-all |
| Lint Go (golangci-lint) | make lint (make lint-install once to install the pinned version) |
| Auto-fix Go formatting | make fmt |
| Regenerate SQL code | make sqlc (after editing *.sql) |
| Regenerate OpenAPI spec | make swagger (after changing handler annotations) |
| Regenerate OpenAPI + frontend client | make api-generate |
| Build frontend | make build-web |
| Seed dev DB | make seed |
Integration and e2e run in their own throwaway docker compose projects
(gostop-itest / gostop-e2e, see docker-compose.itest.yml /
docker-compose.e2e.yml) with no published host ports — so they spin up
their own Postgres (and, for e2e, the production app image + Playwright), never
touch the devstack's DB or ports, and run in parallel with the devstack and each
other. No need to start Postgres yourself, and make test no longer truncates
the dev database.
Code generation pipeline — keep these in sync
Three generated artifacts are committed and must be regenerated (never hand-edited) when their source changes:
- sqlc — edit
internal/infrastructure/postgres/sqlc/queries/sql/*.sql, thenmake sqlc. Regenerates.../queries/*.sql.go. - OpenAPI — change swaggo annotations on handlers, then
make swagger. Regeneratesdocs/docs.go,docs/swagger.json,docs/swagger.yaml. - Frontend API client —
make api-generateruns swagger then orval, which regeneratesfrontend/src/lib/api/generated/go-stop-api.tsfrom the spec.
A handler/endpoint change typically touches all three: update SQL/use case →
make sqlc, update annotations → make api-generate, commit the generated files.
Configuration (env vars)
DATABASE_URL (required), PORT (default 8080), SITE_NAME (heading; default
Go-Stop), SERVICE_TZ (IANA tz used to interpret time-only searches; default
UTC, Europe/Paris in compose), RIDE_GRACE_MINUTES (default 60),
RETURN_DELAY_HOURS (default 2), GIN_MODE (release in prod), and the
VAPID_* Web Push keys (auto-generated and persisted to the DB on first boot if
unset — see internal/infrastructure/vapid). Copy .env.example → .env for
local work.
Deployment (Scalingo)
Procfile: web: migratedb up && go-stop — migrations run at boot, before the
server starts (postdeploy is not used for migrations; boot-time DB reads
depend on this ordering). .buildpacks chains the Node then Go buildpacks;
bin/go-pre-compile injects the git SHA into internal/version/build.go.
scalingo.json defines the one-click deploy form. The production image
(Dockerfile) is a 3-stage build: Vite build → Go build → alpine runtime.
Licensing & file headers
The project is AGPL-3.0-or-later (LICENSE, NOTICE). Every new
hand-written source file must start with an SPDX header.
Go / TypeScript / JavaScript (.go, .ts, .js):
// SPDX-FileCopyrightText: 2026 Zeno Kerr
// SPDX-License-Identifier: AGPL-3.0-or-later
Svelte (.svelte) — leading HTML comment:
<!--
SPDX-FileCopyrightText: 2026 Zeno Kerr
SPDX-License-Identifier: AGPL-3.0-or-later
-->
In Go, the header goes above everything; for build-tagged files keep the order
SPDX → blank line → //go:build → blank line → package.
Do NOT add headers to generated or vendored code — it isn't ours to mark:
sqlc queries, docs/docs.go, internal/version/build.go,
frontend/src/lib/api/generated/, the shadcn-svelte frontend/src/lib/components/ui/
primitives, and Paraglide output. (Update the copyright holder for genuinely new
contributors as appropriate.)
⚠️ Known issues to fix (these are NOT conventions — do not imitate)
- The many loose
*.pngscreenshots at the repo root are clutter (already gitignored via*.png, but messy to have on disk). (Fixed: a 35 MB compiledgo-stopbinary that had been committed is now removed and/go-stop,/migratedb,/backfill-matchesare gitignored.) - The migration numbering on
mainjumps 006 → 008: phone-at-rest encryption and its migration007_widen_phone_columnslive only on thefeature/phone-encryptionbranch. golang-migrate tolerates the gap (it tracks versions by name), so this is harmless — just do not reuse 007. (Fixed: the unimplementedPHONE_ENCRYPTION_KEYenv var that had been advertised inscalingo.jsonis now removed, since no Go code onmainreads it.) (All earlier README/config call-outs have been fixed: the committed binary, the deadPHONE_ENCRYPTION_KEYenv var, and the staleREADME.mddev/test instructions — which now point at :5173/Vite,make dev, and the realmake testflow instead of the removeddocker-compose.test.yml.)
