diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 6b7042d..bbbf93d 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -125,7 +125,7 @@ Rather than replacing existing memory bank systems, agentMemory **enhances** the - Bi-directional sync engine - Parses markdown files from agents' memory banks - Exports MCP memories to markdown format -- Supports KiloCode, Cline, and RooCode +- Supports KiloCode, Cline, RooCode, and OpenCode **File Mapping:** ```typescript @@ -152,6 +152,14 @@ RooCode: .roo/memory-bank/ techContext.md → decision progress.md → feature decisionLog.md → decision + +OpenCode: .opencode/memory-bank/ + architecture.md → architecture + patterns.md → pattern + decisions.md → decision + features.md → feature + +OpenCode also syncs to AGENTS.md (project root) for session-start context. ``` ### 3. Configuration Management (`src/config.ts`) @@ -164,9 +172,17 @@ RooCode: .roo/memory-bank/ **Agent Detection:** ```typescript detectInstalledAgents(): string[] { - // Checks for: + // Checks for VS Code extensions: // - saoudrizwan.claude-dev (Cline) // - kilocode.kilo-code (KiloCode) + // - roo-cline.roo-cline (RooCode) + // - Continue.continue (Continue) + + // Also checks for OpenCode by file system: + // - opencode.json in workspace root + // - .opencode/ directory in workspace +} +``` // - rooveterinaryinc.roo-cline (RooCode) } ``` diff --git a/README.md b/README.md index ba7af4f..60e760d 100644 --- a/README.md +++ b/README.md @@ -153,11 +153,12 @@ memory_search({ ### 🤖 Multi-Agent Support -| Agent | Memory Bank Location | Sync Status | -|-------|---------------------|-------------| -| **KiloCode** | `.kilocode/rules/memory-bank/` | ✅ Full sync | -| **Cline** | `.clinerules/memory-bank/` | ✅ Full sync | -| **RooCode** | `.roo/memory-bank/` | ✅ Full sync | +| Agent | Type | Memory Bank Location | Sync Status | +|-------|------|---------------------|-------------| +| **KiloCode** | VS Code Extension | `.kilocode/rules/memory-bank/` | ✅ Full sync | +| **Cline** | VS Code Extension | `.clinerules/memory-bank/` | ✅ Full sync | +| **RooCode** | VS Code Extension | `.roo/memory-bank/` | ✅ Full sync | +| **OpenCode** | Terminal TUI | `AGENTS.md` + `.opencode/commands/` | ✅ Full sync | **Files Synced:** - `projectBrief.md` / `brief.md` @@ -260,6 +261,16 @@ your-project/ │ ├── uuid-002.json # Memory: API patterns │ └── ... │ +├── AGENTS.md # OpenCode rules (auto-created) +│ +├── opencode.json # OpenCode MCP config (auto-created) +│ +├── .opencode/ +│ └── commands/ # OpenCode custom commands (auto-created) +│ ├── memory-search.md +│ ├── memory-write.md +│ └── memory-review.md +│ ├── .kilocode/rules/memory-bank/ # KiloCode memory bank │ ├── brief.md # ⬍ Synced with our database │ ├── architecture.md # ⬍ Auto-updated @@ -323,7 +334,7 @@ your-project/ | **Search** | ✅ Fast indexed | ❌ No | ✅ Yes | | **Analytics** | ✅ Dashboard | ❌ No | ❌ No | | **Automation** | ✅ Auto-sync | ❌ Manual | ⚠️ Partial | -| **Multi-Agent** | ✅ All 3 | ✅ Per-agent | ✅ All | +| **Multi-Agent** | ✅ All 4 | ✅ Per-agent | ✅ All | | **Git-Friendly** | ✅ Yes | ✅ Yes | ⚠️ Depends | | **Cross-Project** | ✅ Yes | ❌ No | ❌ No | diff --git a/SKILL.md b/SKILL.md index d9f22eb..8842ae1 100644 --- a/SKILL.md +++ b/SKILL.md @@ -17,22 +17,76 @@ This skill extends your capabilities by providing a persistent, searchable memor ## Setup +### For VS Code Agents (Cline, RooCode, KiloCode) + 1. **Install Dependencies**: ```bash - npm install + cd ~/.agents/skills/agent-memory && npm install && npm run compile ``` -2. **Build the Project**: +2. **Start the Memory Server**: ```bash - npm run compile + npm run start-server ``` -3. **Start the Memory Server**: - You need to run the MCP server to interact with the memory bank. +### For OpenCode (Terminal-based Agent) + +1. **Build the MCP server**: ```bash - npm run start-server + cd ~/.agents/skills/agent-memory && npm install && npm run compile + ``` + +2. **Add MCP server to your `opencode.json`** in the project root: + ```json + { + "$schema": "https://opencode.ai/config.json", + "mcp": { + "agentmemory": { + "type": "local", + "command": ["node", "/Users/YOUR_USER/.agents/skills/agent-memory/out/mcp-server/server.js", "PROJECT_ID", "/ABSOLUTE/PATH/TO/WORKSPACE"], + "enabled": true + } + } + } + ``` + Replace `YOUR_USER`, `PROJECT_ID`, and `/ABSOLUTE/PATH/TO/WORKSPACE` with actual values. + +3. **Add memory instructions to `AGENTS.md`** in the project root: + ```markdown + ## agentMemory System (REQUIRED) + + This project uses agentMemory for persistent knowledge management. + + ### Required Workflow + + **EVERY task MUST follow this sequence:** + + 1. **Before ANY work:** Call `memory_search()` to check existing knowledge + 2. **After ANY significant work:** Call `memory_write()` to document what was done + + ### Available MCP Tools + + - `agentmemory_memory_search` - Search for memories by query, type, or tags + - `agentmemory_memory_write` - Save new memory + - `agentmemory_memory_read` - Retrieve specific memory by key + - `agentmemory_memory_list` - List memories by type + - `agentmemory_memory_update` - Update existing memory + - `agentmemory_memory_stats` - View memory statistics + - `agentmemory_project_init` - Initialize project storage + + **Failure to use memory tools = Incomplete work** + ``` + +4. **Optionally add custom commands** to `.opencode/commands/memory-search.md`: + ```markdown + --- + description: Search project memories + --- + Search the agentMemory system for relevant context about: $ARGUMENTS + + Use the agentmemory_memory_search tool with query "$ARGUMENTS". + If results are found, summarize them clearly. If no results, suggest creating a new memory. ``` - *Note: This skill typically runs as a background process or via an mcp-server configuration. ensuring it is running is key.* ## Capabilities (MCP Tools) @@ -57,9 +111,18 @@ Retrieve specific memory content by key. View analytics on memory usage. - **Usage**: "Show memory statistics" -> `memory_stats({})` +## Supported Agents + +| Agent | Type | Config Location | Memory Bank Path | +|-------|------|-----------------|------------------| +| KiloCode | VS Code Extension | VS Code MCP settings | `.kilocode/rules/memory-bank/` | +| Cline | VS Code Extension | VS Code MCP settings | `.clinerules/memory-bank/` | +| RooCode | VS Code Extension | VS Code MCP settings | `.roo/memory-bank/` | +| **OpenCode** | Terminal TUI | `opencode.json` | `AGENTS.md` + `.opencode/commands/` | + ## Workflow -1. **Initialization**: The first time you run this in a project, it may attempt to import existing markdown memory banks from `.kilocode/`, `.clinerules/`, or `.roo/`. +1. **Initialization**: The first time you run this in a project, it may attempt to import existing markdown memory banks from `.kilocode/`, `.clinerules/`, `.roo/`, or `AGENTS.md`. 2. **Development Loop**: - **Before Task**: Search memory for relevant context. - **During Task**: Use read/search to answer questions. diff --git a/src/config.ts b/src/config.ts index f306444..ba31097 100644 --- a/src/config.ts +++ b/src/config.ts @@ -34,6 +34,12 @@ export class ConfigManager { // Configure each agent's settings file for (const agent of installedAgents) { + // OpenCode uses opencode.json (handled by InterceptorManager), skip VS Code settings + if (agent === 'opencode') { + this.outputChannel.appendLine(` ℹ️ opencode: configured via opencode.json (handled by interceptor)`); + continue; + } + const settingsPath = this.getAgentSettingsPath(agent); if (!settingsPath) { this.outputChannel.appendLine(`⚠️ Unknown settings path for ${agent}`); @@ -117,6 +123,23 @@ export class ConfigManager { } } + // Detect OpenCode by checking for opencode.json or .opencode/ directory + const workspacePath = this.workspaceFolder.uri.fsPath; + + try { + await fs.access(path.join(workspacePath, 'opencode.json')); + installed.push('opencode'); + this.outputChannel.appendLine(' ✅ opencode: opencode.json found'); + } catch { + try { + await fs.access(path.join(workspacePath, '.opencode')); + installed.push('opencode'); + this.outputChannel.appendLine(' ✅ opencode: .opencode/ directory found'); + } catch { + // OpenCode not detected + } + } + return installed; } } diff --git a/src/interceptor.ts b/src/interceptor.ts index 9a3fa52..cf089f6 100644 --- a/src/interceptor.ts +++ b/src/interceptor.ts @@ -37,6 +37,11 @@ export class InterceptorManager { if (installedAgents.includes('continue')) { promises.push(this.injectContinueConfig(workspacePath)); } + if (installedAgents.includes('opencode')) { + promises.push(this.injectOpenCodeRules(workspacePath)); + promises.push(this.injectOpenCodeCommands(workspacePath)); + promises.push(this.injectOpenCodeMCPConfig(workspacePath)); + } await Promise.all(promises); @@ -63,6 +68,25 @@ export class InterceptorManager { } } + // Detect OpenCode by checking for opencode.json or .opencode/ directory + const workspacePath = this.workspaceFolder.uri.fsPath; + const opencodeConfigPath = path.join(workspacePath, 'opencode.json'); + const opencodeDirPath = path.join(workspacePath, '.opencode'); + + try { + await fs.access(opencodeConfigPath); + installed.push('opencode'); + this.outputChannel.appendLine(` ✅ opencode: opencode.json found`); + } catch { + try { + await fs.access(opencodeDirPath); + installed.push('opencode'); + this.outputChannel.appendLine(` ✅ opencode: .opencode/ directory found`); + } catch { + // OpenCode not detected + } + } + return installed; } @@ -392,6 +416,175 @@ This project uses agentMemory for persistent knowledge management. } } + /** + * Inject memory instructions into AGENTS.md for OpenCode. + * OpenCode reads AGENTS.md at session start for project rules. + * Keeps AGENTS.md clean — only instructions, no operational data. + */ + private async injectOpenCodeRules(workspacePath: string): Promise { + const agentsMdPath = path.join(workspacePath, 'AGENTS.md'); + const memorySection = `## agentMemory + +This project uses agentMemory for persistent knowledge management. + +### Episodic Memory + +Project decisions, patterns, and context are stored in \`.opencode/memory-context.md\`. + +**Before starting any task:** Read \`.opencode/memory-context.md\` to load recent project context. +**After significant work:** Use \`agentmemory_memory_write\` to persist new knowledge. Entries sync to the context file automatically. + +The context file is a sliding window of the 25 most recent memories (newest first). Older entries are pruned automatically — full history remains searchable via \`agentmemory_memory_search\`. + +### Available Tools (MCP server: agentmemory) + +- \`agentmemory_memory_search\` — Search all memories by query, type, or tags +- \`agentmemory_memory_write\` — Save new memory (key, type, content, tags) +- \`agentmemory_memory_read\` — Retrieve specific memory by key +- \`agentmemory_memory_list\` — List memories by type +- \`agentmemory_memory_update\` — Update existing memory +- \`agentmemory_memory_stats\` — View memory usage statistics + +`; + + try { + let existing = ''; + try { + existing = await fs.readFile(agentsMdPath, 'utf-8'); + } catch { + // File doesn't exist yet + } + + if (existing.includes('## agentMemory')) { + this.outputChannel.appendLine(` ℹ️ AGENTS.md already contains agentMemory section`); + return; + } + + if (existing.trim()) { + // Append to existing AGENTS.md + await fs.writeFile(agentsMdPath, existing.trimEnd() + '\n\n' + memorySection, 'utf-8'); + this.outputChannel.appendLine(` 📄 Updated: AGENTS.md with agentMemory rules`); + } else { + // Create new AGENTS.md + const header = `# AGENTS.md\n\nInstructions for AI coding agents working on this project.\n\n`; + await fs.writeFile(agentsMdPath, header + memorySection, 'utf-8'); + this.outputChannel.appendLine(` 📄 Created: AGENTS.md with agentMemory rules`); + } + } catch (error) { + this.outputChannel.appendLine(` ❌ Failed to create/update AGENTS.md`); + } + } + + /** + * Create OpenCode custom commands for memory operations. + * These appear as /memory-search and /memory-write in the TUI. + */ + private async injectOpenCodeCommands(workspacePath: string): Promise { + const commandsDir = path.join(workspacePath, '.opencode', 'commands'); + + const memorySearchCommand = `--- +description: Search project memories for relevant context +--- +Search the agentMemory system for context relevant to: $ARGUMENTS + +Use the \`agentmemory_memory_search\` tool with the query "$ARGUMENTS". + +If results are found: +1. Summarize each memory's key information +2. Note the memory type and tags +3. Suggest how the findings relate to the current task + +If no results are found: +- Suggest alternative search terms +- Consider if this is new knowledge that should be documented +`; + + const memoryWriteCommand = `--- +description: Save findings to project memory +--- +Document the following in the agentMemory system: $ARGUMENTS + +Use the \`agentmemory_memory_write\` tool with: +- \`key\`: A unique kebab-case identifier derived from the topic +- \`type\`: Choose from architecture, pattern, feature, api, bug, decision +- \`content\`: Detailed markdown documentation of the finding +- \`tags\`: Relevant keywords for searchability (e.g., ["auth", "security", "backend"]) + +After writing, confirm the memory was saved successfully. +`; + + const memoryReviewCommand = `--- +description: Review all project memories and summarize patterns +--- +List all memories in the project using \`agentmemory_memory_stats\` and \`agentmemory_memory_list\`. + +Then summarize: +1. Total number of memories by type +2. Most frequently accessed memories +3. Recent additions +4. Coverage gaps (areas with no memories) + +This helps identify knowledge gaps in the project documentation. +`; + + try { + await fs.mkdir(commandsDir, { recursive: true }); + await fs.writeFile(path.join(commandsDir, 'memory-search.md'), memorySearchCommand, 'utf-8'); + await fs.writeFile(path.join(commandsDir, 'memory-write.md'), memoryWriteCommand, 'utf-8'); + await fs.writeFile(path.join(commandsDir, 'memory-review.md'), memoryReviewCommand, 'utf-8'); + this.outputChannel.appendLine(` 📄 Created: .opencode/commands/memory-search.md`); + this.outputChannel.appendLine(` 📄 Created: .opencode/commands/memory-write.md`); + this.outputChannel.appendLine(` 📄 Created: .opencode/commands/memory-review.md`); + } catch (error) { + this.outputChannel.appendLine(` ❌ Failed to create OpenCode commands`); + } + } + + /** + * Inject agentMemory MCP server config into opencode.json. + * Adds the local MCP server so OpenCode can use memory tools. + */ + private async injectOpenCodeMCPConfig(workspacePath: string): Promise { + const configPath = path.join(workspacePath, 'opencode.json'); + + const serverPath = path.join( + this.workspaceFolder.uri.fsPath, + '..', '..', '.agents', 'skills', 'agent-memory', 'out', 'mcp-server', 'server.js' + ); + + const projectId = path.basename(workspacePath); + + try { + let config: any = {}; + try { + const content = await fs.readFile(configPath, 'utf-8'); + config = JSON.parse(content); + } catch { + // File doesn't exist, start fresh + } + + if (!config.mcp) { + config.mcp = {}; + } + + if (config.mcp.agentmemory) { + this.outputChannel.appendLine(` ℹ️ opencode.json already has agentmemory MCP config`); + return; + } + + config.mcp.agentmemory = { + type: 'local', + command: ['node', serverPath, projectId, workspacePath], + enabled: true + }; + + await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8'); + this.outputChannel.appendLine(` 📄 Updated: opencode.json with agentmemory MCP server`); + } catch (error) { + this.outputChannel.appendLine(` ❌ Failed to update opencode.json`); + } + } + /** * Inject context provider config for Continue */ diff --git a/src/mcp-server/memory-bank-sync.ts b/src/mcp-server/memory-bank-sync.ts index a115073..d4b2ac6 100644 --- a/src/mcp-server/memory-bank-sync.ts +++ b/src/mcp-server/memory-bank-sync.ts @@ -73,6 +73,16 @@ export class MemoryBankSync { 'progress.md': { type: 'feature', tags: ['progress', 'tracking'] }, 'decisionLog.md': { type: 'decision', tags: ['decisions', 'log'] } } + }, + { + name: 'opencode', + memoryBankPath: '.opencode/memory-bank', + fileMapping: { + 'architecture.md': { type: 'architecture', tags: ['design', 'system', 'opencode'] }, + 'patterns.md': { type: 'pattern', tags: ['patterns', 'design', 'opencode'] }, + 'decisions.md': { type: 'decision', tags: ['decisions', 'tech', 'opencode'] }, + 'features.md': { type: 'feature', tags: ['features', 'product', 'opencode'] } + } } ]; @@ -334,6 +344,144 @@ As you work on this project, document: await this.appendToMarkdown(memory, agent, targetFile); } + + // Sync to episodic memory file + ensure AGENTS.md has instructions + await this.syncToMemoryContext(memory); + await this.ensureAgentsMDInstructions(); + } + + /** + * Maximum number of episodic memory entries to keep in memory-context.md + */ + private static readonly MAX_MEMORY_CONTEXT_ENTRIES = 25; + + /** + * Write memory summary to .opencode/memory-context.md (episodic memory). + * Prunes to MAX_MEMORY_CONTEXT_ENTRIES by keeping the most recent entries. + */ + private async syncToMemoryContext(memory: Memory): Promise { + const memoryContextDir = path.join(this.workspacePath, '.opencode'); + const memoryContextPath = path.join(memoryContextDir, 'memory-context.md'); + + const entry = `### ${memory.key}\n- **Type:** ${memory.type}\n- **Tags:** ${memory.tags.join(', ')}\n- **Created:** ${new Date(memory.createdAt).toISOString()}\n- **Summary:** ${memory.content.substring(0, 200).replace(/\n/g, ' ').trim()}...\n`; + + try { + await fs.mkdir(memoryContextDir, { recursive: true }); + + let existing = ''; + try { + existing = await fs.readFile(memoryContextPath, 'utf-8'); + } catch { + // File doesn't exist yet + } + + // Check if this memory entry already exists + if (existing.includes(`### ${memory.key}\n`)) { + return; + } + + const header = `# Episodic Memory\n\nProject knowledge base synced by agentMemory.\nNewest entries first. Max ${MemoryBankSync.MAX_MEMORY_CONTEXT_ENTRIES} entries.\n\n`; + + // Build new content + let content: string; + if (existing.trim()) { + // Insert new entry right after the header, before existing entries + const headerEnd = existing.indexOf('### '); + if (headerEnd === -1) { + content = header + entry + '\n' + existing; + } else { + content = existing.substring(0, headerEnd) + entry + '\n' + existing.substring(headerEnd); + } + } else { + content = header + entry; + } + + // Prune: keep only the first MAX_MEMORY_CONTEXT_ENTRIES + const lines = content.split('\n'); + const entryHeaders: number[] = []; + for (let i = 0; i < lines.length; i++) { + if (lines[i].startsWith('### ')) { + entryHeaders.push(i); + } + } + + if (entryHeaders.length > MemoryBankSync.MAX_MEMORY_CONTEXT_ENTRIES) { + // Find where the 25th entry ends + const keepUntil = entryHeaders[MemoryBankSync.MAX_MEMORY_CONTEXT_ENTRIES]; + // Find the end of the last kept entry (next ### or end of file) + let endOfLastKept = lines.length; + for (let i = keepUntil; i < lines.length; i++) { + if (lines[i].startsWith('### ') && i > keepUntil) { + endOfLastKept = i; + break; + } + } + content = lines.slice(0, endOfLastKept).join('\n').trimEnd() + '\n'; + console.error(`[MemoryBankSync] Pruned episodic memory to ${MemoryBankSync.MAX_MEMORY_CONTEXT_ENTRIES} entries`); + } + + await fs.writeFile(memoryContextPath, content, 'utf-8'); + console.error(`[MemoryBankSync] ✓ Synced ${memory.key} to .opencode/memory-context.md`); + } catch (error) { + console.error(`[MemoryBankSync] Failed to sync memory context: ${error}`); + } + } + + /** + * Ensure AGENTS.md has a reference to .opencode/memory-context.md. + * AGENTS.md stays clean — only instructions, no operational data. + */ + private async ensureAgentsMDInstructions(): Promise { + const agentsMdPath = path.join(this.workspacePath, 'AGENTS.md'); + + const memorySection = `## agentMemory + +This project uses agentMemory for persistent knowledge management. + +### Episodic Memory + +Project decisions, patterns, and context are stored in \`.opencode/memory-context.md\`. + +**Before starting any task:** Read \`.opencode/memory-context.md\` to load recent project context. +**After significant work:** Use \`agentmemory_memory_write\` to persist new knowledge. Entries sync to the context file automatically. + +The context file is a sliding window of the 25 most recent memories (newest first). Older entries are pruned automatically — full history remains searchable via \`agentmemory_memory_search\`. + +### Available Tools (MCP server: agentmemory) + +- \`agentmemory_memory_search\` — Search all memories by query, type, or tags +- \`agentmemory_memory_write\` — Save new memory (key, type, content, tags) +- \`agentmemory_memory_read\` — Retrieve specific memory by key +- \`agentmemory_memory_list\` — List memories by type +- \`agentmemory_memory_update\` — Update existing memory +- \`agentmemory_memory_stats\` — View memory usage statistics + +`; + + try { + let existing = ''; + try { + existing = await fs.readFile(agentsMdPath, 'utf-8'); + } catch { + // File doesn't exist yet + } + + // Already has agentMemory section — skip + if (existing.includes('## agentMemory')) { + return; + } + + if (existing.trim()) { + await fs.writeFile(agentsMdPath, existing.trimEnd() + '\n\n' + memorySection, 'utf-8'); + console.error(`[MemoryBankSync] ✓ Updated AGENTS.md with agentMemory instructions`); + } else { + const header = `# AGENTS.md\n\nInstructions for AI coding agents working on this project.\n\n`; + await fs.writeFile(agentsMdPath, header + memorySection, 'utf-8'); + console.error(`[MemoryBankSync] ✓ Created AGENTS.md with agentMemory instructions`); + } + } catch (error) { + console.error(`[MemoryBankSync] Failed to update AGENTS.md: ${error}`); + } } /**