Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion packages/opencode/src/kilocode/session/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,16 @@ export namespace KiloSessionPrompt {
}
}

/**
* Ensures the plan file directory exists. Pre-checks with `Filesystem.isDir`
* because `fs.mkdir(recursive: true)` still throws `EEXIST` on Windows
* OneDrive ReparsePoint directories in some Node versions (kilocode#9755).
*/
export async function ensurePlanDir(dir: string) {
if (await Filesystem.isDir(dir)) return
await fs.mkdir(dir, { recursive: true })
}

/**
* Injects plan-specific reminders into the user message when using the plan agent.
* Ensures the plan file directory exists and tells the agent where to write.
Expand All @@ -145,7 +155,7 @@ export namespace KiloSessionPrompt {
if (input.agent.name !== "plan") return
const plan = Session.plan(input.session)
const exists = await Filesystem.exists(plan)
if (!exists) await fs.mkdir(path.dirname(plan), { recursive: true })
if (!exists) await ensurePlanDir(path.dirname(plan))
const info = exists
? `A plan file already exists at ${plan}. You can read it and make incremental edits using the edit tool.`
: `No plan file exists yet. You should create your plan at ${plan} using the write tool.`
Expand Down
32 changes: 32 additions & 0 deletions packages/opencode/test/kilocode/ensure-plan-dir.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, expect, test } from "bun:test"
import fs from "fs/promises"
import path from "path"
import { KiloSessionPrompt } from "../../src/kilocode/session/prompt"
import { tmpdir } from "../fixture/fixture"

describe("KiloSessionPrompt.ensurePlanDir", () => {
test("creates a missing plan directory", async () => {
await using tmp = await tmpdir({})
const dir = path.join(tmp.path, ".kilo", "plans")
await KiloSessionPrompt.ensurePlanDir(dir)
const stat = await fs.stat(dir)
expect(stat.isDirectory()).toBe(true)
})

test("is idempotent when the directory already exists", async () => {
await using tmp = await tmpdir({})
const dir = path.join(tmp.path, ".kilo", "plans")
await fs.mkdir(dir, { recursive: true })
await expect(KiloSessionPrompt.ensurePlanDir(dir)).resolves.toBeUndefined()
const stat = await fs.stat(dir)
expect(stat.isDirectory()).toBe(true)
})

test("creates intermediate parent directories", async () => {
await using tmp = await tmpdir({})
const dir = path.join(tmp.path, "deep", "nested", ".kilo", "plans")
await KiloSessionPrompt.ensurePlanDir(dir)
const stat = await fs.stat(dir)
expect(stat.isDirectory()).toBe(true)
})
})