|
All checks were successful
secrets-guard / encrypted (pull_request) Successful in 6s
shell-lint / shellcheck (pull_request) Successful in 8s
PR build (required check) / changes (pull_request) Successful in 19s
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / build-backend (pull_request) Successful in 40s
PR build (required check) / gate (pull_request) Successful in 4s
Reported from #general: "@Thermograph Vilnius, Lithuania" came back "I don't track a city called 'Vilnius, Lithuania' yet." Vilnius has been in cities.json the whole time, with country "Lithuania" sitting right there in the record. The resolver matched the query against `name` alone, exact-or-prefix, so the comma form could never match anything and the country field was never consulted. The same message also showed the two neighbouring failures: "Vilinus" (one transposition) fell through to the same refusal, which reads as "we've never heard of Vilnius" rather than "you typed it wrong"; and "Tell me about weather in lithuania" was quoted back verbatim as a city we don't track, which is what made it look broken rather than merely unmatched. So the resolver now goes, in descending order of confidence: exact name honouring a "City, Country/Region" qualifier; the same ignoring an unrecognised qualifier (better to grade Paris and name it in the reply than refuse over "Paris, Wherever"); prefix; close-enough name for typos; and finally a city or country named somewhere inside a sentence, so "weather in lithuania" lands on Vilnius. Accents fold both ways, since people type Zurich and Sao Paulo far more often than Zürich and São Paulo. Free-text matching is whole-word and ignores names under four characters, so ordinary chatter doesn't turn into a weather report -- "how are you today" still resolves to nothing. And the unmatched reply no longer reads a whole sentence back as a place name. This fixes /grade identically; it shared the resolver and the same bug. Claude-Session: https://claude.ai/code/session_015Z1ebLbhUxeZ9ozpNrVTCP |
||
|---|---|---|
| .claude | ||
| .forgejo/workflows | ||
| backend | ||
| docs/onboarding | ||
| frontend | ||
| infra | ||
| observability | ||
| .gitignore | ||
| CLAUDE.md | ||
| CUTOVER-NOTES.md | ||
| Makefile | ||
| README.md | ||
thermograph
The Thermograph monorepo — the split repos reunified (2026-07-22) with full history via subtree merges, while keeping everything the split was actually for: per-domain images, per-domain deploys, and an async FE/BE contract.
Domains
| Dir | What | CI |
|---|---|---|
backend/ |
FastAPI graded-climate API, accounts, notifications (Discord bot, push, mail), data pipeline | build-push → image jinemi/thermograph/backend; deploy |
frontend/ |
Public client: static JS/CSS + SSR pages | same build-push / deploy workflows, matrixed by domain; image jinemi/thermograph/frontend |
infra/ |
Compose (dev, on vps1) + two Swarm stacks co-resident on vps2 (beta, prod), deploy scripts, terraform, SOPS secrets vault, ops cron | infra-sync (host checkout + secrets render), secrets-guard, ops-cron |
observability/ |
Loki + Grafana + Alloy stack | observability-validate |
thermograph-docs deliberately stays its own repo (ADRs + runbooks, no
build artifacts, different change cadence).
New here?
docs/onboarding/ is the developer onboarding
path: orientation, verified local-setup recipes, a per-domain deep dive, the
cross-service contracts that break silently, the release flow, and a list of
which docs in this repo are currently stale.
How CI stays decoupled
Every workflow in .forgejo/workflows/ is path-filtered to its domain: a
push touching only frontend/** builds/deploys nothing else. Images stay
separate (jinemi/thermograph/backend, jinemi/thermograph/frontend, each tagged
sha-<12hex>), deploys stay per-service (infra/deploy/deploy.sh SERVICE=backend|frontend|all), and the API version contract
(GET /api/version, PAYLOAD_VER) still lets FE and BE ship out of lockstep.
The one intentionally coupled piece is pr-build.yml: a single always-running
gate required check that builds only the domains a PR touches (a
path-filtered required check would deadlock auto-merge).
Branch model (unchanged from the split era): PRs → dev, main → beta,
release → prod. Infra isn't environment-staged the same way app images are:
beta's and prod's checkouts (both on vps2) track main; dev's checkout (on
vps1) tracks dev itself, since it's the one environment that isn't a
rehearsal for something downstream.
Before pointing anything live at this repo, read CUTOVER-NOTES.md.