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:
| Name | What it held |
|---|---|
state.ts | Audit-event builder and a lease key |
verification-state.ts | A 1,396-line persistence store |
contracts.ts | Connection-check schemas, action state, a credential guard |
verification-contracts.ts | Verification schemas |
admin-status.ts | One schema alias |
verification-admin.ts | Server-side admin operations |
status-polling.ts | A 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.