2
0
Эх сурвалжийг харах

docs(effect): refresh migration status specs (#23206)

Kit Langton 3 өдөр өмнө
parent
commit
b493dabfe6

+ 32 - 49
packages/opencode/specs/effect/facades.md

@@ -1,12 +1,13 @@
 # Facade removal checklist
 
-Concrete inventory of the remaining `makeRuntime(...)`-backed service facades in `packages/opencode`.
+Concrete inventory of the remaining `makeRuntime(...)`-backed facades in `packages/opencode`.
 
-As of 2026-04-13, latest `origin/dev`:
+Current status on this branch:
 
-- `src/` still has 15 `makeRuntime(...)` call sites.
-- 13 of those are still in scope for facade removal.
-- 2 are excluded from this checklist: `bus/index.ts` and `effect/cross-spawn-spawner.ts`.
+- `src/` has 5 `makeRuntime(...)` call sites total.
+- 2 are intentionally excluded from this checklist: `src/bus/index.ts` and `src/effect/cross-spawn-spawner.ts`.
+- 1 is tracked primarily by the instance-context migration rather than facade removal: `src/project/instance.ts`.
+- That leaves 2 live runtime-backed service facades still worth tracking here: `src/npm/index.ts` and `src/cli/cmd/tui/config/tui.ts`.
 
 Recent progress:
 
@@ -15,8 +16,9 @@ Recent progress:
 
 ## Priority hotspots
 
-- `server/instance/session.ts` still depends on `Session`, `SessionPrompt`, `SessionRevert`, `SessionCompaction`, `SessionSummary`, `ShareSession`, `Agent`, and `Permission` facades.
-- `src/effect/app-runtime.ts` still references many facade namespaces directly, so it should stay in view during each deletion.
+- `src/cli/cmd/tui/config/tui.ts` still exports `makeRuntime(...)` plus async facade helpers for `get()` and `waitForDependencies()`.
+- `src/npm/index.ts` still exports `makeRuntime(...)` plus async facade helpers for `install()`, `add()`, `outdated()`, and `which()`.
+- `src/project/instance.ts` still uses a dedicated runtime for project boot, but that file is really part of the broader legacy instance-context transition tracked in `instance-context.md`.
 
 ## Completed Batches
 
@@ -184,53 +186,34 @@ These were the recurring mistakes and useful corrections from the first two batc
 5. For CLI readability, extract file-local preload helpers when the handler starts doing config load + service load + batched effect fanout inline.
 6. When rebasing a facade branch after nearby merges, prefer the already-cleaned service/test version over older inline facade-era code.
 
-## Next batch
+## Remaining work
 
-Recommended next five, in order:
+Most of the original facade-removal backlog is already done. The practical remaining work is narrower now:
 
-1. `src/permission/index.ts`
-2. `src/agent/agent.ts`
-3. `src/session/summary.ts`
-4. `src/session/revert.ts`
-5. `src/mcp/auth.ts`
-
-Why this batch:
-
-- It keeps pushing the session-adjacent cleanup without jumping straight into `session/index.ts` or `session/prompt.ts`.
-- `Permission`, `Agent`, `SessionSummary`, and `SessionRevert` all reduce fanout in `server/instance/session.ts`.
-- `McpAuth` is small and closely related to the just-landed `MCP` cleanup.
-
-After that batch, the expected follow-up is the main session cluster:
-
-1. `src/session/index.ts`
-2. `src/session/prompt.ts`
-3. `src/session/compaction.ts`
+1. remove the `Npm` runtime-backed facade from `src/npm/index.ts`
+2. remove the `TuiConfig` runtime-backed facade from `src/cli/cmd/tui/config/tui.ts`
+3. keep `src/project/instance.ts` in the separate instance-context migration, not this checklist
 
 ## Checklist
 
-- [ ] `src/session/index.ts` (`Session`) - facades: `create`, `fork`, `get`, `setTitle`, `setArchived`, `setPermission`, `setRevert`, `messages`, `children`, `remove`, `updateMessage`, `removeMessage`, `removePart`, `updatePart`; main callers: `server/instance/session.ts`, `cli/cmd/session.ts`, `cli/cmd/export.ts`, `cli/cmd/github.ts`; tests: `test/server/session-actions.test.ts`, `test/server/session-list.test.ts`, `test/server/global-session-list.test.ts`
-- [ ] `src/session/prompt.ts` (`SessionPrompt`) - facades: `prompt`, `resolvePromptParts`, `cancel`, `loop`, `shell`, `command`; main callers: `server/instance/session.ts`, `cli/cmd/github.ts`; tests: `test/session/prompt.test.ts`, `test/session/prompt-effect.test.ts`, `test/session/structured-output-integration.test.ts`
-- [ ] `src/session/revert.ts` (`SessionRevert`) - facades: `revert`, `unrevert`, `cleanup`; main callers: `server/instance/session.ts`; tests: `test/session/revert-compact.test.ts`
-- [ ] `src/session/compaction.ts` (`SessionCompaction`) - facades: `isOverflow`, `prune`, `create`; main callers: `server/instance/session.ts`; tests: `test/session/compaction.test.ts`
-- [ ] `src/session/summary.ts` (`SessionSummary`) - facades: `summarize`, `diff`; main callers: `session/prompt.ts`, `session/processor.ts`, `server/instance/session.ts`; tests: `test/session/snapshot-tool-race.test.ts`
-- [ ] `src/share/session.ts` (`ShareSession`) - facades: `create`, `share`, `unshare`; main callers: `server/instance/session.ts`, `cli/cmd/github.ts`
-- [ ] `src/agent/agent.ts` (`Agent`) - facades: `get`, `list`, `defaultAgent`, `generate`; main callers: `cli/cmd/agent.ts`, `server/instance/session.ts`, `server/instance/experimental.ts`; tests: `test/agent/agent.test.ts`
-- [ ] `src/permission/index.ts` (`Permission`) - facades: `ask`, `reply`, `list`; main callers: `server/instance/permission.ts`, `server/instance/session.ts`, `session/llm.ts`; tests: `test/permission/next.test.ts`
-- [x] `src/file/index.ts` (`File`) - facades removed and merged.
-- [x] `src/lsp/index.ts` (`LSP`) - facades removed and merged.
-- [x] `src/mcp/index.ts` (`MCP`) - facades removed and merged.
-- [x] `src/config/config.ts` (`Config`) - facades removed and merged.
-- [x] `src/provider/provider.ts` (`Provider`) - facades removed and merged.
-- [x] `src/pty/index.ts` (`Pty`) - facades removed and merged.
-- [x] `src/skill/index.ts` (`Skill`) - facades removed and merged.
-- [x] `src/project/vcs.ts` (`Vcs`) - facades removed and merged.
-- [x] `src/tool/registry.ts` (`ToolRegistry`) - facades removed and merged.
-- [ ] `src/worktree/index.ts` (`Worktree`) - facades: `makeWorktreeInfo`, `createFromInfo`, `create`, `remove`, `reset`; main callers: `control-plane/adaptors/worktree.ts`, `server/instance/experimental.ts`; tests: `test/project/worktree.test.ts`, `test/project/worktree-remove.test.ts`
-- [x] `src/auth/index.ts` (`Auth`) - facades removed and merged.
-- [ ] `src/mcp/auth.ts` (`McpAuth`) - facades: `get`, `getForUrl`, `all`, `set`, `remove`, `updateTokens`, `updateClientInfo`, `updateCodeVerifier`, `updateOAuthState`; main callers: `mcp/oauth-provider.ts`, `cli/cmd/mcp.ts`; tests: `test/mcp/oauth-auto-connect.test.ts`
-- [ ] `src/plugin/index.ts` (`Plugin`) - facades: `trigger`, `list`, `init`; main callers: `agent/agent.ts`, `session/llm.ts`, `project/bootstrap.ts`; tests: `test/plugin/trigger.test.ts`, `test/provider/provider.test.ts`
-- [ ] `src/project/project.ts` (`Project`) - facades: `fromDirectory`, `discover`, `initGit`, `update`, `sandboxes`, `addSandbox`, `removeSandbox`; main callers: `project/instance.ts`, `server/instance/project.ts`, `server/instance/experimental.ts`; tests: `test/project/project.test.ts`, `test/project/migrate-global.test.ts`
-- [ ] `src/snapshot/index.ts` (`Snapshot`) - facades: `init`, `track`, `patch`, `restore`, `revert`, `diff`, `diffFull`; main callers: `project/bootstrap.ts`, `cli/cmd/debug/snapshot.ts`; tests: `test/snapshot/snapshot.test.ts`, `test/session/revert-compact.test.ts`
+- [ ] `src/npm/index.ts` (`Npm`) - still exports runtime-backed async facade helpers on top of `Npm.Service`
+- [ ] `src/cli/cmd/tui/config/tui.ts` (`TuiConfig`) - still exports runtime-backed async facade helpers on top of `TuiConfig.Service`
+- [x] `src/session/session.ts` / `src/session/prompt.ts` / `src/session/revert.ts` / `src/session/summary.ts` - service-local facades removed
+- [x] `src/agent/agent.ts` (`Agent`) - service-local facades removed
+- [x] `src/permission/index.ts` (`Permission`) - service-local facades removed
+- [x] `src/worktree/index.ts` (`Worktree`) - service-local facades removed
+- [x] `src/plugin/index.ts` (`Plugin`) - service-local facades removed
+- [x] `src/snapshot/index.ts` (`Snapshot`) - service-local facades removed
+- [x] `src/file/index.ts` (`File`) - facades removed and merged
+- [x] `src/lsp/index.ts` (`LSP`) - facades removed and merged
+- [x] `src/mcp/index.ts` (`MCP`) - facades removed and merged
+- [x] `src/config/config.ts` (`Config`) - facades removed and merged
+- [x] `src/provider/provider.ts` (`Provider`) - facades removed and merged
+- [x] `src/pty/index.ts` (`Pty`) - facades removed and merged
+- [x] `src/skill/index.ts` (`Skill`) - facades removed and merged
+- [x] `src/project/vcs.ts` (`Vcs`) - facades removed and merged
+- [x] `src/tool/registry.ts` (`ToolRegistry`) - facades removed and merged
+- [x] `src/auth/index.ts` (`Auth`) - facades removed and merged
 
 ## Excluded `makeRuntime(...)` sites
 

+ 27 - 27
packages/opencode/specs/effect/http-api.md

@@ -76,7 +76,7 @@ Many route boundaries still use Zod-first validators. That does not block all ex
 
 ### Mixed handler styles
 
-Many current `server/instance/*.ts` handlers still call async facades directly. Migrating those to composed `Effect.gen(...)` handlers is the low-risk step to do first.
+Many current `server/routes/instance/*.ts` handlers still mix composed Effect code with smaller Promise- or ALS-backed seams. Migrating those to consistent `Effect.gen(...)` handlers is the low-risk step to do first.
 
 ### Non-JSON routes
 
@@ -90,7 +90,7 @@ The current server composition, middleware, and docs flow are Hono-centered toda
 
 ### 1. Finish the prerequisites first
 
-- continue route-handler effectification in `server/instance/*.ts`
+- continue route-handler effectification in `server/routes/instance/*.ts`
 - continue schema migration toward Effect Schema-first DTOs and errors
 - keep removing service facades
 
@@ -98,9 +98,9 @@ The current server composition, middleware, and docs flow are Hono-centered toda
 
 Introduce one small `HttpApi` group for plain JSON endpoints only. Good initial candidates are the least stateful endpoints in:
 
-- `server/instance/question.ts`
-- `server/instance/provider.ts`
-- `server/instance/permission.ts`
+- `server/routes/instance/question.ts`
+- `server/routes/instance/provider.ts`
+- `server/routes/instance/permission.ts`
 
 Avoid `session.ts`, SSE, websocket, and TUI-facing routes first.
 
@@ -155,9 +155,9 @@ This gives:
 
 As each route group is ported to `HttpApi`:
 
-1. change its `root` path from `/experimental/httpapi/<group>` to `/<group>`
-2. add `.all("/<group>", handler)` / `.all("/<group>/*", handler)` to the flag block in `instance/index.ts`
-3. for partial ports (e.g. only `GET /provider/auth`), bridge only the specific path
+1. add `.get(...)` / `.post(...)` bridge entries to the flag block in `server/routes/instance/index.ts`
+2. for partial ports (e.g. only `GET /provider/auth`), bridge only the specific path
+3. keep the legacy Hono route registered behind it for OpenAPI / SDK generation until the spec pipeline changes
 4. verify SDK output is unchanged
 
 Leave streaming-style endpoints on Hono until there is a clear reason to move them.
@@ -267,7 +267,7 @@ Use the same sequence for each route group.
 3. Apply the schema migration ordering above so those types are Effect Schema-first.
 4. Define the `HttpApi` contract separately from the handlers.
 5. Implement handlers by yielding the existing service from context.
-6. Mount the new surface in parallel under an experimental prefix.
+6. Mount the new surface in parallel behind the `OPENCODE_EXPERIMENTAL_HTTPAPI` bridge.
 7. Regenerate the SDK and verify zero diff against `dev` (see SDK shape rule above).
 8. Add one end-to-end test and one OpenAPI-focused test.
 9. Compare ergonomics before migrating the next endpoint.
@@ -286,20 +286,20 @@ Placement rule:
 - keep `HttpApi` code under `src/server`, not `src/effect`
 - `src/effect` should stay focused on runtimes, layers, instance state, and shared Effect plumbing
 - place each `HttpApi` slice next to the HTTP boundary it serves
-- for instance-scoped routes, prefer `src/server/instance/httpapi/*`
-- if control-plane routes ever migrate, prefer `src/server/control/httpapi/*`
+- for instance-scoped routes, prefer `src/server/routes/instance/httpapi/*`
+- if control-plane routes ever migrate, prefer `src/server/routes/control/httpapi/*`
 
 Suggested file layout for a repeatable spike:
 
-- `src/server/instance/httpapi/question.ts` — contract and handler layer for one route group
-- `src/server/instance/httpapi/server.ts` — standalone Effect HTTP server that composes all groups
-- `test/server/question-httpapi.test.ts` — end-to-end test against the real service
+- `src/server/routes/instance/httpapi/question.ts` — contract and handler layer for one route group
+- `src/server/routes/instance/httpapi/server.ts` — bridged Effect HTTP layer that composes all groups
+- route or OpenAPI verification should live alongside the existing server tests; there is no dedicated `question-httpapi` test file on this branch
 
 Suggested responsibilities:
 
 - `question.ts` defines the `HttpApi` contract and `HttpApiBuilder.group(...)` handlers
-- `server.ts` composes all route groups into one `HttpRouter.serve` layer with shared middleware (auth, instance lookup)
-- tests use `ExperimentalHttpApiServer.layerTest` to run against a real in-process HTTP server
+- `server.ts` composes all route groups into one `HttpRouter.toWebHandler(...)` bridge with shared middleware (auth, instance lookup)
+- tests should verify the bridged routes through the normal server surface
 
 ## Example migration shape
 
@@ -319,33 +319,33 @@ Each route-group spike should follow the same shape.
 - keep handler bodies thin
 - keep transport mapping at the HTTP boundary only
 
-### 3. Standalone server
+### 3. Bridged server
 
-- the Effect HTTP server is self-contained in `httpapi/server.ts`
-- it is **not** mounted into the Hono app — no bridge, no `toWebHandler`
-- route paths use the `/experimental/httpapi` prefix so they match the eventual cutover
-- each route group exposes its own OpenAPI doc endpoint
+- the Effect HTTP layer is composed in `httpapi/server.ts`
+- it is mounted into the Hono app via `HttpRouter.toWebHandler(...)`
+- routes keep their normal instance paths and are gated by the `OPENCODE_EXPERIMENTAL_HTTPAPI` flag
+- the legacy Hono handlers stay registered after the bridge so current OpenAPI / SDK generation still works
 
 ### 4. Verification
 
 - seed real state through the existing service
-- call the experimental endpoints
+- call the bridged endpoints with the flag enabled
 - assert that the service behavior is unchanged
 - assert that the generated OpenAPI contains the migrated paths and schemas
 
 ## Boundary composition
 
-The standalone Effect server owns its own middleware stack. It does not share middleware with the Hono server.
+The Effect `HttpApi` layer owns its own auth and instance middleware, but it is currently mounted inside the existing Hono server.
 
 ### Auth
 
-- the standalone server implements auth as an `HttpApiMiddleware.Service` using `HttpApiSecurity.basic`
+- the bridged `HttpApi` layer implements auth as an `HttpApiMiddleware.Service` using `HttpApiSecurity.basic`
 - each route group's `HttpApi` is wrapped with `.middleware(Authorization)` before being served
-- this is independent of the Hono `AuthMiddleware` — when the Effect server eventually replaces Hono, this becomes the only auth layer
+- this is independent of the Hono auth layer; the current bridge keeps the responsibility local to the `HttpApi` slice
 
 ### Instance and workspace lookup
 
-- the standalone server resolves instance context via an `HttpRouter.middleware` that reads `x-opencode-directory` headers and `directory` query params
+- the bridged `HttpApi` layer resolves instance context via an `HttpRouter.middleware` that reads `x-opencode-directory` headers and `directory` query params
 - this is the Effect equivalent of the Hono `WorkspaceRouterMiddleware`
 - `HttpApi` handlers yield services from context and assume the correct instance has already been provided
 
@@ -360,7 +360,7 @@ The standalone Effect server owns its own middleware stack. It does not share mi
 
 The first slice is successful if:
 
-- the standalone Effect server starts and serves the endpoints independently of the Hono server
+- the bridged endpoints serve correctly through the existing Hono host when the flag is enabled
 - the handlers reuse the existing Effect service
 - request decoding and response shapes are schema-defined from canonical Effect schemas
 - any remaining Zod boundary usage is derived from `.zod` or clearly temporary

+ 10 - 10
packages/opencode/specs/effect/instance-context.md

@@ -157,7 +157,7 @@ Direct legacy usage means any source file that still calls one of:
 - `Instance.reload(...)`
 - `Instance.dispose()` / `Instance.disposeAll()`
 
-Current total: `54` files in `packages/opencode/src`.
+Current total: `56` files in `packages/opencode/src`.
 
 ### Core bridge and plumbing
 
@@ -177,13 +177,13 @@ Migration rule:
 
 These are the current request-entry seams that still create or consume instance context through the legacy helper.
 
-- `src/server/instance/middleware.ts`
-- `src/server/instance/index.ts`
-- `src/server/instance/project.ts`
-- `src/server/instance/workspace.ts`
-- `src/server/instance/file.ts`
-- `src/server/instance/experimental.ts`
-- `src/server/instance/global.ts`
+- `src/server/routes/instance/middleware.ts`
+- `src/server/routes/instance/index.ts`
+- `src/server/routes/instance/project.ts`
+- `src/server/routes/control/workspace.ts`
+- `src/server/routes/instance/file.ts`
+- `src/server/routes/instance/experimental.ts`
+- `src/server/routes/global.ts`
 
 Migration rule:
 
@@ -239,7 +239,7 @@ Migration rule:
 These modules are already the best near-term migration targets because they are in Effect code but still read sync getters from the legacy helper.
 
 - `src/agent/agent.ts`
-- `src/config/tui-migrate.ts`
+- `src/cli/cmd/tui/config/tui-migrate.ts`
 - `src/file/index.ts`
 - `src/file/watcher.ts`
 - `src/format/formatter.ts`
@@ -250,7 +250,7 @@ These modules are already the best near-term migration targets because they are
 - `src/project/vcs.ts`
 - `src/provider/provider.ts`
 - `src/pty/index.ts`
-- `src/session/index.ts`
+- `src/session/session.ts`
 - `src/session/instruction.ts`
 - `src/session/llm.ts`
 - `src/session/system.ts`

+ 6 - 8
packages/opencode/specs/effect/loose-ends.md

@@ -4,11 +4,11 @@ Small follow-ups that do not fit neatly into the main facade, route, tool, or sc
 
 ## Config / TUI
 
-- [ ] `config/tui.ts` - finish the internal Effect migration after the `Instance.state(...)` removal.
+- [ ] `cli/cmd/tui/config/tui.ts` - finish the internal Effect migration.
       Keep the current precedence and migration semantics intact while converting the remaining internal async helpers (`loadState`, `mergeFile`, `loadFile`, `load`) to `Effect.gen(...)` / `Effect.fn(...)`.
-- [ ] `config/tui.ts` callers - once the internal service is stable, migrate plain async callers to use `TuiConfig.Service` directly where that actually simplifies the code.
+- [ ] `cli/cmd/tui/config/tui.ts` callers - once the internal service is stable, migrate plain async callers to use `TuiConfig.Service` directly where that actually simplifies the code.
       Likely first callers: `cli/cmd/tui/attach.ts`, `cli/cmd/tui/thread.ts`, `cli/cmd/tui/plugin/runtime.ts`.
-- [ ] `env/index.ts` - move the last production `Instance.state(...)` usage onto `InstanceState` (or its replacement) so `Instance.state` can be deleted.
+- [x] `env/index.ts` - already uses `InstanceState.make(...)`.
 
 ## ConfigPaths
 
@@ -21,14 +21,12 @@ Small follow-ups that do not fit neatly into the main facade, route, tool, or sc
   - `readFile(...)`
   - `parseText(...)`
 - [ ] `config/config.ts` - switch internal config loading from `Effect.promise(() => ConfigPaths.*(...))` to `yield* paths.*(...)` once the service exists.
-- [ ] `config/tui.ts` - switch TUI config loading from async `ConfigPaths.*` wrappers to the `ConfigPaths.Service` once that service exists.
-- [ ] `config/tui-migrate.ts` - decide whether to leave this as a plain async module using wrapper functions or effectify it fully after `ConfigPaths.Service` lands.
+- [ ] `cli/cmd/tui/config/tui.ts` - switch TUI config loading from async `ConfigPaths.*` wrappers to the `ConfigPaths.Service` once that service exists.
+- [ ] `cli/cmd/tui/config/tui-migrate.ts` - decide whether to leave this as a plain async module using wrapper functions or effectify it fully after `ConfigPaths.Service` lands.
 
 ## Instance cleanup
 
-- [ ] `project/instance.ts` - remove `Instance.state(...)` once `env/index.ts` is migrated.
-- [ ] `project/state.ts` - delete the bespoke per-instance state helper after the last production caller is gone.
-- [ ] `test/project/state.test.ts` - replace or delete the old `Instance.state(...)` tests after the removal.
+- [ ] `project/instance.ts` - keep shrinking the legacy ALS / Promise cache after the remaining `Instance.*` callers move over.
 
 ## Notes
 

+ 30 - 40
packages/opencode/specs/effect/migration.md

@@ -19,53 +19,43 @@ See `instance-context.md` for the phased plan to remove the legacy ALS / promise
 
 ## Service shape
 
-Every service follows the same pattern — a single namespace with the service definition, layer, `runPromise`, and async facade functions:
+Every service follows the same pattern: one module, flat top-level exports, traced Effect methods, and a self-reexport at the bottom when the file is the public module.
 
 ```ts
-export namespace Foo {
-  export interface Interface {
-    readonly get: (id: FooID) => Effect.Effect<FooInfo, FooError>
-  }
-
-  export class Service extends Context.Service<Service, Interface>()("@opencode/Foo") {}
-
-  export const layer = Layer.effect(
-    Service,
-    Effect.gen(function* () {
-      // For instance-scoped services:
-      const state = yield* InstanceState.make<State>(
-        Effect.fn("Foo.state")(() => Effect.succeed({ ... })),
-      )
+export interface Interface {
+  readonly get: (id: FooID) => Effect.Effect<FooInfo, FooError>
+}
 
-      const get = Effect.fn("Foo.get")(function* (id: FooID) {
-        const s = yield* InstanceState.get(state)
-        // ...
-      })
+export class Service extends Context.Service<Service, Interface>()("@opencode/Foo") {}
 
-      return Service.of({ get })
-    }),
-  )
+export const layer = Layer.effect(
+  Service,
+  Effect.gen(function* () {
+    const state = yield* InstanceState.make<State>(
+      Effect.fn("Foo.state")(() => Effect.succeed({ ... })),
+    )
 
-  // Optional: wire dependencies
-  export const defaultLayer = layer.pipe(Layer.provide(FooDep.layer))
+    const get = Effect.fn("Foo.get")(function* (id: FooID) {
+      const s = yield* InstanceState.get(state)
+      // ...
+    })
 
-  // Per-service runtime (inside the namespace)
-  const { runPromise } = makeRuntime(Service, defaultLayer)
+    return Service.of({ get })
+  }),
+)
 
-  // Async facade functions
-  export async function get(id: FooID) {
-    return runPromise((svc) => svc.get(id))
-  }
-}
+export const defaultLayer = layer.pipe(Layer.provide(FooDep.layer))
+
+export * as Foo from "."
 ```
 
 Rules:
 
-- Keep everything in one namespace, one file — no separate `service.ts` / `index.ts` split
-- `runPromise` goes inside the namespace (not exported unless tests need it)
-- Facade functions are plain `async function` — no `fn()` wrappers
-- Use `Effect.fn("Namespace.method")` for all Effect functions (for tracing)
-- No `Layer.fresh` — InstanceState handles per-directory isolation
+- Keep the service surface in one module; prefer flat top-level exports over `export namespace Foo { ... }`
+- Use `Effect.fn("Foo.method")` for Effect methods
+- Use a self-reexport (`export * as Foo from "."` or `"./foo"`) for the public namespace projection
+- Avoid service-local `makeRuntime(...)` facades unless a file is still intentionally in the older migration phase
+- No `Layer.fresh` for normal per-directory isolation; use `InstanceState`
 
 ## Schema → Zod interop
 
@@ -266,7 +256,7 @@ Tool-specific filesystem cleanup notes live in `tools.md`.
 
 ## Destroying the facades
 
-This phase is still broadly open. As of 2026-04-13 there are still 15 `makeRuntime(...)` call sites under `src/`, with 13 still in scope for facade removal. The live checklist now lives in `facades.md`.
+This phase is no longer broadly open. There are 5 `makeRuntime(...)` call sites under `src/`, and only a small subset are still ordinary facade-removal targets. The live checklist now lives in `facades.md`.
 
 These facades exist because cyclic imports used to force each service to build its own independent runtime. Now that the layer DAG is acyclic and `AppRuntime` (`src/effect/app-runtime.ts`) composes everything into one `ManagedRuntime`, we're removing them.
 
@@ -297,11 +287,11 @@ For each service, the migration is roughly:
 - `ShareNext` — migrated 2026-04-11. Swapped remaining async callers to `AppRuntime.runPromise(ShareNext.Service.use(...))`, removed the `makeRuntime(...)` facade, and kept instance bootstrap on the shared app runtime.
 - `SessionTodo` — migrated 2026-04-10. Already matched the target service shape in `session/todo.ts`: single namespace, traced Effect methods, and no `makeRuntime(...)` facade remained; checklist updated to reflect the completed migration.
 - `Storage` — migrated 2026-04-10. One production caller (`Session.diff`) and all storage.test.ts tests converted to effectful style. Facades and `makeRuntime` removed.
-- `SessionRunState` — migrated 2026-04-11. Single caller in `server/instance/session.ts` converted; facade removed.
-- `Account` — migrated 2026-04-11. Callers in `server/instance/experimental.ts` and `cli/cmd/account.ts` converted; facade removed.
+- `SessionRunState` — migrated 2026-04-11. Single caller in `server/routes/instance/session.ts` converted; facade removed.
+- `Account` — migrated 2026-04-11. Callers in `server/routes/instance/experimental.ts` and `cli/cmd/account.ts` converted; facade removed.
 - `Instruction` — migrated 2026-04-11. Test-only callers converted; facade removed.
 - `FileWatcher` — migrated 2026-04-11. Callers in `project/bootstrap.ts` and test converted; facade removed.
-- `Question` — migrated 2026-04-11. Callers in `server/instance/question.ts` and test converted; facade removed.
+- `Question` — migrated 2026-04-11. Callers in `server/routes/instance/question.ts` and test converted; facade removed.
 - `Truncate` — migrated 2026-04-11. Caller in `tool/tool.ts` and test converted; facade removed.
 
 ## Route handler effectification

+ 16 - 18
packages/opencode/specs/effect/routes.md

@@ -39,28 +39,26 @@ This eliminates multiple `runPromise` round-trips and lets handlers compose natu
 
 ## Current route files
 
-Current instance route files live under `src/server/instance`, not `server/routes`.
+Current instance route files live under `src/server/routes/instance`.
 
-The main migration targets are:
+Files that are already mostly on the intended service-yielding shape:
 
-- [ ] `server/instance/session.ts` — heaviest; still has many direct facade calls for Session, SessionPrompt, SessionRevert, SessionCompaction, SessionShare, SessionSummary, Agent, Bus
-- [ ] `server/instance/global.ts` — still has direct facade calls for Config and instance lifecycle actions
-- [ ] `server/instance/provider.ts` — still has direct facade calls for Config and Provider
-- [ ] `server/instance/question.ts` — partially converted; still worth tracking here until it consistently uses the composed style
-- [ ] `server/instance/pty.ts` — still calls Pty facades directly
-- [ ] `server/instance/experimental.ts` — mixed state; some handlers are already composed, others still use facades
+- [x] `server/routes/instance/question.ts` — handlers yield `Question.Service`
+- [x] `server/routes/instance/provider.ts` — handlers yield `Provider.Service`, `ProviderAuth.Service`, and `Config.Service`
+- [x] `server/routes/instance/permission.ts` — handlers yield `Permission.Service`
+- [x] `server/routes/instance/mcp.ts` — handlers mostly yield `MCP.Service`
+- [x] `server/routes/instance/pty.ts` — handlers yield `Pty.Service`
 
-Additional route files that still participate in the migration:
+Files still worth tracking here:
 
-- [ ] `server/instance/index.ts` — Vcs, Agent, Skill, LSP, Format
-- [ ] `server/instance/file.ts` — Ripgrep, File, LSP
-- [ ] `server/instance/mcp.ts` — MCP facade-heavy
-- [ ] `server/instance/permission.ts` — Permission
-- [ ] `server/instance/workspace.ts` — Workspace
-- [ ] `server/instance/tui.ts` — Bus and Session
-- [ ] `server/instance/middleware.ts` — Session and Workspace lookups
+- [ ] `server/routes/instance/session.ts` — still the heaviest mixed file; many handlers are composed, but the file still mixes patterns and has direct `Bus.publish(...)` / `Session.list(...)` usage
+- [ ] `server/routes/instance/index.ts` — mostly converted, but still has direct `Instance.dispose()` / `Instance.*` reads for `/instance/dispose` and `/path`
+- [ ] `server/routes/instance/file.ts` — most handlers yield services, but `/find` still passes `Instance.directory` directly into ripgrep and `/find/symbol` is still stubbed
+- [ ] `server/routes/instance/experimental.ts` — mixed state; many handlers are composed, but some still rely on `runRequest(...)` or direct `Instance.project` reads
+- [ ] `server/routes/instance/middleware.ts` — still enters the instance via `Instance.provide(...)`
+- [ ] `server/routes/global.ts` — still uses `Instance.disposeAll()` and remains partly outside the fully-composed style
 
 ## Notes
 
-- Some handlers already use `AppRuntime.runPromise(Effect.gen(...))` in isolated places. Keep pushing those files toward one consistent style.
-- Route conversion is closely tied to facade removal. As services lose `makeRuntime`-backed async exports, route handlers should switch to yielding the service directly.
+- Route conversion is now less about facade removal and more about removing the remaining direct `Instance.*` reads, `Instance.provide(...)` boundaries, and small Promise-style bridges inside route files.
+- `jsonRequest(...)` / `runRequest(...)` already provide a good intermediate shape for many handlers. The remaining cleanup is mostly consistency work in the heavier files.

+ 19 - 17
packages/opencode/specs/effect/server-package.md

@@ -40,13 +40,13 @@ Everything still lives in `packages/opencode`.
 Important current facts:
 
 - there is no `packages/core` or `packages/cli` workspace yet
-- `packages/server` now exists as a minimal scaffold package, but it does not own any real route contracts, handlers, or runtime composition yet
+- there is no `packages/server` workspace yet on this branch
 - the main host server is still Hono-based in `src/server/server.ts`
 - current OpenAPI generation is Hono-based through `Server.openapi()` and `cli/cmd/generate.ts`
 - the Effect runtime and app layer are centralized in `src/effect/app-runtime.ts` and `src/effect/run-service.ts`
-- there is already one experimental Effect `HttpApi` slice at `src/server/instance/httpapi/question.ts`
-- that experimental slice is mounted under `/experimental/httpapi/question`
-- that experimental slice already has an end-to-end test at `test/server/question-httpapi.test.ts`
+- there are already bridged Effect `HttpApi` slices under `src/server/routes/instance/httpapi/*`
+- those slices are mounted into the Hono server behind `OPENCODE_EXPERIMENTAL_HTTPAPI`
+- the bridge currently covers `question`, `permission`, `provider`, partial `config`, and partial `project` routes
 
 This means the package split should start from an extraction path, not from greenfield package ownership.
 
@@ -209,17 +209,19 @@ Current host and route composition:
 
 - `src/server/server.ts`
 - `src/server/control/index.ts`
-- `src/server/instance/index.ts`
+- `src/server/routes/instance/index.ts`
 - `src/server/middleware.ts`
 - `src/server/adapter.bun.ts`
 - `src/server/adapter.node.ts`
 
-Current experimental `HttpApi` slice:
+Current bridged `HttpApi` slices:
 
-- `src/server/instance/httpapi/question.ts`
-- `src/server/instance/httpapi/index.ts`
-- `src/server/instance/experimental.ts`
-- `test/server/question-httpapi.test.ts`
+- `src/server/routes/instance/httpapi/question.ts`
+- `src/server/routes/instance/httpapi/permission.ts`
+- `src/server/routes/instance/httpapi/provider.ts`
+- `src/server/routes/instance/httpapi/config.ts`
+- `src/server/routes/instance/httpapi/project.ts`
+- `src/server/routes/instance/httpapi/server.ts`
 
 Current OpenAPI flow:
 
@@ -245,7 +247,7 @@ Keep in `packages/opencode` for now:
 
 - `src/server/server.ts`
 - `src/server/control/index.ts`
-- `src/server/instance/*.ts`
+- `src/server/routes/**/*.ts`
 - `src/server/middleware.ts`
 - `src/server/adapter.*.ts`
 - `src/effect/app-runtime.ts`
@@ -305,14 +307,13 @@ Bad early migration targets:
 
 ## First vertical slice
 
-The first slice for the package split is the existing experimental `question` group.
+The first slice for the package split is still the existing `question` `HttpApi` group.
 
 Why `question` first:
 
 - it already exists as an experimental `HttpApi` slice
 - it already follows the desired contract and implementation split in one file
 - it is already mounted through the current Hono host
-- it already has an end-to-end test
 - it is JSON-only
 - it has low blast radius
 
@@ -357,7 +358,7 @@ Done means:
 
 Scope:
 
-- extract the pure `HttpApi` contract from `src/server/instance/httpapi/question.ts`
+- extract the pure `HttpApi` contract from `src/server/routes/instance/httpapi/question.ts`
 - place it in `packages/server/src/definition/question.ts`
 - aggregate it in `packages/server/src/definition/api.ts`
 - generate OpenAPI in `packages/server/src/openapi.ts`
@@ -399,8 +400,9 @@ Scope:
 
 - replace local experimental question route wiring in `packages/opencode`
 - keep the same mount path:
-- `/experimental/httpapi/question`
-- `/experimental/httpapi/question/doc`
+- `/question`
+- `/question/:requestID/reply`
+- `/question/:requestID/reject`
 
 Rules:
 
@@ -569,7 +571,7 @@ For package-split PRs, validate the smallest useful thing.
 Typical validation for the first waves:
 
 - `bun typecheck` in the touched package directory or directories
-- the relevant route test, especially `test/server/question-httpapi.test.ts`
+- the relevant server / route coverage for the migrated slice
 - merged OpenAPI coverage if the PR touches spec generation
 
 Do not run tests from repo root.

+ 3 - 5
packages/opencode/specs/effect/tools.md

@@ -36,7 +36,7 @@ This keeps tool tests aligned with the production service graph and makes follow
 
 ## Exported tools
 
-These exported tool definitions already exist in `src/tool` and are on the current Effect-native `Tool.define(...)` path:
+These exported tool definitions currently use `Tool.define(...)` in `src/tool`:
 
 - [x] `apply_patch.ts`
 - [x] `bash.ts`
@@ -45,7 +45,6 @@ These exported tool definitions already exist in `src/tool` and are on the curre
 - [x] `glob.ts`
 - [x] `grep.ts`
 - [x] `invalid.ts`
-- [x] `ls.ts`
 - [x] `lsp.ts`
 - [x] `multiedit.ts`
 - [x] `plan.ts`
@@ -60,7 +59,7 @@ These exported tool definitions already exist in `src/tool` and are on the curre
 
 Notes:
 
-- `batch.ts` is no longer a current tool file and should not be tracked here.
+- There is no current `ls.ts` tool file on this branch.
 - `truncate.ts` is an Effect service used by tools, not a tool definition itself.
 - `mcp-exa.ts`, `external-directory.ts`, and `schema.ts` are support modules, not standalone tool definitions.
 
@@ -73,7 +72,7 @@ Current spot cleanups worth tracking:
 - [ ] `read.ts` — still bridges to Node stream / `readline` helpers and Promise-based binary detection
 - [ ] `bash.ts` — already uses Effect child-process primitives; only keep tracking shell-specific platform bridges and parser/loading details as they come up
 - [ ] `webfetch.ts` — already uses `HttpClient`; remaining work is limited to smaller boundary helpers like HTML text extraction
-- [ ] `file/ripgrep.ts` — adjacent to tool migration; still has raw fs/process usage that affects `grep.ts` and `ls.ts`
+- [ ] `file/ripgrep.ts` — adjacent to tool migration; still has raw fs/process usage that affects `grep.ts` and file-search routes
 - [ ] `patch/index.ts` — adjacent to tool migration; still has raw fs usage behind patch application
 
 Notable items that are already effectively on the target path and do not need separate migration bullets right now:
@@ -83,7 +82,6 @@ Notable items that are already effectively on the target path and do not need se
 - `write.ts`
 - `codesearch.ts`
 - `websearch.ts`
-- `ls.ts`
 - `multiedit.ts`
 - `edit.ts`