Feature 01 task 6: AGENTS.md, CLAUDE.md, README and architecture docs #7
@@ -0,0 +1,143 @@
|
||||
# AGENTS.md — MealQuest agent guide
|
||||
|
||||
This is the authoritative guide for every agent (and human) working in this
|
||||
repository. `CLAUDE.md` only imports this file. Where this guide and a general
|
||||
habit disagree, this guide wins.
|
||||
|
||||
MealQuest is a single Go service with an embedded React PWA for one
|
||||
household. The architecture of record is [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md);
|
||||
product requirements are in [design/MealQuest Requirements.md](design/MealQuest%20Requirements.md).
|
||||
|
||||
## House rules
|
||||
|
||||
1. **Fail fast.** Invalid configuration, input or state is an error reported
|
||||
immediately and clearly — the service refuses to start on bad config.
|
||||
2. **No silent fallbacks.** Never substitute a default, retry quietly, or
|
||||
swallow an error to "keep going". If something is missing, say so.
|
||||
3. **One way to do a thing.** No parallel code paths, alternative settings or
|
||||
"just in case" options. Pick one approach and use it everywhere.
|
||||
4. **No new third-party dependency without owner approval.** Use only the
|
||||
approved list below; anything else needs the owner's explicit yes first.
|
||||
5. **Never use `tea`.** The Gitea CLI on this host is authenticated as the
|
||||
human owner. Agents act only through their own Gitea identities.
|
||||
|
||||
## Approved dependencies (from CTXD)
|
||||
|
||||
Backend:
|
||||
|
||||
- Go 1.25, built with `CGO_ENABLED=0`.
|
||||
- `github.com/go-chi/chi/v5` — HTTP router.
|
||||
- `modernc.org/sqlite` — CGO-free SQLite driver.
|
||||
- `sqlc` — query code generation (dev tool only).
|
||||
- `github.com/coreos/go-oidc/v3` — OIDC (Authentik).
|
||||
- `golang.org/x/oauth2` and `golang.org/x/oauth2/google`.
|
||||
- `github.com/SherClockHolmes/webpush-go` — Web Push.
|
||||
- `golang.org/x/image` — photo processing.
|
||||
- `github.com/oklog/ulid/v2` — IDs.
|
||||
- Standard library `log/slog` and `net/http`.
|
||||
- **Not allowed:** LLM vendor SDKs, Google API SDKs (call the HTTP APIs
|
||||
directly), goose, golang-migrate.
|
||||
|
||||
Frontend:
|
||||
|
||||
- React 19, TypeScript, Vite.
|
||||
- React Router 7, TanStack Query 5.
|
||||
- `vite-plugin-pwa`.
|
||||
- `lucide-react` icons at stroke width 2.75.
|
||||
- CSS Modules over the design tokens in
|
||||
[tokens.css](design/MealQuest%20UI%20Designs/design_handoff_mealquest/tokens.css).
|
||||
**No Tailwind, no Framer Motion, no hard-coded hex colours.**
|
||||
- Self-hosted fonts: Bagel Fat One and Figtree.
|
||||
- Tests: Vitest, Testing Library, jsdom.
|
||||
|
||||
## Quality floor
|
||||
|
||||
Every change must pass all of these before review (CI in
|
||||
[.gitea/workflows/ci.yml](.gitea/workflows/ci.yml) enforces them):
|
||||
|
||||
```sh
|
||||
gofmt -l . # must print nothing
|
||||
go vet ./...
|
||||
go test -race ./...
|
||||
cd web && npm run typecheck && npm test && npm run build
|
||||
```
|
||||
|
||||
And the build entry points in the [Makefile](Makefile) and [Dockerfile](Dockerfile):
|
||||
|
||||
```sh
|
||||
make test
|
||||
make build
|
||||
docker build .
|
||||
```
|
||||
|
||||
Lint proves code parses, not that it works: where a change has runtime
|
||||
behaviour, exercise it (hit the endpoint, render the page).
|
||||
|
||||
## Repository layout
|
||||
|
||||
| Path | Purpose |
|
||||
| --- | --- |
|
||||
| `cmd/server` | Entry point. Wiring only — no business logic. |
|
||||
| `internal/config` | `MEALQUEST_*` environment config; secrets from `*_FILE`. |
|
||||
| `internal/server` | HTTP server, routes, `/health`. |
|
||||
| `internal/frontend` | `go:embed` of the built PWA with SPA fallback. |
|
||||
| `internal/*` (planned) | `auth`, `db`, `store`, `secretbox`, `household`, `dinners`, `recipes`, `ai/llm`, `ai/harness`, `ai/skills`, `suggest`, `plan`, `shopping`, `ideas`, `gcal`, `notify`, `push`, `chat`, `jobs`, `scheduler`. |
|
||||
| `migrations/` | SQL migrations (planned). |
|
||||
| `web/` | React PWA source. |
|
||||
| `deploy/` | Deployment files (planned). |
|
||||
| `docs/` | Project documentation, including [ARCHITECTURE.md](docs/ARCHITECTURE.md). |
|
||||
| `design/` | Requirements and UI handoff. **Read-only reference** — do not edit. |
|
||||
| `Dockerfile` | Multi-stage image build, at the repository root. |
|
||||
| `Makefile` | `dev`, `test`, `build`, `docker`. |
|
||||
| `.gitea/workflows/ci.yml` | CI quality floor. |
|
||||
|
||||
Design references:
|
||||
|
||||
- [design/MealQuest Requirements.md](design/MealQuest%20Requirements.md)
|
||||
- [design/MealQuest UI Designs/design_handoff_mealquest/README.md](design/MealQuest%20UI%20Designs/design_handoff_mealquest/README.md)
|
||||
- [design/MealQuest UI Designs/design_handoff_mealquest/tokens.css](design/MealQuest%20UI%20Designs/design_handoff_mealquest/tokens.css)
|
||||
|
||||
## Branches, pull requests and merges
|
||||
|
||||
- Integration branch is `dev`. `main` is the release branch.
|
||||
- **Feature branches:** `feature/<feature-uuid>-<slug>`, branched from `dev`.
|
||||
- **Task branches:** `task/<slug>_<task-uuid>`, branched from the feature branch.
|
||||
- Task pull requests **squash-merge into the feature branch**.
|
||||
- The feature pull request merges into `dev`.
|
||||
- **Only `overseer` merges `dev` → `main`** (a release).
|
||||
- Never push directly to `dev` or `main`.
|
||||
- Authors never review, approve, merge or close their own work.
|
||||
- Never store credentials in the repository; environment or ignored files only.
|
||||
|
||||
## Where context lives and where work is tracked
|
||||
|
||||
- **Context:** CTXD project `mealquest` — client guide at
|
||||
<https://ctxd.cubecraftcreations.com>. Project documents: `CONTEXT.MD`
|
||||
(architecture), `DECISIONS.MD` (decision log, e.g. D-021), `RUNBOOKS.MD`,
|
||||
`PROMPTS.MD`, `GLOSSARY.MD`.
|
||||
- **Work tracking:** Maestro at <https://maestro.cubecraftlabs.com>,
|
||||
workspace **Maniacs-Mansion** → project **MealQuest**. Every change has a
|
||||
task with a verifiable done-condition.
|
||||
|
||||
## Vocabulary (GLOSSARY)
|
||||
|
||||
Use these product terms in code names, UI copy, docs and commit messages. Do
|
||||
not invent synonyms.
|
||||
|
||||
| Term | Meaning |
|
||||
| --- | --- |
|
||||
| **Regulars** | Dinners the household makes routinely. |
|
||||
| **Side quest** | An AI-suggested new dinner to try. |
|
||||
| **Quest log** | The to-try pool of accepted Side quests, scheduled later. |
|
||||
| **Try it / Pass** | Accept (swipe right) / decline (swipe left) a suggestion. |
|
||||
| **Rotation check** / **"Keep it in rotation?"** | The prompt after a Quest-log dinner is tried, asking whether it becomes a Regular. Promotion is always the household's choice, never automatic. |
|
||||
| **Supply run** | The current week's shopping list, grouped by dinner. |
|
||||
| **Supply archive** | The blog-style history of past shopping lists (by week, month, year). |
|
||||
| **Draft board** | The mobile weekly plan layout the AI drafts for the household to adjust. |
|
||||
| **Rebuild week** | Ask the AI to redraft the week's plan. |
|
||||
| **Push to Calendar** | Push the plan one-way to Google Calendar. |
|
||||
| **Quick dinner** | Cooked in a crock pot **or** 30 minutes or less from prep to table. |
|
||||
| **Cook from what I have** | Ideas from a list of ingredients on hand. |
|
||||
| **Kitchen reading mode** | Tablet recipe view: large text, ingredient checkboxes, step-by-step. |
|
||||
| **"Why this?"** | The reason shown for a suggestion, referencing the household's Regulars. |
|
||||
| **Our kitchen / Shared household** | The single shared household identity; no per-person profiles. |
|
||||
@@ -0,0 +1,50 @@
|
||||
# MealQuest
|
||||
|
||||
MealQuest is a mobile-first Progressive Web App for weekly dinner planning for
|
||||
one household. The household lists the dinners it makes regularly, an AI
|
||||
assistant suggests new dinners to try (swipe "Try it" or "Pass"), and the app
|
||||
builds a weekly plan, pushes it to Google Calendar, and generates a shopping
|
||||
list grouped by dinner, kept as a browsable weekly history. It ships as a
|
||||
single Go service with the React PWA embedded in the binary.
|
||||
|
||||
> Status: this repository is the Feature 01 scaffold (Go service with
|
||||
> `/health`, embedded PWA skeleton, Makefile, Dockerfile, CI). The planned
|
||||
> application is described in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
||||
|
||||
## Quick start
|
||||
|
||||
Requirements: Go 1.25 and Node.js 22.
|
||||
|
||||
```sh
|
||||
(cd web && npm ci)
|
||||
mkdir -p ~/.mealquest-dev # outside the repository: it holds a secret
|
||||
head -c 32 /dev/urandom | base64 > ~/.mealquest-dev/secret_key
|
||||
export MEALQUEST_HTTP_ADDR=:8080
|
||||
export MEALQUEST_PUBLIC_URL=http://localhost:5173
|
||||
export MEALQUEST_DATA_DIR="$HOME/.mealquest-dev"
|
||||
export MEALQUEST_SHUTDOWN_TIMEOUT=10s
|
||||
export MEALQUEST_SECRET_KEY_FILE="$HOME/.mealquest-dev/secret_key"
|
||||
make dev
|
||||
```
|
||||
|
||||
`make dev` runs the Go server on `:8080` and the Vite dev server on `:5173`
|
||||
(which proxies `/api`, `/auth` and `/media` to `:8080`). Open
|
||||
http://localhost:5173. Configuration is environment-only and fails fast:
|
||||
every variable above is required, and secrets are read only from files named
|
||||
by `*_FILE` variables.
|
||||
|
||||
## Common commands
|
||||
|
||||
| Command | What it does |
|
||||
| -------------- | --------------------------------------------------------------- |
|
||||
| `make dev` | Go server plus Vite dev server. |
|
||||
| `make test` | `gofmt` check, `go vet`, `go test -race`, web typecheck and tests. |
|
||||
| `make build` | Build the PWA, embed it, build the static binary `bin/mealquest`. |
|
||||
| `make docker` | Build the container image from the root `Dockerfile`. |
|
||||
|
||||
## Where to read next
|
||||
|
||||
- [AGENTS.md](AGENTS.md) — house rules, approved dependencies, conventions.
|
||||
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — architecture of record.
|
||||
- [design/MealQuest Requirements.md](design/MealQuest%20Requirements.md) — product requirements.
|
||||
- [design/MealQuest UI Designs/design_handoff_mealquest/README.md](design/MealQuest%20UI%20Designs/design_handoff_mealquest/README.md) — UI handoff.
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
# MealQuest architecture
|
||||
|
||||
This mirrors the architecture section of `CONTEXT.MD` in the CTXD project
|
||||
`mealquest`, which is the source of record; if the two disagree, CTXD wins and
|
||||
this file should be updated. It describes the **planned** application. The
|
||||
repository today is the Feature 01 scaffold: `cmd/server`, `internal/config`,
|
||||
`internal/server` (`/health`), `internal/frontend` (embedded PWA with SPA
|
||||
fallback), the `web/` skeleton, `Makefile`, `Dockerfile` and CI.
|
||||
|
||||
Decision numbers (D-NNN) refer to `DECISIONS.MD` in CTXD.
|
||||
|
||||
## Topology
|
||||
|
||||
```
|
||||
PWA (phone / tablet / desktop)
|
||||
│ HTTPS
|
||||
▼
|
||||
Traefik v3.6 — 10.60.1.33
|
||||
file route: mealquest-ccl.yml
|
||||
│
|
||||
▼
|
||||
docker compose project "mealquest" — 10.60.1.14
|
||||
├── mealquest Go service + embedded PWA, port 8090
|
||||
├── searxng sidecar for recipe search
|
||||
└── Litestream SQLite replication → Synology NAS 10.60.1.50
|
||||
```
|
||||
|
||||
One container serves the API, the auth flow, media and the PWA. SQLite lives
|
||||
on the host volume and is continuously replicated by Litestream to the NAS.
|
||||
|
||||
## Repository layout
|
||||
|
||||
| Path | Purpose |
|
||||
| --- | --- |
|
||||
| `cmd/server` | Entry point; wiring only. |
|
||||
| `internal/config` | Env-only `MEALQUEST_*` config; secrets from `*_FILE`. |
|
||||
| `internal/server` | HTTP server, router, middleware. |
|
||||
| `internal/frontend` | Embedded PWA, SPA fallback. |
|
||||
| `internal/auth` | Authentik OIDC, sessions, CSRF. |
|
||||
| `internal/db`, `internal/store` | SQLite connections, sqlc queries. |
|
||||
| `internal/secretbox` | AES-256-GCM encryption for `_ct` columns. |
|
||||
| `internal/household`, `internal/dinners`, `internal/recipes` | Domain. |
|
||||
| `internal/ai/llm`, `internal/ai/harness`, `internal/ai/skills` | AI provider adapters, skill harness, skills. |
|
||||
| `internal/suggest`, `internal/plan`, `internal/shopping`, `internal/ideas` | Feature services. |
|
||||
| `internal/gcal`, `internal/notify`, `internal/push`, `internal/chat` | Integrations. |
|
||||
| `internal/jobs`, `internal/scheduler` | Background jobs and schedules. |
|
||||
| `migrations/` | SQL migrations (embedded; no goose/golang-migrate). |
|
||||
| `web/` | React PWA. |
|
||||
| `deploy/` | Compose, Traefik route, Litestream config. |
|
||||
| `docs/` | Documentation. |
|
||||
| `design/` | Requirements and UI handoff (read-only reference). |
|
||||
|
||||
## Backend stack and conventions
|
||||
|
||||
- Go 1.25, `CGO_ENABLED=0`, a single static binary.
|
||||
- Router: `go-chi/chi/v5`. Logging: `log/slog` (structured).
|
||||
- SQLite via `modernc.org/sqlite`, with `journal_mode=WAL`,
|
||||
`foreign_keys=ON` and `busy_timeout`. **One writer connection plus a read
|
||||
pool.** Queries generated by `sqlc`.
|
||||
- IDs are ULIDs (`oklog/ulid/v2`).
|
||||
- Timestamps are stored in UTC; local calendar dates are `YYYY-MM-DD` strings
|
||||
in the household timezone.
|
||||
- Configuration is environment-only (`MEALQUEST_*`). Secrets come only from
|
||||
files named by `*_FILE` variables. Missing or invalid config stops startup.
|
||||
- Error responses always have the body
|
||||
`{"error":{"code":"…","message":"…","retryable":false}}`.
|
||||
|
||||
## Authentication
|
||||
|
||||
- Authentik OIDC; the backend is a **confidential client** (authorization
|
||||
code flow handled server-side with `coreos/go-oidc/v3` and `x/oauth2`).
|
||||
- Session cookie `__Host-mealquest_session` (Secure, HttpOnly, path `/`).
|
||||
- Unsafe requests require the CSRF header `X-MealQuest-CSRF`.
|
||||
- Access requires membership of the Authentik group `mealquest-admin`.
|
||||
There is a **single role**: one shared household, no per-person profiles.
|
||||
|
||||
## Data model
|
||||
|
||||
Columns ending in `_ct` hold AES-256-GCM ciphertext (via `secretbox`).
|
||||
|
||||
| Table | Holds |
|
||||
| --- | --- |
|
||||
| `household_settings` | Household name, timezone, preferences. |
|
||||
| `recipes` | Dinners: name, method, minutes, difficulty, source, lifecycle status. |
|
||||
| `recipe_ingredients` | Ingredients with quantities per recipe. |
|
||||
| `recipe_steps` | Ordered steps per recipe. |
|
||||
| `photos` | Cached recipe photos. |
|
||||
| `dinners` | Dinner records and lifecycle state. |
|
||||
| `suggestion_batches` | One AI suggestion run. |
|
||||
| `suggestions` | Side quests with reason ("Why this?") and Try it / Pass status. |
|
||||
| `week_plans` | One plan per Monday-start week; draft or synced. |
|
||||
| `plan_days` | The dinner for each day of a week plan. |
|
||||
| `rotation_reviews` | Rotation check answers ("Keep it in rotation?"). |
|
||||
| `shopping_lists` | One Supply run per week; frozen into the Supply archive after the week. |
|
||||
| `shopping_groups` | Items grouped by dinner. |
|
||||
| `shopping_items` | Items, quantity, needed / have-it. |
|
||||
| `ai_settings` | Provider, model, temperature, system prompt, API key (`_ct`). |
|
||||
| `recipe_search_settings` | Recipe search configuration (searxng). |
|
||||
| `integrations_google` | Google Calendar OAuth tokens (`_ct`) and calendar id. |
|
||||
| `chat_settings` | Google Chat webhook URL (`_ct`). |
|
||||
| `notification_prefs` | Per-event push / Chat toggles, reminder time. |
|
||||
| `push_subscriptions` | Web Push subscriptions. |
|
||||
| `notification_outbox` | Pending notifications for delivery. |
|
||||
| `jobs` | Durable background job queue. |
|
||||
| `schedule_runs` | Record of scheduled runs (idempotency). |
|
||||
| `ai_runs` | Every AI skill call: inputs, outputs, validation, timing. |
|
||||
| `sessions` | Server-side sessions. |
|
||||
|
||||
## Dinner lifecycle
|
||||
|
||||
```
|
||||
suggested ──Try it──▶ quest_log ──cooked──▶ tried ──Rotation check──▶ regular
|
||||
│ └────────────────────▶ archived
|
||||
└──Pass──▶ declined (not suggested again)
|
||||
```
|
||||
|
||||
Promotion to Regular is **never automatic** (D-021): it happens only when the
|
||||
household answers "Keep it in rotation?".
|
||||
|
||||
## API surface
|
||||
|
||||
- JSON API under `/api/v1`, using the error body above for every failure.
|
||||
- `/auth/*` — OIDC login, callback, logout.
|
||||
- `/media/*` — cached photos.
|
||||
- `/health` — liveness.
|
||||
- Everything else serves the embedded PWA with SPA fallback.
|
||||
|
||||
## AI layer
|
||||
|
||||
- **Provider interface** (`ai/llm`) with adapters for OpenAI-compatible,
|
||||
Anthropic and Ollama endpoints, called over plain HTTP — no vendor SDKs.
|
||||
- **Skill harness** (`ai/harness`): each skill (suggestions, week draft,
|
||||
ingredient ideas, quick ideas, archive post titles, …) layers its prompt —
|
||||
system prompt from `ai_settings`, skill instructions, household context —
|
||||
then validates the structured output against the skill's schema. An invalid
|
||||
response gets **one repair retry**; a second failure is an error, never a
|
||||
silent fallback. Every call is recorded in `ai_runs`.
|
||||
|
||||
## Recipe grounding
|
||||
|
||||
- Recipes are grounded in real pages found via the `searxng` sidecar.
|
||||
- Fetching is **SSRF-safe** (public addresses only, size and time limits).
|
||||
- Recipe data is parsed from schema.org `Recipe` JSON-LD.
|
||||
- Photos are downloaded, processed (`golang.org/x/image`) and cached locally;
|
||||
the client never hot-links external images.
|
||||
|
||||
## Scheduler
|
||||
|
||||
Times are in the household timezone.
|
||||
|
||||
| Schedule | When | Does |
|
||||
| --- | --- | --- |
|
||||
| `suggestions_weekly` | Sunday 08:00 | New batch of Side quests. |
|
||||
| `plan_draft` | Friday 09:00 | AI drafts next week's plan. |
|
||||
| `shopping_chat_autosend` | Saturday 08:00 | Sends the Supply run to Google Chat. |
|
||||
| `day_of_reminder` | Daily 16:30 | Tonight's dinner reminder. |
|
||||
| `rotation_check` | Daily 09:00 | Asks "Keep it in rotation?" for tried dinners. |
|
||||
| `week_rollover` | Monday 00:05 | Freezes last week's list into the Supply archive, rolls the plan. |
|
||||
| `ai_runs` purge | Periodic | Deletes old `ai_runs` rows. |
|
||||
|
||||
Runs are recorded in `schedule_runs` and executed through `jobs`.
|
||||
|
||||
## Notifications
|
||||
|
||||
- Events: plan ready, list ready, day-of reminder, new suggestions; each can
|
||||
go to Web Push and/or Google Chat per `notification_prefs`.
|
||||
- Delivery goes through `notification_outbox`.
|
||||
- Web Push uses VAPID (`webpush-go`). Google Chat uses the stored incoming
|
||||
webhook.
|
||||
|
||||
## Google Calendar
|
||||
|
||||
- One-way push: the app is the source of truth; edits in Google Calendar do
|
||||
not come back.
|
||||
- OAuth once in Settings; one event per planned dinner.
|
||||
- **Deterministic event ids** derived from the plan day, so re-pushing
|
||||
updates the same events instead of creating duplicates.
|
||||
|
||||
## Frontend
|
||||
|
||||
- React 19, TypeScript, Vite, React Router 7, TanStack Query 5,
|
||||
`vite-plugin-pwa`, `lucide-react` (stroke 2.75).
|
||||
- CSS Modules over `tokens.css`; no Tailwind, no Framer Motion, no
|
||||
hard-coded hex. Self-hosted Bagel Fat One and Figtree.
|
||||
- Tests: Vitest, Testing Library, jsdom.
|
||||
- Offline cache for the current plan, shopping list and recipes.
|
||||
|
||||
| Route | Screen |
|
||||
| --- | --- |
|
||||
| `/` | Home |
|
||||
| `/onboarding` | Add Regulars |
|
||||
| `/plan` | Weekly plan (Draft board) |
|
||||
| `/plan/shopping` | Supply run |
|
||||
| `/plan/shopping/archive` | Supply archive |
|
||||
| `/discover` | Swipe Side quests |
|
||||
| `/quick` | Quick dinners |
|
||||
| `/cook-from-pantry` | Cook from what I have |
|
||||
| `/dinner/:id` | Recipe detail / Kitchen reading mode |
|
||||
| `/settings` | Settings home |
|
||||
| `/settings/notifications` | Notifications and Google Chat |
|
||||
| `/settings/ai` | Admin · AI assistant |
|
||||
|
||||
## Boundaries and gotchas
|
||||
|
||||
- **Secrets never reach the client** — API keys, OAuth tokens and the Chat
|
||||
webhook stay server-side, encrypted at rest.
|
||||
- **The app is the source of truth for the plan**; Google Calendar is a
|
||||
one-way mirror.
|
||||
- **Never auto-promote to Regular** (D-021).
|
||||
- **Quick** means cooked in a crock pot **or** ≤ 30 minutes prep to table.
|
||||
- **Weeks run Monday–Sunday** in the household timezone.
|
||||
- **No silent fallbacks** — fail fast and report.
|
||||
- **iOS Web Push requires the PWA to be installed to the Home Screen**; show
|
||||
an install hint.
|
||||
Reference in New Issue
Block a user