From fb076960bf49b8d689dc1b4794598fbd3a7fa92a Mon Sep 17 00:00:00 2001 From: Maksim Skorobogatov Date: Sat, 9 May 2026 00:01:11 +0300 Subject: [PATCH 1/2] add opencode support --- ARCHITECTURE.md | 20 ++- README.md | 23 +++- SKILL.md | 79 ++++++++++-- src/config.ts | 23 ++++ src/interceptor.ts | 193 +++++++++++++++++++++++++++++ src/mcp-server/memory-bank-sync.ts | 148 ++++++++++++++++++++++ 6 files changed, 470 insertions(+), 16 deletions(-) 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}`); + } } /** From 9d97d9845e11adf8b2963b1089b985aa2a97b92f Mon Sep 17 00:00:00 2001 From: Maksim Skorobogatov Date: Sat, 9 May 2026 01:09:21 +0300 Subject: [PATCH 2/2] feat: add agent selection for memory bank sync Previously, agentMemory automatically configured and synced with ALL agents (kilocode, cline, roocode, opencode) simultaneously, cluttering the project directory with files the user never intended to use. This commit adds full support for choosing which coding agents to sync with: - New src/mcp-server/agent-config.ts: centralized agent registry and .agentMemory/agents.json config file management - Updated memory-bank-sync.ts: only syncs to agents listed in agents.json - Updated tools.ts: new configure_agents MCP tool + interactive TTY prompt - Updated server.ts: parses --agents=kilocode,opencode CLI flag - Updated config.ts: VS Code QuickPick multi-select during setup - Updated SKILL.md, README.md, API.md, ARCHITECTURE.md: document the new flow Agent selection methods: - VS Code Extension: showQuickPick with canPickMany - CLI: --agents=kilocode,opencode flag - TTY: interactive readline prompt during project_init - Later: configure_agents({ agents: "..." }) MCP tool The selection is persisted in .agentMemory/agents.json and respected by import, export, file watching, and AGENTS.md generation. --- API.md | 51 ++++++- ARCHITECTURE.md | 49 +++++- README.md | 74 +++++++--- SKILL.md | 230 +++++++++++++++++++---------- src/config.ts | 71 ++++++++- src/mcp-server/agent-config.ts | 200 +++++++++++++++++++++++++ src/mcp-server/memory-bank-sync.ts | 129 ++++++++-------- src/mcp-server/server.ts | 70 ++++++++- src/mcp-server/tools.ts | 206 +++++++++++++++++++++++--- 9 files changed, 882 insertions(+), 198 deletions(-) create mode 100644 src/mcp-server/agent-config.ts diff --git a/API.md b/API.md index d44f730..ee50ba1 100644 --- a/API.md +++ b/API.md @@ -15,6 +15,40 @@ code --install-extension your-publisher.agentmemory --- +## Agent Configuration API + +### `configureAgents(agents: string[])` + +Select which AI coding agents should receive memory bank sync. Only selected agents will have memory bank directories created and files synced. + +**Example:** +```typescript +// Select KiloCode and OpenCode only +await api.configureAgents(['kilocode', 'opencode']); + +// Get current selection +const current = await api.getActiveAgents(); +console.log('Active agents:', current); // ['kilocode', 'opencode'] +``` + +| Agent Key | Full Name | Type | +|-----------|-----------|------| +| `kilocode` | KiloCode | VS Code Extension | +| `cline` | Cline | VS Code Extension | +| `roocode` | RooCode | VS Code Extension | +| `opencode` | OpenCode | Terminal TUI | + +Configuration is persisted in `.agentMemory/agents.json`: +```json +{ + "selectedAgents": ["kilocode", "opencode"], + "createdAt": "2025-01-15T10:00:00Z", + "updatedAt": "2025-01-15T10:00:00Z" +} +``` + +--- + ## Getting the API ```typescript @@ -218,7 +252,7 @@ unsubscribe(); Get statistics about memory usage. -**Returns:** `Promise` - Statistics object with memory counts, cache info, etc. +**Returns:** `Promise` - Statistics object with memory counts, cache info, agent sync status, etc. **Example:** ```typescript @@ -227,6 +261,8 @@ const stats = await api.getStats(); console.log(`Total memories: ${stats.totalMemories}`); console.log(`By type:`, stats.byType); console.log(`Cache size: ${stats.cache.size}`); +console.log(`Synced agents:`, stats.sync.agents); +console.log(`Config exists:`, stats.sync.configExists); ``` --- @@ -280,6 +316,12 @@ interface MemoryEvent { agent: string; timestamp: number; } + +interface AgentConfigData { + selectedAgents: string[]; + createdAt: string; + updatedAt: string; +} ``` --- @@ -299,6 +341,9 @@ export async function activate(context: vscode.ExtensionContext) { const memoryAPI = await agentMemoryExt.activate(); + // Configure active agents (only KiloCode and OpenCode) + await memoryAPI.configureAgents(['kilocode', 'opencode']); + // Example: Store architecture decision const storeDecision = vscode.commands.registerCommand('myext.storeDecision', async () => { const key = await vscode.window.showInputBox({ prompt: 'Decision key' }); @@ -364,6 +409,9 @@ Query memories to help new developers understand the codebase. ### 5. Testing Framework Store test patterns and retrieve them when generating new tests. +### 6. Agent Configuration Manager +Programmatically switch which agents sync with the memory bank based on team preferences. + --- ## Best Practices @@ -374,6 +422,7 @@ Store test patterns and retrieve them when generating new tests. 4. **Set createdBy**: Identify your extension in metadata for analytics 5. **Handle Errors**: Always check for null returns from `read()` and `update()` 6. **Unsubscribe**: Clean up event subscriptions when no longer needed +7. **Configure Agents Early**: Call `configureAgents()` during extension activation to set up the right agents for your workspace --- diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index bbbf93d..e906373 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -2,7 +2,7 @@ ## System Overview -agentMemory is a **hybrid memory system** that enhances the built-in memory banks of KiloCode, Cline, and RooCode with powerful search, analytics, and automation capabilities while maintaining full compatibility with their markdown-based documentation. +agentMemory is a **hybrid memory system** that enhances the built-in memory banks of KiloCode, Cline, RooCode, and OpenCode with powerful search, analytics, and automation capabilities while maintaining full compatibility with their markdown-based documentation. ## Design Philosophy @@ -14,8 +14,30 @@ Rather than replacing existing memory bank systems, agentMemory **enhances** the 2. **Syncing** bi-directionally to keep both systems in harmony 3. **Adding** capabilities they lack (search, analytics, automation) 4. **Maintaining** git-friendly, human-readable files +5. **Letting the user choose** which agents to sync with, preventing directory clutter -### Why Hybrid? +### Why Agent Selection? + +Previously, agentMemory automatically configured **all** agents (KiloCode, Cline, RooCode, OpenCode) regardless of which ones the user actually uses. This created unnecessary files and directories in projects, causing confusion and clutter. + +**Solution:** The user explicitly selects which agents to sync with. This selection is persisted in `.agentMemory/agents.json` and respected by: +- The VS Code Extension setup wizard +- The CLI server startup (`--agents=...` flag) +- The `configure_agents` MCP tool +- The bi-directional sync engine + +### Agent Configuration Storage + +``` +.agentMemory/agents.json +{ + "selectedAgents": ["kilocode", "opencode"], + "createdAt": "2025-01-15T10:00:00Z", + "updatedAt": "2025-01-15T12:30:00Z" +} +``` + +This file is the **single source of truth** for agent selection. All components read from it. **Their Systems (Markdown)** - ✅ Human-readable @@ -126,8 +148,12 @@ Rather than replacing existing memory bank systems, agentMemory **enhances** the - Parses markdown files from agents' memory banks - Exports MCP memories to markdown format - Supports KiloCode, Cline, RooCode, and OpenCode +- **Respects agent selection** from `.agentMemory/agents.json` +- **Only syncs to selected agents**, prevents directory clutter -**File Mapping:** +**Agent Configuration:** +All agent definitions (name, memory bank path, file mapping) live in `src/mcp-server/agent-config.ts` (`ALL_AGENTS`). +The sync engine reads `agents.json` to determine which agents to sync with. ```typescript KiloCode: .kilocode/rules/memory-bank/ brief.md → architecture @@ -166,25 +192,34 @@ OpenCode also syncs to AGENTS.md (project root) for session-start context. **Responsibilities:** - Detect installed AI coding agents +- **Prompt user to select which agents to sync with** (via `showQuickPick` multi-select) - Write MCP server config to `.vscode/settings.json` +- Persist agent selection to `.agentMemory/agents.json` - Configure socket paths for each project +**Agent Selection Flow:** +``` +1. Detect installed extensions (KiloCode, Cline, RooCode) +2. Detect OpenCode by file system (opencode.json, .opencode/) +3. Show QuickPick with all agents, pre-selecting installed ones +4. User picks which agents to sync with +5. Save selection to .agentMemory/agents.json +6. Only configure MCP settings for selected agents +``` + **Agent Detection:** ```typescript detectInstalledAgents(): string[] { // Checks for VS Code extensions: // - saoudrizwan.claude-dev (Cline) // - kilocode.kilo-code (KiloCode) - // - roo-cline.roo-cline (RooCode) + // - roo-code.roo-code (RooCode) // - Continue.continue (Continue) // Also checks for OpenCode by file system: // - opencode.json in workspace root // - .opencode/ directory in workspace } -``` - // - rooveterinaryinc.roo-cline (RooCode) -} ``` **Configuration Format:** diff --git a/README.md b/README.md index 60e760d..7e3df3a 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,39 @@ **Hybrid Memory System for AI Coding Agents** -Seamlessly integrate with KiloCode, Cline, and RooCode's built-in memory banks while providing powerful search, analytics, and automation. +Seamlessly integrate with KiloCode, Cline, RooCode, and OpenCode's built-in memory banks while providing powerful search, analytics, and automation. [![VS Code Marketplace](https://img.shields.io/visual-studio-marketplace/v/webzler.agentmemory?label=VS%20Code%20Marketplace)](https://marketplace.visualstudio.com/) [![Installs](https://img.shields.io/visual-studio-marketplace/i/webzler.agentmemory)](https://marketplace.visualstudio.com/) --- +## 📢 Latest Release Highlights + +### v0.2.0 — Agent Selector & OpenCode Support + +**🎯 Agent Selection (New in v0.2.0)** +- You can now **choose which coding agents** to sync with instead of having all agents configured at once. +- Memory bank files are only created for the agents you actively use, keeping your project directory clean and uncluttered. +- Supports selection via: + - **VS Code Extension** — `showQuickPick` multi-select during setup + - **SKILL.md / Terminal** — Interactive readline prompt or `--agents` CLI flag + - **MCP Tool** — `configure_agents({ agents: "kilocode,opencode" })` anytime + +**🤖 OpenCode Support (Added in v0.2.0)** +- Full compatibility with the **OpenCode** terminal-based agent. +- Syncs to `.opencode/memory-bank/` and maintains `AGENTS.md` + `.opencode/commands/`. + +--- + +## 🚀 Now Available as an Antigravity Skill! + +agentMemory is now fully compatible with **Antigravity**. Use it as a skill to give your agents persistent, searchable memory that syncs with your project documentation. + +See [SKILL.md](SKILL.md) for usage instructions. + +--- + ## 🚀 Now Available as an Antigravity Skill! @@ -155,12 +181,20 @@ memory_search({ | 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 | +| **KiloCode** | VS Code Extension | `.kilocode/rules/memory-bank/` | ✅ Selectable | +| **Cline** | VS Code Extension | `.clinerules/memory-bank/` | ✅ Selectable | +| **RooCode** | VS Code Extension | `.roo/memory-bank/` | ✅ Selectable | +| **OpenCode** | Terminal TUI | `AGENTS.md` + `.opencode/commands/` | ✅ Selectable | + +**You choose which agents to sync.** Only selected agents receive: +- Memory bank directories +- MCP settings +- Synced markdown files +- `opencode.json` (OpenCode only) -**Files Synced:** +Use `configure_agents({ agents: "kilocode,opencode" })` to change your selection anytime. + +**Files Synced (for selected agents only):** - `projectBrief.md` / `brief.md` - `architecture.md` / `systemPatterns.md` - `productContext.md` / `product.md` @@ -181,9 +215,10 @@ memory_search({ 4. Reload VS Code **That's it!** The extension will: +- ✅ Ask which agents to sync with (via QuickPick) - ✅ Create MCP server configuration -- ✅ Inject memory-first instructions into memory banks -- ✅ Start bi-directional sync +- ✅ Inject memory-first instructions into selected memory banks +- ✅ Start bi-directional sync with **only** selected agents - ✅ Enable dashboard ### Manual Installation @@ -244,50 +279,53 @@ Agents treat this as **project architecture** and follow it automatically. | `memory_list` | List by type | Show all architecture decisions | | `memory_update` | Modify existing | Append to existing pattern | | `memory_stats` | View analytics | Usage statistics | +| `configure_agents` | Change agent selection | `configure_agents({ agents: "kilocode,opencode" })` | --- ## � Project Structure -After installation, your project will have: +After installation, your project will have (only for **selected** agents): ``` your-project/ ├── .vscode/ │ └── settings.json # MCP server config (auto-created) │ -├── .agentMemory/ # Our structured storage +├── .agentMemory/ # Our structured storage + config │ ├── uuid-001.json # Memory: OAuth architecture │ ├── uuid-002.json # Memory: API patterns -│ └── ... +│ └── agents.json # Active agent configuration ⭐ NEW │ -├── AGENTS.md # OpenCode rules (auto-created) +├── AGENTS.md # OpenCode rules (auto-created if OpenCode selected) │ -├── opencode.json # OpenCode MCP config (auto-created) +├── opencode.json # OpenCode MCP config (auto-created if selected) │ -├── .opencode/ -│ └── commands/ # OpenCode custom commands (auto-created) +├── .opencode/ # ONLY if OpenCode is selected +│ └── commands/ # OpenCode custom commands │ ├── memory-search.md │ ├── memory-write.md │ └── memory-review.md │ -├── .kilocode/rules/memory-bank/ # KiloCode memory bank +├── .kilocode/rules/memory-bank/ # ONLY if KiloCode is selected │ ├── brief.md # ⬍ Synced with our database │ ├── architecture.md # ⬍ Auto-updated │ ├── product.md # ⬍ Auto-updated │ └── tech.md # ⬍ Auto-updated │ -├── .clinerules/memory-bank/ # Cline memory bank +├── .clinerules/memory-bank/ # ONLY if Cline is selected │ ├── projectBrief.md # ⬍ Synced │ ├── systemPatterns.md # ⬍ Synced │ └── ... # ⬍ Synced │ -└── .roo/memory-bank/ # RooCode memory bank +└── .roo/memory-bank/ # ONLY if RooCode is selected ├── projectBrief.md # ⬍ Synced ├── decisionLog.md # ⬍ Synced └── ... # ⬍ Synced ``` +**Only the agents you select get directories and files created!** This keeps your project clean. + **All markdown files stay human-readable and git-friendly!** --- diff --git a/SKILL.md b/SKILL.md index 8842ae1..f1b1caf 100644 --- a/SKILL.md +++ b/SKILL.md @@ -17,76 +17,138 @@ This skill extends your capabilities by providing a persistent, searchable memor ## Setup -### For VS Code Agents (Cline, RooCode, KiloCode) - -1. **Install Dependencies**: - ```bash - cd ~/.agents/skills/agent-memory && npm install && npm run compile - ``` - -2. **Start the Memory Server**: - ```bash - npm run start-server - ``` - -### For OpenCode (Terminal-based Agent) - -1. **Build the MCP server**: - ```bash - 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. - ``` +### 1. Choose Your Agents (Required!) + +Before initializing the memory bank, you MUST ask the user which AI coding agents they plan to use in this project. + +**Supported agents:** + +| # | Agent | Type | Memory Bank Path | +|---|-------|------|------------------| +| 1 | **KiloCode** | VS Code Extension | `.kilocode/rules/memory-bank/` | +| 2 | **Cline** | VS Code Extension | `.clinerules/memory-bank/` | +| 3 | **RooCode** | VS Code Extension | `.roo/memory-bank/` | +| 4 | **OpenCode** | Terminal TUI | `.opencode/memory-bank/` | + +**Ask the user:** +``` +Which AI coding agents will you use for this project? +You can select multiple: +[ ] KiloCode +[ ] Cline +[ ] RooCode +[ ] OpenCode +``` + +Then configure them using the MCP tool: +``` +configure_agents({ agents: "kilocode,opencode" }) +``` + +### 2. Build the MCP Server + +Once agents are configured, install dependencies and compile: +```bash +cd ~/.agents/skills/agent-memory && npm install && npm run compile +``` + +### 3. Start the Memory Server + +#### For VS Code Agents (Cline, RooCode, KiloCode) + +```bash +npm run start-server +``` + +#### For OpenCode (Terminal-based Agent) + +```bash +# Include the agents you configured: +node ~/.agents/skills/agent-memory/out/mcp-server/server.js --agents=kilocode,opencode +``` + +Or add 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", + "--agents=kilocode,opencode" + ], + "enabled": true + } + } +} +``` +Replace `YOUR_USER`, `PROJECT_ID`, `/ABSOLUTE/PATH/TO/WORKSPACE`, and the agents list with actual values. + +### 4. Add Memory Instructions to `AGENTS.md` + +Create or update `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_configure_agents` — Change which agents to sync with + +**Failure to use memory tools = Incomplete work** +``` + +### 5. 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. +``` + +## Changing Agents Later + +If the user wants to add or remove agents after initial setup, use: +``` +configure_agents({ agents: "kilocode,roocode" }) +``` + +Or run interactively (only in TTY): +``` +configure_agents({ interactive: true }) +``` + +The configuration is stored in `.agentMemory/agents.json`: +```json +{ + "selectedAgents": ["kilocode", "opencode"], + "createdAt": "2025-01-15T10:00:00Z", + "updatedAt": "2025-01-15T10:00:00Z" +} +``` ## Capabilities (MCP Tools) @@ -108,9 +170,14 @@ Retrieve specific memory content by key. - **Usage**: "Get the auth design" -> `memory_read({ key: "auth-v1" })` ### `memory_stats` -View analytics on memory usage. +View analytics on memory usage, including which agents are active. - **Usage**: "Show memory statistics" -> `memory_stats({})` +### `configure_agents` +Switch which agents receive memory bank sync. +- **Args**: `agents` (string — comma-separated) +- **Usage**: `configure_agents({ agents: "kilocode,opencode" })` + ## Supported Agents | Agent | Type | Config Location | Memory Bank Path | @@ -118,13 +185,28 @@ View analytics on memory usage. | 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/` | +| **OpenCode** | Terminal TUI | `opencode.json` | `.opencode/memory-bank/` | ## Workflow -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`. +1. **Initialization**: The first time you run this in a project, ask which agents to use and call `configure_agents()`. 2. **Development Loop**: - **Before Task**: Search memory for relevant context. - **During Task**: Use read/search to answer questions. - **After Task**: Write new findings to memory. -3. **Sync**: Your writes are automatically synced to standard markdown files in the project. +3. **Sync**: Your writes are automatically synced to standard markdown files **only for the selected agents**. + +## Why Agent Selection Matters + +Previously, agentMemory automatically created configuration and files for **all agents** (KiloCode, Cline, RooCode, OpenCode) simultaneously, which cluttered the project directory with files the user never intended to use. + +Now, only the agents you explicitly choose will have: +- Memory bank directories created +- MCP settings configured +- Memory files synced + +This keeps your project clean and focused. + +Base directory for this skill: file:///Users/maksim/.agents/skills/agent-memory +Relative paths in this skill (e.g., scripts/, reference/) are relative to this base directory. +Note: file list is sampled. diff --git a/src/config.ts b/src/config.ts index ba31097..5670401 100644 --- a/src/config.ts +++ b/src/config.ts @@ -22,24 +22,44 @@ export class ConfigManager { args: [serverPath, projectId, workspacePath] }; - // Detect installed agents - const installedAgents = await this.detectInstalledAgents(); - - if (installedAgents.length === 0) { - this.outputChannel.appendLine('⚠️ No AI coding agents detected. Skipping MCP configuration.'); + // --- Agent Selection Dialog --- + // Ask user which agents to sync with before writing any config files + const selectedAgents = await this.promptAgentSelection(); + if (!selectedAgents || selectedAgents.length === 0) { + this.outputChannel.appendLine('⚠️ No agents selected. Skipping MCP configuration.'); return; } - this.outputChannel.appendLine(`📡 Detected agents: ${installedAgents.join(', ')}`); + this.outputChannel.appendLine(`📡 Selected agents: ${selectedAgents.join(', ')}`); + + // Write the agent selection to .agentMemory/agents.json so the MCP server and sync engine respect it + try { + const { AgentConfig } = require('./mcp-server/agent-config'); + const agentCfg = new AgentConfig(workspacePath); + agentCfg.write(selectedAgents); + this.outputChannel.appendLine(`💾 Saved agent selection to .agentMemory/agents.json`); + } catch (err: any) { + this.outputChannel.appendLine(`⚠️ Failed to save agent selection: ${err.message}`); + } + // --- + + // Detect installed agents to know WHICH ones can be configured + const installedAgents = await this.detectInstalledAgents(); - // Configure each agent's settings file - for (const agent of installedAgents) { + // Configure each SELECTED agent's settings file + for (const agent of selectedAgents) { // 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; } + // Only configure if the agent is actually installed (has extension) + if (!installedAgents.includes(agent)) { + this.outputChannel.appendLine(` ⚠️ ${agent}: not installed, skipping MCP settings`); + continue; + } + const settingsPath = this.getAgentSettingsPath(agent); if (!settingsPath) { this.outputChannel.appendLine(`⚠️ Unknown settings path for ${agent}`); @@ -50,6 +70,41 @@ export class ConfigManager { } } + /** + * Show a VS Code QuickPick to let the user select which agents to sync with. + * Returns the selected agent keys. + */ + private async promptAgentSelection(): Promise { + const allAgents = [ + { label: 'KiloCode', description: 'VS Code Extension', key: 'kilocode', picked: true }, + { label: 'Cline', description: 'VS Code Extension', key: 'cline', picked: true }, + { label: 'RooCode', description: 'VS Code Extension', key: 'roocode', picked: true }, + { label: 'OpenCode', description: 'Terminal TUI Agent', key: 'opencode', picked: false } + ]; + + const installed = await this.detectInstalledAgents(); + + // Default-pick agents that are actually installed + const items = allAgents.map(agent => ({ + label: `${agent.label}${installed.includes(agent.key) ? ' (installed)' : ''}`, + description: agent.description, + key: agent.key, + picked: installed.includes(agent.key) + })); + + const selected = await vscode.window.showQuickPick(items, { + canPickMany: true, + placeHolder: 'Select which AI coding agents to sync memory bank with (multi-select)', + ignoreFocusOut: true + }); + + if (!selected) { + return undefined; // User cancelled + } + + return selected.map(s => s.key); + } + /** * Get the path to an agent's MCP settings file */ diff --git a/src/mcp-server/agent-config.ts b/src/mcp-server/agent-config.ts new file mode 100644 index 0000000..5e1a348 --- /dev/null +++ b/src/mcp-server/agent-config.ts @@ -0,0 +1,200 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +export const ALL_AGENTS: Record }> = { + kilocode: { + name: 'kilocode', + fullName: 'KiloCode', + description: 'KiloCode VS Code Extension', + memoryBankPath: '.kilocode/rules/memory-bank', + fileMapping: { + 'brief.md': { type: 'architecture', tags: ['overview', 'project'] }, + 'product.md': { type: 'feature', tags: ['product', 'features'] }, + 'context.md': { type: 'bug', tags: ['context', 'issues'] }, + 'architecture.md': { type: 'architecture', tags: ['design', 'system'] }, + 'tech.md': { type: 'decision', tags: ['technology', 'stack'] } + } + }, + cline: { + name: 'cline', + fullName: 'Cline', + description: 'Cline VS Code Extension', + memoryBankPath: '.clinerules/memory-bank', + fileMapping: { + 'projectBrief.md': { type: 'architecture', tags: ['overview', 'project'] }, + 'productContext.md': { type: 'feature', tags: ['product', 'goals'] }, + 'activeContext.md': { type: 'pattern', tags: ['current', 'focus'] }, + 'systemPatterns.md': { type: 'pattern', tags: ['patterns', 'design'] }, + 'techContext.md': { type: 'decision', tags: ['technology', 'decisions'] }, + 'progress.md': { type: 'feature', tags: ['progress', 'status'] } + } + }, + roocode: { + name: 'roocode', + fullName: 'RooCode', + description: 'RooCode VS Code Extension', + memoryBankPath: '.roo/memory-bank', + fileMapping: { + 'projectBrief.md': { type: 'architecture', tags: ['overview', 'project'] }, + 'productContext.md': { type: 'feature', tags: ['product', 'vision'] }, + 'activeContext.md': { type: 'pattern', tags: ['current', 'work'] }, + 'systemPatterns.md': { type: 'pattern', tags: ['patterns', 'architecture'] }, + 'techContext.md': { type: 'decision', tags: ['technology', 'stack'] }, + 'progress.md': { type: 'feature', tags: ['progress', 'tracking'] }, + 'decisionLog.md': { type: 'decision', tags: ['decisions', 'log'] } + } + }, + opencode: { + name: 'opencode', + fullName: 'OpenCode', + description: 'OpenCode Terminal TUI Agent', + 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'] } + } + } +}; + +export type AgentName = keyof typeof ALL_AGENTS; + +export interface AgentConfigData { + selectedAgents: AgentName[]; + createdAt: string; + updatedAt: string; +} + +export class AgentConfig { + private configPath: string; + + constructor(private workspacePath: string) { + this.configPath = path.join(workspacePath, '.agentMemory', 'agents.json'); + } + + /** + * Detect which agents are "present" in the workspace by checking for their + * configuration files or directories. + */ + detectPresentAgents(): AgentName[] { + const present: AgentName[] = []; + for (const [key, agent] of Object.entries(ALL_AGENTS)) { + const agentPath = path.join(this.workspacePath, agent.memoryBankPath); + try { + fs.accessSync(agentPath); + present.push(key as AgentName); + } catch { + // Also check for opencode-specific files + if (key === 'opencode') { + try { + fs.accessSync(path.join(this.workspacePath, 'opencode.json')); + present.push('opencode'); + } catch { + try { + fs.accessSync(path.join(this.workspacePath, '.opencode')); + present.push('opencode'); + } catch { + // not present + } + } + } + } + } + return present; + } + + /** + * Check if agents.json already exists + */ + exists(): boolean { + return fs.existsSync(this.configPath); + } + + /** + * Read selected agents from agents.json. + * Returns null if no config exists. + */ + read(): AgentConfigData | null { + try { + if (!fs.existsSync(this.configPath)) { + return null; + } + const content = fs.readFileSync(this.configPath, 'utf-8'); + const data = JSON.parse(content) as AgentConfigData; + // Validate agent names + data.selectedAgents = data.selectedAgents.filter(a => ALL_AGENTS[a as AgentName] !== undefined); + return data; + } catch (error) { + console.error(`[AgentConfig] Failed to read config: ${error}`); + return null; + } + } + + /** + * Write selected agents to agents.json + */ + write(selectedAgents: AgentName[]): AgentConfigData { + const data: AgentConfigData = { + selectedAgents: [...new Set(selectedAgents)], // deduplicate + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + }; + + try { + const dir = path.dirname(this.configPath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + fs.writeFileSync(this.configPath, JSON.stringify(data, null, 2), 'utf-8'); + console.error(`[AgentConfig] Saved config: ${data.selectedAgents.join(', ')}`); + } catch (error) { + console.error(`[AgentConfig] Failed to write config: ${error}`); + } + return data; + } + + /** + * Update only selected agents, preserving createdAt + */ + updateAgents(selectedAgents: AgentName[]): AgentConfigData { + const existing = this.read(); + const data: AgentConfigData = { + selectedAgents: [...new Set(selectedAgents)], + createdAt: existing?.createdAt || new Date().toISOString(), + updatedAt: new Date().toISOString() + }; + try { + fs.writeFileSync(this.configPath, JSON.stringify(data, null, 2), 'utf-8'); + console.error(`[AgentConfig] Updated config: ${data.selectedAgents.join(', ')}`); + } catch (error) { + console.error(`[AgentConfig] Failed to update config: ${error}`); + } + return data; + } + + /** + * Get the list of agents that should be active. + * This reads the config. If no config exists, returns all present agents or all agents as fallback. + */ + getActiveAgents(): AgentName[] { + const config = this.read(); + if (config && config.selectedAgents.length > 0) { + return config.selectedAgents; + } + const present = this.detectPresentAgents(); + if (present.length > 0) { + return present; + } + // Fallback: none active until configured + return []; + } + + /** + * Returns agent configuration objects for active agents + */ + getActiveAgentConfigs(): Array { + const active = this.getActiveAgents(); + return active.map(name => ALL_AGENTS[name]).filter(Boolean); + } +} diff --git a/src/mcp-server/memory-bank-sync.ts b/src/mcp-server/memory-bank-sync.ts index d4b2ac6..3b9797f 100644 --- a/src/mcp-server/memory-bank-sync.ts +++ b/src/mcp-server/memory-bank-sync.ts @@ -2,6 +2,7 @@ import * as fs from 'fs/promises'; import * as fsCallback from 'fs'; import * as path from 'path'; import { v4 as uuidv4 } from 'uuid'; +import { AgentConfig, ALL_AGENTS, AgentName } from './agent-config'; interface Memory { id: string; @@ -23,82 +24,55 @@ interface Memory { updatedAt: number; } -interface AgentConfig { +interface AgentFileMappingConfig { name: string; memoryBankPath: string; fileMapping: Record; } /** - * Bi-directional sync between agent memory bank files and MCP storage + * Bi-directional sync between agent memory bank files and MCP storage, + * respecting the user's agent selection in .agentMemory/agents.json. */ export class MemoryBankSync { private workspacePath: string; private mcpDataPath: string; - - // Multi-agent configuration - private agents: AgentConfig[] = [ - { - name: 'kilocode', - memoryBankPath: '.kilocode/rules/memory-bank', - fileMapping: { - 'brief.md': { type: 'architecture', tags: ['overview', 'project'] }, - 'product.md': { type: 'feature', tags: ['product', 'features'] }, - 'context.md': { type: 'bug', tags: ['context', 'issues'] }, - 'architecture.md': { type: 'architecture', tags: ['design', 'system'] }, - 'tech.md': { type: 'decision', tags: ['technology', 'stack'] } - } - }, - { - name: 'cline', - memoryBankPath: '.clinerules/memory-bank', - fileMapping: { - 'projectBrief.md': { type: 'architecture', tags: ['overview', 'project'] }, - 'productContext.md': { type: 'feature', tags: ['product', 'goals'] }, - 'activeContext.md': { type: 'pattern', tags: ['current', 'focus'] }, - 'systemPatterns.md': { type: 'pattern', tags: ['patterns', 'design'] }, - 'techContext.md': { type: 'decision', tags: ['technology', 'decisions'] }, - 'progress.md': { type: 'feature', tags: ['progress', 'status'] } - } - }, - { - name: 'roocode', - memoryBankPath: '.roo/memory-bank', - fileMapping: { - 'projectBrief.md': { type: 'architecture', tags: ['overview', 'project'] }, - 'productContext.md': { type: 'feature', tags: ['product', 'vision'] }, - 'activeContext.md': { type: 'pattern', tags: ['current', 'work'] }, - 'systemPatterns.md': { type: 'pattern', tags: ['patterns', 'architecture'] }, - 'techContext.md': { type: 'decision', tags: ['technology', 'stack'] }, - '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'] } - } - } - ]; + private agentConfig: AgentConfig; constructor(workspacePath: string, mcpDataPath: string = '.agentMemory') { this.workspacePath = workspacePath; this.mcpDataPath = path.join(workspacePath, mcpDataPath); + this.agentConfig = new AgentConfig(workspacePath); + } + + private getActiveAgents(): AgentFileMappingConfig[] { + const activeNames = this.agentConfig.getActiveAgents(); + if (activeNames.length === 0) { + console.error('[MemoryBankSync] No agents configured. Use configure_agents tool or select agents during setup.'); + return []; + } + return activeNames.map(name => ({ + name: ALL_AGENTS[name].name, + memoryBankPath: ALL_AGENTS[name].memoryBankPath, + fileMapping: ALL_AGENTS[name].fileMapping + })); } /** - * Import all memory bank files from all agents into MCP storage + * Import all memory bank files from selected agents into MCP storage */ async importAll(): Promise { - console.error('[MemoryBankSync] Starting import from all agents...'); + const agents = this.getActiveAgents(); + if (agents.length === 0) { + console.error('[MemoryBankSync] No active agents to import from. Run configure_agents to select agents.'); + await this.initializeProject(); + return; + } + + console.error('[MemoryBankSync] Starting import from selected agents...'); let totalImported = 0; - for (const agent of this.agents) { + for (const agent of agents) { totalImported += await this.importFromAgent(agent); } @@ -114,7 +88,7 @@ export class MemoryBankSync { * Import memory bank files from a specific agent * Returns the number of memories imported */ - private async importFromAgent(agent: AgentConfig): Promise { + private async importFromAgent(agent: AgentFileMappingConfig): Promise { const memoryBankDir = path.join(this.workspacePath, agent.memoryBankPath); try { @@ -161,6 +135,8 @@ export class MemoryBankSync { console.error('[MemoryBankSync] Creating default project memory...'); const projectName = path.basename(this.workspacePath); + const activeAgents = this.agentConfig.getActiveAgents(); + const defaultMemory: Memory = { id: uuidv4(), projectId: projectName, @@ -168,7 +144,7 @@ export class MemoryBankSync { type: 'architecture', content: `# ${projectName} - Project Overview -This project uses **agentMemory** for persistent knowledge management. +This project uses **agentMemory** for persistent knowledge management.${activeAgents.length > 0 ? `\n\n**Active Agents:** ${activeAgents.map(a => ALL_AGENTS[a]?.fullName || a).join(', ')}` : ''} ## How It Works @@ -209,7 +185,7 @@ As you work on this project, document: // Save to MCP storage await this.saveMCPMemory(defaultMemory); - // Export to all agent markdown files + // Export to all selected agent markdown files await this.exportToAgents(defaultMemory); console.error('[MemoryBankSync] ✅ Default project memory created and synced'); @@ -333,10 +309,12 @@ As you work on this project, document: } /** - * Export MCP memory to appropriate agent markdown files + * Export MCP memory to appropriate agent markdown files (only selected agents) */ async exportToAgents(memory: Memory): Promise { - for (const agent of this.agents) { + const agents = this.getActiveAgents(); + + for (const agent of agents) { // Find which file this memory type maps to const targetFile = this.getTargetFile(memory.type, agent); @@ -433,11 +411,21 @@ As you work on this project, document: */ private async ensureAgentsMDInstructions(): Promise { const agentsMdPath = path.join(this.workspacePath, 'AGENTS.md'); + const activeAgents = this.agentConfig.getActiveAgents(); + const activeAgentsText = activeAgents.length > 0 + ? activeAgents.map(a => `| ${ALL_AGENTS[a]?.fullName || a} | ${ALL_AGENTS[a]?.memoryBankPath || 'N/A'} |`).join('\n') + : '| (none configured) | Run `configure_agents` to enable |'; const memorySection = `## agentMemory This project uses agentMemory for persistent knowledge management. +### Active Agents + +| Agent | Memory Bank Path | +|-------|-----------------| +${activeAgentsText} + ### Episodic Memory Project decisions, patterns, and context are stored in \`.opencode/memory-context.md\`. @@ -455,6 +443,7 @@ The context file is a sliding window of the 25 most recent memories (newest firs - \`agentmemory_memory_list\` — List memories by type - \`agentmemory_memory_update\` — Update existing memory - \`agentmemory_memory_stats\` — View memory usage statistics +- \`agentmemory_configure_agents\` — Configure which coding agents to sync with `; @@ -466,8 +455,14 @@ The context file is a sliding window of the 25 most recent memories (newest firs // File doesn't exist yet } - // Already has agentMemory section — skip + // If the section already exists, replace it to keep agent list up to date if (existing.includes('## agentMemory')) { + const regex = /## agentMemory[\s\S]*?(?=\n#{1,2} .|$)/; + const updated = existing.replace(regex, memorySection.trimEnd()); + if (updated !== existing) { + await fs.writeFile(agentsMdPath, updated.trimEnd() + '\n', 'utf-8'); + console.error(`[MemoryBankSync] ✓ Updated AGENTS.md with current active agents`); + } return; } @@ -487,7 +482,7 @@ The context file is a sliding window of the 25 most recent memories (newest firs /** * Get target markdown file for a memory type */ - private getTargetFile(type: Memory['type'], agent: AgentConfig): string | null { + private getTargetFile(type: Memory['type'], agent: AgentFileMappingConfig): string | null { for (const [filename, config] of Object.entries(agent.fileMapping)) { if (config.type === type) { return filename; @@ -501,7 +496,7 @@ The context file is a sliding window of the 25 most recent memories (newest firs */ private async appendToMarkdown( memory: Memory, - agent: AgentConfig, + agent: AgentFileMappingConfig, filename: string ): Promise { const memoryBankDir = path.join(this.workspacePath, agent.memoryBankPath); @@ -584,8 +579,14 @@ ${memory.content} async startWatching(): Promise { console.error('[MemoryBankSync] Starting file watching for memory bank sync...'); + const agents = this.getActiveAgents(); + if (agents.length === 0) { + console.error('[MemoryBankSync] No active agents to watch. Configure agents to enable file watching.'); + return; + } + // Watch each agent's memory bank directory - for (const agent of this.agents) { + for (const agent of agents) { const memoryBankPath = path.join(this.workspacePath, agent.memoryBankPath); try { diff --git a/src/mcp-server/server.ts b/src/mcp-server/server.ts index 0c22eb1..3b9acbe 100644 --- a/src/mcp-server/server.ts +++ b/src/mcp-server/server.ts @@ -5,6 +5,7 @@ import { CacheManager } from './cache'; import { MCPTools } from './tools'; import { SocketBridge } from './socket-bridge'; import { MemoryBankSync } from './memory-bank-sync'; +import { AgentConfig } from './agent-config'; import { StandaloneDashboard } from '../standalone-dashboard'; interface MCPRequest { @@ -25,6 +26,45 @@ interface MCPResponse { }; } +/** + * Parse CLI arguments. + * Supported: + * node server.js [--agents=kilocode,opencode] + */ +function parseArgs(): { projectId: string; workspacePath: string; agentsArg?: string } { + const args = process.argv.slice(2); + let projectId = 'default-project'; + let workspacePath = process.cwd(); + let agentsArg: string | undefined; + + for (const arg of args) { + if (arg.startsWith('--agents=')) { + agentsArg = arg.split('=')[1]; + } else if (arg.startsWith('--agents')) { + // Handle --agents val (next arg) could be done but keep simple + const eqIdx = arg.indexOf('='); + if (eqIdx !== -1) { + agentsArg = arg.substring(eqIdx + 1); + } + } else if (!projectId || projectId === 'default-project') { + // First positional = projectId + if (projectId === 'default-project' && arg !== workspacePath) { + projectId = arg; + } + } else { + // Second positional = workspacePath + workspacePath = arg; + } + } + + // More robust positional parsing + const positional = args.filter(a => !a.startsWith('--')); + if (positional.length >= 1) projectId = positional[0]; + if (positional.length >= 2) workspacePath = positional[1]; + + return { projectId, workspacePath, agentsArg }; +} + /** * Simple MCP Server using stdio transport * This server implements the Model Context Protocol for memory tools @@ -35,9 +75,11 @@ class MCPServer { private tools: MCPTools; private projectId: string; private syncEngine: MemoryBankSync; + private workspacePath: string; constructor(projectId: string, workspacePath: string) { this.projectId = projectId; + this.workspacePath = workspacePath; // Use absolute path based on workspace const storagePath = workspacePath + '/.agentMemory'; @@ -50,13 +92,20 @@ class MCPServer { // Initialize sync engine this.syncEngine = new MemoryBankSync(workspacePath); - this.tools = new MCPTools(this.storage, this.cache, this.syncEngine); + this.tools = new MCPTools(this.storage, this.cache, this.syncEngine, workspacePath); console.error(`[MCP Server] Initialized for project: ${projectId}`); console.error(`[MCP Server] Workspace path: ${workspacePath}`); console.error(`[MCP Server] Storage path: ${storagePath}`); } + /** + * Public accessor for the tools instance (used for initialization with agents arg) + */ + public getTools(): MCPTools { + return this.tools; + } + /** * Handle incoming MCP request (public for socket bridge) */ @@ -107,6 +156,9 @@ class MCPServer { case 'project_init': result = await this.tools.project_init(toolArgs); break; + case 'configure_agents': + result = await this.tools.configure_agents(toolArgs); + break; case 'memory_stats': result = await this.tools.memory_stats(toolArgs); break; @@ -224,10 +276,22 @@ class MCPServer { } // Main entry point -const projectId = process.argv[2] || 'default-project'; -const workspacePath = process.argv[3] || process.cwd(); +const { projectId, workspacePath, agentsArg } = parseArgs(); const server = new MCPServer(projectId, workspacePath); + +// If --agents was passed, write to config immediately before starting +if (agentsArg) { + const agentConfig = new AgentConfig(workspacePath); + if (!agentConfig.exists()) { + server.getTools().configure_agents({ projectId, agents: agentsArg }).then(() => { + console.error(`[MCP Server] Pre-configured agents from CLI: ${agentsArg}`); + }).catch(err => { + console.error(`[MCP Server] Failed to pre-configure agents: ${err}`); + }); + } +} + server.start(); // Also start Unix socket bridge for KiloCode diff --git a/src/mcp-server/tools.ts b/src/mcp-server/tools.ts index 63609c5..07773c8 100644 --- a/src/mcp-server/tools.ts +++ b/src/mcp-server/tools.ts @@ -1,9 +1,11 @@ import { StorageManager } from './storage'; import { CacheManager } from './cache'; import { MemoryBankSync } from './memory-bank-sync'; +import { AgentConfig, AgentConfigData, ALL_AGENTS, AgentName } from './agent-config'; import { v4 as uuidv4 } from 'uuid'; import * as fs from 'fs'; import * as path from 'path'; +import * as readline from 'readline'; interface Memory { id: string; @@ -34,11 +36,17 @@ export class MCPTools { private storage: StorageManager; private cache: CacheManager; private syncEngine?: MemoryBankSync; + private agentConfig?: AgentConfig; + private workspacePath: string; - constructor(storage: StorageManager, cache: CacheManager, syncEngine?: MemoryBankSync) { + constructor(storage: StorageManager, cache: CacheManager, syncEngine?: MemoryBankSync, workspacePath?: string) { this.storage = storage; this.cache = cache; this.syncEngine = syncEngine; + this.workspacePath = workspacePath || process.cwd(); + if (this.workspacePath) { + this.agentConfig = new AgentConfig(this.workspacePath); + } } /** @@ -150,29 +158,61 @@ export class MCPTools { } /** - * Tool 6: project_init - Auto-detect workspace (10μs target) + * Tool 6: project_init - Auto-detect workspace and setup agents + * + * Supports a comma-separated `agents` argument for non-interactive + * configuration: e.g., { agents: "kilocode,opencode" }. + * If omitted and no agents.json exists, interactive readline prompts + * the user (only in a TTY environment). */ - async project_init(params: ToolCallParams): Promise<{ success: boolean; projectId: string }> { - const { projectId } = params; + async project_init(params: ToolCallParams): Promise<{ success: boolean; projectId: string; configuredAgents: string[] }> { + const { projectId, agents } = params; await this.storage.initProject(projectId); - // Auto-create .agent structure if it doesn't exist (Antigravity support) - // We need to resolve the workspace path. Since we don't have it passed explicitly in params, - // we'll rely on the storage manager's base path or try to infer it. - // Ideally, we should pass workspacePath to project_init. - // For now, let's assume storage manager knows where the root is. - // Or better yet, let's update call args to include workspacePath if possible, - // but for safety, we'll try to use the parent of .agentMemory if available. + let configuredAgents: string[] = []; + + if (this.agentConfig) { + const existing = this.agentConfig.read(); + if (existing && existing.selectedAgents.length > 0) { + configuredAgents = existing.selectedAgents; + console.error(`[project_init] Using existing agent config: ${configuredAgents.join(', ')}`); + } else if (typeof agents === 'string' && agents.trim()) { + const requestedAgents = agents.split(',').map(a => a.trim().toLowerCase() as AgentName); + const validAgents = requestedAgents.filter(a => ALL_AGENTS[a] !== undefined); + if (validAgents.length > 0) { + this.agentConfig.write(validAgents); + configuredAgents = validAgents; + console.error(`[project_init] Agents configured from CLI args: ${configuredAgents.join(', ')}`); + } + } else if (process.stdin.isTTY) { + try { + configuredAgents = await this.interactiveAgentPrompt(); + if (configuredAgents.length > 0) { + this.agentConfig.write(configuredAgents); + console.error(`[project_init] Agents configured interactively: ${configuredAgents.join(', ')}`); + } + } catch (error) { + console.error(`[project_init] Interactive prompt failed: ${error}`); + } + } else { + console.error('[project_init] No TTY available, skipping interactive agent selection. Pass --agents or configure later.'); + } + if (configuredAgents.length === 0) { + const detected = this.agentConfig.detectPresentAgents(); + if (detected.length > 0) { + this.agentConfig.write(detected); + configuredAgents = detected; + console.error(`[project_init] Auto-detected present agents: ${configuredAgents.join(', ')}`); + } + } + } + // Auto-create .agent structure if it doesn't exist (Antigravity support) try { // @ts-ignore const storagePath = this.storage.baseDir; - console.error('[project_init] Storage path:', storagePath); - if (storagePath) { - const projectRoot = path.dirname(storagePath); // Parent of .agentMemory - console.error('[project_init] Project root:', projectRoot); - + const projectRoot = path.dirname(storagePath); const agentDir = path.join(projectRoot, '.agent'); const workflowsDir = path.join(agentDir, 'workflows'); @@ -182,6 +222,7 @@ export class MCPTools { const workflowFile = path.join(workflowsDir, 'update-memory.md'); if (!fs.existsSync(workflowFile)) { + const activeAgents = configuredAgents.length > 0 ? configuredAgents.map(a => ALL_AGENTS[a]?.fullName || a).join(', ') : 'All'; const workflowContent = `--- description: How to update the project memory bank with new findings --- @@ -190,6 +231,8 @@ description: How to update the project memory bank with new findings Follow this workflow to document important architectural decisions, patterns, or features. +**Active Agents:** ${activeAgents} + 1. **Search First**: Check if a similar memory already exists. \`\`\`bash # Use the memory_search tool @@ -222,10 +265,99 @@ Follow this workflow to document important architectural decisions, patterns, or } } catch (error) { console.error('[project_init] Failed to scaffold .agent directory:', error); - // Don't fail the init, just log the error } - return { success: true, projectId }; + return { success: true, projectId, configuredAgents }; + } + + /** + * Tool 6.5: configure_agents - Select which agents to enable/disable for syncing + */ + async configure_agents(params: ToolCallParams): Promise<{ success: boolean; selectedAgents: string[]; previousAgents: string[] }> { + const { agents, interactive } = params; + let selectedAgents: string[] = []; + let previousAgents: string[] = []; + + if (!this.agentConfig) { + return { success: false, selectedAgents: [], previousAgents: [] }; + } + + const existing = this.agentConfig.read(); + if (existing) { + previousAgents = [...existing.selectedAgents]; + } + + if (typeof agents === 'string' && agents.trim()) { + const requestedAgents = agents.split(',').map(a => a.trim().toLowerCase() as AgentName); + selectedAgents = requestedAgents.filter(a => ALL_AGENTS[a] !== undefined); + } else if (interactive !== false && process.stdin.isTTY) { + try { + selectedAgents = await this.interactiveAgentPrompt(previousAgents); + } catch (error) { + console.error(`[configure_agents] Interactive prompt failed: ${error}`); + } + } + + if (selectedAgents.length === 0) { + // Keep previous selection or fall back to detected + if (previousAgents.length > 0) { + selectedAgents = previousAgents; + } else { + selectedAgents = this.agentConfig.detectPresentAgents(); + } + } + + if (selectedAgents.length > 0) { + this.agentConfig.write(selectedAgents); + console.error(`[configure_agents] Agents updated: ${selectedAgents.join(', ')}`); + } + + return { success: true, selectedAgents, previousAgents }; + } + + /** + * Interactive readline prompt for selecting agents + */ + private async interactiveAgentPrompt(defaultSelection: string[] = []): Promise { + return new Promise((resolve) => { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + const choices = Object.entries(ALL_AGENTS).map(([key, agent]) => { + const index = Object.keys(ALL_AGENTS).indexOf(key) + 1; + const checked = defaultSelection.includes(key) ? '✅' : '⬜'; + return `${index}. ${checked} ${agent.fullName} - ${agent.description}`; + }).join('\n'); + + console.error('\n╔════════════════════════════════════════════════════╗'); + console.error('║ Select AI Coding Agents for Memory Bank Sync ║'); + console.error('╠════════════════════════════════════════════════════╣'); + console.error(choices); + console.error('╠════════════════════════════════════════════════════╣'); + console.error('║ Enter numbers separated by commas (e.g., 1,3,4) ║'); + console.error('║ Or type "all" to select every agent ║'); + console.error('╚════════════════════════════════════════════════════╝\n'); + + rl.question('Your selection: ', (answer) => { + rl.close(); + const trimmed = answer.trim().toLowerCase(); + + if (trimmed === 'all') { + resolve(Object.keys(ALL_AGENTS) as AgentName[]); + return; + } + + const indices = trimmed.split(',').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n)); + const keys = Object.keys(ALL_AGENTS) as AgentName[]; + const selected = indices + .map(idx => keys[idx - 1]) + .filter((k): k is AgentName => k !== undefined && ALL_AGENTS[k] !== undefined); + + resolve(selected); + }); + }); } /** @@ -236,10 +368,25 @@ Follow this workflow to document important architectural decisions, patterns, or const stats = await this.storage.getStats(projectId); const cacheStats = this.cache.getStats(); - // Add sync status if available + // Show active agent configuration + let activeAgents: string[] = []; + let configExists = false; + if (this.agentConfig) { + const config = this.agentConfig.read(); + if (config) { + activeAgents = config.selectedAgents; + configExists = true; + } + if (activeAgents.length === 0) { + activeAgents = this.agentConfig.detectPresentAgents(); + } + } + const syncStatus = this.syncEngine ? { enabled: true, - agents: ['kilocode', 'cline', 'roocode'] + agents: activeAgents, + configExists, + allSupportedAgents: Object.keys(ALL_AGENTS) } : { enabled: false }; return { @@ -326,18 +473,31 @@ Follow this workflow to document important architectural decisions, patterns, or }, { name: 'project_init', - description: 'Initialize project storage', + description: 'Initialize project storage and optionally configure agents (pass agents: "kilocode,opencode" or use interactive mode)', inputSchema: { type: 'object', properties: { - projectId: { type: 'string' } + projectId: { type: 'string' }, + agents: { type: 'string', description: 'Comma-separated list of agents to sync with (e.g., \"kilocode,opencode\")' } }, required: ['projectId'] } }, + { + name: 'configure_agents', + description: 'Configure which coding agents to sync memory bank files with. Pass agents: "kilocode,opencode" or use interactive mode.', + inputSchema: { + type: 'object', + properties: { + agents: { type: 'string', description: 'Comma-separated list of agents to enable (e.g., \"kilocode,opencode\")' }, + interactive: { type: 'boolean', description: 'If true and TTY available, prompt interactively. Set to false for non-interactive.' } + }, + required: [] + } + }, { name: 'memory_stats', - description: 'Get storage and cache statistics', + description: 'Get storage, cache, and sync statistics', inputSchema: { type: 'object', properties: {