From cfdb9ab7058349fad1b4bb317ba186139371fecb Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 2 Jul 2026 00:29:52 -0400 Subject: [PATCH] refactor(core): delete the system context registry Builtins and instruction context become ordinary Location-scoped services exposing load(), the same shape as skill, reference, and MCP guidance. The runner composes all six producers explicitly in loadSystemContext, so the full context roster is readable at one call site. Dynamic registration and scoped deregistration had no consumers. --- packages/core/src/instruction-context.ts | 43 +++---- packages/core/src/location-services.ts | 4 +- packages/core/src/session/runner/llm.ts | 22 +++- packages/core/src/system-context/builtins.ts | 24 ++-- packages/core/src/system-context/registry.ts | 49 -------- .../core/test/instruction-context.test.ts | 17 ++- .../core/test/session-runner-recorded.test.ts | 15 ++- packages/core/test/session-runner.test.ts | 61 +++++----- .../core/test/system-context/builtins.test.ts | 16 ++- .../core/test/system-context/registry.test.ts | 114 ------------------ 10 files changed, 108 insertions(+), 257 deletions(-) delete mode 100644 packages/core/src/system-context/registry.ts delete mode 100644 packages/core/test/system-context/registry.test.ts diff --git a/packages/core/src/instruction-context.ts b/packages/core/src/instruction-context.ts index b0cc330678..94d9e787ce 100644 --- a/packages/core/src/instruction-context.ts +++ b/packages/core/src/instruction-context.ts @@ -1,6 +1,6 @@ export * as InstructionContext from "./instruction-context" -import { Array, Effect, Layer, Schema } from "effect" +import { Array, Context, Effect, Layer, Schema } from "effect" import { isAbsolute, join, relative, sep } from "path" import { FSUtil } from "./fs-util" import { Flag } from "./flag/flag" @@ -8,7 +8,6 @@ import { Global } from "./global" import { Location } from "./location" import { AbsolutePath } from "./schema" import { SystemContext } from "./system-context/index" -import { SystemContextRegistry } from "./system-context/registry" import { makeLocationNode } from "./effect/app-node" class File extends Schema.Class("InstructionContext.File")({ @@ -19,12 +18,18 @@ class File extends Schema.Class("InstructionContext.File")({ const Files = Schema.Array(File) const key = SystemContext.Key.make("core/instructions") -const layer = Layer.effectDiscard( +export interface Interface { + readonly load: () => Effect.Effect +} + +export class Service extends Context.Service()("@opencode/v2/InstructionContext") {} + +const layer = Layer.effect( + Service, Effect.gen(function* () { const fs = yield* FSUtil.Service const global = yield* Global.Service const location = yield* Location.Service - const registry = yield* SystemContextRegistry.Service const source = (value: ReadonlyArray | SystemContext.Unavailable) => SystemContext.make({ @@ -71,28 +76,24 @@ const layer = Layer.effectDiscard( return files.filter((file): file is File => file !== undefined) }) - yield* registry.register({ - key, - load: observe().pipe( - Effect.map((files) => - files === SystemContext.unavailable - ? source(files) - : files.length === 0 - ? SystemContext.empty - : source(files), + return Service.of({ + load: () => + observe().pipe( + Effect.map((files) => + files === SystemContext.unavailable + ? source(files) + : files.length === 0 + ? SystemContext.empty + : source(files), + ), + Effect.catch(() => Effect.succeed(source(SystemContext.unavailable))), + Effect.catchDefect(() => Effect.succeed(source(SystemContext.unavailable))), ), - Effect.catch(() => Effect.succeed(source(SystemContext.unavailable))), - Effect.catchDefect(() => Effect.succeed(source(SystemContext.unavailable))), - ), }) }), ) -export const node = makeLocationNode({ - name: "instruction-context", - layer, - deps: [FSUtil.node, Global.node, Location.node, SystemContextRegistry.node], -}) +export const node = makeLocationNode({ service: Service, layer, deps: [FSUtil.node, Global.node, Location.node] }) function render(files: ReadonlyArray) { return files.map((file) => `Instructions from: ${file.path}\n${file.content}`).join("\n\n") diff --git a/packages/core/src/location-services.ts b/packages/core/src/location-services.ts index 0d913c10fb..c4d177ef25 100644 --- a/packages/core/src/location-services.ts +++ b/packages/core/src/location-services.ts @@ -36,8 +36,8 @@ import { SessionTodo } from "./session/todo" import { SkillV2 } from "./skill" import { SkillGuidance } from "./skill/guidance" import { Snapshot } from "./snapshot" +import { InstructionContext } from "./instruction-context" import { SystemContextBuiltIns } from "./system-context/builtins" -import { SystemContextRegistry } from "./system-context/registry" import { SessionInstructions } from "./session/instructions" import { BuiltInTools } from "./tool/builtins" import { McpTool } from "./tool/mcp" @@ -68,8 +68,8 @@ export const locationServices = LayerNode.group([ Pty.node, Shell.node, SkillV2.node, - SystemContextRegistry.node, SystemContextBuiltIns.node, + InstructionContext.node, LocationMutation.node, FileMutation.node, MCP.node, diff --git a/packages/core/src/session/runner/llm.ts b/packages/core/src/session/runner/llm.ts index 7b34f1aedb..cb89e9f9af 100644 --- a/packages/core/src/session/runner/llm.ts +++ b/packages/core/src/session/runner/llm.ts @@ -18,7 +18,8 @@ import { ModelV2 } from "../../model" import { ProviderV2 } from "../../provider" import { QuestionV2 } from "../../question" import { SystemContext } from "../../system-context/index" -import { SystemContextRegistry } from "../../system-context/registry" +import { SystemContextBuiltIns } from "../../system-context/builtins" +import { InstructionContext } from "../../instruction-context" import { SkillGuidance } from "../../skill/guidance" import { ReferenceGuidance } from "../../reference/guidance" import { McpGuidance } from "../../mcp/guidance" @@ -102,7 +103,8 @@ const layer = Layer.effect( const models = yield* SessionRunnerModel.Service const store = yield* SessionStore.Service const location = yield* Location.Service - const systemContext = yield* SystemContextRegistry.Service + const builtins = yield* SystemContextBuiltIns.Service + const instructions = yield* InstructionContext.Service const skillGuidance = yield* SkillGuidance.Service const referenceGuidance = yield* ReferenceGuidance.Service const mcpGuidance = yield* McpGuidance.Service @@ -154,9 +156,16 @@ const layer = Layer.effect( cause.reasons.some((reason) => Cause.isDieReason(reason) && reason.defect instanceof QuestionV2.RejectedError) const loadSystemContext = (agent: AgentV2.Selection) => - Effect.all([systemContext.load(), skillGuidance.load(agent), referenceGuidance.load(), mcpGuidance.load(agent)], { - concurrency: "unbounded", - }).pipe(Effect.map(SystemContext.combine)) + Effect.all( + [ + builtins.load(), + instructions.load(), + skillGuidance.load(agent), + referenceGuidance.load(), + mcpGuidance.load(agent), + ], + { concurrency: "unbounded" }, + ).pipe(Effect.map(SystemContext.combine)) const runTurnAttempt = Effect.fn("SessionRunner.runTurn")(function* ( sessionID: SessionSchema.ID, @@ -467,7 +476,8 @@ export const node = makeLocationNode({ SessionRunnerModel.node, SessionStore.node, Location.node, - SystemContextRegistry.node, + SystemContextBuiltIns.node, + InstructionContext.node, SkillGuidance.node, ReferenceGuidance.node, McpGuidance.node, diff --git a/packages/core/src/system-context/builtins.ts b/packages/core/src/system-context/builtins.ts index b8b50577cc..1cf470fb4a 100644 --- a/packages/core/src/system-context/builtins.ts +++ b/packages/core/src/system-context/builtins.ts @@ -1,18 +1,20 @@ export * as SystemContextBuiltIns from "./builtins" import { makeLocationNode } from "../effect/app-node" -import { DateTime, Effect, Layer, Schema } from "effect" +import { Context, DateTime, Effect, Layer, Schema } from "effect" import { Location } from "../location" import { SystemContext } from "./index" -import { InstructionContext } from "../instruction-context" -import { SystemContextRegistry } from "./registry" -import { FSUtil } from "../fs-util" -import { Global } from "../global" -const builtIns = Layer.effectDiscard( +export interface Interface { + readonly load: () => Effect.Effect +} + +export class Service extends Context.Service()("@opencode/v2/SystemContextBuiltIns") {} + +const layer = Layer.effect( + Service, Effect.gen(function* () { const location = yield* Location.Service - const registry = yield* SystemContextRegistry.Service const environment = [ "", ` Working directory: ${location.directory}`, @@ -39,12 +41,8 @@ const builtIns = Layer.effectDiscard( }), ]) - yield* registry.register({ key: SystemContext.Key.make("core/builtins"), load: Effect.succeed(context) }) + return Service.of({ load: () => Effect.succeed(context) }) }), ) -export const node = makeLocationNode({ - name: "system-context-builtins", - layer: builtIns, - deps: [Location.node, SystemContextRegistry.node, InstructionContext.node, FSUtil.node, Global.node], -}) +export const node = makeLocationNode({ service: Service, layer, deps: [Location.node] }) diff --git a/packages/core/src/system-context/registry.ts b/packages/core/src/system-context/registry.ts deleted file mode 100644 index c1e7ca5e85..0000000000 --- a/packages/core/src/system-context/registry.ts +++ /dev/null @@ -1,49 +0,0 @@ -export * as SystemContextRegistry from "./registry" - -import { Context, Effect, Layer, Ref, Scope } from "effect" -import { SystemContext } from "./index" -import { makeLocationNode } from "../effect/app-node" - -export interface Entry { - readonly key: SystemContext.Key - readonly load: Effect.Effect -} - -export interface Interface { - readonly register: (entry: Entry) => Effect.Effect - readonly load: () => Effect.Effect -} - -export class Service extends Context.Service()("@opencode/v2/SystemContextRegistry") {} - -const layer = Layer.effect( - Service, - Effect.gen(function* () { - const entries = yield* Ref.make>([]) - - return Service.of({ - register: Effect.fn("SystemContextRegistry.register")(function* (entry) { - yield* Effect.acquireRelease( - Ref.modify(entries, (current) => { - if (current.some((item) => item.key === entry.key)) return [false, current] - return [true, [...current, entry]] - }).pipe( - Effect.flatMap((added) => - added ? Effect.void : Effect.die(`Duplicate system context entry key: ${entry.key}`), - ), - Effect.as(entry), - ), - (entry) => Ref.update(entries, (current) => current.filter((item) => item !== entry)), - ) - }), - load: Effect.fn("SystemContextRegistry.load")(function* () { - const current = (yield* Ref.get(entries)).toSorted((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0)) - return SystemContext.combine( - yield* Effect.forEach(current, (entry) => entry.load, { concurrency: "unbounded" }), - ) - }), - }) - }), -) - -export const node = makeLocationNode({ service: Service, layer, deps: [] }) diff --git a/packages/core/test/instruction-context.test.ts b/packages/core/test/instruction-context.test.ts index 1c3c764ee9..8bd04d305a 100644 --- a/packages/core/test/instruction-context.test.ts +++ b/packages/core/test/instruction-context.test.ts @@ -10,7 +10,6 @@ import { InstructionContext } from "@opencode-ai/core/instruction-context" import { Location } from "@opencode-ai/core/location" import { AbsolutePath } from "@opencode-ai/core/schema" import { SystemContext } from "@opencode-ai/core/system-context" -import { SystemContextRegistry } from "@opencode-ai/core/system-context/registry" import { location } from "./fixture/location" import { tmpdir } from "./fixture/tmpdir" import { testEffect } from "./lib/effect" @@ -22,7 +21,7 @@ const instructionLayer = (input: { locationServiceLayer: Layer.Layer filesystemLayer?: Layer.Layer }) => - AppNodeBuilder.build(LayerNode.group([SystemContextRegistry.node, InstructionContext.node]), [ + AppNodeBuilder.build(InstructionContext.node, [ [Global.node, Global.layerWith({ config: input.config })], [Location.node, input.locationServiceLayer], ...(input.filesystemLayer ? [[FSUtil.node, input.filesystemLayer] as const] : []), @@ -52,7 +51,7 @@ describe("InstructionContext", () => { await fs.writeFile(packageFile, "package") }) - const load = SystemContextRegistry.Service.pipe( + const load = InstructionContext.Service.pipe( Effect.flatMap((service) => service.load()), Effect.provide( instructionLayer({ @@ -118,7 +117,7 @@ describe("InstructionContext", () => { Effect.gen(function* () { const file = path.join(tmp.path, "AGENTS.md") yield* Effect.promise(() => fs.writeFile(file, "")) - const context = yield* SystemContextRegistry.Service.pipe( + const context = yield* InstructionContext.Service.pipe( Effect.flatMap((service) => service.load()), Effect.provide( instructionLayer({ @@ -147,7 +146,7 @@ describe("InstructionContext", () => { ), ), ).pipe(Layer.provide(LayerNode.compile(FSUtil.node))) - const context = yield* SystemContextRegistry.Service.pipe( + const context = yield* InstructionContext.Service.pipe( Effect.flatMap((service) => service.load()), Effect.provide( instructionLayer({ @@ -187,7 +186,7 @@ describe("InstructionContext", () => { ), ), ).pipe(Layer.provide(LayerNode.compile(FSUtil.node))) - const context = yield* SystemContextRegistry.Service.pipe( + const context = yield* InstructionContext.Service.pipe( Effect.flatMap((service) => service.load()), Effect.provide( instructionLayer({ @@ -231,7 +230,7 @@ describe("InstructionContext", () => { ), ).pipe(Layer.provide(LayerNode.compile(FSUtil.node))) - yield* SystemContextRegistry.Service.pipe( + yield* InstructionContext.Service.pipe( Effect.flatMap((service) => service.load()), Effect.provide( instructionLayer({ @@ -261,7 +260,7 @@ describe("InstructionContext", () => { let scanned = false process.env.OPENCODE_DISABLE_PROJECT_CONFIG = "1" - yield* SystemContextRegistry.Service.pipe( + yield* InstructionContext.Service.pipe( Effect.flatMap((service) => service.load()), Effect.provide( instructionLayer({ @@ -293,7 +292,7 @@ describe("InstructionContext", () => { it.effect("does not discover project instructions outside the canonical project root", () => Effect.gen(function* () { let scanned = false - yield* SystemContextRegistry.Service.pipe( + yield* InstructionContext.Service.pipe( Effect.flatMap((service) => service.load()), Effect.provide( instructionLayer({ diff --git a/packages/core/test/session-runner-recorded.test.ts b/packages/core/test/session-runner-recorded.test.ts index 7366105a7c..57e0ba42f6 100644 --- a/packages/core/test/session-runner-recorded.test.ts +++ b/packages/core/test/session-runner-recorded.test.ts @@ -31,7 +31,8 @@ import { ToolOutputStore } from "@opencode-ai/core/tool-output-store" import { SessionTable } from "@opencode-ai/core/session/sql" import { SessionStore } from "@opencode-ai/core/session/store" import { Location } from "@opencode-ai/core/location" -import { SystemContextRegistry } from "@opencode-ai/core/system-context/registry" +import { SystemContextBuiltIns } from "@opencode-ai/core/system-context/builtins" +import { InstructionContext } from "@opencode-ai/core/instruction-context" import { SystemContext } from "@opencode-ai/core/system-context" import { SkillGuidance } from "@opencode-ai/core/skill/guidance" import { ReferenceGuidance } from "@opencode-ai/core/reference/guidance" @@ -72,7 +73,8 @@ const model = OpenAIChat.route }) .model({ id: "gpt-4o-mini" }) const models = SessionRunnerModel.layerWith(() => Effect.succeed(model)) -const systemContext = AppNodeBuilder.build(SystemContextRegistry.node) +const systemContext = Layer.mock(SystemContextBuiltIns.Service, { load: () => Effect.succeed(SystemContext.empty) }) +const instructionContext = Layer.mock(InstructionContext.Service, { load: () => Effect.succeed(SystemContext.empty) }) const skillGuidance = Layer.mock(SkillGuidance.Service, { load: () => Effect.succeed(SystemContext.empty) }) const referenceGuidance = Layer.mock(ReferenceGuidance.Service, { load: () => Effect.succeed(SystemContext.empty) }) const mcpGuidance = Layer.mock(McpGuidance.Service, { load: () => Effect.succeed(SystemContext.empty) }) @@ -81,7 +83,8 @@ const runnerLayer = AppNodeBuilder.build(SessionRunnerLLM.node, [ [Snapshot.node, Snapshot.noopLayer], [LayerNodePlatform.llmClient, client], [SessionRunnerModel.node, models], - [SystemContextRegistry.node, systemContext], + [SystemContextBuiltIns.node, systemContext], + [InstructionContext.node, instructionContext], [Location.node, Location.boundNode({ directory: AbsolutePath.make("/project") })], [SkillGuidance.node, skillGuidance], [ReferenceGuidance.node, referenceGuidance], @@ -116,7 +119,8 @@ const it = testEffect( AgentV2.node, ToolRegistry.node, SessionRunnerModel.node, - SystemContextRegistry.node, + SystemContextBuiltIns.node, + InstructionContext.node, SkillGuidance.node, ReferenceGuidance.node, Config.node, @@ -129,7 +133,8 @@ const it = testEffect( [PermissionV2.node, permission], [ToolOutputStore.node, ToolOutputStore.nodeWithoutConfig], [SessionRunnerModel.node, models], - [SystemContextRegistry.node, systemContext], + [SystemContextBuiltIns.node, systemContext], + [InstructionContext.node, instructionContext], [Location.node, Location.boundNode({ directory: AbsolutePath.make("/project") })], [SkillGuidance.node, skillGuidance], [ReferenceGuidance.node, referenceGuidance], diff --git a/packages/core/test/session-runner.test.ts b/packages/core/test/session-runner.test.ts index f822fbe9c6..5830d4c699 100644 --- a/packages/core/test/session-runner.test.ts +++ b/packages/core/test/session-runner.test.ts @@ -52,7 +52,8 @@ import { } from "@opencode-ai/core/session/sql" import { SessionStore } from "@opencode-ai/core/session/store" import { SystemContext } from "@opencode-ai/core/system-context" -import { SystemContextRegistry } from "@opencode-ai/core/system-context/registry" +import { SystemContextBuiltIns } from "@opencode-ai/core/system-context/builtins" +import { InstructionContext } from "@opencode-ai/core/instruction-context" import { SkillGuidance } from "@opencode-ai/core/skill/guidance" import { ReferenceGuidance } from "@opencode-ai/core/reference/guidance" import { McpGuidance } from "@opencode-ai/core/mcp/guidance" @@ -176,35 +177,28 @@ let systemRemoved = false let systemUnavailable = false let systemLoadHook = Effect.void const skillBaselines = new Map() -const systemContext = Layer.effectDiscard( - SystemContextRegistry.Service.pipe( - Effect.flatMap((registry) => - registry.register({ - key: systemContextKey, - load: Effect.sync(() => - SystemContext.combine( - systemRemoved - ? [] - : [ - SystemContext.make({ - key: systemContextKey, - codec: Schema.toCodecJson(Schema.String), - load: systemLoadHook.pipe( - Effect.andThen( - Effect.sync(() => (systemUnavailable ? SystemContext.unavailable : systemBaseline)), - ), - ), - baseline: String, - update: (_previous, current) => current, - removed: () => "System context source removed: test/context", - }), - ], - ), - ), - }), +const systemContext = Layer.mock(SystemContextBuiltIns.Service, { + load: () => + Effect.sync(() => + SystemContext.combine( + systemRemoved + ? [] + : [ + SystemContext.make({ + key: systemContextKey, + codec: Schema.toCodecJson(Schema.String), + load: systemLoadHook.pipe( + Effect.andThen(Effect.sync(() => (systemUnavailable ? SystemContext.unavailable : systemBaseline))), + ), + baseline: String, + update: (_previous, current) => current, + removed: () => "System context source removed: test/context", + }), + ], + ), ), - ), -).pipe(Layer.provideMerge(AppNodeBuilder.build(SystemContextRegistry.node))) +}) +const instructionContext = Layer.mock(InstructionContext.Service, { load: () => Effect.succeed(SystemContext.empty) }) const skillGuidance = Layer.mock(SkillGuidance.Service, { load: (agent) => Effect.succeed( @@ -243,7 +237,8 @@ const runnerLayer = AppNodeBuilder.build(SessionRunnerLLM.node, [ [Snapshot.node, Snapshot.noopLayer], [LayerNodePlatform.llmClient, client], [SessionRunnerModel.node, models], - [SystemContextRegistry.node, systemContext], + [SystemContextBuiltIns.node, systemContext], + [InstructionContext.node, instructionContext], [Location.node, Location.boundNode({ directory: AbsolutePath.make("/project") })], [SkillGuidance.node, skillGuidance], [ReferenceGuidance.node, referenceGuidance], @@ -281,7 +276,8 @@ const it = testEffect( ToolRegistry.toolsNode, echoNode, SessionRunnerModel.node, - SystemContextRegistry.node, + SystemContextBuiltIns.node, + InstructionContext.node, SkillGuidance.node, ReferenceGuidance.node, Config.node, @@ -294,7 +290,8 @@ const it = testEffect( [LayerNodePlatform.llmClient, client], [PermissionV2.node, permission], [SessionRunnerModel.node, models], - [SystemContextRegistry.node, systemContext], + [SystemContextBuiltIns.node, systemContext], + [InstructionContext.node, instructionContext], [Location.node, Location.boundNode({ directory: AbsolutePath.make("/project") })], [SkillGuidance.node, skillGuidance], [ReferenceGuidance.node, referenceGuidance], diff --git a/packages/core/test/system-context/builtins.test.ts b/packages/core/test/system-context/builtins.test.ts index 8545c90449..2d4ac356b9 100644 --- a/packages/core/test/system-context/builtins.test.ts +++ b/packages/core/test/system-context/builtins.test.ts @@ -9,7 +9,7 @@ import { Global } from "@opencode-ai/core/global" import { AbsolutePath } from "@opencode-ai/core/schema" import { SystemContext } from "@opencode-ai/core/system-context" import { SystemContextBuiltIns } from "@opencode-ai/core/system-context/builtins" -import { SystemContextRegistry } from "@opencode-ai/core/system-context/registry" +import { InstructionContext } from "@opencode-ai/core/instruction-context" import { location } from "../fixture/location" import { testEffect } from "../lib/effect" @@ -27,7 +27,7 @@ const locationLayer = Layer.succeed( ), ), ) -const builtInsNode = LayerNode.group([SystemContextBuiltIns.node, SystemContextRegistry.node]) +const builtInsNode = LayerNode.group([SystemContextBuiltIns.node, InstructionContext.node]) const it = testEffect( AppNodeBuilder.build(builtInsNode, [ [Location.node, locationLayer], @@ -58,7 +58,7 @@ describe("SystemContextBuiltIns", () => { it.effect("loads location-scoped environment and host-local date context", () => Effect.gen(function* () { yield* TestClock.setTime(timestamp) - const context = yield* SystemContextRegistry.Service + const context = yield* SystemContextBuiltIns.Service const initialized = yield* SystemContext.initialize(yield* context.load()) expect(initialized.text).toBe( @@ -80,7 +80,7 @@ describe("SystemContextBuiltIns", () => { it.effect("reconciles the date without repeating unchanged environment context", () => Effect.gen(function* () { yield* TestClock.setTime(timestamp) - const context = yield* SystemContextRegistry.Service + const context = yield* SystemContextBuiltIns.Service const initialized = yield* SystemContext.initialize(yield* context.load()) yield* TestClock.setTime(timestamp + 24 * 60 * 60 * 1000) @@ -96,7 +96,7 @@ describe("SystemContextBuiltIns", () => { it.effect("does not update again within the same local calendar day", () => Effect.gen(function* () { yield* TestClock.setTime(timestamp) - const context = yield* SystemContextRegistry.Service + const context = yield* SystemContextBuiltIns.Service const initialized = yield* SystemContext.initialize(yield* context.load()) yield* TestClock.setTime(timestamp + 60 * 60 * 1000) @@ -107,7 +107,11 @@ describe("SystemContextBuiltIns", () => { itWithInstructions.effect("composes ambient instructions after built-in context", () => Effect.gen(function* () { yield* TestClock.setTime(timestamp) - const context = yield* SystemContextRegistry.Service + const builtIns = yield* SystemContextBuiltIns.Service + const instructions = yield* InstructionContext.Service + const context = { + load: () => Effect.all([builtIns.load(), instructions.load()]).pipe(Effect.map(SystemContext.combine)), + } expect((yield* SystemContext.initialize(yield* context.load())).text).toBe( [ diff --git a/packages/core/test/system-context/registry.test.ts b/packages/core/test/system-context/registry.test.ts deleted file mode 100644 index 3fd3bf5aac..0000000000 --- a/packages/core/test/system-context/registry.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { describe, expect } from "bun:test" -import { Cause, Effect, Exit, Schema, Scope } from "effect" -import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" -import { SystemContext } from "@opencode-ai/core/system-context" -import { SystemContextRegistry } from "@opencode-ai/core/system-context/registry" -import { testEffect } from "../lib/effect" - -const entry = (key: string, text: string, sourceKey = key) => ({ - key: SystemContext.Key.make(key), - load: Effect.succeed( - SystemContext.make({ - key: SystemContext.Key.make(sourceKey), - codec: Schema.toCodecJson(Schema.String), - load: Effect.succeed(text), - baseline: String, - update: (_previous, current) => current, - }), - ), -}) - -const it = testEffect(AppNodeBuilder.build(SystemContextRegistry.node)) - -describe("SystemContextRegistry", () => { - it.effect("loads empty system context when there are no entries", () => - Effect.gen(function* () { - const registry = yield* SystemContextRegistry.Service - - expect(yield* SystemContext.initialize(yield* registry.load())).toEqual({ text: "", applied: {} }) - }), - ) - - it.effect("loads scoped entries in stable key order", () => - Effect.gen(function* () { - const registry = yield* SystemContextRegistry.Service - yield* registry.register(entry("test/second", "second")) - yield* registry.register(entry("test/first", "first")) - - expect((yield* SystemContext.initialize(yield* registry.load())).text).toBe("first\n\nsecond") - }), - ) - - it.effect("re-evaluates entry producers on each load", () => - Effect.gen(function* () { - const registry = yield* SystemContextRegistry.Service - let loads = 0 - yield* registry.register({ - key: SystemContext.Key.make("test/dynamic"), - load: Effect.sync(() => { - loads++ - return SystemContext.empty - }), - }) - - yield* registry.load() - yield* registry.load() - - expect(loads).toBe(2) - }), - ) - - it.effect("propagates entry producer failures", () => - Effect.gen(function* () { - const registry = yield* SystemContextRegistry.Service - const failure = new Error("entry failed") - yield* registry.register({ key: SystemContext.Key.make("test/failure"), load: Effect.die(failure) }) - - const exit = yield* registry.load().pipe(Effect.exit) - - expect(Exit.isFailure(exit)).toBe(true) - if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBe(failure) - }), - ) - - it.effect("rejects duplicate source keys from separate entries", () => - Effect.gen(function* () { - const registry = yield* SystemContextRegistry.Service - yield* registry.register(entry("test/first", "first", "test/duplicate")) - yield* registry.register(entry("test/second", "second", "test/duplicate")) - - const exit = yield* registry.load().pipe(Effect.exit) - - expect(Exit.isFailure(exit)).toBe(true) - if (Exit.isFailure(exit)) { - expect(Cause.squash(exit.cause)).toBeInstanceOf(SystemContext.DuplicateKeyError) - expect(Cause.squash(exit.cause)).toMatchObject({ key: SystemContext.Key.make("test/duplicate") }) - } - }), - ) - - it.effect("rejects duplicate entry keys", () => - Effect.gen(function* () { - const registry = yield* SystemContextRegistry.Service - yield* registry.register(entry("test/duplicate", "first")) - - const exit = yield* registry.register(entry("test/duplicate", "second", "test/other")).pipe(Effect.exit) - - expect(Exit.isFailure(exit)).toBe(true) - if (Exit.isFailure(exit)) expect(Cause.pretty(exit.cause)).toContain("Duplicate system context entry key") - }), - ) - - it.effect("removes an entry when its owning scope closes", () => - Effect.gen(function* () { - const registry = yield* SystemContextRegistry.Service - const scope = yield* Scope.make() - yield* registry.register(entry("test/scoped", "scoped")).pipe(Scope.provide(scope)) - - expect((yield* SystemContext.initialize(yield* registry.load())).text).toBe("scoped") - - yield* Scope.close(scope, Exit.void) - expect(yield* SystemContext.initialize(yield* registry.load())).toEqual({ text: "", applied: {} }) - }), - ) -})