@stackmemoryai/stackmemory 0.3.16 → 0.3.18

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 (213) hide show
  1. package/README.md +48 -2
  2. package/dist/cli/commands/skills.js +15 -2
  3. package/dist/cli/commands/skills.js.map +2 -2
  4. package/dist/cli/index.js +113 -834
  5. package/dist/cli/index.js.map +3 -3
  6. package/dist/core/context/dual-stack-manager.js +1 -1
  7. package/dist/core/context/dual-stack-manager.js.map +1 -1
  8. package/dist/core/context/frame-manager.js +3 -0
  9. package/dist/core/context/frame-manager.js.map +2 -2
  10. package/dist/integrations/claude-code/subagent-client.js +106 -3
  11. package/dist/integrations/claude-code/subagent-client.js.map +2 -2
  12. package/dist/servers/railway/config.js +51 -0
  13. package/dist/servers/railway/config.js.map +7 -0
  14. package/dist/servers/railway/index-enhanced.js +156 -0
  15. package/dist/servers/railway/index-enhanced.js.map +7 -0
  16. package/dist/servers/railway/minimal.js +48 -3
  17. package/dist/servers/railway/minimal.js.map +2 -2
  18. package/dist/servers/railway/storage-test.js +455 -0
  19. package/dist/servers/railway/storage-test.js.map +7 -0
  20. package/dist/skills/claude-skills.js +13 -12
  21. package/dist/skills/claude-skills.js.map +2 -2
  22. package/dist/skills/recursive-agent-orchestrator.js +27 -18
  23. package/dist/skills/recursive-agent-orchestrator.js.map +2 -2
  24. package/dist/skills/unified-rlm-orchestrator.js.map +2 -2
  25. package/package.json +6 -18
  26. package/scripts/README-TESTING.md +186 -0
  27. package/scripts/analyze-cli-security.js +288 -0
  28. package/scripts/archive/add-phase-tasks-to-linear.js +163 -0
  29. package/scripts/archive/analyze-linear-duplicates.js +214 -0
  30. package/scripts/archive/analyze-remaining-duplicates.js +230 -0
  31. package/scripts/archive/analyze-sta-duplicates.js +292 -0
  32. package/scripts/archive/analyze-sta-graphql.js +399 -0
  33. package/scripts/archive/cancel-duplicate-tasks.ts +246 -0
  34. package/scripts/archive/check-all-duplicates.ts +419 -0
  35. package/scripts/archive/clean-duplicate-tasks.js +114 -0
  36. package/scripts/archive/cleanup-duplicate-tasks.ts +286 -0
  37. package/scripts/archive/create-phase-tasks.js +387 -0
  38. package/scripts/archive/delete-linear-duplicates.js +182 -0
  39. package/scripts/archive/delete-remaining-duplicates.js +158 -0
  40. package/scripts/archive/delete-sta-duplicates.js +201 -0
  41. package/scripts/archive/delete-sta-oauth.js +201 -0
  42. package/scripts/archive/export-sta-tasks.js +62 -0
  43. package/scripts/archive/install-auto-sync.js +266 -0
  44. package/scripts/archive/install-chromadb-hooks.sh +133 -0
  45. package/scripts/archive/install-enhanced-clear-hooks.sh +431 -0
  46. package/scripts/archive/install-post-task-hooks.sh +289 -0
  47. package/scripts/archive/install-stackmemory-hooks.sh +420 -0
  48. package/scripts/archive/merge-linear-duplicates-safe.ts +362 -0
  49. package/scripts/archive/merge-linear-duplicates.ts +180 -0
  50. package/scripts/archive/remove-sta-tasks.js +70 -0
  51. package/scripts/archive/setup-background-sync.sh +168 -0
  52. package/scripts/archive/setup-claude-auto-triggers.sh +181 -0
  53. package/scripts/archive/setup-claude-autostart.sh +305 -0
  54. package/scripts/archive/setup-git-hooks.sh +25 -0
  55. package/scripts/archive/setup-linear-oauth.sh +46 -0
  56. package/scripts/archive/setup-mcp.sh +113 -0
  57. package/scripts/archive/setup-railway-deployment.sh +81 -0
  58. package/scripts/auto-handoff.sh +262 -0
  59. package/scripts/background-sync-manager.js +416 -0
  60. package/scripts/benchmark-performance.ts +57 -0
  61. package/scripts/check-redis.ts +48 -0
  62. package/scripts/chromadb-auto-loader.sh +128 -0
  63. package/scripts/chromadb-context-loader.js +479 -0
  64. package/scripts/claude-chromadb-hook.js +460 -0
  65. package/scripts/claude-code-wrapper.sh +66 -0
  66. package/scripts/claude-linear-skill.js +455 -0
  67. package/scripts/claude-pre-commit.sh +302 -0
  68. package/scripts/claude-sm-autostart.js +532 -0
  69. package/scripts/claude-sm-setup.sh +367 -0
  70. package/scripts/claude-with-chromadb.sh +69 -0
  71. package/scripts/claude-worktree-manager.sh +323 -0
  72. package/scripts/claude-worktree-monitor.sh +371 -0
  73. package/scripts/claude-worktree-setup.sh +327 -0
  74. package/scripts/clean-linear-backlog.js +273 -0
  75. package/scripts/cleanup-old-sessions.sh +57 -0
  76. package/scripts/codex-wrapper.sh +88 -0
  77. package/scripts/create-sandbox.sh +269 -0
  78. package/scripts/debug-linear-update.js +174 -0
  79. package/scripts/delete-linear-tasks.js +167 -0
  80. package/scripts/deploy.sh +89 -0
  81. package/scripts/deployment/railway.sh +352 -0
  82. package/scripts/deployment/test-deployment.js +194 -0
  83. package/scripts/detect-and-rehydrate.js +162 -0
  84. package/scripts/detect-and-rehydrate.mjs +165 -0
  85. package/scripts/development/create-demo-tasks.js +143 -0
  86. package/scripts/development/debug-frame-test.js +16 -0
  87. package/scripts/development/demo-auto-sync.js +128 -0
  88. package/scripts/development/fix-all-imports.js +213 -0
  89. package/scripts/development/fix-imports.js +229 -0
  90. package/scripts/development/fix-lint-loop.cjs +103 -0
  91. package/scripts/development/fix-project-id.ts +161 -0
  92. package/scripts/development/fix-strict-mode-issues.ts +291 -0
  93. package/scripts/development/reorganize-structure.sh +228 -0
  94. package/scripts/development/test-persistence-direct.js +148 -0
  95. package/scripts/development/test-persistence.js +114 -0
  96. package/scripts/development/test-tasks.js +93 -0
  97. package/scripts/development/update-imports.js +212 -0
  98. package/scripts/fetch-linear-status.js +125 -0
  99. package/scripts/git-hooks/README.md +310 -0
  100. package/scripts/git-hooks/branch-context-manager.sh +342 -0
  101. package/scripts/git-hooks/post-checkout-stackmemory.sh +63 -0
  102. package/scripts/git-hooks/post-commit-stackmemory.sh +305 -0
  103. package/scripts/git-hooks/pre-commit-stackmemory.sh +275 -0
  104. package/scripts/hooks/cleanup-shell.sh +130 -0
  105. package/scripts/hooks/task-complete.sh +114 -0
  106. package/scripts/initialize.ts +129 -0
  107. package/scripts/install-claude-hooks-auto.js +104 -0
  108. package/scripts/install-claude-hooks.sh +133 -0
  109. package/scripts/install-global.sh +296 -0
  110. package/scripts/install.sh +235 -0
  111. package/scripts/linear-auto-sync.js +262 -0
  112. package/scripts/linear-auto-sync.sh +161 -0
  113. package/scripts/linear-sync-daemon.js +150 -0
  114. package/scripts/linear-task-review.js +237 -0
  115. package/scripts/list-linear-tasks.ts +178 -0
  116. package/scripts/mcp-proxy.js +66 -0
  117. package/scripts/opencode-wrapper.sh +85 -0
  118. package/scripts/publish-local.js +74 -0
  119. package/scripts/query-chromadb.ts +201 -0
  120. package/scripts/railway-env-setup.sh +39 -0
  121. package/scripts/reconcile-local-tasks.js +170 -0
  122. package/scripts/recreate-frames-db.js +89 -0
  123. package/scripts/setup/claude-integration.js +138 -0
  124. package/scripts/setup/configure-alias.js +125 -0
  125. package/scripts/setup/configure-codex-alias.js +161 -0
  126. package/scripts/setup/configure-opencode-alias.js +175 -0
  127. package/scripts/setup-claude-integration.js +204 -0
  128. package/scripts/setup-claude-integration.sh +183 -0
  129. package/scripts/setup.sh +31 -0
  130. package/scripts/show-linear-summary.ts +172 -0
  131. package/scripts/stackmemory-auto-handoff.sh +231 -0
  132. package/scripts/stackmemory-daemon.sh +40 -0
  133. package/scripts/start-linear-sync-daemon.sh +141 -0
  134. package/scripts/start-temporal-paradox.sh +214 -0
  135. package/scripts/status.ts +159 -0
  136. package/scripts/sync-and-clean-tasks.js +258 -0
  137. package/scripts/sync-frames-from-railway.js +228 -0
  138. package/scripts/sync-linear-graphql.js +303 -0
  139. package/scripts/sync-linear-tasks.js +186 -0
  140. package/scripts/test-auto-triggers.sh +57 -0
  141. package/scripts/test-browser-mcp.js +74 -0
  142. package/scripts/test-chromadb-full.js +115 -0
  143. package/scripts/test-chromadb-hooks.sh +28 -0
  144. package/scripts/test-chromadb-sync.ts +245 -0
  145. package/scripts/test-cli-security.js +293 -0
  146. package/scripts/test-hooks-persistence.sh +220 -0
  147. package/scripts/test-installation-scenarios.sh +359 -0
  148. package/scripts/test-installation.sh +224 -0
  149. package/scripts/test-mcp.js +163 -0
  150. package/scripts/test-pre-publish-quick.sh +75 -0
  151. package/scripts/test-quality-gates.sh +263 -0
  152. package/scripts/test-railway-db.js +222 -0
  153. package/scripts/test-redis-storage.ts +490 -0
  154. package/scripts/test-rlm-basic.sh +122 -0
  155. package/scripts/test-rlm-comprehensive.sh +260 -0
  156. package/scripts/test-rlm-e2e.sh +268 -0
  157. package/scripts/test-rlm-simple.js +90 -0
  158. package/scripts/test-rlm.js +110 -0
  159. package/scripts/test-session-handoff.sh +165 -0
  160. package/scripts/test-shell-integration.sh +275 -0
  161. package/scripts/testing/ab-test-runner.ts +508 -0
  162. package/scripts/testing/collect-metrics.ts +457 -0
  163. package/scripts/testing/quick-effectiveness-demo.js +187 -0
  164. package/scripts/testing/real-performance-test.js +422 -0
  165. package/scripts/testing/run-effectiveness-tests.sh +176 -0
  166. package/scripts/testing/scripts/testing/ab-test-runner.js +363 -0
  167. package/scripts/testing/scripts/testing/collect-metrics.js +292 -0
  168. package/scripts/testing/simple-effectiveness-test.js +310 -0
  169. package/scripts/testing/src/core/context/context-bridge.js +253 -0
  170. package/scripts/testing/src/core/context/frame-manager.js +746 -0
  171. package/scripts/testing/src/core/context/shared-context-layer.js +437 -0
  172. package/scripts/testing/src/core/database/database-adapter.js +54 -0
  173. package/scripts/testing/src/core/errors/index.js +291 -0
  174. package/scripts/testing/src/core/errors/recovery.js +268 -0
  175. package/scripts/testing/src/core/monitoring/logger.js +145 -0
  176. package/scripts/testing/src/core/retrieval/context-retriever.js +516 -0
  177. package/scripts/testing/src/core/session/index.js +1 -0
  178. package/scripts/testing/src/core/session/session-manager.js +323 -0
  179. package/scripts/testing/src/core/trace/cli-trace-wrapper.js +140 -0
  180. package/scripts/testing/src/core/trace/db-trace-wrapper.js +251 -0
  181. package/scripts/testing/src/core/trace/debug-trace.js +398 -0
  182. package/scripts/testing/src/core/trace/index.js +120 -0
  183. package/scripts/testing/src/core/trace/linear-api-wrapper.js +204 -0
  184. package/scripts/update-linear-status.js +268 -0
  185. package/scripts/update-linear-tasks-fixed.js +284 -0
  186. package/templates/claude-hooks/hooks.json +5 -0
  187. package/templates/claude-hooks/on-clear.js +56 -0
  188. package/templates/claude-hooks/on-startup.js +56 -0
  189. package/templates/claude-hooks/tool-use-trace.js +67 -0
  190. package/dist/features/tui/components/analytics-panel.js +0 -157
  191. package/dist/features/tui/components/analytics-panel.js.map +0 -7
  192. package/dist/features/tui/components/frame-visualizer.js +0 -377
  193. package/dist/features/tui/components/frame-visualizer.js.map +0 -7
  194. package/dist/features/tui/components/pr-tracker.js +0 -135
  195. package/dist/features/tui/components/pr-tracker.js.map +0 -7
  196. package/dist/features/tui/components/session-monitor.js +0 -299
  197. package/dist/features/tui/components/session-monitor.js.map +0 -7
  198. package/dist/features/tui/components/subagent-fleet.js +0 -395
  199. package/dist/features/tui/components/subagent-fleet.js.map +0 -7
  200. package/dist/features/tui/components/task-board.js +0 -1139
  201. package/dist/features/tui/components/task-board.js.map +0 -7
  202. package/dist/features/tui/index.js +0 -408
  203. package/dist/features/tui/index.js.map +0 -7
  204. package/dist/features/tui/services/data-service.js +0 -641
  205. package/dist/features/tui/services/data-service.js.map +0 -7
  206. package/dist/features/tui/services/linear-task-reader.js +0 -102
  207. package/dist/features/tui/services/linear-task-reader.js.map +0 -7
  208. package/dist/features/tui/services/websocket-client.js +0 -162
  209. package/dist/features/tui/services/websocket-client.js.map +0 -7
  210. package/dist/features/tui/terminal-compat.js +0 -220
  211. package/dist/features/tui/terminal-compat.js.map +0 -7
  212. package/dist/features/tui/types.js +0 -1
  213. package/dist/features/tui/types.js.map +0 -7
@@ -0,0 +1,416 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * StackMemory Background Sync Manager
5
+ * Handles all background synchronization tasks:
6
+ * - Linear task sync
7
+ * - Frame and context backup
8
+ * - Cross-session sync
9
+ * - Cloud backup (S3/GCS)
10
+ * - Redis cache sync
11
+ */
12
+
13
+ import fs from 'fs';
14
+ import path from 'path';
15
+ import { fileURLToPath } from 'url';
16
+ import dotenv from 'dotenv';
17
+ import { exec } from 'child_process';
18
+ import { promisify } from 'util';
19
+
20
+ const execAsync = promisify(exec);
21
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
22
+
23
+ // Load environment variables from .env file (check .env first as per user preference)
24
+ dotenv.config({
25
+ path: path.join(__dirname, '..', '.env'),
26
+ override: true,
27
+ silent: true
28
+ });
29
+
30
+ // Sync intervals (in milliseconds)
31
+ const SYNC_INTERVALS = {
32
+ linear: 60 * 60 * 1000, // 1 hour - Linear task sync
33
+ context: 15 * 60 * 1000, // 15 minutes - Context and frame sync
34
+ backup: 4 * 60 * 60 * 1000, // 4 hours - Cloud backup
35
+ redis: 5 * 60 * 1000, // 5 minutes - Redis cache sync
36
+ crossSession: 10 * 60 * 1000, // 10 minutes - Cross-session sync
37
+ };
38
+
39
+ class BackgroundSyncManager {
40
+ constructor() {
41
+ this.syncTasks = new Map();
42
+ this.stats = {
43
+ linear: { count: 0, lastSync: null, errors: 0 },
44
+ context: { count: 0, lastSync: null, errors: 0 },
45
+ backup: { count: 0, lastSync: null, errors: 0 },
46
+ redis: { count: 0, lastSync: null, errors: 0 },
47
+ crossSession: { count: 0, lastSync: null, errors: 0 }
48
+ };
49
+ this.logFile = path.join(__dirname, '..', '.stackmemory', 'sync-manager.log');
50
+ this.isRunning = false;
51
+ }
52
+
53
+ log(message, level = 'INFO') {
54
+ const timestamp = new Date().toISOString();
55
+ const logMessage = `[${timestamp}] [${level}] ${message}\n`;
56
+
57
+ console.log(logMessage.trim());
58
+
59
+ try {
60
+ fs.appendFileSync(this.logFile, logMessage);
61
+ } catch (error) {
62
+ console.error('Failed to write to log file:', error.message);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Sync Linear tasks
68
+ */
69
+ async syncLinear() {
70
+ if (!process.env.LINEAR_API_KEY) {
71
+ this.log('Linear sync skipped - no API key', 'WARN');
72
+ return;
73
+ }
74
+
75
+ this.log('Starting Linear sync...');
76
+ try {
77
+ const { stdout, stderr } = await execAsync(
78
+ `node ${path.join(__dirname, 'sync-linear-graphql.js')}`
79
+ );
80
+
81
+ // Parse output for summary
82
+ const addedMatch = stdout.match(/Added to local: (\d+)/);
83
+ const added = addedMatch ? addedMatch[1] : '0';
84
+
85
+ this.stats.linear.count++;
86
+ this.stats.linear.lastSync = new Date();
87
+ this.log(`Linear sync completed - added ${added} tasks`);
88
+ } catch (error) {
89
+ this.stats.linear.errors++;
90
+ this.log(`Linear sync failed: ${error.message}`, 'ERROR');
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Sync context and frames
96
+ */
97
+ async syncContext() {
98
+ this.log('Starting context sync...');
99
+ try {
100
+ const homeDir = process.env.HOME;
101
+ const stackMemoryDir = path.join(homeDir, '.stackmemory');
102
+
103
+ // Get all session directories
104
+ const sessionsDir = path.join(stackMemoryDir, 'sessions');
105
+ const sharedContextDir = path.join(stackMemoryDir, 'shared-context');
106
+
107
+ // Count items to sync
108
+ let sessionCount = 0;
109
+ let contextCount = 0;
110
+
111
+ if (fs.existsSync(sessionsDir)) {
112
+ sessionCount = fs.readdirSync(sessionsDir).length;
113
+ }
114
+
115
+ if (fs.existsSync(sharedContextDir)) {
116
+ const projects = fs.readdirSync(path.join(sharedContextDir, 'projects')).filter(f => f.endsWith('.json'));
117
+ contextCount = projects.length;
118
+
119
+ // Consolidate shared contexts
120
+ for (const projectFile of projects) {
121
+ const projectPath = path.join(sharedContextDir, 'projects', projectFile);
122
+ const data = JSON.parse(fs.readFileSync(projectPath, 'utf8'));
123
+
124
+ // Remove old/stale entries (older than 30 days)
125
+ const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);
126
+ if (data.contexts) {
127
+ data.contexts = data.contexts.filter(c =>
128
+ c.timestamp && c.timestamp > thirtyDaysAgo
129
+ );
130
+ fs.writeFileSync(projectPath, JSON.stringify(data, null, 2));
131
+ }
132
+ }
133
+ }
134
+
135
+ this.stats.context.count++;
136
+ this.stats.context.lastSync = new Date();
137
+ this.log(`Context sync completed - ${sessionCount} sessions, ${contextCount} shared contexts`);
138
+ } catch (error) {
139
+ this.stats.context.errors++;
140
+ this.log(`Context sync failed: ${error.message}`, 'ERROR');
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Backup to cloud storage (S3/GCS)
146
+ */
147
+ async syncBackup() {
148
+ this.log('Starting cloud backup...');
149
+ try {
150
+ const homeDir = process.env.HOME;
151
+ const stackMemoryDir = path.join(homeDir, '.stackmemory');
152
+ const backupDir = path.join(__dirname, '..', 'backups');
153
+
154
+ // Create backup directory
155
+ if (!fs.existsSync(backupDir)) {
156
+ fs.mkdirSync(backupDir, { recursive: true });
157
+ }
158
+
159
+ // Create timestamp for backup
160
+ const timestamp = new Date().toISOString().replace(/:/g, '-').split('.')[0];
161
+ const backupFile = path.join(backupDir, `stackmemory-backup-${timestamp}.tar.gz`);
162
+
163
+ // Create tar archive of important data
164
+ const { stdout, stderr } = await execAsync(
165
+ `tar -czf ${backupFile} -C ${homeDir} .stackmemory/sessions .stackmemory/shared-context .stackmemory/projects.db 2>/dev/null || true`
166
+ );
167
+
168
+ const stats = fs.statSync(backupFile);
169
+ const sizeMB = (stats.size / (1024 * 1024)).toFixed(2);
170
+
171
+ // Upload to cloud if configured
172
+ if (process.env.AWS_S3_BUCKET) {
173
+ await execAsync(
174
+ `aws s3 cp ${backupFile} s3://${process.env.AWS_S3_BUCKET}/stackmemory-backups/ --storage-class GLACIER_IR`
175
+ );
176
+ this.log(`Backup uploaded to S3: ${sizeMB}MB`);
177
+ } else if (process.env.GCS_BUCKET) {
178
+ await execAsync(
179
+ `gsutil cp ${backupFile} gs://${process.env.GCS_BUCKET}/stackmemory-backups/`
180
+ );
181
+ this.log(`Backup uploaded to GCS: ${sizeMB}MB`);
182
+ } else {
183
+ this.log(`Local backup created: ${sizeMB}MB (no cloud storage configured)`);
184
+ }
185
+
186
+ // Clean up old local backups (keep last 5)
187
+ const backups = fs.readdirSync(backupDir)
188
+ .filter(f => f.startsWith('stackmemory-backup-'))
189
+ .sort()
190
+ .reverse();
191
+
192
+ for (let i = 5; i < backups.length; i++) {
193
+ fs.unlinkSync(path.join(backupDir, backups[i]));
194
+ }
195
+
196
+ this.stats.backup.count++;
197
+ this.stats.backup.lastSync = new Date();
198
+ this.log(`Backup completed - ${sizeMB}MB`);
199
+ } catch (error) {
200
+ this.stats.backup.errors++;
201
+ this.log(`Backup failed: ${error.message}`, 'ERROR');
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Sync with Redis cache
207
+ */
208
+ async syncRedis() {
209
+ if (!process.env.REDIS_URL) {
210
+ return; // Skip if Redis not configured
211
+ }
212
+
213
+ this.log('Starting Redis sync...');
214
+ try {
215
+ // Import Redis client dynamically
216
+ const { default: Redis } = await import('ioredis');
217
+ const redis = new Redis(process.env.REDIS_URL);
218
+
219
+ // Sync recent frames to Redis for fast access
220
+ const homeDir = process.env.HOME;
221
+ const projectsDb = path.join(homeDir, '.stackmemory', 'projects.db');
222
+
223
+ if (fs.existsSync(projectsDb)) {
224
+ // Get recent frames from SQLite
225
+ const { stdout } = await execAsync(
226
+ `sqlite3 ${projectsDb} "SELECT frame_id, title, created_at FROM frames WHERE created_at > datetime('now', '-7 days') ORDER BY created_at DESC LIMIT 100"`
227
+ );
228
+
229
+ const frames = stdout.trim().split('\n').filter(Boolean);
230
+
231
+ // Store in Redis with expiry
232
+ for (const frame of frames) {
233
+ const [id, title, created] = frame.split('|');
234
+ await redis.setex(
235
+ `frame:${id}`,
236
+ 7 * 24 * 60 * 60, // 7 days TTL
237
+ JSON.stringify({ id, title, created })
238
+ );
239
+ }
240
+
241
+ this.log(`Redis sync completed - ${frames.length} frames cached`);
242
+ }
243
+
244
+ await redis.quit();
245
+
246
+ this.stats.redis.count++;
247
+ this.stats.redis.lastSync = new Date();
248
+ } catch (error) {
249
+ this.stats.redis.errors++;
250
+ this.log(`Redis sync failed: ${error.message}`, 'ERROR');
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Sync across sessions
256
+ */
257
+ async syncCrossSession() {
258
+ this.log('Starting cross-session sync...');
259
+ try {
260
+ const homeDir = process.env.HOME;
261
+ const sharedContextDir = path.join(homeDir, '.stackmemory', 'shared-context');
262
+ const projectsDir = path.join(sharedContextDir, 'projects');
263
+
264
+ if (!fs.existsSync(projectsDir)) {
265
+ fs.mkdirSync(projectsDir, { recursive: true });
266
+ }
267
+
268
+ // Get current project
269
+ const projectName = path.basename(process.cwd());
270
+ const projectFile = path.join(projectsDir, `${projectName}.json`);
271
+
272
+ let projectData = {
273
+ name: projectName,
274
+ sessions: [],
275
+ lastSync: null,
276
+ contexts: []
277
+ };
278
+
279
+ if (fs.existsSync(projectFile)) {
280
+ projectData = JSON.parse(fs.readFileSync(projectFile, 'utf8'));
281
+ }
282
+
283
+ // Update sync timestamp
284
+ projectData.lastSync = new Date().toISOString();
285
+
286
+ // Add current session info
287
+ const sessionId = process.env.STACKMEMORY_SESSION_ID || 'unknown';
288
+ const existingSession = projectData.sessions.find(s => s.id === sessionId);
289
+
290
+ if (existingSession) {
291
+ existingSession.lastActive = new Date().toISOString();
292
+ } else {
293
+ projectData.sessions.push({
294
+ id: sessionId,
295
+ startTime: new Date().toISOString(),
296
+ lastActive: new Date().toISOString()
297
+ });
298
+ }
299
+
300
+ // Keep only recent sessions (last 30 days)
301
+ const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
302
+ projectData.sessions = projectData.sessions.filter(s =>
303
+ new Date(s.lastActive) > thirtyDaysAgo
304
+ );
305
+
306
+ fs.writeFileSync(projectFile, JSON.stringify(projectData, null, 2));
307
+
308
+ this.stats.crossSession.count++;
309
+ this.stats.crossSession.lastSync = new Date();
310
+ this.log(`Cross-session sync completed - ${projectData.sessions.length} active sessions`);
311
+ } catch (error) {
312
+ this.stats.crossSession.errors++;
313
+ this.log(`Cross-session sync failed: ${error.message}`, 'ERROR');
314
+ }
315
+ }
316
+
317
+ /**
318
+ * Run all sync tasks
319
+ */
320
+ async runSync(taskName) {
321
+ switch (taskName) {
322
+ case 'linear':
323
+ await this.syncLinear();
324
+ break;
325
+ case 'context':
326
+ await this.syncContext();
327
+ break;
328
+ case 'backup':
329
+ await this.syncBackup();
330
+ break;
331
+ case 'redis':
332
+ await this.syncRedis();
333
+ break;
334
+ case 'crossSession':
335
+ await this.syncCrossSession();
336
+ break;
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Start the sync manager
342
+ */
343
+ async start() {
344
+ if (this.isRunning) {
345
+ this.log('Sync manager already running', 'WARN');
346
+ return;
347
+ }
348
+
349
+ this.isRunning = true;
350
+ this.log('🚀 StackMemory Background Sync Manager starting...');
351
+ this.log(`📅 Sync intervals:`);
352
+ this.log(` Linear: every ${SYNC_INTERVALS.linear / 60000} minutes`);
353
+ this.log(` Context: every ${SYNC_INTERVALS.context / 60000} minutes`);
354
+ this.log(` Backup: every ${SYNC_INTERVALS.backup / 3600000} hours`);
355
+ this.log(` Redis: every ${SYNC_INTERVALS.redis / 60000} minutes`);
356
+ this.log(` Cross-session: every ${SYNC_INTERVALS.crossSession / 60000} minutes`);
357
+
358
+ // Run initial sync for all tasks
359
+ await this.syncLinear();
360
+ await this.syncContext();
361
+ await this.syncCrossSession();
362
+
363
+ // Schedule recurring syncs
364
+ this.syncTasks.set('linear', setInterval(() => this.runSync('linear'), SYNC_INTERVALS.linear));
365
+ this.syncTasks.set('context', setInterval(() => this.runSync('context'), SYNC_INTERVALS.context));
366
+ this.syncTasks.set('backup', setInterval(() => this.runSync('backup'), SYNC_INTERVALS.backup));
367
+ this.syncTasks.set('redis', setInterval(() => this.runSync('redis'), SYNC_INTERVALS.redis));
368
+ this.syncTasks.set('crossSession', setInterval(() => this.runSync('crossSession'), SYNC_INTERVALS.crossSession));
369
+
370
+ this.log('⏰ All sync tasks scheduled');
371
+
372
+ // Handle graceful shutdown
373
+ process.on('SIGINT', () => this.stop());
374
+ process.on('SIGTERM', () => this.stop());
375
+ }
376
+
377
+ /**
378
+ * Stop the sync manager
379
+ */
380
+ stop() {
381
+ this.log('🛑 Stopping Background Sync Manager...');
382
+
383
+ // Clear all intervals
384
+ for (const [name, interval] of this.syncTasks) {
385
+ clearInterval(interval);
386
+ }
387
+ this.syncTasks.clear();
388
+
389
+ // Log final stats
390
+ this.log('📊 Final statistics:');
391
+ for (const [name, stats] of Object.entries(this.stats)) {
392
+ if (stats.count > 0) {
393
+ this.log(` ${name}: ${stats.count} syncs, ${stats.errors} errors`);
394
+ }
395
+ }
396
+
397
+ this.isRunning = false;
398
+ this.log('👋 Sync manager stopped');
399
+ process.exit(0);
400
+ }
401
+
402
+ /**
403
+ * Get status
404
+ */
405
+ getStatus() {
406
+ return {
407
+ running: this.isRunning,
408
+ stats: this.stats,
409
+ tasks: Array.from(this.syncTasks.keys())
410
+ };
411
+ }
412
+ }
413
+
414
+ // Start the manager
415
+ const manager = new BackgroundSyncManager();
416
+ manager.start();
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Run performance benchmarks
4
+ */
5
+
6
+ import Database from 'better-sqlite3';
7
+ import { join } from 'path';
8
+ import { existsSync } from 'fs';
9
+ import { PerformanceBenchmark } from '../src/core/performance/performance-benchmark.js';
10
+ import { logger } from '../src/core/monitoring/logger.js';
11
+
12
+ async function runBenchmarks() {
13
+ const projectRoot = process.cwd();
14
+ const dbPath = join(projectRoot, '.stackmemory', 'context.db');
15
+
16
+ if (!existsSync(dbPath)) {
17
+ console.error(
18
+ '❌ StackMemory not initialized. Run "stackmemory init" first.'
19
+ );
20
+ process.exit(1);
21
+ }
22
+
23
+ const db = new Database(dbPath, { readonly: false });
24
+ const projectId = 'benchmark-test';
25
+
26
+ console.log('🚀 Starting Performance Benchmarks...\n');
27
+
28
+ const benchmark = new PerformanceBenchmark();
29
+
30
+ try {
31
+ const suite = await benchmark.runFullSuite(projectRoot, db, projectId);
32
+
33
+ console.log('\n✅ Benchmarks completed successfully!');
34
+
35
+ // Summary
36
+ if (suite.averageImprovement > 0) {
37
+ console.log(
38
+ `\n🎉 Overall performance improved by ${suite.averageImprovement.toFixed(1)}%`
39
+ );
40
+ }
41
+
42
+ process.exit(0);
43
+ } catch (error: unknown) {
44
+ console.error('\n❌ Benchmark failed:', error);
45
+ logger.error('Benchmark error', error as Error);
46
+ process.exit(1);
47
+ } finally {
48
+ db.close();
49
+ }
50
+ }
51
+
52
+ // Run if called directly
53
+ if (import.meta.url === `file://${process.argv[1]}`) {
54
+ runBenchmarks().catch(console.error);
55
+ }
56
+
57
+ export { runBenchmarks };
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env tsx
2
+
3
+ import { createClient } from 'redis';
4
+ import dotenv from 'dotenv';
5
+ // Type-safe environment variable access
6
+ function getEnv(key: string, defaultValue?: string): string {
7
+ const value = process.env[key];
8
+ if (value === undefined) {
9
+ if (defaultValue !== undefined) return defaultValue;
10
+ throw new Error(`Environment variable ${key} is required`);
11
+ }
12
+ return value;
13
+ }
14
+
15
+ function getOptionalEnv(key: string): string | undefined {
16
+ return process.env[key];
17
+ }
18
+
19
+ dotenv.config();
20
+
21
+ async function checkRedis() {
22
+ const client = createClient({ url: process.env['REDIS_URL'] });
23
+ await client.connect();
24
+
25
+ const keys = await client.keys('trace:*');
26
+ console.log('Redis trace keys:', keys.length);
27
+
28
+ if (keys.length > 0) {
29
+ console.log('Sample keys:', keys.slice(0, 3));
30
+ // Using hGetAll since we store as hash
31
+ const sample = await client.hGetAll(keys[0]);
32
+ console.log('Sample trace fields:', Object.keys(sample));
33
+ console.log('Sample trace data size:', sample.data?.length || 0, 'bytes');
34
+ console.log(
35
+ 'Sample trace compressed:',
36
+ sample.compressed === 'true' ? 'yes' : 'no'
37
+ );
38
+ }
39
+
40
+ const scoreIndex = await client.zCard('traces:by_score');
41
+ const timeIndex = await client.zCard('traces:by_time');
42
+ console.log('Score index entries:', scoreIndex);
43
+ console.log('Time index entries:', timeIndex);
44
+
45
+ await client.quit();
46
+ }
47
+
48
+ checkRedis().catch(console.error);
@@ -0,0 +1,128 @@
1
+ #!/bin/bash
2
+
3
+ # ChromaDB Auto-Loader for Claude
4
+ # Automatically loads context from ChromaDB every 15 minutes
5
+ # and when Claude starts a new session
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
9
+ LOG_DIR="$HOME/.stackmemory/logs"
10
+ PID_FILE="$HOME/.stackmemory/chromadb-loader.pid"
11
+
12
+ # Create log directory
13
+ mkdir -p "$LOG_DIR"
14
+
15
+ # Function to load context
16
+ load_context() {
17
+ echo "[$(date)] Loading context from ChromaDB..."
18
+
19
+ # Load recent context
20
+ cd "$PROJECT_DIR"
21
+ node scripts/chromadb-context-loader.js load 1 >> "$LOG_DIR/chromadb-loader.log" 2>&1
22
+
23
+ # Track changes
24
+ node scripts/chromadb-context-loader.js changes >> "$LOG_DIR/chromadb-loader.log" 2>&1
25
+
26
+ # Sync with StackMemory
27
+ node scripts/chromadb-context-loader.js sync >> "$LOG_DIR/chromadb-loader.log" 2>&1
28
+
29
+ echo "[$(date)] Context loaded successfully"
30
+ }
31
+
32
+ # Function to start the loader daemon
33
+ start_daemon() {
34
+ # Check if already running
35
+ if [ -f "$PID_FILE" ]; then
36
+ OLD_PID=$(cat "$PID_FILE")
37
+ if kill -0 "$OLD_PID" 2>/dev/null; then
38
+ echo "ChromaDB loader already running (PID: $OLD_PID)"
39
+ exit 0
40
+ fi
41
+ fi
42
+
43
+ echo "Starting ChromaDB auto-loader daemon..."
44
+
45
+ # Initial load
46
+ load_context
47
+
48
+ # Start background loop
49
+ (
50
+ while true; do
51
+ # Wait 15 minutes
52
+ sleep 900
53
+
54
+ # Load context
55
+ load_context
56
+ done
57
+ ) &
58
+
59
+ # Save PID
60
+ echo $! > "$PID_FILE"
61
+ echo "ChromaDB auto-loader started (PID: $!)"
62
+ }
63
+
64
+ # Function to stop the daemon
65
+ stop_daemon() {
66
+ if [ -f "$PID_FILE" ]; then
67
+ PID=$(cat "$PID_FILE")
68
+ if kill "$PID" 2>/dev/null; then
69
+ echo "ChromaDB auto-loader stopped"
70
+ rm -f "$PID_FILE"
71
+ else
72
+ echo "ChromaDB auto-loader not running"
73
+ rm -f "$PID_FILE"
74
+ fi
75
+ else
76
+ echo "ChromaDB auto-loader not running"
77
+ fi
78
+ }
79
+
80
+ # Function to check status
81
+ check_status() {
82
+ if [ -f "$PID_FILE" ]; then
83
+ PID=$(cat "$PID_FILE")
84
+ if kill -0 "$PID" 2>/dev/null; then
85
+ echo "✅ ChromaDB auto-loader running (PID: $PID)"
86
+ echo ""
87
+ echo "Recent activity:"
88
+ tail -5 "$LOG_DIR/chromadb-loader.log" 2>/dev/null
89
+ else
90
+ echo "❌ ChromaDB auto-loader not running (stale PID file)"
91
+ rm -f "$PID_FILE"
92
+ fi
93
+ else
94
+ echo "❌ ChromaDB auto-loader not running"
95
+ fi
96
+ }
97
+
98
+ # Handle commands
99
+ case "$1" in
100
+ start)
101
+ start_daemon
102
+ ;;
103
+ stop)
104
+ stop_daemon
105
+ ;;
106
+ status)
107
+ check_status
108
+ ;;
109
+ load)
110
+ # Manual load
111
+ load_context
112
+ ;;
113
+ restart)
114
+ stop_daemon
115
+ sleep 1
116
+ start_daemon
117
+ ;;
118
+ *)
119
+ echo "Usage: $0 {start|stop|status|load|restart}"
120
+ echo ""
121
+ echo " start - Start auto-loader daemon (loads every 15 min)"
122
+ echo " stop - Stop auto-loader daemon"
123
+ echo " status - Check daemon status"
124
+ echo " load - Manually load context now"
125
+ echo " restart - Restart daemon"
126
+ exit 1
127
+ ;;
128
+ esac