mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-30 17:14:40 +08:00
refactor(cli): scope project ID cache lifecycle
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
import { Cache, Context, Effect, Layer } from "effect"
|
||||
import { Instance } from "@/kilocode/instance"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { registerDisposer } from "@/effect/instance-registry"
|
||||
import { makeRuntime } from "@/effect/run-service"
|
||||
import path from "path"
|
||||
import { $ } from "bun"
|
||||
@@ -83,9 +83,7 @@ async function getProjectIdFromGit(directory: string): Promise<string | undefine
|
||||
* Resolve project ID with priority: .kilo/config.json -> .kilocode/config.json -> git origin URL
|
||||
* @returns Normalized project ID or undefined
|
||||
*/
|
||||
async function resolveProjectId(): Promise<string | undefined> {
|
||||
const dir = Instance.directory
|
||||
|
||||
async function resolveProjectId(dir: string): Promise<string | undefined> {
|
||||
// Priority 1: .kilo/config.json (falls back to .kilocode/config.json)
|
||||
const id = await getProjectIdFromConfig(dir)
|
||||
if (id) return id
|
||||
@@ -104,13 +102,15 @@ export namespace KiloProjectID {
|
||||
export const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const state = yield* InstanceState.make(
|
||||
Effect.fn("KiloProjectID.state")(function* () {
|
||||
return { id: yield* Effect.promise(() => resolveProjectId()) }
|
||||
}),
|
||||
)
|
||||
const cache = yield* Cache.make<string, string | undefined>({
|
||||
capacity: Number.POSITIVE_INFINITY,
|
||||
lookup: (directory) => Effect.promise(() => resolveProjectId(directory)),
|
||||
})
|
||||
const off = registerDisposer((directory) => Effect.runPromise(Cache.invalidate(cache, directory)))
|
||||
yield* Effect.addFinalizer(() => Effect.sync(off))
|
||||
|
||||
return Service.of({
|
||||
get: () => InstanceState.use(state, (s) => s.id),
|
||||
get: () => Cache.get(cache, Instance.directory),
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -2,8 +2,9 @@ import { test, expect, describe } from "bun:test"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
import path from "path"
|
||||
import fs from "fs/promises"
|
||||
import { provideTestInstance } from "../fixture/fixture"
|
||||
import { provideTestInstance, withTestInstance } from "../fixture/fixture"
|
||||
import { getKiloProjectId } from "../../src/kilocode/project-id"
|
||||
import { disposeInstance } from "../../src/effect/instance-registry"
|
||||
|
||||
describe("project-id", () => {
|
||||
describe("normalization", () => {
|
||||
@@ -365,26 +366,49 @@ describe("project-id", () => {
|
||||
})
|
||||
|
||||
describe("caching", () => {
|
||||
test("caches project ID per Instance", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
git: true,
|
||||
test("keeps project IDs isolated across active project contexts", async () => {
|
||||
await using first = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Bun.$`git remote add origin https://github.com/Kilo-Org/handbook.git`.cwd(dir).quiet()
|
||||
await fs.mkdir(path.join(dir, ".kilo"), { recursive: true })
|
||||
await Bun.write(path.join(dir, ".kilo", "config.json"), JSON.stringify({ project: { id: "first" } }))
|
||||
},
|
||||
})
|
||||
await using second = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await fs.mkdir(path.join(dir, ".kilo"), { recursive: true })
|
||||
await Bun.write(path.join(dir, ".kilo", "config.json"), JSON.stringify({ project: { id: "second" } }))
|
||||
},
|
||||
})
|
||||
|
||||
const id1 = await provideTestInstance({
|
||||
directory: tmp.path,
|
||||
fn: () => getKiloProjectId(),
|
||||
const ids = await Promise.all([
|
||||
withTestInstance({ directory: first.path, fn: () => getKiloProjectId() }),
|
||||
withTestInstance({ directory: second.path, fn: () => getKiloProjectId() }),
|
||||
])
|
||||
|
||||
expect(ids).toEqual(["first", "second"])
|
||||
})
|
||||
|
||||
test("invalidates the cached project ID when the instance is disposed", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await fs.mkdir(path.join(dir, ".kilo"), { recursive: true })
|
||||
await Bun.write(path.join(dir, ".kilo", "config.json"), JSON.stringify({ project: { id: "first" } }))
|
||||
},
|
||||
})
|
||||
|
||||
const id2 = await provideTestInstance({
|
||||
const ids = await provideTestInstance({
|
||||
directory: tmp.path,
|
||||
fn: () => getKiloProjectId(),
|
||||
fn: async (ctx) => {
|
||||
const first = await getKiloProjectId()
|
||||
await Bun.write(path.join(tmp.path, ".kilo", "config.json"), JSON.stringify({ project: { id: "second" } }))
|
||||
const cached = await getKiloProjectId()
|
||||
await disposeInstance(ctx.directory)
|
||||
const refreshed = await getKiloProjectId()
|
||||
return { first, cached, refreshed }
|
||||
},
|
||||
})
|
||||
|
||||
expect(id1).toBe(id2)
|
||||
expect(id1).toBe("handbook")
|
||||
expect(ids).toEqual({ first: "first", cached: "first", refreshed: "second" })
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
"packages/opencode/src/kilocode/background-process/index.ts": { "count": 1, "owner": "process-runtime", "reason": "Directory-keyed background process registry" },
|
||||
"packages/opencode/src/kilocode/interactive-terminal/index.ts": { "count": 1, "owner": "terminal-runtime", "reason": "Interactive terminal manager state" },
|
||||
"packages/opencode/src/kilocode/notebook/service.ts": { "count": 1, "owner": "notebook-runtime", "reason": "Notebook cell execution service state" },
|
||||
"packages/opencode/src/kilocode/project-id.ts": { "count": 1, "owner": "project-runtime", "reason": "Cached project identifier resolution" },
|
||||
"packages/opencode/src/kilocode/watcher.ts": { "count": 1, "owner": "watcher-runtime", "reason": "Eager location watcher subscription" }
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user