| ... | ... | @@ -0,0 +1,70 @@ |
| 1 | --- |
| 2 | title: Effect Service Conventions |
| 3 | model: claude-opus-4-8 |
| 4 | effort: high |
| 5 | input: full_diff |
| 6 | tools: |
| 7 | - browse_code |
| 8 | - git_tools |
| 9 | - github_api_read_only |
| 10 | - modify_pr |
| 11 | include: |
| 12 | - "apps/**/*.ts" |
| 13 | - "apps/**/*.tsx" |
| 14 | - "packages/**/*.ts" |
| 15 | - "packages/**/*.tsx" |
| 16 | - "infra/**/*.ts" |
| 17 | - "infra/**/*.tsx" |
| 18 | conclusion: failure |
| 19 | showToolCalls: true |
| 20 | --- |
| 21 | |
| 22 | # Effect service review |
| 23 | |
| 24 | Review changed TypeScript and directly affected call sites for the conventions below. Apply them when a pull request creates, moves, refactors, or consumes an Effect service. Do not demand unrelated repository-wide cleanup. Treat these instructions as authoritative when older code differs. |
| 25 | |
| 26 | ## Imports and module namespaces |
| 27 | |
| 28 | - Import Effect library modules from their subpaths as namespaces, for example `import * as Effect from "effect/Effect"` and `import * as Layer from "effect/Layer"`. Flag consolidated named imports from `"effect"` in touched Effect service code. |
| 29 | - At a service boundary, import the local service module as a namespace and use its public module shape: `WorkspacePaths.WorkspacePaths`, `WorkspacePaths.make`, and `WorkspacePaths.layer`. Flag aliases such as `import { layer as workspacePathsLayer }` that erase the module namespace. |
| 30 | - Namespace imports are not a blanket rule. Keep named imports for whole packages such as `@t3tools/contracts` and `electron`, and for modules used only for a pure helper, error, schema, config value, or standalone type. Do not request `import type * as Contracts`. |
| 31 | - A package subpath that is itself a service module may use a namespace import when callers access its service/tag, `make`, or `layer` members. |
| 32 | - When a barrel exposes an entire service module, prefer `export * as TokenStore from "./tokenStore.ts"` so consumers can use `TokenStore.TokenStore` and `TokenStore.layer`. Do not individually rename `make` and `layer` exports to simulate a namespace. |
| 33 | |
| 34 | ## Service definition |
| 35 | |
| 36 | - Use the canonical single-file order: imports, error/schema declarations, the `Context.Service` tag with its inline interface, `make`, then `layer`. |
| 37 | - Keep a service's schemas/errors, `Context.Service` tag, construction, and layer in one canonical module when they form one implementation. |
| 38 | - Define the service interface inline in the `Context.Service` declaration. Do not retain a standalone `FooShape` or `FooServiceShape` interface/type. |
| 39 | - Refer to the inferred service interface as `Foo["Service"]`, including in mechanically updated orchestration, MCP, tests, and integration harnesses. |
| 40 | - Export a real `make` when the module owns construction. Do not create `make = Effect.succeed(...)` solely to force `Layer.effect`. |
| 41 | - Export the canonical layer as `export const layer = Layer...`. `Layer.effect` is not required: use `Layer.succeed`, `Layer.scoped`, or another appropriate constructor when that matches the implementation. |
| 42 | - In a concrete implementation module already named for the implementation, use plain `make` and `layer` (for example `BunPtyAdapter.ts` and `NodePtyAdapter.ts`). |
| 43 | - Keep implementation-specific names when an abstract port module contains one of several possible implementations, for example `makeCloudflaredRelayClient` and `layerCloudflared` in `RelayClient.ts`. |
| 44 | - `infra/relay/src/db.ts` is an intentional exception: an inline `Layer.succeed(RelayDb, db)` is acceptable without generic `make`/`layer` exports. |
| 45 | |
| 46 | ## Errors and predicates |
| 47 | |
| 48 | - Define service failures with `Schema.TaggedErrorClass` and structured attributes. Derive `message` from those attributes rather than storing an unstructured message as the only data. |
| 49 | - Split semantically distinct failures into separate error classes when a `reason`, `kind`, `phase`, or similar discriminator is used to choose the user-facing message or drive caller control flow. A discriminator used only for internal diagnostics may remain a field. |
| 50 | - Use `Schema.Union` of error classes when a shared schema, predicate, or helper type is useful. |
| 51 | - Export direct schema predicates such as `export const isFoo = Schema.is(Foo)`. Flag a private `Schema.is` constant wrapped by a redundant function with the same signature. |
| 52 | - Do not introduce a large `switch` or lookup table in an error's `message` getter to model failures that deserve separate error classes. |
| 53 | |
| 54 | ## File layout and migrations |
| 55 | |
| 56 | - When combining `domain/Services/Foo.ts` and `domain/Layers/Foo.ts`, hoist the result to `domain/Foo.ts`. |
| 57 | - Delete the old service/layer files. Do not leave compatibility re-export shims. Mechanically update every consumer, including orchestration, MCP, tests, and integration harnesses, to the canonical path. |
| 58 | - Do not flag genuinely separate implementation/adapter modules merely because they remain in an implementation-oriented directory. |
| 59 | - Avoid substantive orchestration or MCP redesign in service-cleanup PRs. Mechanical import, layer, and `Service["Service"]` updates are expected when required to remove obsolete paths or shapes. |
| 60 | |
| 61 | ## Change discipline |
| 62 | |
| 63 | - Preserve useful comments, invariants, and specification documentation while moving code. |
| 64 | - Do not add large tests solely to prove a mechanical refactor. Update existing tests and imports as needed. |
| 65 | - If backend behavior changes, require focused tests. Use test implementations/layers for external services only; do not mock out core business logic. |
| 66 | - Do not require `Layer.effect`, universal namespace imports, generic `make`/`layer` names for abstract-port implementations, separate error classes for diagnostic-only fields, or new tests for import-only changes. |
| 67 | |
| 68 | ## Reporting |
| 69 | |
| 70 | Report only concrete violations introduced or retained in the pull request's changed scope. Prefer precise inline comments on the smallest relevant line range and state the expected fix. A clear convention violation may fail the check. Do not fail for optional style preferences or unrelated legacy code. If there are no findings, report exactly `All clear`. |