5.2 KiB
5.2 KiB
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 - Queries the public PocketBase collection endpoint.
│ │ Accepts 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 endpoint URL from compile-time var.
│ 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 endpoint URL (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. Themake checktarget will reject any diff. - Imports: Group standard library first, then third-party (none expected),
then local/internal.
goimportsordering 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.Clientwith a configured timeout and share it across requests. Do not usehttp.DefaultClientunwrapped.
Build Rules
- No local Go installation. Use
maketargets which wrapdocker run --rm -v $(pwd):/src -w /src golang:1.22. - Endpoint injection: The Makefile reads
.envand passes the value as-ldflags '-X main.PBURL=...'. - Output: The compiled binary is written to
./royalroadupdatesin 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. Uset.Parallel()where independent. - End-to-end test (
e2e_test.go):- Uses the
.envcredentials and hits real services. - Compiles the binary via
os/execso it tests the actual build path. - Runs
./royalroadupdates --json. - Assertions:
- Exit code 0.
- Stderr is empty.
- Stdout is valid JSON.
- JSON is a non-empty array.
- Array contains at least 3 entries.
- Each entry has a non-empty
nameand a parseabledate.
- 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.
- Uses the
- Fix-and-rerun cycle: If
make checkfails at any step, fix the root cause and rerunmake checkuntil all gates pass.