@soulcraft/brainy 6.3.0 → 6.3.2

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.
Files changed (34) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/brainy.d.ts +55 -0
  3. package/dist/brainy.js +86 -0
  4. package/dist/versioning/VersionIndex.d.ts +42 -47
  5. package/dist/versioning/VersionIndex.js +141 -166
  6. package/dist/versioning/VersionManager.d.ts +26 -6
  7. package/dist/versioning/VersionManager.js +101 -8
  8. package/dist/versioning/VersionStorage.d.ts +25 -15
  9. package/dist/versioning/VersionStorage.js +49 -65
  10. package/package.json +1 -1
  11. package/dist/augmentations/KnowledgeAugmentation.d.ts +0 -40
  12. package/dist/augmentations/KnowledgeAugmentation.js +0 -251
  13. package/dist/importManager.d.ts +0 -78
  14. package/dist/importManager.js +0 -267
  15. package/dist/query/typeInference.d.ts +0 -158
  16. package/dist/query/typeInference.js +0 -760
  17. package/dist/storage/adapters/typeAwareStorageAdapter.d.ts +0 -252
  18. package/dist/storage/adapters/typeAwareStorageAdapter.js +0 -814
  19. package/dist/types/brainyDataInterface.d.ts +0 -52
  20. package/dist/types/brainyDataInterface.js +0 -10
  21. package/dist/vfs/ConceptSystem.d.ts +0 -203
  22. package/dist/vfs/ConceptSystem.js +0 -545
  23. package/dist/vfs/EntityManager.d.ts +0 -75
  24. package/dist/vfs/EntityManager.js +0 -216
  25. package/dist/vfs/EventRecorder.d.ts +0 -84
  26. package/dist/vfs/EventRecorder.js +0 -269
  27. package/dist/vfs/GitBridge.d.ts +0 -167
  28. package/dist/vfs/GitBridge.js +0 -537
  29. package/dist/vfs/KnowledgeLayer.d.ts +0 -35
  30. package/dist/vfs/KnowledgeLayer.js +0 -443
  31. package/dist/vfs/PersistentEntitySystem.d.ts +0 -165
  32. package/dist/vfs/PersistentEntitySystem.js +0 -503
  33. package/dist/vfs/SemanticVersioning.d.ts +0 -105
  34. package/dist/vfs/SemanticVersioning.js +0 -309
@@ -1,31 +1,23 @@
1
1
  /**
2
- * VersionIndex - Fast Version Lookup Using Existing Index Infrastructure (v5.3.0)
2
+ * VersionIndex - Pure Key-Value Version Storage (v6.3.0)
3
3
  *
4
- * Integrates with Brainy's existing index system:
5
- * - Uses MetadataIndexManager for field indexing
6
- * - Leverages UnifiedCache for memory management
7
- * - Uses EntityIdMapper for efficient ID handling
8
- * - Uses ChunkManager for adaptive chunking
9
- * - Leverages Roaring Bitmaps for fast set operations
4
+ * Stores version metadata using simple key-value storage:
5
+ * - Version list per entity: __version_meta_{entityId}_{branch}
6
+ * - Content stored separately by VersionStorage
10
7
  *
11
- * Version metadata is stored as regular entities with type='_version'
12
- * This allows us to use existing index infrastructure without modification!
13
- *
14
- * Fields indexed:
15
- * - versionEntityId: Entity being versioned
16
- * - versionBranch: Branch version was created on
17
- * - versionNumber: Version number
18
- * - versionTag: Optional user tag
19
- * - versionTimestamp: Creation timestamp
20
- * - versionCommitHash: Commit hash
8
+ * Key Design Decisions:
9
+ * - Versions are NOT entities (no brain.add())
10
+ * - Versions do NOT pollute find() results
11
+ * - Simple O(1) lookups per entity
12
+ * - Versions per entity is typically small (10-1000)
21
13
  *
22
14
  * NO MOCKS - Production implementation
23
15
  */
24
16
  /**
25
- * VersionIndex - Version lookup and querying using existing indexes
17
+ * VersionIndex - Pure key-value version metadata storage
26
18
  *
27
- * Strategy: Store version metadata as special entities with type='_version'
28
- * This leverages ALL existing index infrastructure automatically!
19
+ * Uses simple JSON storage instead of creating entities.
20
+ * This ensures versions never appear in find() results.
29
21
  */
30
22
  export class VersionIndex {
31
23
  constructor(brain) {
@@ -34,8 +26,6 @@ export class VersionIndex {
34
26
  }
35
27
  /**
36
28
  * Initialize version index
37
- *
38
- * No special setup needed - we use existing entity storage and indexes!
39
29
  */
40
30
  async initialize() {
41
31
  if (this.initialized)
@@ -45,75 +35,85 @@ export class VersionIndex {
45
35
  /**
46
36
  * Add version to index
47
37
  *
48
- * Stores version metadata as a special entity with type='_version'
49
- * This automatically indexes it using existing MetadataIndexManager!
38
+ * Stores version entry in key-value storage.
39
+ * Handles deduplication by content hash.
50
40
  *
51
- * @param version Version metadata
41
+ * @param version Version metadata to store
52
42
  */
53
43
  async addVersion(version) {
54
44
  await this.initialize();
55
- // Generate unique ID for version entity
56
- const versionEntityId = this.getVersionEntityId(version.entityId, version.version, version.branch);
57
- // Store as special entity with type='state' (version is a snapshot/state)
58
- // This automatically gets indexed by MetadataIndexManager!
59
- await this.brain.saveNounMetadata(versionEntityId, {
60
- id: versionEntityId,
61
- type: 'state', // Use standard 'state' type (version = snapshot state)
62
- name: `Version ${version.version} of ${version.entityId}`,
63
- metadata: {
64
- // Flag to identify as version metadata
65
- _isVersion: true,
66
- // These fields are automatically indexed by MetadataIndexManager
67
- versionEntityId: version.entityId, // Entity being versioned
68
- versionBranch: version.branch, // Branch
69
- versionNumber: version.version, // Version number
70
- versionTag: version.tag, // Optional tag
71
- versionTimestamp: version.timestamp, // Timestamp (indexed with bucketing)
72
- versionCommitHash: version.commitHash, // Commit hash
73
- versionContentHash: version.contentHash, // Content hash
74
- versionAuthor: version.author, // Author
75
- versionDescription: version.description, // Description
76
- versionMetadata: version.metadata // Additional metadata
45
+ const key = this.getMetaKey(version.entityId, version.branch);
46
+ const store = await this.loadStore(key) || {
47
+ entityId: version.entityId,
48
+ branch: version.branch,
49
+ versions: []
50
+ };
51
+ // Check for duplicate content hash (deduplication)
52
+ const existing = store.versions.find(v => v.contentHash === version.contentHash);
53
+ if (existing) {
54
+ // Update tag/description if provided on duplicate save
55
+ let updated = false;
56
+ if (version.tag && version.tag !== existing.tag) {
57
+ existing.tag = version.tag;
58
+ updated = true;
59
+ }
60
+ if (version.description && version.description !== existing.description) {
61
+ existing.description = version.description;
62
+ updated = true;
77
63
  }
64
+ if (updated) {
65
+ await this.saveStore(key, store);
66
+ }
67
+ return;
68
+ }
69
+ // Add new version entry
70
+ store.versions.push({
71
+ version: version.version,
72
+ timestamp: version.timestamp,
73
+ contentHash: version.contentHash,
74
+ commitHash: version.commitHash,
75
+ tag: version.tag,
76
+ description: version.description,
77
+ author: version.author
78
78
  });
79
+ await this.saveStore(key, store);
79
80
  }
80
81
  /**
81
82
  * Get versions for an entity
82
83
  *
83
- * Uses existing MetadataIndexManager to query efficiently!
84
- *
85
- * @param query Version query
84
+ * @param query Version query with filters
86
85
  * @returns List of versions (newest first)
87
86
  */
88
87
  async getVersions(query) {
89
88
  await this.initialize();
90
- // Build metadata filter using existing query system
91
- const filters = {
92
- type: 'state',
93
- _isVersion: true,
94
- versionEntityId: query.entityId,
95
- versionBranch: query.branch
96
- };
97
- // Add optional filters
89
+ const branch = query.branch || this.brain.currentBranch;
90
+ const key = this.getMetaKey(query.entityId, branch);
91
+ const store = await this.loadStore(key);
92
+ if (!store)
93
+ return [];
94
+ // Convert entries to EntityVersion format
95
+ let versions = store.versions.map(entry => ({
96
+ version: entry.version,
97
+ entityId: store.entityId,
98
+ branch: store.branch,
99
+ timestamp: entry.timestamp,
100
+ contentHash: entry.contentHash,
101
+ commitHash: entry.commitHash || '',
102
+ tag: entry.tag,
103
+ description: entry.description,
104
+ author: entry.author
105
+ }));
106
+ // Apply filters
98
107
  if (query.tag) {
99
- filters.versionTag = query.tag;
108
+ versions = versions.filter(v => v.tag === query.tag);
100
109
  }
101
- // Query using existing search infrastructure
102
- const results = await this.brain.searchByMetadata(filters);
103
- // Convert entities back to EntityVersion format
104
- const versions = [];
105
- for (const entity of results) {
106
- const version = this.entityToVersion(entity);
107
- if (version) {
108
- // Filter by date range if specified
109
- if (query.startDate && version.timestamp < query.startDate)
110
- continue;
111
- if (query.endDate && version.timestamp > query.endDate)
112
- continue;
113
- versions.push(version);
114
- }
110
+ if (query.startDate) {
111
+ versions = versions.filter(v => v.timestamp >= query.startDate);
115
112
  }
116
- // Sort by version number (newest first)
113
+ if (query.endDate) {
114
+ versions = versions.filter(v => v.timestamp <= query.endDate);
115
+ }
116
+ // Sort newest first (highest version number first)
117
117
  versions.sort((a, b) => b.version - a.version);
118
118
  // Apply pagination
119
119
  const start = query.offset || 0;
@@ -129,12 +129,8 @@ export class VersionIndex {
129
129
  * @returns Version metadata or null
130
130
  */
131
131
  async getVersion(entityId, version, branch) {
132
- await this.initialize();
133
- const versionEntityId = this.getVersionEntityId(entityId, version, branch);
134
- const entity = await this.brain.getNounMetadata(versionEntityId);
135
- if (!entity)
136
- return null;
137
- return this.entityToVersion(entity);
132
+ const versions = await this.getVersions({ entityId, branch });
133
+ return versions.find(v => v.version === version) || null;
138
134
  }
139
135
  /**
140
136
  * Get version by tag
@@ -145,19 +141,8 @@ export class VersionIndex {
145
141
  * @returns Version metadata or null
146
142
  */
147
143
  async getVersionByTag(entityId, tag, branch) {
148
- await this.initialize();
149
- // Query using existing metadata index
150
- const results = await this.brain.searchByMetadata({
151
- type: 'state',
152
- _isVersion: true,
153
- versionEntityId: entityId,
154
- versionBranch: branch,
155
- versionTag: tag
156
- });
157
- if (results.length === 0)
158
- return null;
159
- // Return first match (tags should be unique per entity/branch)
160
- return this.entityToVersion(results[0]);
144
+ const versions = await this.getVersions({ entityId, branch, tag });
145
+ return versions[0] || null;
161
146
  }
162
147
  /**
163
148
  * Get version count for entity
@@ -167,15 +152,9 @@ export class VersionIndex {
167
152
  * @returns Number of versions
168
153
  */
169
154
  async getVersionCount(entityId, branch) {
170
- await this.initialize();
171
- // Use existing search infrastructure
172
- const results = await this.brain.searchByMetadata({
173
- type: 'state',
174
- _isVersion: true,
175
- versionEntityId: entityId,
176
- versionBranch: branch
177
- });
178
- return results.length;
155
+ const key = this.getMetaKey(entityId, branch);
156
+ const store = await this.loadStore(key);
157
+ return store?.versions.length || 0;
179
158
  }
180
159
  /**
181
160
  * Remove version from index
@@ -186,90 +165,86 @@ export class VersionIndex {
186
165
  */
187
166
  async removeVersion(entityId, version, branch) {
188
167
  await this.initialize();
189
- const versionEntityId = this.getVersionEntityId(entityId, version, branch);
190
- // Delete version entity (automatically removed from indexes)
191
- await this.brain.deleteNounMetadata(versionEntityId);
192
- }
193
- /**
194
- * Convert entity to EntityVersion format
195
- *
196
- * @param entity Entity from storage
197
- * @returns EntityVersion or null if invalid
198
- */
199
- entityToVersion(entity) {
200
- if (!entity || !entity.metadata)
201
- return null;
202
- const m = entity.metadata;
203
- if (!m.versionEntityId ||
204
- !m.versionBranch ||
205
- m.versionNumber === undefined ||
206
- !m.versionCommitHash ||
207
- !m.versionContentHash ||
208
- !m.versionTimestamp) {
209
- return null;
168
+ const key = this.getMetaKey(entityId, branch);
169
+ const store = await this.loadStore(key);
170
+ if (!store)
171
+ return;
172
+ const initialLength = store.versions.length;
173
+ store.versions = store.versions.filter(v => v.version !== version);
174
+ // Only save if something was removed
175
+ if (store.versions.length < initialLength) {
176
+ await this.saveStore(key, store);
210
177
  }
211
- return {
212
- version: m.versionNumber,
213
- entityId: m.versionEntityId,
214
- branch: m.versionBranch,
215
- commitHash: m.versionCommitHash,
216
- timestamp: m.versionTimestamp,
217
- contentHash: m.versionContentHash,
218
- tag: m.versionTag,
219
- description: m.versionDescription,
220
- author: m.versionAuthor,
221
- metadata: m.versionMetadata
222
- };
223
178
  }
224
179
  /**
225
- * Generate unique ID for version entity
226
- *
227
- * Format: _version:{entityId}:{version}:{branch}
180
+ * Clear all versions for an entity
228
181
  *
229
182
  * @param entityId Entity ID
230
- * @param version Version number
231
183
  * @param branch Branch name
232
- * @returns Version entity ID
184
+ * @returns Number of versions deleted
233
185
  */
234
- getVersionEntityId(entityId, version, branch) {
235
- return `_version:${entityId}:${version}:${branch}`;
186
+ async clearVersions(entityId, branch) {
187
+ const key = this.getMetaKey(entityId, branch);
188
+ const store = await this.loadStore(key);
189
+ if (!store)
190
+ return 0;
191
+ const count = store.versions.length;
192
+ // Delete the store by saving null/empty
193
+ await this.saveStore(key, { entityId, branch, versions: [] });
194
+ return count;
236
195
  }
237
196
  /**
238
197
  * Get all versioned entities (for cleanup/debugging)
239
198
  *
240
- * @returns List of entity IDs that have versions
199
+ * Note: This is an expensive operation that requires scanning.
200
+ * In the simple key-value approach, we don't maintain a global index.
201
+ * This method returns an empty array - use storage-level scanning if needed.
202
+ *
203
+ * @returns Empty array (not supported in simple approach)
241
204
  */
242
205
  async getVersionedEntities() {
243
- await this.initialize();
244
- // Query all version entities
245
- const results = await this.brain.searchByMetadata({
246
- type: 'state',
247
- _isVersion: true
248
- });
249
- // Extract unique entity IDs
250
- const entityIds = new Set();
251
- for (const entity of results) {
252
- const version = this.entityToVersion(entity);
253
- if (version) {
254
- entityIds.add(version.entityId);
255
- }
256
- }
257
- return Array.from(entityIds);
206
+ // In the simple key-value approach, we don't maintain a global index
207
+ // of all versioned entities. This would require scanning storage.
208
+ // For most use cases, you know which entities you've versioned.
209
+ return [];
258
210
  }
211
+ // ============= Private Helpers =============
259
212
  /**
260
- * Clear all versions for an entity
213
+ * Generate storage key for version metadata
261
214
  *
262
215
  * @param entityId Entity ID
263
216
  * @param branch Branch name
264
- * @returns Number of versions deleted
217
+ * @returns Storage key
265
218
  */
266
- async clearVersions(entityId, branch) {
267
- await this.initialize();
268
- const versions = await this.getVersions({ entityId, branch });
269
- for (const version of versions) {
270
- await this.removeVersion(entityId, version.version, branch);
219
+ getMetaKey(entityId, branch) {
220
+ return `__version_meta_${entityId}_${branch}`;
221
+ }
222
+ /**
223
+ * Load version store from storage
224
+ *
225
+ * @param key Storage key
226
+ * @returns Version store or null
227
+ */
228
+ async loadStore(key) {
229
+ try {
230
+ const store = await this.brain.storageAdapter.getMetadata(key);
231
+ // Handle empty store
232
+ if (!store || !store.versions)
233
+ return null;
234
+ return store;
271
235
  }
272
- return versions.length;
236
+ catch {
237
+ return null;
238
+ }
239
+ }
240
+ /**
241
+ * Save version store to storage
242
+ *
243
+ * @param key Storage key
244
+ * @param store Version store
245
+ */
246
+ async saveStore(key, store) {
247
+ await this.brain.storageAdapter.saveMetadata(key, store);
273
248
  }
274
249
  }
275
250
  //# sourceMappingURL=VersionIndex.js.map
@@ -1,20 +1,26 @@
1
1
  /**
2
- * VersionManager - Entity-Level Versioning Engine (v5.3.0)
2
+ * VersionManager - Entity-Level Versioning Engine (v5.3.0, v6.3.0 fix)
3
3
  *
4
4
  * Provides entity-level version control with:
5
5
  * - save() - Create entity version
6
- * - restore() - Restore entity to specific version
6
+ * - restore() - Restore entity to specific version (v6.3.0: now updates all indexes)
7
7
  * - list() - List all versions of an entity
8
8
  * - compare() - Deep diff between versions
9
9
  * - prune() - Remove old versions (retention policies)
10
10
  *
11
- * Architecture:
12
- * - Hybrid storage: COW commits for full snapshots + version index for fast queries
11
+ * Architecture (v6.3.0 - Clean Key-Value Storage):
12
+ * - Versions stored as key-value pairs, NOT as entities (no index pollution)
13
13
  * - Content-addressable: SHA-256 hashing for deduplication
14
- * - Space-efficient: Only stores changed data
14
+ * - Space-efficient: Only stores unique content
15
15
  * - Branch-aware: Versions tied to current branch
16
+ * - restore() uses brain.update() to refresh ALL indexes (HNSW, metadata, graph)
16
17
  *
17
- * NO MOCKS - Production implementation
18
+ * Storage keys:
19
+ * - Version metadata: __version_meta_{entityId}_{branch}
20
+ * - Version content: __system_version_{entityId}_{contentHash}
21
+ *
22
+ * ZERO-CONFIG - Works automatically with existing storage infrastructure.
23
+ * NO MOCKS - Production implementation.
18
24
  */
19
25
  import { VersionDiff } from './VersionDiff.js';
20
26
  export interface EntityVersion {
@@ -93,6 +99,20 @@ export declare class VersionManager {
93
99
  private versionIndex;
94
100
  private initialized;
95
101
  constructor(brain: any);
102
+ /**
103
+ * Check if an entity is a VFS file
104
+ * VFS files store content in BlobStorage, not in entity.data
105
+ *
106
+ * @param entity Entity metadata object
107
+ * @returns True if entity is a VFS file
108
+ */
109
+ private isVFSFile;
110
+ /**
111
+ * Check if content is text-based for encoding decisions
112
+ * @param mimeType MIME type of the content
113
+ * @returns True if content should be stored as UTF-8 string
114
+ */
115
+ private isTextContent;
96
116
  /**
97
117
  * Initialize versioning system (lazy)
98
118
  */
@@ -1,20 +1,26 @@
1
1
  /**
2
- * VersionManager - Entity-Level Versioning Engine (v5.3.0)
2
+ * VersionManager - Entity-Level Versioning Engine (v5.3.0, v6.3.0 fix)
3
3
  *
4
4
  * Provides entity-level version control with:
5
5
  * - save() - Create entity version
6
- * - restore() - Restore entity to specific version
6
+ * - restore() - Restore entity to specific version (v6.3.0: now updates all indexes)
7
7
  * - list() - List all versions of an entity
8
8
  * - compare() - Deep diff between versions
9
9
  * - prune() - Remove old versions (retention policies)
10
10
  *
11
- * Architecture:
12
- * - Hybrid storage: COW commits for full snapshots + version index for fast queries
11
+ * Architecture (v6.3.0 - Clean Key-Value Storage):
12
+ * - Versions stored as key-value pairs, NOT as entities (no index pollution)
13
13
  * - Content-addressable: SHA-256 hashing for deduplication
14
- * - Space-efficient: Only stores changed data
14
+ * - Space-efficient: Only stores unique content
15
15
  * - Branch-aware: Versions tied to current branch
16
+ * - restore() uses brain.update() to refresh ALL indexes (HNSW, metadata, graph)
16
17
  *
17
- * NO MOCKS - Production implementation
18
+ * Storage keys:
19
+ * - Version metadata: __version_meta_{entityId}_{branch}
20
+ * - Version content: __system_version_{entityId}_{contentHash}
21
+ *
22
+ * ZERO-CONFIG - Works automatically with existing storage infrastructure.
23
+ * NO MOCKS - Production implementation.
18
24
  */
19
25
  import { VersionStorage } from './VersionStorage.js';
20
26
  import { VersionIndex } from './VersionIndex.js';
@@ -29,6 +35,34 @@ export class VersionManager {
29
35
  this.versionStorage = new VersionStorage(brain);
30
36
  this.versionIndex = new VersionIndex(brain);
31
37
  }
38
+ /**
39
+ * Check if an entity is a VFS file
40
+ * VFS files store content in BlobStorage, not in entity.data
41
+ *
42
+ * @param entity Entity metadata object
43
+ * @returns True if entity is a VFS file
44
+ */
45
+ isVFSFile(entity) {
46
+ return (entity?.isVFS === true &&
47
+ entity?.vfsType === 'file' &&
48
+ typeof entity?.path === 'string');
49
+ }
50
+ /**
51
+ * Check if content is text-based for encoding decisions
52
+ * @param mimeType MIME type of the content
53
+ * @returns True if content should be stored as UTF-8 string
54
+ */
55
+ isTextContent(mimeType) {
56
+ if (!mimeType)
57
+ return false;
58
+ return (mimeType.startsWith('text/') ||
59
+ mimeType === 'application/json' ||
60
+ mimeType === 'application/javascript' ||
61
+ mimeType === 'application/typescript' ||
62
+ mimeType === 'application/xml' ||
63
+ mimeType.includes('+xml') ||
64
+ mimeType.includes('+json'));
65
+ }
32
66
  /**
33
67
  * Initialize versioning system (lazy)
34
68
  */
@@ -56,6 +90,27 @@ export class VersionManager {
56
90
  if (!entity) {
57
91
  throw new Error(`Entity ${entityId} not found`);
58
92
  }
93
+ // v6.3.2 FIX: For VFS file entities, fetch current content from blob storage
94
+ // The entity.data field contains stale embedding text, not actual file content
95
+ // VFS files store their real content in BlobStorage (content-addressable)
96
+ if (this.isVFSFile(entity)) {
97
+ if (!this.brain.vfs) {
98
+ throw new Error(`Cannot version VFS file ${entityId}: VFS not initialized. ` +
99
+ `Ensure brain.vfs is available before versioning VFS files.`);
100
+ }
101
+ // Read fresh content from blob storage via VFS
102
+ const freshContent = await this.brain.vfs.readFile(entity.path);
103
+ // Store content with appropriate encoding
104
+ // Text files as UTF-8 string (readable, smaller)
105
+ // Binary files as base64 (safe for JSON serialization)
106
+ if (this.isTextContent(entity.mimeType)) {
107
+ entity.data = freshContent.toString('utf-8');
108
+ }
109
+ else {
110
+ entity.data = freshContent.toString('base64');
111
+ entity._vfsEncoding = 'base64'; // Flag for restore to decode
112
+ }
113
+ }
59
114
  // Get current branch
60
115
  const currentBranch = this.brain.currentBranch;
61
116
  // Get next version number
@@ -201,8 +256,46 @@ export class VersionManager {
201
256
  if (!versionedEntity) {
202
257
  throw new Error(`Version data not found for entity ${entityId} version ${version}`);
203
258
  }
204
- // Restore entity in storage
205
- await this.brain.saveNounMetadata(entityId, versionedEntity);
259
+ // v6.3.2 FIX: For VFS file entities, write content back to blob storage
260
+ // The versioned data contains the actual file content (not stale embedding text)
261
+ // Using vfs.writeFile() ensures proper blob creation and metadata update
262
+ if (this.isVFSFile(versionedEntity)) {
263
+ if (!this.brain.vfs) {
264
+ throw new Error(`Cannot restore VFS file ${entityId}: VFS not initialized. ` +
265
+ `Ensure brain.vfs is available before restoring VFS files.`);
266
+ }
267
+ // Decode content based on how it was stored
268
+ let content;
269
+ if (versionedEntity._vfsEncoding === 'base64') {
270
+ // Binary file stored as base64
271
+ content = Buffer.from(versionedEntity.data, 'base64');
272
+ }
273
+ else {
274
+ // Text file stored as UTF-8 string
275
+ content = Buffer.from(versionedEntity.data, 'utf-8');
276
+ }
277
+ // Write content back to VFS - this handles:
278
+ // - BlobStorage write (new hash)
279
+ // - Entity metadata update
280
+ // - Path resolver cache update
281
+ await this.brain.vfs.writeFile(versionedEntity.path, content);
282
+ return targetVersion;
283
+ }
284
+ // For non-VFS entities, use existing brain.update() logic
285
+ // Extract standard fields vs custom metadata
286
+ // NounMetadata has: noun, data, createdAt, updatedAt, createdBy, service, confidence, weight
287
+ const { noun, data, createdAt, updatedAt, createdBy, service, confidence, weight, ...customMetadata } = versionedEntity;
288
+ // Use brain.update() to restore - this updates ALL indexes (HNSW, metadata, graph)
289
+ // This is critical: saveNounMetadata() only saves to storage without updating indexes
290
+ await this.brain.update({
291
+ id: entityId,
292
+ data: data,
293
+ type: noun,
294
+ metadata: customMetadata,
295
+ confidence: confidence,
296
+ weight: weight,
297
+ merge: false // Replace entirely, don't merge with existing metadata
298
+ });
206
299
  return targetVersion;
207
300
  }
208
301
  /**