···11+# CLAUDE.md
22+33+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
44+55+## What This Is
66+77+OpenPDS is a directory of AT Protocol PDS servers with open registration. It runs on Val Town as a serverless app with two entry points: an HTTP server and a daily cron job. Data comes from [mary-ext/atproto-scraping](https://github.com/mary-ext/atproto-scraping) and gets enriched with health, geo-IP, version, and trust metadata.
88+99+## Commands
1010+1111+```bash
1212+deno task check # Type check entry points
1313+deno task test # Run tests (--allow-net --allow-read --allow-import)
1414+deno fmt # Format all files
1515+deno lint # Lint all .ts files
1616+deno task deploy # fmt + lint + check + test + vt push (full deploy pipeline)
1717+```
1818+1919+Do **not** use `vt push` directly — always use `deno task deploy` which runs quality checks first.
2020+2121+## Architecture
2222+2323+**Runtime:** Deno on Val Town. **Framework:** Hono. **Database:** Val Town SQLite. **Imports:** via `esm.sh` (not npm/node_modules).
2424+2525+### Entry Points
2626+2727+- `backend/index.http.ts` — HTTP trigger. Hono app serving HTML directory (`/`) and JSON API (`/api/servers`). Runs migrations on first request.
2828+- `cron/refresh.cron.ts` — Cron trigger. Daily refresh: fetch PDS list → sync to DB → fetch latest version → enrich batch of servers.
2929+3030+### Data Flow (Cron)
3131+3232+1. `pds-fetcher.ts` downloads state.json (~2900 PDSes)
3333+2. Each PDS upserted to SQLite with open/closed status
3434+3. `version-checker.ts` gets latest PDS version from GitHub API
3535+4. `pds-enricher.ts` enriches a batch (configurable, default 20): DNS resolve → health check → describeServer → listRepos user count → geo-IP batch lookup
3636+5. Results written back to DB via `updateEnrichment()`
3737+3838+### Module Layout
3939+4040+- `backend/database/` — `migrations.ts` (schema), `queries.ts` (all SQL operations, row-to-object mapping)
4141+- `backend/routes/` — `pages.ts` (server-rendered HTML), `api.ts` (JSON endpoint)
4242+- `backend/services/` — Pure async functions: `pds-fetcher`, `pds-enricher`, `geo-resolver`, `version-checker`
4343+- `shared/` — `types.ts` (all TypeScript interfaces), `constants.ts` (config values, country flag helper)
4444+- `cron/` — Scheduled job orchestration
4545+4646+### Trust Score
4747+4848+5 boolean signals, each worth 20%: contact email, ToS, privacy policy, active users (>5), latest PDS version. Calculated in SQL via CASE expressions.
4949+5050+## Val Town Specifics
5151+5252+- Exports use Val Town conventions: HTTP files export `app.fetch`, cron files export a default async function
5353+- SQLite accessed via `https://esm.town/v/stevekrouse/sqlite`
5454+- Schema changes: create new table names (append `_2`, `_3`) rather than ALTER TABLE
5555+- No `Deno` namespace in `shared/` code (must work in browser context too)
5656+- Secrets via `Deno.env.get()`, never hardcoded
5757+5858+## Conventions
5959+6060+- Database columns: `snake_case`. TypeScript code: `camelCase`. `rowToServer()` maps between them.
6161+- Per-request timeouts via `AbortSignal.timeout()` (5s for PDS endpoints, 10s for geo batch)
6262+- Batch operations use `Promise.allSettled()` for partial-failure resilience
6363+- HTML output uses custom `esc()` function for XSS prevention