diff --git a/packages/opencode/src/effect/service-use.ts b/packages/core/src/effect/service-use.ts similarity index 85% rename from packages/opencode/src/effect/service-use.ts rename to packages/core/src/effect/service-use.ts index a93cdecbb15..89d9acf98ea 100644 --- a/packages/opencode/src/effect/service-use.ts +++ b/packages/core/src/effect/service-use.ts @@ -15,6 +15,7 @@ type ServiceUse = { } export const serviceUse = (tag: Context.Service) => { + const cache = new Map Effect.Effect>() // This is the only dynamic boundary: TypeScript knows the accessor shape, // but Proxy property names are runtime values. const access = new Proxy( @@ -22,7 +23,9 @@ export const serviceUse = (tag: Context.Service { if (typeof key !== "string") return undefined - return (...args: unknown[]) => + const cached = cache.get(key) + if (cached) return cached + const accessor = (...args: unknown[]) => tag.use((service) => { // oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- Proxy keys are checked at runtime. const method = service[key as keyof Shape] @@ -30,6 +33,8 @@ export const serviceUse = (tag: Context.Service Effect.Effect)(...args) }) + cache.set(key, accessor) + return accessor }, }, ) diff --git a/packages/core/src/filesystem.ts b/packages/core/src/filesystem.ts index 06170beed9d..d4bfe6dba43 100644 --- a/packages/core/src/filesystem.ts +++ b/packages/core/src/filesystem.ts @@ -3,46 +3,10 @@ import { dirname, join, relative, resolve as pathResolve } from "path" import { realpathSync } from "fs" import * as NFS from "fs/promises" import { lookup } from "mime-types" -import { Effect, FileSystem, Layer, Schema, Context } from "effect" +import { Context, Effect, FileSystem, Layer, Schema } from "effect" import type { PlatformError } from "effect/PlatformError" import { Glob } from "./util/glob" - -type EffectMethod = (...args: ReadonlyArray) => Effect.Effect - -type ServiceUse = { - readonly [Key in keyof Shape as Shape[Key] extends EffectMethod ? Key : never]: Shape[Key] extends ( - ...args: infer Args - ) => infer Return - ? Args extends ReadonlyArray - ? Return extends Effect.Effect - ? (...args: Args) => Effect.Effect - : never - : never - : never -} - -const serviceUse = (tag: Context.Service) => { - // This is the only dynamic boundary: TypeScript knows the accessor shape, - // but Proxy property names are runtime values. - const access = new Proxy( - {}, - { - get: (_, key) => { - if (typeof key !== "string") return undefined - return (...args: unknown[]) => - tag.use((service) => { - // oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- Proxy keys are checked at runtime. - const method = service[key as keyof Shape] - if (typeof method !== "function") return Effect.die(new Error(`Service method not found: ${key}`)) - // oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- ServiceUse exposes only Effect-returning methods. - return (method as (...args: unknown[]) => Effect.Effect)(...args) - }) - }, - }, - ) - // oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- Proxy implements the mapped accessor surface lazily. - return access as ServiceUse -} +import { serviceUse } from "./effect/service-use" export namespace AppFileSystem { export class FileSystemError extends Schema.TaggedErrorClass()("FileSystemError", { diff --git a/packages/opencode/src/account/account.ts b/packages/opencode/src/account/account.ts index dcff82664b1..2d855e0e952 100644 --- a/packages/opencode/src/account/account.ts +++ b/packages/opencode/src/account/account.ts @@ -1,5 +1,5 @@ import { Cache, Clock, Duration, Effect, Layer, Option, Schema, SchemaGetter, Context } from "effect" -import { serviceUse } from "@/effect/service-use" +import { serviceUse } from "@opencode-ai/core/effect/service-use" import { FetchHttpClient, HttpClient, diff --git a/packages/opencode/src/account/repo.ts b/packages/opencode/src/account/repo.ts index a5291e8283c..052f1cbd65a 100644 --- a/packages/opencode/src/account/repo.ts +++ b/packages/opencode/src/account/repo.ts @@ -1,5 +1,5 @@ import { eq } from "drizzle-orm" -import { serviceUse } from "@/effect/service-use" +import { serviceUse } from "@opencode-ai/core/effect/service-use" import { Effect, Layer, Option, Schema, Context } from "effect" import { Database } from "@/storage/db" diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index f7e2c134a37..064a59f59ed 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -1,5 +1,5 @@ import { Config } from "@/config/config" -import { serviceUse } from "@/effect/service-use" +import { serviceUse } from "@opencode-ai/core/effect/service-use" import { Provider } from "@/provider/provider" import { ModelID, ProviderID } from "../provider/schema" import { generateObject, streamObject, type ModelMessage } from "ai" diff --git a/packages/opencode/src/bus/index.ts b/packages/opencode/src/bus/index.ts index b5f4320bda6..73ec18d73b1 100644 --- a/packages/opencode/src/bus/index.ts +++ b/packages/opencode/src/bus/index.ts @@ -5,7 +5,7 @@ import { BusEvent } from "./bus-event" import { GlobalBus } from "./global" import { InstanceState } from "@/effect/instance-state" import { makeRuntime } from "@/effect/run-service" -import { serviceUse } from "@/effect/service-use" +import { serviceUse } from "@opencode-ai/core/effect/service-use" import { Identifier } from "@/id/id" import type { InstanceContext } from "@/project/instance-context" import { InstanceRef } from "@/effect/instance-ref" diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 349b7e6a074..307b02ca4d9 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -1,5 +1,5 @@ import * as Log from "@opencode-ai/core/util/log" -import { serviceUse } from "@/effect/service-use" +import { serviceUse } from "@opencode-ai/core/effect/service-use" import path from "path" import { pathToFileURL } from "url" import os from "os" diff --git a/packages/opencode/src/control-plane/workspace.ts b/packages/opencode/src/control-plane/workspace.ts index d1b4b12f635..9f44d22334c 100644 --- a/packages/opencode/src/control-plane/workspace.ts +++ b/packages/opencode/src/control-plane/workspace.ts @@ -1,5 +1,5 @@ import { Context, Effect, FiberMap, Iterable, Layer, Schema, Stream } from "effect" -import { serviceUse } from "@/effect/service-use" +import { serviceUse } from "@opencode-ai/core/effect/service-use" import { FetchHttpClient, HttpBody, HttpClient, HttpClientError, HttpClientRequest } from "effect/unstable/http" import { Database } from "@/storage/db" import { asc } from "drizzle-orm" diff --git a/packages/opencode/src/env/index.ts b/packages/opencode/src/env/index.ts index efdfd0ac01a..89ccf7d0fd3 100644 --- a/packages/opencode/src/env/index.ts +++ b/packages/opencode/src/env/index.ts @@ -1,5 +1,5 @@ import { Context, Effect, Layer } from "effect" -import { serviceUse } from "@/effect/service-use" +import { serviceUse } from "@opencode-ai/core/effect/service-use" import { InstanceState } from "@/effect/instance-state" type State = Record diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts index 0f17ed2792b..0992289fe29 100644 --- a/packages/opencode/src/file/index.ts +++ b/packages/opencode/src/file/index.ts @@ -1,5 +1,5 @@ import { BusEvent } from "@/bus/bus-event" -import { serviceUse } from "@/effect/service-use" +import { serviceUse } from "@opencode-ai/core/effect/service-use" import { InstanceState } from "@/effect/instance-state" import { AppFileSystem } from "@opencode-ai/core/filesystem" diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts index c1a636782bb..8b3d5cf174a 100644 --- a/packages/opencode/src/file/ripgrep.ts +++ b/packages/opencode/src/file/ripgrep.ts @@ -1,5 +1,5 @@ import path from "path" -import { serviceUse } from "@/effect/service-use" +import { serviceUse } from "@opencode-ai/core/effect/service-use" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Cause, Context, Effect, Fiber, Layer, Queue, Schema, Stream } from "effect" import type { PlatformError } from "effect/PlatformError" diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts index eb55b0713b8..6d0311f6fa7 100644 --- a/packages/opencode/src/format/index.ts +++ b/packages/opencode/src/format/index.ts @@ -1,5 +1,5 @@ import { Effect, Layer, Context, Schema } from "effect" -import { serviceUse } from "@/effect/service-use" +import { serviceUse } from "@opencode-ai/core/effect/service-use" import { ChildProcess } from "effect/unstable/process" import { AppProcess } from "@opencode-ai/core/process" import { InstanceState } from "@/effect/instance-state" diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index d96924a2a0b..1f8dc116b18 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -1,5 +1,5 @@ import { Effect, Layer, Schema, Context, Stream } from "effect" -import { serviceUse } from "@/effect/service-use" +import { serviceUse } from "@opencode-ai/core/effect/service-use" import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" import { withTransientReadRetry } from "@/util/effect-http-client" import { errorMessage } from "@/util/error" diff --git a/packages/opencode/src/mcp/auth.ts b/packages/opencode/src/mcp/auth.ts index d7406e5192d..adddea0b35a 100644 --- a/packages/opencode/src/mcp/auth.ts +++ b/packages/opencode/src/mcp/auth.ts @@ -1,5 +1,5 @@ import path from "path" -import { serviceUse } from "@/effect/service-use" +import { serviceUse } from "@opencode-ai/core/effect/service-use" import { Global } from "@opencode-ai/core/global" import { Effect, Layer, Context, Option, Schema } from "effect" import { AppFileSystem } from "@opencode-ai/core/filesystem" diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 85985876e72..efd72c0c1f7 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -1,5 +1,5 @@ import { dynamicTool, type Tool, jsonSchema, type JSONSchema7 } from "ai" -import { serviceUse } from "@/effect/service-use" +import { serviceUse } from "@opencode-ai/core/effect/service-use" import { Client } from "@modelcontextprotocol/sdk/client/index.js" import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js" import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js" diff --git a/packages/opencode/src/project/instance-store.ts b/packages/opencode/src/project/instance-store.ts index 8c847fd9934..1f513fb1b49 100644 --- a/packages/opencode/src/project/instance-store.ts +++ b/packages/opencode/src/project/instance-store.ts @@ -1,5 +1,5 @@ import { GlobalBus } from "@/bus/global" -import { serviceUse } from "@/effect/service-use" +import { serviceUse } from "@opencode-ai/core/effect/service-use" import { WorkspaceContext } from "@/control-plane/workspace-context" import { InstanceRef } from "@/effect/instance-ref" import { disposeInstance as runDisposers } from "@/effect/instance-registry" diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index a12c2cde1f8..4b4f2cdf584 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -20,7 +20,7 @@ import { AppProcess } from "@opencode-ai/core/process" import { Project as ProjectV2 } from "@opencode-ai/core/project" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { AbsolutePath, NonNegativeInt, optionalOmitUndefined } from "@opencode-ai/core/schema" -import { serviceUse } from "@/effect/service-use" +import { serviceUse } from "@opencode-ai/core/effect/service-use" import { RuntimeFlags } from "@/effect/runtime-flags" const log = Log.create({ service: "project" }) diff --git a/packages/opencode/src/provider/auth.ts b/packages/opencode/src/provider/auth.ts index 214ab3bd997..a304fec540b 100644 --- a/packages/opencode/src/provider/auth.ts +++ b/packages/opencode/src/provider/auth.ts @@ -1,5 +1,5 @@ import type { AuthOAuthResult, Hooks } from "@opencode-ai/plugin" -import { serviceUse } from "@/effect/service-use" +import { serviceUse } from "@opencode-ai/core/effect/service-use" import { Auth } from "@/auth" import { InstanceState } from "@/effect/instance-state" import { optionalOmitUndefined } from "@opencode-ai/core/schema" diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 2140bea4df9..496a2f6d2d3 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -7,7 +7,7 @@ import * as Log from "@opencode-ai/core/util/log" import { Npm } from "@opencode-ai/core/npm" import { Hash } from "@opencode-ai/core/util/hash" import { Plugin } from "../plugin" -import { serviceUse } from "@/effect/service-use" +import { serviceUse } from "@opencode-ai/core/effect/service-use" import { type LanguageModelV3 } from "@ai-sdk/provider" import * as ModelsDev from "@opencode-ai/core/models-dev" import { Auth } from "../auth" diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index ef007fe74d3..4f87edf64a5 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -16,7 +16,7 @@ import { Effect, Layer, Context, Schema } from "effect" import * as DateTime from "effect/DateTime" import { InstanceState } from "@/effect/instance-state" import { isOverflow as overflow, usable } from "./overflow" -import { serviceUse } from "@/effect/service-use" +import { serviceUse } from "@opencode-ai/core/effect/service-use" import { RuntimeFlags } from "@/effect/runtime-flags" import { EventV2Bridge } from "@/event-v2-bridge" import { SessionEvent } from "@opencode-ai/core/session-event" diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index ec87bfc4469..ea2efc99d00 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -1,5 +1,5 @@ import { Provider } from "@/provider/provider" -import { serviceUse } from "@/effect/service-use" +import { serviceUse } from "@opencode-ai/core/effect/service-use" import * as Log from "@opencode-ai/core/util/log" import { Context, Effect, Layer } from "effect" import * as Stream from "effect/Stream" diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index 0131d443893..f75ac910d40 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -1,5 +1,5 @@ import { Slug } from "@opencode-ai/core/util/slug" -import { serviceUse } from "@/effect/service-use" +import { serviceUse } from "@opencode-ai/core/effect/service-use" import path from "path" import { BackgroundJob } from "@/background/job" import { BusEvent } from "@/bus/bus-event" diff --git a/packages/opencode/src/share/share-next.ts b/packages/opencode/src/share/share-next.ts index 98d5c822550..ab2d9d151d6 100644 --- a/packages/opencode/src/share/share-next.ts +++ b/packages/opencode/src/share/share-next.ts @@ -1,5 +1,5 @@ import type * as SDK from "@opencode-ai/sdk/v2" -import { serviceUse } from "@/effect/service-use" +import { serviceUse } from "@opencode-ai/core/effect/service-use" import { Effect, Exit, Layer, Option, Schema, Scope, Context, Stream } from "effect" import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" import { Account } from "@/account/account" diff --git a/packages/opencode/src/sync/index.ts b/packages/opencode/src/sync/index.ts index a8858255f22..8573636615d 100644 --- a/packages/opencode/src/sync/index.ts +++ b/packages/opencode/src/sync/index.ts @@ -12,7 +12,7 @@ import { EventID } from "./schema" import { Context, Effect, Layer, Schema as EffectSchema } from "effect" import type { DeepMutable } from "@opencode-ai/core/schema" import { EventV2 } from "@opencode-ai/core/event" -import { serviceUse } from "@/effect/service-use" +import { serviceUse } from "@opencode-ai/core/effect/service-use" import { InstanceState } from "@/effect/instance-state" import { RuntimeFlags } from "@/effect/runtime-flags" import { EffectBridge } from "@/effect/bridge" diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index f0778a6a54b..49b7643d7bc 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -26,11 +26,6 @@ import { import { InstanceRuntime } from "@/project/instance-runtime" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { testEffect } from "../lib/effect" - -/** Infra layer that provides FileSystem, Path, ChildProcessSpawner for test fixtures */ -const infra = CrossSpawnSpawner.defaultLayer.pipe( - Layer.provideMerge(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer)), -) import path from "path" import fs from "fs/promises" import { pathToFileURL } from "url" @@ -42,6 +37,11 @@ import { AccountTest } from "../fake/account" import { AuthTest } from "../fake/auth" import { NpmTest } from "../fake/npm" +/** Infra layer that provides FileSystem, Path, ChildProcessSpawner for test fixtures */ +const infra = CrossSpawnSpawner.defaultLayer.pipe( + Layer.provideMerge(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer)), +) + const testFlock = EffectFlock.defaultLayer const unexpectedHttp = HttpClient.make((request) => @@ -148,28 +148,29 @@ async function writeConfig(dir: string, config: object, name = "opencode.json") } const writeConfigEffect = (dir: string, config: object, name = "opencode.json") => - AppFileSystem.use.writeFileString(path.join(dir, name), JSON.stringify(config)) + AppFileSystem.use.writeWithDirs(path.join(dir, name), JSON.stringify(config)) -const project = { - dir: (relative: string) => - TestInstance.use((test) => AppFileSystem.use.ensureDir(path.join(test.directory, relative))), - file: (relative: string, content: string) => - TestInstance.use((test) => AppFileSystem.use.writeWithDirs(path.join(test.directory, relative), content)), -} +const withInstanceDir = (dir: string, effect: Effect.Effect) => + effect.pipe( + Effect.provideService(TestInstance, { directory: dir }), + provideInstanceEffect(dir), + Effect.provide(testInstanceStoreLayer), + Effect.provide(CrossSpawnSpawner.defaultLayer), + ) const withGlobalConfigDir = (dir: string, effect: Effect.Effect) => Effect.acquireUseRelease( Effect.gen(function* () { const previous = Global.Path.config ;(Global.Path as { config: string }).config = dir - yield* clearEffect(true) + yield* clearEffect() return previous }), () => effect, (previous) => Effect.gen(function* () { ;(Global.Path as { config: string }).config = previous - yield* clearEffect(true) + yield* clearEffect() }), ) @@ -190,17 +191,17 @@ const withConfigTree = ( Effect.gen(function* () { const root = yield* tmpdirScoped() const directory = path.join(root, "project") - const local = path.join(directory, ".opencode") - yield* AppFileSystem.use.ensureDir(local) - if (input.global) yield* writeConfigEffect(root, schemaConfig(input.global)) - if (input.project) yield* writeConfigEffect(directory, schemaConfig(input.project)) - if (input.local) yield* writeConfigEffect(local, schemaConfig(input.local)) - return yield* effect.pipe( - Effect.provideService(TestInstance, { directory }), - provideInstanceEffect(directory), - Effect.provide(testInstanceStoreLayer), - Effect.provide(CrossSpawnSpawner.defaultLayer), + yield* Effect.all( + [ + input.global ? writeConfigEffect(root, schemaConfig(input.global)) : undefined, + input.project ? writeConfigEffect(directory, schemaConfig(input.project)) : undefined, + input.local ? writeConfigEffect(path.join(directory, ".opencode"), schemaConfig(input.local)) : undefined, + ].filter( + (effect): effect is Effect.Effect => effect !== undefined, + ), + { concurrency: "unbounded" }, ) + return yield* withInstanceDir(directory, effect) }) const wellKnown = (input: { @@ -705,7 +706,6 @@ it.instance("migrates mode field to agent field", () => it.instance("loads config from .opencode directory", () => Effect.gen(function* () { const test = yield* TestInstance - yield* AppFileSystem.use.ensureDir(path.join(test.directory, ".opencode", "agent")) yield* AppFileSystem.use.writeWithDirs( path.join(test.directory, ".opencode", "agent", "test.md"), `--- @@ -728,7 +728,6 @@ Test agent prompt`, it.instance("agent markdown permission config preserves user key order", () => Effect.gen(function* () { const test = yield* TestInstance - yield* AppFileSystem.use.ensureDir(path.join(test.directory, ".opencode", "agent")) yield* AppFileSystem.use.writeWithDirs( path.join(test.directory, ".opencode", "agent", "ordered.md"), `--- @@ -748,7 +747,6 @@ Ordered permissions`, it.instance("loads agents from .opencode/agents (plural)", () => Effect.gen(function* () { const test = yield* TestInstance - yield* AppFileSystem.use.ensureDir(path.join(test.directory, ".opencode", "agents", "nested")) yield* AppFileSystem.use.writeWithDirs( path.join(test.directory, ".opencode", "agents", "helper.md"), `--- @@ -788,7 +786,6 @@ Nested agent prompt`, it.instance("loads commands from .opencode/command (singular)", () => Effect.gen(function* () { const test = yield* TestInstance - yield* AppFileSystem.use.ensureDir(path.join(test.directory, ".opencode", "command", "nested")) yield* AppFileSystem.use.writeWithDirs( path.join(test.directory, ".opencode", "command", "hello.md"), `--- @@ -822,7 +819,6 @@ Nested command template`, it.instance("loads commands from .opencode/commands (plural)", () => Effect.gen(function* () { const test = yield* TestInstance - yield* AppFileSystem.use.ensureDir(path.join(test.directory, ".opencode", "commands", "nested")) yield* AppFileSystem.use.writeWithDirs( path.join(test.directory, ".opencode", "commands", "hello.md"), `--- @@ -912,7 +908,6 @@ it.instance("resolves scoped npm plugins in config", () => Effect.gen(function* () { const test = yield* TestInstance const pluginDir = path.join(test.directory, "node_modules", "@scope", "plugin") - yield* AppFileSystem.use.ensureDir(pluginDir) yield* AppFileSystem.use.writeWithDirs( path.join(test.directory, "package.json"), JSON.stringify({ name: "config-fixture", version: "1.0.0", type: "module" }, null, 2), @@ -960,7 +955,6 @@ it.effect("merges plugin arrays from global and local configs", () => it.instance("does not error when only custom agent is a subagent", () => Effect.gen(function* () { const test = yield* TestInstance - yield* AppFileSystem.use.ensureDir(path.join(test.directory, ".opencode", "agent")) yield* AppFileSystem.use.writeWithDirs( path.join(test.directory, ".opencode", "agent", "helper.md"), `--- @@ -1671,8 +1665,11 @@ describe("deduplicatePluginOrigins", () => { withConfigTree( { global: { plugin: ["my-plugin@1.0.0"] } }, Effect.gen(function* () { - yield* project.dir(".opencode/plugin") - yield* project.file(".opencode/plugin/my-plugin.js", "export default {}") + const test = yield* TestInstance + yield* AppFileSystem.use.writeWithDirs( + path.join(test.directory, ".opencode", "plugin", "my-plugin.js"), + "export default {}", + ) const plugins = (yield* Config.use.get()).plugin ?? [] expect(plugins.some((p) => ConfigPlugin.pluginSpecifier(p) === "my-plugin@1.0.0")).toBe(true) @@ -1704,7 +1701,6 @@ describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => { "true", Effect.gen(function* () { const test = yield* TestInstance - yield* AppFileSystem.use.ensureDir(path.join(test.directory, ".opencode", "command")) yield* AppFileSystem.use.writeWithDirs( path.join(test.directory, ".opencode", "command", "test-cmd.md"), "# Test Command\nThis is a test command.",