New internal/services/membership package with AttendanceLoader, TransactionLoader, ExceptionLoader interfaces and NewStubSources() stub returning ErrIOPending until M4 Sheets loaders land.
FeesReport and ReconcileReport orchestrate domain/fees + domain/reconcile and write fixed-width text reports matching the Python output of calculate_fees.py and match_payments.py print_report.
cmd/fuj/main.go: fuj fees and fuj reconcile now dispatch; fuj sync / fuj infer keep the [M4] placeholder.
Both subcommands exit 1 with a clean actionable error message while IO is pending.
13 unit tests covering stubs, all formatter branches, and orchestration wiring via fake loaders.
Test plan
cd go && go test -race ./internal/services/membership/... — all green
cd go && go test -race ./... — all packages green
cd go && golangci-lint run ./... — clean
./bin/fuj fees → exits 1, stderr: "io layer not yet wired up; lands in milestone M4"
./bin/fuj reconcile → same
./bin/fuj help → fees / reconcile no longer tagged [M2]; sync / infer still [M4]
## Summary
- New `internal/services/membership` package with `AttendanceLoader`, `TransactionLoader`, `ExceptionLoader` interfaces and `NewStubSources()` stub returning `ErrIOPending` until M4 Sheets loaders land.
- `FeesReport` and `ReconcileReport` orchestrate `domain/fees` + `domain/reconcile` and write fixed-width text reports matching the Python output of `calculate_fees.py` and `match_payments.py print_report`.
- `cmd/fuj/main.go`: `fuj fees` and `fuj reconcile` now dispatch; `fuj sync` / `fuj infer` keep the [M4] placeholder.
- Both subcommands exit 1 with a clean actionable error message while IO is pending.
- 13 unit tests covering stubs, all formatter branches, and orchestration wiring via fake loaders.
## Test plan
- [x] `cd go && go test -race ./internal/services/membership/...` — all green
- [x] `cd go && go test -race ./...` — all packages green
- [x] `cd go && golangci-lint run ./...` — clean
- [x] `./bin/fuj fees` → exits 1, stderr: `"io layer not yet wired up; lands in milestone M4"`
- [x] `./bin/fuj reconcile` → same
- [x] `./bin/fuj help` → `fees` / `reconcile` no longer tagged `[M2]`; `sync` / `infer` still `[M4]`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Add internal/services/membership package: AttendanceLoader,
TransactionLoader, ExceptionLoader interfaces + NewStubSources stub
(returns ErrIOPending until M4 lands real Sheets loaders).
FeesReport and ReconcileReport orchestrate domain/fees + domain/reconcile
and write fixed-width text reports matching Python calculate_fees.py and
match_payments.py print_report output. 13 unit tests cover all formatter
branches and orchestration wiring via fake loaders.
cmd/fuj/main.go: fees and reconcile subcommands now dispatch; sync/infer
retain the [M4] placeholder.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
internal/services/membershippackage withAttendanceLoader,TransactionLoader,ExceptionLoaderinterfaces andNewStubSources()stub returningErrIOPendinguntil M4 Sheets loaders land.FeesReportandReconcileReportorchestratedomain/fees+domain/reconcileand write fixed-width text reports matching the Python output ofcalculate_fees.pyandmatch_payments.py print_report.cmd/fuj/main.go:fuj feesandfuj reconcilenow dispatch;fuj sync/fuj inferkeep the [M4] placeholder.Test plan
cd go && go test -race ./internal/services/membership/...— all greencd go && go test -race ./...— all packages greencd go && golangci-lint run ./...— clean./bin/fuj fees→ exits 1, stderr:"io layer not yet wired up; lands in milestone M4"./bin/fuj reconcile→ same./bin/fuj help→fees/reconcileno longer tagged[M2];sync/inferstill[M4]🤖 Generated with Claude Code