docs(effect): add cleanup roadmap (#27228)

This commit is contained in:
Kit Langton
2026-05-12 22:45:26 -04:00
committed by GitHub
parent 588b5240d0
commit 13fbc9acfc
7 changed files with 804 additions and 1072 deletions

View File

@@ -0,0 +1,237 @@
# Effect TODO
Short roadmap for Effect cleanup in `packages/opencode`.
Current patterns and examples live in [`guide.md`](./guide.md). Test
migration rules live in
[`test/EFFECT_TEST_MIGRATION.md`](../../test/EFFECT_TEST_MIGRATION.md).
Older deep-dive notes in this directory may still be useful, but treat
this roadmap and the guide as the current entry points.
This is a planning map, not a verified inventory. Before starting a task,
re-run a targeted `git grep` from current `dev` and update this file if
the inventory changed.
## Priorities
```text
P0 ERR + RENDER + HTTP
Make expected failures typed, render them well, and stop relying on
generic HTTP error guesswork.
P1 TEST
Convert touched tests to the ideal Effect test patterns from the guide.
P2 RF
Move mutable runtime flags into typed runtime/config services.
P3 GLOBAL
Make global paths explicit and remove import-time side effects.
P4 INST + BRIDGE
Remove ambient Instance coupling while keeping Promise/callback interop.
P5 PROC + FS
Replace raw process/filesystem edges with typed Effect services.
P6 OA
Shrink OpenAPI compatibility shims as source schemas improve.
```
## Work Paths
- `ERR` Typed errors — replace legacy `NamedError.create(...)` and
`Effect.die(...)` for expected service failures with
`Schema.TaggedErrorClass` errors on the Effect error channel.
Shrinks: [`NamedError`](../../../core/src/util/error.ts) usage.
- `RENDER` User-visible error rendering — preserve structured typed-error
details at CLI, HTTP, and tool boundaries.
Shrinks: opaque `Error: Name` rendering.
- `HTTP` HTTP route cleanup — make route errors explicit instead of
relying on generic middleware to guess status/body from error names.
Shrinks: [`middleware/error.ts`](../../src/server/routes/instance/httpapi/middleware/error.ts)
and route-level compatibility shims.
- `TEST` Effect test migration — use `testEffect`, `it.live`, and
`it.instance` with explicit layers.
Shrinks: Promise-style tests, sleeps, mutable global test flags.
- `RF` RuntimeFlags / Flag deletion — move mutable
[`Flag`](../../../core/src/flag/flag.ts) reads into typed runtime/config
services.
Shrinks: [`flag.ts`](../../../core/src/flag/flag.ts),
[`test/fixture/flag.ts`](../../test/fixture/flag.ts).
- `GLOBAL` Global paths / import side effects — make global path state
explicit and testable instead of mutable module state.
Shrinks: [`global.ts`](../../../core/src/global.ts) import-time side
effects, mutable `Global.Path` overrides, and its `Flag` dependency.
- `INST` Instance shim — remove ambient `Instance` usage and old ALS
access patterns.
Shrinks: [`src/project/instance.ts`](../../src/project/instance.ts).
- `BRIDGE` Promise/callback interop — keep bridge helpers, but reduce
legacy ALS coupling.
Shrinks: [`src/effect/bridge.ts`](../../src/effect/bridge.ts)
dependency on [`project/instance.ts`](../../src/project/instance.ts).
- `PROC` AppProcess migration — prefer `AppProcess.Service` over raw
process wrappers.
Shrinks: direct spawn callsites and legacy process helpers.
- `FS` AppFileSystem migration — prefer `AppFileSystem.Service` over raw
filesystem APIs.
Shrinks: direct `fs` / `Bun.file` service callsites where inappropriate.
- `RT` Runtime/facade cleanup — remove service-local `makeRuntime`
facades when not intentional.
Shrinks: async facade exports around services and
[`run-service.ts`](../../src/effect/run-service.ts) usage.
- `OA` OpenAPI compatibility — tighten source schemas instead of
post-processing generated OpenAPI.
Shrinks: schema workaround blocks in
[`public.ts`](../../src/server/routes/instance/httpapi/public.ts).
## P0: Errors, Rendering, And HTTP
This should be the next big cleanup theme. The codebase is moving toward
typed Effect failures, but the user-facing boundaries still leak old
shapes and sometimes collapse rich errors into opaque strings.
### Problems
- Some expected service failures still use `NamedError.create(...)`.
- Some expected service failures still become `Effect.die(...)`, which
makes them defects instead of typed, recoverable failures.
- CLI and HTTP boundaries can render structured errors as generic
`Error: SomeName` output.
- HTTP error middleware still guesses status codes from error names like
`Worktree*` or `ProviderAuthValidationFailed`.
- Route handlers and route groups do not consistently declare the public
error body they intend to expose.
- Repeated route error translations do not yet have a clear home: some
should stay inline, some deserve tiny shared mapper helpers.
- Unknown 500s should log full detail server-side while returning a safe
public body.
### Target Shape
- Services define expected failures with `Schema.TaggedErrorClass`.
- Services export an `Error` union and include it in method return types.
- Expected failures stay on the Effect error channel.
- `Effect.die(...)` is reserved for defects: bugs, impossible states,
violated invariants, or final unknown-boundary fallbacks.
- Inside `Effect.gen` / `Effect.fn`, use `yield* new MyError(...)` for
direct expected failures.
- Domain services do not import HTTP status codes, `HttpApiError`, or
route-specific error schemas.
- HTTP route groups make their public error contracts obvious.
- Handlers map service errors to declared HTTP errors at the boundary.
- Shared mapper helpers are only for repeated translations, not a giant
central registry of every domain error.
- Generic HTTP middleware should shrink; it should not accumulate more
name-based domain knowledge.
### First PR Candidates
- [ ] `RENDER-1` Fix CLI top-level rendering for typed config errors.
- [ ] `ERR-1` Convert [`storage/storage.ts`](../../src/storage/storage.ts)
not-found errors.
- [ ] `ERR-2` Convert [`worktree/index.ts`](../../src/worktree/index.ts)
errors and remove matching HTTP name checks where possible.
- [ ] `ERR-3` Convert [`provider/auth.ts`](../../src/provider/auth.ts)
validation errors.
- [ ] `HTTP-1` Remove the unknown-500 stack leak from
[`middleware/error.ts`](../../src/server/routes/instance/httpapi/middleware/error.ts).
- [ ] `HTTP-2` Audit one route group for explicit error contracts and
decide which mappings stay inline vs. shared helper.
## P1: Tests
When touching tests, migrate them toward the ideal patterns in
[`test/EFFECT_TEST_MIGRATION.md`](../../test/EFFECT_TEST_MIGRATION.md):
- Use `testEffect(...)` with explicit layers.
- Prefer `it.instance(...)` for service tests that need an instance.
- Prefer `it.live(...)` for real timers, filesystem mtimes, child
processes, git, locks, or other live integration behavior.
- Avoid sleeps; wait on real events or deterministic state transitions.
- Do not mutate `process.env` or mutable globals after layers are built.
- Use explicit layer variants, such as `RuntimeFlags.layer(...)`, for
behavior changes.
## P2: RuntimeFlags / Flag Deletion
Recently completed:
- [x] Plugin/pure-mode flags moved to RuntimeFlags.
- [x] Tool visibility flags moved to RuntimeFlags.
- [x] Built-in websearch provider selection uses the same runtime flags as
tool visibility.
- [x] Removed global default-plugin disabling from test preload.
Recommended next PRs:
```text
RF-1 scout consumers ─┐
├─ can run in parallel
RF-2 plan-mode prompt ┘
└─ RF-3 event-system cluster, stacked only if RF-2 still touches prompt.ts
RF-4 workspaces cluster: later, after mutable Flag tests are cleaned up
```
- [ ] `RF-1` Move scout reads in [`agent.ts`](../../src/agent/agent.ts)
and [`reference.ts`](../../src/reference/reference.ts).
- [ ] `RF-2` Move plan-mode prompt read in
[`session/prompt.ts`](../../src/session/prompt.ts).
- [ ] `RF-3` Move event-system reads in session prompt/processor/
compaction and TUI debug plugin.
- [ ] `RF-4` Move workspaces reads in session/sync/control-plane after
tests stop relying on mutable `Flag` timing.
- [ ] Delete [`test/fixture/flag.ts`](../../test/fixture/flag.ts) once
tests no longer mutate `Flag`.
- [ ] Delete [`flag.ts`](../../../core/src/flag/flag.ts) once no packages
import it.
## P3: Global Paths
[`global.ts`](../../../core/src/global.ts) is real connective tissue, not
just cosmetic ugliness. It currently mixes path calculation, import-time
directory creation, `Flock` setup, mutable exported `Path` state, and a
`Flag` dependency.
Problems to reduce:
- Importing the module creates directories.
- Tests override `Global.Path` by mutating exported module state.
- Most callers use `Global.Path` directly instead of the Effect service.
- `Global.make()` still reads mutable `Flag.OPENCODE_CONFIG_DIR`.
Next PR candidates:
- [ ] Replace mutable `Global.Path` test overrides with explicit test
layers or scoped helpers.
- [ ] Move directory creation and `Flock` setup behind an explicit init
boundary where possible.
- [ ] Remove the `Flag` dependency from global path resolution.
## P4: Instance And Bridge
[`project/instance.ts`](../../src/project/instance.ts) is the deletion
target. [`effect/bridge.ts`](../../src/effect/bridge.ts) is not a near-term
deletion target; Promise/callback interop will continue to exist.
Goal:
- Keep a sanctioned bridge for Promise/callback boundaries.
- Reduce bridge dependence on legacy `Instance.restore` / `Instance.current`.
- Move callers toward `InstanceRef`, `WorkspaceRef`, `InstanceState`, or
explicit context where practical.
- Delete `project/instance.ts` only after ambient Instance coupling is gone.
## Lower Priority Tracks
- `PROC` / `FS` — continue AppProcess and AppFileSystem migrations as
focused PRs when touching relevant files.
- `RT` — remove service-local runtime facades only when they are not an
intentional boundary.
- `OA` — shrink [`public.ts`](../../src/server/routes/instance/httpapi/public.ts)
by tightening source schemas one workaround at a time.
- `fetch``HttpClient` — migrate raw fetch callsites when the caller is
already effectful or being effectified.
- `Tools` — remaining tool cleanup is narrow: `webfetch` HTML extraction
and `shell` raw stream/promise edges.