@soulcraft/brainy 3.9.0 → 3.10.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.
Files changed (43) hide show
  1. package/README.md +89 -33
  2. package/dist/augmentations/KnowledgeAugmentation.d.ts +40 -0
  3. package/dist/augmentations/KnowledgeAugmentation.js +251 -0
  4. package/dist/augmentations/defaultAugmentations.d.ts +1 -0
  5. package/dist/augmentations/defaultAugmentations.js +5 -0
  6. package/dist/brainy.d.ts +11 -0
  7. package/dist/brainy.js +87 -1
  8. package/dist/embeddings/EmbeddingManager.js +14 -2
  9. package/dist/utils/mutex.d.ts +2 -0
  10. package/dist/utils/mutex.js +14 -3
  11. package/dist/vfs/ConceptSystem.d.ts +202 -0
  12. package/dist/vfs/ConceptSystem.js +598 -0
  13. package/dist/vfs/EntityManager.d.ts +75 -0
  14. package/dist/vfs/EntityManager.js +216 -0
  15. package/dist/vfs/EventRecorder.d.ts +83 -0
  16. package/dist/vfs/EventRecorder.js +292 -0
  17. package/dist/vfs/FSCompat.d.ts +85 -0
  18. package/dist/vfs/FSCompat.js +257 -0
  19. package/dist/vfs/GitBridge.d.ts +167 -0
  20. package/dist/vfs/GitBridge.js +537 -0
  21. package/dist/vfs/KnowledgeAugmentation.d.ts +104 -0
  22. package/dist/vfs/KnowledgeAugmentation.js +146 -0
  23. package/dist/vfs/KnowledgeLayer.d.ts +35 -0
  24. package/dist/vfs/KnowledgeLayer.js +443 -0
  25. package/dist/vfs/PathResolver.d.ts +96 -0
  26. package/dist/vfs/PathResolver.js +362 -0
  27. package/dist/vfs/PersistentEntitySystem.d.ts +163 -0
  28. package/dist/vfs/PersistentEntitySystem.js +525 -0
  29. package/dist/vfs/SemanticVersioning.d.ts +105 -0
  30. package/dist/vfs/SemanticVersioning.js +318 -0
  31. package/dist/vfs/VirtualFileSystem.d.ts +246 -0
  32. package/dist/vfs/VirtualFileSystem.js +1927 -0
  33. package/dist/vfs/importers/DirectoryImporter.d.ts +86 -0
  34. package/dist/vfs/importers/DirectoryImporter.js +298 -0
  35. package/dist/vfs/index.d.ts +19 -0
  36. package/dist/vfs/index.js +26 -0
  37. package/dist/vfs/streams/VFSReadStream.d.ts +19 -0
  38. package/dist/vfs/streams/VFSReadStream.js +54 -0
  39. package/dist/vfs/streams/VFSWriteStream.d.ts +21 -0
  40. package/dist/vfs/streams/VFSWriteStream.js +70 -0
  41. package/dist/vfs/types.d.ts +330 -0
  42. package/dist/vfs/types.js +46 -0
  43. package/package.json +1 -1
@@ -0,0 +1,525 @@
1
+ /**
2
+ * Persistent Entity System for VFS
3
+ *
4
+ * Manages entities that evolve across files and time
5
+ * Not just story characters - any evolving entity: APIs, customers, services, models
6
+ * PRODUCTION-READY: Real implementation using Brainy
7
+ */
8
+ import { NounType, VerbType } from '../types/graphTypes.js';
9
+ import { cosineDistance } from '../utils/distance.js';
10
+ import { v4 as uuidv4 } from '../universal/uuid.js';
11
+ /**
12
+ * Persistent Entity System
13
+ *
14
+ * Tracks entities that exist across multiple files and evolve over time
15
+ * Examples:
16
+ * - Story characters that appear in multiple chapters
17
+ * - API endpoints that evolve across documentation
18
+ * - Business entities that appear in multiple reports
19
+ * - Code classes/functions that span multiple files
20
+ */
21
+ export class PersistentEntitySystem {
22
+ constructor(brain, config) {
23
+ this.brain = brain;
24
+ this.entityCache = new Map();
25
+ this.config = {
26
+ autoExtract: config?.autoExtract ?? false,
27
+ similarityThreshold: config?.similarityThreshold ?? 0.8,
28
+ maxAppearances: config?.maxAppearances ?? 100,
29
+ evolutionTracking: config?.evolutionTracking ?? true
30
+ };
31
+ }
32
+ /**
33
+ * Create a new persistent entity
34
+ */
35
+ async createEntity(entity) {
36
+ const entityId = uuidv4();
37
+ const timestamp = Date.now();
38
+ const persistentEntity = {
39
+ ...entity,
40
+ id: entityId,
41
+ created: timestamp,
42
+ lastUpdated: timestamp,
43
+ version: 1,
44
+ appearances: []
45
+ };
46
+ // Generate embedding for entity description
47
+ let embedding;
48
+ try {
49
+ if (entity.description) {
50
+ embedding = await this.generateEntityEmbedding(persistentEntity);
51
+ }
52
+ }
53
+ catch (error) {
54
+ console.warn('Failed to generate entity embedding:', error);
55
+ }
56
+ // Store entity in Brainy
57
+ const brainyEntity = await this.brain.add({
58
+ type: NounType.Concept,
59
+ data: Buffer.from(JSON.stringify(persistentEntity)),
60
+ metadata: {
61
+ ...persistentEntity,
62
+ entityType: 'persistent',
63
+ system: 'vfs-entity'
64
+ },
65
+ vector: embedding
66
+ });
67
+ // Update cache
68
+ this.entityCache.set(entityId, persistentEntity);
69
+ return brainyEntity;
70
+ }
71
+ /**
72
+ * Find an existing entity by name or attributes
73
+ */
74
+ async findEntity(query) {
75
+ const searchQuery = {
76
+ entityType: 'persistent',
77
+ system: 'vfs-entity'
78
+ };
79
+ if (query.name) {
80
+ // Search by exact name or aliases
81
+ searchQuery.$or = [
82
+ { name: query.name },
83
+ { aliases: { $in: [query.name] } }
84
+ ];
85
+ }
86
+ if (query.type) {
87
+ searchQuery.type = query.type;
88
+ }
89
+ if (query.attributes) {
90
+ for (const [key, value] of Object.entries(query.attributes)) {
91
+ searchQuery[`attributes.${key}`] = value;
92
+ }
93
+ }
94
+ // Search in Brainy
95
+ let results = await this.brain.find({
96
+ where: searchQuery,
97
+ type: NounType.Concept,
98
+ limit: 100
99
+ });
100
+ // If searching for similar entities, use vector similarity
101
+ if (query.similar) {
102
+ try {
103
+ const queryEmbedding = await this.generateTextEmbedding(query.similar);
104
+ if (queryEmbedding) {
105
+ // Get all entities and rank by similarity
106
+ const allEntities = await this.brain.find({
107
+ where: { entityType: 'persistent', system: 'vfs-entity' },
108
+ type: NounType.Concept,
109
+ limit: 1000
110
+ });
111
+ const withSimilarity = allEntities
112
+ .filter(e => e.entity.vector && e.entity.vector.length > 0)
113
+ .map(e => ({
114
+ entity: e,
115
+ similarity: 1 - cosineDistance(queryEmbedding, e.entity.vector)
116
+ }))
117
+ .filter(s => s.similarity > this.config.similarityThreshold)
118
+ .sort((a, b) => b.similarity - a.similarity);
119
+ results = withSimilarity.map(s => s.entity);
120
+ }
121
+ }
122
+ catch (error) {
123
+ console.warn('Failed to perform similarity search:', error);
124
+ }
125
+ }
126
+ return results.map(r => r.entity.metadata);
127
+ }
128
+ /**
129
+ * Record an appearance of an entity in a file
130
+ */
131
+ async recordAppearance(entityId, filePath, context, options) {
132
+ const entity = await this.getEntity(entityId);
133
+ if (!entity) {
134
+ throw new Error(`Entity ${entityId} not found`);
135
+ }
136
+ const appearanceId = uuidv4();
137
+ const timestamp = Date.now();
138
+ // Detect changes if enabled
139
+ let changes = [];
140
+ if (options?.extractChanges && this.config.evolutionTracking) {
141
+ changes = await this.detectChanges(entity, context, filePath);
142
+ }
143
+ const appearance = {
144
+ id: appearanceId,
145
+ entityId,
146
+ filePath,
147
+ context,
148
+ position: options?.position,
149
+ timestamp,
150
+ version: entity.version,
151
+ changes,
152
+ confidence: options?.confidence ?? 1.0
153
+ };
154
+ // Store appearance as Brainy entity
155
+ await this.brain.add({
156
+ type: NounType.Event,
157
+ data: Buffer.from(context),
158
+ metadata: {
159
+ ...appearance,
160
+ eventType: 'entity-appearance',
161
+ system: 'vfs-entity'
162
+ }
163
+ });
164
+ // Create relationship to entity
165
+ await this.brain.relate({
166
+ from: appearanceId,
167
+ to: entityId,
168
+ type: VerbType.References
169
+ });
170
+ // Update entity with new appearance
171
+ entity.appearances.push(appearance);
172
+ entity.lastUpdated = timestamp;
173
+ // Apply changes if any detected
174
+ if (changes.length > 0) {
175
+ entity.version++;
176
+ for (const change of changes) {
177
+ if (change.field in entity.attributes) {
178
+ entity.attributes[change.field] = change.newValue;
179
+ }
180
+ }
181
+ }
182
+ // Prune old appearances if needed
183
+ if (entity.appearances.length > this.config.maxAppearances) {
184
+ entity.appearances = entity.appearances
185
+ .sort((a, b) => b.timestamp - a.timestamp)
186
+ .slice(0, this.config.maxAppearances);
187
+ }
188
+ // Update stored entity
189
+ await this.updateEntity(entity);
190
+ return appearanceId;
191
+ }
192
+ /**
193
+ * Get entity evolution history
194
+ */
195
+ async getEvolution(entityId) {
196
+ const entity = await this.getEntity(entityId);
197
+ if (!entity) {
198
+ throw new Error(`Entity ${entityId} not found`);
199
+ }
200
+ // Get all appearances sorted by time
201
+ const appearances = entity.appearances.sort((a, b) => a.timestamp - b.timestamp);
202
+ // Build timeline
203
+ const timeline = [];
204
+ for (const appearance of appearances) {
205
+ if (appearance.changes && appearance.changes.length > 0) {
206
+ timeline.push({
207
+ timestamp: appearance.timestamp,
208
+ version: appearance.version,
209
+ changes: appearance.changes,
210
+ appearance
211
+ });
212
+ }
213
+ }
214
+ return { entity, timeline };
215
+ }
216
+ /**
217
+ * Find all appearances of an entity
218
+ */
219
+ async findAppearances(entityId, options) {
220
+ const query = {
221
+ entityId,
222
+ eventType: 'entity-appearance',
223
+ system: 'vfs-entity'
224
+ };
225
+ if (options?.filePath) {
226
+ query.filePath = options.filePath;
227
+ }
228
+ if (options?.since || options?.until) {
229
+ query.timestamp = {};
230
+ if (options.since)
231
+ query.timestamp.$gte = options.since;
232
+ if (options.until)
233
+ query.timestamp.$lte = options.until;
234
+ }
235
+ if (options?.minConfidence) {
236
+ query.confidence = { $gte: options.minConfidence };
237
+ }
238
+ const results = await this.brain.find({
239
+ where: query,
240
+ type: NounType.Event,
241
+ limit: 1000
242
+ });
243
+ return results
244
+ .map(r => r.entity.metadata)
245
+ .sort((a, b) => b.timestamp - a.timestamp);
246
+ }
247
+ /**
248
+ * Evolve an entity with new information
249
+ */
250
+ async evolveEntity(entityId, updates, source, reason) {
251
+ const entity = await this.getEntity(entityId);
252
+ if (!entity) {
253
+ throw new Error(`Entity ${entityId} not found`);
254
+ }
255
+ const timestamp = Date.now();
256
+ const changes = [];
257
+ // Track changes
258
+ for (const [field, newValue] of Object.entries(updates)) {
259
+ if (field in entity && entity[field] !== newValue) {
260
+ changes.push({
261
+ field,
262
+ oldValue: entity[field],
263
+ newValue,
264
+ timestamp,
265
+ source,
266
+ reason
267
+ });
268
+ }
269
+ }
270
+ // Apply updates
271
+ Object.assign(entity, updates);
272
+ entity.lastUpdated = timestamp;
273
+ entity.version++;
274
+ // Update stored entity
275
+ await this.updateEntity(entity);
276
+ // Record evolution event
277
+ if (changes.length > 0) {
278
+ await this.brain.add({
279
+ type: NounType.Event,
280
+ data: Buffer.from(JSON.stringify(changes)),
281
+ metadata: {
282
+ entityId,
283
+ changes,
284
+ timestamp,
285
+ source,
286
+ reason,
287
+ eventType: 'entity-evolution',
288
+ system: 'vfs-entity'
289
+ }
290
+ });
291
+ }
292
+ }
293
+ /**
294
+ * Extract entities from content (auto-extraction)
295
+ */
296
+ async extractEntities(filePath, content) {
297
+ if (!this.config.autoExtract) {
298
+ return [];
299
+ }
300
+ // Convert content to text for processing
301
+ const text = content.toString('utf8');
302
+ const entities = [];
303
+ // Simple entity extraction patterns
304
+ // In production, this would use NLP/ML models
305
+ const patterns = [
306
+ // Character names (capitalized words)
307
+ /\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*\b/g,
308
+ // API endpoints
309
+ /\/api\/[a-zA-Z0-9\/\-_]+/g,
310
+ // Class names
311
+ /class\s+([A-Z][a-zA-Z0-9_]*)/g,
312
+ // Function names
313
+ /function\s+([a-zA-Z_][a-zA-Z0-9_]*)/g
314
+ ];
315
+ for (const pattern of patterns) {
316
+ const matches = text.matchAll(pattern);
317
+ for (const match of matches) {
318
+ const entityName = match[1] || match[0];
319
+ // Check if entity already exists
320
+ const existing = await this.findEntity({ name: entityName });
321
+ if (existing.length === 0) {
322
+ // Create new entity
323
+ const entityId = await this.createEntity({
324
+ name: entityName,
325
+ type: this.detectEntityType(entityName, text),
326
+ aliases: [],
327
+ attributes: {}
328
+ });
329
+ entities.push(entityId);
330
+ }
331
+ // Record appearance for existing or new entity
332
+ const entity = existing[0] || await this.getEntity(entities[entities.length - 1]);
333
+ if (entity) {
334
+ await this.recordAppearance(entity.id, filePath, this.extractContext(text, match.index || 0), { confidence: 0.7, extractChanges: true });
335
+ }
336
+ }
337
+ }
338
+ return entities;
339
+ }
340
+ /**
341
+ * Update references when a file moves
342
+ */
343
+ async updateReferences(oldPath, newPath) {
344
+ // Find all appearances in the old path
345
+ const results = await this.brain.find({
346
+ where: {
347
+ filePath: oldPath,
348
+ eventType: 'entity-appearance',
349
+ system: 'vfs-entity'
350
+ },
351
+ type: NounType.Event,
352
+ limit: 10000
353
+ });
354
+ // Update each appearance
355
+ for (const result of results) {
356
+ const appearance = result.entity.metadata;
357
+ appearance.filePath = newPath;
358
+ // Update the stored appearance
359
+ await this.brain.update({
360
+ id: result.entity.id,
361
+ metadata: appearance
362
+ });
363
+ }
364
+ // Update cache
365
+ for (const entity of this.entityCache.values()) {
366
+ for (const appearance of entity.appearances) {
367
+ if (appearance.filePath === oldPath) {
368
+ appearance.filePath = newPath;
369
+ }
370
+ }
371
+ }
372
+ }
373
+ /**
374
+ * Get entity by ID
375
+ */
376
+ async getEntity(entityId) {
377
+ // Check cache first
378
+ if (this.entityCache.has(entityId)) {
379
+ return this.entityCache.get(entityId);
380
+ }
381
+ // Query from Brainy
382
+ const results = await this.brain.find({
383
+ where: {
384
+ id: entityId,
385
+ entityType: 'persistent',
386
+ system: 'vfs-entity'
387
+ },
388
+ type: NounType.Concept,
389
+ limit: 1
390
+ });
391
+ if (results.length === 0) {
392
+ return null;
393
+ }
394
+ const entity = results[0].entity.metadata;
395
+ this.entityCache.set(entityId, entity);
396
+ return entity;
397
+ }
398
+ /**
399
+ * Update stored entity
400
+ */
401
+ async updateEntity(entity) {
402
+ // Find the Brainy entity
403
+ const results = await this.brain.find({
404
+ where: {
405
+ id: entity.id,
406
+ entityType: 'persistent',
407
+ system: 'vfs-entity'
408
+ },
409
+ type: NounType.Concept,
410
+ limit: 1
411
+ });
412
+ if (results.length > 0) {
413
+ await this.brain.update({
414
+ id: results[0].entity.id,
415
+ data: Buffer.from(JSON.stringify(entity)),
416
+ metadata: {
417
+ ...entity,
418
+ entityType: 'persistent',
419
+ system: 'vfs-entity'
420
+ }
421
+ });
422
+ }
423
+ // Update cache
424
+ this.entityCache.set(entity.id, entity);
425
+ }
426
+ /**
427
+ * Detect changes in entity from context
428
+ */
429
+ async detectChanges(entity, context, source) {
430
+ // Simple change detection - in production would use NLP
431
+ const changes = [];
432
+ const timestamp = Date.now();
433
+ // Look for attribute changes in context
434
+ const attributePatterns = [
435
+ /(\w+):\s*"([^"]+)"/g, // key: "value"
436
+ /(\w+)\s*=\s*"([^"]+)"/g, // key = "value"
437
+ /set(\w+)\("([^"]+)"\)/g // setProperty("value")
438
+ ];
439
+ for (const pattern of attributePatterns) {
440
+ const matches = context.matchAll(pattern);
441
+ for (const match of matches) {
442
+ const field = match[1].toLowerCase();
443
+ const newValue = match[2];
444
+ if (field in entity.attributes && entity.attributes[field] !== newValue) {
445
+ changes.push({
446
+ field: `attributes.${field}`,
447
+ oldValue: entity.attributes[field],
448
+ newValue,
449
+ timestamp,
450
+ source
451
+ });
452
+ }
453
+ }
454
+ }
455
+ return changes;
456
+ }
457
+ /**
458
+ * Generate embedding for entity
459
+ */
460
+ async generateEntityEmbedding(entity) {
461
+ try {
462
+ // Create text representation of entity
463
+ const text = [
464
+ entity.name,
465
+ entity.description || '',
466
+ entity.type,
467
+ ...entity.aliases,
468
+ JSON.stringify(entity.attributes)
469
+ ].join(' ');
470
+ return await this.generateTextEmbedding(text);
471
+ }
472
+ catch (error) {
473
+ console.error('Failed to generate entity embedding:', error);
474
+ return undefined;
475
+ }
476
+ }
477
+ /**
478
+ * Generate embedding for text
479
+ */
480
+ async generateTextEmbedding(text) {
481
+ try {
482
+ // Generate embedding using Brainy's embed method
483
+ const vector = await this.brain.embed(text);
484
+ return vector;
485
+ }
486
+ catch (error) {
487
+ console.debug('Failed to generate embedding:', error);
488
+ return undefined;
489
+ }
490
+ }
491
+ /**
492
+ * Detect entity type from name and context
493
+ */
494
+ detectEntityType(name, context) {
495
+ if (context.includes('class ' + name))
496
+ return 'class';
497
+ if (context.includes('function ' + name))
498
+ return 'function';
499
+ if (context.includes('/api/'))
500
+ return 'api';
501
+ if (/^[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*$/.test(name))
502
+ return 'person';
503
+ return 'entity';
504
+ }
505
+ /**
506
+ * Extract context around a position
507
+ */
508
+ extractContext(text, position, radius = 100) {
509
+ const start = Math.max(0, position - radius);
510
+ const end = Math.min(text.length, position + radius);
511
+ return text.slice(start, end);
512
+ }
513
+ /**
514
+ * Clear entity cache
515
+ */
516
+ clearCache(entityId) {
517
+ if (entityId) {
518
+ this.entityCache.delete(entityId);
519
+ }
520
+ else {
521
+ this.entityCache.clear();
522
+ }
523
+ }
524
+ }
525
+ //# sourceMappingURL=PersistentEntitySystem.js.map
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Semantic Versioning System for VFS
3
+ *
4
+ * Only creates versions when the MEANING of content changes significantly
5
+ * PRODUCTION-READY: Real implementation using embeddings
6
+ */
7
+ import { Brainy } from '../brainy.js';
8
+ /**
9
+ * Version metadata
10
+ */
11
+ export interface Version {
12
+ id: string;
13
+ path: string;
14
+ version: number;
15
+ timestamp: number;
16
+ hash: string;
17
+ size: number;
18
+ semanticHash?: string;
19
+ author?: string;
20
+ message?: string;
21
+ parentVersion?: string;
22
+ }
23
+ /**
24
+ * Semantic versioning configuration
25
+ */
26
+ export interface SemanticVersioningConfig {
27
+ threshold?: number;
28
+ maxVersions?: number;
29
+ minInterval?: number;
30
+ sizeChangeThreshold?: number;
31
+ }
32
+ /**
33
+ * Semantic Versioning System
34
+ *
35
+ * Creates versions only when content meaning changes significantly
36
+ * Uses vector embeddings to detect semantic changes
37
+ */
38
+ export declare class SemanticVersioning {
39
+ private brain;
40
+ private config;
41
+ private versionCache;
42
+ constructor(brain: Brainy, config?: SemanticVersioningConfig);
43
+ /**
44
+ * Check if content has changed enough to warrant a new version
45
+ */
46
+ shouldVersion(oldContent: Buffer, newContent: Buffer): Promise<boolean>;
47
+ /**
48
+ * Create a new version
49
+ */
50
+ createVersion(path: string, content: Buffer, metadata?: {
51
+ author?: string;
52
+ message?: string;
53
+ }): Promise<string>;
54
+ /**
55
+ * Get all versions for a file
56
+ */
57
+ getVersions(path: string): Promise<Version[]>;
58
+ /**
59
+ * Get a specific version's content
60
+ */
61
+ getVersion(path: string, versionId: string): Promise<Buffer | null>;
62
+ /**
63
+ * Restore a file to a specific version
64
+ */
65
+ restoreVersion(path: string, versionId: string): Promise<Buffer | null>;
66
+ /**
67
+ * Get version history with diffs
68
+ */
69
+ getVersionHistory(path: string, limit?: number): Promise<Array<{
70
+ version: Version;
71
+ changes?: {
72
+ additions: number;
73
+ deletions: number;
74
+ semanticChange: number;
75
+ };
76
+ }>>;
77
+ /**
78
+ * Prune old versions beyond the limit
79
+ */
80
+ private pruneVersions;
81
+ /**
82
+ * Calculate semantic distance between two pieces of content
83
+ */
84
+ private calculateSemanticDistance;
85
+ /**
86
+ * Generate embedding for content
87
+ */
88
+ private generateEmbedding;
89
+ /**
90
+ * Hash content for quick comparison
91
+ */
92
+ private hashContent;
93
+ /**
94
+ * Hash embedding for quick comparison
95
+ */
96
+ private hashEmbedding;
97
+ /**
98
+ * Estimate semantic change from hashes (rough approximation)
99
+ */
100
+ private estimateSemanticChange;
101
+ /**
102
+ * Clear version cache for a file
103
+ */
104
+ clearCache(path?: string): void;
105
+ }