@soulcraft/brainy 4.1.4 → 4.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/import/FormatDetector.d.ts +6 -1
  3. package/dist/import/FormatDetector.js +40 -1
  4. package/dist/import/ImportCoordinator.d.ts +102 -4
  5. package/dist/import/ImportCoordinator.js +248 -6
  6. package/dist/import/InstancePool.d.ts +136 -0
  7. package/dist/import/InstancePool.js +231 -0
  8. package/dist/importers/SmartCSVImporter.d.ts +2 -1
  9. package/dist/importers/SmartCSVImporter.js +11 -22
  10. package/dist/importers/SmartDOCXImporter.d.ts +125 -0
  11. package/dist/importers/SmartDOCXImporter.js +227 -0
  12. package/dist/importers/SmartExcelImporter.d.ts +12 -1
  13. package/dist/importers/SmartExcelImporter.js +40 -25
  14. package/dist/importers/SmartJSONImporter.d.ts +1 -0
  15. package/dist/importers/SmartJSONImporter.js +25 -6
  16. package/dist/importers/SmartMarkdownImporter.d.ts +2 -1
  17. package/dist/importers/SmartMarkdownImporter.js +11 -16
  18. package/dist/importers/SmartPDFImporter.d.ts +2 -1
  19. package/dist/importers/SmartPDFImporter.js +11 -22
  20. package/dist/importers/SmartYAMLImporter.d.ts +121 -0
  21. package/dist/importers/SmartYAMLImporter.js +275 -0
  22. package/dist/importers/VFSStructureGenerator.js +12 -0
  23. package/dist/neural/SmartExtractor.d.ts +279 -0
  24. package/dist/neural/SmartExtractor.js +592 -0
  25. package/dist/neural/SmartRelationshipExtractor.d.ts +217 -0
  26. package/dist/neural/SmartRelationshipExtractor.js +396 -0
  27. package/dist/neural/embeddedTypeEmbeddings.d.ts +1 -1
  28. package/dist/neural/embeddedTypeEmbeddings.js +2 -2
  29. package/dist/neural/entityExtractor.d.ts +3 -0
  30. package/dist/neural/entityExtractor.js +34 -36
  31. package/dist/neural/presets.d.ts +189 -0
  32. package/dist/neural/presets.js +365 -0
  33. package/dist/neural/signals/ContextSignal.d.ts +166 -0
  34. package/dist/neural/signals/ContextSignal.js +646 -0
  35. package/dist/neural/signals/EmbeddingSignal.d.ts +175 -0
  36. package/dist/neural/signals/EmbeddingSignal.js +435 -0
  37. package/dist/neural/signals/ExactMatchSignal.d.ts +220 -0
  38. package/dist/neural/signals/ExactMatchSignal.js +542 -0
  39. package/dist/neural/signals/PatternSignal.d.ts +159 -0
  40. package/dist/neural/signals/PatternSignal.js +478 -0
  41. package/dist/neural/signals/VerbContextSignal.d.ts +102 -0
  42. package/dist/neural/signals/VerbContextSignal.js +390 -0
  43. package/dist/neural/signals/VerbEmbeddingSignal.d.ts +131 -0
  44. package/dist/neural/signals/VerbEmbeddingSignal.js +304 -0
  45. package/dist/neural/signals/VerbExactMatchSignal.d.ts +115 -0
  46. package/dist/neural/signals/VerbExactMatchSignal.js +335 -0
  47. package/dist/neural/signals/VerbPatternSignal.d.ts +104 -0
  48. package/dist/neural/signals/VerbPatternSignal.js +457 -0
  49. package/dist/types/graphTypes.d.ts +2 -0
  50. package/dist/utils/metadataIndex.d.ts +22 -0
  51. package/dist/utils/metadataIndex.js +76 -0
  52. package/package.json +4 -1
@@ -0,0 +1,646 @@
1
+ /**
2
+ * ContextSignal - Relationship-based entity type classification
3
+ *
4
+ * PRODUCTION-READY: Infers types from relationship patterns in context
5
+ *
6
+ * Weight: 5% (lowest, but critical for ambiguous cases)
7
+ * Speed: Very fast (~1ms) - pure pattern matching on context
8
+ *
9
+ * Key insight: Context reveals type even when entity itself is ambiguous
10
+ * Examples:
11
+ * - "CEO of X" → X is Organization
12
+ * - "lives in Y" → Y is Location
13
+ * - "uses Z" → Z is Technology
14
+ * - "attended W" → W is Event
15
+ *
16
+ * This signal fills the gap when:
17
+ * - Entity is too ambiguous for other signals
18
+ * - Multiple signals conflict
19
+ * - Need relationship-aware classification
20
+ */
21
+ /**
22
+ * ContextSignal - Infer entity types from relationship context
23
+ *
24
+ * Production features:
25
+ * - 50+ relationship patterns organized by type
26
+ * - Attribute patterns (e.g., "fast X" → X is Object/Technology)
27
+ * - Multi-pattern matching with confidence boosting
28
+ * - LRU cache for hot entities
29
+ * - Graceful degradation on errors
30
+ */
31
+ export class ContextSignal {
32
+ constructor(brain, options) {
33
+ // Pre-compiled relationship patterns
34
+ this.relationshipPatterns = [];
35
+ // LRU cache for hot entities
36
+ this.cache = new Map();
37
+ this.cacheOrder = [];
38
+ // Statistics
39
+ this.stats = {
40
+ calls: 0,
41
+ cacheHits: 0,
42
+ relationshipMatches: 0,
43
+ attributeMatches: 0,
44
+ combinedMatches: 0
45
+ };
46
+ this.brain = brain;
47
+ this.options = {
48
+ minConfidence: options?.minConfidence ?? 0.50,
49
+ timeout: options?.timeout ?? 50,
50
+ cacheSize: options?.cacheSize ?? 500
51
+ };
52
+ this.initializePatterns();
53
+ }
54
+ /**
55
+ * Initialize relationship patterns
56
+ *
57
+ * Patterns organized by target type:
58
+ * - Person: roles, actions, possessives
59
+ * - Organization: membership, leadership, employment
60
+ * - Location: spatial relationships, residence
61
+ * - Technology: usage, implementation, integration
62
+ * - Event: attendance, occurrence, scheduling
63
+ * - Concept: understanding, application, theory
64
+ * - Object: ownership, interaction, description
65
+ */
66
+ initializePatterns() {
67
+ // Person patterns - who someone is or what they do
68
+ this.addPatterns([
69
+ {
70
+ pattern: /\b(?:CEO|CTO|CFO|director|manager|founder|owner|president)\s+(?:of|at)\s+$/i,
71
+ targetType: 'organization',
72
+ confidence: 0.75,
73
+ evidence: 'Leadership role indicates organization'
74
+ },
75
+ {
76
+ pattern: /\b(?:employee|member|staff|worker|engineer|developer|team member)\s+(?:of|at)\s+$/i,
77
+ targetType: 'organization',
78
+ confidence: 0.70,
79
+ evidence: 'Employment relationship indicates organization'
80
+ },
81
+ {
82
+ pattern: /\b(?:lives|resides|located|based)\s+(?:in|at)\s+$/i,
83
+ targetType: 'location',
84
+ confidence: 0.80,
85
+ evidence: 'Residence indicates location'
86
+ },
87
+ {
88
+ pattern: /\b(?:born|raised|grew up)\s+(?:in|at)\s+$/i,
89
+ targetType: 'location',
90
+ confidence: 0.75,
91
+ evidence: 'Origin indicates location'
92
+ },
93
+ {
94
+ pattern: /\b(?:uses|utilizes|works with|familiar with)\s+$/i,
95
+ targetType: 'thing',
96
+ confidence: 0.65,
97
+ evidence: 'Tool usage indicates thing/tool'
98
+ },
99
+ {
100
+ pattern: /\b(?:attended|participated in|spoke at|presented at)\s+$/i,
101
+ targetType: 'event',
102
+ confidence: 0.75,
103
+ evidence: 'Attendance indicates event'
104
+ },
105
+ {
106
+ pattern: /\b(?:believes in|understands|studies|researches)\s+$/i,
107
+ targetType: 'concept',
108
+ confidence: 0.60,
109
+ evidence: 'Intellectual engagement indicates concept'
110
+ },
111
+ {
112
+ pattern: /\b(?:owns|possesses|has|carries)\s+(?:a|an|the)?\s*$/i,
113
+ targetType: 'thing',
114
+ confidence: 0.70,
115
+ evidence: 'Possession indicates physical thing'
116
+ }
117
+ ]);
118
+ // Organization patterns - organizational relationships
119
+ this.addPatterns([
120
+ {
121
+ pattern: /\b(?:subsidiary|division|branch|department)\s+of\s+$/i,
122
+ targetType: 'organization',
123
+ confidence: 0.80,
124
+ evidence: 'Organizational hierarchy indicates parent organization'
125
+ },
126
+ {
127
+ pattern: /\b(?:partner|collaborator|vendor|supplier)\s+(?:of|to)\s+$/i,
128
+ targetType: 'organization',
129
+ confidence: 0.70,
130
+ evidence: 'Business relationship indicates organization'
131
+ },
132
+ {
133
+ pattern: /\b(?:headquarters|office|facility)\s+(?:in|at)\s+$/i,
134
+ targetType: 'location',
135
+ confidence: 0.75,
136
+ evidence: 'Physical presence indicates location'
137
+ },
138
+ {
139
+ pattern: /\b(?:acquired|purchased|bought|merged with)\s+$/i,
140
+ targetType: 'organization',
141
+ confidence: 0.75,
142
+ evidence: 'Acquisition indicates organization'
143
+ },
144
+ {
145
+ pattern: /\b(?:implements|uses|adopts|integrates)\s+$/i,
146
+ targetType: 'thing',
147
+ confidence: 0.65,
148
+ evidence: 'Tool adoption indicates thing/service'
149
+ },
150
+ {
151
+ pattern: /\b(?:organized|hosted|sponsored)\s+$/i,
152
+ targetType: 'event',
153
+ confidence: 0.70,
154
+ evidence: 'Event organization indicates event'
155
+ }
156
+ ]);
157
+ // Location patterns - spatial relationships
158
+ this.addPatterns([
159
+ {
160
+ pattern: /\b(?:capital|largest city|major city)\s+of\s+$/i,
161
+ targetType: 'location',
162
+ confidence: 0.85,
163
+ evidence: 'Geographic relationship indicates location'
164
+ },
165
+ {
166
+ pattern: /\b(?:near|adjacent to|next to|close to)\s+$/i,
167
+ targetType: 'location',
168
+ confidence: 0.70,
169
+ evidence: 'Spatial proximity indicates location'
170
+ },
171
+ {
172
+ pattern: /\b(?:north|south|east|west)\s+of\s+$/i,
173
+ targetType: 'location',
174
+ confidence: 0.75,
175
+ evidence: 'Directional relationship indicates location'
176
+ },
177
+ {
178
+ pattern: /\b(?:located in|situated in|found in)\s+$/i,
179
+ targetType: 'location',
180
+ confidence: 0.80,
181
+ evidence: 'Location reference indicates place'
182
+ }
183
+ ]);
184
+ // Technology patterns - technical relationships
185
+ this.addPatterns([
186
+ {
187
+ pattern: /\b(?:built with|powered by|runs on)\s+$/i,
188
+ targetType: 'thing',
189
+ confidence: 0.75,
190
+ evidence: 'Technical foundation indicates thing/tool'
191
+ },
192
+ {
193
+ pattern: /\b(?:integrated with|connects to|compatible with)\s+$/i,
194
+ targetType: 'interface',
195
+ confidence: 0.70,
196
+ evidence: 'Integration indicates interface/API'
197
+ },
198
+ {
199
+ pattern: /\b(?:deployed on|hosted on|running on)\s+$/i,
200
+ targetType: 'service',
201
+ confidence: 0.75,
202
+ evidence: 'Deployment indicates service/platform'
203
+ },
204
+ {
205
+ pattern: /\b(?:developed by|created by|maintained by)\s+$/i,
206
+ targetType: 'organization',
207
+ confidence: 0.70,
208
+ evidence: 'Development indicates organization/person'
209
+ },
210
+ {
211
+ pattern: /\b(?:API for|SDK for|library for)\s+$/i,
212
+ targetType: 'interface',
213
+ confidence: 0.75,
214
+ evidence: 'Technical tooling indicates interface/API'
215
+ }
216
+ ]);
217
+ // Event patterns - temporal relationships
218
+ this.addPatterns([
219
+ {
220
+ pattern: /\b(?:before|after|during|since)\s+$/i,
221
+ targetType: 'event',
222
+ confidence: 0.60,
223
+ evidence: 'Temporal relationship indicates event'
224
+ },
225
+ {
226
+ pattern: /\b(?:scheduled for|planned for|occurring on)\s+$/i,
227
+ targetType: 'event',
228
+ confidence: 0.70,
229
+ evidence: 'Scheduling indicates event'
230
+ },
231
+ {
232
+ pattern: /\b(?:keynote at|session at|talk at|presentation at)\s+$/i,
233
+ targetType: 'event',
234
+ confidence: 0.80,
235
+ evidence: 'Speaking engagement indicates event'
236
+ },
237
+ {
238
+ pattern: /\b(?:registration for|tickets to|attending)\s+$/i,
239
+ targetType: 'event',
240
+ confidence: 0.75,
241
+ evidence: 'Participation indicates event'
242
+ }
243
+ ]);
244
+ // Concept patterns - intellectual relationships
245
+ this.addPatterns([
246
+ {
247
+ pattern: /\b(?:theory of|principle of|concept of|idea of)\s+$/i,
248
+ targetType: 'concept',
249
+ confidence: 0.75,
250
+ evidence: 'Theoretical framework indicates concept'
251
+ },
252
+ {
253
+ pattern: /\b(?:based on|derived from|inspired by)\s+$/i,
254
+ targetType: 'concept',
255
+ confidence: 0.60,
256
+ evidence: 'Intellectual lineage indicates concept'
257
+ },
258
+ {
259
+ pattern: /\b(?:example of|instance of|case of)\s+$/i,
260
+ targetType: 'concept',
261
+ confidence: 0.65,
262
+ evidence: 'Exemplification indicates concept'
263
+ },
264
+ {
265
+ pattern: /\b(?:methodology|approach|strategy)\s+(?:for|of)\s+$/i,
266
+ targetType: 'process',
267
+ confidence: 0.70,
268
+ evidence: 'Method reference indicates process'
269
+ }
270
+ ]);
271
+ // Object patterns - physical relationships
272
+ this.addPatterns([
273
+ {
274
+ pattern: /\b(?:made of|composed of|constructed from)\s+$/i,
275
+ targetType: 'thing',
276
+ confidence: 0.65,
277
+ evidence: 'Material composition indicates thing'
278
+ },
279
+ {
280
+ pattern: /\b(?:part of|component of|piece of)\s+$/i,
281
+ targetType: 'thing',
282
+ confidence: 0.70,
283
+ evidence: 'Physical composition indicates thing'
284
+ },
285
+ {
286
+ pattern: /\b(?:weighs|measures|dimensions)\s+$/i,
287
+ targetType: 'thing',
288
+ confidence: 0.75,
289
+ evidence: 'Physical measurement indicates thing'
290
+ }
291
+ ]);
292
+ // Attribute patterns - descriptive relationships
293
+ this.addPatterns([
294
+ {
295
+ pattern: /\b(?:fast|slow|quick|rapid|speedy)\s+$/i,
296
+ targetType: 'thing',
297
+ confidence: 0.55,
298
+ evidence: 'Speed attribute indicates thing/tool'
299
+ },
300
+ {
301
+ pattern: /\b(?:large|small|tiny|huge|massive)\s+$/i,
302
+ targetType: 'thing',
303
+ confidence: 0.55,
304
+ evidence: 'Size attribute indicates physical thing'
305
+ },
306
+ {
307
+ pattern: /\b(?:expensive|cheap|affordable|costly)\s+$/i,
308
+ targetType: 'thing',
309
+ confidence: 0.60,
310
+ evidence: 'Price attribute indicates purchasable thing'
311
+ },
312
+ {
313
+ pattern: /\b(?:annual|monthly|weekly|daily)\s+$/i,
314
+ targetType: 'event',
315
+ confidence: 0.65,
316
+ evidence: 'Frequency indicates recurring event'
317
+ }
318
+ ]);
319
+ // Document patterns - information relationships
320
+ this.addPatterns([
321
+ {
322
+ pattern: /\b(?:chapter (?:in|of)|section (?:in|of)|page in)\s+$/i,
323
+ targetType: 'document',
324
+ confidence: 0.75,
325
+ evidence: 'Document structure indicates document'
326
+ },
327
+ {
328
+ pattern: /\b(?:author of|wrote|published)\s+$/i,
329
+ targetType: 'document',
330
+ confidence: 0.70,
331
+ evidence: 'Authorship indicates document'
332
+ },
333
+ {
334
+ pattern: /\b(?:reference to|citation of|mentioned in)\s+$/i,
335
+ targetType: 'document',
336
+ confidence: 0.65,
337
+ evidence: 'Citation indicates document'
338
+ }
339
+ ]);
340
+ // Project patterns - work relationships
341
+ this.addPatterns([
342
+ {
343
+ pattern: /\b(?:milestone in|phase of|stage of)\s+$/i,
344
+ targetType: 'project',
345
+ confidence: 0.70,
346
+ evidence: 'Project structure indicates project'
347
+ },
348
+ {
349
+ pattern: /\b(?:deliverable for|outcome of|goal of)\s+$/i,
350
+ targetType: 'project',
351
+ confidence: 0.65,
352
+ evidence: 'Project objective indicates project'
353
+ },
354
+ {
355
+ pattern: /\b(?:working on|contributing to|involved in)\s+$/i,
356
+ targetType: 'project',
357
+ confidence: 0.60,
358
+ evidence: 'Work engagement indicates project'
359
+ }
360
+ ]);
361
+ }
362
+ /**
363
+ * Add relationship patterns in bulk
364
+ */
365
+ addPatterns(patterns) {
366
+ this.relationshipPatterns.push(...patterns);
367
+ }
368
+ /**
369
+ * Classify entity type using context-based signals
370
+ *
371
+ * Main entry point - checks relationship patterns in definition/context
372
+ *
373
+ * @param candidate Entity text to classify
374
+ * @param context Context with definition and related entities
375
+ * @returns TypeSignal with classification result
376
+ */
377
+ async classify(candidate, context) {
378
+ this.stats.calls++;
379
+ // Context signal requires context to work
380
+ if (!context?.definition && !context?.allTerms) {
381
+ return null;
382
+ }
383
+ // Check cache first
384
+ const cacheKey = this.getCacheKey(candidate, context);
385
+ const cached = this.getFromCache(cacheKey);
386
+ if (cached !== undefined) {
387
+ this.stats.cacheHits++;
388
+ return cached;
389
+ }
390
+ try {
391
+ // Build search text from context
392
+ const searchText = this.buildSearchText(candidate, context);
393
+ // Check relationship patterns
394
+ const relationshipMatch = this.matchRelationshipPatterns(candidate, searchText);
395
+ // Check attribute patterns
396
+ const attributeMatch = this.matchAttributePatterns(candidate, searchText);
397
+ // Combine results
398
+ const result = this.combineResults([relationshipMatch, attributeMatch]);
399
+ // Cache result (including nulls to avoid recomputation)
400
+ if (!result || result.confidence >= this.options.minConfidence) {
401
+ this.addToCache(cacheKey, result);
402
+ }
403
+ return result;
404
+ }
405
+ catch (error) {
406
+ // Graceful degradation - return null instead of throwing
407
+ console.warn(`ContextSignal error for "${candidate}":`, error);
408
+ return null;
409
+ }
410
+ }
411
+ /**
412
+ * Build search text from candidate and context
413
+ *
414
+ * Extracts text around candidate to find relationship patterns
415
+ */
416
+ buildSearchText(candidate, context) {
417
+ const parts = [];
418
+ // Add definition if available
419
+ if (context.definition) {
420
+ parts.push(context.definition);
421
+ }
422
+ // Add related terms if available
423
+ if (context.allTerms && context.allTerms.length > 0) {
424
+ parts.push(...context.allTerms);
425
+ }
426
+ return parts.join(' ').toLowerCase();
427
+ }
428
+ /**
429
+ * Match relationship patterns in context
430
+ *
431
+ * Looks for patterns like "CEO of X" where X is the candidate
432
+ */
433
+ matchRelationshipPatterns(candidate, searchText) {
434
+ const candidateLower = candidate.toLowerCase();
435
+ const matches = [];
436
+ // Find all occurrences of candidate in search text
437
+ // Only use word boundaries if candidate is all word characters
438
+ const useWordBoundaries = /^[a-z0-9_]+$/i.test(candidateLower);
439
+ const pattern = useWordBoundaries
440
+ ? `\\b${this.escapeRegex(candidateLower)}\\b`
441
+ : this.escapeRegex(candidateLower);
442
+ const regex = new RegExp(pattern, 'gi');
443
+ let match;
444
+ while ((match = regex.exec(searchText)) !== null) {
445
+ const beforeText = searchText.substring(Math.max(0, match.index - 100), match.index);
446
+ // Check each pattern against the text before the candidate
447
+ for (const pattern of this.relationshipPatterns) {
448
+ // Skip attribute patterns in relationship matching
449
+ if (pattern.evidence.includes('attribute'))
450
+ continue;
451
+ const patternMatch = pattern.pattern.test(beforeText);
452
+ if (patternMatch) {
453
+ matches.push({ pattern, matchText: beforeText });
454
+ }
455
+ }
456
+ }
457
+ if (matches.length === 0)
458
+ return null;
459
+ // Find best match
460
+ let bestMatch = matches[0];
461
+ for (const match of matches) {
462
+ if (match.pattern.confidence > bestMatch.pattern.confidence) {
463
+ bestMatch = match;
464
+ }
465
+ }
466
+ this.stats.relationshipMatches++;
467
+ return {
468
+ source: 'context-relationship',
469
+ type: bestMatch.pattern.targetType,
470
+ confidence: bestMatch.pattern.confidence,
471
+ evidence: bestMatch.pattern.evidence,
472
+ metadata: {
473
+ relationshipPattern: bestMatch.pattern.pattern.source,
474
+ contextMatch: bestMatch.matchText.substring(Math.max(0, bestMatch.matchText.length - 50))
475
+ }
476
+ };
477
+ }
478
+ /**
479
+ * Match attribute patterns in context
480
+ *
481
+ * Looks for descriptive patterns like "fast X" where X is the candidate
482
+ */
483
+ matchAttributePatterns(candidate, searchText) {
484
+ const candidateLower = candidate.toLowerCase();
485
+ const matches = [];
486
+ // Find all occurrences of candidate in search text
487
+ // Only use word boundaries if candidate is all word characters
488
+ const useWordBoundaries = /^[a-z0-9_]+$/i.test(candidateLower);
489
+ const pattern = useWordBoundaries
490
+ ? `\\b${this.escapeRegex(candidateLower)}\\b`
491
+ : this.escapeRegex(candidateLower);
492
+ const regex = new RegExp(pattern, 'gi');
493
+ let match;
494
+ while ((match = regex.exec(searchText)) !== null) {
495
+ const beforeText = searchText.substring(Math.max(0, match.index - 50), match.index);
496
+ // Check each attribute pattern against the text before the candidate
497
+ for (const pattern of this.relationshipPatterns) {
498
+ // Only check attribute patterns
499
+ if (!pattern.evidence.includes('attribute'))
500
+ continue;
501
+ const patternMatch = pattern.pattern.test(beforeText);
502
+ if (patternMatch) {
503
+ matches.push({ pattern, matchText: beforeText });
504
+ }
505
+ }
506
+ }
507
+ if (matches.length === 0)
508
+ return null;
509
+ // Find best match
510
+ let bestMatch = matches[0];
511
+ for (const match of matches) {
512
+ if (match.pattern.confidence > bestMatch.pattern.confidence) {
513
+ bestMatch = match;
514
+ }
515
+ }
516
+ this.stats.attributeMatches++;
517
+ return {
518
+ source: 'context-attribute',
519
+ type: bestMatch.pattern.targetType,
520
+ confidence: bestMatch.pattern.confidence,
521
+ evidence: bestMatch.pattern.evidence,
522
+ metadata: {
523
+ relationshipPattern: bestMatch.pattern.pattern.source,
524
+ contextMatch: bestMatch.matchText
525
+ }
526
+ };
527
+ }
528
+ /**
529
+ * Combine results from relationship and attribute matching
530
+ *
531
+ * Prefers relationship matches over attribute matches
532
+ */
533
+ combineResults(matches) {
534
+ // Filter out null matches
535
+ const validMatches = matches.filter((m) => m !== null);
536
+ if (validMatches.length === 0)
537
+ return null;
538
+ // Prefer relationship matches over attribute matches
539
+ const relationshipMatch = validMatches.find(m => m.source === 'context-relationship');
540
+ if (relationshipMatch && relationshipMatch.confidence >= this.options.minConfidence) {
541
+ return relationshipMatch;
542
+ }
543
+ // Fall back to attribute match
544
+ const attributeMatch = validMatches.find(m => m.source === 'context-attribute');
545
+ if (attributeMatch && attributeMatch.confidence >= this.options.minConfidence) {
546
+ return attributeMatch;
547
+ }
548
+ // If multiple matches of same type, use highest confidence
549
+ validMatches.sort((a, b) => b.confidence - a.confidence);
550
+ const best = validMatches[0];
551
+ if (best.confidence >= this.options.minConfidence) {
552
+ return best;
553
+ }
554
+ return null;
555
+ }
556
+ /**
557
+ * Get statistics about signal performance
558
+ */
559
+ getStats() {
560
+ return {
561
+ ...this.stats,
562
+ cacheSize: this.cache.size,
563
+ cacheHitRate: this.stats.calls > 0 ? this.stats.cacheHits / this.stats.calls : 0,
564
+ relationshipMatchRate: this.stats.calls > 0 ? this.stats.relationshipMatches / this.stats.calls : 0,
565
+ attributeMatchRate: this.stats.calls > 0 ? this.stats.attributeMatches / this.stats.calls : 0
566
+ };
567
+ }
568
+ /**
569
+ * Reset statistics (useful for testing)
570
+ */
571
+ resetStats() {
572
+ this.stats = {
573
+ calls: 0,
574
+ cacheHits: 0,
575
+ relationshipMatches: 0,
576
+ attributeMatches: 0,
577
+ combinedMatches: 0
578
+ };
579
+ }
580
+ /**
581
+ * Clear cache
582
+ */
583
+ clearCache() {
584
+ this.cache.clear();
585
+ this.cacheOrder = [];
586
+ }
587
+ /**
588
+ * Get pattern count (for testing)
589
+ */
590
+ getPatternCount() {
591
+ return this.relationshipPatterns.length;
592
+ }
593
+ // ========== Private Helper Methods ==========
594
+ /**
595
+ * Generate cache key from candidate and context
596
+ */
597
+ getCacheKey(candidate, context) {
598
+ const normalized = candidate.toLowerCase().trim();
599
+ if (!context?.definition)
600
+ return normalized;
601
+ return `${normalized}:${context.definition.substring(0, 50)}`;
602
+ }
603
+ /**
604
+ * Get from LRU cache (returns undefined if not found, null if cached as null)
605
+ */
606
+ getFromCache(key) {
607
+ // Check if key exists in cache (including null values)
608
+ if (!this.cache.has(key))
609
+ return undefined;
610
+ const cached = this.cache.get(key);
611
+ // Move to end (most recently used)
612
+ this.cacheOrder = this.cacheOrder.filter(k => k !== key);
613
+ this.cacheOrder.push(key);
614
+ return cached ?? null;
615
+ }
616
+ /**
617
+ * Add to LRU cache with eviction
618
+ */
619
+ addToCache(key, value) {
620
+ // Add to cache
621
+ this.cache.set(key, value);
622
+ this.cacheOrder.push(key);
623
+ // Evict oldest if over limit
624
+ if (this.cache.size > this.options.cacheSize) {
625
+ const oldest = this.cacheOrder.shift();
626
+ if (oldest) {
627
+ this.cache.delete(oldest);
628
+ }
629
+ }
630
+ }
631
+ /**
632
+ * Escape special regex characters
633
+ */
634
+ escapeRegex(str) {
635
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
636
+ }
637
+ }
638
+ /**
639
+ * Create a new ContextSignal instance
640
+ *
641
+ * Convenience factory function
642
+ */
643
+ export function createContextSignal(brain, options) {
644
+ return new ContextSignal(brain, options);
645
+ }
646
+ //# sourceMappingURL=ContextSignal.js.map