@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,369 @@
|
|
|
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 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_NODE_TYPES, MEMORY_PATTERNS, validateMemoryNode, validateRelationship, } from './memory-schema.js';
|
|
23
|
+
/**
|
|
24
|
+
* Memory directory path relative to base directory
|
|
25
|
+
*/
|
|
26
|
+
const MEMORY_DIR = '.beacon/memory';
|
|
27
|
+
/**
|
|
28
|
+
* Relationships file name
|
|
29
|
+
*/
|
|
30
|
+
const RELATIONSHIPS_FILE_NAME = 'relationships.jsonl';
|
|
31
|
+
/**
|
|
32
|
+
* Default node type
|
|
33
|
+
*/
|
|
34
|
+
const DEFAULT_NODE_TYPE = 'discovery';
|
|
35
|
+
/**
|
|
36
|
+
* Session file path relative to main checkout
|
|
37
|
+
*/
|
|
38
|
+
const SESSION_FILE_PATH = '.beacon/sessions/current.json';
|
|
39
|
+
/**
|
|
40
|
+
* Type aliases for user-friendly CLI experience (WU-1762)
|
|
41
|
+
* Maps alias names to canonical types and additional tags
|
|
42
|
+
*/
|
|
43
|
+
const TYPE_ALIASES = {
|
|
44
|
+
bug: { type: 'discovery', tag: 'bug' },
|
|
45
|
+
idea: { type: 'discovery', tag: 'idea' },
|
|
46
|
+
question: { type: 'discovery', tag: 'question' },
|
|
47
|
+
dependency: { type: 'discovery', tag: 'dependency' },
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Lifecycle mapping by node type
|
|
51
|
+
* - session: Lives for WU duration (wu)
|
|
52
|
+
* - discovery: Lives for WU duration (wu)
|
|
53
|
+
* - checkpoint: Lives for session (session)
|
|
54
|
+
* - note: Lives for session (session)
|
|
55
|
+
* - summary: Persists across WUs (project)
|
|
56
|
+
*/
|
|
57
|
+
const LIFECYCLE_BY_TYPE = {
|
|
58
|
+
session: 'wu',
|
|
59
|
+
discovery: 'wu',
|
|
60
|
+
checkpoint: 'session',
|
|
61
|
+
note: 'session',
|
|
62
|
+
summary: 'project',
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Error messages for validation
|
|
66
|
+
*/
|
|
67
|
+
const ERROR_MESSAGES = {
|
|
68
|
+
TITLE_REQUIRED: 'title is required',
|
|
69
|
+
TITLE_EMPTY: 'title cannot be empty',
|
|
70
|
+
INVALID_TYPE: `Invalid node type. Must be one of: ${MEMORY_NODE_TYPES.join(', ')}`,
|
|
71
|
+
WU_ID_INVALID: 'Invalid WU ID format. Expected pattern: WU-XXX (e.g., WU-123)',
|
|
72
|
+
MEMORY_ID_INVALID: 'Invalid memory ID format. Expected pattern: mem-XXXX (e.g., mem-a1b2)',
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Normalizes type aliases to canonical types (WU-1762)
|
|
76
|
+
*
|
|
77
|
+
* Converts user-friendly type aliases (bug, idea) to canonical types (discovery)
|
|
78
|
+
* and returns the tag to be added.
|
|
79
|
+
*
|
|
80
|
+
* @param inputType - User-provided type (may be alias)
|
|
81
|
+
* @returns Normalized type and optional tag
|
|
82
|
+
*/
|
|
83
|
+
function normalizeType(inputType) {
|
|
84
|
+
const alias = TYPE_ALIASES[inputType];
|
|
85
|
+
if (alias) {
|
|
86
|
+
return { type: alias.type, aliasTag: alias.tag };
|
|
87
|
+
}
|
|
88
|
+
return { type: inputType, aliasTag: null };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Checks if a path is a git worktree (has .git file pointing to main)
|
|
92
|
+
*
|
|
93
|
+
* @param dir - Directory to check
|
|
94
|
+
* @returns Path to main checkout or null if not a worktree
|
|
95
|
+
*/
|
|
96
|
+
async function getMainCheckoutFromWorktree(dir) {
|
|
97
|
+
const gitPath = path.join(dir, '.git');
|
|
98
|
+
try {
|
|
99
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- Known path
|
|
100
|
+
const stat = await fs.stat(gitPath);
|
|
101
|
+
if (stat.isFile()) {
|
|
102
|
+
// .git is a file = we're in a worktree
|
|
103
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- Known path
|
|
104
|
+
const gitContent = await fs.readFile(gitPath, { encoding: 'utf-8' });
|
|
105
|
+
// Format: "gitdir: /path/to/main/.git/worktrees/name"
|
|
106
|
+
const match = gitContent.match(/^gitdir:\s*(.+)/);
|
|
107
|
+
if (match && match[1]) {
|
|
108
|
+
const gitDir = match[1].trim();
|
|
109
|
+
// Extract main checkout: /path/to/main/.git/worktrees/name → /path/to/main
|
|
110
|
+
const worktreesIndex = gitDir.indexOf('/.git/worktrees/');
|
|
111
|
+
if (worktreesIndex !== -1) {
|
|
112
|
+
return gitDir.substring(0, worktreesIndex);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Not a worktree or .git doesn't exist
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Reads the current session from session file
|
|
124
|
+
*
|
|
125
|
+
* @param baseDir - Base directory (main checkout)
|
|
126
|
+
* @returns Session data or null
|
|
127
|
+
*/
|
|
128
|
+
async function readCurrentSession(baseDir) {
|
|
129
|
+
const sessionPath = path.join(baseDir, SESSION_FILE_PATH);
|
|
130
|
+
try {
|
|
131
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- Known session path
|
|
132
|
+
const content = await fs.readFile(sessionPath, { encoding: 'utf-8' });
|
|
133
|
+
return JSON.parse(content);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Auto-infers WU ID from current session (WU-1762)
|
|
141
|
+
*
|
|
142
|
+
* Checks for session file in:
|
|
143
|
+
* 1. Current baseDir (if running in main checkout)
|
|
144
|
+
* 2. Main checkout (if running in worktree)
|
|
145
|
+
*
|
|
146
|
+
* @param baseDir - Current working directory
|
|
147
|
+
* @returns Session data from session file or null
|
|
148
|
+
*/
|
|
149
|
+
async function inferSessionFromSessionFile(baseDir) {
|
|
150
|
+
// First, try to read session from current directory
|
|
151
|
+
let session = await readCurrentSession(baseDir);
|
|
152
|
+
if (session) {
|
|
153
|
+
return session;
|
|
154
|
+
}
|
|
155
|
+
// If not found, check if we're in a worktree and look in main checkout
|
|
156
|
+
const mainCheckout = await getMainCheckoutFromWorktree(baseDir);
|
|
157
|
+
if (mainCheckout) {
|
|
158
|
+
session = await readCurrentSession(mainCheckout);
|
|
159
|
+
if (session) {
|
|
160
|
+
return session;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
function isValidSessionId(value) {
|
|
166
|
+
if (!value)
|
|
167
|
+
return false;
|
|
168
|
+
if (typeof value !== 'string')
|
|
169
|
+
return false;
|
|
170
|
+
// UUID v4 format (sufficient for validation here; actual schema validates uuid too)
|
|
171
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Validates WU ID format if provided
|
|
175
|
+
*
|
|
176
|
+
* @param wuId - WU ID to validate
|
|
177
|
+
* @returns True if valid or not provided
|
|
178
|
+
*/
|
|
179
|
+
function isValidWuId(wuId) {
|
|
180
|
+
if (!wuId)
|
|
181
|
+
return true;
|
|
182
|
+
return MEMORY_PATTERNS.WU_ID.test(wuId);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Validates memory ID format
|
|
186
|
+
*
|
|
187
|
+
* @param memId - Memory ID to validate
|
|
188
|
+
* @returns True if valid
|
|
189
|
+
*/
|
|
190
|
+
function isValidMemoryId(memId) {
|
|
191
|
+
return MEMORY_PATTERNS.MEMORY_ID.test(memId);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Ensures the memory directory exists
|
|
195
|
+
*
|
|
196
|
+
* @param baseDir - Base directory
|
|
197
|
+
* @returns Memory directory path
|
|
198
|
+
*/
|
|
199
|
+
async function ensureMemoryDir(baseDir) {
|
|
200
|
+
const memoryDir = path.join(baseDir, MEMORY_DIR);
|
|
201
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- Known directory path
|
|
202
|
+
await fs.mkdir(memoryDir, { recursive: true });
|
|
203
|
+
return memoryDir;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Appends a relationship to the relationships.jsonl file
|
|
207
|
+
*
|
|
208
|
+
* @param memoryDir - Memory directory path
|
|
209
|
+
* @param relationship - Relationship object
|
|
210
|
+
* @returns The appended relationship
|
|
211
|
+
*/
|
|
212
|
+
async function appendRelationship(memoryDir, relationship) {
|
|
213
|
+
// Validate relationship before appending
|
|
214
|
+
const validation = validateRelationship(relationship);
|
|
215
|
+
if (!validation.success) {
|
|
216
|
+
const issues = validation.error.issues
|
|
217
|
+
.map((issue) => `${issue.path.join('.')}: ${issue.message}`)
|
|
218
|
+
.join(', ');
|
|
219
|
+
throw new Error(`Relationship validation error: ${issues}`);
|
|
220
|
+
}
|
|
221
|
+
const filePath = path.join(memoryDir, RELATIONSHIPS_FILE_NAME);
|
|
222
|
+
const line = `${JSON.stringify(relationship)}\n`;
|
|
223
|
+
// Use append flag to avoid rewriting the file
|
|
224
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool writes known file
|
|
225
|
+
await fs.appendFile(filePath, line, { encoding: 'utf-8' });
|
|
226
|
+
return relationship;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Gets the lifecycle for a node type
|
|
230
|
+
*
|
|
231
|
+
* @param type - Node type
|
|
232
|
+
* @returns Lifecycle value
|
|
233
|
+
*/
|
|
234
|
+
function getLifecycleForType(type) {
|
|
235
|
+
return LIFECYCLE_BY_TYPE[type];
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Creates a new memory node with optional discovered-from provenance.
|
|
239
|
+
*
|
|
240
|
+
* Creates a memory node with:
|
|
241
|
+
* - Unique ID (mem-XXXX format) generated from content hash
|
|
242
|
+
* - User-provided title as content
|
|
243
|
+
* - Type-appropriate lifecycle
|
|
244
|
+
* - Optional discovered-from relationship for provenance tracking
|
|
245
|
+
*
|
|
246
|
+
* @param {string} baseDir - Base directory containing .beacon/memory/
|
|
247
|
+
* @param {CreateMemoryNodeOptions} options - Node creation options
|
|
248
|
+
* @returns {Promise<CreateMemoryNodeResult>} Result with created node and optional relationship
|
|
249
|
+
* @throws {Error} If title is missing, type is invalid, or IDs are malformed
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* // Create a simple discovery node
|
|
253
|
+
* const result = await createMemoryNode(baseDir, {
|
|
254
|
+
* title: 'Found relevant file at src/utils.mjs',
|
|
255
|
+
* type: 'discovery',
|
|
256
|
+
* wuId: 'WU-1469',
|
|
257
|
+
* });
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* // Create a node with provenance (scope-creep tracking)
|
|
261
|
+
* const parent = await createMemoryNode(baseDir, {
|
|
262
|
+
* title: 'Found src/components/',
|
|
263
|
+
* type: 'discovery',
|
|
264
|
+
* });
|
|
265
|
+
* const child = await createMemoryNode(baseDir, {
|
|
266
|
+
* title: 'Found src/components/Button.tsx',
|
|
267
|
+
* type: 'discovery',
|
|
268
|
+
* discoveredFrom: parent.node.id, // Track where this came from
|
|
269
|
+
* });
|
|
270
|
+
*/
|
|
271
|
+
export async function createMemoryNode(baseDir, options) {
|
|
272
|
+
const { title, type: inputType = DEFAULT_NODE_TYPE, wuId: explicitWuId, sessionId: explicitSessionId, discoveredFrom, tags: inputTags, priority, } = options;
|
|
273
|
+
// Validate required fields
|
|
274
|
+
if (title === undefined || title === null) {
|
|
275
|
+
throw new Error(ERROR_MESSAGES.TITLE_REQUIRED);
|
|
276
|
+
}
|
|
277
|
+
if (title === '') {
|
|
278
|
+
throw new Error(ERROR_MESSAGES.TITLE_EMPTY);
|
|
279
|
+
}
|
|
280
|
+
// Normalize type aliases (WU-1762): bug → discovery + tag, idea → discovery + tag
|
|
281
|
+
const { type: normalizedType, aliasTag } = normalizeType(inputType);
|
|
282
|
+
// Validate node type (after alias normalization) and narrow type
|
|
283
|
+
if (!MEMORY_NODE_TYPES.includes(normalizedType)) {
|
|
284
|
+
throw new Error(ERROR_MESSAGES.INVALID_TYPE);
|
|
285
|
+
}
|
|
286
|
+
// Type is now validated - safe to cast
|
|
287
|
+
const type = normalizedType;
|
|
288
|
+
// Merge tags: alias tag + user-provided tags, deduplicated (WU-1762)
|
|
289
|
+
let tags = inputTags ? [...inputTags] : [];
|
|
290
|
+
if (aliasTag && !tags.includes(aliasTag)) {
|
|
291
|
+
tags = [aliasTag, ...tags];
|
|
292
|
+
}
|
|
293
|
+
// Remove duplicates while preserving order
|
|
294
|
+
tags = [...new Set(tags)];
|
|
295
|
+
// Auto-infer WU ID and session ID from current session if not provided (WU-1762)
|
|
296
|
+
const inferredSession = explicitWuId && explicitSessionId ? null : await inferSessionFromSessionFile(baseDir);
|
|
297
|
+
const wuId = explicitWuId || inferredSession?.wu_id;
|
|
298
|
+
const sessionId = explicitSessionId ||
|
|
299
|
+
(isValidSessionId(inferredSession?.session_id) ? inferredSession.session_id : undefined);
|
|
300
|
+
// Validate WU ID format if provided
|
|
301
|
+
if (wuId && !isValidWuId(wuId)) {
|
|
302
|
+
throw new Error(ERROR_MESSAGES.WU_ID_INVALID);
|
|
303
|
+
}
|
|
304
|
+
// Validate discovered-from ID format if provided
|
|
305
|
+
if (discoveredFrom && !isValidMemoryId(discoveredFrom)) {
|
|
306
|
+
throw new Error(ERROR_MESSAGES.MEMORY_ID_INVALID);
|
|
307
|
+
}
|
|
308
|
+
// Ensure memory directory exists
|
|
309
|
+
const memoryDir = await ensureMemoryDir(baseDir);
|
|
310
|
+
// Generate node
|
|
311
|
+
const timestamp = new Date().toISOString();
|
|
312
|
+
// Generate deterministic ID from content + timestamp for uniqueness
|
|
313
|
+
const idContent = `${title}-${timestamp}`;
|
|
314
|
+
const id = generateMemId(idContent);
|
|
315
|
+
// Get lifecycle for this type
|
|
316
|
+
const lifecycle = getLifecycleForType(type);
|
|
317
|
+
// Build metadata object
|
|
318
|
+
const metadata = {};
|
|
319
|
+
if (priority) {
|
|
320
|
+
metadata.priority = priority;
|
|
321
|
+
}
|
|
322
|
+
const node = {
|
|
323
|
+
id,
|
|
324
|
+
type,
|
|
325
|
+
lifecycle,
|
|
326
|
+
content: title,
|
|
327
|
+
created_at: timestamp,
|
|
328
|
+
};
|
|
329
|
+
// Add optional fields
|
|
330
|
+
if (wuId) {
|
|
331
|
+
node.wu_id = wuId;
|
|
332
|
+
}
|
|
333
|
+
if (sessionId) {
|
|
334
|
+
node.session_id = sessionId;
|
|
335
|
+
}
|
|
336
|
+
if (Object.keys(metadata).length > 0) {
|
|
337
|
+
node.metadata = metadata;
|
|
338
|
+
}
|
|
339
|
+
if (tags && tags.length > 0) {
|
|
340
|
+
node.tags = tags;
|
|
341
|
+
}
|
|
342
|
+
// Validate node against schema
|
|
343
|
+
const nodeValidation = validateMemoryNode(node);
|
|
344
|
+
if (!nodeValidation.success) {
|
|
345
|
+
const issues = nodeValidation.error.issues
|
|
346
|
+
.map((issue) => `${issue.path.join('.')}: ${issue.message}`)
|
|
347
|
+
.join(', ');
|
|
348
|
+
throw new Error(`Node validation error: ${issues}`);
|
|
349
|
+
}
|
|
350
|
+
// Persist node to memory store
|
|
351
|
+
await appendNode(memoryDir, node);
|
|
352
|
+
// Build result
|
|
353
|
+
const result = {
|
|
354
|
+
success: true,
|
|
355
|
+
node,
|
|
356
|
+
};
|
|
357
|
+
// Create discovered-from relationship if parent specified
|
|
358
|
+
if (discoveredFrom) {
|
|
359
|
+
const relationship = {
|
|
360
|
+
from_id: node.id,
|
|
361
|
+
to_id: discoveredFrom,
|
|
362
|
+
type: 'discovered_from',
|
|
363
|
+
created_at: timestamp,
|
|
364
|
+
};
|
|
365
|
+
await appendRelationship(memoryDir, relationship);
|
|
366
|
+
result.relationship = relationship;
|
|
367
|
+
}
|
|
368
|
+
return result;
|
|
369
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mem-create-core.js","sourceRoot":"","sources":["../src/mem-create-core.ts"],"names":[],"mappings":"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"}
|
package/dist/mem-id.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory ID Generator (WU-1465)
|
|
3
|
+
*
|
|
4
|
+
* Hash-based collision-free ID generation for memory nodes.
|
|
5
|
+
* Format: mem-[4 hex chars] derived from content hash.
|
|
6
|
+
* Supports hierarchical IDs (mem-a1b2.1.2) for sub-task decomposition.
|
|
7
|
+
*
|
|
8
|
+
* @see {@link tools/lib/__tests__/mem-id.test.mjs} - Tests
|
|
9
|
+
* @see {@link tools/lib/memory-schema.mjs} - Schema definitions
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Regex patterns for memory ID validation
|
|
13
|
+
*/
|
|
14
|
+
export declare const MEM_ID_PATTERNS: {
|
|
15
|
+
/**
|
|
16
|
+
* Base memory ID format: mem-[a-f0-9]{4}
|
|
17
|
+
* Only lowercase hex characters (0-9, a-f)
|
|
18
|
+
*/
|
|
19
|
+
BASE_ID: RegExp;
|
|
20
|
+
/**
|
|
21
|
+
* Hierarchical memory ID format: mem-[a-f0-9]{4}(.[1-9][0-9]*)*
|
|
22
|
+
* Base ID followed by optional dot-separated positive integers
|
|
23
|
+
* Examples: mem-a1b2, mem-a1b2.1, mem-a1b2.1.2
|
|
24
|
+
*/
|
|
25
|
+
HIERARCHICAL_ID: RegExp;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Generates a deterministic memory ID from content.
|
|
29
|
+
*
|
|
30
|
+
* Uses SHA-256 hash of the content, taking the first 4 hex characters.
|
|
31
|
+
* Same content always produces the same ID (deterministic).
|
|
32
|
+
*
|
|
33
|
+
* @param content - Content to hash for ID generation
|
|
34
|
+
* @returns Memory ID in format mem-[a-f0-9]{4}
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* const id = generateMemId('discovered file at src/utils.mjs');
|
|
38
|
+
* // Returns something like 'mem-a3f2'
|
|
39
|
+
*/
|
|
40
|
+
export declare function generateMemId(content: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Generates a hierarchical memory ID by appending an index to a parent ID.
|
|
43
|
+
*
|
|
44
|
+
* Used for sub-task decomposition where a parent task (mem-a1b2) has
|
|
45
|
+
* child tasks (mem-a1b2.1, mem-a1b2.2) and grandchildren (mem-a1b2.1.1).
|
|
46
|
+
*
|
|
47
|
+
* @param parentId - Parent memory ID (base or hierarchical)
|
|
48
|
+
* @param index - Positive integer index (1-based)
|
|
49
|
+
* @returns Hierarchical memory ID
|
|
50
|
+
* @throws If parentId is invalid or index is not positive
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* generateHierarchicalId('mem-a1b2', 1); // 'mem-a1b2.1'
|
|
54
|
+
* generateHierarchicalId('mem-a1b2.1', 2); // 'mem-a1b2.1.2'
|
|
55
|
+
*/
|
|
56
|
+
export declare function generateHierarchicalId(parentId: string, index: number): string;
|
|
57
|
+
/**
|
|
58
|
+
* Validation result from validateMemId
|
|
59
|
+
*/
|
|
60
|
+
export interface MemIdValidationResult {
|
|
61
|
+
/** Whether the ID is valid */
|
|
62
|
+
valid: boolean;
|
|
63
|
+
/** Type of ID if valid */
|
|
64
|
+
type?: 'base' | 'hierarchical';
|
|
65
|
+
/** Base ID portion if hierarchical */
|
|
66
|
+
baseId?: string;
|
|
67
|
+
/** Hierarchical indices (empty for base IDs) */
|
|
68
|
+
indices: number[];
|
|
69
|
+
/** Error message if invalid */
|
|
70
|
+
error?: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Validates a memory ID and extracts its components.
|
|
74
|
+
*
|
|
75
|
+
* Returns validation result with type classification and parsed components.
|
|
76
|
+
* Compatible with MEMORY_PATTERNS.MEMORY_ID from memory-schema.mjs.
|
|
77
|
+
*
|
|
78
|
+
* @param id - Memory ID to validate
|
|
79
|
+
* @returns Validation result with parsed components
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* validateMemId('mem-a1b2');
|
|
83
|
+
* // { valid: true, type: 'base', baseId: 'mem-a1b2', indices: [] }
|
|
84
|
+
*
|
|
85
|
+
* validateMemId('mem-a1b2.1.2');
|
|
86
|
+
* // { valid: true, type: 'hierarchical', baseId: 'mem-a1b2', indices: [1, 2] }
|
|
87
|
+
*
|
|
88
|
+
* validateMemId('invalid');
|
|
89
|
+
* // { valid: false, error: 'Invalid memory ID format', indices: [] }
|
|
90
|
+
*/
|
|
91
|
+
export declare function validateMemId(id: string): MemIdValidationResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mem-id.d.ts","sourceRoot":"","sources":["../src/mem-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAcH;;GAEG;AACH,eAAO,MAAM,eAAe;IAC1B;;;OAGG;;IAGH;;;;OAIG;;CAEJ,CAAC;AAWF;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,OAAO,KAAA,UAOpC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,KAAA,EAAE,KAAK,KAAA,UAYrD;AAED;;;;;;;;;GASG;AAEH;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,EAAE,KAAA;;;;;;;;;;;;EA+B/B"}
|
package/dist/mem-id.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory ID Generator (WU-1465)
|
|
3
|
+
*
|
|
4
|
+
* Hash-based collision-free ID generation for memory nodes.
|
|
5
|
+
* Format: mem-[4 hex chars] derived from content hash.
|
|
6
|
+
* Supports hierarchical IDs (mem-a1b2.1.2) for sub-task decomposition.
|
|
7
|
+
*
|
|
8
|
+
* @see {@link tools/lib/__tests__/mem-id.test.mjs} - Tests
|
|
9
|
+
* @see {@link tools/lib/memory-schema.mjs} - Schema definitions
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
12
|
+
/**
|
|
13
|
+
* Memory ID prefix constant
|
|
14
|
+
*/
|
|
15
|
+
const MEM_ID_PREFIX = 'mem-';
|
|
16
|
+
/**
|
|
17
|
+
* Number of hex characters in base ID suffix
|
|
18
|
+
*/
|
|
19
|
+
const HEX_SUFFIX_LENGTH = 4;
|
|
20
|
+
/**
|
|
21
|
+
* Regex patterns for memory ID validation
|
|
22
|
+
*/
|
|
23
|
+
export const MEM_ID_PATTERNS = {
|
|
24
|
+
/**
|
|
25
|
+
* Base memory ID format: mem-[a-f0-9]{4}
|
|
26
|
+
* Only lowercase hex characters (0-9, a-f)
|
|
27
|
+
*/
|
|
28
|
+
BASE_ID: /^mem-[a-f0-9]{4}$/,
|
|
29
|
+
/**
|
|
30
|
+
* Hierarchical memory ID format: mem-[a-f0-9]{4}(.[1-9][0-9]*)*
|
|
31
|
+
* Base ID followed by optional dot-separated positive integers
|
|
32
|
+
* Examples: mem-a1b2, mem-a1b2.1, mem-a1b2.1.2
|
|
33
|
+
*/
|
|
34
|
+
HIERARCHICAL_ID: /^mem-[a-f0-9]{4}(\.[1-9][0-9]*)*$/,
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Error messages for validation and exceptions
|
|
38
|
+
*/
|
|
39
|
+
const ERROR_MESSAGES = {
|
|
40
|
+
INVALID_ID: 'Invalid memory ID format',
|
|
41
|
+
INVALID_BASE_ID: 'Invalid base memory ID format',
|
|
42
|
+
INVALID_INDEX: 'Index must be a positive integer',
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Generates a deterministic memory ID from content.
|
|
46
|
+
*
|
|
47
|
+
* Uses SHA-256 hash of the content, taking the first 4 hex characters.
|
|
48
|
+
* Same content always produces the same ID (deterministic).
|
|
49
|
+
*
|
|
50
|
+
* @param content - Content to hash for ID generation
|
|
51
|
+
* @returns Memory ID in format mem-[a-f0-9]{4}
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* const id = generateMemId('discovered file at src/utils.mjs');
|
|
55
|
+
* // Returns something like 'mem-a3f2'
|
|
56
|
+
*/
|
|
57
|
+
export function generateMemId(content) {
|
|
58
|
+
const hash = createHash('sha256').update(content, 'utf-8').digest('hex');
|
|
59
|
+
// Take first 4 hex characters from hash
|
|
60
|
+
const suffix = hash.slice(0, HEX_SUFFIX_LENGTH);
|
|
61
|
+
return `${MEM_ID_PREFIX}${suffix}`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Generates a hierarchical memory ID by appending an index to a parent ID.
|
|
65
|
+
*
|
|
66
|
+
* Used for sub-task decomposition where a parent task (mem-a1b2) has
|
|
67
|
+
* child tasks (mem-a1b2.1, mem-a1b2.2) and grandchildren (mem-a1b2.1.1).
|
|
68
|
+
*
|
|
69
|
+
* @param parentId - Parent memory ID (base or hierarchical)
|
|
70
|
+
* @param index - Positive integer index (1-based)
|
|
71
|
+
* @returns Hierarchical memory ID
|
|
72
|
+
* @throws If parentId is invalid or index is not positive
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* generateHierarchicalId('mem-a1b2', 1); // 'mem-a1b2.1'
|
|
76
|
+
* generateHierarchicalId('mem-a1b2.1', 2); // 'mem-a1b2.1.2'
|
|
77
|
+
*/
|
|
78
|
+
export function generateHierarchicalId(parentId, index) {
|
|
79
|
+
// Validate parent ID
|
|
80
|
+
if (!MEM_ID_PATTERNS.HIERARCHICAL_ID.test(parentId)) {
|
|
81
|
+
throw new Error(`${ERROR_MESSAGES.INVALID_BASE_ID}: ${parentId}`);
|
|
82
|
+
}
|
|
83
|
+
// Validate index is positive integer
|
|
84
|
+
if (!Number.isInteger(index) || index < 1) {
|
|
85
|
+
throw new Error(`${ERROR_MESSAGES.INVALID_INDEX}: ${index}`);
|
|
86
|
+
}
|
|
87
|
+
return `${parentId}.${index}`;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Validates a memory ID and extracts its components.
|
|
91
|
+
*
|
|
92
|
+
* Returns validation result with type classification and parsed components.
|
|
93
|
+
* Compatible with MEMORY_PATTERNS.MEMORY_ID from memory-schema.mjs.
|
|
94
|
+
*
|
|
95
|
+
* @param id - Memory ID to validate
|
|
96
|
+
* @returns Validation result with parsed components
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* validateMemId('mem-a1b2');
|
|
100
|
+
* // { valid: true, type: 'base', baseId: 'mem-a1b2', indices: [] }
|
|
101
|
+
*
|
|
102
|
+
* validateMemId('mem-a1b2.1.2');
|
|
103
|
+
* // { valid: true, type: 'hierarchical', baseId: 'mem-a1b2', indices: [1, 2] }
|
|
104
|
+
*
|
|
105
|
+
* validateMemId('invalid');
|
|
106
|
+
* // { valid: false, error: 'Invalid memory ID format', indices: [] }
|
|
107
|
+
*/
|
|
108
|
+
export function validateMemId(id) {
|
|
109
|
+
// Quick validation using regex
|
|
110
|
+
if (!MEM_ID_PATTERNS.HIERARCHICAL_ID.test(id)) {
|
|
111
|
+
return {
|
|
112
|
+
valid: false,
|
|
113
|
+
error: ERROR_MESSAGES.INVALID_ID,
|
|
114
|
+
indices: [],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// Check if it's a base ID (no dots after the prefix)
|
|
118
|
+
if (MEM_ID_PATTERNS.BASE_ID.test(id)) {
|
|
119
|
+
return {
|
|
120
|
+
valid: true,
|
|
121
|
+
type: 'base',
|
|
122
|
+
baseId: id,
|
|
123
|
+
indices: [],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// It's a hierarchical ID - parse components
|
|
127
|
+
const parts = id.split('.');
|
|
128
|
+
const baseId = parts[0];
|
|
129
|
+
const indices = parts.slice(1).map((part) => parseInt(part, 10));
|
|
130
|
+
return {
|
|
131
|
+
valid: true,
|
|
132
|
+
type: 'hierarchical',
|
|
133
|
+
baseId,
|
|
134
|
+
indices,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mem-id.js","sourceRoot":"","sources":["../src/mem-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;GAEG;AACH,MAAM,aAAa,GAAG,MAAM,CAAC;AAE7B;;GAEG;AACH,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAE5B;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B;;;OAGG;IACH,OAAO,EAAE,mBAAmB;IAE5B;;;;OAIG;IACH,eAAe,EAAE,mCAAmC;CACrD,CAAC;AAEF;;GAEG;AACH,MAAM,cAAc,GAAG;IACrB,UAAU,EAAE,0BAA0B;IACtC,eAAe,EAAE,+BAA+B;IAChD,aAAa,EAAE,kCAAkC;CAClD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,aAAa,CAAC,OAAO;IACnC,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAEzE,wCAAwC;IACxC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAC;IAEhD,OAAO,GAAG,aAAa,GAAG,MAAM,EAAE,CAAC;AACrC,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAQ,EAAE,KAAK;IACpD,qBAAqB;IACrB,IAAI,CAAC,eAAe,CAAC,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,GAAG,cAAc,CAAC,eAAe,KAAK,QAAQ,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,qCAAqC;IACrC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,GAAG,cAAc,CAAC,aAAa,KAAK,KAAK,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,OAAO,GAAG,QAAQ,IAAI,KAAK,EAAE,CAAC;AAChC,CAAC;AAED;;;;;;;;;GASG;AAEH;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,aAAa,CAAC,EAAE;IAC9B,+BAA+B;IAC/B,IAAI,CAAC,eAAe,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QAC9C,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,cAAc,CAAC,UAAU;YAChC,OAAO,EAAE,EAAE;SACZ,CAAC;IACJ,CAAC;IAED,qDAAqD;IACrD,IAAI,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QACrC,OAAO;YACL,KAAK,EAAE,IAAI;YACX,IAAI,EAAE,MAAM;YACZ,MAAM,EAAE,EAAE;YACV,OAAO,EAAE,EAAE;SACZ,CAAC;IACJ,CAAC;IAED,4CAA4C;IAC5C,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC5B,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;IAEjE,OAAO;QACL,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,cAAc;QACpB,MAAM;QACN,OAAO;KACR,CAAC;AACJ,CAAC"}
|