import { tool } from "@opencode-ai/plugin" import { resolveWorktree, debugLog, loadConfig } from "../state-io.ts" import { getMemoryManager } from "../memory/index.ts" import type { MemoryCategory, MemoryEntry, MemorySearchResult } from "../types.ts" import { basename } from "path" const _initializedWorktrees = new Set() export default tool({ description: "Store and query product/technical decisions with semantic search. " + "Actions: store (save a decision), query (semantic search), list (all entries), delete (remove entry), update (edit existing).", args: { action: tool.schema .enum(["store", "query", "list", "delete", "update"]) .describe("Action to perform: store | query | list | delete | update"), category: tool.schema .enum(["product", "technical", "architecture", "convention"]) .optional() .describe("Category (for store: required; for list: optional filter; for update: optional)"), title: tool.schema .string() .optional() .describe("Short label for the entry (store and update)"), content: tool.schema .string() .optional() .describe("Full description of the decision (store and update)"), tags: tool.schema .array(tool.schema.string()) .optional() .describe("Tags for categorization (store and update)"), query: tool.schema .string() .optional() .describe("Search text (query only)"), max_results: tool.schema .number() .optional() .describe("Maximum number of results (query only)"), summary: tool.schema .boolean() .optional() .describe("Return compact titles+tags only instead of full entries (query only)"), id: tool.schema .string() .optional() .describe("Entry ID (delete and update)"), }, async execute(args, context) { const wt = resolveWorktree(context.worktree) const config = loadConfig(wt) if (!config.memory.enabled) { return JSON.stringify({ error: "Memory system is disabled. Enable it in agenteam.config.json" }) } const mm = getMemoryManager(wt) if (!_initializedWorktrees.has(wt)) { try { await mm.init(wt, config.memory) _initializedWorktrees.add(wt) } catch (err) { return JSON.stringify({ error: `Memory initialization failed: ${String(err)}` }) } } // --- store --- if (args.action === "store") { if (!args.category || !args.title || !args.content) { return JSON.stringify({ error: "store requires category, title, and content" }) } const entry: MemoryEntry = { id: crypto.randomUUID(), category: args.category, title: args.title, content: args.content, tags: args.tags ?? [], project: basename(wt), created_at: new Date().toISOString(), source: "manual", } try { await mm.store(entry) } catch (err) { return JSON.stringify({ error: `Store failed: ${String(err)}` }) } debugLog("tool", "memory.store", { id: entry.id, category: entry.category, title: entry.title, }) return JSON.stringify({ id: entry.id, stored: true }) } // --- query --- if (args.action === "query") { if (!args.query) { return JSON.stringify({ error: "query requires a query string" }) } let results: MemorySearchResult[] try { results = await mm.query(args.query, args.max_results) } catch (err) { return JSON.stringify({ error: `Query failed: ${String(err)}` }) } debugLog("tool", "memory.query", { query: args.query, max_results: args.max_results, result_count: results.length, }) if (args.summary) { const lines = results.map((r, i) => { const e = r.entry return `${i + 1}. [${e.category}] ${e.title} — tags: ${e.tags.join(", ")} (score: ${r.score.toFixed(3)})` }) return JSON.stringify({ summary: lines.join("\n"), count: results.length }) } return JSON.stringify({ results, count: results.length }) } // --- list --- if (args.action === "list") { let entries: MemoryEntry[] try { entries = await mm.list(args.category as MemoryCategory | undefined) } catch (err) { return JSON.stringify({ error: `List failed: ${String(err)}` }) } debugLog("tool", "memory.list", { category: args.category ?? "all", count: entries.length, }) return JSON.stringify({ entries, count: entries.length }) } // --- delete --- if (args.action === "delete") { if (!args.id) { return JSON.stringify({ error: "delete requires an id" }) } try { await mm.delete(args.id) } catch (err) { return JSON.stringify({ error: `Delete failed: ${String(err)}` }) } debugLog("tool", "memory.delete", { id: args.id }) return JSON.stringify({ ok: true }) } // --- update --- if (args.action === "update") { if (!args.id) { return JSON.stringify({ error: "update requires an id" }) } const fields: Record = {} if (args.title !== undefined) fields.title = args.title if (args.content !== undefined) fields.content = args.content if (args.tags !== undefined) fields.tags = args.tags if (args.category !== undefined) fields.category = args.category if (Object.keys(fields).length === 0) { return JSON.stringify({ error: "update requires at least one field to update" }) } try { await mm.update(args.id, fields) } catch (err) { return JSON.stringify({ error: `Update failed: ${String(err)}` }) } debugLog("tool", "memory.update", { id: args.id, fields: Object.keys(fields) }) return JSON.stringify({ id: args.id, updated: true, fields: Object.keys(fields) }) } return JSON.stringify({ error: `Unknown action: ${args.action}` }) }, })