From 033f4f3aa73b70a32a00b6bc8b84a8bfda6272d0 Mon Sep 17 00:00:00 2001 From: phatpham9 Date: Mon, 22 Jun 2026 19:26:56 +0700 Subject: [PATCH] docs: add AGENTS.md and architecture/deployment docs for AI agents Adds AGENTS.md as the canonical cross-tool source of truth (read natively by Claude Code, Codex, Copilot, and Cursor), a thin CLAUDE.md pointing at it, and docs/ARCHITECTURE.md + docs/DEPLOYMENT.md as an in-repo "wiki" so agents and contributors don't have to re-derive package layout and release flow from scratch. Co-Authored-By: Claude Sonnet 4.6 --- AGENTS.md | 50 ++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 7 +++++++ docs/ARCHITECTURE.md | 40 +++++++++++++++++++++++++++++++++++ docs/DEPLOYMENT.md | 38 +++++++++++++++++++++++++++++++++ 4 files changed, 135 insertions(+) create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/DEPLOYMENT.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..5e47fc5 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,50 @@ +# AGENTS.md + +## Project overview + +`feedreader` is a small Go service that aggregates Hacker News, GitHub Trending, +Hugging Face Papers Trending, and alphaXiv into a private, server-rendered feed +reader backed by SQLite. It ships as a Docker container. + +## Build & run + +```bash +go run ./cmd/feedreader serve --host 127.0.0.1 --port 8080 +``` + +Configuration is env-var driven — see [internal/config/config.go](internal/config/config.go) +for the full list and defaults rather than duplicating it here. + +## Test + +```bash +gofmt -l $(git ls-files '*.go') # must print nothing +go test ./... +``` + +Both checks run in CI ([.github/workflows/ci.yml](.github/workflows/ci.yml)) on every PR. + +## Code style + +`gofmt` is the only formatter. No additional linter is configured. + +## Architecture + +See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the package-by-package layout. + +## Adding a new feed source + +Implement the `sources.Source` interface and register it in `sources.Build()` +in [internal/sources/sources.go](internal/sources/sources.go). + +## Security considerations + +There is no authentication on the HTTP API surface — this is designed for +private/personal deployment behind your own network or reverse proxy. Do not +add public write endpoints beyond the existing `POST /api/refresh`. + +## Commit / PR conventions + +Conventional Commits (`feat:`, `fix:`, `chore:`, ...) — releases are automated +via [release-please](.github/workflows/release-please.yml) based on commit +messages. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..c7a05fe --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,7 @@ +@AGENTS.md + +## Claude-specific notes + +- Don't read or modify files under `data/` (gitignored local SQLite DBs). +- Prefer `go test ./internal//...` over a full `go test ./...` run + when iterating on a single package. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..6c80ad4 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,40 @@ +# Architecture + +## Packages + +| Package | Responsibility | +| --- | --- | +| [`cmd/feedreader`](../cmd/feedreader) | CLI entrypoint: `serve`, `fetch`, `healthcheck` subcommands. | +| [`internal/config`](../internal/config) | Env-var configuration loading and defaults. | +| [`internal/db`](../internal/db) | SQLite bootstrap: schema DDL, WAL/pragma setup. | +| [`internal/domain`](../internal/domain) | Plain data types shared across packages (`FeedItem`, `SyncState`, `CardView`, ...). | +| [`internal/repository`](../internal/repository) | Persistence: upserts, sync-state tracking, feed queries. | +| [`internal/service`](../internal/service) | Refresh orchestration, scheduler, card-building/display logic. | +| [`internal/sources`](../internal/sources) | Upstream source adapters (Hacker News, GitHub Trending, Hugging Face Papers, alphaXiv). | +| [`internal/web`](../internal/web) | HTTP routes, SSR page rendering, JSON APIs. | + +## Data model and refresh behavior + +- Items are upserted by `(source, external_id)`. A refresh never deletes + existing rows — a failed fetch just records the failure in `sync_state` and + leaves prior data in place. +- The original `published_at` is preserved across re-fetches via + `coalesce(items.published_at, excluded.published_at)` — a source that later + starts reporting a different date for the same item doesn't reorder it. +- `internal/repository/sqlite.go`'s `ListFeedItems` sorts and paginates **in + application memory**, not in SQL. This is intentional: total item count + across all 4 sources is small (a few hundred rows), so the simplicity of one + in-memory comparator outweighs the complexity of expressing the same + fallback-ordering (published date, else first-seen date, else source rank) + in SQL. +- The scheduler in `internal/service/service.go` wakes on N-hour wall-clock + boundaries in `Asia/Ho_Chi_Minh` (UTC+7, no DST) — default hourly via + `FEEDREADER_REFRESH_INTERVAL_HOURS`. It does not refresh immediately on + startup. + +## Frontend + +Server-rendered HTML (`web/templates/index.html`) plus vanilla JS +(`web/static/app.js`) — no frontend build step. The service worker +(`web/static/service-worker.js`) caches the app shell and visited +`/api/items` responses for offline reuse. diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md new file mode 100644 index 0000000..bca7a2f --- /dev/null +++ b/docs/DEPLOYMENT.md @@ -0,0 +1,38 @@ +# Deployment (Docker) + +This is the only supported deployment for this repo. A Cloudflare Workers +port lives in a separate repo, `boringcode-dev/feedreader-edge`. + +## Build + +```bash +docker build -t feedreader . +``` + +CI publishes multi-arch images (`linux/amd64`, `linux/arm64`) to +`ghcr.io/boringcode-dev/feedreader` on `v*.*.*` tag pushes +([.github/workflows/cd.yml](../.github/workflows/cd.yml)). + +## Run + +```bash +docker run --rm -p 8080:8080 -v $(pwd)/data:/data feedreader +``` + +The `/data` volume holds the SQLite database (`FEEDREADER_DB_PATH`, default +`/data/feedreader.db` inside the container). Losing this volume loses all +fetched history; sources are re-fetched from scratch on the next refresh. + +## Configuration + +See the env var table in [README.md](../README.md#configuration) — this doc +intentionally doesn't duplicate it. + +## Release flow + +1. Merge to `main` — CI runs `gofmt -l` + `go test ./...`. +2. [release-please](../.github/workflows/release-please.yml) opens a release + PR based on Conventional Commit messages; merging it tags a version and + updates `CHANGELOG.md`. +3. The tag push triggers `cd.yml`, which builds and pushes the image, then + appends container-pull instructions to the GitHub release notes.