@soulcraft/brainy 6.1.0 → 6.2.1

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 (40) hide show
  1. package/CHANGELOG.md +271 -0
  2. package/dist/augmentations/KnowledgeAugmentation.d.ts +40 -0
  3. package/dist/augmentations/KnowledgeAugmentation.js +251 -0
  4. package/dist/brainy.d.ts +17 -13
  5. package/dist/brainy.js +172 -41
  6. package/dist/coreTypes.d.ts +12 -0
  7. package/dist/graph/graphAdjacencyIndex.d.ts +23 -0
  8. package/dist/graph/graphAdjacencyIndex.js +49 -0
  9. package/dist/importManager.d.ts +78 -0
  10. package/dist/importManager.js +267 -0
  11. package/dist/query/typeInference.d.ts +158 -0
  12. package/dist/query/typeInference.js +760 -0
  13. package/dist/storage/adapters/typeAwareStorageAdapter.d.ts +252 -0
  14. package/dist/storage/adapters/typeAwareStorageAdapter.js +814 -0
  15. package/dist/storage/baseStorage.d.ts +36 -0
  16. package/dist/storage/baseStorage.js +159 -4
  17. package/dist/storage/cow/binaryDataCodec.d.ts +13 -2
  18. package/dist/storage/cow/binaryDataCodec.js +15 -2
  19. package/dist/types/brainy.types.d.ts +1 -0
  20. package/dist/types/brainyDataInterface.d.ts +52 -0
  21. package/dist/types/brainyDataInterface.js +10 -0
  22. package/dist/utils/metadataIndex.d.ts +17 -0
  23. package/dist/utils/metadataIndex.js +63 -0
  24. package/dist/vfs/ConceptSystem.d.ts +203 -0
  25. package/dist/vfs/ConceptSystem.js +545 -0
  26. package/dist/vfs/EntityManager.d.ts +75 -0
  27. package/dist/vfs/EntityManager.js +216 -0
  28. package/dist/vfs/EventRecorder.d.ts +84 -0
  29. package/dist/vfs/EventRecorder.js +269 -0
  30. package/dist/vfs/GitBridge.d.ts +167 -0
  31. package/dist/vfs/GitBridge.js +537 -0
  32. package/dist/vfs/KnowledgeLayer.d.ts +35 -0
  33. package/dist/vfs/KnowledgeLayer.js +443 -0
  34. package/dist/vfs/PersistentEntitySystem.d.ts +165 -0
  35. package/dist/vfs/PersistentEntitySystem.js +503 -0
  36. package/dist/vfs/SemanticVersioning.d.ts +105 -0
  37. package/dist/vfs/SemanticVersioning.js +309 -0
  38. package/dist/vfs/VirtualFileSystem.d.ts +37 -2
  39. package/dist/vfs/VirtualFileSystem.js +105 -68
  40. package/package.json +1 -1
@@ -0,0 +1,216 @@
1
+ /**
2
+ * EntityManager Base Class
3
+ *
4
+ * Provides standardized entity ID management for all Knowledge Layer components
5
+ * Solves the root cause of ID mismatch issues by establishing clear patterns
6
+ */
7
+ import { v4 as uuidv4 } from '../universal/uuid.js';
8
+ /**
9
+ * EntityManager Base Class
10
+ *
11
+ * All Knowledge Layer components should extend this to get standardized:
12
+ * - Entity storage and retrieval
13
+ * - ID management and mapping
14
+ * - Query patterns
15
+ * - Relationship creation
16
+ */
17
+ export class EntityManager {
18
+ constructor(brain, systemName) {
19
+ this.brain = brain;
20
+ this.systemName = systemName;
21
+ this.idMappings = new Map(); // domainId -> brainyId
22
+ this.brainyToMappings = new Map(); // brainyId -> domainId
23
+ }
24
+ /**
25
+ * Store an entity with proper ID management
26
+ */
27
+ async storeEntity(entity, nounType, embedding, data) {
28
+ // Generate domain ID if not provided
29
+ if (!entity.id) {
30
+ entity.id = uuidv4();
31
+ }
32
+ // Store in Brainy and get the Brainy entity ID
33
+ const brainyId = await this.brain.add({
34
+ type: nounType,
35
+ data: data || Buffer.from(JSON.stringify(entity)),
36
+ metadata: {
37
+ ...entity,
38
+ system: this.systemName,
39
+ domainId: entity.id, // Store the domain ID for reference
40
+ storedAt: Date.now()
41
+ },
42
+ vector: embedding
43
+ });
44
+ // Store ID mapping
45
+ this.idMappings.set(entity.id, brainyId);
46
+ this.brainyToMappings.set(brainyId, entity.id);
47
+ // Update entity with Brainy ID
48
+ entity.brainyId = brainyId;
49
+ return entity.id; // Return domain ID for external use
50
+ }
51
+ /**
52
+ * Update an existing entity
53
+ */
54
+ async updateEntity(entity, embedding) {
55
+ const brainyId = this.idMappings.get(entity.id);
56
+ if (!brainyId) {
57
+ throw new Error(`Entity ${entity.id} not found in mappings`);
58
+ }
59
+ await this.brain.update({
60
+ id: brainyId,
61
+ data: Buffer.from(JSON.stringify(entity)),
62
+ metadata: {
63
+ ...entity,
64
+ system: this.systemName,
65
+ domainId: entity.id,
66
+ updatedAt: Date.now()
67
+ },
68
+ vector: embedding
69
+ });
70
+ }
71
+ /**
72
+ * Retrieve entity by domain ID
73
+ */
74
+ async getEntity(domainId) {
75
+ // First try from cache
76
+ let brainyId = this.idMappings.get(domainId);
77
+ // If not in cache, search by domain ID in metadata
78
+ if (!brainyId) {
79
+ const results = await this.brain.find({
80
+ where: {
81
+ domainId,
82
+ system: this.systemName
83
+ },
84
+ limit: 1
85
+ });
86
+ if (results.length === 0) {
87
+ return null;
88
+ }
89
+ brainyId = results[0].entity.id;
90
+ // Cache the mapping
91
+ this.idMappings.set(domainId, brainyId);
92
+ this.brainyToMappings.set(brainyId, domainId);
93
+ }
94
+ // Get entity using Brainy ID
95
+ const brainyEntity = await this.brain.get(brainyId);
96
+ if (!brainyEntity) {
97
+ return null;
98
+ }
99
+ // Parse entity from metadata
100
+ const entity = brainyEntity.metadata;
101
+ entity.brainyId = brainyId;
102
+ return entity;
103
+ }
104
+ /**
105
+ * Find entities by metadata criteria
106
+ */
107
+ async findEntities(criteria, nounType, limit = 100) {
108
+ const query = {
109
+ ...criteria,
110
+ system: this.systemName
111
+ };
112
+ const results = await this.brain.find({
113
+ where: query,
114
+ type: nounType,
115
+ limit
116
+ });
117
+ const entities = [];
118
+ for (const result of results) {
119
+ const entity = result.entity.metadata;
120
+ entity.brainyId = result.entity.id;
121
+ // Update mappings cache
122
+ this.idMappings.set(entity.id, result.entity.id);
123
+ this.brainyToMappings.set(result.entity.id, entity.id);
124
+ entities.push(entity);
125
+ }
126
+ return entities;
127
+ }
128
+ /**
129
+ * Create relationship between entities using proper Brainy IDs
130
+ */
131
+ async createRelationship(fromDomainId, toDomainId, relationshipType, metadata) {
132
+ // Get Brainy IDs for both entities
133
+ const fromBrainyId = await this.getBrainyId(fromDomainId);
134
+ const toBrainyId = await this.getBrainyId(toDomainId);
135
+ if (!fromBrainyId || !toBrainyId) {
136
+ throw new Error(`Cannot find Brainy IDs for relationship: ${fromDomainId} -> ${toDomainId}`);
137
+ }
138
+ // Create relationship using Brainy IDs
139
+ await this.brain.relate({
140
+ from: fromBrainyId,
141
+ to: toBrainyId,
142
+ type: relationshipType,
143
+ metadata
144
+ });
145
+ }
146
+ /**
147
+ * Get Brainy ID for a domain ID
148
+ */
149
+ async getBrainyId(domainId) {
150
+ // Check cache first
151
+ let brainyId = this.idMappings.get(domainId);
152
+ if (!brainyId) {
153
+ // Search in Brainy
154
+ const results = await this.brain.find({
155
+ where: {
156
+ domainId,
157
+ system: this.systemName
158
+ },
159
+ limit: 1
160
+ });
161
+ if (results.length > 0) {
162
+ brainyId = results[0].entity.id;
163
+ // Cache the mapping
164
+ this.idMappings.set(domainId, brainyId);
165
+ this.brainyToMappings.set(brainyId, domainId);
166
+ }
167
+ }
168
+ return brainyId || null;
169
+ }
170
+ /**
171
+ * Get domain ID for a Brainy ID
172
+ */
173
+ getDomainId(brainyId) {
174
+ return this.brainyToMappings.get(brainyId) || null;
175
+ }
176
+ /**
177
+ * Delete entity by domain ID
178
+ */
179
+ async deleteEntity(domainId) {
180
+ const brainyId = await this.getBrainyId(domainId);
181
+ if (brainyId) {
182
+ await this.brain.delete(brainyId);
183
+ // Clean up mappings
184
+ this.idMappings.delete(domainId);
185
+ this.brainyToMappings.delete(brainyId);
186
+ }
187
+ }
188
+ /**
189
+ * Clear ID mapping cache (useful for tests)
190
+ */
191
+ clearMappingCache() {
192
+ this.idMappings.clear();
193
+ this.brainyToMappings.clear();
194
+ }
195
+ /**
196
+ * Batch load mappings for performance
197
+ */
198
+ async loadMappings(domainIds) {
199
+ const unknownIds = domainIds.filter(id => !this.idMappings.has(id));
200
+ if (unknownIds.length === 0)
201
+ return;
202
+ const results = await this.brain.find({
203
+ where: {
204
+ domainId: { $in: unknownIds },
205
+ system: this.systemName
206
+ },
207
+ limit: unknownIds.length
208
+ });
209
+ for (const result of results) {
210
+ const domainId = result.entity.metadata.domainId;
211
+ this.idMappings.set(domainId, result.entity.id);
212
+ this.brainyToMappings.set(result.entity.id, domainId);
213
+ }
214
+ }
215
+ }
216
+ //# sourceMappingURL=EntityManager.js.map
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Event Recording System for VFS
3
+ *
4
+ * Records every file operation as an event for complete history tracking
5
+ * PRODUCTION-READY: No mocks, real implementation
6
+ */
7
+ import { Brainy } from '../brainy.js';
8
+ import { EntityManager, ManagedEntity } from './EntityManager.js';
9
+ /**
10
+ * File operation event
11
+ */
12
+ export interface FileEvent extends ManagedEntity {
13
+ id: string;
14
+ type: 'create' | 'write' | 'append' | 'delete' | 'move' | 'rename' | 'mkdir' | 'rmdir';
15
+ path: string;
16
+ timestamp: number;
17
+ content?: Buffer;
18
+ size?: number;
19
+ hash?: string;
20
+ author?: string;
21
+ metadata?: Record<string, any>;
22
+ previousHash?: string;
23
+ oldPath?: string;
24
+ eventType?: string;
25
+ }
26
+ /**
27
+ * Event Recorder - Stores all file operations as searchable events
28
+ */
29
+ export declare class EventRecorder extends EntityManager {
30
+ constructor(brain: Brainy);
31
+ /**
32
+ * Record a file operation event
33
+ */
34
+ recordEvent(event: Omit<FileEvent, 'id' | 'timestamp'>): Promise<string>;
35
+ /**
36
+ * Get all events matching criteria
37
+ */
38
+ getEvents(options?: {
39
+ since?: number;
40
+ until?: number;
41
+ types?: string[];
42
+ limit?: number;
43
+ }): Promise<FileEvent[]>;
44
+ /**
45
+ * Get complete history for a file
46
+ */
47
+ getHistory(path: string, options?: {
48
+ limit?: number;
49
+ since?: number;
50
+ until?: number;
51
+ types?: FileEvent['type'][];
52
+ }): Promise<FileEvent[]>;
53
+ /**
54
+ * Replay events to reconstruct file state at a specific time
55
+ */
56
+ reconstructFileAtTime(path: string, timestamp: number): Promise<Buffer | null>;
57
+ /**
58
+ * Get file changes between two timestamps
59
+ */
60
+ getChanges(since: number, until?: number): Promise<FileEvent[]>;
61
+ /**
62
+ * Calculate statistics for a file or directory
63
+ */
64
+ getStatistics(path: string): Promise<{
65
+ totalEvents: number;
66
+ firstEvent: number | null;
67
+ lastEvent: number | null;
68
+ totalWrites: number;
69
+ totalBytes: number;
70
+ authors: string[];
71
+ }>;
72
+ /**
73
+ * Find files that changed together (temporal coupling)
74
+ */
75
+ findTemporalCoupling(path: string, windowMs?: number): Promise<Map<string, number>>;
76
+ /**
77
+ * Generate embedding for an event (for semantic search)
78
+ */
79
+ private generateEventEmbedding;
80
+ /**
81
+ * Prune old events (for storage management)
82
+ */
83
+ pruneEvents(olderThan: number, keepEvery?: number): Promise<number>;
84
+ }
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Event Recording System for VFS
3
+ *
4
+ * Records every file operation as an event for complete history tracking
5
+ * PRODUCTION-READY: No mocks, real implementation
6
+ */
7
+ import { NounType } from '../types/graphTypes.js';
8
+ import { v4 as uuidv4 } from '../universal/uuid.js';
9
+ import { createHash } from 'crypto';
10
+ import { EntityManager } from './EntityManager.js';
11
+ /**
12
+ * Event Recorder - Stores all file operations as searchable events
13
+ */
14
+ export class EventRecorder extends EntityManager {
15
+ constructor(brain) {
16
+ super(brain, 'vfs');
17
+ }
18
+ /**
19
+ * Record a file operation event
20
+ */
21
+ async recordEvent(event) {
22
+ const eventId = uuidv4();
23
+ const timestamp = Date.now();
24
+ // Calculate content hash if content provided
25
+ const hash = event.content
26
+ ? createHash('sha256').update(event.content).digest('hex')
27
+ : undefined;
28
+ // Create file event
29
+ const fileEvent = {
30
+ id: eventId,
31
+ type: event.type,
32
+ path: event.path,
33
+ timestamp,
34
+ content: event.content,
35
+ size: event.size,
36
+ hash,
37
+ author: event.author,
38
+ metadata: event.metadata,
39
+ previousHash: event.previousHash,
40
+ oldPath: event.oldPath
41
+ };
42
+ // Generate embedding for content-based events
43
+ const embedding = event.content && event.content.length < 100000
44
+ ? await this.generateEventEmbedding(event)
45
+ : undefined;
46
+ // Add eventType for event classification
47
+ const eventWithType = {
48
+ ...fileEvent,
49
+ eventType: 'file-operation'
50
+ };
51
+ // Store event using EntityManager
52
+ await this.storeEntity(eventWithType, NounType.Event, embedding, event.content || Buffer.from(JSON.stringify(event)));
53
+ return eventId;
54
+ }
55
+ /**
56
+ * Get all events matching criteria
57
+ */
58
+ async getEvents(options) {
59
+ const limit = options?.limit || 100;
60
+ const since = options?.since || 0;
61
+ const until = options?.until || Date.now();
62
+ const query = {
63
+ eventType: 'file-operation'
64
+ };
65
+ // Add time filters
66
+ if (since || until) {
67
+ query.timestamp = {};
68
+ if (since)
69
+ query.timestamp.$gte = since;
70
+ if (until)
71
+ query.timestamp.$lte = until;
72
+ }
73
+ // Add type filter
74
+ if (options?.types && options.types.length > 0) {
75
+ query.type = { $in: options.types };
76
+ }
77
+ // Query using EntityManager
78
+ const events = await this.findEntities(query, NounType.Event, limit);
79
+ return events.sort((a, b) => b.timestamp - a.timestamp);
80
+ }
81
+ /**
82
+ * Get complete history for a file
83
+ */
84
+ async getHistory(path, options) {
85
+ const query = {
86
+ path,
87
+ eventType: 'file-operation'
88
+ };
89
+ // Add time filters
90
+ if (options?.since || options?.until) {
91
+ query.timestamp = {};
92
+ if (options.since)
93
+ query.timestamp.$gte = options.since;
94
+ if (options.until)
95
+ query.timestamp.$lte = options.until;
96
+ }
97
+ // Add type filter
98
+ if (options?.types && options.types.length > 0) {
99
+ query.type = { $in: options.types };
100
+ }
101
+ // Query using EntityManager
102
+ const events = await this.findEntities(query, NounType.Event, options?.limit || 100);
103
+ // Sort by timestamp (newest first)
104
+ return events.sort((a, b) => b.timestamp - a.timestamp);
105
+ }
106
+ /**
107
+ * Replay events to reconstruct file state at a specific time
108
+ */
109
+ async reconstructFileAtTime(path, timestamp) {
110
+ // Get all events up to the specified time
111
+ const events = await this.getHistory(path, {
112
+ until: timestamp,
113
+ types: ['create', 'write', 'append', 'delete']
114
+ });
115
+ // Sort chronologically for replay
116
+ const chronological = events.reverse();
117
+ // Find last write or create event
118
+ let lastContent = null;
119
+ let deleted = false;
120
+ for (const event of chronological) {
121
+ switch (event.type) {
122
+ case 'create':
123
+ case 'write':
124
+ lastContent = event.content || null;
125
+ deleted = false;
126
+ break;
127
+ case 'append':
128
+ if (lastContent && event.content) {
129
+ lastContent = Buffer.concat([lastContent, event.content]);
130
+ }
131
+ break;
132
+ case 'delete':
133
+ deleted = true;
134
+ lastContent = null;
135
+ break;
136
+ }
137
+ }
138
+ return deleted ? null : lastContent;
139
+ }
140
+ /**
141
+ * Get file changes between two timestamps
142
+ */
143
+ async getChanges(since, until) {
144
+ const query = {
145
+ eventType: 'file-operation',
146
+ timestamp: { $gte: since }
147
+ };
148
+ if (until) {
149
+ query.timestamp.$lte = until;
150
+ }
151
+ // Query using EntityManager
152
+ const events = await this.findEntities(query, NounType.Event, 1000);
153
+ return events.map(event => ({
154
+ id: event.id,
155
+ type: event.type,
156
+ path: event.path,
157
+ timestamp: event.timestamp,
158
+ size: event.size,
159
+ hash: event.hash,
160
+ author: event.author
161
+ }));
162
+ }
163
+ /**
164
+ * Calculate statistics for a file or directory
165
+ */
166
+ async getStatistics(path) {
167
+ const events = await this.getHistory(path, { limit: 10000 });
168
+ if (events.length === 0) {
169
+ return {
170
+ totalEvents: 0,
171
+ firstEvent: null,
172
+ lastEvent: null,
173
+ totalWrites: 0,
174
+ totalBytes: 0,
175
+ authors: []
176
+ };
177
+ }
178
+ const stats = {
179
+ totalEvents: events.length,
180
+ firstEvent: events[events.length - 1].timestamp, // Oldest
181
+ lastEvent: events[0].timestamp, // Newest
182
+ totalWrites: 0,
183
+ totalBytes: 0,
184
+ authors: new Set()
185
+ };
186
+ for (const event of events) {
187
+ if (event.type === 'write' || event.type === 'append') {
188
+ stats.totalWrites++;
189
+ stats.totalBytes += event.size || 0;
190
+ }
191
+ if (event.author) {
192
+ stats.authors.add(event.author);
193
+ }
194
+ }
195
+ return {
196
+ ...stats,
197
+ authors: Array.from(stats.authors)
198
+ };
199
+ }
200
+ /**
201
+ * Find files that changed together (temporal coupling)
202
+ */
203
+ async findTemporalCoupling(path, windowMs = 60000) {
204
+ const events = await this.getHistory(path);
205
+ const coupling = new Map();
206
+ for (const event of events) {
207
+ // Find other files changed within the time window
208
+ const related = await this.getChanges(event.timestamp - windowMs, event.timestamp + windowMs);
209
+ for (const relatedEvent of related) {
210
+ if (relatedEvent.path !== path) {
211
+ const count = coupling.get(relatedEvent.path) || 0;
212
+ coupling.set(relatedEvent.path, count + 1);
213
+ }
214
+ }
215
+ }
216
+ // Sort by coupling strength
217
+ return new Map(Array.from(coupling.entries())
218
+ .sort((a, b) => b[1] - a[1]));
219
+ }
220
+ /**
221
+ * Generate embedding for an event (for semantic search)
222
+ */
223
+ async generateEventEmbedding(event) {
224
+ try {
225
+ // Generate embedding based on event type and content
226
+ let textToEmbed;
227
+ if (event.type === 'write' || event.type === 'create') {
228
+ // For write/create events with content, embed the content
229
+ if (event.content && event.content.length < 100000) {
230
+ // Convert Buffer to string for text files
231
+ textToEmbed = Buffer.isBuffer(event.content)
232
+ ? event.content.toString('utf8', 0, Math.min(10240, event.content.length))
233
+ : String(event.content).slice(0, 10240);
234
+ }
235
+ else {
236
+ // For large files or no content, create descriptive text
237
+ textToEmbed = `File ${event.type} event at ${event.path}, size: ${event.size || 0} bytes`;
238
+ }
239
+ }
240
+ else {
241
+ // For other events (read, delete, rename, etc), create descriptive text
242
+ textToEmbed = `File ${event.type} event at ${event.path}${event.oldPath ? ` from ${event.oldPath}` : ''}`;
243
+ }
244
+ // Use Brainy's embed function
245
+ const vector = await this.brain.embed(textToEmbed);
246
+ return vector;
247
+ }
248
+ catch (error) {
249
+ console.error('Failed to generate event embedding:', error);
250
+ return undefined;
251
+ }
252
+ }
253
+ /**
254
+ * Prune old events (for storage management)
255
+ */
256
+ async pruneEvents(olderThan, keepEvery = 10) {
257
+ const events = await this.getChanges(0, Date.now() - olderThan);
258
+ let pruned = 0;
259
+ for (let i = 0; i < events.length; i++) {
260
+ // Keep every Nth event for history sampling
261
+ if (i % keepEvery !== 0) {
262
+ await this.deleteEntity(events[i].id);
263
+ pruned++;
264
+ }
265
+ }
266
+ return pruned;
267
+ }
268
+ }
269
+ //# sourceMappingURL=EventRecorder.js.map