Files

216 lines
12 KiB
Markdown

# AGENTS.md
Guidance for AI coding agents working in this repository. See
<https://agents.md/> for the file convention.
## Coding guidelines
These rules apply repository-wide. They are normative: **MUST** = required,
**MUST NOT** = prohibited, and **PREFER** = the default unless a concrete reason
justifies otherwise. Preserve the snapshot's behavior and compatibility contracts;
consistency with established project conventions beats introducing a second style.
## Lifetime and RAII
- **MUST** make it evident when every object and resource starts and stops existing.
- **MUST** bind files, sockets, processes, handles, locks, subscriptions, and
library resources to move-only RAII owners. Destructors must be safe,
deterministic, and non-throwing.
- **PREFER** automatic storage and the Rule of Zero. Do not use manual cleanup or
process exit as resource management.
- **MUST NOT** return or retain a pointer, reference, iterator, span, view,
callback, or coroutine state that can outlive its referent.
- **MUST** give every asynchronous task, timer, callback, process, and subscription
an owner plus explicit completion and cancellation paths. No detached work.
- **MUST NOT** capture local references in asynchronous callbacks unless completion
before scope exit is structurally guaranteed.
- **MUST** declare members so reverse destruction order is safe when one member
depends on another.
## Ownership
- **MUST** give every resource one identifiable owner unless shared lifetime is an
essential domain requirement.
- **PREFER** values. Use `std::unique_ptr` for exclusive dynamic or polymorphic
ownership.
- **MUST NOT** use `std::shared_ptr` to avoid deciding ownership. When independent
shared owners are necessary, document why and break cycles with `std::weak_ptr`.
- **MUST** treat raw pointers and references as non-owning. Never delete through
them or assume they extend lifetime.
- **MUST** express ownership transfer in the type: factories return values or
`std::unique_ptr`; consuming parameters take a value or `std::unique_ptr`.
- **MUST NOT** store borrowed data unless the owner's longer lifetime is both
guaranteed and locally obvious.
## Copying, movement, and borrowing
- **MUST** make value flow clear in every interface:
- small, cheap values: pass by value;
- required borrows: `T&` or `const T&`;
- nullable borrows: `T*`;
- call-bounded contiguous/text views: `std::span` or `std::string_view`;
- values retained by the callee: accept by value and move into place;
- explicit ownership transfer: `std::unique_ptr<T>`.
- **MUST NOT** persist a span or view unless the containing type encodes and
guarantees the backing storage lifetime.
- **MUST** use `std::move` only for intentional transfer. After a move, only
destroy, reassign, or perform operations guaranteed for the moved-from state.
- **MUST** avoid accidental copies in loops, structured bindings, callbacks,
variants, and return paths. Keep inexpensive copies when they make code clearer.
- **MUST** explicitly default or delete copy/move operations for resource-owning
types when the Rule of Zero cannot apply.
- **MUST NOT** return `const` values; it obstructs movement without adding safety.
## Modeling data
- **PREFER** values, enums, and `std::variant` over classes with optional fields or
mode booleans. Make illegal states unrepresentable when that improves clarity.
- **MUST** handle variants and state enums exhaustively so adding a case produces a
compile error at every required decision point.
- **PREFER** strong types for identifiers, paths, units, money, token counts, and
states that would otherwise be easy to confuse.
- **PREFER** immutable values. Keep mutation narrow, deliberate, and protected by
an invariant.
- **MUST** validate external input once at its boundary, then preserve the validated
invariant internally.
## Compile-time and zero-cost abstraction
- **PREFER** values, templates, concepts, `constexpr`, enums, and `std::variant`
when behavior is known at compile time and the result remains readable.
- **MUST** use runtime polymorphism only at genuine runtime boundaries such as
providers, storage backends, platform services, frontends, and integrations.
- **MUST NOT** add allocation, virtual dispatch, type erasure, reference counting,
or synchronization unless the required runtime flexibility justifies it.
- **MUST** keep templates small, constrained, and domain-named. Do not create a
generic framework before the shared invariant and variation points are known.
- **MUST NOT** use macros for ordinary abstraction. Contain unavoidable platform,
build, and feature macros at narrow boundaries.
- **MUST** verify performance-sensitive zero-cost claims with an optimized build,
compiler output, or a benchmark—not intuition.
## Abstractions and classes
- **MUST NOT** introduce an abstraction unless it materially improves readability,
flexibility, correctness, or maintainability. If direct code is clearer, use it.
- **MUST NOT** use a class when a value or function suffices. A class must have an
identity or resource, mutable state, an invariant to protect, and behavior.
- **MUST** name the invariant protected by a class. If no invalid instance state can
be described, prefer a value type or free function.
- **MUST NOT** perform I/O or expected-failure work in constructors. Use a named
factory returning `Result<T, E>` when construction can fail.
- **PREFER** composition over inheritance. Use inheritance only for genuinely
substitutable runtime behavior.
- **MUST** keep public surfaces minimal; implementation details remain private.
## Functions and dependencies
- **MUST** keep functions cohesive and side-effect-free where practical. Push I/O
and mutation to adapters and keep domain transformations pure.
- **MUST** inject clocks, IDs, filesystems, processes, transports, stores, and other
external capabilities. Do not construct them inside domain logic.
- **MUST** define dependency interfaces from the consumer's view and expose only
the operations that consumer needs.
- **PREFER** returned values and typed events over mutation of distant shared state.
- **MUST NOT** use service locators, hidden global state, or implicit environment
reads in domain code. Resolve configuration at the composition root.
## DRY and modularization
- **MUST** keep one authoritative representation of each rule, invariant, protocol,
and piece of domain knowledge.
- **MUST NOT** extract superficial repeated syntax when doing so hides intent,
couples unrelated modules, or creates flags and branches. DRY applies to
knowledge, not every repeated line.
- **MUST** organize modules around cohesive responsibilities and stable boundaries;
each module should have one clear reason to change.
- **MUST** keep domain logic independent of UI, transport, storage, platform, and
third-party SDK types. Convert representations at adapter boundaries.
- **MUST** keep dependencies acyclic and inward: delivery/adapters → application →
domain. The domain must not import an outer layer.
- **MUST NOT** create `utils`, `helpers`, or `misc` grab-bags. Name modules after
their domain responsibility.
- **PREFER** a helper beside its only caller. Share it only when cohesive callers
depend on the same semantics rather than merely similar syntax.
- **MUST** avoid both oversized files that mix concepts and tiny fragments that
force readers to cross many files to understand one operation.
## Naming and comments
- **MUST** use precise domain names that expose purpose, units, ownership, and
state. Prefer `request_timeout` to `timeout` and `message_count` to `count` when
the distinction matters.
- **MUST NOT** use unexplained abbreviations, single-letter names outside tiny
conventional scopes, or vague names such as `data`, `manager`, `helper`, and
`util` when a domain name exists.
- **MUST** keep terminology consistent across code, tests, logs, documentation,
wire formats, and user-facing concepts.
- **MUST** write comments for rationale, invariants, ownership/lifetime assumptions,
compatibility constraints, and non-obvious tradeoffs—not to narrate syntax.
- **MUST** update or remove comments when behavior changes. A stale comment is a
defect.
- **MUST** document public-interface ownership, borrowing lifetime, executor/thread
affinity, cancellation, ordering, and errors when the types do not make them
obvious.
- **MUST** cite the authoritative behavior or fixture beside byte-level, ordering,
or compatibility code that a future cleanup could accidentally break.
## Locality and orthogonality
- **MUST** keep related state, invariants, transformations, and error handling near
one another. Declare and initialize variables in the narrowest useful scope.
- **MUST** handle errors at the layer with enough context to act. Do not repeatedly
catch only to log and rethrow.
- **MUST** isolate platform code, third-party types, serialization quirks, and
compatibility shims behind narrow adapters.
- **MUST** keep components independently understandable, testable, and replaceable.
- **MUST NOT** combine model conversion, domain decisions, permissions, tool
execution, persistence, and presentation in one object.
- **MUST** model independent dimensions independently. Avoid boolean clusters and
modes that create invalid combinations.
- **MUST** use typed events/results across subsystem boundaries, not callbacks that
mutate another subsystem's internals.
- **MUST** ensure adding a provider, tool, command, backend, or frontend does not
require unrelated edits across the repository.
## Errors and invariants
- **PREFER** typed `Result<T, E>` values for expected operational failures. Reserve
exceptions for programming errors and library boundaries where exceptions are
the natural contract.
- **MUST** model domain failure modes in the return type and preserve original
context while adding actionable domain context.
- **MUST NOT** ignore an error without an explicit reason. Destructors must not
throw.
- **MUST** use assertions only for programmer invariants, never input validation or
behavior required in release builds.
## Verification
- **MUST** test success, expected failure, cancellation, early return, partial
initialization, shutdown, boundary values, ownership transfer, and compatibility
behavior relevant to the change.
- **MUST** run formatting, warnings-as-errors, focused tests, and relevant ASan/
UBSan builds before declaring C++ work complete.
- **MUST** use byte golden tests—not parsed-value equality—for byte-compatible JSONL
behavior.
- **MUST** review every stored reference and asynchronous callback for lifetime,
every resource for one clear owner, and every interface for explicit copy/move/
borrow semantics.
- **MUST NOT** approve code merely because it compiles or passes a narrow test.
## Meta
- **MUST** prefer an established convention over introducing a "better" second one.
Predictability is the point.
- **MUST** delete an abstraction when removing it makes the code easier to read,
change, and verify without losing a real invariant.
## Git/PR Workflow
- Recent human commit format: (type) imperative summary.
- Examples from this repo: (feat) add deployment script, (fix) add shebang to index.ts for execution in github actions, (chore) bump cli version.
- AI-created commit format when the user asks for a commit: (type) (openai/gpt-5.5, reviewed T|F, tested T|F) imperative summary.
- Before creating an AI commit, ask the user whether a human reviewed the changes so reviewed T|F is accurate.
- Mark tested T only after the relevant checks have run successfully. Otherwise use tested F.
- Before committing, inspect git status --short, git diff, and git log --oneline -10; stage only intended files.
- Run git diff --check before commit/PR handoff.