@grec0/memory-bank-mcp 0.1.40 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,9 +7,11 @@ import * as path from "path";
7
7
  import * as fs from "fs";
8
8
  import * as crypto from "crypto";
9
9
  import { fileURLToPath } from "url";
10
+ import { createRequire } from "module";
10
11
  import { encode } from "gpt-tokenizer";
11
12
  const __filename = fileURLToPath(import.meta.url);
12
13
  const __dirname = path.dirname(__filename);
14
+ const require = createRequire(import.meta.url);
13
15
  // Constants
14
16
  const MAX_TOKENS_PER_CHUNK = 6000;
15
17
  const DEFAULT_CHUNK_OVERLAP_TOKENS = 200;
@@ -131,6 +133,7 @@ const SEMANTIC_NODE_TYPES = {
131
133
  // Cache for loaded languages
132
134
  // Using 'any' types because web-tree-sitter's TypeScript types don't work well with dynamic imports
133
135
  let ParserClass = null;
136
+ let LanguageClass = null;
134
137
  const loadedLanguages = new Map();
135
138
  let treeSitterInitialized = false;
136
139
  /**
@@ -155,15 +158,28 @@ function generateChunkId(filePath, content, startLine) {
155
158
  * Initializes Tree-sitter WASM module
156
159
  */
157
160
  async function initTreeSitter() {
158
- if (treeSitterInitialized && ParserClass) {
161
+ if (treeSitterInitialized && ParserClass && LanguageClass) {
159
162
  return true;
160
163
  }
161
164
  try {
162
165
  // Dynamic import of web-tree-sitter
163
- // web-tree-sitter exports Parser as a named export, not default
164
166
  const TreeSitterModule = await import("web-tree-sitter");
165
- // Get Parser from named export
166
- ParserClass = TreeSitterModule.Parser;
167
+ // Handle different export formats across web-tree-sitter versions:
168
+ // - v0.20.x: exports Parser as default, Language is Parser.Language
169
+ // - v0.26.x: exports Parser and Language as separate named exports
170
+ if (TreeSitterModule.Parser) {
171
+ // v0.26.x style: named exports
172
+ ParserClass = TreeSitterModule.Parser;
173
+ LanguageClass = TreeSitterModule.Language;
174
+ }
175
+ else if (TreeSitterModule.default) {
176
+ // v0.20.x style: default export is Parser, Language is a static property
177
+ ParserClass = TreeSitterModule.default;
178
+ LanguageClass = null; // Will use ParserClass.Language after init
179
+ }
180
+ else {
181
+ throw new Error('Could not find Parser in web-tree-sitter module');
182
+ }
167
183
  if (!ParserClass) {
168
184
  throw new Error('Parser class not found in web-tree-sitter module');
169
185
  }
@@ -171,6 +187,13 @@ async function initTreeSitter() {
171
187
  if (typeof ParserClass.init === 'function') {
172
188
  await ParserClass.init();
173
189
  }
190
+ // For v0.20.x, Language becomes available after init
191
+ if (!LanguageClass && ParserClass.Language) {
192
+ LanguageClass = ParserClass.Language;
193
+ }
194
+ if (!LanguageClass) {
195
+ throw new Error('Language class not found in web-tree-sitter module');
196
+ }
174
197
  treeSitterInitialized = true;
175
198
  console.error("[AST Chunker] Tree-sitter initialized successfully");
176
199
  return true;
@@ -179,6 +202,7 @@ async function initTreeSitter() {
179
202
  console.error(`[AST Chunker] Failed to initialize Tree-sitter: ${error}`);
180
203
  treeSitterInitialized = false;
181
204
  ParserClass = null;
205
+ LanguageClass = null;
182
206
  return false;
183
207
  }
184
208
  }
@@ -190,26 +214,46 @@ function getWasmPath(language) {
190
214
  if (!wasmFile) {
191
215
  return null;
192
216
  }
193
- // Try to find in node_modules
217
+ // Build comprehensive list of possible paths
218
+ // The compiled JS runs from dist/common/, so we need to account for various scenarios:
219
+ // 1. Local development: dist/common/../node_modules = dist/node_modules (wrong)
220
+ // 2. Local development: __dirname/../../node_modules = node_modules (correct)
221
+ // 3. NPM installed package: need to resolve from the package location
222
+ // 4. Global install or monorepo: process.cwd() based paths
194
223
  const possiblePaths = [
195
- path.join(__dirname, "..", "node_modules", "tree-sitter-wasms", "out", wasmFile),
196
- path.join(process.cwd(), "node_modules", "tree-sitter-wasms", "out", wasmFile),
224
+ // From dist/common/ go up two levels to project root then into node_modules
197
225
  path.join(__dirname, "..", "..", "node_modules", "tree-sitter-wasms", "out", wasmFile),
226
+ // From current working directory
227
+ path.join(process.cwd(), "node_modules", "tree-sitter-wasms", "out", wasmFile),
228
+ // From dist/ go up one level (in case __dirname is dist/)
229
+ path.join(__dirname, "..", "node_modules", "tree-sitter-wasms", "out", wasmFile),
230
+ // Three levels up (for nested structures like dist/common/subdir)
231
+ path.join(__dirname, "..", "..", "..", "node_modules", "tree-sitter-wasms", "out", wasmFile),
198
232
  ];
233
+ // Try to use require.resolve to find the package (works in most Node.js scenarios)
234
+ try {
235
+ const treeSitterWasmsPath = require.resolve("tree-sitter-wasms/package.json");
236
+ const packageDir = path.dirname(treeSitterWasmsPath);
237
+ possiblePaths.unshift(path.join(packageDir, "out", wasmFile));
238
+ }
239
+ catch {
240
+ // Package not found via require.resolve, continue with file-based search
241
+ }
199
242
  for (const p of possiblePaths) {
200
243
  if (fs.existsSync(p)) {
201
244
  return p;
202
245
  }
203
246
  }
204
247
  console.error(`[AST Chunker] WASM file not found for ${language}: ${wasmFile}`);
248
+ console.error(`[AST Chunker] Searched paths: ${possiblePaths.map(p => `\n - ${p}`).join('')}`);
205
249
  return null;
206
250
  }
207
251
  /**
208
252
  * Loads a language parser
209
253
  */
210
254
  async function loadLanguage(language) {
211
- if (!ParserClass) {
212
- console.error("[AST Chunker] Parser not initialized");
255
+ if (!ParserClass || !LanguageClass) {
256
+ console.error("[AST Chunker] Parser or Language not initialized");
213
257
  return null;
214
258
  }
215
259
  const langKey = language.toLowerCase();
@@ -220,15 +264,19 @@ async function loadLanguage(language) {
220
264
  if (!wasmPath) {
221
265
  return null;
222
266
  }
267
+ console.error(`[AST Chunker] Attempting to load WASM from: ${wasmPath}`);
223
268
  try {
224
269
  // Use the Language.load static method
225
- const lang = await ParserClass.Language.load(wasmPath);
270
+ const lang = await LanguageClass.load(wasmPath);
226
271
  loadedLanguages.set(langKey, lang);
227
272
  console.error(`[AST Chunker] Loaded language: ${language}`);
228
273
  return lang;
229
274
  }
230
275
  catch (error) {
231
- console.error(`[AST Chunker] Failed to load language ${language}: ${error}`);
276
+ console.error(`[AST Chunker] Failed to load language ${language}: ${error instanceof Error ? error.message : error}`);
277
+ if (error instanceof Error && error.stack) {
278
+ console.error(`[AST Chunker] Stack: ${error.stack}`);
279
+ }
232
280
  return null;
233
281
  }
234
282
  }
@@ -556,5 +604,6 @@ export function disposeASTChunker() {
556
604
  loadedLanguages.clear();
557
605
  treeSitterInitialized = false;
558
606
  ParserClass = null;
607
+ LanguageClass = null;
559
608
  console.error("[AST Chunker] Disposed");
560
609
  }
@@ -1008,6 +1008,72 @@ ${chunk.content}
1008
1008
  const files = fs.readdirSync(docsPath);
1009
1009
  return files.filter(f => f.endsWith(".md"));
1010
1010
  }
1011
+ // ==========================================
1012
+ // Project Summary Generation (for Registry)
1013
+ // ==========================================
1014
+ /**
1015
+ * Generates a structured summary of the project for the global registry.
1016
+ * Extracts responsibilities, ownership patterns, and project type from projectBrief.
1017
+ * This is called automatically after generateAllDocuments.
1018
+ */
1019
+ async generateProjectSummary(projectId) {
1020
+ const projectBrief = this.getDocument(projectId, "projectBrief");
1021
+ const techContext = this.getDocument(projectId, "techContext");
1022
+ if (!projectBrief) {
1023
+ console.error(`Cannot generate project summary: projectBrief not found for ${projectId}`);
1024
+ return null;
1025
+ }
1026
+ console.error(`Generating project summary for registry: ${projectId}`);
1027
+ const prompt = `Analyze the following project documentation and extract a structured summary.
1028
+
1029
+ PROJECT BRIEF:
1030
+ ${projectBrief.content}
1031
+
1032
+ ${techContext ? `TECHNICAL CONTEXT:\n${techContext.content}` : ''}
1033
+
1034
+ Extract the following information in JSON format:
1035
+ {
1036
+ "description": "A concise 1-2 sentence description of what this project does",
1037
+ "responsibilities": ["List of 3-5 specific things this project is responsible for"],
1038
+ "owns": ["File patterns or types this project owns, e.g., '*DTO.ts', 'services/', 'controllers/'"],
1039
+ "projectType": "One of: api, library, frontend, backend, cli, service, monorepo, fullstack",
1040
+ "exports": "Package name if it's a library (e.g., '@company/lib-dtos'), or null if not applicable",
1041
+ "keywords": ["5-8 keywords describing this project"]
1042
+ }
1043
+
1044
+ IMPORTANT:
1045
+ - Be specific about responsibilities - they help the orchestrator decide where code belongs
1046
+ - For "owns", think about what file patterns ONLY this project should create
1047
+ - If it's a library, identify what it exports/provides to other projects
1048
+
1049
+ Respond ONLY with the JSON object, no markdown or explanation.`;
1050
+ try {
1051
+ const result = await this.callResponsesAPI(prompt);
1052
+ // Parse JSON response
1053
+ const jsonMatch = result.content.match(/\{[\s\S]*\}/);
1054
+ if (!jsonMatch) {
1055
+ console.error(`Failed to parse project summary JSON for ${projectId}`);
1056
+ return null;
1057
+ }
1058
+ const summary = JSON.parse(jsonMatch[0]);
1059
+ console.error(`Project summary generated for ${projectId}:`);
1060
+ console.error(` - Type: ${summary.projectType}`);
1061
+ console.error(` - Responsibilities: ${summary.responsibilities?.length || 0}`);
1062
+ console.error(` - Owns: ${summary.owns?.length || 0}`);
1063
+ return {
1064
+ description: summary.description || '',
1065
+ responsibilities: summary.responsibilities || [],
1066
+ owns: summary.owns || [],
1067
+ projectType: summary.projectType || 'unknown',
1068
+ exports: summary.exports || undefined,
1069
+ keywords: summary.keywords || [],
1070
+ };
1071
+ }
1072
+ catch (error) {
1073
+ console.error(`Error generating project summary: ${error}`);
1074
+ return null;
1075
+ }
1076
+ }
1011
1077
  }
1012
1078
  /**
1013
1079
  * Creates a Project Knowledge Service from environment variables
@@ -22,10 +22,10 @@ export class RegistryManager {
22
22
  async saveRegistry(registry) {
23
23
  await fs.writeFile(this.globalPath, JSON.stringify(registry, null, 2), 'utf-8');
24
24
  }
25
- async registerProject(projectId, workspacePath, description, keywords = [], embeddingService) {
25
+ async registerProject(projectId, workspacePath, description, keywords = [], embeddingService, enhancedInfo) {
26
26
  const registry = await this.ensureRegistry();
27
27
  const idx = registry.projects.findIndex(p => p.projectId === projectId);
28
- // Preserve existing description/keywords if not provided
28
+ // Preserve existing data if not provided
29
29
  const existing = idx >= 0 ? registry.projects[idx] : null;
30
30
  const card = {
31
31
  projectId,
@@ -33,7 +33,12 @@ export class RegistryManager {
33
33
  description: description || existing?.description || '',
34
34
  keywords: keywords.length > 0 ? keywords : (existing?.keywords || []),
35
35
  lastActive: new Date().toISOString(),
36
- status: 'ACTIVE'
36
+ status: 'ACTIVE',
37
+ // Enhanced fields - preserve existing if not provided
38
+ responsibilities: enhancedInfo?.responsibilities || existing?.responsibilities,
39
+ owns: enhancedInfo?.owns || existing?.owns,
40
+ exports: enhancedInfo?.exports || existing?.exports,
41
+ projectType: enhancedInfo?.projectType || existing?.projectType,
37
42
  };
38
43
  if (idx >= 0) {
39
44
  registry.projects[idx] = card;
@@ -53,12 +58,30 @@ export class RegistryManager {
53
58
  }
54
59
  }
55
60
  async updateProjectEmbedding(card, embeddingService) {
56
- const text = `Project: ${card.projectId}\nDescription: ${card.description || ''}\nKeywords: ${card.keywords.join(', ')}`;
61
+ // Build rich text for embedding including responsibilities
62
+ const textParts = [
63
+ `Project: ${card.projectId}`,
64
+ `Description: ${card.description || ''}`,
65
+ `Keywords: ${card.keywords.join(', ')}`,
66
+ ];
67
+ if (card.responsibilities && card.responsibilities.length > 0) {
68
+ textParts.push(`Responsibilities: ${card.responsibilities.join('. ')}`);
69
+ }
70
+ if (card.owns && card.owns.length > 0) {
71
+ textParts.push(`Owns: ${card.owns.join(', ')}`);
72
+ }
73
+ if (card.projectType) {
74
+ textParts.push(`Type: ${card.projectType}`);
75
+ }
76
+ if (card.exports) {
77
+ textParts.push(`Exports: ${card.exports}`);
78
+ }
79
+ const text = textParts.join('\n');
57
80
  const result = await embeddingService.generateEmbedding(card.projectId, text);
58
81
  await this.projectVectorStore.upsertProject({
59
82
  id: card.projectId,
60
83
  vector: result.vector,
61
- name: card.projectId, // Using ID as name for now if name not available
84
+ name: card.projectId,
62
85
  description: card.description || '',
63
86
  tags: card.keywords,
64
87
  path: card.path,
@@ -101,6 +124,14 @@ export class RegistryManager {
101
124
  const registry = await this.ensureRegistry();
102
125
  return registry.projects.find(p => p.projectId === projectId);
103
126
  }
127
+ /**
128
+ * Gets all projects from the registry.
129
+ * Useful for the orchestrator to analyze responsibilities across all projects.
130
+ */
131
+ async getAllProjects() {
132
+ const registry = await this.ensureRegistry();
133
+ return registry.projects;
134
+ }
104
135
  /**
105
136
  * Syncs all projects from the JSON registry to the vector store.
106
137
  * Useful for migrating existing projects to the new semantic discovery system.
package/dist/index.js CHANGED
@@ -83,6 +83,7 @@ import { manageAgentsTool, manageAgentsToolDefinition } from "./tools/manageAgen
83
83
  import { discoverProjectsTool, discoverProjectsToolDefinition } from "./tools/discoverProjects.js";
84
84
  import { delegateTaskTool, delegateTaskToolDefinition } from "./tools/delegateTask.js";
85
85
  import { syncProjectsTool, syncProjectsToolDefinition } from "./tools/syncProjects.js";
86
+ import { routeTaskTool, routeTaskToolDefinition } from "./tools/routeTask.js";
86
87
  import { RegistryManager } from "./common/registryManager.js";
87
88
  import { VERSION } from "./common/version.js";
88
89
  // Global services
@@ -864,6 +865,19 @@ server.tool(delegateTaskToolDefinition.name, delegateTaskToolDefinition.descript
864
865
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
865
866
  };
866
867
  });
868
+ // Tool: Route Task (Orchestrator)
869
+ server.tool(routeTaskToolDefinition.name, routeTaskToolDefinition.description, {
870
+ projectId: z.string().describe("ID del proyecto que está solicitando el enrutamiento"),
871
+ taskDescription: z.string().describe("Descripción detallada de la tarea a implementar")
872
+ }, async (args) => {
873
+ const result = await routeTaskTool({
874
+ projectId: args.projectId,
875
+ taskDescription: args.taskDescription
876
+ });
877
+ return {
878
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
879
+ };
880
+ });
867
881
  /**
868
882
  * Starts the stdio server
869
883
  */
@@ -900,6 +914,7 @@ async function startStdioServer() {
900
914
  console.error(" - memorybank_complete_task: Marcar una tarea como completada");
901
915
  console.error(" - memorybank_discover_projects: Descubrir otros proyectos en el ecosistema");
902
916
  console.error(" - memorybank_delegate_task: Delegar tareas a otros proyectos");
917
+ console.error(" - memorybank_route_task: Orquestador - analiza y distribuye tareas");
903
918
  console.error("");
904
919
  console.error("Available resources:");
905
920
  console.error(" - memory://{projectId}/active: Contexto activo");
@@ -4,6 +4,8 @@
4
4
  */
5
5
  import { AgentBoard } from "../common/agentBoard.js";
6
6
  import { sessionLogger } from "../common/sessionLogger.js";
7
+ import { RegistryManager } from "../common/registryManager.js";
8
+ import { EmbeddingService } from "../common/embeddingService.js";
7
9
  import { sessionState } from "../common/sessionState.js";
8
10
  /**
9
11
  * Generates project documentation using AI reasoning
@@ -94,6 +96,41 @@ export async function generateProjectDocs(params, projectKnowledgeService, vecto
94
96
  console.error(` - Reasoning tokens: ${result.totalReasoningTokens}`);
95
97
  console.error(` - Output tokens: ${result.totalOutputTokens}`);
96
98
  console.error(` - Estimated cost: $${totalCost.toFixed(4)}`);
99
+ // === AUTO-UPDATE GLOBAL REGISTRY ===
100
+ // Generate project summary and update registry with enriched info
101
+ if (result.documentsGenerated.length > 0 || result.documentsUpdated.length > 0) {
102
+ try {
103
+ console.error(`\n=== Updating Global Registry ===`);
104
+ const summary = await projectKnowledgeService.generateProjectSummary(projectId);
105
+ if (summary) {
106
+ const registryManager = new RegistryManager();
107
+ let embeddingService;
108
+ if (process.env.OPENAI_API_KEY) {
109
+ try {
110
+ embeddingService = new EmbeddingService(process.env.OPENAI_API_KEY);
111
+ }
112
+ catch (e) {
113
+ console.error(`Warning: Could not init embedding service: ${e}`);
114
+ }
115
+ }
116
+ await registryManager.registerProject(projectId, workspaceRoot, summary.description, summary.keywords, embeddingService, {
117
+ responsibilities: summary.responsibilities,
118
+ owns: summary.owns,
119
+ exports: summary.exports,
120
+ projectType: summary.projectType,
121
+ });
122
+ console.error(`Registry updated for ${projectId}:`);
123
+ console.error(` - Description: ${summary.description.slice(0, 80)}...`);
124
+ console.error(` - Type: ${summary.projectType}`);
125
+ console.error(` - Responsibilities: ${summary.responsibilities.length}`);
126
+ message += ` Registry updated with project summary.`;
127
+ }
128
+ }
129
+ catch (regError) {
130
+ console.error(`Warning: Failed to update registry: ${regError}`);
131
+ // Don't fail the whole operation if registry update fails
132
+ }
133
+ }
97
134
  return {
98
135
  success: result.success,
99
136
  message,
@@ -15,3 +15,5 @@ export * from "./syncProjects.js";
15
15
  export * from "./generateProjectDocs.js";
16
16
  export * from "./getProjectDocs.js";
17
17
  export * from "./manageAgents.js";
18
+ // Export Task Orchestration tools
19
+ export * from "./routeTask.js";
@@ -0,0 +1,302 @@
1
+ /**
2
+ * @fileoverview Task Routing Orchestrator
3
+ * Uses AI reasoning to analyze tasks and distribute work across projects
4
+ * based on their responsibilities. MANDATORY before any implementation.
5
+ */
6
+ import OpenAI from "openai";
7
+ import { RegistryManager } from "../common/registryManager.js";
8
+ /**
9
+ * Builds a context string describing all projects and their responsibilities
10
+ */
11
+ function buildProjectsContext(projects, currentProjectId) {
12
+ if (projects.length === 0) {
13
+ return "No other projects registered in the workspace.";
14
+ }
15
+ let context = "## Registered Projects in Workspace\n\n";
16
+ for (const project of projects) {
17
+ const isCurrent = project.projectId === currentProjectId;
18
+ context += `### ${project.projectId}${isCurrent ? ' (CURRENT - requesting agent)' : ''}\n`;
19
+ context += `- **Path**: ${project.path}\n`;
20
+ context += `- **Type**: ${project.projectType || 'unknown'}\n`;
21
+ context += `- **Description**: ${project.description || 'No description'}\n`;
22
+ if (project.responsibilities && project.responsibilities.length > 0) {
23
+ context += `- **Responsibilities**:\n`;
24
+ for (const resp of project.responsibilities) {
25
+ context += ` - ${resp}\n`;
26
+ }
27
+ }
28
+ if (project.owns && project.owns.length > 0) {
29
+ context += `- **Owns (file patterns)**: ${project.owns.join(', ')}\n`;
30
+ }
31
+ if (project.exports) {
32
+ context += `- **Exports**: ${project.exports}\n`;
33
+ }
34
+ context += '\n';
35
+ }
36
+ return context;
37
+ }
38
+ /**
39
+ * The main routing prompt for the AI orchestrator
40
+ */
41
+ const ROUTING_PROMPT = `You are a Task Routing Orchestrator for a multi-project workspace. Your job is to analyze a task and determine which parts belong to which project based on their responsibilities.
42
+
43
+ {projectsContext}
44
+
45
+ ## Task to Analyze
46
+ **From Project**: {currentProject}
47
+ **Task Description**: {taskDescription}
48
+
49
+ ## Your Analysis
50
+
51
+ Analyze the task and determine:
52
+ 1. What components/code need to be created or modified?
53
+ 2. Which project is responsible for each component based on their declared responsibilities?
54
+ 3. If something doesn't exist in any project, it can be created by the requesting project
55
+ 4. If something SHOULD exist in another project (based on responsibilities), it must be delegated
56
+
57
+ ## Rules
58
+ - If a project is responsible for DTOs, ALL DTOs must be created there, not in the API
59
+ - If a project is responsible for services, shared services go there
60
+ - If a project is responsible for utils/common code, shared utilities go there
61
+ - The requesting project can ONLY implement what falls within its responsibilities
62
+ - When in doubt, check the "owns" patterns to see what file types belong where
63
+
64
+ ## Response Format
65
+ Respond with a JSON object:
66
+ {
67
+ "action": "proceed" | "delegate" | "mixed",
68
+ "myResponsibilities": ["List of things the requesting project should implement"],
69
+ "delegations": [
70
+ {
71
+ "targetProject": "project-id",
72
+ "taskTitle": "Short title for the task",
73
+ "taskDescription": "Detailed description of what to create",
74
+ "reasoning": "Why this belongs to this project"
75
+ }
76
+ ],
77
+ "suggestedImports": ["packages or modules to import after delegations complete"],
78
+ "architectureNotes": "Explanation of the distribution decision",
79
+ "warning": "Optional warning if something seems off"
80
+ }
81
+
82
+ IMPORTANT:
83
+ - "action" is "proceed" if everything can be done by the requesting project
84
+ - "action" is "delegate" if everything needs to go to other projects
85
+ - "action" is "mixed" if some work is local and some needs delegation
86
+ - Be specific in taskDescription so the receiving project knows exactly what to create
87
+ - Always explain the reasoning based on project responsibilities
88
+
89
+ Respond ONLY with the JSON object.`;
90
+ /**
91
+ * Routes a task to the appropriate project(s) based on responsibilities
92
+ */
93
+ export async function routeTaskTool(params) {
94
+ const { projectId, taskDescription } = params;
95
+ if (!taskDescription || taskDescription.trim() === '') {
96
+ return {
97
+ success: false,
98
+ action: 'proceed',
99
+ myResponsibilities: [],
100
+ delegations: [],
101
+ suggestedImports: [],
102
+ architectureNotes: 'No task description provided.',
103
+ warning: 'Please provide a task description to analyze.',
104
+ };
105
+ }
106
+ console.error(`\n=== Task Routing Orchestrator ===`);
107
+ console.error(`Project: ${projectId}`);
108
+ console.error(`Task: ${taskDescription.slice(0, 100)}...`);
109
+ // Get all projects with their responsibilities
110
+ const registryManager = new RegistryManager();
111
+ const allProjects = await registryManager.getAllProjects();
112
+ console.error(`Found ${allProjects.length} projects in registry`);
113
+ // Check if we have any projects with responsibilities defined
114
+ const projectsWithResponsibilities = allProjects.filter(p => p.responsibilities && p.responsibilities.length > 0);
115
+ if (projectsWithResponsibilities.length === 0) {
116
+ console.error(`Warning: No projects have responsibilities defined`);
117
+ return {
118
+ success: true,
119
+ action: 'proceed',
120
+ myResponsibilities: ['All components (no other project responsibilities defined)'],
121
+ delegations: [],
122
+ suggestedImports: [],
123
+ architectureNotes: 'No other projects have responsibilities defined. You can proceed with the full implementation. Consider running memorybank_generate_project_docs on other projects to define their responsibilities.',
124
+ warning: 'No project responsibilities found in workspace. Run generate_project_docs on all projects first.',
125
+ };
126
+ }
127
+ // Build context for the AI
128
+ const projectsContext = buildProjectsContext(allProjects, projectId);
129
+ // Call AI to analyze and route
130
+ const apiKey = process.env.OPENAI_API_KEY;
131
+ if (!apiKey) {
132
+ return {
133
+ success: false,
134
+ action: 'proceed',
135
+ myResponsibilities: [],
136
+ delegations: [],
137
+ suggestedImports: [],
138
+ architectureNotes: 'OPENAI_API_KEY not configured. Cannot analyze task routing.',
139
+ warning: 'AI routing unavailable. Proceeding without validation.',
140
+ };
141
+ }
142
+ try {
143
+ const client = new OpenAI({ apiKey });
144
+ const prompt = ROUTING_PROMPT
145
+ .replace('{projectsContext}', projectsContext)
146
+ .replace('{currentProject}', projectId)
147
+ .replace('{taskDescription}', taskDescription);
148
+ console.error(`Calling AI orchestrator...`);
149
+ // Use reasoning model for better analysis
150
+ const model = process.env.MEMORYBANK_REASONING_MODEL || "gpt-5-mini";
151
+ const response = await client.responses.create({
152
+ model,
153
+ reasoning: {
154
+ effort: "medium",
155
+ },
156
+ input: [
157
+ {
158
+ role: "user",
159
+ content: prompt,
160
+ },
161
+ ],
162
+ max_output_tokens: 4000,
163
+ });
164
+ // Extract content from response
165
+ let content = "";
166
+ for (const item of response.output || []) {
167
+ if (item.type === "message" && item.content) {
168
+ for (const contentItem of item.content) {
169
+ if (contentItem.type === "output_text") {
170
+ content += contentItem.text;
171
+ }
172
+ }
173
+ }
174
+ }
175
+ // Parse JSON response
176
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
177
+ if (!jsonMatch) {
178
+ console.error(`Failed to parse orchestrator response`);
179
+ return {
180
+ success: false,
181
+ action: 'proceed',
182
+ myResponsibilities: [],
183
+ delegations: [],
184
+ suggestedImports: [],
185
+ architectureNotes: 'Failed to parse AI response.',
186
+ warning: 'Orchestrator analysis failed. Review task manually.',
187
+ };
188
+ }
189
+ const result = JSON.parse(jsonMatch[0]);
190
+ console.error(`\nOrchestrator Decision:`);
191
+ console.error(` Action: ${result.action}`);
192
+ console.error(` My responsibilities: ${result.myResponsibilities?.length || 0}`);
193
+ console.error(` Delegations: ${result.delegations?.length || 0}`);
194
+ if (result.delegations && result.delegations.length > 0) {
195
+ console.error(`\n Delegations:`);
196
+ for (const d of result.delegations) {
197
+ console.error(` → ${d.targetProject}: ${d.taskTitle}`);
198
+ }
199
+ }
200
+ return {
201
+ success: true,
202
+ action: result.action || 'proceed',
203
+ myResponsibilities: result.myResponsibilities || [],
204
+ delegations: result.delegations || [],
205
+ suggestedImports: result.suggestedImports || [],
206
+ architectureNotes: result.architectureNotes || '',
207
+ warning: result.warning,
208
+ };
209
+ }
210
+ catch (error) {
211
+ console.error(`Error in task routing: ${error.message}`);
212
+ // Fallback to chat completions if responses API fails
213
+ if (error?.status === 404 || error?.code === "model_not_found") {
214
+ try {
215
+ const client = new OpenAI({ apiKey });
216
+ const prompt = ROUTING_PROMPT
217
+ .replace('{projectsContext}', projectsContext)
218
+ .replace('{currentProject}', projectId)
219
+ .replace('{taskDescription}', taskDescription);
220
+ const response = await client.chat.completions.create({
221
+ model: "gpt-4o",
222
+ messages: [
223
+ {
224
+ role: "system",
225
+ content: "You are a Task Routing Orchestrator. Analyze tasks and route them to the appropriate projects based on responsibilities. Respond with JSON only.",
226
+ },
227
+ {
228
+ role: "user",
229
+ content: prompt,
230
+ },
231
+ ],
232
+ max_tokens: 4000,
233
+ });
234
+ const content = response.choices[0]?.message?.content || "";
235
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
236
+ if (jsonMatch) {
237
+ const result = JSON.parse(jsonMatch[0]);
238
+ return {
239
+ success: true,
240
+ action: result.action || 'proceed',
241
+ myResponsibilities: result.myResponsibilities || [],
242
+ delegations: result.delegations || [],
243
+ suggestedImports: result.suggestedImports || [],
244
+ architectureNotes: result.architectureNotes || '',
245
+ warning: result.warning,
246
+ };
247
+ }
248
+ }
249
+ catch (fallbackError) {
250
+ console.error(`Fallback also failed: ${fallbackError}`);
251
+ }
252
+ }
253
+ return {
254
+ success: false,
255
+ action: 'proceed',
256
+ myResponsibilities: [],
257
+ delegations: [],
258
+ suggestedImports: [],
259
+ architectureNotes: `Error analyzing task: ${error.message}`,
260
+ warning: 'Orchestrator failed. Review task distribution manually.',
261
+ };
262
+ }
263
+ }
264
+ /**
265
+ * Tool definition for MCP
266
+ */
267
+ export const routeTaskToolDefinition = {
268
+ name: "memorybank_route_task",
269
+ description: `🚨 OBLIGATORIO antes de implementar cualquier código.
270
+
271
+ Analiza una tarea y determina qué partes corresponden a qué proyecto según sus responsabilidades.
272
+
273
+ El orquestador:
274
+ 1. Lee las responsabilidades de TODOS los proyectos del workspace
275
+ 2. Analiza qué componentes necesita la tarea (DTOs, services, controllers, etc.)
276
+ 3. Asigna cada componente al proyecto responsable
277
+ 4. Devuelve un plan de acción con delegaciones
278
+
279
+ DEBES llamar esta herramienta ANTES de escribir código para evitar:
280
+ - Crear DTOs en un API cuando existe una lib-dtos
281
+ - Duplicar services que ya existen en otro proyecto
282
+ - Violar la separación de responsabilidades
283
+
284
+ La respuesta incluye:
285
+ - myResponsibilities: Lo que TÚ debes implementar
286
+ - delegations: Tareas a delegar a otros proyectos
287
+ - suggestedImports: Dependencias a usar tras las delegaciones`,
288
+ inputSchema: {
289
+ type: "object",
290
+ properties: {
291
+ projectId: {
292
+ type: "string",
293
+ description: "ID del proyecto que solicita el análisis (tu proyecto actual)",
294
+ },
295
+ taskDescription: {
296
+ type: "string",
297
+ description: "Descripción completa de la tarea a realizar",
298
+ },
299
+ },
300
+ required: ["projectId", "taskDescription"],
301
+ },
302
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grec0/memory-bank-mcp",
3
- "version": "0.1.40",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server for semantic code indexing with Memory Bank - AI-powered codebase understanding",
5
5
  "license": "MIT",
6
6
  "author": "@grec0",
@@ -40,14 +40,14 @@
40
40
  "@lancedb/lancedb": "^0.9.0",
41
41
  "@modelcontextprotocol/sdk": "1.25.2",
42
42
  "@types/node": "^22",
43
+ "better-sqlite3": "^11.7.0",
43
44
  "gpt-tokenizer": "3.4.0",
44
45
  "ignore": "^5.3.0",
45
46
  "openai": "^4.0.0",
46
47
  "tree-sitter-wasms": "0.1.13",
47
- "web-tree-sitter": "0.26.3",
48
+ "web-tree-sitter": "0.20.8",
48
49
  "zod": "^3.22.4",
49
- "zod-to-json-schema": "^3.23.5",
50
- "better-sqlite3": "^11.7.0"
50
+ "zod-to-json-schema": "^3.23.5"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@jest/globals": "^29.7.0",