From ddf731d1125c3d1b5a8320f37c507ea66aabf215 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 3 Jul 2026 16:05:48 +0000 Subject: [PATCH 1/4] Use Effect FileSystem for workspace reads Co-authored-by: Julius Marminge --- .../src/workspace/WorkspaceFileSystem.test.ts | 55 ++++++- .../src/workspace/WorkspaceFileSystem.ts | 149 +++++++----------- 2 files changed, 107 insertions(+), 97 deletions(-) diff --git a/apps/server/src/workspace/WorkspaceFileSystem.test.ts b/apps/server/src/workspace/WorkspaceFileSystem.test.ts index cecffbc1993..7e75a94d40f 100644 --- a/apps/server/src/workspace/WorkspaceFileSystem.test.ts +++ b/apps/server/src/workspace/WorkspaceFileSystem.test.ts @@ -1,9 +1,10 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; -import { it, describe, expect } from "@effect/vitest"; +import { assert, it, describe, expect } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Path from "effect/Path"; +import * as PlatformError from "effect/PlatformError"; import * as ServerConfig from "../config.ts"; import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; @@ -30,6 +31,34 @@ const TestLayer = Layer.empty.pipe( Layer.provideMerge(NodeServices.layer), ); +const OpenFailureFileSystemLayer = Layer.effect( + FileSystem.FileSystem, + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + + return { + ...fileSystem, + open: (path, options) => + Effect.fail( + PlatformError.systemError({ + _tag: "PermissionDenied", + module: "FileSystem", + method: "open", + pathOrDescriptor: String(path), + description: `open intercepted by FileSystem test layer (options: ${String(Boolean(options))})`, + }), + ), + } satisfies FileSystem.FileSystem; + }), +).pipe(Layer.provide(NodeServices.layer)); + +const WorkspaceFileSystemOpenFailureLayer = WorkspaceFileSystem.layer.pipe( + Layer.provide(WorkspacePaths.layer), + Layer.provide(Layer.mock(WorkspaceEntries.WorkspaceEntries)({})), + Layer.provideMerge(OpenFailureFileSystemLayer), + Layer.provide(Path.layer), +); + const makeTempDir = Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; return yield* fileSystem.makeTempDirectoryScoped({ @@ -185,10 +214,30 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i operationPath: resolvedPath, operation: "realpath-target", }); - expect(error.cause).toBeInstanceOf(Error); - expect((error.cause as NodeJS.ErrnoException).code).toBe("ENOENT"); + expect(error.cause).toBeInstanceOf(PlatformError.PlatformError); + expect((error.cause as PlatformError.PlatformError).reason._tag).toBe("NotFound"); }), ); + + it.effect("opens files through the injected FileSystem service", () => + Effect.gen(function* () { + const workspaceFileSystem = yield* WorkspaceFileSystem.WorkspaceFileSystem; + const cwd = yield* makeTempDir; + yield* writeTextFile(cwd, "src/index.ts", "export const answer = 42;\n"); + const fileSystem = yield* FileSystem.FileSystem; + const resolvedPath = yield* fileSystem.realPath(`${cwd}/src/index.ts`); + + const error = yield* workspaceFileSystem + .readFile({ cwd, relativePath: "src/index.ts" }) + .pipe(Effect.flip); + + assert.instanceOf(error, WorkspaceFileSystem.WorkspaceFileSystemOperationError); + assert.equal(error.operation, "open"); + assert.equal(error.operationPath, resolvedPath); + assert.instanceOf(error.cause, PlatformError.PlatformError); + assert.equal((error.cause as PlatformError.PlatformError).reason._tag, "PermissionDenied"); + }).pipe(Effect.provide(WorkspaceFileSystemOpenFailureLayer)), + ); }); describe("writeFile", () => { diff --git a/apps/server/src/workspace/WorkspaceFileSystem.ts b/apps/server/src/workspace/WorkspaceFileSystem.ts index e2dc9cbbb39..4a3898a2476 100644 --- a/apps/server/src/workspace/WorkspaceFileSystem.ts +++ b/apps/server/src/workspace/WorkspaceFileSystem.ts @@ -1,4 +1,3 @@ -// @effect-diagnostics nodeBuiltinImport:off /** * WorkspaceFileSystem - Effect service contract for workspace file mutations. * @@ -7,8 +6,6 @@ * * @module WorkspaceFileSystem */ -import * as NodeFSP from "node:fs/promises"; - import type { ProjectReadFileInput, ProjectReadFileResult, @@ -19,6 +16,7 @@ import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import * as Path from "effect/Path"; import * as Schema from "effect/Schema"; @@ -140,30 +138,28 @@ export const make = Effect.gen(function* () { relativePath: input.relativePath, }); - const realWorkspaceRoot = yield* Effect.tryPromise({ - try: () => NodeFSP.realpath(input.cwd), - catch: (cause) => + const toOperationError = + ( + operation: WorkspaceFileSystemOperationError["operation"], + operationPath: string, + resolvedPath = target.absolutePath, + ) => + (cause: unknown) => new WorkspaceFileSystemOperationError({ workspaceRoot: input.cwd, relativePath: input.relativePath, - resolvedPath: target.absolutePath, - operationPath: input.cwd, - operation: "realpath-workspace-root", + resolvedPath, + operationPath, + operation, cause, - }), - }); - const realTargetPath = yield* Effect.tryPromise({ - try: () => NodeFSP.realpath(target.absolutePath), - catch: (cause) => - new WorkspaceFileSystemOperationError({ - workspaceRoot: input.cwd, - relativePath: input.relativePath, - resolvedPath: target.absolutePath, - operationPath: target.absolutePath, - operation: "realpath-target", - cause, - }), - }); + }); + + const realWorkspaceRoot = yield* fileSystem.realPath(input.cwd).pipe( + Effect.mapError(toOperationError("realpath-workspace-root", input.cwd)), + ); + const realTargetPath = yield* fileSystem.realPath(target.absolutePath).pipe( + Effect.mapError(toOperationError("realpath-target", target.absolutePath)), + ); const relativeRealPath = path.relative(realWorkspaceRoot, realTargetPath); if ( relativeRealPath.startsWith(`..${path.sep}`) || @@ -178,84 +174,49 @@ export const make = Effect.gen(function* () { }); } - return yield* Effect.acquireUseRelease( - Effect.tryPromise({ - try: () => NodeFSP.open(realTargetPath, "r"), - catch: (cause) => - new WorkspaceFileSystemOperationError({ + return yield* Effect.scoped( + Effect.gen(function* () { + const file = yield* fileSystem.open(realTargetPath, { flag: "r" }).pipe( + Effect.mapError(toOperationError("open", realTargetPath, realTargetPath)), + ); + const stat = yield* file.stat.pipe( + Effect.mapError(toOperationError("stat", realTargetPath, realTargetPath)), + ); + if (stat.type !== "File") { + return yield* new WorkspacePathNotFileError({ workspaceRoot: input.cwd, relativePath: input.relativePath, resolvedPath: realTargetPath, - operationPath: realTargetPath, - operation: "open", - cause, - }), - }), - (handle) => - Effect.gen(function* () { - const stat = yield* Effect.tryPromise({ - try: () => handle.stat(), - catch: (cause) => - new WorkspaceFileSystemOperationError({ - workspaceRoot: input.cwd, - relativePath: input.relativePath, - resolvedPath: realTargetPath, - operationPath: realTargetPath, - operation: "stat", - cause, - }), }); - if (!stat.isFile()) { - return yield* new WorkspacePathNotFileError({ - workspaceRoot: input.cwd, - relativePath: input.relativePath, - resolvedPath: realTargetPath, - }); - } + } - const bytesToRead = Math.min(stat.size, PROJECT_READ_FILE_MAX_BYTES); - const buffer = Buffer.alloc(bytesToRead); - const { bytesRead } = yield* Effect.tryPromise({ - try: () => handle.read(buffer, 0, bytesToRead, 0), - catch: (cause) => - new WorkspaceFileSystemOperationError({ - workspaceRoot: input.cwd, - relativePath: input.relativePath, - resolvedPath: realTargetPath, - operationPath: realTargetPath, - operation: "read", - cause, - }), + const byteLength = Number(stat.size); + const truncated = stat.size > BigInt(PROJECT_READ_FILE_MAX_BYTES); + const bytesToRead = truncated ? PROJECT_READ_FILE_MAX_BYTES : byteLength; + const fileBytes = + bytesToRead === 0 + ? new Uint8Array() + : Option.getOrElse( + yield* file.readAlloc(bytesToRead).pipe( + Effect.mapError(toOperationError("read", realTargetPath, realTargetPath)), + ), + () => new Uint8Array(), + ); + if (fileBytes.includes(0)) { + return yield* new WorkspaceBinaryFileError({ + workspaceRoot: input.cwd, + relativePath: input.relativePath, + resolvedPath: realTargetPath, }); - const fileBytes = buffer.subarray(0, bytesRead); - if (fileBytes.includes(0)) { - return yield* new WorkspaceBinaryFileError({ - workspaceRoot: input.cwd, - relativePath: input.relativePath, - resolvedPath: realTargetPath, - }); - } + } - return { - relativePath: target.relativePath, - contents: new TextDecoder("utf-8").decode(fileBytes), - byteLength: stat.size, - truncated: stat.size > PROJECT_READ_FILE_MAX_BYTES, - }; - }), - (handle) => - Effect.tryPromise({ - try: () => handle.close(), - catch: (cause) => - new WorkspaceFileSystemOperationError({ - workspaceRoot: input.cwd, - relativePath: input.relativePath, - resolvedPath: realTargetPath, - operationPath: realTargetPath, - operation: "close", - cause, - }), - }), + return { + relativePath: target.relativePath, + contents: new TextDecoder("utf-8").decode(fileBytes), + byteLength, + truncated, + }; + }), ); }); From ccfad54d734fe50bc739ec7f0d2d116303bca23d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 3 Jul 2026 16:06:46 +0000 Subject: [PATCH 2/4] Test workspace reads use injected filesystem Co-authored-by: Julius Marminge --- .../src/workspace/WorkspaceFileSystem.test.ts | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/server/src/workspace/WorkspaceFileSystem.test.ts b/apps/server/src/workspace/WorkspaceFileSystem.test.ts index 7e75a94d40f..6fda81f7ea1 100644 --- a/apps/server/src/workspace/WorkspaceFileSystem.test.ts +++ b/apps/server/src/workspace/WorkspaceFileSystem.test.ts @@ -80,6 +80,26 @@ const writeTextFile = Effect.fn("writeTextFile")(function* ( yield* fileSystem.writeFileString(absolutePath, contents).pipe(Effect.orDie); }); +it.effect("WorkspaceFileSystem.readFile opens files through the injected FileSystem service", () => + Effect.gen(function* () { + const workspaceFileSystem = yield* WorkspaceFileSystem.WorkspaceFileSystem; + const cwd = yield* makeTempDir; + yield* writeTextFile(cwd, "src/index.ts", "export const answer = 42;\n"); + const fileSystem = yield* FileSystem.FileSystem; + const resolvedPath = yield* fileSystem.realPath(`${cwd}/src/index.ts`); + + const error = yield* workspaceFileSystem + .readFile({ cwd, relativePath: "src/index.ts" }) + .pipe(Effect.flip); + + assert.instanceOf(error, WorkspaceFileSystem.WorkspaceFileSystemOperationError); + assert.equal(error.operation, "open"); + assert.equal(error.operationPath, resolvedPath); + assert.instanceOf(error.cause, PlatformError.PlatformError); + assert.equal((error.cause as PlatformError.PlatformError).reason._tag, "PermissionDenied"); + }).pipe(Effect.provide(WorkspaceFileSystemOpenFailureLayer)), +); + it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (it) => { describe("readFile", () => { it.effect("reads UTF-8 files relative to the workspace root", () => @@ -218,26 +238,6 @@ it.layer(TestLayer, { excludeTestServices: true })("WorkspaceFileSystemLive", (i expect((error.cause as PlatformError.PlatformError).reason._tag).toBe("NotFound"); }), ); - - it.effect("opens files through the injected FileSystem service", () => - Effect.gen(function* () { - const workspaceFileSystem = yield* WorkspaceFileSystem.WorkspaceFileSystem; - const cwd = yield* makeTempDir; - yield* writeTextFile(cwd, "src/index.ts", "export const answer = 42;\n"); - const fileSystem = yield* FileSystem.FileSystem; - const resolvedPath = yield* fileSystem.realPath(`${cwd}/src/index.ts`); - - const error = yield* workspaceFileSystem - .readFile({ cwd, relativePath: "src/index.ts" }) - .pipe(Effect.flip); - - assert.instanceOf(error, WorkspaceFileSystem.WorkspaceFileSystemOperationError); - assert.equal(error.operation, "open"); - assert.equal(error.operationPath, resolvedPath); - assert.instanceOf(error.cause, PlatformError.PlatformError); - assert.equal((error.cause as PlatformError.PlatformError).reason._tag, "PermissionDenied"); - }).pipe(Effect.provide(WorkspaceFileSystemOpenFailureLayer)), - ); }); describe("writeFile", () => { From b356b22c7639bd48b0a2826e2d15657a325b3e7c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 3 Jul 2026 16:07:06 +0000 Subject: [PATCH 3/4] Provide path service in workspace filesystem test Co-authored-by: Julius Marminge --- apps/server/src/workspace/WorkspaceFileSystem.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/workspace/WorkspaceFileSystem.test.ts b/apps/server/src/workspace/WorkspaceFileSystem.test.ts index 6fda81f7ea1..e5a5a53d013 100644 --- a/apps/server/src/workspace/WorkspaceFileSystem.test.ts +++ b/apps/server/src/workspace/WorkspaceFileSystem.test.ts @@ -56,7 +56,7 @@ const WorkspaceFileSystemOpenFailureLayer = WorkspaceFileSystem.layer.pipe( Layer.provide(WorkspacePaths.layer), Layer.provide(Layer.mock(WorkspaceEntries.WorkspaceEntries)({})), Layer.provideMerge(OpenFailureFileSystemLayer), - Layer.provide(Path.layer), + Layer.provideMerge(Path.layer), ); const makeTempDir = Effect.gen(function* () { From e72791d6b8a6af47133d8c85df1bed48f16841e5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 3 Jul 2026 16:07:37 +0000 Subject: [PATCH 4/4] Format workspace filesystem effect chains Co-authored-by: Julius Marminge --- .../src/workspace/WorkspaceFileSystem.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/server/src/workspace/WorkspaceFileSystem.ts b/apps/server/src/workspace/WorkspaceFileSystem.ts index 4a3898a2476..a07f8c97b9d 100644 --- a/apps/server/src/workspace/WorkspaceFileSystem.ts +++ b/apps/server/src/workspace/WorkspaceFileSystem.ts @@ -154,12 +154,12 @@ export const make = Effect.gen(function* () { cause, }); - const realWorkspaceRoot = yield* fileSystem.realPath(input.cwd).pipe( - Effect.mapError(toOperationError("realpath-workspace-root", input.cwd)), - ); - const realTargetPath = yield* fileSystem.realPath(target.absolutePath).pipe( - Effect.mapError(toOperationError("realpath-target", target.absolutePath)), - ); + const realWorkspaceRoot = yield* fileSystem + .realPath(input.cwd) + .pipe(Effect.mapError(toOperationError("realpath-workspace-root", input.cwd))); + const realTargetPath = yield* fileSystem + .realPath(target.absolutePath) + .pipe(Effect.mapError(toOperationError("realpath-target", target.absolutePath))); const relativeRealPath = path.relative(realWorkspaceRoot, realTargetPath); if ( relativeRealPath.startsWith(`..${path.sep}`) || @@ -176,9 +176,9 @@ export const make = Effect.gen(function* () { return yield* Effect.scoped( Effect.gen(function* () { - const file = yield* fileSystem.open(realTargetPath, { flag: "r" }).pipe( - Effect.mapError(toOperationError("open", realTargetPath, realTargetPath)), - ); + const file = yield* fileSystem + .open(realTargetPath, { flag: "r" }) + .pipe(Effect.mapError(toOperationError("open", realTargetPath, realTargetPath))); const stat = yield* file.stat.pipe( Effect.mapError(toOperationError("stat", realTargetPath, realTargetPath)), ); @@ -197,9 +197,9 @@ export const make = Effect.gen(function* () { bytesToRead === 0 ? new Uint8Array() : Option.getOrElse( - yield* file.readAlloc(bytesToRead).pipe( - Effect.mapError(toOperationError("read", realTargetPath, realTargetPath)), - ), + yield* file + .readAlloc(bytesToRead) + .pipe(Effect.mapError(toOperationError("read", realTargetPath, realTargetPath))), () => new Uint8Array(), ); if (fileBytes.includes(0)) {