The CLI is organized into lowercase top-level slices under src/:
src/
cli/
commands/
docs/
auth/
config/
output/
runtime/
telemetry/
commands/is the user-facing entry point. Each command owns its own parsing, handler, and tests.auth/,config/,output/,runtime/, andtelemetry/are reusable concern slices shared by multiple commands or flags.docs/owns shared command documentation content and renderers used by both the runtime CLI and the docs generation script.- Shared concern slices still split contracts from implementations:
*.service.tsdefines Effect services and public interfaces*.layer.tsdefines live implementations and wiring
This split keeps the service contract readable on its own and prevents large implementation files from turning the service definition into a mixed abstraction + wiring file. The flatter layout is preferred because it maximizes colocation and avoids adding extra folders when the file suffix already communicates the role clearly.
cli/may import fromcommands/,docs/, and concern slices.commands/may import from concern slices.auth/,config/,output/,runtime/, andtelemetry/must not import fromcommands/orcli/.docs/must not import fromcli/orcommands/.- Commands must not import another command's internals.
Use direct file imports. Do not add barrel index.ts files.
Folders are lowercase everywhere.
Command files:
commands/login/
login.command.ts
login.handler.ts
login.errors.ts
login.integration.test.ts
login.e2e.test.ts
Shared concern files:
auth/
credentials.service.ts
credentials.layer.ts
errors.ts
Rules:
- Command files use the command name as a prefix.
- Flow files use
<topic>.flow.ts. - Shared service files use
<topic>.service.ts. - Shared layer files use
<topic>.layer.ts. - Do not prefix leaf files with the slice name.
tracing.layer.ts, nottelemetry.tracing.layer.tscredentials.service.ts, notauth.credentials.service.ts
- Service symbols are plain nouns:
Credentials,Tracing,Output. - Layer exports use concrete
*Layernames:credentialsLayer,tracingLayer,outputLayer. - Do not use
.Default. - Do not rely on
static layeras the default pattern for shared concern slices.
Each shared concern slice should prefer this shape:
<slice>/
<topic>.service.ts
<topic>.layer.ts
errors.ts
types.ts
schemas.ts
Only keep the root files the slice actually needs. Root-of-slice files are for real slice-owned artifacts such as:
errors.tstypes.tsschemas.tsconsent.tsidentity.tsjson-formatter.ts
Only introduce services/ or layers/ folders later if a slice becomes genuinely crowded or needs distinct implementation families. They are an exception, not the default.
If code is shared across multiple commands, move it into the owning concern slice. If it is only used by one command, keep it inside that command.
docs/ is a special slice. It may contain pure helpers such as:
command-docs.tsmarkdown-formatter.ts
Commands stay flat by default. Add extra folders only when the command genuinely needs them:
flows/for orchestration pathsui/for Ink mini-app code and UI-local state/model files
Do not add generic lib/, utils/, or modes/ folders inside commands.
If a command needs private DI, prefer colocated *.service.ts and *.layer.ts files in the command folder instead of adding nested folders by default.
- Slice-wide errors live at the slice root in
errors.ts. - Command-specific errors live in
<command>.errors.ts. - If an error starts command-local and becomes shared, promote it to the owning concern slice.
- Comment shared boundaries and non-obvious orchestration, not every file mechanically.
- Prefer short file headers on meaningful
*.service.tsand*.layer.tsfiles. - Use section comments in large layer files when the implementation has distinct phases or policy branches.
- Avoid comments on trivial wrappers and obvious code paths.
- Avoid comments that only restate the code line by line.
Examples where comments are expected:
telemetry/tracing.layer.tsoutput/output.layer.tsauth/credentials.layer.ts
Consistency does not mean every service or layer file needs a header. The goal is high-signal comments on important boundaries.