- Command handlers must use
Command.action(...)withEffectvalues. - Do not use
actionAsyncanywhere. - Prefer
Effect.genfor multi-step flows andEffect.syncfor pure sync handlers. - Use
Effect.catchAllat command boundaries to map runtime errors into structured CLI envelopes.
- Public module APIs use this shape:
- Internal Promise implementation:
async function fooPromise(...). - Effect API:
export function fooEffect(...) => Effect.tryPromise(...). - Promise boundary wrapper (for compatibility):
export function foo(...) => Effect.runPromise(fooEffect(...)).
- Internal Promise implementation:
- Keep
Effect.runPromiseusage at boundaries only (CLI entrypoint and compatibility wrappers).
- Use
import * as Effect from "effect/Effect"directly. - Do not reintroduce
toEffect/effect-interop; wrappers are explicit per function. - Prefer static imports of
*EffectAPIs over dynamic imports in commands.
- For streamed command output, emit:
- start event,
- progress/step events,
- final result event,
- mapped stream error event on failure.
- Keep stream callbacks best-effort and non-fatal.
pnpm exec tsc --noEmitpnpm run buildpnpm test tests/integration/cli-smoke.test.ts tests/unit/application-deploy-security.test.ts tests/unit/cli/deploy-stream.test.tsrg "export async function" srcshould be0.rg "toEffect|effect-interop" srcshould be0.
- Name collisions: if a file already has a hand-written
*Effect(example: deploy), do not route compatibility wrappers to the wrong effect signature. - Codemods can break import blocks; run typecheck immediately after broad transforms.
- Keep command-level error emission consistent (
mapRuntimeError+nextActionsFor(...)).