Merge pull request #10684 from Kilo-Org/effect-project-facade-10676

refactor(cli): remove Project promise facade
This commit is contained in:
Marius
2026-05-28 17:45:25 +02:00
committed by GitHub
6 changed files with 72 additions and 10 deletions
@@ -5,6 +5,7 @@ import { ProjectID } from "../../project/schema"
import { WorkspaceID } from "../../control-plane/schema"
import { SessionImportType } from "./types"
import { Project } from "../../project/project"
import { AppRuntime } from "../../effect/app-runtime"
import { eq } from "drizzle-orm"
const key = (input: unknown) => [input] as never
@@ -18,7 +19,7 @@ export namespace SessionImportService {
throw new Error("Legacy project import requires a non-empty worktree")
}
const result = await Project.fromDirectory(input.worktree)
const result = await AppRuntime.runPromise(Project.Service.use((svc) => svc.fromDirectory(input.worktree)))
return { ok: true, id: result.project.id }
}
@@ -32,7 +32,8 @@ export namespace WorktreeFamily {
}
}
const dirs = [ctx.worktree, ...(yield* Effect.promise(() => Project.sandboxes(ctx.project.id)))]
const project = yield* Project.Service
const dirs = [ctx.worktree, ...(yield* project.sandboxes(ctx.project.id))]
return [...new Set(dirs.map((dir) => Filesystem.resolve(dir)))]
})
}
-7
View File
@@ -5,7 +5,6 @@ import { eq } from "drizzle-orm"
import { ProjectTable } from "./project.sql"
import { SessionTable } from "../session/session.sql"
import * as Log from "@opencode-ai/core/util/log"
import { makeRuntime } from "@/effect/run-service" // kilocode_change
import { Flag } from "@opencode-ai/core/flag/flag"
import { BusEvent } from "@/bus/bus-event"
import { GlobalBus } from "@/bus/global"
@@ -540,10 +539,4 @@ export function setInitialized(id: ProjectID) {
)
}
// kilocode_change start - legacy promise helpers for Kilo callsites
const { runPromise } = makeRuntime(Service, defaultLayer)
export const fromDirectory = (directory: string) => runPromise((svc) => svc.fromDirectory(directory))
export const sandboxes = (id: ProjectID) => runPromise((svc) => svc.sandboxes(id))
// kilocode_change end
export * as Project from "./project"
@@ -1,6 +1,8 @@
import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test"
import { Database } from "../../src/storage/db"
import { SessionImportService } from "../../src/kilocode/session-import/service"
import { resetDatabase } from "../fixture/db"
import { tmpdir } from "../fixture/fixture"
let spy: ReturnType<typeof spyOn>
@@ -76,6 +78,37 @@ function input(force?: boolean) {
}
}
function project(worktree: string) {
return {
id: "legacy_project",
worktree,
timeCreated: 1,
timeUpdated: 1,
sandboxes: [],
}
}
describe("SessionImportService.project", () => {
afterEach(async () => {
await resetDatabase()
})
test("rejects an empty legacy worktree", async () => {
await expect(SessionImportService.project(project(" "))).rejects.toThrow(
"Legacy project import requires a non-empty worktree",
)
})
test("resolves a valid legacy project through Project.Service", async () => {
await using tmp = await tmpdir({ git: true })
const result = await SessionImportService.project(project(tmp.path))
expect(result.ok).toBe(true)
expect(result.id).not.toBe("global")
})
})
describe("SessionImportService.session", () => {
beforeEach(() => {
spy = spyOn(Database, "use").mockImplementation((fn: any) => fn(db))
@@ -0,0 +1,35 @@
import { describe, expect } from "bun:test"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { Effect, Layer } from "effect"
import { Git } from "../../src/git"
import { InstanceRef } from "../../src/effect/instance-ref"
import { WorktreeFamily } from "../../src/kilocode/worktree-family"
import { Project } from "../../src/project/project"
import { resetDatabase } from "../fixture/db"
import { tmpdirScoped } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
const it = testEffect(Layer.mergeAll(Project.defaultLayer, Git.defaultLayer, CrossSpawnSpawner.defaultLayer))
describe("WorktreeFamily.list", () => {
it.live("returns recorded sandboxes when git worktree listing fails", () =>
Effect.gen(function* () {
yield* Effect.addFinalizer(() => Effect.promise(() => resetDatabase()))
const root = yield* tmpdirScoped()
const sandbox = yield* tmpdirScoped()
const project = yield* Project.Service
const info = (yield* project.fromDirectory(root)).project
yield* project.addSandbox(info.id, sandbox)
const dirs = yield* WorktreeFamily.list().pipe(
Effect.provideService(InstanceRef, {
directory: root,
worktree: root,
project: { ...info, vcs: "git" },
}),
)
expect(dirs).toEqual([root, sandbox])
}),
)
})