@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.
Files changed (55) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +181 -0
  3. package/dist/index.d.ts +16 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +16 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/mem-checkpoint-core.d.ts +91 -0
  8. package/dist/mem-checkpoint-core.d.ts.map +1 -0
  9. package/dist/mem-checkpoint-core.js +175 -0
  10. package/dist/mem-checkpoint-core.js.map +1 -0
  11. package/dist/mem-cleanup-core.d.ts +202 -0
  12. package/dist/mem-cleanup-core.d.ts.map +1 -0
  13. package/dist/mem-cleanup-core.js +364 -0
  14. package/dist/mem-cleanup-core.js.map +1 -0
  15. package/dist/mem-create-core.d.ts +93 -0
  16. package/dist/mem-create-core.d.ts.map +1 -0
  17. package/dist/mem-create-core.js +369 -0
  18. package/dist/mem-create-core.js.map +1 -0
  19. package/dist/mem-id.d.ts +91 -0
  20. package/dist/mem-id.d.ts.map +1 -0
  21. package/dist/mem-id.js +136 -0
  22. package/dist/mem-id.js.map +1 -0
  23. package/dist/mem-init-core.d.ts +91 -0
  24. package/dist/mem-init-core.d.ts.map +1 -0
  25. package/dist/mem-init-core.js +138 -0
  26. package/dist/mem-init-core.js.map +1 -0
  27. package/dist/mem-ready-core.d.ts +56 -0
  28. package/dist/mem-ready-core.d.ts.map +1 -0
  29. package/dist/mem-ready-core.js +232 -0
  30. package/dist/mem-ready-core.js.map +1 -0
  31. package/dist/mem-signal-core.d.ts +132 -0
  32. package/dist/mem-signal-core.d.ts.map +1 -0
  33. package/dist/mem-signal-core.js +249 -0
  34. package/dist/mem-signal-core.js.map +1 -0
  35. package/dist/mem-start-core.d.ts +76 -0
  36. package/dist/mem-start-core.d.ts.map +1 -0
  37. package/dist/mem-start-core.js +133 -0
  38. package/dist/mem-start-core.js.map +1 -0
  39. package/dist/mem-summarize-core.d.ts +105 -0
  40. package/dist/mem-summarize-core.d.ts.map +1 -0
  41. package/dist/mem-summarize-core.js +263 -0
  42. package/dist/mem-summarize-core.js.map +1 -0
  43. package/dist/mem-triage-core.d.ts +127 -0
  44. package/dist/mem-triage-core.d.ts.map +1 -0
  45. package/dist/mem-triage-core.js +332 -0
  46. package/dist/mem-triage-core.js.map +1 -0
  47. package/dist/memory-schema.d.ts +150 -0
  48. package/dist/memory-schema.d.ts.map +1 -0
  49. package/dist/memory-schema.js +149 -0
  50. package/dist/memory-schema.js.map +1 -0
  51. package/dist/memory-store.d.ts +108 -0
  52. package/dist/memory-store.d.ts.map +1 -0
  53. package/dist/memory-store.js +234 -0
  54. package/dist/memory-store.js.map +1 -0
  55. package/package.json +76 -0
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Memory Signal Core Logic (WU-1473)
3
+ *
4
+ * Core logic for creating coordination signals between parallel agents.
5
+ * Enables sub-100ms agent communication via JSONL append operations.
6
+ *
7
+ * Features:
8
+ * - Append-only writes for sub-100ms performance
9
+ * - WU-scoped signals for focused coordination
10
+ * - Lane-targeted signals for cross-team communication
11
+ * - Read/unread tracking for mem:inbox integration
12
+ *
13
+ * @see {@link tools/mem-signal.mjs} - CLI wrapper
14
+ * @see {@link tools/__tests__/mem-signal.test.mjs} - Tests
15
+ */
16
+ import { randomBytes } from 'node:crypto';
17
+ import fs from 'node:fs/promises';
18
+ import path from 'node:path';
19
+ /**
20
+ * Signal file name constant
21
+ */
22
+ export const SIGNAL_FILE_NAME = 'signals.jsonl';
23
+ /**
24
+ * Memory directory path within project
25
+ */
26
+ const MEMORY_DIR = '.beacon/memory';
27
+ /**
28
+ * WU ID validation pattern (from memory-schema.mjs)
29
+ */
30
+ const WU_ID_PATTERN = /^WU-\d+$/;
31
+ /**
32
+ * Signal ID prefix
33
+ */
34
+ const SIGNAL_ID_PREFIX = 'sig-';
35
+ /**
36
+ * Number of hex characters in signal ID suffix
37
+ */
38
+ const SIGNAL_ID_LENGTH = 8;
39
+ /**
40
+ * Error messages for validation
41
+ */
42
+ const ERROR_MESSAGES = {
43
+ MESSAGE_REQUIRED: 'message is required and cannot be empty',
44
+ INVALID_WU_ID: 'Invalid WU ID format. Expected WU-XXX (e.g., WU-1473)',
45
+ };
46
+ /**
47
+ * Generates a unique signal ID using random bytes.
48
+ *
49
+ * Format: sig-[8 hex chars]
50
+ * Uses crypto.randomBytes for uniqueness (not content-based like mem-id).
51
+ *
52
+ * @returns {string} Signal ID in format sig-XXXXXXXX
53
+ */
54
+ function generateSignalId() {
55
+ const bytes = randomBytes(4);
56
+ const hex = bytes.toString('hex').slice(0, SIGNAL_ID_LENGTH);
57
+ return `${SIGNAL_ID_PREFIX}${hex}`;
58
+ }
59
+ /**
60
+ * Gets the memory directory path for a project.
61
+ *
62
+ * @param baseDir - Project base directory
63
+ * @returns Full path to memory directory
64
+ */
65
+ function getMemoryDir(baseDir) {
66
+ return path.join(baseDir, MEMORY_DIR);
67
+ }
68
+ /**
69
+ * Gets the signals file path for a project.
70
+ *
71
+ * @param baseDir - Project base directory
72
+ * @returns Full path to signals.jsonl
73
+ */
74
+ function getSignalsPath(baseDir) {
75
+ return path.join(getMemoryDir(baseDir), SIGNAL_FILE_NAME);
76
+ }
77
+ /**
78
+ * Validates a WU ID format.
79
+ *
80
+ * @param wuId - WU ID to validate
81
+ * @returns True if valid
82
+ */
83
+ function isValidWuId(wuId) {
84
+ return WU_ID_PATTERN.test(wuId);
85
+ }
86
+ /**
87
+ * Creates a coordination signal between parallel agents.
88
+ *
89
+ * Signals are appended to signals.jsonl using append-only writes
90
+ * for sub-100ms performance. Signals can be scoped to a specific
91
+ * WU or targeted at a specific lane.
92
+ *
93
+ * @param {string} baseDir - Project base directory
94
+ * @param {CreateSignalOptions} options - Signal options
95
+ * @returns {Promise<CreateSignalResult>} Result with created signal
96
+ * @throws {Error} If message is missing or WU ID is invalid
97
+ *
98
+ * @example
99
+ * const result = await createSignal('/project', {
100
+ * message: 'Starting feature implementation',
101
+ * wuId: 'WU-1473',
102
+ * lane: 'Operations: Tooling',
103
+ * });
104
+ */
105
+ export async function createSignal(baseDir, options) {
106
+ const { message, wuId, lane } = options;
107
+ // Validate message is provided and non-empty
108
+ if (!message || typeof message !== 'string' || message.trim().length === 0) {
109
+ throw new Error(ERROR_MESSAGES.MESSAGE_REQUIRED);
110
+ }
111
+ // Validate WU ID format if provided
112
+ if (wuId !== undefined && !isValidWuId(wuId)) {
113
+ throw new Error(ERROR_MESSAGES.INVALID_WU_ID);
114
+ }
115
+ // Build signal object
116
+ const signal = {
117
+ id: generateSignalId(),
118
+ message: message.trim(),
119
+ created_at: new Date().toISOString(),
120
+ read: false,
121
+ };
122
+ // Add optional fields
123
+ if (wuId) {
124
+ signal.wu_id = wuId;
125
+ }
126
+ if (lane) {
127
+ signal.lane = lane;
128
+ }
129
+ // Ensure memory directory exists
130
+ const memoryDir = getMemoryDir(baseDir);
131
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool creates known directory
132
+ await fs.mkdir(memoryDir, { recursive: true });
133
+ // Append signal to file (append-only for speed)
134
+ const signalsPath = getSignalsPath(baseDir);
135
+ const line = `${JSON.stringify(signal)}\n`;
136
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool writes signals file
137
+ await fs.appendFile(signalsPath, line, 'utf-8');
138
+ return {
139
+ success: true,
140
+ signal,
141
+ };
142
+ }
143
+ /**
144
+ * Loads signals from the signals file with optional filtering.
145
+ *
146
+ * Signals are returned in chronological order (oldest first).
147
+ * Supports filtering by WU ID, lane, and read status.
148
+ *
149
+ * @param {string} baseDir - Project base directory
150
+ * @param {LoadSignalsOptions} [options={}] - Filter options
151
+ * @returns {Promise<Signal[]>} Array of signals matching filters
152
+ *
153
+ * @example
154
+ * // Load all signals
155
+ * const all = await loadSignals('/project');
156
+ *
157
+ * // Load unread signals for a specific WU
158
+ * const unread = await loadSignals('/project', {
159
+ * wuId: 'WU-1473',
160
+ * unreadOnly: true,
161
+ * });
162
+ */
163
+ export async function loadSignals(baseDir, options = {}) {
164
+ const { wuId, lane, unreadOnly, since } = options;
165
+ const signalsPath = getSignalsPath(baseDir);
166
+ // Read file content
167
+ let content;
168
+ try {
169
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool reads signals file
170
+ content = await fs.readFile(signalsPath, { encoding: 'utf-8' });
171
+ }
172
+ catch (err) {
173
+ const error = err;
174
+ if (error.code === 'ENOENT') {
175
+ // File doesn't exist - return empty array
176
+ return [];
177
+ }
178
+ throw error;
179
+ }
180
+ // Parse JSONL content
181
+ const lines = content.split('\n').filter((line) => line.trim());
182
+ const signals = lines.map((line) => JSON.parse(line));
183
+ // Apply filters
184
+ let filtered = signals;
185
+ if (wuId) {
186
+ filtered = filtered.filter((sig) => sig.wu_id === wuId);
187
+ }
188
+ if (lane) {
189
+ filtered = filtered.filter((sig) => sig.lane === lane);
190
+ }
191
+ if (unreadOnly) {
192
+ filtered = filtered.filter((sig) => sig.read === false);
193
+ }
194
+ if (since) {
195
+ const sinceTime = since instanceof Date ? since : new Date(since);
196
+ filtered = filtered.filter((sig) => new Date(sig.created_at) > sinceTime);
197
+ }
198
+ // Return in chronological order (file order is already chronological due to append-only)
199
+ return filtered;
200
+ }
201
+ /**
202
+ * Marks signals as read by updating the signals file.
203
+ *
204
+ * Reads the entire file, updates the read status for matching IDs,
205
+ * and writes back. Only signals that were previously unread are counted.
206
+ *
207
+ * @param baseDir - Project base directory
208
+ * @param signalIds - Array of signal IDs to mark as read
209
+ * @returns Result with count of signals marked
210
+ *
211
+ * @example
212
+ * const result = await markSignalsAsRead('/project', ['sig-abc12345', 'sig-def67890']);
213
+ * console.log(result.markedCount); // 2
214
+ */
215
+ export async function markSignalsAsRead(baseDir, signalIds) {
216
+ const signalsPath = getSignalsPath(baseDir);
217
+ const idSet = new Set(signalIds);
218
+ let markedCount = 0;
219
+ // Read file content
220
+ let content;
221
+ try {
222
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool reads signals file
223
+ content = await fs.readFile(signalsPath, { encoding: 'utf-8' });
224
+ }
225
+ catch (err) {
226
+ const error = err;
227
+ if (error.code === 'ENOENT') {
228
+ // No signals file - nothing to mark
229
+ return { markedCount: 0 };
230
+ }
231
+ throw error;
232
+ }
233
+ // Parse JSONL content
234
+ const lines = content.split('\n').filter((line) => line.trim());
235
+ const updatedLines = lines.map((line) => {
236
+ const signal = JSON.parse(line);
237
+ if (idSet.has(signal.id) && signal.read === false) {
238
+ signal.read = true;
239
+ markedCount++;
240
+ }
241
+ return JSON.stringify(signal);
242
+ });
243
+ // Write back updated content
244
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool writes signals file
245
+ await fs.writeFile(signalsPath, `${updatedLines.join('\n')}\n`, {
246
+ encoding: 'utf-8',
247
+ });
248
+ return { markedCount };
249
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mem-signal-core.js","sourceRoot":"","sources":["../src/mem-signal-core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Memory Start Core (WU-1466)
3
+ *
4
+ * Core logic for creating session nodes linked to WUs.
5
+ * Called by wu:claim enhancement for context restoration after /clear.
6
+ *
7
+ * Features:
8
+ * - Creates session nodes with WU reference
9
+ * - Stores agent type, start timestamp, context tier
10
+ * - Idempotent: multiple starts create separate sessions
11
+ * - Auto-initializes memory layer if not present
12
+ *
13
+ * @see {@link tools/mem-start.mjs} - CLI wrapper
14
+ * @see {@link tools/__tests__/mem-start.test.mjs} - Tests
15
+ * @see {@link tools/lib/memory-schema.mjs} - Schema definitions
16
+ */
17
+ /**
18
+ * Session start options
19
+ */
20
+ export interface StartSessionOptions {
21
+ /** Work Unit ID (required) */
22
+ wuId: string;
23
+ /** Agent type (defaults to 'unknown') */
24
+ agentType?: string;
25
+ /** Context tier (defaults to 'full') */
26
+ contextTier?: string;
27
+ }
28
+ /**
29
+ * Session node structure
30
+ */
31
+ interface SessionNode {
32
+ id: string;
33
+ type: 'session';
34
+ lifecycle: 'wu';
35
+ content: string;
36
+ created_at: string;
37
+ wu_id: string;
38
+ session_id: string;
39
+ metadata: {
40
+ agentType: string;
41
+ contextTier: string;
42
+ };
43
+ }
44
+ /**
45
+ * Session start result
46
+ */
47
+ export interface StartSessionResult {
48
+ /** Whether the operation succeeded */
49
+ success: boolean;
50
+ /** Created session node */
51
+ session: SessionNode;
52
+ }
53
+ /**
54
+ * Creates a new session node linked to a WU.
55
+ *
56
+ * Creates a session-type memory node with:
57
+ * - Unique ID (mem-XXXX format)
58
+ * - Link to WU via wu_id field
59
+ * - Agent type and context tier in metadata
60
+ * - UUID session_id for unique identification
61
+ *
62
+ * @param baseDir - Base directory containing .beacon/memory/
63
+ * @param options - Session options
64
+ * @returns Result with created session node
65
+ * @throws If wuId is missing or invalid
66
+ *
67
+ * @example
68
+ * const result = await startSession('/path/to/project', {
69
+ * wuId: 'WU-1234',
70
+ * agentType: 'general-purpose',
71
+ * contextTier: 'core',
72
+ * });
73
+ * console.log(result.session.id); // 'mem-a1b2'
74
+ */
75
+ export declare function startSession(baseDir: string, options: StartSessionOptions): Promise<StartSessionResult>;
76
+ export {};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mem-start-core.d.ts","sourceRoot":"","sources":["../src/mem-start-core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AA2EH;;;;;;;GAOG;AAEH;;;;;;GAMG;AAEH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,YAAY,CAAC,OAAO,KAAA,EAAE,OAAO,KAAA;;;;;;;;;;;;;;;GA+ClD"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Memory Start Core (WU-1466)
3
+ *
4
+ * Core logic for creating session nodes linked to WUs.
5
+ * Called by wu:claim enhancement for context restoration after /clear.
6
+ *
7
+ * Features:
8
+ * - Creates session nodes with WU reference
9
+ * - Stores agent type, start timestamp, context tier
10
+ * - Idempotent: multiple starts create separate sessions
11
+ * - Auto-initializes memory layer if not present
12
+ *
13
+ * @see {@link tools/mem-start.mjs} - CLI wrapper
14
+ * @see {@link tools/__tests__/mem-start.test.mjs} - Tests
15
+ * @see {@link tools/lib/memory-schema.mjs} - Schema definitions
16
+ */
17
+ import { randomUUID } from 'node:crypto';
18
+ import fs from 'node:fs/promises';
19
+ import path from 'node:path';
20
+ import { generateMemId } from './mem-id.js';
21
+ import { appendNode } from './memory-store.js';
22
+ import { MEMORY_PATTERNS } from './memory-schema.js';
23
+ /**
24
+ * Memory directory path relative to base directory
25
+ */
26
+ const MEMORY_DIR = '.beacon/memory';
27
+ /**
28
+ * Default values for session metadata
29
+ */
30
+ const DEFAULTS = {
31
+ AGENT_TYPE: 'unknown',
32
+ CONTEXT_TIER: 'full',
33
+ };
34
+ /**
35
+ * Error messages for validation
36
+ */
37
+ const ERROR_MESSAGES = {
38
+ WU_ID_REQUIRED: 'wuId is required',
39
+ WU_ID_INVALID: 'Invalid WU ID format. Expected pattern: WU-XXX (e.g., WU-123)',
40
+ };
41
+ /**
42
+ * Validates WU ID format
43
+ *
44
+ * @param wuId - WU ID to validate
45
+ * @returns True if valid
46
+ */
47
+ function isValidWuId(wuId) {
48
+ return MEMORY_PATTERNS.WU_ID.test(wuId);
49
+ }
50
+ /**
51
+ * Ensures the memory directory exists
52
+ *
53
+ * @param baseDir - Base directory
54
+ * @returns Memory directory path
55
+ */
56
+ async function ensureMemoryDir(baseDir) {
57
+ const memoryDir = path.join(baseDir, MEMORY_DIR);
58
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- Known directory path
59
+ await fs.mkdir(memoryDir, { recursive: true });
60
+ return memoryDir;
61
+ }
62
+ /**
63
+ * Generates content description for session node
64
+ *
65
+ * @param wuId - WU ID
66
+ * @param agentType - Agent type
67
+ * @param contextTier - Context tier
68
+ * @returns Content description
69
+ */
70
+ function generateSessionContent(wuId, agentType, contextTier) {
71
+ return `Session started for ${wuId}. Agent: ${agentType}, Context: ${contextTier}`;
72
+ }
73
+ /**
74
+ * Creates a new session node linked to a WU.
75
+ *
76
+ * Creates a session-type memory node with:
77
+ * - Unique ID (mem-XXXX format)
78
+ * - Link to WU via wu_id field
79
+ * - Agent type and context tier in metadata
80
+ * - UUID session_id for unique identification
81
+ *
82
+ * @param baseDir - Base directory containing .beacon/memory/
83
+ * @param options - Session options
84
+ * @returns Result with created session node
85
+ * @throws If wuId is missing or invalid
86
+ *
87
+ * @example
88
+ * const result = await startSession('/path/to/project', {
89
+ * wuId: 'WU-1234',
90
+ * agentType: 'general-purpose',
91
+ * contextTier: 'core',
92
+ * });
93
+ * console.log(result.session.id); // 'mem-a1b2'
94
+ */
95
+ export async function startSession(baseDir, options) {
96
+ const { wuId, agentType = DEFAULTS.AGENT_TYPE, contextTier = DEFAULTS.CONTEXT_TIER } = options;
97
+ // Validate required fields
98
+ if (!wuId) {
99
+ throw new Error(ERROR_MESSAGES.WU_ID_REQUIRED);
100
+ }
101
+ // Validate WU ID format
102
+ if (!isValidWuId(wuId)) {
103
+ throw new Error(ERROR_MESSAGES.WU_ID_INVALID);
104
+ }
105
+ // Ensure memory directory exists
106
+ const memoryDir = await ensureMemoryDir(baseDir);
107
+ // Generate session node
108
+ const timestamp = new Date().toISOString();
109
+ const sessionId = randomUUID();
110
+ const content = generateSessionContent(wuId, agentType, contextTier);
111
+ // Generate deterministic ID from content + timestamp for uniqueness
112
+ const idContent = `${content}-${timestamp}-${sessionId}`;
113
+ const id = generateMemId(idContent);
114
+ const sessionNode = {
115
+ id,
116
+ type: 'session',
117
+ lifecycle: 'wu',
118
+ content,
119
+ created_at: timestamp,
120
+ wu_id: wuId,
121
+ session_id: sessionId,
122
+ metadata: {
123
+ agentType,
124
+ contextTier,
125
+ },
126
+ };
127
+ // Persist to memory store
128
+ await appendNode(memoryDir, sessionNode);
129
+ return {
130
+ success: true,
131
+ session: sessionNode,
132
+ };
133
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mem-start-core.js","sourceRoot":"","sources":["../src/mem-start-core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Memory Summarize Core (WU-1471)
3
+ *
4
+ * Rollup older memory nodes into summary nodes for compaction.
5
+ * Implements forgetting as first-class feature.
6
+ *
7
+ * Features:
8
+ * - Aggregate checkpoint/note/discovery nodes into summaries
9
+ * - Mark originals for cleanup after summary creation
10
+ * - Respect lifecycle TTL (ephemeral, session, wu, project)
11
+ * - Support dry-run mode for preview
12
+ *
13
+ * @see {@link tools/mem-summarize.mjs} - CLI wrapper
14
+ * @see {@link tools/__tests__/mem-summarize.test.mjs} - Tests
15
+ */
16
+ import { type MemoryNode } from './memory-schema.js';
17
+ /**
18
+ * Summarize options
19
+ */
20
+ export interface SummarizeOptions {
21
+ /** WU ID to summarize (e.g., 'WU-1234') */
22
+ wuId: string;
23
+ /** If true, preview without modifications */
24
+ dryRun?: boolean;
25
+ }
26
+ /**
27
+ * Summary node structure
28
+ */
29
+ interface SummaryNode {
30
+ id: string;
31
+ type: 'summary';
32
+ lifecycle: 'project';
33
+ content: string;
34
+ created_at: string;
35
+ wu_id: string;
36
+ metadata: {
37
+ source_nodes: string[];
38
+ source_count: number;
39
+ summarized_at: string;
40
+ };
41
+ }
42
+ /**
43
+ * Summarize result
44
+ */
45
+ export interface SummarizeResult {
46
+ /** Whether summarization succeeded */
47
+ success: boolean;
48
+ /** Created summary node */
49
+ summary: SummaryNode;
50
+ /** Node IDs marked for cleanup */
51
+ markedForCleanup: string[];
52
+ /** True if in dry-run mode */
53
+ dryRun?: boolean;
54
+ /** Ratio of source nodes to summary */
55
+ compactionRatio: number;
56
+ }
57
+ /**
58
+ * Filter nodes that can be summarized for a given WU.
59
+ *
60
+ * Excludes:
61
+ * - Nodes from different WUs
62
+ * - Ephemeral lifecycle nodes (already transient)
63
+ * - Already-summarized nodes (have summarized_into metadata)
64
+ * - Summary nodes themselves
65
+ *
66
+ * @param nodes - All memory nodes
67
+ * @param wuId - WU ID to filter by
68
+ * @returns Summarizable nodes
69
+ */
70
+ export declare function filterSummarizableNodes(nodes: MemoryNode[], wuId: string): MemoryNode[];
71
+ /**
72
+ * Calculate compaction ratio (source nodes / summary nodes).
73
+ *
74
+ * @param sourceCount - Number of source nodes
75
+ * @param summaryCount - Number of summary nodes created
76
+ * @returns Compaction ratio (0 if invalid input)
77
+ */
78
+ export declare function getCompactionRatio(sourceCount: number, summaryCount: number): number;
79
+ /**
80
+ * Summarize memory nodes for a closed WU.
81
+ *
82
+ * Aggregates checkpoint, note, and discovery nodes into a single
83
+ * summary node. Original nodes are marked for cleanup (unless
84
+ * they have project lifecycle).
85
+ *
86
+ * @param baseDir - Base directory containing .beacon/memory/
87
+ * @param options - Summarization options
88
+ * @returns Result with summary and cleanup info
89
+ * @throws If no summarizable nodes found for WU
90
+ *
91
+ * @example
92
+ * // Summarize nodes for a completed WU
93
+ * const result = await summarizeWu(baseDir, { wuId: 'WU-1234' });
94
+ * console.log(`Created summary ${result.summary.id}`);
95
+ * console.log(`Compaction ratio: ${result.compactionRatio}:1`);
96
+ *
97
+ * @example
98
+ * // Preview without modifications
99
+ * const preview = await summarizeWu(baseDir, {
100
+ * wuId: 'WU-1234',
101
+ * dryRun: true,
102
+ * });
103
+ */
104
+ export declare function summarizeWu(baseDir: string, options: SummarizeOptions): Promise<SummarizeResult>;
105
+ export {};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mem-summarize-core.d.ts","sourceRoot":"","sources":["../src/mem-summarize-core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAuBH;;GAEG;AAEH;;;;GAIG;AAEH;;;;;;;GAOG;AAEH;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,KAAA,EAAE,IAAI,KAAA,OAyBlD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,KAAA,EAAE,YAAY,KAAA,UAK3D;AAqID;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,WAAW,CAAC,OAAO,KAAA,EAAE,OAAO,KAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDjD"}