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
24 changes: 18 additions & 6 deletions internal/setup/plugins/opencode/engram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,15 +153,20 @@ async function isEngramRunning(): Promise<boolean> {

// ─── Helpers ─────────────────────────────────────────────────────────────────

function extractProjectName(directory: string): string {
function basename(path: string): string {
const name = path.split(/[\\/]/).filter(Boolean).pop()
return name && !/^[A-Za-z]:$/.test(name) ? name : "unknown"
}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
function extractProjectNames(directory: string): { oldProject: string; project: string } {
// Try git remote origin URL
try {
const result = Bun.spawnSync(["git", "-C", directory, "remote", "get-url", "origin"])
if (result.exitCode === 0) {
const url = result.stdout?.toString().trim()
if (url) {
const name = url.replace(/\.git$/, "").split(/[/:]/).pop()
if (name) return name
if (name) return { oldProject: name, project: name }
}
}
} catch {}
Expand All @@ -171,12 +176,20 @@ function extractProjectName(directory: string): string {
const result = Bun.spawnSync(["git", "-C", directory, "rev-parse", "--show-toplevel"])
if (result.exitCode === 0) {
const root = result.stdout?.toString().trim()
if (root) return root.split("/").pop() ?? "unknown"
if (root) {
return {
oldProject: root.split("/").pop() ?? "unknown",
project: basename(root),
}
}
}
} catch {}

// Final fallback: cwd basename
return directory.split("/").pop() ?? "unknown"
return {
oldProject: directory.split("/").pop() ?? "unknown",
project: basename(directory),
}
}

function truncate(str: string, max: number): string {
Expand All @@ -197,8 +210,7 @@ function stripPrivateTags(str: string): string {
// ─── Plugin Export ───────────────────────────────────────────────────────────

export const Engram: Plugin = async (ctx) => {
const oldProject = ctx.directory.split("/").pop() ?? "unknown"
const project = extractProjectName(ctx.directory)
const { oldProject, project } = extractProjectNames(ctx.directory)

// Track tool counts per session (in-memory only, not critical)
const toolCounts = new Map<string, number>()
Expand Down
158 changes: 158 additions & 0 deletions plugin/opencode/engram.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import assert from "node:assert/strict"
import { test } from "node:test"

import { Engram } from "./engram.ts"

type SpawnResult = {
exitCode: number
stdout?: Buffer
}

async function createPlugin(
directory: string,
spawnSync: (command: string[]) => SpawnResult,
) {
const requests: Array<{ path: string; body?: unknown }> = []

globalThis.Bun = {
spawnSync,
file: () => ({ exists: async () => false }),
} as typeof Bun

globalThis.fetch = (async (input: string | URL | Request, init?: RequestInit) => {
const url = new URL(input.toString())
const body = init?.body ? JSON.parse(init.body.toString()) : undefined
requests.push({ path: url.pathname, body })

return new Response(JSON.stringify({}), {
status: 200,
headers: { "Content-Type": "application/json" },
})
}) as typeof fetch

const plugin = await Engram({ directory } as never)
return { plugin, requests }
}

test("uses the Windows basename in compaction recovery outside Git", async () => {
const { plugin, requests } = await createPlugin("C:\\Users\\Blackie", () => ({ exitCode: 1 }))
const output = { context: [] as string[] }

await plugin["experimental.session.compacting"]?.(
{ sessionID: "session-652" } as never,
output,
)

assert.match(output.context.at(-1) ?? "", /Use project: 'Blackie'/)
assert.doesNotMatch(output.context.at(-1) ?? "", /C:\\Users\\Blackie/)
assert.deepEqual(
requests.find(({ path }) => path === "/projects/migrate")?.body,
{ old_project: "C:\\Users\\Blackie", new_project: "Blackie" },
)
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.

test("ignores trailing Windows separators in the basename fallback", async () => {
const { plugin, requests } = await createPlugin("C:\\Users\\Blackie\\", () => ({ exitCode: 1 }))
const output = { context: [] as string[] }

await plugin["experimental.session.compacting"]?.(
{ sessionID: "session-652-trailing" } as never,
output,
)

assert.match(output.context.at(-1) ?? "", /Use project: 'Blackie'/)
assert.deepEqual(
requests.find(({ path }) => path === "/projects/migrate")?.body,
{ old_project: "C:\\Users\\Blackie\\", new_project: "Blackie" },
)
})

test("uses the Windows basename from the Git-root fallback", async () => {
const { plugin } = await createPlugin("C:\\Users\\Blackie\\project", (command) => {
if (command.includes("rev-parse")) {
return {
exitCode: 0,
stdout: Buffer.from("C:\\Users\\Blackie\\worktrees\\engram-652\n"),
}
}
return { exitCode: 1 }
})
const output = { context: [] as string[] }

await plugin["experimental.session.compacting"]?.(
{ sessionID: "session-652-git-root" } as never,
output,
)

assert.match(output.context.at(-1) ?? "", /Use project: 'engram-652'/)
})

test("does not migrate when the legacy remote project is unchanged", async () => {
const { requests } = await createPlugin("C:\\Users\\Blackie", (command) => {
if (command.includes("get-url")) {
return {
exitCode: 0,
stdout: Buffer.from("https://github.com/Gentleman-Programming/engram.git\n"),
}
}
return { exitCode: 1 }
})

assert.equal(requests.some(({ path }) => path === "/projects/migrate"), false)
})

test("migrates the legacy Windows Git-root key from a nested directory", async () => {
const { requests } = await createPlugin("C:\\repos\\engram\\nested", (command) => {
if (command.includes("rev-parse")) {
return {
exitCode: 0,
stdout: Buffer.from("C:\\repos\\engram\n"),
}
}
return { exitCode: 1 }
})

assert.deepEqual(
requests.find(({ path }) => path === "/projects/migrate")?.body,
{ old_project: "C:\\repos\\engram", new_project: "engram" },
)
})

test("preserves POSIX basename fallback behavior", async () => {
const { plugin } = await createPlugin("/home/blackie", () => ({ exitCode: 1 }))
const output = { context: [] as string[] }

await plugin["experimental.session.compacting"]?.(
{ sessionID: "session-posix" } as never,
output,
)

assert.match(output.context.at(-1) ?? "", /Use project: 'blackie'/)
})

test("migrates the legacy empty POSIX key for a trailing separator", async () => {
const { requests } = await createPlugin("/home/blackie/", () => ({ exitCode: 1 }))

assert.deepEqual(
requests.find(({ path }) => path === "/projects/migrate")?.body,
{ old_project: "", new_project: "blackie" },
)
})

for (const [directory, oldProject] of [["C:\\", "C:\\"], ["C:/", ""]]) {
test(`uses unknown for the Windows drive root ${directory}`, async () => {
const { plugin, requests } = await createPlugin(directory, () => ({ exitCode: 1 }))
const output = { context: [] as string[] }

await plugin["experimental.session.compacting"]?.(
{ sessionID: `session-drive-root-${directory}` } as never,
output,
)

assert.match(output.context.at(-1) ?? "", /Use project: 'unknown'/)
assert.deepEqual(
requests.find(({ path }) => path === "/projects/migrate")?.body,
{ old_project: oldProject, new_project: "unknown" },
)
})
}
24 changes: 18 additions & 6 deletions plugin/opencode/engram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,15 +153,20 @@ async function isEngramRunning(): Promise<boolean> {

// ─── Helpers ─────────────────────────────────────────────────────────────────

function extractProjectName(directory: string): string {
function basename(path: string): string {
const name = path.split(/[\\/]/).filter(Boolean).pop()
return name && !/^[A-Za-z]:$/.test(name) ? name : "unknown"
}

function extractProjectNames(directory: string): { oldProject: string; project: string } {
// Try git remote origin URL
try {
const result = Bun.spawnSync(["git", "-C", directory, "remote", "get-url", "origin"])
if (result.exitCode === 0) {
const url = result.stdout?.toString().trim()
if (url) {
const name = url.replace(/\.git$/, "").split(/[/:]/).pop()
if (name) return name
if (name) return { oldProject: name, project: name }
}
}
} catch {}
Expand All @@ -171,12 +176,20 @@ function extractProjectName(directory: string): string {
const result = Bun.spawnSync(["git", "-C", directory, "rev-parse", "--show-toplevel"])
if (result.exitCode === 0) {
const root = result.stdout?.toString().trim()
if (root) return root.split("/").pop() ?? "unknown"
if (root) {
return {
oldProject: root.split("/").pop() ?? "unknown",
project: basename(root),
}
}
}
} catch {}

// Final fallback: cwd basename
return directory.split("/").pop() ?? "unknown"
return {
oldProject: directory.split("/").pop() ?? "unknown",
project: basename(directory),
}
}

function truncate(str: string, max: number): string {
Expand All @@ -197,8 +210,7 @@ function stripPrivateTags(str: string): string {
// ─── Plugin Export ───────────────────────────────────────────────────────────

export const Engram: Plugin = async (ctx) => {
const oldProject = ctx.directory.split("/").pop() ?? "unknown"
const project = extractProjectName(ctx.directory)
const { oldProject, project } = extractProjectNames(ctx.directory)

// Track tool counts per session (in-memory only, not critical)
const toolCounts = new Map<string, number>()
Expand Down