@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.
- package/dist/vfs/ConceptSystem.d.ts +5 -4
- package/dist/vfs/ConceptSystem.js +65 -118
- package/dist/vfs/EventRecorder.d.ts +4 -3
- package/dist/vfs/EventRecorder.js +44 -67
- package/dist/vfs/PathResolver.d.ts +2 -0
- package/dist/vfs/PathResolver.js +28 -41
- package/dist/vfs/PersistentEntitySystem.d.ts +10 -8
- package/dist/vfs/PersistentEntitySystem.js +59 -81
- package/dist/vfs/SemanticVersioning.d.ts +3 -3
- package/dist/vfs/SemanticVersioning.js +41 -50
- package/dist/vfs/VirtualFileSystem.js +34 -53
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
|
56
|
-
|
|
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
|
|
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.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
|
114
|
-
|
|
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.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
186
|
-
await this.
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
|
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
|
|
369
|
-
|
|
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
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
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
|
-
//
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
69
|
-
const
|
|
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
|
|
103
|
-
const
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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.
|
|
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
|
/**
|
package/dist/vfs/PathResolver.js
CHANGED
|
@@ -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
|
-
//
|
|
131
|
-
|
|
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
|
-
//
|
|
143
|
-
//
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (
|
|
155
|
-
|
|
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
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
57
|
-
|
|
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
|
|
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
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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.
|
|
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.
|
|
113
|
-
.map(e =>
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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 =
|
|
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
|
|
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.
|
|
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
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
//
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
//
|
|
1367
|
+
// Use proper Brainy relationship API to get all relationships
|
|
1368
1368
|
const [fromRelations, toRelations] = await Promise.all([
|
|
1369
|
-
this.brain.
|
|
1370
|
-
|
|
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
|
-
|
|
1374
|
+
const targetEntity = await this.brain.get(rel.to);
|
|
1375
|
+
if (targetEntity && targetEntity.metadata?.path) {
|
|
1385
1376
|
results.push({
|
|
1386
|
-
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
|
-
|
|
1385
|
+
const sourceEntity = await this.brain.get(rel.from);
|
|
1386
|
+
if (sourceEntity && sourceEntity.metadata?.path) {
|
|
1395
1387
|
results.push({
|
|
1396
|
-
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
|
-
//
|
|
1400
|
+
// Use proper Brainy relationship API
|
|
1409
1401
|
const [fromRelations, toRelations] = await Promise.all([
|
|
1410
|
-
this.brain.
|
|
1411
|
-
|
|
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
|
-
//
|
|
1405
|
+
// Process outgoing relationships (excluding Contains for parent-child)
|
|
1424
1406
|
for (const rel of fromRelations) {
|
|
1425
|
-
if (rel.
|
|
1426
|
-
|
|
1427
|
-
|
|
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:
|
|
1432
|
-
to: rel.
|
|
1433
|
-
type:
|
|
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
|
-
//
|
|
1420
|
+
// Process incoming relationships (excluding Contains for parent-child)
|
|
1440
1421
|
for (const rel of toRelations) {
|
|
1441
|
-
if (rel.
|
|
1442
|
-
|
|
1443
|
-
|
|
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.
|
|
1448
|
-
to:
|
|
1449
|
-
type:
|
|
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.
|
|
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",
|