# Agent Instructions — `royalroadupdates` ## Project Purpose A small, self-contained Go CLI tool that checks RoyalRoad stories from a PocketBase bookmarks collection and reports their last-update times. ## Repository Layout ``` . ├── cmd/royalroadupdates/ Entry point and integration tests │ ├── main.go - CLI bootstrap, flag parsing (--json) │ └── e2e_test.go - End-to-end test: compiles binary, runs it, │ asserts JSON shape and entry count ├── internal/ │ ├── pocketbase/ │ │ └── client.go - Authenticates and queries the │ │ /api/collections/bookmarks/records endpoint. │ │ Accepts filter, perPage, and field params. │ │ Returns a list of structs with URL fields. │ ├── royalroad/ │ │ └── scraper.go - Given a URL, fetches the page, confirms it │ │ is a RoyalRoad fiction page, extracts the │ │ JSON-LD script block, parses name and │ │ dateModified into a struct. │ │ Returns zero-value + error for any failure │ │ so the caller can skip silently. │ ├── formatter/ │ │ ├── table.go - Renders []Story as a plain-text aligned │ │ │ table with relative-time "Last Update" │ │ │ column. │ │ ├── json.go - Renders []Story as JSON (used with --json). │ │ └── relative.go - Computes "X minutes/hours/days/months/years │ │ ago" strings from time.Time. │ └── app.go - Orchestrates the full pipeline: │ 1. Load credentials (compile-time vars). │ 2. Query PocketBase for URLs. │ 3. Iterate URLs sequentially. │ 4. Collect valid RoyalRoad results. │ 5. Sort by dateModified descending. │ 6. Dispatch to appropriate formatter. ├── .env - Real credentials (gitignored). ├── .env.example - Template showing required env vars. ├── .gitignore - Must include .env and the built binary. ├── Makefile - All build, test, and check targets. ├── go.mod - Standard Go module file. └── go.sum - Standard Go checksum file. ``` ## Code Style Rules - **Formatting:** All Go source files must pass `gofmt`. The `make check` target will reject any diff. - **Imports:** Group standard library first, then third-party (none expected), then local/internal. `goimports` ordering is acceptable. - **Naming:** Exported identifiers use PascalCase; unexported use camelCase. Acronyms like HTTP, URL, JSON keep all-caps when exported (`FetchURL`, `ParseJSON`). - **Error handling:** Always check errors explicitly. Do not swallow errors silently unless the design calls for silent skipping (RoyalRoad per-item failures). Return errors wrapped with `fmt.Errorf("...: %w", err)` for context. - **Comments:** Every exported type and function has a concise GoDoc comment. Internal helpers may omit comments if their purpose is obvious from the name. - **No globals:** Avoid package-level mutable state. Pass dependencies (HTTP client, configuration) as struct fields or constructor parameters. - **HTTP client reuse:** Create one `http.Client` with a configured timeout and share it across requests. Do not use `http.DefaultClient` unwrapped. ## Build Rules - **No local Go installation.** Use `make` targets which wrap `docker run --rm -v $(pwd):/src -w /src golang:1.22`. - **Credential injection:** The Makefile reads `.env` and passes values as `-ldflags '-X main.PB_URL=... -X main.PB_USER=... -X main.PB_PASSWORD=...'`. - **Output:** The compiled binary is written to `./royalroadupdates` in the repository root. - **Targets:** - `make build` — compile the binary. - `make check` — full quality loop (format, vet, test, e2e test). - `make clean` — remove the compiled binary. ## Testing Rules - **Unit tests:** Cover pure helpers (e.g., `relative.go`, URL filtering logic) with table-driven tests. Use `t.Parallel()` where independent. - **End-to-end test (`e2e_test.go`):** - Uses the `.env` credentials and hits real services. - Compiles the binary via `os/exec` so it tests the actual build path. - Runs `./royalroadupdates --json`. - Assertions: 1. Exit code 0. 2. Stderr is empty. 3. Stdout is valid JSON. 4. JSON is a non-empty array. 5. Array contains at least 3 entries. 6. Each entry has a non-empty `name` and a parseable `date`. - Since this test hits live services, it is inherently non-deterministic in data content but deterministic in shape. Treat network/Cloudflare flakiness as a signal to tune delays rather than a code defect. - **Fix-and-rerun cycle:** If `make check` fails at any step, fix the root cause and rerun `make check` until all gates pass.