Imported from freecodexyz/free-code (
AGENTS.md). Install upstream withnpx skills add freecodexyz/free-code. Copyright stays with the author.
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_ptrfor exclusive dynamic or polymorphic ownership. - MUST NOT use
std::shared_ptrto avoid deciding ownership. When independent shared owners are necessary, document why and break cycles withstd::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 orstd::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&orconst T&; - nullable borrows:
T*; - call-bounded contiguous/text views:
std::spanorstd::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::moveonly 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
constvalues; it obstructs movement without adding safety.
Modeling data
- PREFER values, enums, and
std::variantover 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, andstd::variantwhen 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, ormiscgrab-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_timeouttotimeoutandmessage_counttocountwhen the distinction matters. - MUST NOT use unexplained abbreviations, single-letter names outside tiny
conventional scopes, or vague names such as
data,manager,helper, andutilwhen 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.