From 009d77c9d81e5188187bb9bc30fbc9111adba81e Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 18 Mar 2026 22:09:14 -0400 Subject: [PATCH] refactor(format): make formatting explicit instead of bus-driven Replace the implicit Bus.subscribe(File.Event.Edited) formatter with an explicit Format.run(filepath) call in write/edit/apply_patch tools. This ensures formatting completes before FileTime stamps and LSP diagnostics run, rather than relying on the bus to block on subscribers. - Add Format.run() to the Effect service interface and legacy adapter - Call Format.run() in write, edit, and apply_patch tools after writes - Remove Bus subscription from Format layer --- packages/opencode/src/format/index.ts | 85 +++++++++++------------ packages/opencode/src/tool/apply_patch.ts | 2 + packages/opencode/src/tool/edit.ts | 3 + packages/opencode/src/tool/write.ts | 2 + 4 files changed, 49 insertions(+), 43 deletions(-) diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts index 6da8caa08c..8be6ceab6d 100644 --- a/packages/opencode/src/format/index.ts +++ b/packages/opencode/src/format/index.ts @@ -4,9 +4,7 @@ import { InstanceContext } from "@/effect/instance-context" import path from "path" import { mergeDeep } from "remeda" import z from "zod" -import { Bus } from "../bus" import { Config } from "../config/config" -import { File } from "../file" import { Instance } from "../project/instance" import { Process } from "../util/process" import { Log } from "../util/log" @@ -27,6 +25,7 @@ export namespace Format { export type Status = z.infer export interface Interface { + readonly run: (filepath: string) => Effect.Effect readonly status: () => Effect.Effect } @@ -90,48 +89,44 @@ export namespace Format { return result } - yield* Effect.acquireRelease( - Effect.sync(() => - Bus.subscribe( - File.Event.Edited, - Instance.bind(async (payload) => { - const file = payload.properties.file - log.info("formatting", { file }) - const ext = path.extname(file) + const run = Effect.fn("Format.run")(function* (filepath: string) { + log.info("formatting", { file: filepath }) + const ext = path.extname(filepath) - for (const item of await getFormatter(ext)) { - log.info("running", { command: item.command }) - try { - const proc = Process.spawn( - item.command.map((x) => x.replace("$FILE", file)), - { - cwd: instance.directory, - env: { ...process.env, ...item.environment }, - stdout: "ignore", - stderr: "ignore", - }, - ) - const exit = await proc.exited - if (exit !== 0) { - log.error("failed", { - command: item.command, - ...item.environment, - }) - } - } catch (error) { - log.error("failed to format file", { - error, - command: item.command, - ...item.environment, - file, - }) - } + for (const item of yield* Effect.promise(() => getFormatter(ext))) { + log.info("running", { command: item.command }) + yield* Effect.tryPromise({ + try: async () => { + const proc = Process.spawn( + item.command.map((x) => x.replace("$FILE", filepath)), + { + cwd: instance.directory, + env: { ...process.env, ...item.environment }, + stdout: "ignore", + stderr: "ignore", + }, + ) + const exit = await proc.exited + if (exit !== 0) { + log.error("failed", { + command: item.command, + ...item.environment, + }) } - }), - ), - ), - (unsubscribe) => Effect.sync(unsubscribe), - ) + }, + catch: (error) => { + log.error("failed to format file", { + error, + command: item.command, + ...item.environment, + file: filepath, + }) + return error + }, + }).pipe(Effect.ignore) + } + }) + log.info("init") const status = Effect.fn("Format.status")(function* () { @@ -147,10 +142,14 @@ export namespace Format { return result }) - return Service.of({ status }) + return Service.of({ run, status }) }), ) + export async function run(filepath: string) { + return runPromiseInstance(Service.use((s) => s.run(filepath))) + } + export async function status() { return runPromiseInstance(Service.use((s) => s.status())) } diff --git a/packages/opencode/src/tool/apply_patch.ts b/packages/opencode/src/tool/apply_patch.ts index 06293b6eba..7b28ba94bb 100644 --- a/packages/opencode/src/tool/apply_patch.ts +++ b/packages/opencode/src/tool/apply_patch.ts @@ -10,6 +10,7 @@ import { createTwoFilesPatch, diffLines } from "diff" import { assertExternalDirectory } from "./external-directory" import { trimDiff } from "./edit" import { LSP } from "../lsp" +import { Format } from "../format" import { Filesystem } from "../util/filesystem" import DESCRIPTION from "./apply_patch.txt" import { File } from "../file" @@ -220,6 +221,7 @@ export const ApplyPatchTool = Tool.define("apply_patch", { } if (edited) { + await Format.run(edited) await Bus.publish(File.Event.Edited, { file: edited, }) diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts index 1a7614fc17..95226212df 100644 --- a/packages/opencode/src/tool/edit.ts +++ b/packages/opencode/src/tool/edit.ts @@ -13,6 +13,7 @@ import { File } from "../file" import { FileWatcher } from "../file/watcher" import { Bus } from "../bus" import { FileTime } from "../file/time" +import { Format } from "../format" import { Filesystem } from "../util/filesystem" import { Instance } from "../project/instance" import { Snapshot } from "@/snapshot" @@ -71,6 +72,7 @@ export const EditTool = Tool.define("edit", { }, }) await Filesystem.write(filePath, params.newString) + await Format.run(filePath) await Bus.publish(File.Event.Edited, { file: filePath, }) @@ -108,6 +110,7 @@ export const EditTool = Tool.define("edit", { }) await Filesystem.write(filePath, contentNew) + await Format.run(filePath) await Bus.publish(File.Event.Edited, { file: filePath, }) diff --git a/packages/opencode/src/tool/write.ts b/packages/opencode/src/tool/write.ts index 83474a543c..78d64fc4fc 100644 --- a/packages/opencode/src/tool/write.ts +++ b/packages/opencode/src/tool/write.ts @@ -8,6 +8,7 @@ import { Bus } from "../bus" import { File } from "../file" import { FileWatcher } from "../file/watcher" import { FileTime } from "../file/time" +import { Format } from "../format" import { Filesystem } from "../util/filesystem" import { Instance } from "../project/instance" import { trimDiff } from "./edit" @@ -42,6 +43,7 @@ export const WriteTool = Tool.define("write", { }) await Filesystem.write(filepath, params.content) + await Format.run(filepath) await Bus.publish(File.Event.Edited, { file: filepath, })