@lumenflow/memory 1.0.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/LICENSE +190 -0
- package/README.md +181 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/mem-checkpoint-core.d.ts +91 -0
- package/dist/mem-checkpoint-core.d.ts.map +1 -0
- package/dist/mem-checkpoint-core.js +175 -0
- package/dist/mem-checkpoint-core.js.map +1 -0
- package/dist/mem-cleanup-core.d.ts +202 -0
- package/dist/mem-cleanup-core.d.ts.map +1 -0
- package/dist/mem-cleanup-core.js +364 -0
- package/dist/mem-cleanup-core.js.map +1 -0
- package/dist/mem-create-core.d.ts +93 -0
- package/dist/mem-create-core.d.ts.map +1 -0
- package/dist/mem-create-core.js +369 -0
- package/dist/mem-create-core.js.map +1 -0
- package/dist/mem-id.d.ts +91 -0
- package/dist/mem-id.d.ts.map +1 -0
- package/dist/mem-id.js +136 -0
- package/dist/mem-id.js.map +1 -0
- package/dist/mem-init-core.d.ts +91 -0
- package/dist/mem-init-core.d.ts.map +1 -0
- package/dist/mem-init-core.js +138 -0
- package/dist/mem-init-core.js.map +1 -0
- package/dist/mem-ready-core.d.ts +56 -0
- package/dist/mem-ready-core.d.ts.map +1 -0
- package/dist/mem-ready-core.js +232 -0
- package/dist/mem-ready-core.js.map +1 -0
- package/dist/mem-signal-core.d.ts +132 -0
- package/dist/mem-signal-core.d.ts.map +1 -0
- package/dist/mem-signal-core.js +249 -0
- package/dist/mem-signal-core.js.map +1 -0
- package/dist/mem-start-core.d.ts +76 -0
- package/dist/mem-start-core.d.ts.map +1 -0
- package/dist/mem-start-core.js +133 -0
- package/dist/mem-start-core.js.map +1 -0
- package/dist/mem-summarize-core.d.ts +105 -0
- package/dist/mem-summarize-core.d.ts.map +1 -0
- package/dist/mem-summarize-core.js +263 -0
- package/dist/mem-summarize-core.js.map +1 -0
- package/dist/mem-triage-core.d.ts +127 -0
- package/dist/mem-triage-core.d.ts.map +1 -0
- package/dist/mem-triage-core.js +332 -0
- package/dist/mem-triage-core.js.map +1 -0
- package/dist/memory-schema.d.ts +150 -0
- package/dist/memory-schema.d.ts.map +1 -0
- package/dist/memory-schema.js +149 -0
- package/dist/memory-schema.js.map +1 -0
- package/dist/memory-store.d.ts +108 -0
- package/dist/memory-store.d.ts.map +1 -0
- package/dist/memory-store.js +234 -0
- package/dist/memory-store.js.map +1 -0
- package/package.json +76 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mem-checkpoint-core.js","sourceRoot":"","sources":["../src/mem-checkpoint-core.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Cleanup Core (WU-1472, WU-1554)
|
|
3
|
+
*
|
|
4
|
+
* Prune closed memory nodes based on lifecycle policy.
|
|
5
|
+
* Implements compaction to prevent memory bloat.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Remove ephemeral nodes (always discarded)
|
|
9
|
+
* - Remove session nodes when session is closed
|
|
10
|
+
* - Archive summarized nodes (marked with summarized_into)
|
|
11
|
+
* - Respect sensitive:true flag for stricter retention
|
|
12
|
+
* - Support dry-run mode for preview
|
|
13
|
+
* - Report compaction metrics (ratio, bytes freed)
|
|
14
|
+
* - WU-1554: TTL-based expiration for old nodes
|
|
15
|
+
* - WU-1554: Active session protection regardless of age
|
|
16
|
+
*
|
|
17
|
+
* Lifecycle Policy:
|
|
18
|
+
* - ephemeral: Always removed (scratch pad data)
|
|
19
|
+
* - session: Removed when session is closed
|
|
20
|
+
* - wu: Removed when marked with summarized_into (after WU completion)
|
|
21
|
+
* - project: Never removed (architectural knowledge)
|
|
22
|
+
*
|
|
23
|
+
* TTL Policy (WU-1554):
|
|
24
|
+
* - Nodes older than TTL are removed regardless of lifecycle
|
|
25
|
+
* - Active sessions (status: 'active') are never removed
|
|
26
|
+
* - Project and sensitive nodes are protected from TTL removal
|
|
27
|
+
*
|
|
28
|
+
* @see {@link tools/mem-cleanup.mjs} - CLI wrapper
|
|
29
|
+
* @see {@link tools/__tests__/mem-cleanup.test.mjs} - Tests
|
|
30
|
+
*/
|
|
31
|
+
import type { MemoryNode } from './memory-schema.js';
|
|
32
|
+
/**
|
|
33
|
+
* Lifecycle policy definition
|
|
34
|
+
*/
|
|
35
|
+
interface LifecyclePolicyEntry {
|
|
36
|
+
alwaysRemove: boolean;
|
|
37
|
+
requiresSummarized: boolean;
|
|
38
|
+
protected?: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Lifecycle policy type for indexed access
|
|
42
|
+
*/
|
|
43
|
+
type LifecycleType = 'ephemeral' | 'session' | 'wu' | 'project';
|
|
44
|
+
/**
|
|
45
|
+
* Lifecycle policy definitions
|
|
46
|
+
*
|
|
47
|
+
* Determines which nodes are eligible for cleanup based on lifecycle.
|
|
48
|
+
*/
|
|
49
|
+
export declare const LIFECYCLE_POLICY: Record<LifecycleType, LifecyclePolicyEntry>;
|
|
50
|
+
/**
|
|
51
|
+
* Metadata flag that indicates sensitive data requiring stricter retention
|
|
52
|
+
*/
|
|
53
|
+
export declare const SENSITIVE_FLAG = "sensitive";
|
|
54
|
+
/**
|
|
55
|
+
* Cleanup options for memory pruning
|
|
56
|
+
*/
|
|
57
|
+
export interface CleanupOptions {
|
|
58
|
+
/** If true, preview without modifications */
|
|
59
|
+
dryRun?: boolean;
|
|
60
|
+
/** Session ID to consider closed (removes session lifecycle nodes) */
|
|
61
|
+
sessionId?: string;
|
|
62
|
+
/** TTL duration string (e.g., '30d', '7d', '24h') for age-based cleanup */
|
|
63
|
+
ttl?: string;
|
|
64
|
+
/** TTL in milliseconds (alternative to ttl string) */
|
|
65
|
+
ttlMs?: number;
|
|
66
|
+
/** Current timestamp for testing (defaults to Date.now()) */
|
|
67
|
+
now?: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Breakdown of cleanup by lifecycle type
|
|
71
|
+
*/
|
|
72
|
+
interface CleanupBreakdown {
|
|
73
|
+
ephemeral: number;
|
|
74
|
+
session: number;
|
|
75
|
+
wu: number;
|
|
76
|
+
sensitive: number;
|
|
77
|
+
ttlExpired: number;
|
|
78
|
+
activeSessionProtected: number;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Result of cleanup operation
|
|
82
|
+
*/
|
|
83
|
+
export interface CleanupResult {
|
|
84
|
+
/** Whether cleanup succeeded */
|
|
85
|
+
success: boolean;
|
|
86
|
+
/** IDs of removed nodes */
|
|
87
|
+
removedIds: string[];
|
|
88
|
+
/** IDs of retained nodes */
|
|
89
|
+
retainedIds: string[];
|
|
90
|
+
/** Approximate bytes freed (0 if dry-run) */
|
|
91
|
+
bytesFreed: number;
|
|
92
|
+
/** Ratio of removed to total nodes */
|
|
93
|
+
compactionRatio: number;
|
|
94
|
+
/** True if in dry-run mode */
|
|
95
|
+
dryRun?: boolean;
|
|
96
|
+
/** TTL in milliseconds if TTL was provided */
|
|
97
|
+
ttlMs?: number;
|
|
98
|
+
/** Breakdown by lifecycle */
|
|
99
|
+
breakdown: CleanupBreakdown;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Parse a TTL duration string into milliseconds (WU-1554).
|
|
103
|
+
*
|
|
104
|
+
* Uses the `ms` package to parse human-readable duration strings.
|
|
105
|
+
*
|
|
106
|
+
* @param ttlString - TTL string (e.g., '30d', '7d', '24h', '60m')
|
|
107
|
+
* @returns TTL in milliseconds
|
|
108
|
+
* @throws If TTL format is invalid
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* parseTtl('30d'); // 2592000000 (30 days in ms)
|
|
112
|
+
* parseTtl('7d'); // 604800000 (7 days in ms)
|
|
113
|
+
* parseTtl('24h'); // 86400000 (24 hours in ms)
|
|
114
|
+
*/
|
|
115
|
+
export declare function parseTtl(ttlString: string): number;
|
|
116
|
+
/**
|
|
117
|
+
* Check if a node has expired based on TTL (WU-1554).
|
|
118
|
+
*
|
|
119
|
+
* @param node - Memory node to check
|
|
120
|
+
* @param ttlMs - TTL in milliseconds
|
|
121
|
+
* @param now - Current timestamp (defaults to Date.now())
|
|
122
|
+
* @returns True if node is older than TTL
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* // Check if node is older than 30 days
|
|
126
|
+
* const expired = isNodeExpired(node, 30 * 24 * 60 * 60 * 1000);
|
|
127
|
+
*/
|
|
128
|
+
export declare function isNodeExpired(node: MemoryNode, ttlMs: number, now?: number): boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Check if a node should be removed based on lifecycle policy and TTL.
|
|
131
|
+
*
|
|
132
|
+
* Policy rules (checked in order):
|
|
133
|
+
* 1. Active sessions are always retained (WU-1554)
|
|
134
|
+
* 2. Sensitive nodes are always retained
|
|
135
|
+
* 3. Protected lifecycle (project) nodes are never removed
|
|
136
|
+
* 4. TTL expiration removes old nodes (WU-1554)
|
|
137
|
+
* 5. Ephemeral nodes are always removed
|
|
138
|
+
* 6. Session nodes are removed when their session is closed
|
|
139
|
+
* 7. WU nodes are removed only when marked with summarized_into
|
|
140
|
+
*
|
|
141
|
+
* @param {MemoryNode} node - Memory node to check
|
|
142
|
+
* @param {CleanupOptions} options - Cleanup options
|
|
143
|
+
* @returns {{remove: boolean, reason: string}} Removal decision with reason
|
|
144
|
+
*/
|
|
145
|
+
export declare function shouldRemoveNode(node: MemoryNode, options?: CleanupOptions): {
|
|
146
|
+
remove: boolean;
|
|
147
|
+
reason: string;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Calculate approximate byte size of a node when serialized.
|
|
151
|
+
*
|
|
152
|
+
* @param node - Memory node
|
|
153
|
+
* @returns Approximate byte size
|
|
154
|
+
*/
|
|
155
|
+
export declare function estimateNodeBytes(node: MemoryNode): number;
|
|
156
|
+
/**
|
|
157
|
+
* Calculate compaction ratio (removed / total).
|
|
158
|
+
*
|
|
159
|
+
* @param removedCount - Number of removed nodes
|
|
160
|
+
* @param totalCount - Total number of nodes
|
|
161
|
+
* @returns Compaction ratio (0 to 1, or 0 if no nodes)
|
|
162
|
+
*/
|
|
163
|
+
export declare function getCompactionRatio(removedCount: number, totalCount: number): number;
|
|
164
|
+
/**
|
|
165
|
+
* Perform memory cleanup based on lifecycle policy and TTL.
|
|
166
|
+
*
|
|
167
|
+
* Removes nodes according to lifecycle policy:
|
|
168
|
+
* - Active sessions are never removed (WU-1554)
|
|
169
|
+
* - Sensitive nodes are always retained
|
|
170
|
+
* - Project nodes are never removed
|
|
171
|
+
* - TTL-expired nodes are removed (WU-1554)
|
|
172
|
+
* - Ephemeral nodes are always removed
|
|
173
|
+
* - Session nodes are removed when their session is closed
|
|
174
|
+
* - WU nodes are removed only when marked with summarized_into
|
|
175
|
+
*
|
|
176
|
+
* In dry-run mode, no modifications are made but the result shows
|
|
177
|
+
* what would be removed.
|
|
178
|
+
*
|
|
179
|
+
* @param {string} baseDir - Base directory containing .beacon/memory/
|
|
180
|
+
* @param {CleanupOptions} options - Cleanup options
|
|
181
|
+
* @returns {Promise<CleanupResult>} Result with removed nodes and metrics
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* // Cleanup with dry-run to preview
|
|
185
|
+
* const preview = await cleanupMemory(baseDir, { dryRun: true });
|
|
186
|
+
* console.log(`Would remove ${preview.removedIds.length} nodes`);
|
|
187
|
+
* console.log(`Would free ${preview.bytesFreed} bytes`);
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* // Cleanup session nodes when session closes
|
|
191
|
+
* const result = await cleanupMemory(baseDir, {
|
|
192
|
+
* sessionId: 'abc-123-def-456',
|
|
193
|
+
* });
|
|
194
|
+
* console.log(`Removed ${result.removedIds.length} nodes`);
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* // WU-1554: TTL-based cleanup
|
|
198
|
+
* const result = await cleanupMemory(baseDir, { ttl: '30d' });
|
|
199
|
+
* console.log(`Removed ${result.breakdown.ttlExpired} expired nodes`);
|
|
200
|
+
*/
|
|
201
|
+
export declare function cleanupMemory(baseDir: string, options?: CleanupOptions): Promise<CleanupResult>;
|
|
202
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mem-cleanup-core.d.ts","sourceRoot":"","sources":["../src/mem-cleanup-core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAYH;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;IAC3B,4DAA4D;;;;;IAG5D,mDAAmD;;;;;IAGnD,wDAAwD;;;;;IAGxD,gEAAgE;;;;;;CAEjE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,cAAc,CAAC;AAO1C;;GAEG;AAEH;;;;;;;GAOG;AAEH;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,QAAQ,CAAC,SAAS,KAAA,OAkBjC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,IAAI,KAAA,EAAE,KAAK,KAAA,EAAE,GAAG,SAAa,WAc1D;AAgDD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,KAAA,EAAE,OAAO,KAAK;;;EA+ClD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,KAAA,UAGrC;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,KAAA,EAAE,UAAU,KAAA,UAK1D;AAyCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAsB,aAAa,CAAC,OAAO,KAAA,EAAE,OAAO,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoExD"}
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Cleanup Core (WU-1472, WU-1554)
|
|
3
|
+
*
|
|
4
|
+
* Prune closed memory nodes based on lifecycle policy.
|
|
5
|
+
* Implements compaction to prevent memory bloat.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Remove ephemeral nodes (always discarded)
|
|
9
|
+
* - Remove session nodes when session is closed
|
|
10
|
+
* - Archive summarized nodes (marked with summarized_into)
|
|
11
|
+
* - Respect sensitive:true flag for stricter retention
|
|
12
|
+
* - Support dry-run mode for preview
|
|
13
|
+
* - Report compaction metrics (ratio, bytes freed)
|
|
14
|
+
* - WU-1554: TTL-based expiration for old nodes
|
|
15
|
+
* - WU-1554: Active session protection regardless of age
|
|
16
|
+
*
|
|
17
|
+
* Lifecycle Policy:
|
|
18
|
+
* - ephemeral: Always removed (scratch pad data)
|
|
19
|
+
* - session: Removed when session is closed
|
|
20
|
+
* - wu: Removed when marked with summarized_into (after WU completion)
|
|
21
|
+
* - project: Never removed (architectural knowledge)
|
|
22
|
+
*
|
|
23
|
+
* TTL Policy (WU-1554):
|
|
24
|
+
* - Nodes older than TTL are removed regardless of lifecycle
|
|
25
|
+
* - Active sessions (status: 'active') are never removed
|
|
26
|
+
* - Project and sensitive nodes are protected from TTL removal
|
|
27
|
+
*
|
|
28
|
+
* @see {@link tools/mem-cleanup.mjs} - CLI wrapper
|
|
29
|
+
* @see {@link tools/__tests__/mem-cleanup.test.mjs} - Tests
|
|
30
|
+
*/
|
|
31
|
+
import fs from 'node:fs/promises';
|
|
32
|
+
import path from 'node:path';
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
34
|
+
const ms = require('ms');
|
|
35
|
+
import { loadMemory, MEMORY_FILE_NAME } from './memory-store.js';
|
|
36
|
+
/**
|
|
37
|
+
* Memory directory path relative to base directory
|
|
38
|
+
*/
|
|
39
|
+
const MEMORY_DIR = '.beacon/memory';
|
|
40
|
+
/**
|
|
41
|
+
* Lifecycle policy definitions
|
|
42
|
+
*
|
|
43
|
+
* Determines which nodes are eligible for cleanup based on lifecycle.
|
|
44
|
+
*/
|
|
45
|
+
export const LIFECYCLE_POLICY = {
|
|
46
|
+
/** Ephemeral nodes are always removed - scratch pad data */
|
|
47
|
+
ephemeral: { alwaysRemove: true, requiresSummarized: false },
|
|
48
|
+
/** Session nodes removed when session is closed */
|
|
49
|
+
session: { alwaysRemove: false, requiresSummarized: true },
|
|
50
|
+
/** WU nodes removed only when summarized_into is set */
|
|
51
|
+
wu: { alwaysRemove: false, requiresSummarized: true },
|
|
52
|
+
/** Project nodes are never removed - architectural knowledge */
|
|
53
|
+
project: { alwaysRemove: false, requiresSummarized: false, protected: true },
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Metadata flag that indicates sensitive data requiring stricter retention
|
|
57
|
+
*/
|
|
58
|
+
export const SENSITIVE_FLAG = 'sensitive';
|
|
59
|
+
/**
|
|
60
|
+
* Status value indicating an active session (WU-1554)
|
|
61
|
+
*/
|
|
62
|
+
const ACTIVE_SESSION_STATUS = 'active';
|
|
63
|
+
/**
|
|
64
|
+
* Parse a TTL duration string into milliseconds (WU-1554).
|
|
65
|
+
*
|
|
66
|
+
* Uses the `ms` package to parse human-readable duration strings.
|
|
67
|
+
*
|
|
68
|
+
* @param ttlString - TTL string (e.g., '30d', '7d', '24h', '60m')
|
|
69
|
+
* @returns TTL in milliseconds
|
|
70
|
+
* @throws If TTL format is invalid
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* parseTtl('30d'); // 2592000000 (30 days in ms)
|
|
74
|
+
* parseTtl('7d'); // 604800000 (7 days in ms)
|
|
75
|
+
* parseTtl('24h'); // 86400000 (24 hours in ms)
|
|
76
|
+
*/
|
|
77
|
+
export function parseTtl(ttlString) {
|
|
78
|
+
if (!ttlString || typeof ttlString !== 'string') {
|
|
79
|
+
throw new Error('Invalid TTL format: TTL string is required');
|
|
80
|
+
}
|
|
81
|
+
const trimmed = ttlString.trim();
|
|
82
|
+
if (!trimmed) {
|
|
83
|
+
throw new Error('Invalid TTL format: TTL string is required');
|
|
84
|
+
}
|
|
85
|
+
// Use ms package to parse the duration
|
|
86
|
+
const result = ms(trimmed);
|
|
87
|
+
if (result === undefined || result <= 0) {
|
|
88
|
+
throw new Error(`Invalid TTL format: "${ttlString}" is not a valid duration`);
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Check if a node has expired based on TTL (WU-1554).
|
|
94
|
+
*
|
|
95
|
+
* @param node - Memory node to check
|
|
96
|
+
* @param ttlMs - TTL in milliseconds
|
|
97
|
+
* @param now - Current timestamp (defaults to Date.now())
|
|
98
|
+
* @returns True if node is older than TTL
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* // Check if node is older than 30 days
|
|
102
|
+
* const expired = isNodeExpired(node, 30 * 24 * 60 * 60 * 1000);
|
|
103
|
+
*/
|
|
104
|
+
export function isNodeExpired(node, ttlMs, now = Date.now()) {
|
|
105
|
+
if (!node.created_at) {
|
|
106
|
+
return false; // No timestamp means we can't determine age - safer to retain
|
|
107
|
+
}
|
|
108
|
+
const createdAt = new Date(node.created_at).getTime();
|
|
109
|
+
// Invalid date - safer to retain
|
|
110
|
+
if (Number.isNaN(createdAt)) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
const age = now - createdAt;
|
|
114
|
+
return age > ttlMs;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Check if a node is an active session (WU-1554).
|
|
118
|
+
*
|
|
119
|
+
* Active sessions are protected from all cleanup including TTL.
|
|
120
|
+
*
|
|
121
|
+
* @param node - Memory node to check
|
|
122
|
+
* @returns True if node is an active session
|
|
123
|
+
*/
|
|
124
|
+
function isActiveSession(node) {
|
|
125
|
+
if (node.type !== 'session') {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
if (!node.metadata) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
return node.metadata.status === ACTIVE_SESSION_STATUS;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Check if a node has the sensitive flag set in metadata.
|
|
135
|
+
*
|
|
136
|
+
* @param node - Memory node to check
|
|
137
|
+
* @returns True if sensitive flag is set
|
|
138
|
+
*/
|
|
139
|
+
function hasSensitiveFlag(node) {
|
|
140
|
+
if (!node.metadata) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
return Object.hasOwn(node.metadata, SENSITIVE_FLAG) && node.metadata[SENSITIVE_FLAG] === true;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get the lifecycle policy for a node's lifecycle.
|
|
147
|
+
*
|
|
148
|
+
* @param lifecycle - Lifecycle name
|
|
149
|
+
* @returns Policy object or undefined if not found
|
|
150
|
+
*/
|
|
151
|
+
function getLifecyclePolicy(lifecycle) {
|
|
152
|
+
if (!Object.hasOwn(LIFECYCLE_POLICY, lifecycle)) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
return LIFECYCLE_POLICY[lifecycle];
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Check if a node should be removed based on lifecycle policy and TTL.
|
|
159
|
+
*
|
|
160
|
+
* Policy rules (checked in order):
|
|
161
|
+
* 1. Active sessions are always retained (WU-1554)
|
|
162
|
+
* 2. Sensitive nodes are always retained
|
|
163
|
+
* 3. Protected lifecycle (project) nodes are never removed
|
|
164
|
+
* 4. TTL expiration removes old nodes (WU-1554)
|
|
165
|
+
* 5. Ephemeral nodes are always removed
|
|
166
|
+
* 6. Session nodes are removed when their session is closed
|
|
167
|
+
* 7. WU nodes are removed only when marked with summarized_into
|
|
168
|
+
*
|
|
169
|
+
* @param {MemoryNode} node - Memory node to check
|
|
170
|
+
* @param {CleanupOptions} options - Cleanup options
|
|
171
|
+
* @returns {{remove: boolean, reason: string}} Removal decision with reason
|
|
172
|
+
*/
|
|
173
|
+
export function shouldRemoveNode(node, options = {}) {
|
|
174
|
+
const { sessionId, ttlMs, now = Date.now() } = options;
|
|
175
|
+
// WU-1554: Active sessions are always protected first
|
|
176
|
+
if (isActiveSession(node)) {
|
|
177
|
+
return { remove: false, reason: 'active-session-protected' };
|
|
178
|
+
}
|
|
179
|
+
// Check sensitive flag - stricter retention
|
|
180
|
+
if (hasSensitiveFlag(node)) {
|
|
181
|
+
return { remove: false, reason: 'sensitive-retained' };
|
|
182
|
+
}
|
|
183
|
+
const policy = getLifecyclePolicy(node.lifecycle);
|
|
184
|
+
// Unknown lifecycle - retain for safety
|
|
185
|
+
if (!policy) {
|
|
186
|
+
return { remove: false, reason: 'unknown-lifecycle' };
|
|
187
|
+
}
|
|
188
|
+
// Protected lifecycle (project) - never remove
|
|
189
|
+
if (policy.protected) {
|
|
190
|
+
return { remove: false, reason: 'protected-lifecycle' };
|
|
191
|
+
}
|
|
192
|
+
// WU-1554: TTL-based expiration (after protection checks)
|
|
193
|
+
if (ttlMs && isNodeExpired(node, ttlMs, now)) {
|
|
194
|
+
return { remove: true, reason: 'ttl-expired' };
|
|
195
|
+
}
|
|
196
|
+
// Ephemeral lifecycle - always remove
|
|
197
|
+
if (policy.alwaysRemove) {
|
|
198
|
+
return { remove: true, reason: 'ephemeral-cleanup' };
|
|
199
|
+
}
|
|
200
|
+
// Session lifecycle - remove if session is closed
|
|
201
|
+
if (node.lifecycle === 'session' && sessionId && node.session_id === sessionId) {
|
|
202
|
+
return { remove: true, reason: 'session-closed' };
|
|
203
|
+
}
|
|
204
|
+
// WU lifecycle - remove only if summarized
|
|
205
|
+
if (policy.requiresSummarized && node.metadata?.summarized_into) {
|
|
206
|
+
return { remove: true, reason: 'summarized-archived' };
|
|
207
|
+
}
|
|
208
|
+
// Default: retain
|
|
209
|
+
return { remove: false, reason: 'policy-retained' };
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Calculate approximate byte size of a node when serialized.
|
|
213
|
+
*
|
|
214
|
+
* @param node - Memory node
|
|
215
|
+
* @returns Approximate byte size
|
|
216
|
+
*/
|
|
217
|
+
export function estimateNodeBytes(node) {
|
|
218
|
+
// JSON.stringify + newline character
|
|
219
|
+
return JSON.stringify(node).length + 1;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Calculate compaction ratio (removed / total).
|
|
223
|
+
*
|
|
224
|
+
* @param removedCount - Number of removed nodes
|
|
225
|
+
* @param totalCount - Total number of nodes
|
|
226
|
+
* @returns Compaction ratio (0 to 1, or 0 if no nodes)
|
|
227
|
+
*/
|
|
228
|
+
export function getCompactionRatio(removedCount, totalCount) {
|
|
229
|
+
if (totalCount === 0) {
|
|
230
|
+
return 0;
|
|
231
|
+
}
|
|
232
|
+
return removedCount / totalCount;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Reason-to-breakdown-key mapping for tracking cleanup statistics.
|
|
236
|
+
*/
|
|
237
|
+
const REASON_TO_BREAKDOWN_KEY = {
|
|
238
|
+
'ephemeral-cleanup': 'ephemeral',
|
|
239
|
+
'session-closed': 'session',
|
|
240
|
+
'summarized-archived': 'wu',
|
|
241
|
+
'sensitive-retained': 'sensitive',
|
|
242
|
+
'ttl-expired': 'ttlExpired',
|
|
243
|
+
'active-session-protected': 'activeSessionProtected',
|
|
244
|
+
};
|
|
245
|
+
/**
|
|
246
|
+
* Update breakdown statistics based on removal decision.
|
|
247
|
+
*
|
|
248
|
+
* @param breakdown - Breakdown object to update
|
|
249
|
+
* @param decision - Removal decision with reason
|
|
250
|
+
*/
|
|
251
|
+
function updateBreakdown(breakdown, decision) {
|
|
252
|
+
const key = REASON_TO_BREAKDOWN_KEY[decision.reason];
|
|
253
|
+
if (key && Object.hasOwn(breakdown, key)) {
|
|
254
|
+
breakdown[key]++;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Write retained nodes to memory file.
|
|
259
|
+
*
|
|
260
|
+
* @param memoryDir - Memory directory path
|
|
261
|
+
* @param retainedNodes - Nodes to write
|
|
262
|
+
*/
|
|
263
|
+
async function writeRetainedNodes(memoryDir, retainedNodes) {
|
|
264
|
+
const filePath = path.join(memoryDir, MEMORY_FILE_NAME);
|
|
265
|
+
const content = retainedNodes.map((n) => JSON.stringify(n)).join('\n') + (retainedNodes.length > 0 ? '\n' : '');
|
|
266
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool writes known path
|
|
267
|
+
await fs.writeFile(filePath, content, { encoding: 'utf-8' });
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Perform memory cleanup based on lifecycle policy and TTL.
|
|
271
|
+
*
|
|
272
|
+
* Removes nodes according to lifecycle policy:
|
|
273
|
+
* - Active sessions are never removed (WU-1554)
|
|
274
|
+
* - Sensitive nodes are always retained
|
|
275
|
+
* - Project nodes are never removed
|
|
276
|
+
* - TTL-expired nodes are removed (WU-1554)
|
|
277
|
+
* - Ephemeral nodes are always removed
|
|
278
|
+
* - Session nodes are removed when their session is closed
|
|
279
|
+
* - WU nodes are removed only when marked with summarized_into
|
|
280
|
+
*
|
|
281
|
+
* In dry-run mode, no modifications are made but the result shows
|
|
282
|
+
* what would be removed.
|
|
283
|
+
*
|
|
284
|
+
* @param {string} baseDir - Base directory containing .beacon/memory/
|
|
285
|
+
* @param {CleanupOptions} options - Cleanup options
|
|
286
|
+
* @returns {Promise<CleanupResult>} Result with removed nodes and metrics
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* // Cleanup with dry-run to preview
|
|
290
|
+
* const preview = await cleanupMemory(baseDir, { dryRun: true });
|
|
291
|
+
* console.log(`Would remove ${preview.removedIds.length} nodes`);
|
|
292
|
+
* console.log(`Would free ${preview.bytesFreed} bytes`);
|
|
293
|
+
*
|
|
294
|
+
* @example
|
|
295
|
+
* // Cleanup session nodes when session closes
|
|
296
|
+
* const result = await cleanupMemory(baseDir, {
|
|
297
|
+
* sessionId: 'abc-123-def-456',
|
|
298
|
+
* });
|
|
299
|
+
* console.log(`Removed ${result.removedIds.length} nodes`);
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* // WU-1554: TTL-based cleanup
|
|
303
|
+
* const result = await cleanupMemory(baseDir, { ttl: '30d' });
|
|
304
|
+
* console.log(`Removed ${result.breakdown.ttlExpired} expired nodes`);
|
|
305
|
+
*/
|
|
306
|
+
export async function cleanupMemory(baseDir, options = {}) {
|
|
307
|
+
const { dryRun = false, sessionId, ttl, ttlMs: providedTtlMs, now = Date.now() } = options;
|
|
308
|
+
const memoryDir = path.join(baseDir, MEMORY_DIR);
|
|
309
|
+
// WU-1554: Parse TTL if provided as string
|
|
310
|
+
let ttlMs = providedTtlMs;
|
|
311
|
+
if (ttl && !ttlMs) {
|
|
312
|
+
ttlMs = parseTtl(ttl);
|
|
313
|
+
}
|
|
314
|
+
// Load existing memory
|
|
315
|
+
const memory = await loadMemory(memoryDir);
|
|
316
|
+
// Track cleanup decisions
|
|
317
|
+
const removedIds = [];
|
|
318
|
+
const retainedIds = [];
|
|
319
|
+
const retainedNodes = [];
|
|
320
|
+
let bytesFreed = 0;
|
|
321
|
+
const breakdown = {
|
|
322
|
+
ephemeral: 0,
|
|
323
|
+
session: 0,
|
|
324
|
+
wu: 0,
|
|
325
|
+
sensitive: 0,
|
|
326
|
+
ttlExpired: 0,
|
|
327
|
+
activeSessionProtected: 0,
|
|
328
|
+
};
|
|
329
|
+
// Process each node
|
|
330
|
+
for (const node of memory.nodes) {
|
|
331
|
+
const decision = shouldRemoveNode(node, { sessionId, ttlMs, now });
|
|
332
|
+
if (decision.remove) {
|
|
333
|
+
removedIds.push(node.id);
|
|
334
|
+
bytesFreed += estimateNodeBytes(node);
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
retainedIds.push(node.id);
|
|
338
|
+
retainedNodes.push(node);
|
|
339
|
+
}
|
|
340
|
+
updateBreakdown(breakdown, decision);
|
|
341
|
+
}
|
|
342
|
+
const compactionRatio = getCompactionRatio(removedIds.length, memory.nodes.length);
|
|
343
|
+
const baseResult = {
|
|
344
|
+
success: true,
|
|
345
|
+
removedIds,
|
|
346
|
+
retainedIds,
|
|
347
|
+
bytesFreed,
|
|
348
|
+
compactionRatio,
|
|
349
|
+
breakdown,
|
|
350
|
+
};
|
|
351
|
+
// WU-1554: Include TTL in result if provided
|
|
352
|
+
if (ttlMs) {
|
|
353
|
+
baseResult.ttlMs = ttlMs;
|
|
354
|
+
}
|
|
355
|
+
// If dry-run, return preview without modifications
|
|
356
|
+
if (dryRun) {
|
|
357
|
+
return { ...baseResult, dryRun: true };
|
|
358
|
+
}
|
|
359
|
+
// Write retained nodes back to file (rewrite entire file)
|
|
360
|
+
if (removedIds.length > 0) {
|
|
361
|
+
await writeRetainedNodes(memoryDir, retainedNodes);
|
|
362
|
+
}
|
|
363
|
+
return baseResult;
|
|
364
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mem-cleanup-core.js","sourceRoot":"","sources":["../src/mem-cleanup-core.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Create Core (WU-1469)
|
|
3
|
+
*
|
|
4
|
+
* Core logic for creating memory nodes with discovered-from provenance.
|
|
5
|
+
* KEY DIFFERENTIATOR: supports discovered-from relationship for scope-creep
|
|
6
|
+
* forensics. Creates audit trail of WHY work expanded, not just WHAT changed.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Creates all 5 node types: session, discovery, checkpoint, note, summary
|
|
10
|
+
* - Auto-generates hash-based ID using mem-id
|
|
11
|
+
* - Validates node against memory-schema
|
|
12
|
+
* - Supports discovered-from relationship for provenance tracking
|
|
13
|
+
*
|
|
14
|
+
* @see {@link tools/mem-create.mjs} - CLI wrapper
|
|
15
|
+
* @see {@link tools/__tests__/mem-create.test.mjs} - Tests
|
|
16
|
+
* @see {@link tools/lib/memory-schema.mjs} - Schema definitions
|
|
17
|
+
*/
|
|
18
|
+
import { type MemoryNode } from './memory-schema.js';
|
|
19
|
+
/**
|
|
20
|
+
* Memory node creation options
|
|
21
|
+
*/
|
|
22
|
+
export interface CreateMemoryNodeOptions {
|
|
23
|
+
/** Node title/content (required) */
|
|
24
|
+
title: string;
|
|
25
|
+
/** Node type (session, discovery, checkpoint, note, summary) */
|
|
26
|
+
type?: string;
|
|
27
|
+
/** Work Unit ID to link node to */
|
|
28
|
+
wuId?: string;
|
|
29
|
+
/** Session ID to link node to */
|
|
30
|
+
sessionId?: string;
|
|
31
|
+
/** Parent node ID for provenance tracking */
|
|
32
|
+
discoveredFrom?: string;
|
|
33
|
+
/** Tags for categorization */
|
|
34
|
+
tags?: string[];
|
|
35
|
+
/** Priority level (P0, P1, P2, P3) */
|
|
36
|
+
priority?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Relationship between memory nodes
|
|
40
|
+
*/
|
|
41
|
+
interface Relationship {
|
|
42
|
+
from_id: string;
|
|
43
|
+
to_id: string;
|
|
44
|
+
type: string;
|
|
45
|
+
created_at: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Memory node creation result
|
|
49
|
+
*/
|
|
50
|
+
export interface CreateMemoryNodeResult {
|
|
51
|
+
/** Whether the operation succeeded */
|
|
52
|
+
success: boolean;
|
|
53
|
+
/** Created memory node */
|
|
54
|
+
node: MemoryNode;
|
|
55
|
+
/** Created relationship (if discoveredFrom provided) */
|
|
56
|
+
relationship?: Relationship;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Creates a new memory node with optional discovered-from provenance.
|
|
60
|
+
*
|
|
61
|
+
* Creates a memory node with:
|
|
62
|
+
* - Unique ID (mem-XXXX format) generated from content hash
|
|
63
|
+
* - User-provided title as content
|
|
64
|
+
* - Type-appropriate lifecycle
|
|
65
|
+
* - Optional discovered-from relationship for provenance tracking
|
|
66
|
+
*
|
|
67
|
+
* @param {string} baseDir - Base directory containing .beacon/memory/
|
|
68
|
+
* @param {CreateMemoryNodeOptions} options - Node creation options
|
|
69
|
+
* @returns {Promise<CreateMemoryNodeResult>} Result with created node and optional relationship
|
|
70
|
+
* @throws {Error} If title is missing, type is invalid, or IDs are malformed
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* // Create a simple discovery node
|
|
74
|
+
* const result = await createMemoryNode(baseDir, {
|
|
75
|
+
* title: 'Found relevant file at src/utils.mjs',
|
|
76
|
+
* type: 'discovery',
|
|
77
|
+
* wuId: 'WU-1469',
|
|
78
|
+
* });
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* // Create a node with provenance (scope-creep tracking)
|
|
82
|
+
* const parent = await createMemoryNode(baseDir, {
|
|
83
|
+
* title: 'Found src/components/',
|
|
84
|
+
* type: 'discovery',
|
|
85
|
+
* });
|
|
86
|
+
* const child = await createMemoryNode(baseDir, {
|
|
87
|
+
* title: 'Found src/components/Button.tsx',
|
|
88
|
+
* type: 'discovery',
|
|
89
|
+
* discoveredFrom: parent.node.id, // Track where this came from
|
|
90
|
+
* });
|
|
91
|
+
*/
|
|
92
|
+
export declare function createMemoryNode(baseDir: string, options: CreateMemoryNodeOptions): Promise<CreateMemoryNodeResult>;
|
|
93
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mem-create-core.d.ts","sourceRoot":"","sources":["../src/mem-create-core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAwPH;;;;;;;;;;;GAWG;AAEH;;;;;;;GAOG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,KAAA,EAAE,OAAO,KAAA;;;;;;;;;GA8HtD"}
|