6.7 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Workspace layout
Go workspace (go.work) with two modules:
- Root module
active(go.mod): currently empty placeholder. - Module
coreatsrc/core/(src/core/go.mod): all real code lives here. Imports insidecoreuse the bare module name, e.g.import "core/btes",import "core/errs".
Go version is 1.26 (uses generics throughout, including type parameters on slice aliases).
Commands
All Go commands must be run with the working directory inside src/core (or use -C src/core), since that is the active module in the workspace.
# Build everything in the core module
go -C src/core build ./...
# Run all tests across packages
go -C src/core test ./...
# Run tests in one package (note: tests live in the `test` subpackage)
go -C src/core test ./btes/test
go -C src/core test ./errs/test
go -C src/core test ./opts/test
# Run a single test by name
go -C src/core test ./errs/test -run TestValidateCodeAcceptsValidCodes
# Vet / format
go -C src/core vet ./...
gofmt -w src/core
Tests use Go's standard testing package (no external assertion libraries) and live in a sibling test/ directory using a separate package test — i.e. external (black-box) tests. New tests should follow the same convention rather than *_test.go files alongside the code.
Package architecture
The codebase is structured as a small set of foundational primitives that other packages (and future modules) will build on. Read these in order — errs depends on btes, and most callers of errs will use opts for configuration.
core/btes — ASCII byte operations via LUT
btes implements zero-allocation, O(1) ASCII classification and case conversion using a 256-entry lookup table. The big-picture design:
Attr(uint8) is a bitmask of properties. Predefined bits includeASCIIAttrControl,ASCIIAttrPrintable,ASCIIAttrAlpha,ASCIIAttrAlphaUpper,ASCIIAttrAlphaLower,ASCIIAttrDigit,ASCIIAttrHex, plus combined masks likeASCIIAttrAlphaNum.Attrsis a[256]AttrLUT. GenericSetRange/AddRange/DelRange/ContainsAll/MatchesAll(and theAnyvariants) operate on it without allocations.ASCIIAttrswraps an initializedAttrstable and exposes domain helpers (IsAlpha,IsDigit,EqualFold,ToUpper,Upperize, etc.). It is immutable after construction and safe to share across goroutines.- A package-level singleton is exposed via
btes.ASCII()— use that rather than constructing a new one. Two case-conversion styles exist:Upperize/Lowerizemutate in place;ToUpper/ToLoweruse a pre-scan to return the input slice unchanged when no transformation is needed (zero-copy fast path).
core/errs — hierarchical error codes with a separate cause plane
The error system is built around a single string-typed Code with a strict format: module::segment[.segment...], e.g. auth::user.login.failed. Key design points:
- Module + code:
Moduleis the namespace ("auth", "db"). UseMustRegisterModule("name")at package init for global uniqueness (it panics on duplicates), orMustModulefor a non-registered local module. Build codes withmodule.MustCode("login")and extend hierarchies withcode.Sub("failed"). - Validation (
ValidateCode,ValidateModule) is enforced strictly: only[a-z0-9_],_may not touch other separators or segment ends, exactly one::, and.only allowed after::. The validator usesbtes.ASCII()for fast byte classification. - Identity vs lineage:
Code.Isis strict equality;Code.IsDescendantOfandIsOrDescendantOfwalk the dotted hierarchy (matching prefix +.boundary, souser_groupis NOT a descendant ofuser).Matches(err, target)is the package-level helper that handles wrapping plus lineage in one shot. - Cause is a separate plane from Unwrap. This is the most important invariant in the package:
Unwrapcarries the public identity chain — whaterrors.Is/errors.Assee. It supports bothUnwrap() error(chain) andUnwrap() []error(siblings, e.g.MultipleError).Causer.Cause()carries diagnostic origin information that should NOT change the error's identity. Cause is always singular: a single error has a single reason. If several errors arose at the same semantic level, useMultipleError(siblings on the public plane), not multiple causes.- Example: a
user::cant_change_userwrapping adatabase::invalid_credentialscause should still beerrors.Is(err, user::cant_change_user), not the database code. UseWithCause/AddCauseto attach causes; useGetCause/FindCause/HasCauseto inspect them (they traverse bothUnwrap() errorandUnwrap() []errorchains). Keep this separation when adding new error types.
- MultipleError is a flat sibling collection (NOT a tree).
Unwrap() []errorreturns the slice soerrors.Is/Aswork across siblings; there is no causal relationship between elements.MakeMultipleErrorcleans nil entries. - PanicError wraps recovered panics; pre-defined codes (
ErrInvalidModule,ErrInvalidCode,ErrNilError, etc.) live ina.gounder the reservederrs::module. - Adder pattern:
Add(base, composers...)applies a chain ofAdders to enrich a base error (e.g.errs.Add(base, errs.AddCause(orig))). Use this rather than ad-hoc wrappers when composing decorations.
core/opts — functional options
Generic functional-options pattern in two flavors:
Option[T]/Options[T]— infallible.Apply(*T)mutates; nil options are treated as no-ops to allow conditional composition.TryOption[T]/TryOptions[T]— can return an error (validation, resource acquisition).TryOptions.Applyshort-circuits on first error WITHOUT rolling back prior mutations — opts are not transactional.
Use these types as the standard signature for configurable constructors instead of inventing per-package option types.
Conventions
- Source headers: every
.gofile starts with the long Spanish/Elastic-License v2.0 header block. When creating new files, copy the header from a sibling file and update onlyFichero(filename),Package, andCreado(date). Don't omit it. - Doc comments are in Spanish. Match the existing language and tone when adding new exported docs. Code identifiers remain English.
- No external dependencies. The module is currently stdlib-only; do not introduce third-party deps without an explicit reason.
Must...vsValidate...: by convention,MustXpanics on invalid input (intended for package init / constants), andValidateXreturns an error (intended for runtime input). Preserve this split when adding new constructors.