@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.
Files changed (61) hide show
  1. package/dist/__tests__/edge-cases/edge-cases.test.js +406 -0
  2. package/dist/__tests__/file-path.test.js +5 -5
  3. package/dist/__tests__/integration/workflows.test.js +449 -0
  4. package/dist/__tests__/knowledge-graph.test.js +8 -3
  5. package/dist/__tests__/performance/benchmarks.test.js +411 -0
  6. package/dist/__tests__/unit/core/EntityManager.test.js +334 -0
  7. package/dist/__tests__/unit/core/GraphStorage.test.js +205 -0
  8. package/dist/__tests__/unit/core/RelationManager.test.js +274 -0
  9. package/dist/__tests__/unit/features/CompressionManager.test.js +350 -0
  10. package/dist/__tests__/unit/search/BasicSearch.test.js +311 -0
  11. package/dist/__tests__/unit/search/BooleanSearch.test.js +432 -0
  12. package/dist/__tests__/unit/search/FuzzySearch.test.js +448 -0
  13. package/dist/__tests__/unit/search/RankedSearch.test.js +379 -0
  14. package/dist/__tests__/unit/utils/levenshtein.test.js +77 -0
  15. package/dist/core/EntityManager.js +554 -0
  16. package/dist/core/GraphStorage.js +172 -0
  17. package/dist/core/KnowledgeGraphManager.js +400 -0
  18. package/dist/core/ObservationManager.js +129 -0
  19. package/dist/core/RelationManager.js +186 -0
  20. package/dist/core/TransactionManager.js +389 -0
  21. package/dist/core/index.js +9 -0
  22. package/dist/features/AnalyticsManager.js +222 -0
  23. package/dist/features/ArchiveManager.js +74 -0
  24. package/dist/features/BackupManager.js +311 -0
  25. package/dist/features/CompressionManager.js +310 -0
  26. package/dist/features/ExportManager.js +305 -0
  27. package/dist/features/HierarchyManager.js +219 -0
  28. package/dist/features/ImportExportManager.js +50 -0
  29. package/dist/features/ImportManager.js +328 -0
  30. package/dist/features/TagManager.js +210 -0
  31. package/dist/features/index.js +12 -0
  32. package/dist/index.js +13 -997
  33. package/dist/memory.jsonl +225 -0
  34. package/dist/search/BasicSearch.js +161 -0
  35. package/dist/search/BooleanSearch.js +304 -0
  36. package/dist/search/FuzzySearch.js +115 -0
  37. package/dist/search/RankedSearch.js +206 -0
  38. package/dist/search/SavedSearchManager.js +145 -0
  39. package/dist/search/SearchManager.js +305 -0
  40. package/dist/search/SearchSuggestions.js +57 -0
  41. package/dist/search/TFIDFIndexManager.js +217 -0
  42. package/dist/search/index.js +10 -0
  43. package/dist/server/MCPServer.js +889 -0
  44. package/dist/types/analytics.types.js +6 -0
  45. package/dist/types/entity.types.js +7 -0
  46. package/dist/types/import-export.types.js +7 -0
  47. package/dist/types/index.js +12 -0
  48. package/dist/types/search.types.js +7 -0
  49. package/dist/types/tag.types.js +6 -0
  50. package/dist/utils/constants.js +127 -0
  51. package/dist/utils/dateUtils.js +89 -0
  52. package/dist/utils/errors.js +121 -0
  53. package/dist/utils/index.js +13 -0
  54. package/dist/utils/levenshtein.js +62 -0
  55. package/dist/utils/logger.js +33 -0
  56. package/dist/utils/pathUtils.js +115 -0
  57. package/dist/utils/schemas.js +184 -0
  58. package/dist/utils/searchCache.js +209 -0
  59. package/dist/utils/tfidf.js +90 -0
  60. package/dist/utils/validationUtils.js +109 -0
  61. package/package.json +50 -48
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Analytics Types
3
+ *
4
+ * Type definitions for graph statistics, validation reports, and analytics data.
5
+ */
6
+ export {};
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Entity Types
3
+ *
4
+ * Core type definitions for entities, relations, and the knowledge graph structure.
5
+ * These types form the foundation of the memory MCP server's data model.
6
+ */
7
+ export {};
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Import/Export Types
3
+ *
4
+ * Type definitions for import and export operations, including
5
+ * result summaries and compression results.
6
+ */
7
+ export {};
@@ -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,7 @@
1
+ /**
2
+ * Search Types
3
+ *
4
+ * Type definitions for search operations, including search results,
5
+ * saved searches, and boolean query AST structures.
6
+ */
7
+ export {};
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Tag Types
3
+ *
4
+ * Type definitions for tag management, including tag aliases for synonyms.
5
+ */
6
+ 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
+ }