mark-improving-agent 2.2.3 → 2.2.5

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,177 @@
1
+ /**
2
+ * Hybrid Memory Search
3
+ *
4
+ * Combines vector similarity search with BM25 keyword matching and knowledge graph traversal
5
+ * Based on Dakera AI's hybrid search architecture (87.8% LoCoMo accuracy)
6
+ *
7
+ * @module core/memory
8
+ * @fileoverview Hybrid search combining vector + BM25 + knowledge graph for superior recall
9
+ */
10
+ import { createLogger } from '../../utils/logger.js';
11
+ const logger = createLogger('HybridSearch');
12
+ /**
13
+ * Default hybrid search configuration
14
+ */
15
+ export const DEFAULT_HYBRID_CONFIG = {
16
+ vectorWeight: 0.5,
17
+ bm25Weight: 0.3,
18
+ kgWeight: 0.2,
19
+ bm25: { k1: 1.5, b: 0.75 },
20
+ minScore: 0.1,
21
+ maxResults: 20,
22
+ };
23
+ /**
24
+ * Tokenizer for BM25
25
+ */
26
+ function tokenize(text) {
27
+ return text
28
+ .toLowerCase()
29
+ .replace(/[^\w\s]/g, ' ')
30
+ .split(/\s+/)
31
+ .filter(token => token.length > 2);
32
+ }
33
+ /**
34
+ * Create a BM25 index from memory entries
35
+ */
36
+ export function createBM25Index(entries) {
37
+ const index = {
38
+ docLengths: new Map(),
39
+ termDocFreq: new Map(),
40
+ avgDocLength: 0,
41
+ totalDocs: entries.length,
42
+ invertedIndex: new Map(),
43
+ };
44
+ let totalLength = 0;
45
+ for (const entry of entries) {
46
+ const tokens = tokenize(entry.content);
47
+ const docLength = tokens.length;
48
+ index.docLengths.set(entry.id, docLength);
49
+ totalLength += docLength;
50
+ // Count term frequencies
51
+ const termFreq = new Map();
52
+ for (const token of tokens) {
53
+ termFreq.set(token, (termFreq.get(token) ?? 0) + 1);
54
+ if (!index.invertedIndex.has(token)) {
55
+ index.invertedIndex.set(token, new Map());
56
+ }
57
+ const posting = index.invertedIndex.get(token);
58
+ posting.set(entry.id, termFreq.get(token));
59
+ }
60
+ // Update document frequency
61
+ for (const token of new Set(tokens)) {
62
+ index.termDocFreq.set(token, (index.termDocFreq.get(token) ?? 0) + 1);
63
+ }
64
+ }
65
+ index.avgDocLength = totalLength / Math.max(entries.length, 1);
66
+ return index;
67
+ }
68
+ /**
69
+ * Calculate BM25 score for a single document
70
+ */
71
+ export function bm25Score(index, docId, queryTokens, config) {
72
+ const docLength = index.docLengths.get(docId) ?? 0;
73
+ let score = 0;
74
+ for (const token of queryTokens) {
75
+ const tf = index.invertedIndex.get(token)?.get(docId) ?? 0;
76
+ if (tf === 0)
77
+ continue;
78
+ const df = index.termDocFreq.get(token) ?? 0;
79
+ if (df === 0)
80
+ continue;
81
+ const idf = Math.log((index.totalDocs - df + 0.5) / (df + 0.5) + 1);
82
+ const tfComponent = (tf * (config.k1 + 1)) / (tf + config.k1 * (1 - config.b + config.b * (docLength / index.avgDocLength)));
83
+ score += idf * tfComponent;
84
+ }
85
+ return score;
86
+ }
87
+ /**
88
+ * Normalize BM25 scores to 0-1 range
89
+ */
90
+ export function normalizeBM25Scores(scores) {
91
+ const maxScore = Math.max(...Array.from(scores.values()), 1);
92
+ const normalized = new Map();
93
+ for (const [docId, score] of scores) {
94
+ normalized.set(docId, score / maxScore);
95
+ }
96
+ return normalized;
97
+ }
98
+ /**
99
+ * Create a hybrid search engine
100
+ */
101
+ export function createHybridSearchEngine(initialEntries = [], config = {}) {
102
+ const fullConfig = {
103
+ ...DEFAULT_HYBRID_CONFIG,
104
+ ...config,
105
+ bm25: { ...DEFAULT_HYBRID_CONFIG.bm25, ...config.bm25 },
106
+ };
107
+ let bm25Index = createBM25Index(initialEntries);
108
+ function rebuildIndex(entries) {
109
+ logger.info(`Rebuilding BM25 index with ${entries.length} documents`);
110
+ bm25Index = createBM25Index(entries);
111
+ }
112
+ function search(query, entries, options) {
113
+ const searchConfig = {
114
+ ...fullConfig,
115
+ ...options?.config,
116
+ };
117
+ // Tokenize query for BM25
118
+ const queryTokens = tokenize(query);
119
+ // Calculate BM25 scores for all entries
120
+ const bm25Scores = new Map();
121
+ for (const entry of entries) {
122
+ const score = bm25Score(bm25Index, entry.id, queryTokens, searchConfig.bm25);
123
+ if (score > 0) {
124
+ bm25Scores.set(entry.id, score);
125
+ }
126
+ }
127
+ const normalizedBM25 = normalizeBM25Scores(bm25Scores);
128
+ // Calculate KG scores based on connection count
129
+ const kgScores = new Map();
130
+ if (options?.kgConnections) {
131
+ const maxConnections = Math.max(...Array.from(options.kgConnections.values()).map(arr => arr.length), 1);
132
+ for (const entry of entries) {
133
+ const connections = options.kgConnections.get(entry.id)?.length ?? 0;
134
+ kgScores.set(entry.id, connections / maxConnections);
135
+ }
136
+ }
137
+ // Combine scores
138
+ const combinedResults = new Map();
139
+ for (const entry of entries) {
140
+ const vectorScore = options?.vectorScores?.get(entry.id) ?? 0;
141
+ const bm25Score = normalizedBM25.get(entry.id) ?? 0;
142
+ const kgScore = kgScores.get(entry.id) ?? 0;
143
+ // Weighted combination
144
+ const combinedScore = (vectorScore * searchConfig.vectorWeight) +
145
+ (bm25Score * searchConfig.bm25Weight) +
146
+ (kgScore * searchConfig.kgWeight);
147
+ if (combinedScore >= searchConfig.minScore) {
148
+ combinedResults.set(entry.id, {
149
+ entry,
150
+ score: combinedScore,
151
+ combinedScore,
152
+ vectorScore,
153
+ bm25Score,
154
+ kgScore,
155
+ reason: 'semantic',
156
+ });
157
+ }
158
+ }
159
+ // Sort by combined score
160
+ const results = Array.from(combinedResults.values())
161
+ .sort((a, b) => b.combinedScore - a.combinedScore)
162
+ .slice(0, searchConfig.maxResults);
163
+ logger.debug(`Hybrid search for "${query}": ${results.length} results`);
164
+ return results;
165
+ }
166
+ function getStats() {
167
+ return {
168
+ indexedDocs: bm25Index.totalDocs,
169
+ avgDocLength: Math.round(bm25Index.avgDocLength),
170
+ };
171
+ }
172
+ return {
173
+ search,
174
+ rebuildIndex,
175
+ getStats,
176
+ };
177
+ }
@@ -7,3 +7,5 @@ export * from './dream-consolidation.js';
7
7
  export * from './spaced-repetition.js';
8
8
  export * from './hopfield-network.js';
9
9
  export * from './adaptive-rag.js';
10
+ export { createContextFragmentationEngine } from './context-fragmentation.js';
11
+ export { createHybridSearchEngine, createBM25Index, bm25Score, normalizeBM25Scores, DEFAULT_HYBRID_CONFIG } from './hybrid-search.js';
@@ -0,0 +1,437 @@
1
+ /**
2
+ * Concept Graph - Structured Knowledge Representation
3
+ *
4
+ * Builds and maintains a graph of typed concepts extracted from
5
+ * memory entries. Each concept has structured metadata (frontmatter-
6
+ * style) and relationships to other concepts.
7
+ *
8
+ * Based on oh-my-ontology's approach: markdown frontmatter as graph,
9
+ * but adapted for AI agent memory systems.
10
+ *
11
+ * @module core/ontology
12
+ * @fileoverview Concept graph implementation
13
+ */
14
+ import { randomUUID } from 'crypto';
15
+ import { createLogger } from '../../utils/logger.js';
16
+ const logger = createLogger('[ConceptGraph]');
17
+ // ============================================================
18
+ // Constants
19
+ // ============================================================
20
+ const VALID_DOMAINS = [
21
+ 'auth', 'memory', 'cognition', 'collaboration', 'identity',
22
+ 'evolution', 'consciousness', 'psychology', 'security', 'meta'
23
+ ];
24
+ const VALID_KINDS = [
25
+ 'entity', 'capability', 'principle', 'pattern', 'process', 'rule'
26
+ ];
27
+ const DOMAIN_ALIASES = {
28
+ 'mcp': 'collaboration',
29
+ 'multi-agent': 'collaboration',
30
+ 'agent': 'collaboration',
31
+ 'memory': 'memory',
32
+ ' episodic': 'memory',
33
+ 'semantic': 'memory',
34
+ 'consciousness': 'consciousness',
35
+ 'identity': 'identity',
36
+ 'self': 'identity',
37
+ 'evolution': 'evolution',
38
+ 'learn': 'evolution',
39
+ 'psychology': 'psychology',
40
+ 'emotion': 'psychology',
41
+ 'security': 'security',
42
+ 'safety': 'security',
43
+ };
44
+ // ============================================================
45
+ // Utility Functions
46
+ // ============================================================
47
+ function normalizeDomain(domain) {
48
+ const lower = domain.toLowerCase().replace(/[_-]/g, '-');
49
+ return DOMAIN_ALIASES[lower] || lower;
50
+ }
51
+ function slugify(text) {
52
+ return text
53
+ .toLowerCase()
54
+ .replace(/[^a-z0-9]+/g, '-')
55
+ .replace(/^-|-$/g, '')
56
+ .slice(0, 64);
57
+ }
58
+ function extractKeywords(text) {
59
+ const stopWords = new Set([
60
+ 'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
61
+ 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
62
+ 'should', 'may', 'might', 'must', 'can', 'need', 'to', 'of', 'in',
63
+ 'for', 'on', 'with', 'at', 'by', 'from', 'as', 'into', 'through',
64
+ 'and', 'or', 'if', 'but', 'so', 'than', 'then', 'when', 'where',
65
+ 'why', 'how', 'what', 'which', 'who', 'whom', 'this', 'that', 'these',
66
+ 'those', 'it', 'its', 'they', 'their', 'them', 'we', 'our', 'us',
67
+ '你', '我', '他', '她', '它', '的', '是', '在', '了', '和', '与'
68
+ ]);
69
+ const words = [];
70
+ const tokens = text.toLowerCase().split(/\s+/);
71
+ for (const token of tokens) {
72
+ const cleaned = token.replace(/[^a-zA-Z\u4e00-\u9fff]/g, '');
73
+ if (cleaned.length >= 3 && !stopWords.has(cleaned)) {
74
+ words.push(cleaned);
75
+ }
76
+ }
77
+ return [...new Set(words)].slice(0, 10);
78
+ }
79
+ const DEFAULT_OPTIONS = {
80
+ minConfidence: 0.5,
81
+ orphanMaxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
82
+ autoLink: true,
83
+ };
84
+ export function createConceptGraph(options = {}) {
85
+ const opts = { ...DEFAULT_OPTIONS, ...options };
86
+ const concepts = new Map();
87
+ const conceptsBySlug = new Map(); // key: domain/slug
88
+ const relations = new Map();
89
+ const memoryToConcepts = new Map(); // memoryId -> conceptIds
90
+ function buildSlugKey(domain, slug) {
91
+ return `${domain}/${slug}`;
92
+ }
93
+ function createConcept(data) {
94
+ const now = Date.now();
95
+ const domain = normalizeDomain(data.domain);
96
+ const slug = data.slug || slugify(data.name);
97
+ // Check for duplicate slug in domain
98
+ const key = buildSlugKey(domain, slug);
99
+ if (conceptsBySlug.has(key)) {
100
+ const existing = conceptsBySlug.get(key);
101
+ // Merge sources if same slug
102
+ if (data.sourceMemories) {
103
+ existing.sourceMemories = [...new Set([...existing.sourceMemories, ...data.sourceMemories])];
104
+ existing.updatedAt = now;
105
+ }
106
+ logger.info(`Merged with existing concept: ${key}`);
107
+ return existing;
108
+ }
109
+ const concept = {
110
+ id: data.id || randomUUID(),
111
+ slug,
112
+ name: data.name,
113
+ domain,
114
+ kind: data.kind || 'entity',
115
+ description: data.description || '',
116
+ properties: data.properties || {},
117
+ dependsOn: data.dependsOn || [],
118
+ dependents: data.dependents || [],
119
+ sourceMemories: data.sourceMemories || [],
120
+ confidence: data.confidence || opts.minConfidence,
121
+ referenceCount: 0,
122
+ lastReferenced: now,
123
+ createdAt: now,
124
+ updatedAt: now,
125
+ };
126
+ concepts.set(concept.id, concept);
127
+ conceptsBySlug.set(key, concept);
128
+ logger.info(`Created concept: ${key} (${concept.kind})`);
129
+ return concept;
130
+ }
131
+ function getConcept(id) {
132
+ return concepts.get(id) || null;
133
+ }
134
+ function getConceptBySlug(slug, domain) {
135
+ return conceptsBySlug.get(buildSlugKey(normalizeDomain(domain), slug)) || null;
136
+ }
137
+ function updateConcept(id, updates) {
138
+ const concept = concepts.get(id);
139
+ if (!concept)
140
+ return null;
141
+ // Update slug if name/domain changed
142
+ if (updates.domain && updates.domain !== concept.domain) {
143
+ const oldKey = buildSlugKey(concept.domain, concept.slug);
144
+ conceptsBySlug.delete(oldKey);
145
+ concept.domain = normalizeDomain(updates.domain);
146
+ }
147
+ if (updates.name && updates.name !== concept.name) {
148
+ const oldKey = buildSlugKey(concept.domain, concept.slug);
149
+ conceptsBySlug.delete(oldKey);
150
+ concept.slug = slugify(updates.name);
151
+ }
152
+ Object.assign(concept, updates, { updatedAt: Date.now() });
153
+ const newKey = buildSlugKey(concept.domain, concept.slug);
154
+ conceptsBySlug.set(newKey, concept);
155
+ logger.info(`Updated concept: ${concept.id}`);
156
+ return concept;
157
+ }
158
+ function deleteConcept(id) {
159
+ const concept = concepts.get(id);
160
+ if (!concept)
161
+ return false;
162
+ // Remove from slug index
163
+ const key = buildSlugKey(concept.domain, concept.slug);
164
+ conceptsBySlug.delete(key);
165
+ // Remove relations involving this concept
166
+ for (const [relId, rel] of relations) {
167
+ if (rel.sourceId === id || rel.targetId === id) {
168
+ relations.delete(relId);
169
+ }
170
+ }
171
+ // Remove from memory links
172
+ for (const [, conceptIds] of memoryToConcepts) {
173
+ conceptIds.delete(id);
174
+ }
175
+ // Update dependents of concepts this depended on
176
+ for (const depId of concept.dependsOn) {
177
+ const dep = concepts.get(depId);
178
+ if (dep) {
179
+ dep.dependents = dep.dependents.filter(d => d !== id);
180
+ }
181
+ }
182
+ concepts.delete(id);
183
+ logger.info(`Deleted concept: ${id}`);
184
+ return true;
185
+ }
186
+ function createRelation(sourceId, targetId, relation, weight = 0.8) {
187
+ const source = concepts.get(sourceId);
188
+ const target = concepts.get(targetId);
189
+ if (!source || !target) {
190
+ logger.warn(`Cannot create relation: concept not found`);
191
+ return null;
192
+ }
193
+ // Check for duplicate
194
+ for (const rel of relations.values()) {
195
+ if (rel.sourceId === sourceId && rel.targetId === targetId && rel.relation === relation) {
196
+ return rel;
197
+ }
198
+ }
199
+ const rel = {
200
+ id: randomUUID(),
201
+ sourceId,
202
+ targetId,
203
+ relation,
204
+ weight,
205
+ bidirectional: ['associates', 'part-of'].includes(relation),
206
+ createdAt: Date.now(),
207
+ };
208
+ relations.set(rel.id, rel);
209
+ // Update concept dependency lists
210
+ if (!source.dependsOn.includes(targetId) && (relation === 'depends-on' || relation === 'enables')) {
211
+ source.dependsOn.push(targetId);
212
+ }
213
+ if (!target.dependents.includes(sourceId)) {
214
+ target.dependents.push(sourceId);
215
+ }
216
+ logger.info(`Created relation: ${source.slug} --[${relation}]--> ${target.slug}`);
217
+ return rel;
218
+ }
219
+ function deleteRelation(relationId) {
220
+ const rel = relations.get(relationId);
221
+ if (!rel)
222
+ return false;
223
+ // Update concept dependency lists
224
+ const source = concepts.get(rel.sourceId);
225
+ const target = concepts.get(rel.targetId);
226
+ if (source) {
227
+ source.dependsOn = source.dependsOn.filter(id => id !== rel.targetId);
228
+ }
229
+ if (target) {
230
+ target.dependents = target.dependents.filter(id => id !== rel.sourceId);
231
+ }
232
+ relations.delete(relationId);
233
+ return true;
234
+ }
235
+ function getConceptRelations(conceptId) {
236
+ const result = [];
237
+ for (const rel of relations.values()) {
238
+ if (rel.sourceId === conceptId || rel.targetId === conceptId) {
239
+ result.push(rel);
240
+ }
241
+ }
242
+ return result;
243
+ }
244
+ function search(query) {
245
+ const results = [];
246
+ const now = Date.now();
247
+ for (const concept of concepts.values()) {
248
+ let score = 0;
249
+ // Domain filter
250
+ if (query.domain) {
251
+ if (concept.domain !== normalizeDomain(query.domain))
252
+ continue;
253
+ score += 0.3;
254
+ }
255
+ // Kind filter
256
+ if (query.kind) {
257
+ if (concept.kind !== query.kind)
258
+ continue;
259
+ score += 0.2;
260
+ }
261
+ // Slug exact match
262
+ if (query.slug) {
263
+ if (concept.slug !== query.slug)
264
+ continue;
265
+ score += 0.4;
266
+ }
267
+ // Text search
268
+ if (query.search) {
269
+ const searchLower = query.search.toLowerCase();
270
+ const searchTerms = extractKeywords(query.search);
271
+ if (concept.name.toLowerCase().includes(searchLower)) {
272
+ score += 0.3;
273
+ }
274
+ if (concept.description.toLowerCase().includes(searchLower)) {
275
+ score += 0.2;
276
+ }
277
+ if (searchTerms.some(term => {
278
+ const descLower = concept.description.toLowerCase();
279
+ const propStr = JSON.stringify(concept.properties).toLowerCase();
280
+ return descLower.includes(term) || propStr.includes(term);
281
+ })) {
282
+ score += 0.15;
283
+ }
284
+ }
285
+ // Confidence filter
286
+ if (query.minConfidence && concept.confidence < query.minConfidence) {
287
+ continue;
288
+ }
289
+ if (score > 0) {
290
+ results.push({ concept, score });
291
+ }
292
+ }
293
+ // Sort by score, then by reference count and recency
294
+ results.sort((a, b) => {
295
+ if (b.score !== a.score)
296
+ return b.score - a.score;
297
+ const aEngagement = a.concept.referenceCount * (1 / Math.max(1, now - a.concept.lastReferenced));
298
+ const bEngagement = b.concept.referenceCount * (1 / Math.max(1, now - b.concept.lastReferenced));
299
+ return bEngagement - aEngagement;
300
+ });
301
+ const limit = query.limit || 20;
302
+ return results.slice(0, limit).map(r => r.concept);
303
+ }
304
+ function infer(conceptId, depth = 2) {
305
+ const rootConcept = concepts.get(conceptId);
306
+ if (!rootConcept)
307
+ return [];
308
+ const results = [];
309
+ const visited = new Set([conceptId]);
310
+ function traverse(currentId, path, currentDepth, currentConcept) {
311
+ if (currentDepth >= depth)
312
+ return;
313
+ const relations = getConceptRelations(currentId);
314
+ for (const rel of relations) {
315
+ const neighborId = rel.sourceId === currentId ? rel.targetId : rel.sourceId;
316
+ if (visited.has(neighborId))
317
+ continue;
318
+ visited.add(neighborId);
319
+ const neighbor = concepts.get(neighborId);
320
+ if (!neighbor)
321
+ continue;
322
+ const newPath = [...path, neighbor.slug];
323
+ const reasoning = `Through ${rel.relation} relation from ${path[path.length - 1] || currentConcept.slug}`;
324
+ results.push({
325
+ path: newPath,
326
+ inferredConcept: neighbor,
327
+ confidence: currentConcept.confidence * rel.weight * Math.pow(0.9, currentDepth),
328
+ reasoning,
329
+ });
330
+ traverse(neighborId, newPath, currentDepth + 1, neighbor);
331
+ }
332
+ }
333
+ traverse(conceptId, [rootConcept.slug], 0, rootConcept);
334
+ return results.sort((a, b) => b.confidence - a.confidence);
335
+ }
336
+ function referenceConcept(conceptId) {
337
+ const concept = concepts.get(conceptId);
338
+ if (concept) {
339
+ concept.referenceCount++;
340
+ concept.lastReferenced = Date.now();
341
+ }
342
+ }
343
+ function linkMemoryToConcepts(memoryId, conceptIds) {
344
+ memoryToConcepts.set(memoryId, new Set(conceptIds));
345
+ for (const cid of conceptIds) {
346
+ const concept = concepts.get(cid);
347
+ if (concept && !concept.sourceMemories.includes(memoryId)) {
348
+ concept.sourceMemories.push(memoryId);
349
+ }
350
+ }
351
+ }
352
+ function getOrphanConcepts() {
353
+ const now = Date.now();
354
+ const orphans = [];
355
+ for (const concept of concepts.values()) {
356
+ const relations = getConceptRelations(concept.id);
357
+ const hasConnections = relations.length > 0 || concept.dependsOn.length > 0 || concept.dependents.length > 0;
358
+ const isStale = now - concept.lastReferenced > opts.orphanMaxAge;
359
+ if (!hasConnections || isStale) {
360
+ orphans.push(concept);
361
+ }
362
+ }
363
+ return orphans;
364
+ }
365
+ function getStats() {
366
+ const now = Date.now();
367
+ const domains = new Set();
368
+ let totalConnections = 0;
369
+ let mostConnected = [];
370
+ const orphanConcepts = getOrphanConcepts();
371
+ const recentlyReferenced = [];
372
+ for (const concept of concepts.values()) {
373
+ domains.add(concept.domain);
374
+ const rels = getConceptRelations(concept.id);
375
+ concept.referenceCount = rels.length + concept.dependsOn.length + concept.dependents.length;
376
+ totalConnections += rels.length;
377
+ if (now - concept.lastReferenced < 24 * 60 * 60 * 1000) {
378
+ recentlyReferenced.push(concept);
379
+ }
380
+ }
381
+ mostConnected = [...concepts.values()]
382
+ .sort((a, b) => b.referenceCount - a.referenceCount)
383
+ .slice(0, 5);
384
+ return {
385
+ totalConcepts: concepts.size,
386
+ totalRelations: relations.size,
387
+ domains: [...domains].sort(),
388
+ avgConnections: concepts.size > 0 ? totalConnections / concepts.size : 0,
389
+ mostConnected,
390
+ orphanConcepts,
391
+ recentlyReferenced: recentlyReferenced.sort((a, b) => b.lastReferenced - a.lastReferenced),
392
+ };
393
+ }
394
+ function exportData() {
395
+ return {
396
+ concepts: [...concepts.values()],
397
+ relations: [...relations.values()],
398
+ };
399
+ }
400
+ function importData(data) {
401
+ clear();
402
+ for (const concept of data.concepts) {
403
+ concepts.set(concept.id, concept);
404
+ conceptsBySlug.set(buildSlugKey(concept.domain, concept.slug), concept);
405
+ }
406
+ for (const rel of data.relations) {
407
+ relations.set(rel.id, rel);
408
+ }
409
+ logger.info(`Imported ${data.concepts.length} concepts, ${data.relations.length} relations`);
410
+ }
411
+ function clear() {
412
+ concepts.clear();
413
+ conceptsBySlug.clear();
414
+ relations.clear();
415
+ memoryToConcepts.clear();
416
+ logger.info('Cleared concept graph');
417
+ }
418
+ return {
419
+ createConcept,
420
+ getConcept,
421
+ getConceptBySlug,
422
+ updateConcept,
423
+ deleteConcept,
424
+ createRelation,
425
+ deleteRelation,
426
+ getConceptRelations,
427
+ search,
428
+ infer,
429
+ referenceConcept,
430
+ linkMemoryToConcepts,
431
+ getOrphanConcepts,
432
+ getStats,
433
+ exportData,
434
+ importData,
435
+ clear,
436
+ };
437
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Concept Ontology System
3
+ *
4
+ * Structured knowledge representation using typed concepts
5
+ * and relationships, inspired by oh-my-ontology's frontmatter
6
+ * graph approach. Enables AI-native concept reasoning.
7
+ *
8
+ * Key mechanisms:
9
+ * - Concept nodes with typed properties
10
+ * - Relationship edges with semantic meaning
11
+ * - Cross-reference resolution
12
+ * - Concept hierarchy (is-a, part-of)
13
+ * - Inference through relation traversal
14
+ *
15
+ * @module core/ontology
16
+ * @fileoverview Concept ontology for structured knowledge
17
+ */
18
+ export * from './concept-graph.js';
package/dist/index.js CHANGED
@@ -4,5 +4,8 @@ export { VERSION } from './version.js';
4
4
  export * from './core/psychology/index.js';
5
5
  export * from './core/cognition/index.js';
6
6
  export { createAdaptiveRAGMemory, formatRetrievalResult } from './core/memory/adaptive-rag.js';
7
+ export { createContextFragmentationEngine } from './core/memory/context-fragmentation.js';
8
+ export { createIdentityContinuityVerifier } from './core/identity/identity-continuity.js';
9
+ export { createMCPProtocol } from './core/collaboration/mcp-protocol.js';
7
10
  export { createTruthTeller, formatTruthStatement } from './core/truth-teller.js';
8
11
  export { createActiveInferenceEngine, formatFreeEnergyMetrics, formatBeliefState } from './core/cognition/active-inference.js';
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '2.2.3';
1
+ export const VERSION = '2.2.5';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mark-improving-agent",
3
- "version": "2.2.3",
3
+ "version": "2.2.5",
4
4
  "description": "Self-evolving AI agent with permanent memory, identity continuity, and self-evolution — for AI agents that need to remember, learn, and evolve across sessions",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",