|
| 1 | +# AGENTS.md |
| 2 | + |
| 3 | +**Last Updated:** June 2026 |
| 4 | + |
| 5 | +CLI + API server that syncs Walmart, Costco, and Amazon purchases into Monarch Money: |
| 6 | +matches each order to its Monarch transaction, categorizes items with OpenAI, and splits |
| 7 | +the transaction by category. Local single-user tool — not a SaaS, extension, or real-time service. |
| 8 | + |
| 9 | +> This file is the single source of truth for agent guidance. `CLAUDE.md` is a symlink to it. |
| 10 | +
|
| 11 | +## Commands |
| 12 | + |
| 13 | +```bash |
| 14 | +# Build |
| 15 | +go build -o itemize ./cmd/itemize/ |
| 16 | + |
| 17 | +# Preview a sync — real OpenAI calls, NO Monarch writes. Use this to test safely. |
| 18 | +./itemize walmart -dry-run -days 14 -verbose |
| 19 | +./itemize costco -dry-run -days 7 |
| 20 | + |
| 21 | +# Apply |
| 22 | +./itemize walmart -days 14 |
| 23 | +./itemize costco -days 7 -max 10 |
| 24 | +./itemize walmart -force # reprocess already-processed orders |
| 25 | + |
| 26 | +# Serve |
| 27 | +./itemize serve -port 8085 |
| 28 | + |
| 29 | +# Test |
| 30 | +go test ./... # all |
| 31 | +go test ./... -race # race detector (run before committing) |
| 32 | +go test ./... -cover # coverage (keep 80%+) |
| 33 | +go test ./internal/domain/categorizer/... -v # single package |
| 34 | +``` |
| 35 | + |
| 36 | +## Configuration |
| 37 | + |
| 38 | +Reads `config.yaml` or env vars: |
| 39 | +- `MONARCH_TOKEN` — Monarch API token (required) |
| 40 | +- LLM categorizer — set **one** of: |
| 41 | + - `OPENAI_API_KEY` (also `OPENAI_APIKEY`) with optional `OPENAI_MODEL` (default `gpt-5.4-nano`) |
| 42 | + - `ANTHROPIC_API_KEY` (also `CLAUDE_API_KEY`) with optional `ANTHROPIC_MODEL` (default `claude-haiku-4-5-20251001`) |
| 43 | +- `CATEGORIZER_PROVIDER` — `openai` or `anthropic` to force a backend when both keys are set (auto-detected otherwise) |
| 44 | +- SQLite DB auto-created at `monarch_sync.db` |
| 45 | + |
| 46 | +## Architecture |
| 47 | + |
| 48 | +Layered, with the domain core kept dependency-free. Flow: **CLI → application → domain ← adapters → infrastructure.** |
| 49 | + |
| 50 | +``` |
| 51 | +internal/ |
| 52 | + application/sync/ orchestrator + per-provider handlers (walmart, costco, amazon, simple) |
| 53 | + domain/ pure logic: categorizer/ (pluggable LLM backend), matcher/ (fuzzy), splitter/ (splits + tax) |
| 54 | + adapters/ providers/ (walmart, costco, amazon) and clients/ (Monarch, openai/, anthropic/) |
| 55 | + infrastructure/ config/, storage/ (SQLite + goose migrations), logging/ (slog) |
| 56 | + cli/ flag parsing + output |
| 57 | +``` |
| 58 | + |
| 59 | +The categorizer depends on a `ChatClient` interface; concrete LLM backends |
| 60 | +live under `internal/adapters/clients/{openai,anthropic}`. Selection happens |
| 61 | +in `internal/adapters/clients/clients.go:newChatClient`. |
| 62 | + |
| 63 | +Key entry points: |
| 64 | +- Orchestrator — `internal/application/sync/orchestrator.go` |
| 65 | +- Matcher (amount ±$0.01, date ±5 days) — `internal/domain/matcher/matcher.go` |
| 66 | +- Categorizer (model set here today) — `internal/domain/categorizer/categorizer.go` |
| 67 | +- Splitter (tax is proportional: `category_subtotal/total_subtotal * total_tax`) — `internal/domain/splitter/splitter.go` |
| 68 | +- Storage / dedup — `internal/infrastructure/storage/sqlite.go` |
| 69 | + |
| 70 | +## Conventions agents must follow |
| 71 | + |
| 72 | +**TDD, and it's mandatory for bug fixes.** Before fixing a bug: write a failing test that |
| 73 | +reproduces it, confirm it fails, then fix, then confirm green, then run the full suite, then log |
| 74 | +it in `docs/bug-fixes.md`. Don't fix-first. |
| 75 | + |
| 76 | +**Keep the domain layer pure.** No HTTP, DB, file I/O, or clock access in `internal/domain/` — |
| 77 | +only pure functions over interfaces (`providers.OrderProvider`, `Order`, `OrderItem`). This is |
| 78 | +what makes it testable without mocks. |
| 79 | + |
| 80 | +**Log by level, not by `if verbose`.** `-verbose` sets the logger to debug; let the handler filter. |
| 81 | + |
| 82 | +```go |
| 83 | +// don't |
| 84 | +if opts.Verbose { logger.Info("Processing order", "id", order.ID) } |
| 85 | +// do |
| 86 | +logger.Debug("Processing order", "id", order.ID) |
| 87 | +``` |
| 88 | +Debug = diagnostics, Info = normal ops, Warn = recoverable, Error = needs attention. |
| 89 | + |
| 90 | +**Never bypass dedup casually.** SQLite tracks processed orders to prevent duplicate splits. |
| 91 | +`-force` is the only override and only when intentional — see `docs/deduplication-safety.md`. |
| 92 | + |
| 93 | +**Commits:** `type: brief description` (`feat|fix|refactor|test|docs|chore`). Run `go test ./...` |
| 94 | +and a `-dry-run` before committing. |
| 95 | + |
| 96 | +## Adding a provider |
| 97 | + |
| 98 | +Implement `providers.OrderProvider` in `internal/adapters/providers/<name>/`, add a config struct |
| 99 | +in `config.go`, register in `internal/cli/providers.go`, add a handler in |
| 100 | +`internal/application/sync/handlers/`, and test. Full walkthrough: `docs/adding-providers.md`. |
| 101 | + |
| 102 | +## Gotchas |
| 103 | + |
| 104 | +- **"No matching transaction found"** — transaction not posted yet (wait 1–3 days), amount off by |
| 105 | + >$0.01, or date off by >5 days. Use `-verbose` to see match scoring. |
| 106 | +- **"Order already processed"** — expected; dedup working. `-force` to reprocess. |
| 107 | +- **Provider auth** — Walmart cookies in `~/.walmart-api/cookies.json`; Costco creds saved by costco-go. |
| 108 | +- **DB reset** — back up and delete `monarch_sync.db`; migrations (goose) re-run on startup. |
| 109 | + |
| 110 | +## Deeper docs |
| 111 | + |
| 112 | +`docs/architecture.md` · `docs/testing.md` · `docs/adding-providers.md` · |
| 113 | +`docs/deduplication-safety.md` · `docs/bug-fixes.md` · `docs/logging.md` · `docs/design-migrations.md` |
0 commit comments