@dreb/coding-agent 2.5.2 → 2.6.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.
@@ -0,0 +1,587 @@
1
+ import { execFile } from "node:child_process";
2
+ import { randomUUID } from "node:crypto";
3
+ import { closeSync, existsSync, mkdirSync, openSync, readdirSync, readFileSync, readSync, rmSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
4
+ import { cp, readdir, stat } from "node:fs/promises";
5
+ import { homedir } from "node:os";
6
+ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
7
+ import { promisify } from "node:util";
8
+ import lockfile from "proper-lockfile";
9
+ import { getSessionsDir } from "../config.js";
10
+ // =============================================================================
11
+ // Constants
12
+ // =============================================================================
13
+ const DREAM_LAST_RUN_FILE = ".dream-last-run";
14
+ const DREAM_LOCK_FILE = ".dream.lock";
15
+ const DREAM_TMP_DIR = ".dream-tmp";
16
+ const BACKUP_PREFIX = "dream-backup-";
17
+ const DEFAULT_KEEP_COUNT = 10;
18
+ const LARGE_LISTING_THRESHOLD = 10_000;
19
+ // =============================================================================
20
+ // Command Parsing
21
+ // =============================================================================
22
+ export function parseDreamCommand(text) {
23
+ const stripped = text.replace(/^\/dream\s*/, "").trim();
24
+ if (!stripped) {
25
+ return { type: "run" };
26
+ }
27
+ if (stripped === "backup") {
28
+ return { type: "showBackup" };
29
+ }
30
+ const backupMatch = stripped.match(/^backup\s+(.+)$/);
31
+ if (backupMatch) {
32
+ return { type: "setBackup", path: backupMatch[1].trim() };
33
+ }
34
+ // Unknown subcommand — treat as a plain run
35
+ return { type: "run" };
36
+ }
37
+ // =============================================================================
38
+ // Discovery
39
+ // =============================================================================
40
+ /**
41
+ * Read the `cwd` field from the JSONL session header (first line) of a file.
42
+ * Returns `undefined` if the file cannot be read or the header is invalid.
43
+ * Uses synchronous low-level reads to avoid loading entire files.
44
+ */
45
+ function readSessionCwd(filePath) {
46
+ let fd;
47
+ try {
48
+ fd = openSync(filePath, "r");
49
+ const buffer = Buffer.alloc(4096);
50
+ const bytesRead = readSync(fd, buffer, 0, 4096, 0);
51
+ const firstLine = buffer.toString("utf8", 0, bytesRead).split("\n")[0];
52
+ if (!firstLine)
53
+ return undefined;
54
+ const header = JSON.parse(firstLine);
55
+ if (header.type === "session" && typeof header.cwd === "string") {
56
+ return header.cwd;
57
+ }
58
+ }
59
+ catch {
60
+ // Corrupt or unreadable — skip
61
+ }
62
+ finally {
63
+ if (fd !== undefined) {
64
+ try {
65
+ closeSync(fd);
66
+ }
67
+ catch {
68
+ // Best-effort close
69
+ }
70
+ }
71
+ }
72
+ return undefined;
73
+ }
74
+ export function discoverAllProjectMemoryDirs(overrideSessionsDir) {
75
+ const sessionsDir = overrideSessionsDir ?? getSessionsDir();
76
+ if (!existsSync(sessionsDir))
77
+ return [];
78
+ let dirEntries;
79
+ try {
80
+ dirEntries = readdirSync(sessionsDir);
81
+ }
82
+ catch {
83
+ return [];
84
+ }
85
+ const memoryDirs = [];
86
+ const seen = new Set();
87
+ for (const name of dirEntries) {
88
+ if (!name.startsWith("--") || !name.endsWith("--"))
89
+ continue;
90
+ const sessionSubDir = join(sessionsDir, name);
91
+ // Find any .jsonl file in this session directory
92
+ let jsonlFiles;
93
+ try {
94
+ jsonlFiles = readdirSync(sessionSubDir).filter((f) => f.endsWith(".jsonl"));
95
+ }
96
+ catch {
97
+ continue;
98
+ }
99
+ if (jsonlFiles.length === 0)
100
+ continue;
101
+ let cwd;
102
+ for (const f of jsonlFiles) {
103
+ cwd = readSessionCwd(join(sessionSubDir, f));
104
+ if (cwd)
105
+ break;
106
+ }
107
+ if (!cwd)
108
+ continue;
109
+ const memDir = join(cwd, ".dreb", "memory");
110
+ if (!seen.has(memDir) && existsSync(memDir)) {
111
+ seen.add(memDir);
112
+ memoryDirs.push(memDir);
113
+ }
114
+ }
115
+ return memoryDirs;
116
+ }
117
+ /**
118
+ * Discover claude compatibility memory directories (read-only imports).
119
+ * Scans ~/.claude/projects/ for subdirectories containing a memory/ folder.
120
+ */
121
+ function discoverClaudeMemoryDirs() {
122
+ const claudeProjectsDir = join(homedir(), ".claude", "projects");
123
+ if (!existsSync(claudeProjectsDir))
124
+ return [];
125
+ let dirEntries;
126
+ try {
127
+ dirEntries = readdirSync(claudeProjectsDir);
128
+ }
129
+ catch {
130
+ return [];
131
+ }
132
+ const memoryDirs = [];
133
+ for (const name of dirEntries) {
134
+ const memDir = join(claudeProjectsDir, name, "memory");
135
+ if (existsSync(memDir)) {
136
+ memoryDirs.push(memDir);
137
+ }
138
+ }
139
+ return memoryDirs;
140
+ }
141
+ // =============================================================================
142
+ // Context Resolution
143
+ // =============================================================================
144
+ export async function resolveDreamContext(settingsManager, overrideGlobalMemDir) {
145
+ // Archive path from settings, or default
146
+ const archivePath = settingsManager.getDreamArchivePath();
147
+ // Global memory dir
148
+ const globalMemoryDir = overrideGlobalMemDir ?? join(homedir(), ".dreb", "memory");
149
+ // Last run timestamp
150
+ let lastRunTimestamp = null;
151
+ const markerPath = join(globalMemoryDir, DREAM_LAST_RUN_FILE);
152
+ try {
153
+ if (existsSync(markerPath)) {
154
+ const content = readFileSync(markerPath, "utf-8").trim();
155
+ if (content) {
156
+ lastRunTimestamp = content;
157
+ }
158
+ }
159
+ }
160
+ catch {
161
+ // Marker unreadable — treat as first run
162
+ }
163
+ // Discover memory directories
164
+ const projectMemoryDirs = discoverAllProjectMemoryDirs();
165
+ const claudeMemoryDirs = discoverClaudeMemoryDirs();
166
+ const sessionsDir = getSessionsDir();
167
+ return {
168
+ archivePath,
169
+ lastRunTimestamp,
170
+ globalMemoryDir,
171
+ projectMemoryDirs,
172
+ claudeMemoryDirs,
173
+ sessionsDir,
174
+ };
175
+ }
176
+ // =============================================================================
177
+ // Validation
178
+ // =============================================================================
179
+ export function validateArchivePath(archivePath, memoryDirs) {
180
+ if (!archivePath || !archivePath.trim()) {
181
+ throw new Error("Dream archive path cannot be empty");
182
+ }
183
+ if (!isAbsolute(archivePath)) {
184
+ throw new Error(`Dream archive path must be absolute, got: ${archivePath}`);
185
+ }
186
+ const normalized = resolve(archivePath);
187
+ for (const memDir of memoryDirs) {
188
+ const normalizedMem = resolve(memDir);
189
+ // Archive is inside a memory dir
190
+ if (normalized.startsWith(`${normalizedMem}/`) || normalized === normalizedMem) {
191
+ throw new Error(`Dream archive path "${archivePath}" overlaps with memory directory "${memDir}". ` +
192
+ "The archive must be outside all memory directories.");
193
+ }
194
+ // Memory dir is inside the archive
195
+ if (normalizedMem.startsWith(`${normalized}/`) || normalizedMem === normalized) {
196
+ throw new Error(`Memory directory "${memDir}" is inside the dream archive path "${archivePath}". ` +
197
+ "The archive must be outside all memory directories.");
198
+ }
199
+ }
200
+ }
201
+ export function validateMemoryLinks(memoryDirs) {
202
+ const brokenLinks = [];
203
+ const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
204
+ for (const memDir of memoryDirs) {
205
+ const indexFile = join(memDir, "MEMORY.md");
206
+ if (!existsSync(indexFile))
207
+ continue;
208
+ let content;
209
+ try {
210
+ content = readFileSync(indexFile, "utf-8");
211
+ }
212
+ catch {
213
+ continue;
214
+ }
215
+ for (const match of content.matchAll(linkPattern)) {
216
+ const pointer = match[0];
217
+ const target = match[2];
218
+ // Skip external URLs
219
+ if (target.startsWith("http://") || target.startsWith("https://"))
220
+ continue;
221
+ const targetPath = join(memDir, target);
222
+ if (!existsSync(targetPath)) {
223
+ brokenLinks.push({ memoryDir: memDir, indexFile, pointer, target });
224
+ }
225
+ }
226
+ }
227
+ return { valid: brokenLinks.length === 0, brokenLinks };
228
+ }
229
+ // =============================================================================
230
+ // Backup
231
+ // =============================================================================
232
+ /** Convert a filesystem path to a collision-resistant directory name for backup staging. */
233
+ export function safeDirName(path) {
234
+ return encodeURIComponent(path).replace(/%2F/gi, "_");
235
+ }
236
+ /** Recursively count files and total size in a directory. */
237
+ async function countFilesAndSize(dir) {
238
+ let fileCount = 0;
239
+ let totalSize = 0;
240
+ async function walk(current) {
241
+ let entries;
242
+ try {
243
+ entries = await readdir(current, { withFileTypes: true });
244
+ }
245
+ catch {
246
+ return;
247
+ }
248
+ for (const entry of entries) {
249
+ const fullPath = join(current, entry.name);
250
+ if (entry.isDirectory()) {
251
+ await walk(fullPath);
252
+ }
253
+ else if (entry.isFile()) {
254
+ try {
255
+ const st = await stat(fullPath);
256
+ fileCount++;
257
+ totalSize += st.size;
258
+ }
259
+ catch {
260
+ // File disappeared between readdir and stat — skip
261
+ }
262
+ }
263
+ }
264
+ }
265
+ await walk(dir);
266
+ return { fileCount, totalSize };
267
+ }
268
+ export async function performDreamBackup(context) {
269
+ const timestamp = `${new Date().toISOString().replace(/[:.]/g, "-")}_${randomUUID().slice(0, 8)}`;
270
+ const archiveName = `${BACKUP_PREFIX}${timestamp}`;
271
+ const backupPath = join(context.archivePath, `${archiveName}.tar.gz`);
272
+ const stagingDir = join(context.archivePath, `.staging-${timestamp}`);
273
+ // Ensure archive directory exists
274
+ try {
275
+ mkdirSync(context.archivePath, { recursive: true });
276
+ }
277
+ catch (error) {
278
+ throw new Error(`Failed to create archive directory "${context.archivePath}": ${error instanceof Error ? error.message : String(error)}`);
279
+ }
280
+ // Build list of source directories
281
+ const allSourceDirs = [];
282
+ // Global memory
283
+ if (existsSync(context.globalMemoryDir)) {
284
+ allSourceDirs.push({ dir: context.globalMemoryDir, stagingTarget: join(stagingDir, "global") });
285
+ }
286
+ // Project memories
287
+ for (const dir of context.projectMemoryDirs) {
288
+ if (existsSync(dir)) {
289
+ allSourceDirs.push({
290
+ dir,
291
+ stagingTarget: join(stagingDir, "projects", safeDirName(dir)),
292
+ });
293
+ }
294
+ }
295
+ // Claude compat memories
296
+ for (const dir of context.claudeMemoryDirs) {
297
+ if (existsSync(dir)) {
298
+ allSourceDirs.push({
299
+ dir,
300
+ stagingTarget: join(stagingDir, "claude", safeDirName(dir)),
301
+ });
302
+ }
303
+ }
304
+ // Count source files before copying
305
+ let sourceFileCount = 0;
306
+ let sourceTotalSize = 0;
307
+ for (const { dir } of allSourceDirs) {
308
+ const counts = await countFilesAndSize(dir);
309
+ sourceFileCount += counts.fileCount;
310
+ sourceTotalSize += counts.totalSize;
311
+ }
312
+ // Copy source directories into staging area
313
+ try {
314
+ mkdirSync(stagingDir, { recursive: true });
315
+ for (const { dir, stagingTarget } of allSourceDirs) {
316
+ try {
317
+ await cp(dir, stagingTarget, { recursive: true });
318
+ }
319
+ catch (error) {
320
+ throw new Error(`Failed to copy "${dir}" to "${stagingTarget}": ${error instanceof Error ? error.message : String(error)}`);
321
+ }
322
+ }
323
+ // Create .tar.gz archive from staging directory
324
+ const execFileAsync = promisify(execFile);
325
+ await execFileAsync("tar", ["czf", backupPath, "-C", dirname(stagingDir), basename(stagingDir)]);
326
+ }
327
+ finally {
328
+ // Clean up staging directory
329
+ try {
330
+ rmSync(stagingDir, { recursive: true, force: true });
331
+ }
332
+ catch {
333
+ // Best-effort cleanup
334
+ }
335
+ }
336
+ // Verify: archive exists and has non-zero size
337
+ let verified = false;
338
+ try {
339
+ const archiveStat = statSync(backupPath);
340
+ verified = archiveStat.size > 0;
341
+ }
342
+ catch {
343
+ // Archive missing or unreadable
344
+ }
345
+ return {
346
+ backupPath,
347
+ timestamp,
348
+ fileCount: sourceFileCount,
349
+ totalSize: sourceTotalSize,
350
+ verified,
351
+ };
352
+ }
353
+ // =============================================================================
354
+ // Prompt Building
355
+ // =============================================================================
356
+ function formatBytes(bytes) {
357
+ if (bytes < 1024)
358
+ return `${bytes} bytes`;
359
+ if (bytes < 1024 * 1024)
360
+ return `${(bytes / 1024).toFixed(1)} KB`;
361
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
362
+ }
363
+ export function buildDreamPrompt(context, backupResult) {
364
+ const verifiedStatus = backupResult.verified ? "✓ Verified" : "⚠ Verification mismatch — check backup integrity";
365
+ const lastRun = context.lastRunTimestamp ?? "Never (first run)";
366
+ // Build the file listing section
367
+ let fileListing = "";
368
+ const allDirs = [
369
+ { label: "Global", dir: context.globalMemoryDir },
370
+ ...context.projectMemoryDirs.map((d) => ({ label: `Project: ${d}`, dir: d })),
371
+ ...context.claudeMemoryDirs.map((d) => ({ label: `Claude (READ-ONLY): ${d}`, dir: d })),
372
+ ];
373
+ for (const { label, dir } of allDirs) {
374
+ if (!existsSync(dir))
375
+ continue;
376
+ fileListing += `\n### ${label}\n`;
377
+ try {
378
+ const entries = readdirSync(dir);
379
+ for (const entry of entries) {
380
+ if (entry === DREAM_TMP_DIR ||
381
+ entry === DREAM_LOCK_FILE ||
382
+ entry === `${DREAM_LOCK_FILE}.lock` ||
383
+ entry === DREAM_LAST_RUN_FILE)
384
+ continue;
385
+ fileListing += `- ${entry}\n`;
386
+ }
387
+ }
388
+ catch {
389
+ fileListing += `- (unreadable)\n`;
390
+ }
391
+ }
392
+ // If file listing is too large, spill to a temp file
393
+ let fileListingSection;
394
+ if (fileListing.length > LARGE_LISTING_THRESHOLD) {
395
+ const tmpDir = join(context.globalMemoryDir, DREAM_TMP_DIR);
396
+ try {
397
+ mkdirSync(tmpDir, { recursive: true });
398
+ }
399
+ catch {
400
+ // Best-effort
401
+ }
402
+ const tmpPath = join(tmpDir, `dream-listing-${Date.now()}.md`);
403
+ try {
404
+ writeFileSync(tmpPath, fileListing, "utf-8");
405
+ fileListingSection =
406
+ `File listing too large to include inline. Full listing written to: ${tmpPath}\n` +
407
+ "Read this file for the complete list of memory files.";
408
+ }
409
+ catch {
410
+ // Fallback: include inline anyway
411
+ fileListingSection = fileListing;
412
+ }
413
+ }
414
+ else {
415
+ fileListingSection = fileListing;
416
+ }
417
+ const projectDirsList = context.projectMemoryDirs.length > 0
418
+ ? context.projectMemoryDirs.map((d) => ` - ${d}`).join("\n")
419
+ : " (none discovered)";
420
+ const claudeDirsList = context.claudeMemoryDirs.length > 0
421
+ ? context.claudeMemoryDirs.map((d) => ` - ${d}`).join("\n")
422
+ : " (none discovered)";
423
+ const dreamTmpDirName = DREAM_TMP_DIR;
424
+ const dreamLastRunPath = join(context.globalMemoryDir, DREAM_LAST_RUN_FILE);
425
+ return `You are running a memory consolidation (/dream). A backup archive has been created at: ${backupResult.backupPath}
426
+ Backup verification: ${backupResult.fileCount} files, ${formatBytes(backupResult.totalSize)} — ${verifiedStatus}
427
+
428
+ ## Context
429
+ - Global memory: ${context.globalMemoryDir}
430
+ - Project memories:
431
+ ${projectDirsList}
432
+ - Claude Code memories (READ-ONLY):
433
+ ${claudeDirsList}
434
+ - Last dream run: ${lastRun}
435
+ - Sessions directory: ${context.sessionsDir}
436
+
437
+ ## Memory Files
438
+ ${fileListingSection}
439
+
440
+ ## HARD CONSTRAINTS
441
+ - The \`.claude/\` memory directories are read-only compatibility imports. Do NOT modify, delete, or rewrite any files under \`.claude/\` paths. Only \`~/.dreb/memory/\` and \`<project>/.dreb/memory/\` are writable.
442
+ - NEVER remove session JSONL data files.
443
+ - NEVER delete or modify \`.dream.lock\`, \`.dream.lock.lock\`, or \`.dream-last-run\` files — these are managed by the dream infrastructure, not by you.
444
+ - Explicitly EXCLUDE \`subagent-sessions/\` from scanning scope.
445
+
446
+ ## Pipeline
447
+
448
+ ### Step 0: Verify Backup
449
+ Before proceeding, verify the backup archive exists and is intact:
450
+ 1. Check that the backup file exists at the path shown above
451
+ 2. List its contents (e.g., \`tar tzf <path> | head -20\`) to confirm memory files are present
452
+ 3. If the backup appears incomplete or missing, STOP and inform the user — do not proceed with consolidation
453
+
454
+ ### Step 1: Read All Memories
455
+ Read every MEMORY.md index and every referenced memory file from global, all project scopes, and \`.claude/\` read-only paths.
456
+
457
+ ### Step 2: Analyze & Plan
458
+ Group related entries, identify duplicates, overlapping content, stale references (deleted files, resolved issues). Present the consolidation plan to the user before making any changes.
459
+
460
+ ### Step 3: Consolidate
461
+ Merge related entries, deduplicate, reorganize semantically. Write changes to temp files first (\`<memory-dir>/${dreamTmpDirName}/\`), then atomic rename per file. Maintain a rollback manifest listing every original→tmp→final path.
462
+
463
+ ### Step 4: Rewrite Indexes
464
+ Produce new MEMORY.md indexes organized semantically, under 200 lines. Every pointer must reference an existing file. Write to temp first, then rename.
465
+
466
+ ### Step 5: Remove Dead Files
467
+ Only after indexes are rewritten and validated, delete memory files that have ZERO remaining references in any MEMORY.md index. Never delete a file that is still referenced.
468
+
469
+ ### Step 6: Scan Sessions
470
+ Spawn background Explore subagents to read session JSONL logs from ${context.sessionsDir} (EXCLUDE subagent-sessions/). Only scan sessions since ${lastRun}. First-run cap: 30 days maximum.
471
+
472
+ Session JSONL format: Each line is a JSON object. Relevant entry types:
473
+ - \`{"type":"message","message":{"role":"user"|"assistant","content":...}}\` — conversation messages
474
+ - \`{"type":"tool_use","name":"...","input":{...}}\` — tool calls
475
+ - \`{"type":"tool_result","content":...}\` — tool outputs
476
+
477
+ Each subagent should return findings in structured format:
478
+ \`{"findings": [{"type": "user-preferences|good-practices|project|navigation", "name": "...", "description": "...", "content": "..."}]}\`
479
+
480
+ ### Step 7: STOP AND WAIT
481
+ After spawning subagents, stop generating and wait for ALL background-agent-complete messages before proceeding. Do not continue to Step 8 until every subagent has reported back.
482
+
483
+ ### Step 8: Incorporate Findings
484
+ Create new memory entries from subagent findings, update MEMORY.md indexes.
485
+
486
+ ### Step 9: Report
487
+ Structured summary: X merged, Y pruned, Z added from sessions, W files removed, backup location, any warnings.
488
+
489
+ ### Step 10: Mark Complete
490
+ Write the current ISO timestamp to ${dreamLastRunPath}
491
+ `;
492
+ }
493
+ // =============================================================================
494
+ // Locking
495
+ // =============================================================================
496
+ export async function acquireDreamLock(overrideMemDir) {
497
+ const memDir = overrideMemDir ?? join(homedir(), ".dreb", "memory");
498
+ const lockPath = join(memDir, DREAM_LOCK_FILE);
499
+ // Ensure directory and lock file exist (proper-lockfile requires the file to exist)
500
+ try {
501
+ mkdirSync(memDir, { recursive: true });
502
+ }
503
+ catch {
504
+ // Directory already exists
505
+ }
506
+ if (!existsSync(lockPath)) {
507
+ try {
508
+ writeFileSync(lockPath, "", "utf-8");
509
+ }
510
+ catch (error) {
511
+ throw new Error(`Failed to create dream lock file "${lockPath}": ${error instanceof Error ? error.message : String(error)}`);
512
+ }
513
+ }
514
+ let release;
515
+ try {
516
+ release = await lockfile.lock(lockPath, {
517
+ stale: 60000,
518
+ retries: {
519
+ retries: 5,
520
+ factor: 2,
521
+ minTimeout: 200,
522
+ maxTimeout: 5000,
523
+ randomize: true,
524
+ },
525
+ });
526
+ }
527
+ catch (error) {
528
+ const code = typeof error === "object" && error !== null && "code" in error
529
+ ? String(error.code)
530
+ : undefined;
531
+ if (code === "ELOCKED") {
532
+ throw new Error("Another /dream operation is already running. " +
533
+ "If this is stale, wait 60 seconds or manually remove " +
534
+ `the lock: ${lockPath}.lock`);
535
+ }
536
+ throw new Error(`Failed to acquire dream lock: ${error instanceof Error ? error.message : String(error)}`);
537
+ }
538
+ return () => {
539
+ release().catch(() => {
540
+ // Best-effort unlock — if it fails the stale timeout will clean up
541
+ });
542
+ };
543
+ }
544
+ // =============================================================================
545
+ // Backup Pruning
546
+ // =============================================================================
547
+ export async function pruneOldBackups(archivePath, keepCount = DEFAULT_KEEP_COUNT) {
548
+ if (!existsSync(archivePath))
549
+ return;
550
+ let dirEntries;
551
+ try {
552
+ dirEntries = readdirSync(archivePath);
553
+ }
554
+ catch {
555
+ return;
556
+ }
557
+ const backupFiles = dirEntries.filter((name) => name.startsWith(BACKUP_PREFIX) && name.endsWith(".tar.gz")).sort();
558
+ if (backupFiles.length <= keepCount)
559
+ return;
560
+ const toRemove = backupFiles.slice(0, backupFiles.length - keepCount);
561
+ for (const fileName of toRemove) {
562
+ const fullPath = join(archivePath, fileName);
563
+ try {
564
+ unlinkSync(fullPath);
565
+ }
566
+ catch {
567
+ // Best-effort — log would be nice but we don't have a logger here
568
+ }
569
+ }
570
+ }
571
+ // =============================================================================
572
+ // Temp Dir Cleanup
573
+ // =============================================================================
574
+ export function cleanupDreamTmpDirs(memoryDirs) {
575
+ for (const memDir of memoryDirs) {
576
+ const tmpDir = join(memDir, DREAM_TMP_DIR);
577
+ if (existsSync(tmpDir)) {
578
+ try {
579
+ rmSync(tmpDir, { recursive: true, force: true });
580
+ }
581
+ catch {
582
+ // Best-effort cleanup
583
+ }
584
+ }
585
+ }
586
+ }
587
+ //# sourceMappingURL=dream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dream.js","sourceRoot":"","sources":["../../src/core/dream.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile } from \"node:child_process\";\nimport { randomUUID } from \"node:crypto\";\nimport {\n\tcloseSync,\n\texistsSync,\n\tmkdirSync,\n\topenSync,\n\treaddirSync,\n\treadFileSync,\n\treadSync,\n\trmSync,\n\tstatSync,\n\tunlinkSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { cp, readdir, stat } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, isAbsolute, join, resolve } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport lockfile from \"proper-lockfile\";\nimport { getSessionsDir } from \"../config.js\";\nimport type { SettingsManager } from \"./settings-manager.js\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface DreamContext {\n\tarchivePath: string;\n\tlastRunTimestamp: string | null;\n\tglobalMemoryDir: string;\n\tprojectMemoryDirs: string[];\n\tclaudeMemoryDirs: string[];\n\tsessionsDir: string;\n}\n\nexport interface BackupResult {\n\tbackupPath: string;\n\ttimestamp: string;\n\tfileCount: number;\n\ttotalSize: number;\n\tverified: boolean;\n}\n\nexport interface LinkValidationResult {\n\tvalid: boolean;\n\tbrokenLinks: Array<{ memoryDir: string; indexFile: string; pointer: string; target: string }>;\n}\n\nexport type DreamCommand = { type: \"run\" } | { type: \"setBackup\"; path: string } | { type: \"showBackup\" };\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst DREAM_LAST_RUN_FILE = \".dream-last-run\";\nconst DREAM_LOCK_FILE = \".dream.lock\";\nconst DREAM_TMP_DIR = \".dream-tmp\";\nconst BACKUP_PREFIX = \"dream-backup-\";\nconst DEFAULT_KEEP_COUNT = 10;\nconst LARGE_LISTING_THRESHOLD = 10_000;\n\n// =============================================================================\n// Command Parsing\n// =============================================================================\n\nexport function parseDreamCommand(text: string): DreamCommand {\n\tconst stripped = text.replace(/^\\/dream\\s*/, \"\").trim();\n\n\tif (!stripped) {\n\t\treturn { type: \"run\" };\n\t}\n\n\tif (stripped === \"backup\") {\n\t\treturn { type: \"showBackup\" };\n\t}\n\n\tconst backupMatch = stripped.match(/^backup\\s+(.+)$/);\n\tif (backupMatch) {\n\t\treturn { type: \"setBackup\", path: backupMatch[1].trim() };\n\t}\n\n\t// Unknown subcommand — treat as a plain run\n\treturn { type: \"run\" };\n}\n\n// =============================================================================\n// Discovery\n// =============================================================================\n\n/**\n * Read the `cwd` field from the JSONL session header (first line) of a file.\n * Returns `undefined` if the file cannot be read or the header is invalid.\n * Uses synchronous low-level reads to avoid loading entire files.\n */\nfunction readSessionCwd(filePath: string): string | undefined {\n\tlet fd: number | undefined;\n\ttry {\n\t\tfd = openSync(filePath, \"r\");\n\t\tconst buffer = Buffer.alloc(4096);\n\t\tconst bytesRead = readSync(fd, buffer, 0, 4096, 0);\n\t\tconst firstLine = buffer.toString(\"utf8\", 0, bytesRead).split(\"\\n\")[0];\n\t\tif (!firstLine) return undefined;\n\t\tconst header = JSON.parse(firstLine);\n\t\tif (header.type === \"session\" && typeof header.cwd === \"string\") {\n\t\t\treturn header.cwd;\n\t\t}\n\t} catch {\n\t\t// Corrupt or unreadable — skip\n\t} finally {\n\t\tif (fd !== undefined) {\n\t\t\ttry {\n\t\t\t\tcloseSync(fd);\n\t\t\t} catch {\n\t\t\t\t// Best-effort close\n\t\t\t}\n\t\t}\n\t}\n\treturn undefined;\n}\n\nexport function discoverAllProjectMemoryDirs(overrideSessionsDir?: string): string[] {\n\tconst sessionsDir = overrideSessionsDir ?? getSessionsDir();\n\tif (!existsSync(sessionsDir)) return [];\n\n\tlet dirEntries: string[];\n\ttry {\n\t\tdirEntries = readdirSync(sessionsDir);\n\t} catch {\n\t\treturn [];\n\t}\n\n\tconst memoryDirs: string[] = [];\n\tconst seen = new Set<string>();\n\n\tfor (const name of dirEntries) {\n\t\tif (!name.startsWith(\"--\") || !name.endsWith(\"--\")) continue;\n\n\t\tconst sessionSubDir = join(sessionsDir, name);\n\n\t\t// Find any .jsonl file in this session directory\n\t\tlet jsonlFiles: string[];\n\t\ttry {\n\t\t\tjsonlFiles = readdirSync(sessionSubDir).filter((f) => f.endsWith(\".jsonl\"));\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (jsonlFiles.length === 0) continue;\n\n\t\tlet cwd: string | undefined;\n\t\tfor (const f of jsonlFiles) {\n\t\t\tcwd = readSessionCwd(join(sessionSubDir, f));\n\t\t\tif (cwd) break;\n\t\t}\n\t\tif (!cwd) continue;\n\n\t\tconst memDir = join(cwd, \".dreb\", \"memory\");\n\t\tif (!seen.has(memDir) && existsSync(memDir)) {\n\t\t\tseen.add(memDir);\n\t\t\tmemoryDirs.push(memDir);\n\t\t}\n\t}\n\n\treturn memoryDirs;\n}\n\n/**\n * Discover claude compatibility memory directories (read-only imports).\n * Scans ~/.claude/projects/ for subdirectories containing a memory/ folder.\n */\nfunction discoverClaudeMemoryDirs(): string[] {\n\tconst claudeProjectsDir = join(homedir(), \".claude\", \"projects\");\n\tif (!existsSync(claudeProjectsDir)) return [];\n\n\tlet dirEntries: string[];\n\ttry {\n\t\tdirEntries = readdirSync(claudeProjectsDir);\n\t} catch {\n\t\treturn [];\n\t}\n\n\tconst memoryDirs: string[] = [];\n\tfor (const name of dirEntries) {\n\t\tconst memDir = join(claudeProjectsDir, name, \"memory\");\n\t\tif (existsSync(memDir)) {\n\t\t\tmemoryDirs.push(memDir);\n\t\t}\n\t}\n\treturn memoryDirs;\n}\n\n// =============================================================================\n// Context Resolution\n// =============================================================================\n\nexport async function resolveDreamContext(\n\tsettingsManager: SettingsManager,\n\toverrideGlobalMemDir?: string,\n): Promise<DreamContext> {\n\t// Archive path from settings, or default\n\tconst archivePath = settingsManager.getDreamArchivePath();\n\n\t// Global memory dir\n\tconst globalMemoryDir = overrideGlobalMemDir ?? join(homedir(), \".dreb\", \"memory\");\n\n\t// Last run timestamp\n\tlet lastRunTimestamp: string | null = null;\n\tconst markerPath = join(globalMemoryDir, DREAM_LAST_RUN_FILE);\n\ttry {\n\t\tif (existsSync(markerPath)) {\n\t\t\tconst content = readFileSync(markerPath, \"utf-8\").trim();\n\t\t\tif (content) {\n\t\t\t\tlastRunTimestamp = content;\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Marker unreadable — treat as first run\n\t}\n\n\t// Discover memory directories\n\tconst projectMemoryDirs = discoverAllProjectMemoryDirs();\n\tconst claudeMemoryDirs = discoverClaudeMemoryDirs();\n\tconst sessionsDir = getSessionsDir();\n\n\treturn {\n\t\tarchivePath,\n\t\tlastRunTimestamp,\n\t\tglobalMemoryDir,\n\t\tprojectMemoryDirs,\n\t\tclaudeMemoryDirs,\n\t\tsessionsDir,\n\t};\n}\n\n// =============================================================================\n// Validation\n// =============================================================================\n\nexport function validateArchivePath(archivePath: string, memoryDirs: string[]): void {\n\tif (!archivePath || !archivePath.trim()) {\n\t\tthrow new Error(\"Dream archive path cannot be empty\");\n\t}\n\n\tif (!isAbsolute(archivePath)) {\n\t\tthrow new Error(`Dream archive path must be absolute, got: ${archivePath}`);\n\t}\n\n\tconst normalized = resolve(archivePath);\n\n\tfor (const memDir of memoryDirs) {\n\t\tconst normalizedMem = resolve(memDir);\n\n\t\t// Archive is inside a memory dir\n\t\tif (normalized.startsWith(`${normalizedMem}/`) || normalized === normalizedMem) {\n\t\t\tthrow new Error(\n\t\t\t\t`Dream archive path \"${archivePath}\" overlaps with memory directory \"${memDir}\". ` +\n\t\t\t\t\t\"The archive must be outside all memory directories.\",\n\t\t\t);\n\t\t}\n\n\t\t// Memory dir is inside the archive\n\t\tif (normalizedMem.startsWith(`${normalized}/`) || normalizedMem === normalized) {\n\t\t\tthrow new Error(\n\t\t\t\t`Memory directory \"${memDir}\" is inside the dream archive path \"${archivePath}\". ` +\n\t\t\t\t\t\"The archive must be outside all memory directories.\",\n\t\t\t);\n\t\t}\n\t}\n}\n\nexport function validateMemoryLinks(memoryDirs: string[]): LinkValidationResult {\n\tconst brokenLinks: LinkValidationResult[\"brokenLinks\"] = [];\n\tconst linkPattern = /\\[([^\\]]+)\\]\\(([^)]+)\\)/g;\n\n\tfor (const memDir of memoryDirs) {\n\t\tconst indexFile = join(memDir, \"MEMORY.md\");\n\t\tif (!existsSync(indexFile)) continue;\n\n\t\tlet content: string;\n\t\ttry {\n\t\t\tcontent = readFileSync(indexFile, \"utf-8\");\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (const match of content.matchAll(linkPattern)) {\n\t\t\tconst pointer = match[0];\n\t\t\tconst target = match[2];\n\n\t\t\t// Skip external URLs\n\t\t\tif (target.startsWith(\"http://\") || target.startsWith(\"https://\")) continue;\n\n\t\t\tconst targetPath = join(memDir, target);\n\t\t\tif (!existsSync(targetPath)) {\n\t\t\t\tbrokenLinks.push({ memoryDir: memDir, indexFile, pointer, target });\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { valid: brokenLinks.length === 0, brokenLinks };\n}\n\n// =============================================================================\n// Backup\n// =============================================================================\n\n/** Convert a filesystem path to a collision-resistant directory name for backup staging. */\nexport function safeDirName(path: string): string {\n\treturn encodeURIComponent(path).replace(/%2F/gi, \"_\");\n}\n\n/** Recursively count files and total size in a directory. */\nasync function countFilesAndSize(dir: string): Promise<{ fileCount: number; totalSize: number }> {\n\tlet fileCount = 0;\n\tlet totalSize = 0;\n\n\tasync function walk(current: string): Promise<void> {\n\t\tlet entries: import(\"node:fs\").Dirent[];\n\t\ttry {\n\t\t\tentries = await readdir(current, { withFileTypes: true });\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(current, entry.name);\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tawait walk(fullPath);\n\t\t\t} else if (entry.isFile()) {\n\t\t\t\ttry {\n\t\t\t\t\tconst st = await stat(fullPath);\n\t\t\t\t\tfileCount++;\n\t\t\t\t\ttotalSize += st.size;\n\t\t\t\t} catch {\n\t\t\t\t\t// File disappeared between readdir and stat — skip\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tawait walk(dir);\n\treturn { fileCount, totalSize };\n}\n\nexport async function performDreamBackup(context: DreamContext): Promise<BackupResult> {\n\tconst timestamp = `${new Date().toISOString().replace(/[:.]/g, \"-\")}_${randomUUID().slice(0, 8)}`;\n\tconst archiveName = `${BACKUP_PREFIX}${timestamp}`;\n\tconst backupPath = join(context.archivePath, `${archiveName}.tar.gz`);\n\tconst stagingDir = join(context.archivePath, `.staging-${timestamp}`);\n\n\t// Ensure archive directory exists\n\ttry {\n\t\tmkdirSync(context.archivePath, { recursive: true });\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Failed to create archive directory \"${context.archivePath}\": ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\n\t// Build list of source directories\n\tconst allSourceDirs: Array<{ dir: string; stagingTarget: string }> = [];\n\n\t// Global memory\n\tif (existsSync(context.globalMemoryDir)) {\n\t\tallSourceDirs.push({ dir: context.globalMemoryDir, stagingTarget: join(stagingDir, \"global\") });\n\t}\n\n\t// Project memories\n\tfor (const dir of context.projectMemoryDirs) {\n\t\tif (existsSync(dir)) {\n\t\t\tallSourceDirs.push({\n\t\t\t\tdir,\n\t\t\t\tstagingTarget: join(stagingDir, \"projects\", safeDirName(dir)),\n\t\t\t});\n\t\t}\n\t}\n\n\t// Claude compat memories\n\tfor (const dir of context.claudeMemoryDirs) {\n\t\tif (existsSync(dir)) {\n\t\t\tallSourceDirs.push({\n\t\t\t\tdir,\n\t\t\t\tstagingTarget: join(stagingDir, \"claude\", safeDirName(dir)),\n\t\t\t});\n\t\t}\n\t}\n\n\t// Count source files before copying\n\tlet sourceFileCount = 0;\n\tlet sourceTotalSize = 0;\n\tfor (const { dir } of allSourceDirs) {\n\t\tconst counts = await countFilesAndSize(dir);\n\t\tsourceFileCount += counts.fileCount;\n\t\tsourceTotalSize += counts.totalSize;\n\t}\n\n\t// Copy source directories into staging area\n\ttry {\n\t\tmkdirSync(stagingDir, { recursive: true });\n\n\t\tfor (const { dir, stagingTarget } of allSourceDirs) {\n\t\t\ttry {\n\t\t\t\tawait cp(dir, stagingTarget, { recursive: true });\n\t\t\t} catch (error) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Failed to copy \"${dir}\" to \"${stagingTarget}\": ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Create .tar.gz archive from staging directory\n\t\tconst execFileAsync = promisify(execFile);\n\t\tawait execFileAsync(\"tar\", [\"czf\", backupPath, \"-C\", dirname(stagingDir), basename(stagingDir)]);\n\t} finally {\n\t\t// Clean up staging directory\n\t\ttry {\n\t\t\trmSync(stagingDir, { recursive: true, force: true });\n\t\t} catch {\n\t\t\t// Best-effort cleanup\n\t\t}\n\t}\n\n\t// Verify: archive exists and has non-zero size\n\tlet verified = false;\n\ttry {\n\t\tconst archiveStat = statSync(backupPath);\n\t\tverified = archiveStat.size > 0;\n\t} catch {\n\t\t// Archive missing or unreadable\n\t}\n\n\treturn {\n\t\tbackupPath,\n\t\ttimestamp,\n\t\tfileCount: sourceFileCount,\n\t\ttotalSize: sourceTotalSize,\n\t\tverified,\n\t};\n}\n\n// =============================================================================\n// Prompt Building\n// =============================================================================\n\nfunction formatBytes(bytes: number): string {\n\tif (bytes < 1024) return `${bytes} bytes`;\n\tif (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n\treturn `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nexport function buildDreamPrompt(context: DreamContext, backupResult: BackupResult): string {\n\tconst verifiedStatus = backupResult.verified ? \"✓ Verified\" : \"⚠ Verification mismatch — check backup integrity\";\n\tconst lastRun = context.lastRunTimestamp ?? \"Never (first run)\";\n\n\t// Build the file listing section\n\tlet fileListing = \"\";\n\tconst allDirs = [\n\t\t{ label: \"Global\", dir: context.globalMemoryDir },\n\t\t...context.projectMemoryDirs.map((d) => ({ label: `Project: ${d}`, dir: d })),\n\t\t...context.claudeMemoryDirs.map((d) => ({ label: `Claude (READ-ONLY): ${d}`, dir: d })),\n\t];\n\n\tfor (const { label, dir } of allDirs) {\n\t\tif (!existsSync(dir)) continue;\n\t\tfileListing += `\\n### ${label}\\n`;\n\t\ttry {\n\t\t\tconst entries = readdirSync(dir);\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (\n\t\t\t\t\tentry === DREAM_TMP_DIR ||\n\t\t\t\t\tentry === DREAM_LOCK_FILE ||\n\t\t\t\t\tentry === `${DREAM_LOCK_FILE}.lock` ||\n\t\t\t\t\tentry === DREAM_LAST_RUN_FILE\n\t\t\t\t)\n\t\t\t\t\tcontinue;\n\t\t\t\tfileListing += `- ${entry}\\n`;\n\t\t\t}\n\t\t} catch {\n\t\t\tfileListing += `- (unreadable)\\n`;\n\t\t}\n\t}\n\n\t// If file listing is too large, spill to a temp file\n\tlet fileListingSection: string;\n\tif (fileListing.length > LARGE_LISTING_THRESHOLD) {\n\t\tconst tmpDir = join(context.globalMemoryDir, DREAM_TMP_DIR);\n\t\ttry {\n\t\t\tmkdirSync(tmpDir, { recursive: true });\n\t\t} catch {\n\t\t\t// Best-effort\n\t\t}\n\t\tconst tmpPath = join(tmpDir, `dream-listing-${Date.now()}.md`);\n\t\ttry {\n\t\t\twriteFileSync(tmpPath, fileListing, \"utf-8\");\n\t\t\tfileListingSection =\n\t\t\t\t`File listing too large to include inline. Full listing written to: ${tmpPath}\\n` +\n\t\t\t\t\"Read this file for the complete list of memory files.\";\n\t\t} catch {\n\t\t\t// Fallback: include inline anyway\n\t\t\tfileListingSection = fileListing;\n\t\t}\n\t} else {\n\t\tfileListingSection = fileListing;\n\t}\n\n\tconst projectDirsList =\n\t\tcontext.projectMemoryDirs.length > 0\n\t\t\t? context.projectMemoryDirs.map((d) => ` - ${d}`).join(\"\\n\")\n\t\t\t: \" (none discovered)\";\n\n\tconst claudeDirsList =\n\t\tcontext.claudeMemoryDirs.length > 0\n\t\t\t? context.claudeMemoryDirs.map((d) => ` - ${d}`).join(\"\\n\")\n\t\t\t: \" (none discovered)\";\n\n\tconst dreamTmpDirName = DREAM_TMP_DIR;\n\tconst dreamLastRunPath = join(context.globalMemoryDir, DREAM_LAST_RUN_FILE);\n\n\treturn `You are running a memory consolidation (/dream). A backup archive has been created at: ${backupResult.backupPath}\nBackup verification: ${backupResult.fileCount} files, ${formatBytes(backupResult.totalSize)} — ${verifiedStatus}\n\n## Context\n- Global memory: ${context.globalMemoryDir}\n- Project memories:\n${projectDirsList}\n- Claude Code memories (READ-ONLY):\n${claudeDirsList}\n- Last dream run: ${lastRun}\n- Sessions directory: ${context.sessionsDir}\n\n## Memory Files\n${fileListingSection}\n\n## HARD CONSTRAINTS\n- The \\`.claude/\\` memory directories are read-only compatibility imports. Do NOT modify, delete, or rewrite any files under \\`.claude/\\` paths. Only \\`~/.dreb/memory/\\` and \\`<project>/.dreb/memory/\\` are writable.\n- NEVER remove session JSONL data files.\n- NEVER delete or modify \\`.dream.lock\\`, \\`.dream.lock.lock\\`, or \\`.dream-last-run\\` files — these are managed by the dream infrastructure, not by you.\n- Explicitly EXCLUDE \\`subagent-sessions/\\` from scanning scope.\n\n## Pipeline\n\n### Step 0: Verify Backup\nBefore proceeding, verify the backup archive exists and is intact:\n1. Check that the backup file exists at the path shown above\n2. List its contents (e.g., \\`tar tzf <path> | head -20\\`) to confirm memory files are present\n3. If the backup appears incomplete or missing, STOP and inform the user — do not proceed with consolidation\n\n### Step 1: Read All Memories\nRead every MEMORY.md index and every referenced memory file from global, all project scopes, and \\`.claude/\\` read-only paths.\n\n### Step 2: Analyze & Plan\nGroup related entries, identify duplicates, overlapping content, stale references (deleted files, resolved issues). Present the consolidation plan to the user before making any changes.\n\n### Step 3: Consolidate\nMerge related entries, deduplicate, reorganize semantically. Write changes to temp files first (\\`<memory-dir>/${dreamTmpDirName}/\\`), then atomic rename per file. Maintain a rollback manifest listing every original→tmp→final path.\n\n### Step 4: Rewrite Indexes\nProduce new MEMORY.md indexes organized semantically, under 200 lines. Every pointer must reference an existing file. Write to temp first, then rename.\n\n### Step 5: Remove Dead Files\nOnly after indexes are rewritten and validated, delete memory files that have ZERO remaining references in any MEMORY.md index. Never delete a file that is still referenced.\n\n### Step 6: Scan Sessions\nSpawn background Explore subagents to read session JSONL logs from ${context.sessionsDir} (EXCLUDE subagent-sessions/). Only scan sessions since ${lastRun}. First-run cap: 30 days maximum.\n\nSession JSONL format: Each line is a JSON object. Relevant entry types:\n- \\`{\"type\":\"message\",\"message\":{\"role\":\"user\"|\"assistant\",\"content\":...}}\\` — conversation messages\n- \\`{\"type\":\"tool_use\",\"name\":\"...\",\"input\":{...}}\\` — tool calls\n- \\`{\"type\":\"tool_result\",\"content\":...}\\` — tool outputs\n\nEach subagent should return findings in structured format:\n\\`{\"findings\": [{\"type\": \"user-preferences|good-practices|project|navigation\", \"name\": \"...\", \"description\": \"...\", \"content\": \"...\"}]}\\`\n\n### Step 7: STOP AND WAIT\nAfter spawning subagents, stop generating and wait for ALL background-agent-complete messages before proceeding. Do not continue to Step 8 until every subagent has reported back.\n\n### Step 8: Incorporate Findings\nCreate new memory entries from subagent findings, update MEMORY.md indexes.\n\n### Step 9: Report\nStructured summary: X merged, Y pruned, Z added from sessions, W files removed, backup location, any warnings.\n\n### Step 10: Mark Complete\nWrite the current ISO timestamp to ${dreamLastRunPath}\n`;\n}\n\n// =============================================================================\n// Locking\n// =============================================================================\n\nexport async function acquireDreamLock(overrideMemDir?: string): Promise<() => void> {\n\tconst memDir = overrideMemDir ?? join(homedir(), \".dreb\", \"memory\");\n\tconst lockPath = join(memDir, DREAM_LOCK_FILE);\n\n\t// Ensure directory and lock file exist (proper-lockfile requires the file to exist)\n\ttry {\n\t\tmkdirSync(memDir, { recursive: true });\n\t} catch {\n\t\t// Directory already exists\n\t}\n\n\tif (!existsSync(lockPath)) {\n\t\ttry {\n\t\t\twriteFileSync(lockPath, \"\", \"utf-8\");\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to create dream lock file \"${lockPath}\": ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tlet release: () => Promise<void>;\n\ttry {\n\t\trelease = await lockfile.lock(lockPath, {\n\t\t\tstale: 60000,\n\t\t\tretries: {\n\t\t\t\tretries: 5,\n\t\t\t\tfactor: 2,\n\t\t\t\tminTimeout: 200,\n\t\t\t\tmaxTimeout: 5000,\n\t\t\t\trandomize: true,\n\t\t\t},\n\t\t});\n\t} catch (error) {\n\t\tconst code =\n\t\t\ttypeof error === \"object\" && error !== null && \"code\" in error\n\t\t\t\t? String((error as { code?: unknown }).code)\n\t\t\t\t: undefined;\n\t\tif (code === \"ELOCKED\") {\n\t\t\tthrow new Error(\n\t\t\t\t\"Another /dream operation is already running. \" +\n\t\t\t\t\t\"If this is stale, wait 60 seconds or manually remove \" +\n\t\t\t\t\t`the lock: ${lockPath}.lock`,\n\t\t\t);\n\t\t}\n\t\tthrow new Error(`Failed to acquire dream lock: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n\n\treturn () => {\n\t\trelease().catch(() => {\n\t\t\t// Best-effort unlock — if it fails the stale timeout will clean up\n\t\t});\n\t};\n}\n\n// =============================================================================\n// Backup Pruning\n// =============================================================================\n\nexport async function pruneOldBackups(archivePath: string, keepCount: number = DEFAULT_KEEP_COUNT): Promise<void> {\n\tif (!existsSync(archivePath)) return;\n\n\tlet dirEntries: string[];\n\ttry {\n\t\tdirEntries = readdirSync(archivePath);\n\t} catch {\n\t\treturn;\n\t}\n\n\tconst backupFiles = dirEntries.filter((name) => name.startsWith(BACKUP_PREFIX) && name.endsWith(\".tar.gz\")).sort();\n\n\tif (backupFiles.length <= keepCount) return;\n\n\tconst toRemove = backupFiles.slice(0, backupFiles.length - keepCount);\n\tfor (const fileName of toRemove) {\n\t\tconst fullPath = join(archivePath, fileName);\n\t\ttry {\n\t\t\tunlinkSync(fullPath);\n\t\t} catch {\n\t\t\t// Best-effort — log would be nice but we don't have a logger here\n\t\t}\n\t}\n}\n\n// =============================================================================\n// Temp Dir Cleanup\n// =============================================================================\n\nexport function cleanupDreamTmpDirs(memoryDirs: string[]): void {\n\tfor (const memDir of memoryDirs) {\n\t\tconst tmpDir = join(memDir, DREAM_TMP_DIR);\n\t\tif (existsSync(tmpDir)) {\n\t\t\ttry {\n\t\t\t\trmSync(tmpDir, { recursive: true, force: true });\n\t\t\t} catch {\n\t\t\t\t// Best-effort cleanup\n\t\t\t}\n\t\t}\n\t}\n}\n"]}