@soulcraft/brainy 6.2.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.
@@ -0,0 +1,545 @@
1
+ /**
2
+ * Universal Concept System for VFS
3
+ *
4
+ * Manages concepts that transcend files and exist independently
5
+ * Ideas that can be linked to multiple manifestations across domains
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
+ * Universal Concept System
14
+ *
15
+ * Manages concepts that exist independently of any specific file or context
16
+ * Examples:
17
+ * - "Authentication" concept appearing in docs, code, tests
18
+ * - "Customer Journey" concept in marketing, UX, analytics
19
+ * - "Dependency Injection" pattern across multiple codebases
20
+ * - "Sustainability" theme in various research papers
21
+ */
22
+ export class ConceptSystem extends EntityManager {
23
+ constructor(brain, config) {
24
+ super(brain, 'vfs-concept');
25
+ this.conceptCache = new Map();
26
+ this.config = {
27
+ autoLink: config?.autoLink ?? false,
28
+ similarityThreshold: config?.similarityThreshold ?? 0.7,
29
+ maxManifestations: config?.maxManifestations ?? 1000,
30
+ strengthDecay: config?.strengthDecay ?? 0.95 // 5% decay over time
31
+ };
32
+ }
33
+ /**
34
+ * Create a new universal concept
35
+ */
36
+ async createConcept(concept) {
37
+ const conceptId = uuidv4();
38
+ const timestamp = Date.now();
39
+ const universalConcept = {
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 || {},
48
+ created: timestamp,
49
+ lastUpdated: timestamp,
50
+ version: 1,
51
+ links: [],
52
+ manifestations: [],
53
+ conceptType: 'universal' // Add conceptType for querying
54
+ };
55
+ // Generate embedding for concept
56
+ let embedding;
57
+ try {
58
+ embedding = await this.generateConceptEmbedding(universalConcept);
59
+ }
60
+ catch (error) {
61
+ console.warn('Failed to generate concept embedding:', error);
62
+ }
63
+ // Store concept using EntityManager
64
+ await this.storeEntity(universalConcept, NounType.Concept, embedding, Buffer.from(JSON.stringify(universalConcept)));
65
+ // Auto-link to similar concepts if enabled
66
+ if (this.config.autoLink) {
67
+ await this.autoLinkConcept(conceptId);
68
+ }
69
+ // Update cache
70
+ this.conceptCache.set(conceptId, universalConcept);
71
+ return conceptId; // Return domain ID, not Brainy ID
72
+ }
73
+ /**
74
+ * Find concepts by various criteria
75
+ */
76
+ async findConcepts(query) {
77
+ const searchQuery = {
78
+ conceptType: 'universal',
79
+ system: 'vfs-concept'
80
+ };
81
+ // Direct attribute matching
82
+ if (query.name)
83
+ searchQuery.name = query.name;
84
+ if (query.domain)
85
+ searchQuery.domain = query.domain;
86
+ if (query.category)
87
+ searchQuery.category = query.category;
88
+ // Keyword matching
89
+ if (query.keywords && query.keywords.length > 0) {
90
+ searchQuery.keywords = { $in: query.keywords };
91
+ }
92
+ // File manifestation search
93
+ if (query.manifestedIn) {
94
+ // Find concepts that have manifestations in this file
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);
100
+ if (conceptIds.length > 0) {
101
+ searchQuery.id = { $in: conceptIds };
102
+ }
103
+ else {
104
+ return []; // No concepts found in this file
105
+ }
106
+ }
107
+ // Search using EntityManager
108
+ const results = await this.findEntities(searchQuery, NounType.Concept, 1000);
109
+ // If searching for similar concepts, use vector similarity
110
+ if (query.similar) {
111
+ try {
112
+ const queryEmbedding = await this.generateTextEmbedding(query.similar);
113
+ if (queryEmbedding) {
114
+ // Get all concepts and rank by similarity
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
130
+ .map(c => ({
131
+ concept: c.concept,
132
+ similarity: 1 - cosineDistance(queryEmbedding, c.vector)
133
+ }))
134
+ .filter(s => s.similarity > this.config.similarityThreshold)
135
+ .sort((a, b) => b.similarity - a.similarity);
136
+ return withSimilarity.map(s => s.concept);
137
+ }
138
+ }
139
+ catch (error) {
140
+ console.warn('Failed to perform concept similarity search:', error);
141
+ }
142
+ }
143
+ return results;
144
+ }
145
+ /**
146
+ * Link two concepts together
147
+ */
148
+ async linkConcept(fromConceptId, toConceptId, relationship, options) {
149
+ const linkId = uuidv4();
150
+ const fromConcept = await this.getConcept(fromConceptId);
151
+ const toConcept = await this.getConcept(toConceptId);
152
+ if (!fromConcept || !toConcept) {
153
+ throw new Error('One or both concepts not found');
154
+ }
155
+ // Create link
156
+ const link = {
157
+ id: linkId,
158
+ targetConceptId: toConceptId,
159
+ relationship,
160
+ strength: options?.strength ?? 0.8,
161
+ context: options?.context,
162
+ bidirectional: options?.bidirectional ?? false
163
+ };
164
+ // Add link to source concept
165
+ fromConcept.links.push(link);
166
+ fromConcept.lastUpdated = Date.now();
167
+ await this.updateConcept(fromConcept);
168
+ // Add bidirectional link if specified
169
+ if (link.bidirectional) {
170
+ const reverseRelationship = this.getReverseRelationship(relationship);
171
+ const reverseLink = {
172
+ id: uuidv4(),
173
+ targetConceptId: fromConceptId,
174
+ relationship: reverseRelationship,
175
+ strength: link.strength,
176
+ context: link.context,
177
+ bidirectional: true
178
+ };
179
+ toConcept.links.push(reverseLink);
180
+ toConcept.lastUpdated = Date.now();
181
+ await this.updateConcept(toConcept);
182
+ }
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
188
+ });
189
+ return linkId;
190
+ }
191
+ /**
192
+ * Record a manifestation of a concept in a file
193
+ */
194
+ async recordManifestation(conceptId, filePath, context, form, options) {
195
+ const concept = await this.getConcept(conceptId);
196
+ if (!concept) {
197
+ throw new Error(`Concept ${conceptId} not found`);
198
+ }
199
+ const manifestationId = uuidv4();
200
+ const timestamp = Date.now();
201
+ const manifestation = {
202
+ id: manifestationId,
203
+ conceptId,
204
+ filePath,
205
+ context,
206
+ form,
207
+ position: options?.position,
208
+ confidence: options?.confidence ?? 1.0,
209
+ timestamp,
210
+ extractedBy: options?.extractedBy ?? 'manual'
211
+ };
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);
220
+ // Update concept with new manifestation
221
+ concept.manifestations.push(manifestation);
222
+ concept.lastUpdated = timestamp;
223
+ // Update concept strength based on manifestations
224
+ concept.strength = Math.min(1.0, concept.strength + 0.1);
225
+ // Prune old manifestations if needed
226
+ if (concept.manifestations.length > this.config.maxManifestations) {
227
+ concept.manifestations = concept.manifestations
228
+ .sort((a, b) => b.timestamp - a.timestamp)
229
+ .slice(0, this.config.maxManifestations);
230
+ }
231
+ // Update stored concept
232
+ await this.updateConcept(concept);
233
+ return manifestationId;
234
+ }
235
+ /**
236
+ * Extract and link concepts from content
237
+ */
238
+ async extractAndLinkConcepts(filePath, content) {
239
+ if (!this.config.autoLink) {
240
+ return [];
241
+ }
242
+ const text = content.toString('utf8');
243
+ const extractedConcepts = [];
244
+ // Simple concept extraction patterns
245
+ // In production, this would use advanced NLP/AI models
246
+ const conceptPatterns = [
247
+ // Technical concepts
248
+ /\b(authentication|authorization|validation|encryption|caching|logging|monitoring)\b/gi,
249
+ // Business concepts
250
+ /\b(customer\s+journey|user\s+experience|business\s+logic|revenue\s+model)\b/gi,
251
+ // Design patterns
252
+ /\b(singleton|factory|observer|strategy|adapter|decorator)\b/gi,
253
+ // General concepts
254
+ /\b(security|performance|scalability|maintainability|reliability)\b/gi
255
+ ];
256
+ for (const pattern of conceptPatterns) {
257
+ const matches = text.matchAll(pattern);
258
+ for (const match of matches) {
259
+ const conceptName = match[0].toLowerCase();
260
+ const context = this.extractContext(text, match.index || 0);
261
+ // Find or create concept
262
+ let concepts = await this.findConcepts({ name: conceptName });
263
+ let conceptId;
264
+ if (concepts.length === 0) {
265
+ // Create new concept
266
+ conceptId = await this.createConcept({
267
+ name: conceptName,
268
+ domain: this.detectDomain(conceptName, text),
269
+ category: this.detectCategory(conceptName),
270
+ keywords: [conceptName],
271
+ strength: 0.5,
272
+ metadata: {}
273
+ });
274
+ extractedConcepts.push(conceptId);
275
+ }
276
+ else {
277
+ conceptId = concepts[0].id;
278
+ }
279
+ // Record manifestation
280
+ await this.recordManifestation(conceptId, filePath, context, this.detectManifestationForm(context), {
281
+ confidence: 0.8,
282
+ extractedBy: 'auto'
283
+ });
284
+ }
285
+ }
286
+ return extractedConcepts;
287
+ }
288
+ /**
289
+ * Get concept graph for visualization
290
+ */
291
+ async getConceptGraph(options) {
292
+ const query = {
293
+ conceptType: 'universal',
294
+ system: 'vfs-concept'
295
+ };
296
+ if (options?.domain) {
297
+ query.domain = options.domain;
298
+ }
299
+ if (options?.minStrength) {
300
+ query.strength = { $gte: options.minStrength };
301
+ }
302
+ const concepts = await this.findEntities(query, NounType.Concept, options?.maxConcepts || 1000);
303
+ // Build graph structure
304
+ const graphConcepts = concepts.map(c => ({
305
+ id: c.id,
306
+ name: c.name,
307
+ domain: c.domain,
308
+ strength: c.strength,
309
+ manifestationCount: c.manifestations.length
310
+ }));
311
+ const graphLinks = [];
312
+ for (const concept of concepts) {
313
+ for (const link of concept.links) {
314
+ // Only include links to concepts in our result set
315
+ if (concepts.find(c => c.id === link.targetConceptId)) {
316
+ graphLinks.push({
317
+ source: concept.id,
318
+ target: link.targetConceptId,
319
+ relationship: link.relationship,
320
+ strength: link.strength
321
+ });
322
+ }
323
+ }
324
+ }
325
+ return {
326
+ concepts: graphConcepts,
327
+ links: graphLinks
328
+ };
329
+ }
330
+ /**
331
+ * Find appearances of a concept
332
+ */
333
+ async findAppearances(conceptId, options) {
334
+ const query = {
335
+ conceptId,
336
+ eventType: 'concept-manifestation',
337
+ system: 'vfs-concept'
338
+ };
339
+ if (options?.filePath) {
340
+ query.filePath = options.filePath;
341
+ }
342
+ if (options?.form) {
343
+ query.form = options.form;
344
+ }
345
+ if (options?.minConfidence) {
346
+ query.confidence = { $gte: options.minConfidence };
347
+ }
348
+ const manifestations = await this.findEntities(query, NounType.Event, options?.limit || 1000);
349
+ return manifestations.sort((a, b) => b.timestamp - a.timestamp);
350
+ }
351
+ /**
352
+ * Auto-link concept to similar concepts
353
+ */
354
+ async autoLinkConcept(conceptId) {
355
+ const concept = await this.getConcept(conceptId);
356
+ if (!concept)
357
+ return;
358
+ // Find similar concepts
359
+ const similar = await this.findConcepts({
360
+ similar: concept.name + ' ' + (concept.description || '')
361
+ });
362
+ for (const similarConcept of similar) {
363
+ if (similarConcept.id === conceptId)
364
+ continue;
365
+ // Calculate relationship strength based on similarity
366
+ const strength = await this.calculateConceptSimilarity(concept, similarConcept);
367
+ if (strength > this.config.similarityThreshold) {
368
+ await this.linkConcept(conceptId, similarConcept.id, 'related', { strength, bidirectional: true });
369
+ }
370
+ }
371
+ }
372
+ /**
373
+ * Get concept by ID
374
+ */
375
+ async getConcept(conceptId) {
376
+ // Check cache first
377
+ if (this.conceptCache.has(conceptId)) {
378
+ return this.conceptCache.get(conceptId);
379
+ }
380
+ // Query using EntityManager
381
+ const concept = await this.getEntity(conceptId);
382
+ if (concept) {
383
+ this.conceptCache.set(conceptId, concept);
384
+ }
385
+ return concept;
386
+ }
387
+ /**
388
+ * Update stored concept
389
+ */
390
+ async updateConcept(concept) {
391
+ // Add conceptType metadata before updating
392
+ concept.conceptType = 'universal';
393
+ // Update using EntityManager
394
+ await this.updateEntity(concept);
395
+ // Update cache
396
+ this.conceptCache.set(concept.id, concept);
397
+ }
398
+ /**
399
+ * Calculate similarity between two concepts
400
+ */
401
+ async calculateConceptSimilarity(concept1, concept2) {
402
+ // Simple similarity calculation
403
+ let similarity = 0;
404
+ // Domain similarity
405
+ if (concept1.domain === concept2.domain)
406
+ similarity += 0.3;
407
+ // Category similarity
408
+ if (concept1.category === concept2.category)
409
+ similarity += 0.2;
410
+ // Keyword overlap
411
+ const commonKeywords = concept1.keywords.filter(k => concept2.keywords.includes(k));
412
+ similarity += (commonKeywords.length / Math.max(concept1.keywords.length, concept2.keywords.length)) * 0.3;
413
+ // Name similarity (simple string comparison)
414
+ const nameWords1 = concept1.name.toLowerCase().split(/\s+/);
415
+ const nameWords2 = concept2.name.toLowerCase().split(/\s+/);
416
+ const commonWords = nameWords1.filter(w => nameWords2.includes(w));
417
+ similarity += (commonWords.length / Math.max(nameWords1.length, nameWords2.length)) * 0.2;
418
+ return Math.min(1.0, similarity);
419
+ }
420
+ /**
421
+ * Generate embedding for concept
422
+ */
423
+ async generateConceptEmbedding(concept) {
424
+ try {
425
+ const text = [
426
+ concept.name,
427
+ concept.description || '',
428
+ concept.domain,
429
+ concept.category,
430
+ ...(Array.isArray(concept.keywords) ? concept.keywords : [])
431
+ ].join(' ');
432
+ return await this.generateTextEmbedding(text);
433
+ }
434
+ catch (error) {
435
+ console.error('Failed to generate concept embedding:', error);
436
+ return undefined;
437
+ }
438
+ }
439
+ /**
440
+ * Generate embedding for text
441
+ */
442
+ async generateTextEmbedding(text) {
443
+ try {
444
+ // Generate embedding using Brainy's embed method
445
+ const vector = await this.brain.embed(text);
446
+ return vector;
447
+ }
448
+ catch (error) {
449
+ console.debug('Failed to generate embedding:', error);
450
+ return undefined;
451
+ }
452
+ }
453
+ /**
454
+ * Get reverse relationship type
455
+ */
456
+ getReverseRelationship(relationship) {
457
+ const reverseMap = {
458
+ 'extends': 'extended-by',
459
+ 'implements': 'implemented-by',
460
+ 'uses': 'used-by',
461
+ 'opposite': 'opposite',
462
+ 'related': 'related',
463
+ 'contains': 'part-of',
464
+ 'part-of': 'contains'
465
+ };
466
+ return reverseMap[relationship] || 'related';
467
+ }
468
+ /**
469
+ * Map concept relationship to VerbType
470
+ */
471
+ getVerbType(relationship) {
472
+ const verbMap = {
473
+ 'extends': VerbType.Extends,
474
+ 'implements': VerbType.Implements,
475
+ 'uses': VerbType.Uses,
476
+ 'opposite': VerbType.Conflicts,
477
+ 'related': VerbType.RelatedTo,
478
+ 'contains': VerbType.Contains,
479
+ 'part-of': VerbType.PartOf
480
+ };
481
+ return verbMap[relationship] || VerbType.RelatedTo;
482
+ }
483
+ /**
484
+ * Detect concept domain from context
485
+ */
486
+ detectDomain(conceptName, context) {
487
+ if (/import|export|function|class|const|var|let/.test(context))
488
+ return 'technical';
489
+ if (/customer|user|business|revenue|market/.test(context))
490
+ return 'business';
491
+ if (/design|pattern|architecture/.test(context))
492
+ return 'design';
493
+ if (/research|study|analysis/.test(context))
494
+ return 'academic';
495
+ return 'general';
496
+ }
497
+ /**
498
+ * Detect concept category
499
+ */
500
+ detectCategory(conceptName) {
501
+ if (/pattern|strategy|factory|singleton/.test(conceptName))
502
+ return 'pattern';
503
+ if (/principle|rule|law/.test(conceptName))
504
+ return 'principle';
505
+ if (/method|approach|technique/.test(conceptName))
506
+ return 'method';
507
+ if (/entity|object|model/.test(conceptName))
508
+ return 'entity';
509
+ return 'concept';
510
+ }
511
+ /**
512
+ * Detect manifestation form from context
513
+ */
514
+ detectManifestationForm(context) {
515
+ if (context.includes('definition') || context.includes('is defined as'))
516
+ return 'definition';
517
+ if (context.includes('example') || context.includes('for instance'))
518
+ return 'example';
519
+ if (context.includes('implements') || context.includes('function'))
520
+ return 'implementation';
521
+ if (context.includes('discussed') || context.includes('explains'))
522
+ return 'discussion';
523
+ return 'usage';
524
+ }
525
+ /**
526
+ * Extract context around a position
527
+ */
528
+ extractContext(text, position, radius = 150) {
529
+ const start = Math.max(0, position - radius);
530
+ const end = Math.min(text.length, position + radius);
531
+ return text.slice(start, end);
532
+ }
533
+ /**
534
+ * Clear concept cache
535
+ */
536
+ clearCache(conceptId) {
537
+ if (conceptId) {
538
+ this.conceptCache.delete(conceptId);
539
+ }
540
+ else {
541
+ this.conceptCache.clear();
542
+ }
543
+ }
544
+ }
545
+ //# sourceMappingURL=ConceptSystem.js.map
@@ -0,0 +1,75 @@
1
+ /**
2
+ * EntityManager Base Class
3
+ *
4
+ * Provides standardized entity ID management for all Knowledge Layer components
5
+ * Solves the root cause of ID mismatch issues by establishing clear patterns
6
+ */
7
+ import { Brainy } from '../brainy.js';
8
+ import { NounType } from '../types/graphTypes.js';
9
+ /**
10
+ * Standard entity structure used by all Knowledge Layer components
11
+ */
12
+ export interface ManagedEntity {
13
+ /** Domain-specific ID (for external references) */
14
+ id: string;
15
+ /** The actual Brainy entity ID (for internal operations) */
16
+ brainyId?: string;
17
+ /** Entity metadata */
18
+ [key: string]: any;
19
+ }
20
+ /**
21
+ * EntityManager Base Class
22
+ *
23
+ * All Knowledge Layer components should extend this to get standardized:
24
+ * - Entity storage and retrieval
25
+ * - ID management and mapping
26
+ * - Query patterns
27
+ * - Relationship creation
28
+ */
29
+ export declare abstract class EntityManager {
30
+ protected brain: Brainy;
31
+ protected systemName: string;
32
+ private idMappings;
33
+ private brainyToMappings;
34
+ constructor(brain: Brainy, systemName: string);
35
+ /**
36
+ * Store an entity with proper ID management
37
+ */
38
+ protected storeEntity<T extends ManagedEntity>(entity: T, nounType: NounType, embedding?: number[], data?: any): Promise<string>;
39
+ /**
40
+ * Update an existing entity
41
+ */
42
+ protected updateEntity<T extends ManagedEntity>(entity: T, embedding?: number[]): Promise<void>;
43
+ /**
44
+ * Retrieve entity by domain ID
45
+ */
46
+ protected getEntity<T extends ManagedEntity>(domainId: string): Promise<T | null>;
47
+ /**
48
+ * Find entities by metadata criteria
49
+ */
50
+ protected findEntities<T extends ManagedEntity>(criteria: Record<string, any>, nounType?: NounType, limit?: number): Promise<T[]>;
51
+ /**
52
+ * Create relationship between entities using proper Brainy IDs
53
+ */
54
+ protected createRelationship(fromDomainId: string, toDomainId: string, relationshipType: any, metadata?: Record<string, any>): Promise<void>;
55
+ /**
56
+ * Get Brainy ID for a domain ID
57
+ */
58
+ protected getBrainyId(domainId: string): Promise<string | null>;
59
+ /**
60
+ * Get domain ID for a Brainy ID
61
+ */
62
+ protected getDomainId(brainyId: string): string | null;
63
+ /**
64
+ * Delete entity by domain ID
65
+ */
66
+ protected deleteEntity(domainId: string): Promise<void>;
67
+ /**
68
+ * Clear ID mapping cache (useful for tests)
69
+ */
70
+ protected clearMappingCache(): void;
71
+ /**
72
+ * Batch load mappings for performance
73
+ */
74
+ protected loadMappings(domainIds: string[]): Promise<void>;
75
+ }