You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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 core at src/core/ (src/core/go.mod): all real code lives here. Imports inside core use 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 include ASCIIAttrControl, ASCIIAttrPrintable, ASCIIAttrAlpha, ASCIIAttrAlphaUpper, ASCIIAttrAlphaLower, ASCIIAttrDigit, ASCIIAttrHex, plus combined masks like ASCIIAttrAlphaNum.
  • Attrs is a [256]Attr LUT. Generic SetRange / AddRange / DelRange / ContainsAll / MatchesAll (and the Any variants) operate on it without allocations.
  • ASCIIAttrs wraps an initialized Attrs table 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 / Lowerize mutate in place; ToUpper / ToLower use 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: Module is the namespace ("auth", "db"). Use MustRegisterModule("name") at package init for global uniqueness (it panics on duplicates), or MustModule for a non-registered local module. Build codes with module.MustCode("login") and extend hierarchies with code.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 uses btes.ASCII() for fast byte classification.
  • Identity vs lineage: Code.Is is strict equality; Code.IsDescendantOf and IsOrDescendantOf walk the dotted hierarchy (matching prefix + . boundary, so user_group is NOT a descendant of user). 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:
    • Unwrap carries the public identity chain — what errors.Is / errors.As see. It supports both Unwrap() error (chain) and Unwrap() []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, use MultipleError (siblings on the public plane), not multiple causes.
    • Example: a user::cant_change_user wrapping a database::invalid_credentials cause should still be errors.Is(err, user::cant_change_user), not the database code. Use WithCause / AddCause to attach causes; use GetCause / FindCause / HasCause to inspect them (they traverse both Unwrap() error and Unwrap() []error chains). Keep this separation when adding new error types.
  • MultipleError is a flat sibling collection (NOT a tree). Unwrap() []error returns the slice so errors.Is/As work across siblings; there is no causal relationship between elements. MakeMultipleError cleans nil entries.
  • PanicError wraps recovered panics; pre-defined codes (ErrInvalidModule, ErrInvalidCode, ErrNilError, etc.) live in a.go under the reserved errs:: module.
  • Adder pattern: Add(base, composers...) applies a chain of Adders 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.Apply short-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 .go file starts with the long Spanish/Elastic-License v2.0 header block. When creating new files, copy the header from a sibling file and update only Fichero (filename), Package, and Creado (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... vs Validate...: by convention, MustX panics on invalid input (intended for package init / constants), and ValidateX returns an error (intended for runtime input). Preserve this split when adding new constructors.

Powered by TurnKey Linux.