import assert from "node:assert/strict" import test from "node:test" import toolkit from "../dist/plugin.js" function createContext({ options = {}, projectID = "project-1", projectCanonical = "/workspace/demo", storage = new Map() } = {}) { const registered = [] const vcs = { get: async () => ({ branch: { current: "feature/toolkit", default: "main" } }), status: async () => [{ path: "src/app.ts", status: "modified" }], } const context = { location: { directory: "/workspace/demo", project: { id: projectID, directory: "/workspace/demo", canonical: projectCanonical }, }, options, vcs, session: { get: async ({ sessionID }) => ({ id: sessionID, projectID, location: { directory: "/workspace/demo" }, }), }, storage: { get: async (key) => storage.get(key), set: async (key, value) => { storage.set(key, value) }, remove: async (key) => { storage.delete(key) }, scan: async ({ prefix, after, limit }) => { const keys = [...storage.keys()].filter((key) => key.startsWith(prefix)).sort().filter((key) => after === undefined || key > after) const selected = keys.slice(0, limit) return { entries: selected.map((key) => ({ key, value: storage.get(key) })), ...(keys.length > selected.length && selected.length > 0 ? { next: selected.at(-1) } : {}), } }, }, tool: { transform: async (register) => { register({ namespace: () => {}, add: (definition) => registered.push(definition), list: () => [], get: () => undefined, update: () => {}, remove: () => {}, }) return { dispose: async () => {} } }, }, } return { context, registered, vcs } } test("exports a stable OpenCode V2 plugin definition", () => { assert.equal(toolkit.id, "sargatxet.opencode-toolkit") assert.equal(typeof toolkit.setup, "function") }) test("registers namespaced read-only project status tool", async () => { const { context, registered } = createContext() await toolkit.setup(context) assert.equal(registered.length, 1) assert.equal(registered[0].name, "project_status") assert.deepEqual(registered[0].options, { namespace: "toolkit", codemode: true }) assert.equal(typeof registered[0].execute, "function") assert.deepEqual(registered[0].input, { type: "object", properties: {}, additionalProperties: false, }) }) test("status tool reports VCS context without mutating it", async () => { const { context, registered, vcs } = createContext() const originalGet = vcs.get const originalStatus = vcs.status await toolkit.setup(context) const result = await registered[0].execute({}, { signal: new AbortController().signal }) assert.deepEqual(JSON.parse(result.content), { project: { id: "project-1", directory: "/workspace/demo", canonical: "/workspace/demo", }, vcs: { branch: { current: "feature/toolkit", default: "main" }, }, changes: [{ path: "src/app.ts", status: "modified" }], }) assert.equal(vcs.get, originalGet) assert.equal(vcs.status, originalStatus) }) test("memory tools are disabled unless explicitly enabled", async () => { const { context, registered } = createContext() await toolkit.setup(context) assert.deepEqual(registered.map((tool) => tool.name), ["project_status"]) }) test("session memories can be saved, listed, retrieved, and deleted without crossing sessions", async () => { const { context, registered } = createContext({ options: { memory: { enabled: true } } }) await toolkit.setup(context) const tools = Object.fromEntries(registered.map((tool) => [tool.name, tool])) const execution = (sessionID) => ({ sessionID, signal: new AbortController().signal }) const saved = JSON.parse((await tools.memory_save.execute({ content: "Use strict TypeScript." }, execution("session-a"))).content) assert.equal(saved.scope, "session") assert.equal(typeof saved.expiresAt, "string") assert.deepEqual(JSON.parse((await tools.memory_list.execute({}, execution("session-a"))).content).map(({ id }) => id), [saved.id]) assert.deepEqual(JSON.parse((await tools.memory_list.execute({}, execution("session-b"))).content), []) assert.match((await tools.memory_get.execute({ id: saved.id }, execution("session-a"))).content, /Use strict TypeScript/) assert.equal((await tools.memory_delete.execute({ id: saved.id }, execution("session-a"))).content, "Memory deleted.") assert.deepEqual(JSON.parse((await tools.memory_list.execute({}, execution("session-a"))).content), []) }) test("project memories require explicit configuration and are isolated by project", async () => { const sharedStorage = new Map() const enabled = createContext({ options: { memory: { enabled: true, project: true } }, storage: sharedStorage }) await toolkit.setup(enabled.context) const tools = Object.fromEntries(enabled.registered.map((tool) => [tool.name, tool])) const execution = (sessionID) => ({ sessionID, signal: new AbortController().signal }) const saved = JSON.parse((await tools.memory_save.execute({ content: "Shared project convention", scope: "project" }, execution("session-a"))).content) assert.equal(saved.scope, "project") assert.equal(JSON.parse((await tools.memory_list.execute({ scope: "project" }, execution("session-b"))).content).length, 1) const other = createContext({ options: { memory: { enabled: true, project: true } }, projectID: "project-2", projectCanonical: "/workspace/other", storage: sharedStorage, }) await toolkit.setup(other.context) const otherTools = Object.fromEntries(other.registered.map((tool) => [tool.name, tool])) assert.deepEqual(JSON.parse((await otherTools.memory_list.execute({ scope: "project" }, execution("session-c"))).content), []) }) test("project scope fails closed when project memory has not been opted into", async () => { const { context, registered } = createContext({ options: { memory: { enabled: true } } }) await toolkit.setup(context) const save = registered.find((tool) => tool.name === "memory_save") await assert.rejects( save.execute({ content: "Not enabled", scope: "project" }, { sessionID: "session-a", signal: new AbortController().signal }), /Project memory is disabled/, ) })