Skip to main content
This page is the operating form of ADR 0006 (naming, exports, imports) and ADR 0007 (documentation). Formatting is not a style question: oxfmt formats and oxlint lints, and both run in pnpm verify.

Naming

No speculative directories: utils/, helpers/ and services/ exist because something real lives in them. A helper used by a second file moves to utils/<name>.util.ts; a single-use private helper stays where it is used.

One declaration per file

Every exported declaration lives in its own file, named for it. Where the kind is not a plain function, the file carries a kind suffix:
  • index.ts is the only barrel name. A barrel declares nothing and names each symbol (export type { Token } from "./token.class"), never export *.
  • A folder is created for one artifact only when it has two or more non-test files.
  • Scaffolded templates are the reference: tools/scaffold/templates/packages/package/src.
  • A reviewer treats a file with two exported declarations, or a barrel that declares, as a finding. Changes to these conventions go through an ADR; there are no package-local exceptions.

Imports

The decorators preset turns consistent-type-imports off because decorator metadata needs value imports (ADR 0005).

File contract

Every source and configuration file opens with a header before its imports. Plain JSON has no comments and is documented by its nearest README. Source files (.ts, .tsx), per ADR 0007:
Configuration and tooling files (pnpm-workspace.yaml, turbo.jsonc, lefthook.yml, preset configs) use the header the repository already carries, in their own comment syntax (#, //):

One formatter and YAML style

oxfmt is the only formatter, in the editor too: .vscode/settings.json sets oxc.oxc-vscode as the default formatter for TypeScript, JavaScript, JSON, JSONC, YAML, Markdown and CSS, formats on save, turns Prettier off and mirrors .editorconfig (LF, final newline, trimmed whitespace, ruler at 100). .vscode/extensions.json recommends the Oxc extension and marks Prettier and ESLint as unwanted. Repo-owned YAML (lefthook.yml, .github/**, .mcp.yaml, .agents/*.yaml and the like) uses block style:
  • Mappings are always block mappings. A flow mapping ({ a: b }) is never written; the one exception is the empty {} (permissions: {}), which has no block form.
  • A flow sequence is allowed only for a short list of scalars that fits on one line (branches: [main]). Anything longer or nested is a block sequence.
  • A trailing comment stays only when the line is still 100 characters or fewer; otherwise the comment moves to the line above.
Generated and vendored YAML is exempt: pnpm-lock.yaml, pnpm-workspace.yaml (until STA-300), .specify/**, .agents/skills/** and .agents/reference/**. pnpm validate rejects a flow mapping and names the file and line.

TSDoc

Types carry the weight first: no any, generics have meaningful names, an exported type is its own documentation. Every exported symbol then gets TSDoc that says something the type does not.
Interfaces and types say what they represent and who produces and consumes them; each property says its meaning, unit or format, and who sets it. Boilerplate is a finding: a comment that restates the signature teaches readers to skip comments.

What must carry TSDoc

ADR 0007, amended on 2026-10-21, makes the block required on:
  • every exported symbol;
  • every property and method signature of an exported interface or type literal;
  • every public and protected member of an exported class (private and # members are internal);
  • every DI token made with defineToken, whose block says what resolves to it and which package or app provides it.
Types never appear in a tag: write @param name - meaning, never @param {type} name, and no typed @returns or @throws. TypeScript owns the types.

Worked example

@returns is kept here because the receipt’s meaning (first successful charge) is not in the type. Drop it when the type already says everything.

Comments in code

Explain why, not what the next line says. Required before a workaround, a security-relevant check, a deliberate deviation and a non-obvious ordering. Forbidden: restating comments, commented-out code, and a TODO without an owner and a tracking reference.

Tests

A test file’s header says what the suite proves. Each it() title is a full sentence stating behaviour, not the function name.

Typed errors

A package never lets a raw Error escape a public function. Every error it throws is a class under errors/ with:
  • a stable, unique code (a string literal in the class);
  • a message that names the offending thing (the token, driver or id involved);
  • an optional hint saying what to do;
  • structural fields, never embedded secrets or values.
A rejected promise or listener failure is reported through the package’s logger port, never swallowed. At an HTTP boundary a typed error maps to a framework error with a stable message; see HTTP and events.

What enforces this

The planned code-standards-steward splits, renames and moves files to meet the naming and barrel rules without changing behaviour; see the agent roster.