@lumenflow/memory 2.2.2 → 2.3.2

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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Memory Store (WU-1463)
2
+ * Memory Store (WU-1463, WU-1238)
3
3
  *
4
4
  * JSONL-based memory store with load, query, and append operations.
5
5
  * Git-friendly format with one node per line for merge-safe diffs.
@@ -8,9 +8,11 @@
8
8
  * - Append-only writes (no full file rewrite)
9
9
  * - Indexed lookups by ID and WU
10
10
  * - Deterministic queryReady() ordering by priority then createdAt
11
+ * - WU-1238: Support for archived node filtering
12
+ * - WU-1238: Decay score-based sorting option
11
13
  *
12
- * @see {@link tools/lib/__tests__/memory-store.test.mjs} - Tests
13
- * @see {@link tools/lib/memory-schema.mjs} - Schema definitions
14
+ * @see {@link packages/@lumenflow/cli/src/lib/__tests__/memory-store.test.ts} - Tests
15
+ * @see {@link packages/@lumenflow/cli/src/lib/memory-schema.ts} - Schema definitions
14
16
  */
15
17
  import fs from 'node:fs/promises';
16
18
  import path from 'node:path';
@@ -68,6 +70,15 @@ function compareNodes(a, b) {
68
70
  // Tertiary: stable sort by ID for identical priority and timestamp
69
71
  return a.id.localeCompare(b.id);
70
72
  }
73
+ /**
74
+ * Check if a node is archived (WU-1238).
75
+ *
76
+ * @param node - Memory node to check
77
+ * @returns True if node has metadata.status = 'archived'
78
+ */
79
+ function isNodeArchived(node) {
80
+ return node.metadata?.status === 'archived';
81
+ }
71
82
  /**
72
83
  * Loads memory from JSONL file and returns indexed nodes.
73
84
  *
@@ -86,71 +97,111 @@ function compareNodes(a, b) {
86
97
  * const memory = await loadMemory('/path/to/project');
87
98
  * const node = memory.byId.get('mem-abc1');
88
99
  * const wuNodes = memory.byWu.get('WU-1463') ?? [];
100
+ *
101
+ * @example
102
+ * // WU-1238: Include archived nodes
103
+ * const allMemory = await loadMemory('/path/to/project', { includeArchived: true });
89
104
  */
90
- export async function loadMemory(baseDir) {
105
+ export async function loadMemory(baseDir, options = {}) {
106
+ const { includeArchived = false } = options;
91
107
  const filePath = path.join(baseDir, MEMORY_FILE_NAME);
92
- const result = {
93
- nodes: [],
94
- byId: new Map(),
95
- byWu: new Map(),
96
- };
97
- // Check if file exists
98
- let content;
108
+ const content = await readMemoryFileOrEmpty(filePath);
109
+ if (content === null) {
110
+ return createEmptyIndexedMemory();
111
+ }
112
+ return parseAndIndexMemory(content, includeArchived);
113
+ }
114
+ /**
115
+ * Reads memory file content, returning null if file doesn't exist
116
+ */
117
+ async function readMemoryFileOrEmpty(filePath) {
99
118
  try {
100
- content = await fs.readFile(filePath, { encoding: 'utf-8' });
119
+ return await fs.readFile(filePath, { encoding: 'utf-8' });
101
120
  }
102
121
  catch (err) {
103
122
  const error = err;
104
123
  if (error.code === 'ENOENT') {
105
- // File doesn't exist - return empty result
106
- return result;
124
+ return null;
107
125
  }
108
126
  throw error;
109
127
  }
110
- // Parse JSONL content
128
+ }
129
+ /**
130
+ * Creates an empty indexed memory result
131
+ */
132
+ function createEmptyIndexedMemory() {
133
+ return {
134
+ nodes: [],
135
+ byId: new Map(),
136
+ byWu: new Map(),
137
+ };
138
+ }
139
+ /**
140
+ * Parses a single JSONL line and validates it
141
+ */
142
+ function parseAndValidateLine(line, lineNumber) {
143
+ let parsed;
144
+ try {
145
+ parsed = JSON.parse(line);
146
+ }
147
+ catch (err) {
148
+ const errMsg = err instanceof Error ? err.message : String(err);
149
+ throw new Error(`Malformed JSON on line ${lineNumber}: ${errMsg}`);
150
+ }
151
+ const validation = validateMemoryNode(parsed);
152
+ if (!validation.success) {
153
+ const issues = validation.error.issues
154
+ .map((issue) => `${issue.path.join('.')}: ${issue.message}`)
155
+ .join(', ');
156
+ throw new Error(`Validation error on line ${lineNumber}: ${issues}`);
157
+ }
158
+ return validation.data;
159
+ }
160
+ /**
161
+ * Adds a node to the WU index
162
+ */
163
+ function indexNodeByWu(result, node) {
164
+ if (!node.wu_id)
165
+ return;
166
+ if (!result.byWu.has(node.wu_id)) {
167
+ result.byWu.set(node.wu_id, []);
168
+ }
169
+ const wuNodes = result.byWu.get(node.wu_id);
170
+ if (wuNodes) {
171
+ wuNodes.push(node);
172
+ }
173
+ }
174
+ /**
175
+ * Parses JSONL content and builds indexed memory
176
+ */
177
+ function parseAndIndexMemory(content, includeArchived) {
178
+ const result = createEmptyIndexedMemory();
111
179
  const lines = content.split('\n');
112
180
  for (let i = 0; i < lines.length; i++) {
113
- const rawLine = lines[i];
114
- const line = rawLine ? rawLine.trim() : '';
115
- // Skip empty lines
116
- if (!line) {
181
+ const line = lines[i]?.trim() ?? '';
182
+ if (!line)
183
+ continue;
184
+ const node = parseAndValidateLine(line, i + 1);
185
+ // WU-1238: Skip archived nodes unless includeArchived is true
186
+ if (!includeArchived && isNodeArchived(node)) {
117
187
  continue;
118
188
  }
119
- // Parse JSON line
120
- let parsed;
121
- try {
122
- parsed = JSON.parse(line);
123
- }
124
- catch (err) {
125
- const errMsg = err instanceof Error ? err.message : String(err);
126
- throw new Error(`Malformed JSON on line ${i + 1}: ${errMsg}`);
127
- }
128
- // Validate against schema
129
- const validation = validateMemoryNode(parsed);
130
- if (!validation.success) {
131
- const issues = validation.error.issues
132
- .map((issue) => `${issue.path.join('.')}: ${issue.message}`)
133
- .join(', ');
134
- throw new Error(`Validation error on line ${i + 1}: ${issues}`);
135
- }
136
- const node = validation.data;
137
- // Add to nodes array
138
189
  result.nodes.push(node);
139
- // Index by ID
140
190
  result.byId.set(node.id, node);
141
- // Index by WU ID if present
142
- if (node.wu_id) {
143
- if (!result.byWu.has(node.wu_id)) {
144
- result.byWu.set(node.wu_id, []);
145
- }
146
- const wuNodes = result.byWu.get(node.wu_id);
147
- if (wuNodes) {
148
- wuNodes.push(node);
149
- }
150
- }
191
+ indexNodeByWu(result, node);
151
192
  }
152
193
  return result;
153
194
  }
195
+ /**
196
+ * Loads all memory including archived nodes.
197
+ * Convenience function for operations that need to see all nodes.
198
+ *
199
+ * @param baseDir - Directory containing memory.jsonl
200
+ * @returns Indexed memory nodes including archived
201
+ */
202
+ export async function loadMemoryAll(baseDir) {
203
+ return loadMemory(baseDir, { includeArchived: true });
204
+ }
154
205
  /**
155
206
  * Appends a single node to the memory file.
156
207
  *
@@ -206,9 +257,14 @@ export async function appendNode(baseDir, node) {
206
257
  * for (const node of ready) {
207
258
  * await processNode(node);
208
259
  * }
260
+ *
261
+ * @example
262
+ * // WU-1238: Include archived nodes
263
+ * const all = await queryReady('/path/to/project', 'WU-1463', { includeArchived: true });
209
264
  */
210
- export async function queryReady(baseDir, wuId) {
211
- const memory = await loadMemory(baseDir);
265
+ export async function queryReady(baseDir, wuId, options = {}) {
266
+ const { includeArchived = false } = options;
267
+ const memory = await loadMemory(baseDir, { includeArchived });
212
268
  // Get nodes for this WU
213
269
  const wuNodes = memory.byWu.get(wuId) ?? [];
214
270
  // Return sorted copy (don't mutate original)
@@ -227,8 +283,13 @@ export async function queryReady(baseDir, wuId) {
227
283
  * @example
228
284
  * const nodes = await queryByWu('/path/to/project', 'WU-1463');
229
285
  * console.log(`Found ${nodes.length} nodes for WU-1463`);
286
+ *
287
+ * @example
288
+ * // WU-1238: Include archived nodes
289
+ * const all = await queryByWu('/path/to/project', 'WU-1463', { includeArchived: true });
230
290
  */
231
- export async function queryByWu(baseDir, wuId) {
232
- const memory = await loadMemory(baseDir);
291
+ export async function queryByWu(baseDir, wuId, options = {}) {
292
+ const { includeArchived = false } = options;
293
+ const memory = await loadMemory(baseDir, { includeArchived });
233
294
  return memory.byWu.get(wuId) ?? [];
234
295
  }
@@ -0,0 +1,355 @@
1
+ /**
2
+ * Signal Cleanup Core (WU-1204)
3
+ *
4
+ * TTL-based cleanup for signals to prevent unbounded growth.
5
+ * Implements configurable retention policies:
6
+ * - Read signals: 7 days default TTL
7
+ * - Unread signals: 30 days default TTL
8
+ * - Max entries: 500 default
9
+ * - Active WU protection: signals linked to in_progress/blocked WUs are never removed
10
+ *
11
+ * Reuses patterns from mem-cleanup-core.ts.
12
+ *
13
+ * @see {@link packages/@lumenflow/cli/src/signal-cleanup.ts} - CLI wrapper
14
+ * @see {@link packages/@lumenflow/memory/src/__tests__/signal-cleanup-core.test.ts} - Tests
15
+ */
16
+ import fs from 'node:fs/promises';
17
+ import path from 'node:path';
18
+ import { createRequire } from 'node:module';
19
+ const require = createRequire(import.meta.url);
20
+ const ms = require('ms');
21
+ import { LUMENFLOW_MEMORY_PATHS } from './paths.js';
22
+ import { SIGNAL_FILE_NAME } from './mem-signal-core.js';
23
+ /**
24
+ * Default TTL values in milliseconds
25
+ */
26
+ const ONE_DAY_MS = 24 * 60 * 60 * 1000;
27
+ /**
28
+ * Default signal cleanup configuration
29
+ */
30
+ export const DEFAULT_SIGNAL_CLEANUP_CONFIG = {
31
+ ttl: 7 * ONE_DAY_MS,
32
+ unreadTtl: 30 * ONE_DAY_MS,
33
+ maxEntries: 500,
34
+ };
35
+ /**
36
+ * Parse a TTL duration string into milliseconds.
37
+ *
38
+ * Uses the `ms` package to parse human-readable duration strings.
39
+ *
40
+ * @param ttlString - TTL string (e.g., '7d', '30d', '24h', '60m')
41
+ * @returns TTL in milliseconds
42
+ * @throws If TTL format is invalid
43
+ *
44
+ * @example
45
+ * parseSignalTtl('7d'); // 604800000 (7 days in ms)
46
+ * parseSignalTtl('30d'); // 2592000000 (30 days in ms)
47
+ * parseSignalTtl('24h'); // 86400000 (24 hours in ms)
48
+ */
49
+ export function parseSignalTtl(ttlString) {
50
+ if (!ttlString || typeof ttlString !== 'string') {
51
+ throw new Error('Invalid TTL format: TTL string is required');
52
+ }
53
+ const trimmed = ttlString.trim();
54
+ if (!trimmed) {
55
+ throw new Error('Invalid TTL format: TTL string is required');
56
+ }
57
+ // Use ms package to parse the duration
58
+ const result = ms(trimmed);
59
+ if (result === undefined || result <= 0) {
60
+ throw new Error(`Invalid TTL format: "${ttlString}" is not a valid duration`);
61
+ }
62
+ return result;
63
+ }
64
+ /**
65
+ * Check if a signal has expired based on TTL.
66
+ *
67
+ * @param signal - Signal to check
68
+ * @param ttlMs - TTL in milliseconds
69
+ * @param now - Current timestamp
70
+ * @returns True if signal is older than TTL
71
+ */
72
+ function isSignalExpired(signal, ttlMs, now) {
73
+ if (!signal.created_at) {
74
+ return false; // No timestamp means we can't determine age - safer to retain
75
+ }
76
+ const createdAt = new Date(signal.created_at).getTime();
77
+ // Invalid date - safer to retain
78
+ if (Number.isNaN(createdAt)) {
79
+ return false;
80
+ }
81
+ const age = now - createdAt;
82
+ return age > ttlMs;
83
+ }
84
+ /**
85
+ * Check if a signal should be removed based on TTL and active WU protection.
86
+ *
87
+ * Policy rules (checked in order):
88
+ * 1. Active WU signals are always retained
89
+ * 2. Read signals older than TTL are removed
90
+ * 3. Unread signals older than unreadTtl are removed
91
+ * 4. Otherwise, signal is retained
92
+ *
93
+ * @param signal - Signal to check
94
+ * @param config - Cleanup configuration
95
+ * @param context - Removal context (now timestamp, active WU IDs)
96
+ * @returns Removal decision with reason
97
+ */
98
+ export function shouldRemoveSignal(signal, config, context) {
99
+ const { now, activeWuIds } = context;
100
+ // Active WU protection: signals linked to in_progress/blocked WUs are always retained
101
+ if (signal.wu_id && activeWuIds.has(signal.wu_id)) {
102
+ return { remove: false, reason: 'active-wu-protected' };
103
+ }
104
+ // Check TTL based on read status
105
+ if (signal.read) {
106
+ // Read signals use the shorter TTL
107
+ if (isSignalExpired(signal, config.ttl, now)) {
108
+ return { remove: true, reason: 'ttl-expired' };
109
+ }
110
+ }
111
+ else {
112
+ // Unread signals use the longer unreadTtl
113
+ if (isSignalExpired(signal, config.unreadTtl, now)) {
114
+ return { remove: true, reason: 'unread-ttl-expired' };
115
+ }
116
+ }
117
+ // Default: retain
118
+ return { remove: false, reason: 'within-ttl' };
119
+ }
120
+ /**
121
+ * Calculate approximate byte size of a signal when serialized.
122
+ *
123
+ * @param signal - Signal to measure
124
+ * @returns Approximate byte size
125
+ */
126
+ function estimateSignalBytes(signal) {
127
+ // JSON.stringify + newline character
128
+ return JSON.stringify(signal).length + 1;
129
+ }
130
+ /**
131
+ * Calculate compaction ratio (removed / total).
132
+ *
133
+ * @param removedCount - Number of removed signals
134
+ * @param totalCount - Total number of signals
135
+ * @returns Compaction ratio (0 to 1, or 0 if no signals)
136
+ */
137
+ function getCompactionRatio(removedCount, totalCount) {
138
+ if (totalCount === 0) {
139
+ return 0;
140
+ }
141
+ return removedCount / totalCount;
142
+ }
143
+ /**
144
+ * Gets the signals file path for a project.
145
+ *
146
+ * @param baseDir - Project base directory
147
+ * @returns Full path to signals.jsonl
148
+ */
149
+ function getSignalsPath(baseDir) {
150
+ return path.join(baseDir, LUMENFLOW_MEMORY_PATHS.MEMORY_DIR, SIGNAL_FILE_NAME);
151
+ }
152
+ /**
153
+ * Load signals directly from file (for cleanup, to avoid loadSignals filter limitations).
154
+ *
155
+ * @param baseDir - Project base directory
156
+ * @returns Array of all signals
157
+ */
158
+ async function loadAllSignals(baseDir) {
159
+ const signalsPath = getSignalsPath(baseDir);
160
+ try {
161
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool reads known path
162
+ const content = await fs.readFile(signalsPath, { encoding: 'utf-8' });
163
+ const lines = content.split('\n').filter((line) => line.trim());
164
+ return lines.map((line) => JSON.parse(line));
165
+ }
166
+ catch (err) {
167
+ const error = err;
168
+ if (error.code === 'ENOENT') {
169
+ return [];
170
+ }
171
+ throw error;
172
+ }
173
+ }
174
+ /**
175
+ * Write retained signals back to file.
176
+ *
177
+ * @param baseDir - Project base directory
178
+ * @param signals - Signals to write
179
+ */
180
+ async function writeSignals(baseDir, signals) {
181
+ const signalsPath = getSignalsPath(baseDir);
182
+ const content = signals.map((s) => JSON.stringify(s)).join('\n') + (signals.length > 0 ? '\n' : '');
183
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool writes known path
184
+ await fs.writeFile(signalsPath, content, { encoding: 'utf-8' });
185
+ }
186
+ /**
187
+ * Default function to get active WU IDs (returns empty set).
188
+ * Override in CLI to actually query WU status.
189
+ */
190
+ async function defaultGetActiveWuIds() {
191
+ return new Set();
192
+ }
193
+ /**
194
+ * Build cleanup configuration from options.
195
+ *
196
+ * @param options - Cleanup options
197
+ * @returns Effective cleanup configuration
198
+ */
199
+ function buildCleanupConfig(options) {
200
+ const { ttl, ttlMs: providedTtlMs, unreadTtl, unreadTtlMs: providedUnreadTtlMs, maxEntries, } = options;
201
+ let ttlMs = providedTtlMs ?? DEFAULT_SIGNAL_CLEANUP_CONFIG.ttl;
202
+ if (ttl && !providedTtlMs) {
203
+ ttlMs = parseSignalTtl(ttl);
204
+ }
205
+ let unreadTtlMs = providedUnreadTtlMs ?? DEFAULT_SIGNAL_CLEANUP_CONFIG.unreadTtl;
206
+ if (unreadTtl && !providedUnreadTtlMs) {
207
+ unreadTtlMs = parseSignalTtl(unreadTtl);
208
+ }
209
+ return {
210
+ ttl: ttlMs,
211
+ unreadTtl: unreadTtlMs,
212
+ maxEntries: maxEntries ?? DEFAULT_SIGNAL_CLEANUP_CONFIG.maxEntries,
213
+ };
214
+ }
215
+ /**
216
+ * Process signals for TTL-based removal decisions.
217
+ *
218
+ * @param signals - All signals to process
219
+ * @param config - Cleanup configuration
220
+ * @param context - Removal context
221
+ * @returns Cleanup state after TTL processing
222
+ */
223
+ function processSignalsForTtl(signals, config, context) {
224
+ const state = {
225
+ removedIds: [],
226
+ retainedIds: [],
227
+ retainedSignals: [],
228
+ bytesFreed: 0,
229
+ breakdown: {
230
+ ttlExpired: 0,
231
+ unreadTtlExpired: 0,
232
+ countLimitExceeded: 0,
233
+ activeWuProtected: 0,
234
+ },
235
+ };
236
+ for (const signal of signals) {
237
+ const decision = shouldRemoveSignal(signal, config, context);
238
+ processSignalDecision(signal, decision, state);
239
+ }
240
+ return state;
241
+ }
242
+ /**
243
+ * Process a single signal's removal decision and update state.
244
+ *
245
+ * @param signal - Signal being processed
246
+ * @param decision - Removal decision for this signal
247
+ * @param state - Cleanup state to update
248
+ */
249
+ function processSignalDecision(signal, decision, state) {
250
+ if (decision.remove) {
251
+ state.removedIds.push(signal.id);
252
+ state.bytesFreed += estimateSignalBytes(signal);
253
+ updateBreakdownForRemoval(decision.reason, state.breakdown);
254
+ }
255
+ else {
256
+ state.retainedIds.push(signal.id);
257
+ state.retainedSignals.push(signal);
258
+ if (decision.reason === 'active-wu-protected') {
259
+ state.breakdown.activeWuProtected++;
260
+ }
261
+ }
262
+ }
263
+ /**
264
+ * Update breakdown statistics for a removed signal.
265
+ *
266
+ * @param reason - Removal reason
267
+ * @param breakdown - Breakdown to update
268
+ */
269
+ function updateBreakdownForRemoval(reason, breakdown) {
270
+ if (reason === 'ttl-expired') {
271
+ breakdown.ttlExpired++;
272
+ }
273
+ else if (reason === 'unread-ttl-expired') {
274
+ breakdown.unreadTtlExpired++;
275
+ }
276
+ }
277
+ /**
278
+ * Apply count-based pruning to keep only maxEntries signals.
279
+ *
280
+ * @param state - Current cleanup state (mutated in place)
281
+ * @param maxEntries - Maximum entries to retain
282
+ */
283
+ function applyCountPruning(state, maxEntries) {
284
+ if (state.retainedSignals.length <= maxEntries) {
285
+ return;
286
+ }
287
+ // Sort by created_at (oldest first)
288
+ state.retainedSignals.sort((a, b) => {
289
+ const aTime = new Date(a.created_at).getTime();
290
+ const bTime = new Date(b.created_at).getTime();
291
+ return aTime - bTime;
292
+ });
293
+ const toRemove = state.retainedSignals.length - maxEntries;
294
+ for (let i = 0; i < toRemove; i++) {
295
+ // eslint-disable-next-line security/detect-object-injection -- Safe: i is a controlled loop index
296
+ const signal = state.retainedSignals[i];
297
+ const idIndex = state.retainedIds.indexOf(signal.id);
298
+ if (idIndex !== -1) {
299
+ state.retainedIds.splice(idIndex, 1);
300
+ }
301
+ state.removedIds.push(signal.id);
302
+ state.bytesFreed += estimateSignalBytes(signal);
303
+ state.breakdown.countLimitExceeded++;
304
+ }
305
+ state.retainedSignals.splice(0, toRemove);
306
+ }
307
+ /**
308
+ * Cleanup signals based on TTL and count limits.
309
+ *
310
+ * Removes signals according to policy:
311
+ * 1. Active WU signals (in_progress/blocked) are always retained
312
+ * 2. Read signals older than TTL (default 7d) are removed
313
+ * 3. Unread signals older than unreadTtl (default 30d) are removed
314
+ * 4. If over maxEntries, oldest signals are removed (keeping newest)
315
+ *
316
+ * In dry-run mode, no modifications are made but the result shows
317
+ * what would be removed.
318
+ *
319
+ * @param baseDir - Project base directory
320
+ * @param options - Cleanup options
321
+ * @returns Cleanup result with removed/retained IDs and metrics
322
+ *
323
+ * @example
324
+ * // Cleanup with dry-run to preview
325
+ * const preview = await cleanupSignals(baseDir, { dryRun: true });
326
+ * console.log(`Would remove ${preview.removedIds.length} signals`);
327
+ *
328
+ * @example
329
+ * // Cleanup with custom TTL
330
+ * const result = await cleanupSignals(baseDir, { ttl: '3d' });
331
+ */
332
+ export async function cleanupSignals(baseDir, options = {}) {
333
+ const { dryRun = false, now = Date.now(), getActiveWuIds = defaultGetActiveWuIds } = options;
334
+ const config = buildCleanupConfig(options);
335
+ const signals = await loadAllSignals(baseDir);
336
+ const activeWuIds = await getActiveWuIds();
337
+ const state = processSignalsForTtl(signals, config, { now, activeWuIds });
338
+ applyCountPruning(state, config.maxEntries);
339
+ const compactionRatio = getCompactionRatio(state.removedIds.length, signals.length);
340
+ const baseResult = {
341
+ success: true,
342
+ removedIds: state.removedIds,
343
+ retainedIds: state.retainedIds,
344
+ bytesFreed: state.bytesFreed,
345
+ compactionRatio,
346
+ breakdown: state.breakdown,
347
+ };
348
+ if (dryRun) {
349
+ return { ...baseResult, dryRun: true };
350
+ }
351
+ if (state.removedIds.length > 0) {
352
+ await writeSignals(baseDir, state.retainedSignals);
353
+ }
354
+ return baseResult;
355
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumenflow/memory",
3
- "version": "2.2.2",
3
+ "version": "2.3.2",
4
4
  "description": "Memory layer for LumenFlow workflow framework - session tracking, context recovery, and agent coordination",
5
5
  "keywords": [
6
6
  "lumenflow",
@@ -25,6 +25,7 @@
25
25
  "exports": {
26
26
  ".": "./dist/index.js",
27
27
  "./checkpoint": "./dist/mem-checkpoint-core.js",
28
+ "./context": "./dist/mem-context-core.js",
28
29
  "./init": "./dist/mem-init-core.js",
29
30
  "./start": "./dist/mem-start-core.js",
30
31
  "./ready": "./dist/mem-ready-core.js",
@@ -33,6 +34,7 @@
33
34
  "./create": "./dist/mem-create-core.js",
34
35
  "./summarize": "./dist/mem-summarize-core.js",
35
36
  "./triage": "./dist/mem-triage-core.js",
37
+ "./index": "./dist/mem-index-core.js",
36
38
  "./schema": "./dist/memory-schema.js",
37
39
  "./store": "./dist/memory-store.js",
38
40
  "./dist/*": "./dist/*"
@@ -47,7 +49,7 @@
47
49
  "ms": "^2.1.3",
48
50
  "yaml": "^2.8.2",
49
51
  "zod": "^4.3.5",
50
- "@lumenflow/core": "2.2.2"
52
+ "@lumenflow/core": "2.3.2"
51
53
  },
52
54
  "devDependencies": {
53
55
  "@vitest/coverage-v8": "^4.0.17",