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