docs: add AGENTS.md with codebase conventions and cleanup context
Document lint helpers, architecture patterns, build commands, the rmo/sync host situation, DB access patterns, remaining lint issues, and the May 2026 cleanup summary.
This commit is contained in:
parent
40ffc2a3ba
commit
6c8f83f42d
1 changed files with 173 additions and 0 deletions
173
AGENTS.md
Normal file
173
AGENTS.md
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
# AGENTS.md — Nidus Sync Codebase Guide
|
||||||
|
|
||||||
|
This file captures conventions, patterns, and gotchas for anyone working on this codebase. It was produced during a lint cleanup pass (May 2026) to document lessons learned.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
**Module:** `github.com/Gleipnir-Technology/nidus-sync`
|
||||||
|
**Language:** Go 1.24
|
||||||
|
**Build:** Nix (`flake.nix`) + standard Go toolchain
|
||||||
|
**ORM:** Bob (legacy) + Jet (new, partial migration)
|
||||||
|
**Frontend:** Vue SPA (Vite) replacing Go HTML templates
|
||||||
|
|
||||||
|
The app serves two hosts from a single binary:
|
||||||
|
- **Sync** (`sync/`) — internal dashboard for mosquito control districts
|
||||||
|
- **RMO** (`rmo/`) — public-facing "Report Mosquitoes Online" site
|
||||||
|
|
||||||
|
Both are migrating from Go `html/template` rendered pages to Vue SPAs served by `static.SinglePageApp()`.
|
||||||
|
|
||||||
|
## Build & Lint Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build everything
|
||||||
|
go build ./...
|
||||||
|
|
||||||
|
# Run linter
|
||||||
|
golangci-lint run
|
||||||
|
|
||||||
|
# Build a specific package
|
||||||
|
go build ./api/
|
||||||
|
go build ./platform/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lint Helpers (`lint/error.go`)
|
||||||
|
|
||||||
|
The `lint/` package provides helpers for common error-handling patterns. **Always use these instead of bare calls** to avoid errcheck lint failures:
|
||||||
|
|
||||||
|
| Helper | Use for | Example |
|
||||||
|
|--------|---------|---------|
|
||||||
|
| `lint.Fprintf(w, fmt, args...)` | `fmt.Fprintf` to writers where errors are non-critical | `lint.Fprintf(w, "ok")` |
|
||||||
|
| `lint.Fprint(w, args...)` | `fmt.Fprint` to writers | `lint.Fprint(w, "User-agent: *\n")` |
|
||||||
|
| `lint.Write(w, p []byte)` | `w.Write(p)` — HTTP response bodies | `lint.Write(w, body)` |
|
||||||
|
| `lint.LogOnErr(f, msg)` | Deferred `Close()` calls | `defer lint.LogOnErr(file.Close, "close file")` |
|
||||||
|
| `lint.LogOnErrCtx(f, ctx, msg)` | `txn.Commit(ctx)` or other ctx funcs | `lint.LogOnErrCtx(txn.Commit, ctx, "commit")` |
|
||||||
|
| `lint.LogOnErrRollback(f, ctx, msg)` | Deferred `txn.Rollback(ctx)` | `defer lint.LogOnErrRollback(txn.Rollback, ctx, "rollback")` |
|
||||||
|
|
||||||
|
**Key rule:** `LogOnErrRollback` silently ignores `"sql: transaction has already been committed or rolled back"` errors, which occur when a deferred rollback fires after a successful commit. Always use it for deferred rollbacks.
|
||||||
|
|
||||||
|
### For DB transactions, use this pattern:
|
||||||
|
|
||||||
|
```go
|
||||||
|
txn, err := db.PGInstance.BobDB.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("begin: %w", err)
|
||||||
|
}
|
||||||
|
defer lint.LogOnErrRollback(txn.Rollback, ctx, "rollback")
|
||||||
|
|
||||||
|
// ... do work ...
|
||||||
|
|
||||||
|
if err := txn.Commit(ctx); err != nil {
|
||||||
|
return fmt.Errorf("commit: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
```
|
||||||
|
|
||||||
|
### For HTTP handlers that render HTML:
|
||||||
|
|
||||||
|
```go
|
||||||
|
if err := renderShim(w, r, errRender(err)); err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("render shim: %v", err), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture Notes
|
||||||
|
|
||||||
|
### Two hosts, one binary
|
||||||
|
|
||||||
|
`main.go` creates two `gorilla/mux` routers and supports three modes via CLI flags:
|
||||||
|
- `-sync` — serve the Sync dashboard
|
||||||
|
- `-report` — serve the RMO public site
|
||||||
|
- `-all` — serve both (default)
|
||||||
|
|
||||||
|
Each host has its own route registration in `sync/routes.go` and `rmo/routes.go`.
|
||||||
|
|
||||||
|
### RMO package — all handlers are dead
|
||||||
|
|
||||||
|
**All** route registrations in `rmo/routes.go` are commented out. The file now only serves the Vue SPA via `static.SinglePageApp("static/gen/rmo")`. During cleanup (May 2026), all handler files were deleted:
|
||||||
|
|
||||||
|
```
|
||||||
|
rmo/compliance.go rmo/error.go rmo/nuisance.go rmo/report.go
|
||||||
|
rmo/district.go rmo/image.go rmo/quick.go rmo/root.go
|
||||||
|
rmo/email.go rmo/mailer.go rmo/notification.go rmo/scss.go
|
||||||
|
rmo/mock.go rmo/status.go rmo/water.go
|
||||||
|
```
|
||||||
|
|
||||||
|
Only `rmo/routes.go` remains. **Do not add new Go template handlers here** — the RMO host is pure Vue SPA now.
|
||||||
|
|
||||||
|
### Sync package — partially live
|
||||||
|
|
||||||
|
Many route registrations in `sync/routes.go` are active. Files deleted during cleanup were those with zero active registrations:
|
||||||
|
|
||||||
|
```
|
||||||
|
sync/admin.go sync/download.go sync/operations.go sync/pool.go
|
||||||
|
sync/cell.go sync/intelligence.go sync/parcel.go sync/radar.go
|
||||||
|
sync/communication.go sync/messages.go sync/planning.go sync/review.go
|
||||||
|
sync/dash.go sync/mock.go sync/notification.go sync/service-request.go
|
||||||
|
sync/signin.go sync/sms.go sync/text.go sync/tile.go
|
||||||
|
```
|
||||||
|
|
||||||
|
### api/ vs resource/ — two handler layers
|
||||||
|
|
||||||
|
The codebase has two HTTP handler patterns:
|
||||||
|
|
||||||
|
1. **`api/`** — route registration (`api/routes.go`) + `http.HandlerFunc` handlers. Handles signin, webhooks (Twilio, VoIP.ms), media uploads, configuration POSTs.
|
||||||
|
|
||||||
|
2. **`resource/`** — typed resource handlers with `List`, `Get`, `Create`, etc. methods. Each resource has a struct embedding `*router` for URI generation. This is the newer, preferred pattern.
|
||||||
|
|
||||||
|
The split is not clean — some `api/` files contain substantial business logic. New handlers should use the `resource/` pattern.
|
||||||
|
|
||||||
|
### DB access — Bob vs Jet
|
||||||
|
|
||||||
|
Two ORMs coexist:
|
||||||
|
- **Bob** (`github.com/Gleipnir-Technology/bob`) — legacy, used by most queries. Models in `db/models/*.bob.go` (103 files).
|
||||||
|
- **Jet** (`db/jet/`) — new, generated queries in `db/query/public/`, `db/query/publicreport/`, `db/query/arcgis/`. Only 3 schemas partially ported.
|
||||||
|
|
||||||
|
The `db.PGInstance` singleton holds both `BobDB` and `PGXPool`. Jet uses PGXPool directly; Bob uses BobDB.
|
||||||
|
|
||||||
|
### db/prepared.go & db/fieldseeker.go
|
||||||
|
|
||||||
|
`db/prepared.go` contains utility functions (`pointOrNull`, `lineOrNull`, `queryStoredProcedure`, etc.) that are **only** called from `db/fieldseeker.go`. That file is **entirely commented out** (`/* ... */`). The 9 unused-prepared-funcs lint warnings are expected — do not delete them unless you're also deleting or uncommenting fieldseeker.go.
|
||||||
|
|
||||||
|
## Lint Cleanup Context (May 2026)
|
||||||
|
|
||||||
|
### What was fixed
|
||||||
|
|
||||||
|
- **errcheck (36→0):** All unchecked error returns eliminated using `lint/` helpers or explicit checks.
|
||||||
|
- **unused (50→9):** ~60 functions/types deleted across ~30 files. Remaining 9 are in `db/prepared.go` (see above).
|
||||||
|
|
||||||
|
### golangci-lint reporting cap
|
||||||
|
|
||||||
|
golangci-lint caps unused reports at **50 items**. During cleanup, each batch of deletions exposed previously hidden items. If you see 50 unused items, there are almost certainly more hidden behind the cap. Delete the visible ones, re-run lint, and repeat.
|
||||||
|
|
||||||
|
### What was NOT fixed (remaining lint categories)
|
||||||
|
|
||||||
|
- **govet (26):** printf format mismatches, copylocks, lostcancel — some are real bugs
|
||||||
|
- **ineffassign (9):** dead assignments that may indicate logic errors
|
||||||
|
- **staticcheck (29):** deprecated `io/ioutil`, redundant returns, error string conventions, comparison always-true bugs
|
||||||
|
|
||||||
|
### Deleted by file count
|
||||||
|
|
||||||
|
| Directory | Files deleted | Reason |
|
||||||
|
|-----------|--------------|--------|
|
||||||
|
| `rmo/` | 15 | All handlers unused — routes commented out |
|
||||||
|
| `sync/` | 17 | Unregistered handlers |
|
||||||
|
| `api/` | 2 | `compliance.go`, `debug.go` — unused handlers |
|
||||||
|
| `platform/` | 2 | `text/db.go`, `dashboard.go`, `publicreport/address.go` |
|
||||||
|
| Other | 1 | `tomtom/` (prior cleanup) |
|
||||||
|
|
||||||
|
## Commit Conventions
|
||||||
|
|
||||||
|
Commits during the cleanup followed a consistent pattern:
|
||||||
|
|
||||||
|
```
|
||||||
|
lint: fix errcheck in api/api.go debug log writes
|
||||||
|
lint: remove unused code from sync/ package
|
||||||
|
```
|
||||||
|
|
||||||
|
Each commit fixes one category of issue in a small set of related files. Build verification (`go build ./...`) was performed before each commit.
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- `CLEANUP.md` — broader cleanup roadmap (Bob→Jet migration, html/ package removal, etc.)
|
||||||
|
- `HISTORY.md` — project history and architectural decisions
|
||||||
|
- `README.md` — administration and build-from-source instructions
|
||||||
Loading…
Add table
Add a link
Reference in a new issue