- Use kebab-case for filenames and directories. Where a filename carries a stereotype role suffix (enforced by .ls-lint.yml, or the port an adapter implements), separate the descriptive prefix from the suffix with a dot:
create-user.command-handler.ts,postgres-user.repository.ts,pino.logger.ts,user.test-factory.ts. Files whose symbol is the bare entity (aggregates, value objects, scalars) stay plain:user.ts,watch-span.ts. - Use snake_case for database tables and columns.
- Use full names for variables, functions, and types. Avoid abbreviations unless they are widely recognized.
- Prefer if + return over else and switch statements when possible.
- Prefer
typeoverinterfaceunless the use case specifically requires an interface. - Provide explicit return types for functions and methods.
- Use
constassertions for immutable data structures.
- Consume every
@yasha/*package as a namespace:import * as postgres from '@yasha/postgres', thenpostgres.eq,valkey.Batch,schema.string(). Never import a package's named symbols bare - the namespace keeps origin visible at the call site. Enforced by therequire-namespace-importlint rule. The rule governs cross-package@yasha/*imports only; app-internal#shared/#<module>specifiers and raw third-party imports at their owning seam are out of scope. - Scalar vocabulary is the exception: branded domain primitives (
Uuid,Url,MediaId,PrincipalId) are imported bare from#shared, never namespaced - a scalar carries its own meaning likestring, andschema.Uuidwould misread as "the Uuid schema".
The export-naming principle these rest on, and the rules for scoping and naming a package, are monorepo.md § Package surface and naming.