@danielsimonjr/memory-mcp 0.7.1 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/edge-cases/edge-cases.test.js +406 -0
- package/dist/__tests__/file-path.test.js +5 -5
- package/dist/__tests__/integration/workflows.test.js +449 -0
- package/dist/__tests__/knowledge-graph.test.js +8 -3
- package/dist/__tests__/performance/benchmarks.test.js +411 -0
- package/dist/__tests__/unit/core/EntityManager.test.js +334 -0
- package/dist/__tests__/unit/core/GraphStorage.test.js +205 -0
- package/dist/__tests__/unit/core/RelationManager.test.js +274 -0
- package/dist/__tests__/unit/features/CompressionManager.test.js +350 -0
- package/dist/__tests__/unit/search/BasicSearch.test.js +311 -0
- package/dist/__tests__/unit/search/BooleanSearch.test.js +432 -0
- package/dist/__tests__/unit/search/FuzzySearch.test.js +448 -0
- package/dist/__tests__/unit/search/RankedSearch.test.js +379 -0
- package/dist/__tests__/unit/utils/levenshtein.test.js +77 -0
- package/dist/core/EntityManager.js +554 -0
- package/dist/core/GraphStorage.js +172 -0
- package/dist/core/KnowledgeGraphManager.js +400 -0
- package/dist/core/ObservationManager.js +129 -0
- package/dist/core/RelationManager.js +186 -0
- package/dist/core/TransactionManager.js +389 -0
- package/dist/core/index.js +9 -0
- package/dist/features/AnalyticsManager.js +222 -0
- package/dist/features/ArchiveManager.js +74 -0
- package/dist/features/BackupManager.js +311 -0
- package/dist/features/CompressionManager.js +310 -0
- package/dist/features/ExportManager.js +305 -0
- package/dist/features/HierarchyManager.js +219 -0
- package/dist/features/ImportExportManager.js +50 -0
- package/dist/features/ImportManager.js +328 -0
- package/dist/features/TagManager.js +210 -0
- package/dist/features/index.js +12 -0
- package/dist/index.js +13 -997
- package/dist/memory.jsonl +225 -0
- package/dist/search/BasicSearch.js +161 -0
- package/dist/search/BooleanSearch.js +304 -0
- package/dist/search/FuzzySearch.js +115 -0
- package/dist/search/RankedSearch.js +206 -0
- package/dist/search/SavedSearchManager.js +145 -0
- package/dist/search/SearchManager.js +305 -0
- package/dist/search/SearchSuggestions.js +57 -0
- package/dist/search/TFIDFIndexManager.js +217 -0
- package/dist/search/index.js +10 -0
- package/dist/server/MCPServer.js +889 -0
- package/dist/types/analytics.types.js +6 -0
- package/dist/types/entity.types.js +7 -0
- package/dist/types/import-export.types.js +7 -0
- package/dist/types/index.js +12 -0
- package/dist/types/search.types.js +7 -0
- package/dist/types/tag.types.js +6 -0
- package/dist/utils/constants.js +127 -0
- package/dist/utils/dateUtils.js +89 -0
- package/dist/utils/errors.js +121 -0
- package/dist/utils/index.js +13 -0
- package/dist/utils/levenshtein.js +62 -0
- package/dist/utils/logger.js +33 -0
- package/dist/utils/pathUtils.js +115 -0
- package/dist/utils/schemas.js +184 -0
- package/dist/utils/searchCache.js +209 -0
- package/dist/utils/tfidf.js +90 -0
- package/dist/utils/validationUtils.js +109 -0
- package/package.json +50 -48
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types Module - Barrel Export
|
|
3
|
+
*
|
|
4
|
+
* Central export point for all type definitions used throughout the
|
|
5
|
+
* Memory MCP Server. Import from this file to access any type.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { Entity, Relation, KnowledgeGraph, SearchResult } from './types/index.js';
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application Constants
|
|
3
|
+
*
|
|
4
|
+
* Centralized configuration constants for file paths, extensions, and default values.
|
|
5
|
+
*
|
|
6
|
+
* @module utils/constants
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* File extensions used by the memory system.
|
|
10
|
+
*/
|
|
11
|
+
export const FILE_EXTENSIONS = {
|
|
12
|
+
/** JSONL format for line-delimited JSON storage */
|
|
13
|
+
JSONL: '.jsonl',
|
|
14
|
+
/** Legacy JSON format (backward compatibility) */
|
|
15
|
+
JSON: '.json',
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* File name suffixes for auxiliary data files.
|
|
19
|
+
* These suffixes are appended to the base memory file name.
|
|
20
|
+
*/
|
|
21
|
+
export const FILE_SUFFIXES = {
|
|
22
|
+
/** Suffix for saved searches file */
|
|
23
|
+
SAVED_SEARCHES: '-saved-searches',
|
|
24
|
+
/** Suffix for tag aliases file */
|
|
25
|
+
TAG_ALIASES: '-tag-aliases',
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Default file names used by the memory system.
|
|
29
|
+
*/
|
|
30
|
+
export const DEFAULT_FILE_NAMES = {
|
|
31
|
+
/** Default memory file name */
|
|
32
|
+
MEMORY: 'memory',
|
|
33
|
+
/** Legacy memory file name (for backward compatibility) */
|
|
34
|
+
MEMORY_LEGACY: 'memory',
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Environment variable names used for configuration.
|
|
38
|
+
*/
|
|
39
|
+
export const ENV_VARS = {
|
|
40
|
+
/** Environment variable for custom memory file path */
|
|
41
|
+
MEMORY_FILE_PATH: 'MEMORY_FILE_PATH',
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Default base directory relative to the compiled code.
|
|
45
|
+
*/
|
|
46
|
+
export const DEFAULT_BASE_DIR = '../';
|
|
47
|
+
/**
|
|
48
|
+
* Log message prefixes for consistent logging.
|
|
49
|
+
*/
|
|
50
|
+
export const LOG_PREFIXES = {
|
|
51
|
+
/** Informational message prefix */
|
|
52
|
+
INFO: '[INFO]',
|
|
53
|
+
/** Error message prefix */
|
|
54
|
+
ERROR: '[ERROR]',
|
|
55
|
+
/** Warning message prefix */
|
|
56
|
+
WARN: '[WARN]',
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Similarity scoring weights for duplicate detection.
|
|
60
|
+
* These weights determine the relative importance of each factor
|
|
61
|
+
* when calculating entity similarity for duplicate detection.
|
|
62
|
+
*/
|
|
63
|
+
export const SIMILARITY_WEIGHTS = {
|
|
64
|
+
/** Name similarity weight (40%) - Uses Levenshtein distance */
|
|
65
|
+
NAME: 0.4,
|
|
66
|
+
/** Entity type match weight (20%) - Exact match required */
|
|
67
|
+
TYPE: 0.2,
|
|
68
|
+
/** Observation overlap weight (30%) - Uses Jaccard similarity */
|
|
69
|
+
OBSERVATION: 0.3,
|
|
70
|
+
/** Tag overlap weight (10%) - Uses Jaccard similarity */
|
|
71
|
+
TAG: 0.1,
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Default threshold for duplicate detection (80% similarity required).
|
|
75
|
+
*/
|
|
76
|
+
export const DEFAULT_DUPLICATE_THRESHOLD = 0.8;
|
|
77
|
+
/**
|
|
78
|
+
* Search result limits to prevent resource exhaustion.
|
|
79
|
+
*/
|
|
80
|
+
export const SEARCH_LIMITS = {
|
|
81
|
+
/** Default number of results to return */
|
|
82
|
+
DEFAULT: 50,
|
|
83
|
+
/** Maximum number of results allowed */
|
|
84
|
+
MAX: 200,
|
|
85
|
+
/** Minimum number of results (must be at least 1) */
|
|
86
|
+
MIN: 1,
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Entity importance range validation constants.
|
|
90
|
+
* Importance is used to prioritize entities (0 = lowest, 10 = highest).
|
|
91
|
+
*/
|
|
92
|
+
export const IMPORTANCE_RANGE = {
|
|
93
|
+
/** Minimum importance value */
|
|
94
|
+
MIN: 0,
|
|
95
|
+
/** Maximum importance value */
|
|
96
|
+
MAX: 10,
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Graph size limits to prevent resource exhaustion and ensure performance.
|
|
100
|
+
* These limits help maintain system stability and responsiveness.
|
|
101
|
+
*/
|
|
102
|
+
export const GRAPH_LIMITS = {
|
|
103
|
+
/** Maximum number of entities in the graph */
|
|
104
|
+
MAX_ENTITIES: 100000,
|
|
105
|
+
/** Maximum number of relations in the graph */
|
|
106
|
+
MAX_RELATIONS: 1000000,
|
|
107
|
+
/** Maximum graph file size in megabytes */
|
|
108
|
+
MAX_FILE_SIZE_MB: 500,
|
|
109
|
+
/** Maximum observations per entity */
|
|
110
|
+
MAX_OBSERVATIONS_PER_ENTITY: 1000,
|
|
111
|
+
/** Maximum tags per entity */
|
|
112
|
+
MAX_TAGS_PER_ENTITY: 100,
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Query complexity limits to prevent expensive query operations.
|
|
116
|
+
* These limits protect against denial-of-service through complex queries.
|
|
117
|
+
*/
|
|
118
|
+
export const QUERY_LIMITS = {
|
|
119
|
+
/** Maximum nesting depth for boolean queries */
|
|
120
|
+
MAX_DEPTH: 10,
|
|
121
|
+
/** Maximum number of terms in a single query */
|
|
122
|
+
MAX_TERMS: 50,
|
|
123
|
+
/** Maximum number of boolean operators (AND/OR/NOT) */
|
|
124
|
+
MAX_OPERATORS: 20,
|
|
125
|
+
/** Maximum query string length */
|
|
126
|
+
MAX_QUERY_LENGTH: 5000,
|
|
127
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Date Utilities
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for date parsing, validation, and range filtering.
|
|
5
|
+
* All dates use ISO 8601 format (YYYY-MM-DDTHH:mm:ss.sssZ).
|
|
6
|
+
*
|
|
7
|
+
* @module utils/dateUtils
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Check if a date falls within a specified range.
|
|
11
|
+
*
|
|
12
|
+
* @param date - ISO 8601 date string to check
|
|
13
|
+
* @param start - Optional start date (inclusive)
|
|
14
|
+
* @param end - Optional end date (inclusive)
|
|
15
|
+
* @returns True if date is within range
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* isWithinDateRange('2024-06-15T00:00:00Z', '2024-01-01T00:00:00Z', '2024-12-31T23:59:59Z'); // true
|
|
20
|
+
* isWithinDateRange('2024-06-15T00:00:00Z', '2024-07-01T00:00:00Z'); // false
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export function isWithinDateRange(date, start, end) {
|
|
24
|
+
const dateObj = new Date(date);
|
|
25
|
+
if (isNaN(dateObj.getTime())) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
if (start) {
|
|
29
|
+
const startObj = new Date(start);
|
|
30
|
+
if (isNaN(startObj.getTime())) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if (dateObj < startObj) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (end) {
|
|
38
|
+
const endObj = new Date(end);
|
|
39
|
+
if (isNaN(endObj.getTime())) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
if (dateObj > endObj) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Parse and validate date range strings.
|
|
50
|
+
*
|
|
51
|
+
* @param startDate - Optional ISO 8601 start date
|
|
52
|
+
* @param endDate - Optional ISO 8601 end date
|
|
53
|
+
* @returns Parsed Date objects or null
|
|
54
|
+
*/
|
|
55
|
+
export function parseDateRange(startDate, endDate) {
|
|
56
|
+
let start = null;
|
|
57
|
+
let end = null;
|
|
58
|
+
if (startDate) {
|
|
59
|
+
start = new Date(startDate);
|
|
60
|
+
if (isNaN(start.getTime())) {
|
|
61
|
+
start = null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (endDate) {
|
|
65
|
+
end = new Date(endDate);
|
|
66
|
+
if (isNaN(end.getTime())) {
|
|
67
|
+
end = null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { start, end };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Validate if a string is a valid ISO 8601 date.
|
|
74
|
+
*
|
|
75
|
+
* @param date - Date string to validate
|
|
76
|
+
* @returns True if valid ISO 8601 date
|
|
77
|
+
*/
|
|
78
|
+
export function isValidISODate(date) {
|
|
79
|
+
const dateObj = new Date(date);
|
|
80
|
+
return !isNaN(dateObj.getTime()) && dateObj.toISOString() === date;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get current timestamp in ISO 8601 format.
|
|
84
|
+
*
|
|
85
|
+
* @returns Current timestamp string
|
|
86
|
+
*/
|
|
87
|
+
export function getCurrentTimestamp() {
|
|
88
|
+
return new Date().toISOString();
|
|
89
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Error Types
|
|
3
|
+
*
|
|
4
|
+
* Defines custom error classes for better error handling and debugging.
|
|
5
|
+
*
|
|
6
|
+
* @module utils/errors
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Base error class for all knowledge graph errors.
|
|
10
|
+
* Extends the native Error class with additional context.
|
|
11
|
+
*/
|
|
12
|
+
export class KnowledgeGraphError extends Error {
|
|
13
|
+
code;
|
|
14
|
+
constructor(message, code) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.name = 'KnowledgeGraphError';
|
|
18
|
+
// Maintains proper stack trace in V8 engines
|
|
19
|
+
if (Error.captureStackTrace) {
|
|
20
|
+
Error.captureStackTrace(this, this.constructor);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Error thrown when an entity is not found.
|
|
26
|
+
*/
|
|
27
|
+
export class EntityNotFoundError extends KnowledgeGraphError {
|
|
28
|
+
constructor(entityName) {
|
|
29
|
+
super(`Entity "${entityName}" not found`, 'ENTITY_NOT_FOUND');
|
|
30
|
+
this.name = 'EntityNotFoundError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Error thrown when a relation is not found.
|
|
35
|
+
*/
|
|
36
|
+
export class RelationNotFoundError extends KnowledgeGraphError {
|
|
37
|
+
constructor(from, to, relationType) {
|
|
38
|
+
const desc = relationType
|
|
39
|
+
? `Relation "${from}" --[${relationType}]--> "${to}"`
|
|
40
|
+
: `Relation from "${from}" to "${to}"`;
|
|
41
|
+
super(`${desc} not found`, 'RELATION_NOT_FOUND');
|
|
42
|
+
this.name = 'RelationNotFoundError';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Error thrown when attempting to create a duplicate entity.
|
|
47
|
+
*/
|
|
48
|
+
export class DuplicateEntityError extends KnowledgeGraphError {
|
|
49
|
+
constructor(entityName) {
|
|
50
|
+
super(`Entity "${entityName}" already exists`, 'DUPLICATE_ENTITY');
|
|
51
|
+
this.name = 'DuplicateEntityError';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Error thrown when validation fails.
|
|
56
|
+
*/
|
|
57
|
+
export class ValidationError extends KnowledgeGraphError {
|
|
58
|
+
errors;
|
|
59
|
+
constructor(message, errors) {
|
|
60
|
+
super(message, 'VALIDATION_ERROR');
|
|
61
|
+
this.errors = errors;
|
|
62
|
+
this.name = 'ValidationError';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Error thrown when a cycle is detected in hierarchies.
|
|
67
|
+
*/
|
|
68
|
+
export class CycleDetectedError extends KnowledgeGraphError {
|
|
69
|
+
constructor(entityName, parentName) {
|
|
70
|
+
super(`Setting parent "${parentName}" for entity "${entityName}" would create a cycle`, 'CYCLE_DETECTED');
|
|
71
|
+
this.name = 'CycleDetectedError';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Error thrown when an invalid importance value is provided.
|
|
76
|
+
*/
|
|
77
|
+
export class InvalidImportanceError extends KnowledgeGraphError {
|
|
78
|
+
constructor(value, min = 0, max = 10) {
|
|
79
|
+
super(`Importance must be between ${min} and ${max}, got ${value}`, 'INVALID_IMPORTANCE');
|
|
80
|
+
this.name = 'InvalidImportanceError';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Error thrown when a file operation fails.
|
|
85
|
+
*/
|
|
86
|
+
export class FileOperationError extends KnowledgeGraphError {
|
|
87
|
+
constructor(operation, filePath, cause) {
|
|
88
|
+
super(`Failed to ${operation} file: ${filePath}${cause ? ` - ${cause.message}` : ''}`, 'FILE_OPERATION_ERROR');
|
|
89
|
+
this.name = 'FileOperationError';
|
|
90
|
+
if (cause) {
|
|
91
|
+
this.cause = cause;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Error thrown when an import operation fails.
|
|
97
|
+
*/
|
|
98
|
+
export class ImportError extends KnowledgeGraphError {
|
|
99
|
+
constructor(format, message) {
|
|
100
|
+
super(`Import failed (${format}): ${message}`, 'IMPORT_ERROR');
|
|
101
|
+
this.name = 'ImportError';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Error thrown when an export operation fails.
|
|
106
|
+
*/
|
|
107
|
+
export class ExportError extends KnowledgeGraphError {
|
|
108
|
+
constructor(format, message) {
|
|
109
|
+
super(`Export failed (${format}): ${message}`, 'EXPORT_ERROR');
|
|
110
|
+
this.name = 'ExportError';
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Error thrown when insufficient entities are provided for an operation.
|
|
115
|
+
*/
|
|
116
|
+
export class InsufficientEntitiesError extends KnowledgeGraphError {
|
|
117
|
+
constructor(operation, required, provided) {
|
|
118
|
+
super(`${operation} requires at least ${required} entities, got ${provided}`, 'INSUFFICIENT_ENTITIES');
|
|
119
|
+
this.name = 'InsufficientEntitiesError';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities Module Barrel Export
|
|
3
|
+
*/
|
|
4
|
+
export { KnowledgeGraphError, EntityNotFoundError, RelationNotFoundError, DuplicateEntityError, ValidationError, CycleDetectedError, InvalidImportanceError, FileOperationError, ImportError, ExportError, InsufficientEntitiesError, } from './errors.js';
|
|
5
|
+
export { levenshteinDistance } from './levenshtein.js';
|
|
6
|
+
export { calculateTF, calculateIDF, calculateTFIDF, tokenize } from './tfidf.js';
|
|
7
|
+
export { logger } from './logger.js';
|
|
8
|
+
export { isWithinDateRange, parseDateRange, isValidISODate, getCurrentTimestamp } from './dateUtils.js';
|
|
9
|
+
export { validateEntity, validateRelation, validateImportance, validateTags } from './validationUtils.js';
|
|
10
|
+
export { defaultMemoryPath, ensureMemoryFilePath, validateFilePath } from './pathUtils.js';
|
|
11
|
+
export { FILE_EXTENSIONS, FILE_SUFFIXES, DEFAULT_FILE_NAMES, ENV_VARS, DEFAULT_BASE_DIR, LOG_PREFIXES, SIMILARITY_WEIGHTS, DEFAULT_DUPLICATE_THRESHOLD, SEARCH_LIMITS, IMPORTANCE_RANGE, } from './constants.js';
|
|
12
|
+
export { EntitySchema, CreateEntitySchema, UpdateEntitySchema, RelationSchema, CreateRelationSchema, SearchQuerySchema, DateRangeSchema, TagAliasSchema, ExportFormatSchema, BatchCreateEntitiesSchema, BatchCreateRelationsSchema, EntityNamesSchema, DeleteRelationsSchema, } from './schemas.js';
|
|
13
|
+
export { SearchCache, searchCaches, clearAllSearchCaches, getAllCacheStats, cleanupAllCaches, } from './searchCache.js';
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Levenshtein Distance Algorithm
|
|
3
|
+
*
|
|
4
|
+
* Calculates the minimum number of single-character edits (insertions,
|
|
5
|
+
* deletions, or substitutions) needed to change one string into another.
|
|
6
|
+
*
|
|
7
|
+
* Used for fuzzy string matching to find similar entities despite typos.
|
|
8
|
+
*
|
|
9
|
+
* @module utils/levenshtein
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Calculate Levenshtein distance between two strings.
|
|
13
|
+
*
|
|
14
|
+
* Returns the minimum number of single-character edits needed to change
|
|
15
|
+
* one word into another.
|
|
16
|
+
*
|
|
17
|
+
* **Algorithm**: Dynamic programming with O(m*n) time and space complexity,
|
|
18
|
+
* where m and n are the lengths of the two strings.
|
|
19
|
+
*
|
|
20
|
+
* @param str1 - First string to compare
|
|
21
|
+
* @param str2 - Second string to compare
|
|
22
|
+
* @returns Minimum number of edits required (0 = identical strings)
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* levenshteinDistance("kitten", "sitting"); // Returns 3
|
|
27
|
+
* levenshteinDistance("hello", "hello"); // Returns 0
|
|
28
|
+
* levenshteinDistance("abc", ""); // Returns 3
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function levenshteinDistance(str1, str2) {
|
|
32
|
+
const m = str1.length;
|
|
33
|
+
const n = str2.length;
|
|
34
|
+
// Create 2D array for dynamic programming
|
|
35
|
+
const dp = Array(m + 1)
|
|
36
|
+
.fill(null)
|
|
37
|
+
.map(() => Array(n + 1).fill(0));
|
|
38
|
+
// Initialize base cases: distance from empty string
|
|
39
|
+
for (let i = 0; i <= m; i++) {
|
|
40
|
+
dp[i][0] = i;
|
|
41
|
+
}
|
|
42
|
+
for (let j = 0; j <= n; j++) {
|
|
43
|
+
dp[0][j] = j;
|
|
44
|
+
}
|
|
45
|
+
// Fill dp table
|
|
46
|
+
for (let i = 1; i <= m; i++) {
|
|
47
|
+
for (let j = 1; j <= n; j++) {
|
|
48
|
+
if (str1[i - 1] === str2[j - 1]) {
|
|
49
|
+
// Characters match, no edit needed
|
|
50
|
+
dp[i][j] = dp[i - 1][j - 1];
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
// Take minimum of three operations
|
|
54
|
+
dp[i][j] = Math.min(dp[i - 1][j] + 1, // deletion
|
|
55
|
+
dp[i][j - 1] + 1, // insertion
|
|
56
|
+
dp[i - 1][j - 1] + 1 // substitution
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return dp[m][n];
|
|
62
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple logging utility for the Memory MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Provides consistent log formatting with levels: debug, info, warn, error
|
|
5
|
+
*/
|
|
6
|
+
export const logger = {
|
|
7
|
+
/**
|
|
8
|
+
* Debug level logging (verbose, for development)
|
|
9
|
+
*/
|
|
10
|
+
debug: (msg, ...args) => {
|
|
11
|
+
if (process.env.LOG_LEVEL === 'debug') {
|
|
12
|
+
console.debug(`[DEBUG] ${msg}`, ...args);
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
/**
|
|
16
|
+
* Info level logging (general informational messages)
|
|
17
|
+
*/
|
|
18
|
+
info: (msg, ...args) => {
|
|
19
|
+
console.log(`[INFO] ${msg}`, ...args);
|
|
20
|
+
},
|
|
21
|
+
/**
|
|
22
|
+
* Warning level logging (warnings that don't prevent operation)
|
|
23
|
+
*/
|
|
24
|
+
warn: (msg, ...args) => {
|
|
25
|
+
console.warn(`[WARN] ${msg}`, ...args);
|
|
26
|
+
},
|
|
27
|
+
/**
|
|
28
|
+
* Error level logging (errors that affect functionality)
|
|
29
|
+
*/
|
|
30
|
+
error: (msg, ...args) => {
|
|
31
|
+
console.error(`[ERROR] ${msg}`, ...args);
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path Utilities
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for file path management and backward compatibility.
|
|
5
|
+
* Handles memory file path resolution with environment variable support.
|
|
6
|
+
*
|
|
7
|
+
* @module utils/pathUtils
|
|
8
|
+
*/
|
|
9
|
+
import { promises as fs } from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import { FileOperationError } from './errors.js';
|
|
13
|
+
/**
|
|
14
|
+
* Validate and normalize a file path to prevent path traversal attacks.
|
|
15
|
+
*
|
|
16
|
+
* This function:
|
|
17
|
+
* - Normalizes the path to canonical form
|
|
18
|
+
* - Converts relative paths to absolute paths
|
|
19
|
+
* - Detects and prevents path traversal attempts (..)
|
|
20
|
+
*
|
|
21
|
+
* @param filePath - The file path to validate
|
|
22
|
+
* @param baseDir - Optional base directory for relative paths (defaults to process.cwd())
|
|
23
|
+
* @returns Validated absolute file path
|
|
24
|
+
* @throws {FileOperationError} If path traversal is detected or path is invalid
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* // Valid paths
|
|
29
|
+
* validateFilePath('/var/data/memory.jsonl'); // Returns absolute path
|
|
30
|
+
* validateFilePath('data/memory.jsonl'); // Returns absolute path from cwd
|
|
31
|
+
*
|
|
32
|
+
* // Invalid paths (throws FileOperationError)
|
|
33
|
+
* validateFilePath('../../../etc/passwd'); // Path traversal detected
|
|
34
|
+
* validateFilePath('/var/data/../../../etc/passwd'); // Path traversal detected
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export function validateFilePath(filePath, baseDir = process.cwd()) {
|
|
38
|
+
// Normalize path to remove redundant separators and resolve . and ..
|
|
39
|
+
const normalized = path.normalize(filePath);
|
|
40
|
+
// Convert to absolute path
|
|
41
|
+
const absolute = path.isAbsolute(normalized)
|
|
42
|
+
? normalized
|
|
43
|
+
: path.join(baseDir, normalized);
|
|
44
|
+
// After normalization, check if path still contains .. which would indicate
|
|
45
|
+
// traversal beyond the base directory
|
|
46
|
+
const finalNormalized = path.normalize(absolute);
|
|
47
|
+
// Split path into segments and check for suspicious patterns
|
|
48
|
+
const segments = finalNormalized.split(path.sep);
|
|
49
|
+
if (segments.includes('..')) {
|
|
50
|
+
throw new FileOperationError(`Path traversal detected in file path: ${filePath}`, filePath);
|
|
51
|
+
}
|
|
52
|
+
return finalNormalized;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Default memory file path (in same directory as compiled code).
|
|
56
|
+
*/
|
|
57
|
+
export const defaultMemoryPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '../memory.jsonl');
|
|
58
|
+
/**
|
|
59
|
+
* Ensure memory file path with backward compatibility migration.
|
|
60
|
+
*
|
|
61
|
+
* Handles:
|
|
62
|
+
* 1. Custom MEMORY_FILE_PATH environment variable (with path traversal protection)
|
|
63
|
+
* 2. Backward compatibility: migrates memory.json to memory.jsonl
|
|
64
|
+
* 3. Absolute vs relative path resolution
|
|
65
|
+
*
|
|
66
|
+
* @returns Resolved and validated memory file path
|
|
67
|
+
* @throws {FileOperationError} If path traversal is detected in MEMORY_FILE_PATH
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* // Use environment variable
|
|
72
|
+
* process.env.MEMORY_FILE_PATH = '/data/memory.jsonl';
|
|
73
|
+
* const path = await ensureMemoryFilePath(); // '/data/memory.jsonl'
|
|
74
|
+
*
|
|
75
|
+
* // Use default path
|
|
76
|
+
* delete process.env.MEMORY_FILE_PATH;
|
|
77
|
+
* const path = await ensureMemoryFilePath(); // './memory.jsonl'
|
|
78
|
+
*
|
|
79
|
+
* // Invalid path (throws error)
|
|
80
|
+
* process.env.MEMORY_FILE_PATH = '../../../etc/passwd';
|
|
81
|
+
* await ensureMemoryFilePath(); // Throws FileOperationError
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export async function ensureMemoryFilePath() {
|
|
85
|
+
if (process.env.MEMORY_FILE_PATH) {
|
|
86
|
+
// Custom path provided, validate and resolve to absolute
|
|
87
|
+
const baseDir = path.dirname(fileURLToPath(import.meta.url)) + '/../';
|
|
88
|
+
const validatedPath = validateFilePath(process.env.MEMORY_FILE_PATH, baseDir);
|
|
89
|
+
return validatedPath;
|
|
90
|
+
}
|
|
91
|
+
// No custom path set, check for backward compatibility migration
|
|
92
|
+
const oldMemoryPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '../memory.json');
|
|
93
|
+
const newMemoryPath = defaultMemoryPath;
|
|
94
|
+
try {
|
|
95
|
+
// Check if old file exists
|
|
96
|
+
await fs.access(oldMemoryPath);
|
|
97
|
+
try {
|
|
98
|
+
// Check if new file exists
|
|
99
|
+
await fs.access(newMemoryPath);
|
|
100
|
+
// Both files exist, use new one (no migration needed)
|
|
101
|
+
return newMemoryPath;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// Old file exists, new file doesn't - migrate
|
|
105
|
+
console.log('[INFO] Found legacy memory.json file, migrating to memory.jsonl for JSONL format compatibility');
|
|
106
|
+
await fs.rename(oldMemoryPath, newMemoryPath);
|
|
107
|
+
console.log('[INFO] Successfully migrated memory.json to memory.jsonl');
|
|
108
|
+
return newMemoryPath;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Old file doesn't exist, use new path
|
|
113
|
+
return newMemoryPath;
|
|
114
|
+
}
|
|
115
|
+
}
|