Skip to main content

Coding style

This guide owns the coding conventions for WednesdayAI core. For the PR gate and contribution workflow, see Contributing. For test conventions, see Testing.

Principles

Follow the Development Principles in Contributing. In short:
  • Reuse maintained packages already in the stack instead of building from scratch.
  • Fix bug classes rather than instances. Use real parsers, not regex, for structured formats.
  • Assert causes in tests.
  • Keep scope to the goal.
  • Block reviews only on in-scope correctness and security.

Language and structure

  • Language: TypeScript ESM with strict: true. No @ts-nocheck. Do not disable no-explicit-any; fix the root cause.
  • Style: functional and module pattern dominant. Use classes for channel adapters and stateful lifecycle managers.
  • Dependency injection: createDefaultDeps() in src/cli/deps.ts returns a plain dependency object. Commands receive it as a parameter. There is no IoC framework.
  • No prototype mutation: never use applyPrototypeMixins, Object.defineProperty on .prototype, or Class.prototype exports. Use explicit inheritance or composition. Get explicit approval first.
  • Comments: write them only for non-obvious logic such as hidden constraints, workarounds, and subtle invariants. Do not write docblocks that restate what the code does.
  • File size: keep files under 500 lines of code. pnpm check:loc runs scripts/check-ts-max-loc.ts --max 500. Split or refactor when it aids clarity or testability.
  • Formatting: run pnpm check before committing. Resolve formatting-only diffs without asking.
  • Naming: use WednesdayAI in user-facing docs and headings. wednesdayai is the primary binary name and openclaw is a permanent backward-compatible alias. Config keys and internal identifiers stay openclaw.
  • Imports: use the .js extension for relative ESM imports, import type { X } for type-only imports, and import directly from the original module. Do not create files that only re-export another file.

Do not duplicate helpers

Before writing a formatter, utility, or helper, search for an existing implementation and import it. If a function already exists, do not create a copy in another file. Never create local formatAge, formatDuration, or formatElapsedTime functions.

Colors and terminal output

Two modules split the work. src/terminal/palette.ts holds the color tokens. src/terminal/theme.ts wraps chalk around those tokens and exposes the semantic helpers. Use theme for output and never hardcode ANSI escapes.

Logging

Create one subsystem logger at module top and use it instead of console.log in production code:

Tool input schemas (TypeBox)

Tool input schemas use @sinclair/typebox. Several model providers reject schema constructs that TypeBox can produce, so keep schemas flat:
  • No Type.Union, and no anyOf, oneOf, or allOf in tool input schemas.
  • Use stringEnum and optionalStringEnum (a Type.Unsafe enum, in src/agents/schema/typebox.ts) for string enumerations.
  • Use Type.Optional(...) instead of ... | null.
  • Never use format as a raw property name. Some validators reject it.

Toolchain

Notes:
  • extensions/ is excluded from Oxlint (ignorePatterns). Extensions manage their own lint.
  • pnpm build does not type-check. Run pnpm tsgo for that.
  • pnpm check runs format check, tsgo, tsgo:web, Oxlint, several custom lint scripts, and the Swift host environment policy check.

Control UI decorators

The Control UI uses Lit with legacy decorators. The current Rollup parsing does not support the accessor fields that standard decorators require. When you add reactive fields, keep the legacy style:
The root tsconfig.json sets experimentalDecorators: true and useDefineForClassFields: false. Do not change either unless you also update the UI build tooling to support standard decorators.