Skip to content

Naming

Sibling files must be distinguishable by name alone

Name a module for what it owns, not for the feature it belongs to. When every file in a directory carries the feature name, the names stop carrying information.

The test

Show a new reader the directory listing and nothing else. Ask which file to open for a given task.

If two names both plausibly answer, one of them is wrong.

Rules

A name must not be a bare qualifier of its neighbor. state.ts beside verification-state.ts tells a reader that one is a special case of the other. In this repository that was false. state.ts held an audit-event builder and a lease key, and verification-state.ts was a database store. The names implied a relationship that the code did not have.

One word, one meaning per directory. apps/concourse/lib/rackspace/ used "state" three ways: a file with no state, a persistence store, and RackspaceAdminActionState for user interface state in contracts.ts. It used "admin" and "status" twice each with unrelated meanings.

Prefer the concrete noun. workflow-policy.ts held three retry constants. It was a retry policy. contracts.ts held connection-check schemas, action state, and a credential guard. It was not one thing, so no single name was honest. That was the signal to split it.

Do not distinguish by word order or number. json-contracts.ts once sat beside management-contract.ts, differing in a qualifier and in plurality. Neither name predicted its contents. They are api-schemas.ts and form-input.ts now, and a reader picks correctly.

Framework and Node boundaries

Put ordinary Next.js adapters in a framework/ folder beside the feature they serve. Put the Promise SDK facade in promise/. Put executable tests in the workspace's top-level tests/ tree and mirror the code or harness path there. Tests inherit the rules of the mirrored path. Do not use a test suffix or a tests/ folder to bypass Effect rules in an Effect-owned module.

A test directly in an app's tests/ folder, such as tests/proxy.test.ts, uses ordinary TypeScript rules. Use that place only for framework adapters, root files such as proxy.ts, and Next.js app/ routes, actions, and components. Mirror components/ tests under tests/components/. Put a test of Effect-owned lib/ code under tests/lib/.

If Effect-owned code needs a direct Node API, use a file named *.node.ts. Use *.node.test.ts for a test with a direct Node import. Keep host operations narrow and use Effect services when they provide the required behavior. The suffix excludes only the Effect Node import diagnostic.

Backend service files

Use a concrete noun file when a service and its canonical Layer form one module. For example, access.ts owns AccessPolicy, its construction effect, and its static open Layer. Keep service.ts when a capability folder contains other focused modules. Do not add layer.ts for the canonical implementation. Use standalone Layer names for alternative adapters and generic factories. Name default assemblies in composition.ts with a DefaultLayer suffix. Keep implementation Layers open. Name additional modules for their responsibility, such as validation.ts, access/rules.ts, or session.ts. Avoid general utils.ts collections. Keep types beside their owner unless a shared type module has a clear responsibility. The service module example shows how these names work together.

The worked example

Before the September 2026 cleanup, six of the thirteen non-test files in apps/concourse/lib/rackspace/ failed the test above:

NameWhat it held
state.tsAudit-event builder and a lease key
verification-state.tsA 1,396-line persistence store
contracts.tsConnection-check schemas, action state, a credential guard
verification-contracts.tsVerification schemas
admin-status.tsOne schema alias
verification-admin.tsServer-side admin operations
status-polling.tsA browser fetch loop

The current app layout names these responsibilities directly: reports/service.ts records report results, verification.ts orchestrates Production checks, connection/check.ts runs Preview checks, and action-state.ts defines browser outcomes. A reader can find each concern without opening every file.

Renaming is cheap

A rename is a mechanical change that the type checker verifies completely. When a name is wrong, fix it in the next change that touches the file. Do not wait for a dedicated cleanup.

How this is checked

Code review, against this page.