@soulcraft/brainy 3.10.0 → 3.12.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.
@@ -6,10 +6,11 @@
6
6
  * PRODUCTION-READY: Real implementation using Brainy
7
7
  */
8
8
  import { Brainy } from '../brainy.js';
9
+ import { EntityManager, ManagedEntity } from './EntityManager.js';
9
10
  /**
10
11
  * Universal concept that exists independently of files
11
12
  */
12
- export interface UniversalConcept {
13
+ export interface UniversalConcept extends ManagedEntity {
13
14
  id: string;
14
15
  name: string;
15
16
  description?: string;
@@ -23,6 +24,7 @@ export interface UniversalConcept {
23
24
  lastUpdated: number;
24
25
  version: number;
25
26
  metadata: Record<string, any>;
27
+ conceptType?: string;
26
28
  }
27
29
  /**
28
30
  * A link between concepts
@@ -38,7 +40,7 @@ export interface ConceptLink {
38
40
  /**
39
41
  * A manifestation of a concept in a specific location
40
42
  */
41
- export interface ConceptManifestation {
43
+ export interface ConceptManifestation extends ManagedEntity {
42
44
  id: string;
43
45
  conceptId: string;
44
46
  filePath: string;
@@ -90,8 +92,7 @@ export interface ConceptGraph {
90
92
  * - "Dependency Injection" pattern across multiple codebases
91
93
  * - "Sustainability" theme in various research papers
92
94
  */
93
- export declare class ConceptSystem {
94
- private brain;
95
+ export declare class ConceptSystem extends EntityManager {
95
96
  private config;
96
97
  private conceptCache;
97
98
  constructor(brain: Brainy, config?: ConceptSystemConfig);
@@ -8,6 +8,7 @@
8
8
  import { NounType, VerbType } from '../types/graphTypes.js';
9
9
  import { cosineDistance } from '../utils/distance.js';
10
10
  import { v4 as uuidv4 } from '../universal/uuid.js';
11
+ import { EntityManager } from './EntityManager.js';
11
12
  /**
12
13
  * Universal Concept System
13
14
  *
@@ -18,9 +19,9 @@ import { v4 as uuidv4 } from '../universal/uuid.js';
18
19
  * - "Dependency Injection" pattern across multiple codebases
19
20
  * - "Sustainability" theme in various research papers
20
21
  */
21
- export class ConceptSystem {
22
+ export class ConceptSystem extends EntityManager {
22
23
  constructor(brain, config) {
23
- this.brain = brain;
24
+ super(brain, 'vfs-concept');
24
25
  this.conceptCache = new Map();
25
26
  this.config = {
26
27
  autoLink: config?.autoLink ?? false,
@@ -36,13 +37,20 @@ export class ConceptSystem {
36
37
  const conceptId = uuidv4();
37
38
  const timestamp = Date.now();
38
39
  const universalConcept = {
39
- ...concept,
40
40
  id: conceptId,
41
+ name: concept.name,
42
+ description: concept.description,
43
+ domain: concept.domain,
44
+ category: concept.category,
45
+ keywords: concept.keywords,
46
+ strength: concept.strength,
47
+ metadata: concept.metadata || {},
41
48
  created: timestamp,
42
49
  lastUpdated: timestamp,
43
50
  version: 1,
44
51
  links: [],
45
- manifestations: []
52
+ manifestations: [],
53
+ conceptType: 'universal' // Add conceptType for querying
46
54
  };
47
55
  // Generate embedding for concept
48
56
  let embedding;
@@ -52,24 +60,15 @@ export class ConceptSystem {
52
60
  catch (error) {
53
61
  console.warn('Failed to generate concept embedding:', error);
54
62
  }
55
- // Store concept in Brainy
56
- const brainyEntity = await this.brain.add({
57
- type: NounType.Concept,
58
- data: Buffer.from(JSON.stringify(universalConcept)),
59
- metadata: {
60
- ...universalConcept,
61
- conceptType: 'universal',
62
- system: 'vfs-concept'
63
- },
64
- vector: embedding
65
- });
63
+ // Store concept using EntityManager
64
+ await this.storeEntity(universalConcept, NounType.Concept, embedding, Buffer.from(JSON.stringify(universalConcept)));
66
65
  // Auto-link to similar concepts if enabled
67
66
  if (this.config.autoLink) {
68
67
  await this.autoLinkConcept(conceptId);
69
68
  }
70
69
  // Update cache
71
70
  this.conceptCache.set(conceptId, universalConcept);
72
- return brainyEntity;
71
+ return conceptId; // Return domain ID, not Brainy ID
73
72
  }
74
73
  /**
75
74
  * Find concepts by various criteria
@@ -93,16 +92,11 @@ export class ConceptSystem {
93
92
  // File manifestation search
94
93
  if (query.manifestedIn) {
95
94
  // Find concepts that have manifestations in this file
96
- const manifestationResults = await this.brain.find({
97
- where: {
98
- filePath: query.manifestedIn,
99
- eventType: 'concept-manifestation',
100
- system: 'vfs-concept'
101
- },
102
- type: NounType.Event,
103
- limit: 1000
104
- });
105
- const conceptIds = manifestationResults.map(r => r.entity.metadata.conceptId);
95
+ const manifestationResults = await this.findEntities({
96
+ filePath: query.manifestedIn,
97
+ eventType: 'concept-manifestation'
98
+ }, NounType.Event, 1000);
99
+ const conceptIds = manifestationResults.map(m => m.conceptId);
106
100
  if (conceptIds.length > 0) {
107
101
  searchQuery.id = { $in: conceptIds };
108
102
  }
@@ -110,39 +104,43 @@ export class ConceptSystem {
110
104
  return []; // No concepts found in this file
111
105
  }
112
106
  }
113
- // Search in Brainy
114
- let results = await this.brain.find({
115
- where: searchQuery,
116
- type: NounType.Concept,
117
- limit: 1000
118
- });
107
+ // Search using EntityManager
108
+ const results = await this.findEntities(searchQuery, NounType.Concept, 1000);
119
109
  // If searching for similar concepts, use vector similarity
120
110
  if (query.similar) {
121
111
  try {
122
112
  const queryEmbedding = await this.generateTextEmbedding(query.similar);
123
113
  if (queryEmbedding) {
124
114
  // Get all concepts and rank by similarity
125
- const allConcepts = await this.brain.find({
126
- where: { conceptType: 'universal', system: 'vfs-concept' },
127
- type: NounType.Concept,
128
- limit: 10000
129
- });
130
- const withSimilarity = allConcepts
131
- .filter(c => c.entity.vector && c.entity.vector.length > 0)
115
+ const allConcepts = await this.findEntities({ conceptType: 'universal' }, NounType.Concept, 10000);
116
+ // For similarity search, we need to get the actual vector data from Brainy
117
+ const conceptsWithVectors = [];
118
+ for (const concept of allConcepts) {
119
+ if (concept.brainyId) {
120
+ const brainyEntity = await this.brain.get(concept.brainyId);
121
+ if (brainyEntity?.vector && brainyEntity.vector.length > 0) {
122
+ conceptsWithVectors.push({
123
+ concept,
124
+ vector: brainyEntity.vector
125
+ });
126
+ }
127
+ }
128
+ }
129
+ const withSimilarity = conceptsWithVectors
132
130
  .map(c => ({
133
- concept: c,
134
- similarity: 1 - cosineDistance(queryEmbedding, c.entity.vector)
131
+ concept: c.concept,
132
+ similarity: 1 - cosineDistance(queryEmbedding, c.vector)
135
133
  }))
136
134
  .filter(s => s.similarity > this.config.similarityThreshold)
137
135
  .sort((a, b) => b.similarity - a.similarity);
138
- results = withSimilarity.map(s => s.concept);
136
+ return withSimilarity.map(s => s.concept);
139
137
  }
140
138
  }
141
139
  catch (error) {
142
140
  console.warn('Failed to perform concept similarity search:', error);
143
141
  }
144
142
  }
145
- return results.map(r => r.entity.metadata);
143
+ return results;
146
144
  }
147
145
  /**
148
146
  * Link two concepts together
@@ -182,16 +180,11 @@ export class ConceptSystem {
182
180
  toConcept.lastUpdated = Date.now();
183
181
  await this.updateConcept(toConcept);
184
182
  }
185
- // Create Brainy relationship
186
- await this.brain.relate({
187
- from: fromConceptId,
188
- to: toConceptId,
189
- type: this.getVerbType(relationship),
190
- metadata: {
191
- strength: link.strength,
192
- context: link.context,
193
- bidirectional: link.bidirectional
194
- }
183
+ // Create relationship using EntityManager
184
+ await this.createRelationship(fromConceptId, toConceptId, this.getVerbType(relationship), {
185
+ strength: link.strength,
186
+ context: link.context,
187
+ bidirectional: link.bidirectional
195
188
  });
196
189
  return linkId;
197
190
  }
@@ -216,22 +209,14 @@ export class ConceptSystem {
216
209
  timestamp,
217
210
  extractedBy: options?.extractedBy ?? 'manual'
218
211
  };
219
- // Store manifestation as Brainy event
220
- await this.brain.add({
221
- type: NounType.Event,
222
- data: Buffer.from(context),
223
- metadata: {
224
- ...manifestation,
225
- eventType: 'concept-manifestation',
226
- system: 'vfs-concept'
227
- }
228
- });
229
- // Create relationship to concept
230
- await this.brain.relate({
231
- from: manifestationId,
232
- to: conceptId,
233
- type: VerbType.Implements
234
- });
212
+ // Store manifestation as managed entity (with eventType for manifestations)
213
+ const manifestationWithEventType = {
214
+ ...manifestation,
215
+ eventType: 'concept-manifestation'
216
+ };
217
+ await this.storeEntity(manifestationWithEventType, NounType.Event, undefined, Buffer.from(context));
218
+ // Create relationship to concept using EntityManager
219
+ await this.createRelationship(manifestationId, conceptId, VerbType.Implements);
235
220
  // Update concept with new manifestation
236
221
  concept.manifestations.push(manifestation);
237
222
  concept.lastUpdated = timestamp;
@@ -314,12 +299,7 @@ export class ConceptSystem {
314
299
  if (options?.minStrength) {
315
300
  query.strength = { $gte: options.minStrength };
316
301
  }
317
- const results = await this.brain.find({
318
- where: query,
319
- type: NounType.Concept,
320
- limit: options?.maxConcepts || 1000
321
- });
322
- const concepts = results.map(r => r.entity.metadata);
302
+ const concepts = await this.findEntities(query, NounType.Concept, options?.maxConcepts || 1000);
323
303
  // Build graph structure
324
304
  const graphConcepts = concepts.map(c => ({
325
305
  id: c.id,
@@ -365,14 +345,8 @@ export class ConceptSystem {
365
345
  if (options?.minConfidence) {
366
346
  query.confidence = { $gte: options.minConfidence };
367
347
  }
368
- const results = await this.brain.find({
369
- where: query,
370
- type: NounType.Event,
371
- limit: options?.limit || 1000
372
- });
373
- return results
374
- .map(r => r.entity.metadata)
375
- .sort((a, b) => b.timestamp - a.timestamp);
348
+ const manifestations = await this.findEntities(query, NounType.Event, options?.limit || 1000);
349
+ return manifestations.sort((a, b) => b.timestamp - a.timestamp);
376
350
  }
377
351
  /**
378
352
  * Auto-link concept to similar concepts
@@ -403,48 +377,21 @@ export class ConceptSystem {
403
377
  if (this.conceptCache.has(conceptId)) {
404
378
  return this.conceptCache.get(conceptId);
405
379
  }
406
- // Query from Brainy
407
- const results = await this.brain.find({
408
- where: {
409
- id: conceptId,
410
- conceptType: 'universal',
411
- system: 'vfs-concept'
412
- },
413
- type: NounType.Concept,
414
- limit: 1
415
- });
416
- if (results.length === 0) {
417
- return null;
380
+ // Query using EntityManager
381
+ const concept = await this.getEntity(conceptId);
382
+ if (concept) {
383
+ this.conceptCache.set(conceptId, concept);
418
384
  }
419
- const concept = results[0].entity.metadata;
420
- this.conceptCache.set(conceptId, concept);
421
385
  return concept;
422
386
  }
423
387
  /**
424
388
  * Update stored concept
425
389
  */
426
390
  async updateConcept(concept) {
427
- // Find the Brainy entity
428
- const results = await this.brain.find({
429
- where: {
430
- id: concept.id,
431
- conceptType: 'universal',
432
- system: 'vfs-concept'
433
- },
434
- type: NounType.Concept,
435
- limit: 1
436
- });
437
- if (results.length > 0) {
438
- await this.brain.update({
439
- id: results[0].entity.id,
440
- data: Buffer.from(JSON.stringify(concept)),
441
- metadata: {
442
- ...concept,
443
- conceptType: 'universal',
444
- system: 'vfs-concept'
445
- }
446
- });
447
- }
391
+ // Add conceptType metadata before updating
392
+ concept.conceptType = 'universal';
393
+ // Update using EntityManager
394
+ await this.updateEntity(concept);
448
395
  // Update cache
449
396
  this.conceptCache.set(concept.id, concept);
450
397
  }
@@ -5,10 +5,11 @@
5
5
  * PRODUCTION-READY: No mocks, real implementation
6
6
  */
7
7
  import { Brainy } from '../brainy.js';
8
+ import { EntityManager, ManagedEntity } from './EntityManager.js';
8
9
  /**
9
10
  * File operation event
10
11
  */
11
- export interface FileEvent {
12
+ export interface FileEvent extends ManagedEntity {
12
13
  id: string;
13
14
  type: 'create' | 'write' | 'append' | 'delete' | 'move' | 'rename' | 'mkdir' | 'rmdir';
14
15
  path: string;
@@ -20,12 +21,12 @@ export interface FileEvent {
20
21
  metadata?: Record<string, any>;
21
22
  previousHash?: string;
22
23
  oldPath?: string;
24
+ eventType?: string;
23
25
  }
24
26
  /**
25
27
  * Event Recorder - Stores all file operations as searchable events
26
28
  */
27
- export declare class EventRecorder {
28
- private brain;
29
+ export declare class EventRecorder extends EntityManager {
29
30
  constructor(brain: Brainy);
30
31
  /**
31
32
  * Record a file operation event
@@ -7,12 +7,13 @@
7
7
  import { NounType } from '../types/graphTypes.js';
8
8
  import { v4 as uuidv4 } from '../universal/uuid.js';
9
9
  import { createHash } from 'crypto';
10
+ import { EntityManager } from './EntityManager.js';
10
11
  /**
11
12
  * Event Recorder - Stores all file operations as searchable events
12
13
  */
13
- export class EventRecorder {
14
+ export class EventRecorder extends EntityManager {
14
15
  constructor(brain) {
15
- this.brain = brain;
16
+ super(brain, 'vfs');
16
17
  }
17
18
  /**
18
19
  * Record a file operation event
@@ -24,24 +25,32 @@ export class EventRecorder {
24
25
  const hash = event.content
25
26
  ? createHash('sha256').update(event.content).digest('hex')
26
27
  : undefined;
27
- // Store event as Brainy entity
28
- const entity = await this.brain.add({
29
- type: NounType.Event,
30
- data: event.content || Buffer.from(JSON.stringify(event)),
31
- metadata: {
32
- ...event,
33
- id: eventId,
34
- timestamp,
35
- hash,
36
- eventType: 'file-operation',
37
- system: 'vfs'
38
- },
39
- // Generate embedding for content-based events
40
- vector: event.content && event.content.length < 100000
41
- ? await this.generateEventEmbedding(event)
42
- : undefined
43
- });
44
- return entity;
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;
45
54
  }
46
55
  /**
47
56
  * Get all events matching criteria
@@ -65,18 +74,8 @@ export class EventRecorder {
65
74
  if (options?.types && options.types.length > 0) {
66
75
  query.type = { $in: options.types };
67
76
  }
68
- // Query events from Brainy
69
- const results = await this.brain.find({
70
- where: query,
71
- type: NounType.Event,
72
- limit
73
- });
74
- const events = [];
75
- for (const result of results) {
76
- if (result.entity?.metadata) {
77
- events.push(result.entity.metadata);
78
- }
79
- }
77
+ // Query using EntityManager
78
+ const events = await this.findEntities(query, NounType.Event, limit);
80
79
  return events.sort((a, b) => b.timestamp - a.timestamp);
81
80
  }
82
81
  /**
@@ -99,27 +98,8 @@ export class EventRecorder {
99
98
  if (options?.types && options.types.length > 0) {
100
99
  query.type = { $in: options.types };
101
100
  }
102
- // Query events from Brainy
103
- const results = await this.brain.find({
104
- where: query,
105
- type: NounType.Event,
106
- limit: options?.limit || 100,
107
- // Sort by timestamp descending (newest first)
108
- // Note: Sorting would need to be implemented in Brainy
109
- });
110
- // Convert results to FileEvent format
111
- const events = results.map(r => ({
112
- id: r.entity.metadata.id,
113
- type: r.entity.metadata.type,
114
- path: r.entity.metadata.path,
115
- timestamp: r.entity.metadata.timestamp,
116
- content: r.entity.metadata.content,
117
- size: r.entity.metadata.size,
118
- hash: r.entity.metadata.hash,
119
- author: r.entity.metadata.author,
120
- metadata: r.entity.metadata.metadata,
121
- previousHash: r.entity.metadata.previousHash
122
- }));
101
+ // Query using EntityManager
102
+ const events = await this.findEntities(query, NounType.Event, options?.limit || 100);
123
103
  // Sort by timestamp (newest first)
124
104
  return events.sort((a, b) => b.timestamp - a.timestamp);
125
105
  }
@@ -168,19 +148,16 @@ export class EventRecorder {
168
148
  if (until) {
169
149
  query.timestamp.$lte = until;
170
150
  }
171
- const results = await this.brain.find({
172
- where: query,
173
- type: NounType.Event,
174
- limit: 1000
175
- });
176
- return results.map(r => ({
177
- id: r.entity.metadata.id,
178
- type: r.entity.metadata.type,
179
- path: r.entity.metadata.path,
180
- timestamp: r.entity.metadata.timestamp,
181
- size: r.entity.metadata.size,
182
- hash: r.entity.metadata.hash,
183
- author: r.entity.metadata.author
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
184
161
  }));
185
162
  }
186
163
  /**
@@ -282,7 +259,7 @@ export class EventRecorder {
282
259
  for (let i = 0; i < events.length; i++) {
283
260
  // Keep every Nth event for history sampling
284
261
  if (i % keepEvery !== 0) {
285
- await this.brain.delete(events[i].id);
262
+ await this.deleteEntity(events[i].id);
286
263
  pruned++;
287
264
  }
288
265
  }
@@ -40,10 +40,12 @@ export declare class PathResolver {
40
40
  private fullResolve;
41
41
  /**
42
42
  * Resolve a child entity by name within a parent directory
43
+ * Uses proper graph relationships instead of metadata queries
43
44
  */
44
45
  private resolveChild;
45
46
  /**
46
47
  * Get all children of a directory
48
+ * Uses proper graph relationships to traverse the tree
47
49
  */
48
50
  getChildren(dirId: string): Promise<VFSEntity[]>;
49
51
  /**
@@ -122,69 +122,56 @@ export class PathResolver {
122
122
  }
123
123
  /**
124
124
  * Resolve a child entity by name within a parent directory
125
+ * Uses proper graph relationships instead of metadata queries
125
126
  */
126
127
  async resolveChild(parentId, name) {
127
128
  // Check parent cache first
128
129
  const cachedChildren = this.parentCache.get(parentId);
129
130
  if (cachedChildren && cachedChildren.has(name)) {
130
- // We know this child exists, find it by path since parent queries don't work
131
- const parentEntity = await this.getEntity(parentId);
132
- const parentPath = parentEntity.metadata.path;
133
- const childPath = this.joinPath(parentPath, name);
134
- const pathResults = await this.brain.find({
135
- where: { path: childPath },
136
- limit: 1
137
- });
138
- if (pathResults.length > 0) {
139
- return pathResults[0].entity.id;
140
- }
131
+ // Use cached knowledge to quickly find the child
132
+ // Still need to verify it exists
141
133
  }
142
- // Since parent field queries don't work reliably, construct the expected path
143
- // and query by path instead
144
- const parentEntity = await this.getEntity(parentId);
145
- const parentPath = parentEntity.metadata.path;
146
- const childPath = this.joinPath(parentPath, name);
147
- const results = await this.brain.find({
148
- where: { path: childPath },
149
- limit: 1
134
+ // Use proper graph traversal to find children
135
+ // Get all relationships where parentId contains other entities
136
+ const relations = await this.brain.getRelations({
137
+ from: parentId,
138
+ type: VerbType.Contains
150
139
  });
151
- if (results.length > 0) {
152
- const childId = results[0].entity.id;
153
- // Update parent cache
154
- if (!this.parentCache.has(parentId)) {
155
- this.parentCache.set(parentId, new Set());
140
+ // Find the child with matching name
141
+ for (const relation of relations) {
142
+ const childEntity = await this.brain.get(relation.to);
143
+ if (childEntity && childEntity.metadata?.name === name) {
144
+ // Update parent cache
145
+ if (!this.parentCache.has(parentId)) {
146
+ this.parentCache.set(parentId, new Set());
147
+ }
148
+ this.parentCache.get(parentId).add(name);
149
+ return childEntity.id;
156
150
  }
157
- this.parentCache.get(parentId).add(name);
158
- return childId;
159
151
  }
160
152
  return null;
161
153
  }
162
154
  /**
163
155
  * Get all children of a directory
156
+ * Uses proper graph relationships to traverse the tree
164
157
  */
165
158
  async getChildren(dirId) {
166
- // Use Brainy's relationship query to find all children
167
- const results = await this.brain.find({
168
- connected: {
169
- from: dirId,
170
- via: VerbType.Contains
171
- },
172
- limit: 10000 // Large limit for directories
159
+ // Use proper graph API to get all Contains relationships from this directory
160
+ const relations = await this.brain.getRelations({
161
+ from: dirId,
162
+ type: VerbType.Contains
173
163
  });
174
- // Filter and process valid VFS entities only
175
164
  const validChildren = [];
176
165
  const childNames = new Set();
177
- for (const result of results) {
178
- const entity = result.entity;
179
- // Only include entities with proper VFS metadata and non-empty names
180
- if (entity.metadata?.vfsType &&
181
- entity.metadata?.name &&
182
- entity.metadata?.path &&
183
- entity.id !== dirId) { // Don't include the directory itself
166
+ // Fetch all child entities
167
+ for (const relation of relations) {
168
+ const entity = await this.brain.get(relation.to);
169
+ if (entity && entity.metadata?.vfsType && entity.metadata?.name) {
184
170
  validChildren.push(entity);
185
171
  childNames.add(entity.metadata.name);
186
172
  }
187
173
  }
174
+ // Update cache
188
175
  this.parentCache.set(dirId, childNames);
189
176
  return validChildren;
190
177
  }
@@ -6,10 +6,11 @@
6
6
  * PRODUCTION-READY: Real implementation using Brainy
7
7
  */
8
8
  import { Brainy } from '../brainy.js';
9
+ import { EntityManager, ManagedEntity } from './EntityManager.js';
9
10
  /**
10
11
  * Persistent entity that exists across files and evolves over time
11
12
  */
12
- export interface PersistentEntity {
13
+ export interface PersistentEntity extends ManagedEntity {
13
14
  id: string;
14
15
  name: string;
15
16
  type: string;
@@ -20,11 +21,12 @@ export interface PersistentEntity {
20
21
  created: number;
21
22
  lastUpdated: number;
22
23
  version: number;
24
+ entityType?: string;
23
25
  }
24
26
  /**
25
27
  * An appearance of an entity in a specific file/location
26
28
  */
27
- export interface EntityAppearance {
29
+ export interface EntityAppearance extends ManagedEntity {
28
30
  id: string;
29
31
  entityId: string;
30
32
  filePath: string;
@@ -38,6 +40,7 @@ export interface EntityAppearance {
38
40
  version: number;
39
41
  changes?: EntityChange[];
40
42
  confidence: number;
43
+ eventType?: string;
41
44
  }
42
45
  /**
43
46
  * A change/evolution to an entity
@@ -69,8 +72,7 @@ export interface PersistentEntityConfig {
69
72
  * - Business entities that appear in multiple reports
70
73
  * - Code classes/functions that span multiple files
71
74
  */
72
- export declare class PersistentEntitySystem {
73
- private brain;
75
+ export declare class PersistentEntitySystem extends EntityManager {
74
76
  private config;
75
77
  private entityCache;
76
78
  constructor(brain: Brainy, config?: PersistentEntityConfig);
@@ -129,13 +131,13 @@ export declare class PersistentEntitySystem {
129
131
  */
130
132
  updateReferences(oldPath: string, newPath: string): Promise<void>;
131
133
  /**
132
- * Get entity by ID
134
+ * Get persistent entity by ID
133
135
  */
134
- private getEntity;
136
+ getPersistentEntity(entityId: string): Promise<PersistentEntity | null>;
135
137
  /**
136
- * Update stored entity
138
+ * Update stored entity (rename to avoid parent method conflict)
137
139
  */
138
- private updateEntity;
140
+ private updatePersistentEntity;
139
141
  /**
140
142
  * Detect changes in entity from context
141
143
  */
@@ -8,6 +8,7 @@
8
8
  import { NounType, VerbType } from '../types/graphTypes.js';
9
9
  import { cosineDistance } from '../utils/distance.js';
10
10
  import { v4 as uuidv4 } from '../universal/uuid.js';
11
+ import { EntityManager } from './EntityManager.js';
11
12
  /**
12
13
  * Persistent Entity System
13
14
  *
@@ -18,9 +19,9 @@ import { v4 as uuidv4 } from '../universal/uuid.js';
18
19
  * - Business entities that appear in multiple reports
19
20
  * - Code classes/functions that span multiple files
20
21
  */
21
- export class PersistentEntitySystem {
22
+ export class PersistentEntitySystem extends EntityManager {
22
23
  constructor(brain, config) {
23
- this.brain = brain;
24
+ super(brain, 'vfs-entity');
24
25
  this.entityCache = new Map();
25
26
  this.config = {
26
27
  autoExtract: config?.autoExtract ?? false,
@@ -36,12 +37,17 @@ export class PersistentEntitySystem {
36
37
  const entityId = uuidv4();
37
38
  const timestamp = Date.now();
38
39
  const persistentEntity = {
39
- ...entity,
40
40
  id: entityId,
41
+ name: entity.name,
42
+ type: entity.type,
43
+ description: entity.description,
44
+ aliases: entity.aliases,
45
+ attributes: entity.attributes,
41
46
  created: timestamp,
42
47
  lastUpdated: timestamp,
43
48
  version: 1,
44
- appearances: []
49
+ appearances: [],
50
+ entityType: 'persistent' // Add entityType for querying
45
51
  };
46
52
  // Generate embedding for entity description
47
53
  let embedding;
@@ -53,29 +59,17 @@ export class PersistentEntitySystem {
53
59
  catch (error) {
54
60
  console.warn('Failed to generate entity embedding:', error);
55
61
  }
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
- });
62
+ // Store entity using EntityManager
63
+ await this.storeEntity(persistentEntity, NounType.Concept, embedding, Buffer.from(JSON.stringify(persistentEntity)));
67
64
  // Update cache
68
65
  this.entityCache.set(entityId, persistentEntity);
69
- return brainyEntity;
66
+ return entityId; // Return domain ID, not Brainy ID
70
67
  }
71
68
  /**
72
69
  * Find an existing entity by name or attributes
73
70
  */
74
71
  async findEntity(query) {
75
- const searchQuery = {
76
- entityType: 'persistent',
77
- system: 'vfs-entity'
78
- };
72
+ const searchQuery = {};
79
73
  if (query.name) {
80
74
  // Search by exact name or aliases
81
75
  searchQuery.$or = [
@@ -91,45 +85,46 @@ export class PersistentEntitySystem {
91
85
  searchQuery[`attributes.${key}`] = value;
92
86
  }
93
87
  }
94
- // Search in Brainy
95
- let results = await this.brain.find({
96
- where: searchQuery,
97
- type: NounType.Concept,
98
- limit: 100
99
- });
88
+ // Add system metadata for EntityManager
89
+ searchQuery.entityType = 'persistent';
90
+ // Search using EntityManager
91
+ let results = await this.findEntities(searchQuery, NounType.Concept, 100);
100
92
  // If searching for similar entities, use vector similarity
101
93
  if (query.similar) {
102
94
  try {
103
95
  const queryEmbedding = await this.generateTextEmbedding(query.similar);
104
96
  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
- });
97
+ // Get all entities and rank by similarity using EntityManager
98
+ const allEntities = await this.findEntities({}, NounType.Concept, 1000);
111
99
  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)
100
+ .filter(e => e.brainyId) // Only entities with brainyId have vectors
101
+ .map(async (e) => {
102
+ const brainyEntity = await this.brain.get(e.brainyId);
103
+ if (brainyEntity?.vector) {
104
+ return {
105
+ entity: e,
106
+ similarity: 1 - cosineDistance(queryEmbedding, brainyEntity.vector)
107
+ };
108
+ }
109
+ return null;
110
+ });
111
+ const resolved = (await Promise.all(withSimilarity))
112
+ .filter(s => s !== null && s.similarity > this.config.similarityThreshold)
118
113
  .sort((a, b) => b.similarity - a.similarity);
119
- results = withSimilarity.map(s => s.entity);
114
+ results = resolved.map(s => s.entity);
120
115
  }
121
116
  }
122
117
  catch (error) {
123
118
  console.warn('Failed to perform similarity search:', error);
124
119
  }
125
120
  }
126
- return results.map(r => r.entity.metadata);
121
+ return results;
127
122
  }
128
123
  /**
129
124
  * Record an appearance of an entity in a file
130
125
  */
131
126
  async recordAppearance(entityId, filePath, context, options) {
132
- const entity = await this.getEntity(entityId);
127
+ const entity = await this.getPersistentEntity(entityId);
133
128
  if (!entity) {
134
129
  throw new Error(`Entity ${entityId} not found`);
135
130
  }
@@ -151,22 +146,14 @@ export class PersistentEntitySystem {
151
146
  changes,
152
147
  confidence: options?.confidence ?? 1.0
153
148
  };
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
- });
149
+ // Store appearance as managed entity (with eventType for appearances)
150
+ const appearanceWithEventType = {
151
+ ...appearance,
152
+ eventType: 'entity-appearance'
153
+ };
154
+ await this.storeEntity(appearanceWithEventType, NounType.Event, undefined, Buffer.from(context));
155
+ // Create relationship to entity using EntityManager
156
+ await this.createRelationship(appearanceId, entityId, VerbType.References);
170
157
  // Update entity with new appearance
171
158
  entity.appearances.push(appearance);
172
159
  entity.lastUpdated = timestamp;
@@ -186,14 +173,14 @@ export class PersistentEntitySystem {
186
173
  .slice(0, this.config.maxAppearances);
187
174
  }
188
175
  // Update stored entity
189
- await this.updateEntity(entity);
176
+ await this.updatePersistentEntity(entity);
190
177
  return appearanceId;
191
178
  }
192
179
  /**
193
180
  * Get entity evolution history
194
181
  */
195
182
  async getEvolution(entityId) {
196
- const entity = await this.getEntity(entityId);
183
+ const entity = await this.getPersistentEntity(entityId);
197
184
  if (!entity) {
198
185
  throw new Error(`Entity ${entityId} not found`);
199
186
  }
@@ -248,7 +235,7 @@ export class PersistentEntitySystem {
248
235
  * Evolve an entity with new information
249
236
  */
250
237
  async evolveEntity(entityId, updates, source, reason) {
251
- const entity = await this.getEntity(entityId);
238
+ const entity = await this.getPersistentEntity(entityId);
252
239
  if (!entity) {
253
240
  throw new Error(`Entity ${entityId} not found`);
254
241
  }
@@ -272,7 +259,7 @@ export class PersistentEntitySystem {
272
259
  entity.lastUpdated = timestamp;
273
260
  entity.version++;
274
261
  // Update stored entity
275
- await this.updateEntity(entity);
262
+ await this.updatePersistentEntity(entity);
276
263
  // Record evolution event
277
264
  if (changes.length > 0) {
278
265
  await this.brain.add({
@@ -306,7 +293,7 @@ export class PersistentEntitySystem {
306
293
  // Character names (capitalized words)
307
294
  /\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*\b/g,
308
295
  // API endpoints
309
- /\/api\/[a-zA-Z0-9\/\-_]+/g,
296
+ /\/api\/[a-zA-Z0-9/\-_]+/g,
310
297
  // Class names
311
298
  /class\s+([A-Z][a-zA-Z0-9_]*)/g,
312
299
  // Function names
@@ -329,7 +316,7 @@ export class PersistentEntitySystem {
329
316
  entities.push(entityId);
330
317
  }
331
318
  // Record appearance for existing or new entity
332
- const entity = existing[0] || await this.getEntity(entities[entities.length - 1]);
319
+ const entity = existing[0] || await this.getPersistentEntity(entities[entities.length - 1]);
333
320
  if (entity) {
334
321
  await this.recordAppearance(entity.id, filePath, this.extractContext(text, match.index || 0), { confidence: 0.7, extractChanges: true });
335
322
  }
@@ -371,34 +358,25 @@ export class PersistentEntitySystem {
371
358
  }
372
359
  }
373
360
  /**
374
- * Get entity by ID
361
+ * Get persistent entity by ID
375
362
  */
376
- async getEntity(entityId) {
363
+ async getPersistentEntity(entityId) {
377
364
  // Check cache first
378
365
  if (this.entityCache.has(entityId)) {
379
366
  return this.entityCache.get(entityId);
380
367
  }
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;
368
+ // Use parent getEntity method
369
+ const entity = await super.getEntity(entityId);
370
+ if (entity) {
371
+ // Cache and return
372
+ this.entityCache.set(entityId, entity);
393
373
  }
394
- const entity = results[0].entity.metadata;
395
- this.entityCache.set(entityId, entity);
396
374
  return entity;
397
375
  }
398
376
  /**
399
- * Update stored entity
377
+ * Update stored entity (rename to avoid parent method conflict)
400
378
  */
401
- async updateEntity(entity) {
379
+ async updatePersistentEntity(entity) {
402
380
  // Find the Brainy entity
403
381
  const results = await this.brain.find({
404
382
  where: {
@@ -5,10 +5,11 @@
5
5
  * PRODUCTION-READY: Real implementation using embeddings
6
6
  */
7
7
  import { Brainy } from '../brainy.js';
8
+ import { EntityManager, ManagedEntity } from './EntityManager.js';
8
9
  /**
9
10
  * Version metadata
10
11
  */
11
- export interface Version {
12
+ export interface Version extends ManagedEntity {
12
13
  id: string;
13
14
  path: string;
14
15
  version: number;
@@ -35,8 +36,7 @@ export interface SemanticVersioningConfig {
35
36
  * Creates versions only when content meaning changes significantly
36
37
  * Uses vector embeddings to detect semantic changes
37
38
  */
38
- export declare class SemanticVersioning {
39
- private brain;
39
+ export declare class SemanticVersioning extends EntityManager {
40
40
  private config;
41
41
  private versionCache;
42
42
  constructor(brain: Brainy, config?: SemanticVersioningConfig);
@@ -8,15 +8,16 @@ import { NounType, VerbType } from '../types/graphTypes.js';
8
8
  import { cosineDistance } from '../utils/distance.js';
9
9
  import { createHash } from 'crypto';
10
10
  import { v4 as uuidv4 } from '../universal/uuid.js';
11
+ import { EntityManager } from './EntityManager.js';
11
12
  /**
12
13
  * Semantic Versioning System
13
14
  *
14
15
  * Creates versions only when content meaning changes significantly
15
16
  * Uses vector embeddings to detect semantic changes
16
17
  */
17
- export class SemanticVersioning {
18
+ export class SemanticVersioning extends EntityManager {
18
19
  constructor(brain, config) {
19
- this.brain = brain;
20
+ super(brain, 'vfs-version');
20
21
  this.versionCache = new Map();
21
22
  this.config = {
22
23
  threshold: config?.threshold ?? 0.3,
@@ -81,32 +82,30 @@ export class SemanticVersioning {
81
82
  catch (error) {
82
83
  console.warn('Failed to generate embedding for version:', error);
83
84
  }
84
- // Store version as Brainy entity
85
- const entity = await this.brain.add({
86
- type: NounType.State,
87
- data: content, // Store actual content
88
- metadata: {
89
- id: versionId,
90
- path,
91
- version: versionNumber,
92
- timestamp,
93
- hash,
94
- semanticHash,
95
- size: content.length,
96
- author: metadata?.author,
97
- message: metadata?.message,
98
- parentVersion,
99
- system: 'vfs-version'
100
- },
101
- vector: embedding
102
- });
85
+ // Create version entity
86
+ const version = {
87
+ id: versionId,
88
+ path,
89
+ version: versionNumber,
90
+ timestamp,
91
+ hash,
92
+ semanticHash,
93
+ size: content.length,
94
+ author: metadata?.author,
95
+ message: metadata?.message,
96
+ parentVersion
97
+ };
98
+ // Store version using EntityManager (with actual content as data)
99
+ await this.storeEntity(version, NounType.State, embedding, content);
103
100
  // Create relationship to parent version if exists
104
101
  if (parentVersion) {
105
- await this.brain.relate({
106
- from: entity,
107
- to: parentVersion,
108
- type: VerbType.Succeeds
109
- });
102
+ try {
103
+ await this.createRelationship(versionId, parentVersion, VerbType.Succeeds);
104
+ }
105
+ catch (error) {
106
+ console.warn(`Failed to create parent relationship for version ${versionId}:`, error);
107
+ // Continue without relationship - non-critical for version functionality
108
+ }
110
109
  }
111
110
  // Update cache
112
111
  if (!this.versionCache.has(path)) {
@@ -136,18 +135,11 @@ export class SemanticVersioning {
136
135
  if (this.versionCache.has(path)) {
137
136
  return this.versionCache.get(path);
138
137
  }
139
- // Query from Brainy
140
- const results = await this.brain.find({
141
- where: {
142
- path,
143
- system: 'vfs-version'
144
- },
145
- type: NounType.State,
146
- limit: this.config.maxVersions * 2 // Get extra in case some are pruned
147
- });
148
- const versions = results
149
- .map(r => r.entity.metadata)
150
- .sort((a, b) => b.timestamp - a.timestamp); // Newest first
138
+ // Query using EntityManager
139
+ const versions = await this.findEntities({ path }, NounType.State, this.config.maxVersions * 2 // Get extra in case some are pruned
140
+ );
141
+ // Sort by timestamp (newest first)
142
+ versions.sort((a, b) => b.timestamp - a.timestamp);
151
143
  // Update cache
152
144
  this.versionCache.set(path, versions);
153
145
  return versions;
@@ -156,19 +148,18 @@ export class SemanticVersioning {
156
148
  * Get a specific version's content
157
149
  */
158
150
  async getVersion(path, versionId) {
159
- const results = await this.brain.find({
160
- where: {
161
- id: versionId,
162
- path,
163
- system: 'vfs-version'
164
- },
165
- type: NounType.State,
166
- limit: 1
167
- });
168
- if (results.length === 0) {
151
+ // Get the version entity
152
+ const version = await this.getEntity(versionId);
153
+ if (!version || version.path !== path) {
154
+ return null;
155
+ }
156
+ // Get the content from Brainy using the Brainy ID
157
+ const brainyId = await this.getBrainyId(versionId);
158
+ if (!brainyId) {
169
159
  return null;
170
160
  }
171
- return results[0].entity.data;
161
+ const entity = await this.brain.get(brainyId);
162
+ return entity?.data;
172
163
  }
173
164
  /**
174
165
  * Restore a file to a specific version
@@ -238,7 +229,7 @@ export class SemanticVersioning {
238
229
  }
239
230
  // Delete excess versions
240
231
  for (const id of toDelete.slice(0, versions.length - this.config.maxVersions)) {
241
- await this.brain.delete(id);
232
+ await this.deleteEntity(id);
242
233
  }
243
234
  // Update cache
244
235
  this.versionCache.set(path, versions.filter(v => !toDelete.includes(v.id)));
@@ -1364,37 +1364,29 @@ export class VirtualFileSystem {
1364
1364
  await this.ensureInitialized();
1365
1365
  const entityId = await this.pathResolver.resolve(path);
1366
1366
  const results = [];
1367
- // Get all relationships involving this entity (both from and to)
1367
+ // Use proper Brainy relationship API to get all relationships
1368
1368
  const [fromRelations, toRelations] = await Promise.all([
1369
- this.brain.find({
1370
- connected: {
1371
- from: entityId
1372
- },
1373
- limit: 1000
1374
- }),
1375
- this.brain.find({
1376
- connected: {
1377
- to: entityId
1378
- },
1379
- limit: 1000
1380
- })
1369
+ this.brain.getRelations({ from: entityId }),
1370
+ this.brain.getRelations({ to: entityId })
1381
1371
  ]);
1382
1372
  // Add outgoing relationships
1383
1373
  for (const rel of fromRelations) {
1384
- if (rel.entity.metadata?.path) {
1374
+ const targetEntity = await this.brain.get(rel.to);
1375
+ if (targetEntity && targetEntity.metadata?.path) {
1385
1376
  results.push({
1386
- path: rel.entity.metadata.path,
1387
- relationship: 'related',
1377
+ path: targetEntity.metadata.path,
1378
+ relationship: rel.type || 'related',
1388
1379
  direction: 'from'
1389
1380
  });
1390
1381
  }
1391
1382
  }
1392
1383
  // Add incoming relationships
1393
1384
  for (const rel of toRelations) {
1394
- if (rel.entity.metadata?.path) {
1385
+ const sourceEntity = await this.brain.get(rel.from);
1386
+ if (sourceEntity && sourceEntity.metadata?.path) {
1395
1387
  results.push({
1396
- path: rel.entity.metadata.path,
1397
- relationship: 'related',
1388
+ path: sourceEntity.metadata.path,
1389
+ relationship: rel.type || 'related',
1398
1390
  direction: 'to'
1399
1391
  });
1400
1392
  }
@@ -1405,49 +1397,37 @@ export class VirtualFileSystem {
1405
1397
  await this.ensureInitialized();
1406
1398
  const entityId = await this.pathResolver.resolve(path);
1407
1399
  const relationships = [];
1408
- // Get all relationships involving this entity (both from and to)
1400
+ // Use proper Brainy relationship API
1409
1401
  const [fromRelations, toRelations] = await Promise.all([
1410
- this.brain.find({
1411
- connected: {
1412
- from: entityId
1413
- },
1414
- limit: 1000
1415
- }),
1416
- this.brain.find({
1417
- connected: {
1418
- to: entityId
1419
- },
1420
- limit: 1000
1421
- })
1402
+ this.brain.getRelations({ from: entityId }),
1403
+ this.brain.getRelations({ to: entityId })
1422
1404
  ]);
1423
- // Add outgoing relationships (exclude parent-child relationships)
1405
+ // Process outgoing relationships (excluding Contains for parent-child)
1424
1406
  for (const rel of fromRelations) {
1425
- if (rel.entity.metadata?.path && rel.entity.metadata?.vfsType) {
1426
- // Skip parent-child relationships to focus on user-defined relationships
1427
- const parentPath = this.getParentPath(rel.entity.metadata.path);
1428
- if (parentPath !== path) { // Not a direct child
1407
+ if (rel.type !== VerbType.Contains) { // Skip filesystem hierarchy
1408
+ const targetEntity = await this.brain.get(rel.to);
1409
+ if (targetEntity && targetEntity.metadata?.path) {
1429
1410
  relationships.push({
1430
- id: crypto.randomUUID(),
1431
- from: path,
1432
- to: rel.entity.metadata.path,
1433
- type: VerbType.References,
1434
- createdAt: Date.now()
1411
+ id: rel.id || crypto.randomUUID(),
1412
+ from: entityId,
1413
+ to: rel.to,
1414
+ type: rel.type,
1415
+ createdAt: rel.createdAt || Date.now()
1435
1416
  });
1436
1417
  }
1437
1418
  }
1438
1419
  }
1439
- // Add incoming relationships (exclude parent-child relationships)
1420
+ // Process incoming relationships (excluding Contains for parent-child)
1440
1421
  for (const rel of toRelations) {
1441
- if (rel.entity.metadata?.path && rel.entity.metadata?.vfsType) {
1442
- // Skip parent-child relationships to focus on user-defined relationships
1443
- const parentPath = this.getParentPath(path);
1444
- if (rel.entity.metadata.path !== parentPath) { // Not the parent
1422
+ if (rel.type !== VerbType.Contains) { // Skip filesystem hierarchy
1423
+ const sourceEntity = await this.brain.get(rel.from);
1424
+ if (sourceEntity && sourceEntity.metadata?.path) {
1445
1425
  relationships.push({
1446
- id: crypto.randomUUID(),
1447
- from: rel.entity.metadata.path,
1448
- to: path,
1449
- type: VerbType.References,
1450
- createdAt: Date.now()
1426
+ id: rel.id || crypto.randomUUID(),
1427
+ from: rel.from,
1428
+ to: entityId,
1429
+ type: rel.type,
1430
+ createdAt: rel.createdAt || Date.now()
1451
1431
  });
1452
1432
  }
1453
1433
  }
@@ -1734,7 +1714,7 @@ export class VirtualFileSystem {
1734
1714
  case 'mkdir':
1735
1715
  await this.mkdir(op.path, op.options);
1736
1716
  break;
1737
- case 'update':
1717
+ case 'update': {
1738
1718
  // Update only metadata without changing content
1739
1719
  const entityId = await this.pathResolver.resolve(op.path);
1740
1720
  await this.brain.update({
@@ -1742,6 +1722,7 @@ export class VirtualFileSystem {
1742
1722
  metadata: op.options?.metadata
1743
1723
  });
1744
1724
  break;
1725
+ }
1745
1726
  }
1746
1727
  result.successful++;
1747
1728
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soulcraft/brainy",
3
- "version": "3.10.0",
3
+ "version": "3.12.0",
4
4
  "description": "Universal Knowledge Protocol™ - World's first Triple Intelligence database unifying vector, graph, and document search in one API. 31 nouns × 40 verbs for infinite expressiveness.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",