@grec0/memory-bank-mcp 0.2.12 → 0.2.14

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.
@@ -1,23 +1,123 @@
1
1
  import * as fs from 'fs/promises';
2
+ import * as fssync from 'fs';
2
3
  import * as path from 'path';
3
4
  import * as os from 'os';
4
5
  import { ProjectVectorStore } from './projectVectorStore.js';
5
6
  export class RegistryManager {
6
7
  globalPath;
7
8
  projectVectorStore;
9
+ storagePath;
8
10
  constructor() {
9
11
  this.globalPath = path.join(os.homedir(), '.memorybank', 'global_registry.json');
12
+ this.storagePath = process.env.MEMORYBANK_STORAGE_PATH || path.join(os.homedir(), '.memorybank');
10
13
  this.projectVectorStore = new ProjectVectorStore();
11
14
  }
15
+ /**
16
+ * Discovers projects by scanning the projects directory
17
+ * Used for auto-recovery when registry.json is corrupted/empty
18
+ */
19
+ async discoverProjectsFromDisk() {
20
+ const projectsDir = path.join(this.storagePath, 'projects');
21
+ if (!fssync.existsSync(projectsDir)) {
22
+ return [];
23
+ }
24
+ try {
25
+ const entries = await fs.readdir(projectsDir, { withFileTypes: true });
26
+ const projectIds = entries
27
+ .filter(e => e.isDirectory())
28
+ .map(e => e.name);
29
+ console.error(`Discovered ${projectIds.length} projects from disk: ${projectIds.join(', ')}`);
30
+ return projectIds;
31
+ }
32
+ catch (error) {
33
+ console.error(`Error discovering projects from disk: ${error}`);
34
+ return [];
35
+ }
36
+ }
37
+ /**
38
+ * Ensures the registry exists and returns it
39
+ * If corrupted/empty, auto-recovers by scanning project folders
40
+ */
12
41
  async ensureRegistry() {
13
42
  try {
14
43
  await fs.mkdir(path.dirname(this.globalPath), { recursive: true });
15
44
  const content = await fs.readFile(this.globalPath, 'utf-8');
16
- return JSON.parse(content);
45
+ // Validate JSON
46
+ if (!content || content.trim() === '') {
47
+ console.error('⚠️ Registry file is empty, attempting auto-recovery...');
48
+ return await this.autoRecoverRegistry();
49
+ }
50
+ const parsed = JSON.parse(content);
51
+ // Validate structure
52
+ if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.projects)) {
53
+ console.error('⚠️ Registry file has invalid structure, attempting auto-recovery...');
54
+ await this.backupCorruptedRegistry(content);
55
+ return await this.autoRecoverRegistry();
56
+ }
57
+ // Check if registry is suspiciously empty (has folders but no projects)
58
+ if (parsed.projects.length === 0) {
59
+ const diskProjects = await this.discoverProjectsFromDisk();
60
+ if (diskProjects.length > 0) {
61
+ console.error(`⚠️ Registry is empty but ${diskProjects.length} projects exist on disk, attempting auto-recovery...`);
62
+ await this.backupCorruptedRegistry(content);
63
+ return await this.autoRecoverRegistry();
64
+ }
65
+ }
66
+ return parsed;
17
67
  }
18
- catch {
68
+ catch (error) {
69
+ if (error.code === 'ENOENT') {
70
+ // File doesn't exist, try auto-recovery
71
+ console.error('Registry file does not exist, attempting auto-recovery...');
72
+ return await this.autoRecoverRegistry();
73
+ }
74
+ // JSON parse error - backup and auto-recover
75
+ console.error(`Error reading registry: ${error.message}, attempting auto-recovery...`);
76
+ try {
77
+ const content = await fs.readFile(this.globalPath, 'utf-8');
78
+ await this.backupCorruptedRegistry(content);
79
+ }
80
+ catch {
81
+ // Can't read for backup
82
+ }
83
+ return await this.autoRecoverRegistry();
84
+ }
85
+ }
86
+ /**
87
+ * Auto-recovers registry by discovering projects from disk
88
+ * Returns a minimal registry with project IDs - full data will be populated by sync
89
+ */
90
+ async autoRecoverRegistry() {
91
+ const projectIds = await this.discoverProjectsFromDisk();
92
+ if (projectIds.length === 0) {
93
+ console.error('No projects found on disk, starting with empty registry');
19
94
  return { projects: [] };
20
95
  }
96
+ console.error(`✓ Auto-recovered ${projectIds.length} projects from disk`);
97
+ console.error(` Run 'memorybank_sync_projects' to populate full metadata`);
98
+ // Return minimal registry - sync will populate the rest
99
+ const projects = projectIds.map(projectId => ({
100
+ projectId,
101
+ path: '', // Will be populated by sync
102
+ description: '',
103
+ keywords: [],
104
+ lastActive: new Date().toISOString(),
105
+ status: 'ACTIVE'
106
+ }));
107
+ return { projects };
108
+ }
109
+ /**
110
+ * Backs up a corrupted registry file before overwriting
111
+ */
112
+ async backupCorruptedRegistry(content) {
113
+ try {
114
+ const backupPath = `${this.globalPath}.backup-${Date.now()}`;
115
+ await fs.writeFile(backupPath, content, 'utf-8');
116
+ console.error(`Backed up corrupted registry to: ${backupPath}`);
117
+ }
118
+ catch (error) {
119
+ console.error(`Failed to backup corrupted registry: ${error}`);
120
+ }
21
121
  }
22
122
  async saveRegistry(registry) {
23
123
  await fs.writeFile(this.globalPath, JSON.stringify(registry, null, 2), 'utf-8');
@@ -29,16 +129,27 @@ export class RegistryManager {
29
129
  const existing = idx >= 0 ? registry.projects[idx] : null;
30
130
  const card = {
31
131
  projectId,
32
- path: workspacePath,
33
- description: description || existing?.description || '',
132
+ // Preserve existing workspace path if new one is empty/invalid
133
+ path: (workspacePath && workspacePath.trim() !== '') ? workspacePath : (existing?.path || workspacePath),
134
+ // Preserve existing description if new one is undefined or empty
135
+ description: (description !== undefined && description.trim() !== '') ? description : (existing?.description || ''),
136
+ // Preserve existing keywords if new array is empty
34
137
  keywords: keywords.length > 0 ? keywords : (existing?.keywords || []),
35
138
  lastActive: new Date().toISOString(),
36
139
  status: 'ACTIVE',
37
140
  // 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,
141
+ responsibilities: enhancedInfo?.responsibilities !== undefined
142
+ ? (enhancedInfo.responsibilities.length > 0 ? enhancedInfo.responsibilities : existing?.responsibilities)
143
+ : existing?.responsibilities,
144
+ owns: enhancedInfo?.owns !== undefined
145
+ ? (enhancedInfo.owns.length > 0 ? enhancedInfo.owns : existing?.owns)
146
+ : existing?.owns,
147
+ exports: enhancedInfo?.exports !== undefined
148
+ ? (enhancedInfo.exports.trim() !== '' ? enhancedInfo.exports : existing?.exports)
149
+ : existing?.exports,
150
+ projectType: enhancedInfo?.projectType !== undefined
151
+ ? (enhancedInfo.projectType.trim() !== '' ? enhancedInfo.projectType : existing?.projectType)
152
+ : existing?.projectType,
42
153
  };
43
154
  if (idx >= 0) {
44
155
  registry.projects[idx] = card;
@@ -378,6 +378,30 @@ export class VectorStore {
378
378
  return new Map();
379
379
  }
380
380
  }
381
+ /**
382
+ * Gets all unique project IDs that have indexed chunks
383
+ * Useful for discovering which projects have code in the vector store
384
+ */
385
+ async getIndexedProjectIds() {
386
+ await this.ensureInitialized();
387
+ if (!this.table) {
388
+ return [];
389
+ }
390
+ try {
391
+ const allChunks = await this.table.query().toArray();
392
+ const projectIds = new Set();
393
+ for (const chunk of allChunks) {
394
+ if (chunk.project_id) {
395
+ projectIds.add(chunk.project_id);
396
+ }
397
+ }
398
+ return Array.from(projectIds);
399
+ }
400
+ catch (error) {
401
+ console.error(`Error getting indexed project IDs: ${error}`);
402
+ return [];
403
+ }
404
+ }
381
405
  }
382
406
  /**
383
407
  * Creates a vector store from environment variables
@@ -1,2 +1,2 @@
1
1
  // Version of the MCP Kanban server
2
- export const VERSION = "0.2.12";
2
+ export const VERSION = "0.2.14";
@@ -98,32 +98,44 @@ export async function generateProjectDocs(params, projectKnowledgeService, vecto
98
98
  console.error(` - Estimated cost: $${totalCost.toFixed(4)}`);
99
99
  // === AUTO-UPDATE GLOBAL REGISTRY ===
100
100
  // Generate project summary and update registry with enriched info
101
- if (result.documentsGenerated.length > 0 || result.documentsUpdated.length > 0) {
101
+ // ONLY if generation was successful AND at least one document was generated/updated
102
+ if (result.success && (result.documentsGenerated.length > 0 || result.documentsUpdated.length > 0)) {
102
103
  try {
103
104
  console.error(`\n=== Updating Global Registry ===`);
104
105
  const summary = await projectKnowledgeService.generateProjectSummary(projectId);
105
106
  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}`);
107
+ // Validate that the summary has meaningful data before updating registry
108
+ const hasResponsibilities = summary.responsibilities && summary.responsibilities.length > 0;
109
+ const hasDescription = summary.description && summary.description.trim().length > 0;
110
+ if (!hasResponsibilities && !hasDescription) {
111
+ console.error(`Warning: Generated summary is empty, skipping registry update`);
112
+ }
113
+ else {
114
+ const registryManager = new RegistryManager();
115
+ let embeddingService;
116
+ if (process.env.OPENAI_API_KEY) {
117
+ try {
118
+ embeddingService = new EmbeddingService(process.env.OPENAI_API_KEY);
119
+ }
120
+ catch (e) {
121
+ console.error(`Warning: Could not init embedding service: ${e}`);
122
+ }
114
123
  }
124
+ await registryManager.registerProject(projectId, workspaceRoot, summary.description, summary.keywords, embeddingService, {
125
+ responsibilities: summary.responsibilities,
126
+ owns: summary.owns,
127
+ exports: summary.exports,
128
+ projectType: summary.projectType,
129
+ });
130
+ console.error(`Registry updated for ${projectId}:`);
131
+ console.error(` - Description: ${summary.description.slice(0, 80)}...`);
132
+ console.error(` - Type: ${summary.projectType}`);
133
+ console.error(` - Responsibilities: ${summary.responsibilities?.length || 0}`);
134
+ message += ` Registry updated with project summary.`;
115
135
  }
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.`;
136
+ }
137
+ else {
138
+ console.error(`Warning: Could not generate project summary, skipping registry update`);
127
139
  }
128
140
  }
129
141
  catch (regError) {
@@ -131,6 +143,9 @@ export async function generateProjectDocs(params, projectKnowledgeService, vecto
131
143
  // Don't fail the whole operation if registry update fails
132
144
  }
133
145
  }
146
+ else if (!result.success) {
147
+ console.error(`Skipping registry update because documentation generation failed`);
148
+ }
134
149
  return {
135
150
  success: result.success,
136
151
  message,
@@ -1,5 +1,10 @@
1
1
  import { RegistryManager } from '../common/registryManager.js';
2
2
  import { EmbeddingService } from '../common/embeddingService.js';
3
+ import { VectorStore } from '../common/vectorStore.js';
4
+ import { ProjectKnowledgeService } from '../common/projectKnowledgeService.js';
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import * as os from 'os';
3
8
  export async function syncProjectsTool() {
4
9
  if (!process.env.OPENAI_API_KEY) {
5
10
  return {
@@ -10,11 +15,155 @@ export async function syncProjectsTool() {
10
15
  try {
11
16
  const registryManager = new RegistryManager();
12
17
  const embeddingService = new EmbeddingService(process.env.OPENAI_API_KEY);
13
- const result = await registryManager.syncRegistry(embeddingService);
18
+ const vectorStore = new VectorStore();
19
+ const projectKnowledgeService = new ProjectKnowledgeService(process.env.OPENAI_API_KEY);
20
+ console.error("\n=== Starting Project Synchronization ===");
21
+ // Step 1: Discover ALL projects from multiple sources
22
+ console.error("\nStep 1: Discovering projects from all sources...");
23
+ // 1a. Find projects with indexed code
24
+ const indexedProjectIds = await vectorStore.getIndexedProjectIds();
25
+ console.error(` - Found ${indexedProjectIds.length} projects with indexed code`);
26
+ // 1b. Find projects with documentation folders (auto-recovery!)
27
+ const storagePath = process.env.MEMORYBANK_STORAGE_PATH || path.join(os.homedir(), '.memorybank');
28
+ const projectsDir = path.join(storagePath, 'projects');
29
+ let documentedProjectIds = [];
30
+ if (fs.existsSync(projectsDir)) {
31
+ const entries = fs.readdirSync(projectsDir, { withFileTypes: true });
32
+ documentedProjectIds = entries
33
+ .filter(e => e.isDirectory())
34
+ .map(e => e.name);
35
+ console.error(` - Found ${documentedProjectIds.length} projects with documentation folders`);
36
+ }
37
+ // 1c. Combine all discovered projects (unique)
38
+ const allProjectIds = Array.from(new Set([...indexedProjectIds, ...documentedProjectIds]));
39
+ console.error(` - Total unique projects discovered: ${allProjectIds.length}`);
40
+ if (allProjectIds.length > 0) {
41
+ console.error(` - Projects: ${allProjectIds.join(', ')}`);
42
+ }
43
+ // Step 2: Sync existing registry to vector store
44
+ console.error("\nStep 2: Syncing existing registry to vector store...");
45
+ const registryResult = await registryManager.syncRegistry(embeddingService);
46
+ console.error(`Registry sync: ${registryResult.processed} processed, ${registryResult.failures} failures`);
47
+ // Step 3: For each discovered project, ensure it has documentation and registry entry
48
+ let docsGenerated = 0;
49
+ let docsSkipped = 0;
50
+ let docsFailed = 0;
51
+ let registryRecovered = 0;
52
+ console.error("\nStep 3: Ensuring documentation and registry for all discovered projects...");
53
+ for (const projectId of allProjectIds) {
54
+ try {
55
+ console.error(`\nProcessing project: ${projectId}`);
56
+ // Check if project has documentation
57
+ const hasDocumentation = projectKnowledgeService.isProjectInitialized(projectId);
58
+ const hasIndexedCode = indexedProjectIds.includes(projectId);
59
+ if (!hasDocumentation && hasIndexedCode) {
60
+ console.error(` - No documentation found, generating...`);
61
+ // Generate documentation (this will automatically update registry)
62
+ const chunks = await vectorStore.getAllChunks(projectId);
63
+ if (chunks.length > 0) {
64
+ const result = await projectKnowledgeService.generateAllDocuments(projectId, chunks, false // Don't force regeneration
65
+ );
66
+ if (result.success) {
67
+ console.error(` - Documentation generated successfully`);
68
+ // Generate and update registry
69
+ const summary = await projectKnowledgeService.generateProjectSummary(projectId);
70
+ if (summary) {
71
+ // Try to find existing workspace path from registry
72
+ let workspacePath = '';
73
+ const existingProject = await registryManager.getProject(projectId);
74
+ if (existingProject) {
75
+ workspacePath = existingProject.path;
76
+ }
77
+ else {
78
+ // Try to infer from chunks
79
+ const firstChunk = chunks[0];
80
+ if (firstChunk && firstChunk.file_path) {
81
+ // Extract workspace path (this is a guess, may not be perfect)
82
+ workspacePath = process.cwd();
83
+ }
84
+ }
85
+ await registryManager.registerProject(projectId, workspacePath, summary.description, summary.keywords, embeddingService, {
86
+ responsibilities: summary.responsibilities,
87
+ owns: summary.owns,
88
+ exports: summary.exports,
89
+ projectType: summary.projectType,
90
+ });
91
+ console.error(` - Registry updated with project summary`);
92
+ docsGenerated++;
93
+ }
94
+ }
95
+ else {
96
+ console.error(` - Failed to generate documentation`);
97
+ docsFailed++;
98
+ }
99
+ }
100
+ else {
101
+ console.error(` - No chunks found for project, skipping`);
102
+ docsSkipped++;
103
+ }
104
+ }
105
+ else if (hasDocumentation) {
106
+ console.error(` - Documentation already exists, ensuring registry is up-to-date...`);
107
+ // Documentation exists, regenerate/update registry from it (RECOVERY!)
108
+ try {
109
+ const summary = await projectKnowledgeService.generateProjectSummary(projectId);
110
+ if (summary) {
111
+ const existingProject = await registryManager.getProject(projectId);
112
+ const workspacePath = existingProject?.path || process.cwd();
113
+ await registryManager.registerProject(projectId, workspacePath, summary.description, summary.keywords, embeddingService, {
114
+ responsibilities: summary.responsibilities,
115
+ owns: summary.owns,
116
+ exports: summary.exports,
117
+ projectType: summary.projectType,
118
+ });
119
+ console.error(` - Registry updated/recovered from documentation`);
120
+ registryRecovered++;
121
+ }
122
+ }
123
+ catch (error) {
124
+ console.error(` - Error recovering registry from docs: ${error}`);
125
+ }
126
+ docsSkipped++;
127
+ }
128
+ else {
129
+ console.error(` - No documentation and no indexed code, skipping`);
130
+ docsSkipped++;
131
+ }
132
+ }
133
+ catch (error) {
134
+ console.error(` - Error processing project ${projectId}: ${error}`);
135
+ docsFailed++;
136
+ }
137
+ }
138
+ console.error("\n=== Synchronization Complete ===");
139
+ console.error(`Projects discovered: ${allProjectIds.length} total`);
140
+ console.error(` - From indexed code: ${indexedProjectIds.length}`);
141
+ console.error(` - From documentation folders: ${documentedProjectIds.length}`);
142
+ console.error(`Registry synced: ${registryResult.processed} projects`);
143
+ console.error(`Registry recovered from docs: ${registryRecovered} projects`);
144
+ console.error(`Documentation generated: ${docsGenerated} projects`);
145
+ console.error(`Documentation skipped (already exists): ${docsSkipped} projects`);
146
+ console.error(`Failures: ${registryResult.failures + docsFailed}`);
14
147
  return {
15
148
  success: true,
16
- message: `Synchronization complete. Processed: ${result.processed}, Failures: ${result.failures}`,
17
- details: result
149
+ message: `Synchronization complete. Discovered ${allProjectIds.length} projects (${indexedProjectIds.length} indexed, ${documentedProjectIds.length} documented). Registry recovered: ${registryRecovered}, Docs generated: ${docsGenerated}, Failures: ${registryResult.failures + docsFailed}`,
150
+ details: {
151
+ discovery: {
152
+ total: allProjectIds.length,
153
+ indexed: indexedProjectIds.length,
154
+ documented: documentedProjectIds.length,
155
+ projects: allProjectIds
156
+ },
157
+ registrySync: registryResult,
158
+ registryRecovery: {
159
+ recovered: registryRecovered
160
+ },
161
+ documentationGeneration: {
162
+ generated: docsGenerated,
163
+ skipped: docsSkipped,
164
+ failed: docsFailed
165
+ }
166
+ }
18
167
  };
19
168
  }
20
169
  catch (error) {
@@ -26,7 +175,26 @@ export async function syncProjectsTool() {
26
175
  }
27
176
  export const syncProjectsToolDefinition = {
28
177
  name: "memorybank_sync_projects",
29
- description: "Sincroniza todos los proyectos del registro JSON al store vectorial para habilitar la búsqueda semántica. Útil para migrar datos existentes.",
178
+ description: `Sincroniza y recupera automáticamente todos los proyectos desde múltiples fuentes.
179
+
180
+ Esta herramienta realiza una sincronización completa con AUTO-RECUPERACIÓN:
181
+
182
+ DESCUBRIMIENTO MULTI-FUENTE:
183
+ 1. Escanea carpetas de documentación (.memorybank/projects/*)
184
+ 2. Escanea código indexado en vector store
185
+ 3. Lee registry JSON existente (si existe)
186
+
187
+ RECUPERACIÓN AUTOMÁTICA:
188
+ - Si el registry.json se corrompe o vacía, lo reconstruye desde las carpetas de documentación
189
+ - Genera documentación para código indexado sin docs
190
+ - Actualiza registry con responsabilidades extraídas
191
+
192
+ Útil cuando:
193
+ - El registry.json se ha corrompido o vaciado (AUTO-RECUPERA desde carpetas!)
194
+ - Has indexado código pero no has generado documentación
195
+ - El registry está desactualizado o incompleto
196
+ - Necesitas poblar las responsabilidades de proyectos para el orquestador
197
+ - Has perdido proyectos del registry pero las carpetas siguen existiendo`,
30
198
  inputSchema: {
31
199
  type: "object",
32
200
  properties: {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grec0/memory-bank-mcp",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "description": "MCP server for semantic code indexing with Memory Bank - AI-powered codebase understanding",
5
5
  "license": "MIT",
6
6
  "author": "@grec0",