diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..ee0b98a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,103 @@ +# AGENTS.md + +Go CLI that reads the encrypted snapshot written by mt5-pnl-exporter +(`snapshot.json.gz.age`: JSON → gzip → age scrypt) and prints P&L/account +tables or JSON. Spec: `docs/superpowers/specs/2026-06-13-mt5-pnl-cli-v1-design.md`. + +## Commands + +```bash +go test ./... # all tests (CI runs -race; ubuntu leg uploads coverage to codecov) +go test ./internal/render -update # regenerate golden files after render changes +go build -o mt5-pnl-cli . +go run github.com/goreleaser/goreleaser/v2@latest check # validate .goreleaser.yaml +pre-commit install # gitleaks secret-scan hook (one-time) +pre-commit run --all-files # run the gitleaks hook manually +``` + +## Architecture + +- `main.go` — `run(args, stdout, stderr, getPassphrase)` is the testable + entry point; `main()` wires `os` streams + `secrets.Get`. One + `flag.FlagSet` per subcommand; `main` is the only caller of `os.Exit`. +- `args.go` / `cmd_common.go` — range parsing (`--last`, `--from/--to`), + snapshot path resolution (flag > `MT5_PNL_SNAPSHOT` > error), staleness + warning, account-label filter resolution. +- `internal/snapshot` — schema 1.x structs, streaming age→gzip→JSON read, + version gate (`CheckSchemaVersion`: same major, minor <= supported). +- `internal/aggregate` — deals → group rows + summary. `--by` chooses the + grouping: time cuts (day/week/month) emit per-account rows plus a combined + `ALL` row per period; symbol/magic cuts emit one row per symbol/magic + aggregated across accounts (no per-account or combined row, Account nil). + Each row and the summary carry net P&L plus its four components + (trade_profit / commission / swap / fee, summing to net). The summary also + carries expectancy, average and largest win/loss, and max drawdown (a + deal-ordered realised-P&L pass, not equity drawdown). `AccountsInScope` + exposes the contributing logins for the currency guard. Full-precision + sums; rounding happens in render only. Breakeven (net == 0) is neither win + nor loss. +- `internal/secrets` — keychain via zalando/go-keyring, service + `mt5-pnl-cli`, account `encryption-passphrase`. +- `internal/render` — fixed-width tables (manual writer; ANSI colour + applied after width padding so it never skews alignment), JSON and CSV; + all display rounding here. +- `internal/snaptest` — test-only fixture builder (encrypts JSON the way + the exporter does; low scrypt work factor for speed). + +## Gotchas + +- **No config file, by design.** Snapshot path via flag/env; everything + else is flags. Don't add a config file without revisiting the spec. +- **The passphrase has no env var or flag, deliberately** (see spec + Security section). Don't add one. Tests inject `getPassphrase`; + cross-process tests can't reach the keychain, so the binary smoke test + only covers pre-keychain failure paths. +- **CI runs on ubuntu/macos/windows** — keep paths `filepath`-safe and + don't add tests that need a real keychain (`keyring.MockInit()` only). +- **Schema bumps:** when the exporter ships a new minor, update + `SupportedMinor`, re-vendor `schema/snapshot.schema.json` from that + release, and add fields to the structs (additive only). +- **Deal times are Unix seconds bucketed in UTC**; weeks start Monday. +- **`--format`.** `pnl`/`accounts` take `--format table|json|csv` + (default `table`). CSV is rows-only (no summary). +- **Mixed-currency guard.** `pnl` never sums across currencies: when + accounts in scope span more than one, combined `ALL` rows and the summary + are suppressed (`n/a`/`null`/omitted) with a stderr warning. A + `--by symbol|magic` cut has no per-account row to fall back to, so it + **refuses** under mixed currency (stderr, exit 1) — narrow `--accounts`. + Scope is the accounts that contributed deals (`aggregate.AccountsInScope`), + not the grouped rows (symbol/magic rows carry no account). +- **`--quiet`/`-q`** silences stderr warnings (staleness, mixed-currency); errors still print. +- **`--color`** (pnl only): auto/always/never; auto needs a `*os.File` TTY and honours `NO_COLOR`/`TERM=dumb`. +- **Summary block is table/JSON only.** The two-group performance/breakdown + summary appears in `--format table` (an aligned key/value block) and + `--format json`; CSV is rows-only by design. Max drawdown is realised-P&L + drawdown over the ordered in-scope deals, deliberately distinct from + broker equity drawdown. +- **`group`/`group_by` are uniform across cuts.** Every `pnl` JSON/CSV row + carries `group` (period date, symbol, or magic) and `group_by` (the + `--by` value); the Go field is `aggregate.Row.Group`. `account` is the + login for per-account time rows and `null` for the combined time row and + for every symbol/magic row. The table labels the first column + `PERIOD`/`SYMBOL`/`MAGIC` and drops the `ACCOUNT` column for dimension + cuts. +- Dependencies are Renovate-managed; don't hand-bump pinned actions or + module versions. + +## Conventions + +- British/Commonwealth English in comments and docs. +- TDD; golden files for table output (`-update` to regenerate, then eyeball + the diff). +- After changing commands, architecture or a gotcha above, update this + file and README.md in the same change. + +## Audience + +Public, open-source project with a global audience. In committed artifacts +(specs, plans, READMEs, code, comments, commit messages): + +- No contributor-specific detail: personal financial or tax specifics, broker + names, account numbers, or local file paths. +- Keep tax, jurisdiction and currency examples generic. If a specific regime is + named, label it as one example among many rather than the default. diff --git a/CLAUDE.md b/CLAUDE.md index 0bd165c..43c994c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,94 +1 @@ -# CLAUDE.md - -Go CLI that reads the encrypted snapshot written by mt5-pnl-exporter -(`snapshot.json.gz.age`: JSON → gzip → age scrypt) and prints P&L/account -tables or JSON. Spec: `docs/superpowers/specs/2026-06-13-mt5-pnl-cli-v1-design.md`. - -## Commands - -```bash -go test ./... # all tests (CI runs -race; ubuntu leg uploads coverage to codecov) -go test ./internal/render -update # regenerate golden files after render changes -go build -o mt5-pnl-cli . -go run github.com/goreleaser/goreleaser/v2@latest check # validate .goreleaser.yaml -pre-commit install # gitleaks secret-scan hook (one-time) -pre-commit run --all-files # run the gitleaks hook manually -``` - -## Architecture - -- `main.go` — `run(args, stdout, stderr, getPassphrase)` is the testable - entry point; `main()` wires `os` streams + `secrets.Get`. One - `flag.FlagSet` per subcommand; `main` is the only caller of `os.Exit`. -- `args.go` / `cmd_common.go` — range parsing (`--last`, `--from/--to`), - snapshot path resolution (flag > `MT5_PNL_SNAPSHOT` > error), staleness - warning, account-label filter resolution. -- `internal/snapshot` — schema 1.x structs, streaming age→gzip→JSON read, - version gate (`CheckSchemaVersion`: same major, minor <= supported). -- `internal/aggregate` — deals → group rows + summary. `--by` chooses the - grouping: time cuts (day/week/month) emit per-account rows plus a combined - `ALL` row per period; symbol/magic cuts emit one row per symbol/magic - aggregated across accounts (no per-account or combined row, Account nil). - Each row and the summary carry net P&L plus its four components - (trade_profit / commission / swap / fee, summing to net). The summary also - carries expectancy, average and largest win/loss, and max drawdown (a - deal-ordered realised-P&L pass, not equity drawdown). `AccountsInScope` - exposes the contributing logins for the currency guard. Full-precision - sums; rounding happens in render only. Breakeven (net == 0) is neither win - nor loss. -- `internal/secrets` — keychain via zalando/go-keyring, service - `mt5-pnl-cli`, account `encryption-passphrase`. -- `internal/render` — fixed-width tables (manual writer; ANSI colour - applied after width padding so it never skews alignment), JSON and CSV; - all display rounding here. `pnl`/`accounts` take `--format table|json|csv` - (default `table`). -- `internal/snaptest` — test-only fixture builder (encrypts JSON the way - the exporter does; low scrypt work factor for speed). - -## Gotchas - -- **No config file, by design.** Snapshot path via flag/env; everything - else is flags. Don't add a config file without revisiting the spec. -- **The passphrase has no env var or flag, deliberately** (see spec - Security section). Don't add one. Tests inject `getPassphrase`; - cross-process tests can't reach the keychain, so the binary smoke test - only covers pre-keychain failure paths. -- **CI runs on ubuntu/macos/windows** — keep paths `filepath`-safe and - don't add tests that need a real keychain (`keyring.MockInit()` only). -- **Schema bumps:** when the exporter ships a new minor, update - `SupportedMinor`, re-vendor `schema/snapshot.schema.json` from that - release, and add fields to the structs (additive only). -- **Deal times are Unix seconds bucketed in UTC**; weeks start Monday. -- **`--format`.** `pnl`/`accounts` take `--format table|json|csv` - (default `table`). CSV is rows-only (no summary). -- **Mixed-currency guard.** `pnl` never sums across currencies: when - accounts in scope span more than one, combined `ALL` rows and the summary - are suppressed (`n/a`/`null`/omitted) with a stderr warning. A - `--by symbol|magic` cut has no per-account row to fall back to, so it - **refuses** under mixed currency (stderr, exit 1) — narrow `--accounts`. - Scope is the accounts that contributed deals (`aggregate.AccountsInScope`), - not the grouped rows (symbol/magic rows carry no account). -- **`--quiet`/`-q`** silences stderr warnings (staleness, mixed-currency); errors still print. -- **`--color`** (pnl only): auto/always/never; auto needs a `*os.File` TTY and honours `NO_COLOR`/`TERM=dumb`. -- **Summary block is table/JSON only.** The two-group performance/breakdown - summary appears in `--format table` (an aligned key/value block) and - `--format json`; CSV is rows-only by design. Max drawdown is realised-P&L - drawdown over the ordered in-scope deals, deliberately distinct from - broker equity drawdown. -- **`group`/`group_by` are uniform across cuts.** Every `pnl` JSON/CSV row - carries `group` (period date, symbol, or magic) and `group_by` (the - `--by` value); the Go field is `aggregate.Row.Group`. `account` is the - login for per-account time rows and `null` for the combined time row and - for every symbol/magic row. The table labels the first column - `PERIOD`/`SYMBOL`/`MAGIC` and drops the `ACCOUNT` column for dimension - cuts. -- Dependencies are Renovate-managed; don't hand-bump pinned actions or - module versions. - -## Conventions - -- British/Commonwealth English in comments and docs. -- TDD; golden files for table output (`-update` to regenerate, then eyeball - the diff). -- After changing commands, architecture or a gotcha above, update this - file and README.md in the same change. +@AGENTS.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bd3635d..2c387b1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -91,7 +91,8 @@ The binary's `version` output is injected from the tag via ldflags. ## Conventions -See [`CLAUDE.md`](CLAUDE.md) — the canonical reference for coding style, +See [`AGENTS.md`](AGENTS.md) — the canonical reference for coding style, architectural rules, and gotchas (British/Commonwealth English, the no-config-file and -keychain-only invariants, golden-file workflow, doc-sync rule). It's -loaded automatically by Claude Code but reads as a normal project doc. +keychain-only invariants, golden-file workflow, doc-sync rule). Coding agents +load it automatically (Claude Code via the one-line `CLAUDE.md` import), but it +reads as a normal project doc.