-
Use the
Makefilefor common development tasks. Thejustfileis secondary. -
When tools are missing from the ambient shell, use:
nix develop --impure .#ci -c <command> -
Always invoke
nix developfrom the repository root. Entering from a subdirectory writes CWD-relative devenv files and can break formatting or the pre-commit hook. -
Never run multiple
nix developcommands concurrently in one worktree; they mutate shared.devenv/state. -
Dev shells share downloaded Go modules through
$HOME/go/pkg/mod.GOPATH,GOCACHE, and installed tools remain per-worktree under.devenv/state/go. Existing worktree module caches are not migrated or removed automatically; clean them up separately after confirming no active shell uses them. -
.nvmrcis the GitHub Actions source of truth for Node. Keep it aligned withnode -vin the Nix.#cishell. -
For slow or noisy commands, save complete output to a temporary log outside the repository, preserve and report the exit status and log path, inspect bounded slices, and never rerun solely because displayed output was truncated.
- The repository has four Go modules: the production root,
collector, the publishableapi/v3/clientSDK, and the test-onlye2emodule. - Shared package imports can expand nested module dependency graphs. Run
make modand module-aware checks for affected consumers; root-module checks do not validatecollectorore2edependency metadata. - The root module must never require
api/v3/client. The nested SDK is not independently tagged, and localreplacedirectives are invisible to downstream consumers. - Root
go build ./...,go test ./..., andgo vet ./...excludee2e. Usemake etoeorgo test -C e2e ./...;make lint-goandmake modalready cover all three modules.
- Name string enum constants
<Type><Value>, for exampleInvoiceStatusDraft. - Prefer methods when behavior is intrinsic to an existing domain type. Use freestanding functions when an operation has no natural receiver.
- Do not extract trivial or single-use helpers unless the name captures non-obvious domain intent. Inline pass-through wrappers.
- For non-trivial operations with multiple related parameters, define a named
<Operation>Inputstruct that implementsmodels.ValidatorthroughValidate() error. Put field and cross-field validation on the input and validate it at the owning boundary. Prefer this over a multi-argument operation paired with a separatevalidate<Operation>(...)helper. Do not introduce input structs for trivial single-value operations. - For
Validate() error, collect field errors and returnmodels.NewNillableGenericValidationError(errors.Join(errs...)). Preserve field context with wrapped errors. - Do not introduce
context.Background()orcontext.TODO()to bypass context propagation. Propagate the caller context or remove an unnecessary context parameter. - Never use
panicin non-test code. Return and propagate errors. - Map external provider failures to explicitly approved public HTTP messages; never return raw provider error text. Preserve the original cause for internal diagnostics.
- Production constructors must receive a
*slog.Logger; do not fall back toslog.Default(). - Prefer standard
slicesandmaps, thengithub.com/samber/lowhere it is clearer. Reusepkg/slicesxandpkg/syncx.OnceValues; do not add localptr,must, or equivalent wrappers. - Mapping helpers between API, domain, and DB representations follow
Type Translation Naming. Use
map/mapped, notproject/projected, for representation translation.
- PostgreSQL-backed tests require PostgreSQL and
POSTGRES_HOST=127.0.0.1; otherwise many suites silently skip. - Root-package tests using
confluent-kafka-gorequire-tags=dynamic. The standalonee2emodule does not. - Keep domain test helpers under
openmeter/.../testutilsindependent fromapp/common; construct repositories, adapters, services, and locks from their package constructors. - Prefer production-backed/service-backed fixtures when the real path can express the scenario. Hardcode suite-wide behavior into the shared harness instead of exposing per-test knobs.
- Use
t.Context()when atesting.Tortesting.TBis available. - Add a test helper only when reused or when its name captures non-obvious domain semantics. Inline one-use setup/assertion wrappers and remove dead helpers.
- Begin non-trivial service/lifecycle subtests with concise
given,when, andthenintent comments. - Pair
clock.FreezeTime(...)immediately withdefer clock.UnFreeze()in the same scope. - When precision permits, compare
alpacadecimal.DecimalthroughInexactFloat64()withrequire.Equal; inline one-off expected balances.
See Testing for harness patterns and examples.
-
Comments and docstrings explain intent, domain constraints, lifecycle state, and failure consequences that are not obvious from the code. Do not narrate conditions the reader can already see.
-
Document domain helpers whose names compress important business semantics; describe observable behavior and why excluded cases are excluded.
-
Preserve explanatory comments during refactors unless the change makes them false or misleading.
-
Keep project documentation short enough that humans will read it.
-
Write comments like you're explaining the code to a teammate: plain words, concrete reasons, no formal filler. Keep the useful explanation:
Bad:
// scopedEntries applies the account, route, provenance, and whole-history // exclusions shared by both branches. AsOf is left to each branch: filtering // here would hide future postings whose balance impact must be preserved.
Good:
// Apply the shared filters, but keep entries after AsOf: we need those to see // how low the balance gets later.
- TypeSpec and SDK generator work follows api/spec/AGENTS.md.
- Focused developer guides cover service packages, testing, refactoring, type conversions, and collection helpers.
For changes or reviews that affect domain behavior, use the
domain-docs skill after establishing
the scope to locate the relevant package documentation. Treat domain docs as
intended semantics and code and tests as current behavior; investigate
discrepancies instead of automatically preferring either.
- Use CodeGraph when it materially shortens symbol, caller, flow, or impact
exploration. If the worktree is not initialized, run
nix develop --impure .#ci -c codegraph init -ifrom the repository root, then retry. Checkcodegraph status; if initialization left a truncated index, rebuild it withnix develop --impure .#ci -c codegraph indexbefore falling back. Its server keeps a healthy index synchronized; do not manually sync it. - Treat graph relationships as navigation hints. Current source and tests are authoritative; fall back to normal source navigation only when initialization fails, CodeGraph is unavailable, or its results are inconclusive.
- When planning or implementing ambiguous, design-heavy, or cross-cutting
engineering changes, use the
iterative-engineering-designskill.
- Keep automatically required repo guidance in this file. Use skills for
task-shaped procedures and explicitly linked docs for scoped detail; do not
assume root-started agents will discover descendant
AGENTS.mdfiles. - Fold new guidance into the relevant section and merge or remove duplicates.
.agents/skillsis the source of truth for skills;.claude/skillscontains compatibility symlinks. Keep skill text usable by both Codex and Claude.