@stackmemoryai/stackmemory 0.3.17 → 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.
- package/dist/cli/commands/skills.js +15 -2
- package/dist/cli/commands/skills.js.map +2 -2
- package/dist/cli/index.js +113 -834
- package/dist/cli/index.js.map +3 -3
- package/dist/core/context/dual-stack-manager.js +1 -1
- package/dist/core/context/dual-stack-manager.js.map +1 -1
- package/dist/core/context/frame-manager.js +3 -0
- package/dist/core/context/frame-manager.js.map +2 -2
- package/dist/integrations/claude-code/subagent-client.js +106 -3
- package/dist/integrations/claude-code/subagent-client.js.map +2 -2
- package/dist/servers/railway/config.js +51 -0
- package/dist/servers/railway/config.js.map +7 -0
- package/dist/servers/railway/index-enhanced.js +156 -0
- package/dist/servers/railway/index-enhanced.js.map +7 -0
- package/dist/servers/railway/minimal.js +48 -3
- package/dist/servers/railway/minimal.js.map +2 -2
- package/dist/servers/railway/storage-test.js +455 -0
- package/dist/servers/railway/storage-test.js.map +7 -0
- package/dist/skills/claude-skills.js +13 -12
- package/dist/skills/claude-skills.js.map +2 -2
- package/dist/skills/recursive-agent-orchestrator.js +27 -18
- package/dist/skills/recursive-agent-orchestrator.js.map +2 -2
- package/dist/skills/unified-rlm-orchestrator.js.map +2 -2
- package/package.json +6 -18
- package/scripts/README-TESTING.md +186 -0
- package/scripts/analyze-cli-security.js +288 -0
- package/scripts/archive/add-phase-tasks-to-linear.js +163 -0
- package/scripts/archive/analyze-linear-duplicates.js +214 -0
- package/scripts/archive/analyze-remaining-duplicates.js +230 -0
- package/scripts/archive/analyze-sta-duplicates.js +292 -0
- package/scripts/archive/analyze-sta-graphql.js +399 -0
- package/scripts/archive/cancel-duplicate-tasks.ts +246 -0
- package/scripts/archive/check-all-duplicates.ts +419 -0
- package/scripts/archive/clean-duplicate-tasks.js +114 -0
- package/scripts/archive/cleanup-duplicate-tasks.ts +286 -0
- package/scripts/archive/create-phase-tasks.js +387 -0
- package/scripts/archive/delete-linear-duplicates.js +182 -0
- package/scripts/archive/delete-remaining-duplicates.js +158 -0
- package/scripts/archive/delete-sta-duplicates.js +201 -0
- package/scripts/archive/delete-sta-oauth.js +201 -0
- package/scripts/archive/export-sta-tasks.js +62 -0
- package/scripts/archive/install-auto-sync.js +266 -0
- package/scripts/archive/install-chromadb-hooks.sh +133 -0
- package/scripts/archive/install-enhanced-clear-hooks.sh +431 -0
- package/scripts/archive/install-post-task-hooks.sh +289 -0
- package/scripts/archive/install-stackmemory-hooks.sh +420 -0
- package/scripts/archive/merge-linear-duplicates-safe.ts +362 -0
- package/scripts/archive/merge-linear-duplicates.ts +180 -0
- package/scripts/archive/remove-sta-tasks.js +70 -0
- package/scripts/archive/setup-background-sync.sh +168 -0
- package/scripts/archive/setup-claude-auto-triggers.sh +181 -0
- package/scripts/archive/setup-claude-autostart.sh +305 -0
- package/scripts/archive/setup-git-hooks.sh +25 -0
- package/scripts/archive/setup-linear-oauth.sh +46 -0
- package/scripts/archive/setup-mcp.sh +113 -0
- package/scripts/archive/setup-railway-deployment.sh +81 -0
- package/scripts/auto-handoff.sh +262 -0
- package/scripts/background-sync-manager.js +416 -0
- package/scripts/benchmark-performance.ts +57 -0
- package/scripts/check-redis.ts +48 -0
- package/scripts/chromadb-auto-loader.sh +128 -0
- package/scripts/chromadb-context-loader.js +479 -0
- package/scripts/claude-chromadb-hook.js +460 -0
- package/scripts/claude-code-wrapper.sh +66 -0
- package/scripts/claude-linear-skill.js +455 -0
- package/scripts/claude-pre-commit.sh +302 -0
- package/scripts/claude-sm-autostart.js +532 -0
- package/scripts/claude-sm-setup.sh +367 -0
- package/scripts/claude-with-chromadb.sh +69 -0
- package/scripts/claude-worktree-manager.sh +323 -0
- package/scripts/claude-worktree-monitor.sh +371 -0
- package/scripts/claude-worktree-setup.sh +327 -0
- package/scripts/clean-linear-backlog.js +273 -0
- package/scripts/cleanup-old-sessions.sh +57 -0
- package/scripts/codex-wrapper.sh +88 -0
- package/scripts/create-sandbox.sh +269 -0
- package/scripts/debug-linear-update.js +174 -0
- package/scripts/delete-linear-tasks.js +167 -0
- package/scripts/deploy.sh +89 -0
- package/scripts/deployment/railway.sh +352 -0
- package/scripts/deployment/test-deployment.js +194 -0
- package/scripts/detect-and-rehydrate.js +162 -0
- package/scripts/detect-and-rehydrate.mjs +165 -0
- package/scripts/development/create-demo-tasks.js +143 -0
- package/scripts/development/debug-frame-test.js +16 -0
- package/scripts/development/demo-auto-sync.js +128 -0
- package/scripts/development/fix-all-imports.js +213 -0
- package/scripts/development/fix-imports.js +229 -0
- package/scripts/development/fix-lint-loop.cjs +103 -0
- package/scripts/development/fix-project-id.ts +161 -0
- package/scripts/development/fix-strict-mode-issues.ts +291 -0
- package/scripts/development/reorganize-structure.sh +228 -0
- package/scripts/development/test-persistence-direct.js +148 -0
- package/scripts/development/test-persistence.js +114 -0
- package/scripts/development/test-tasks.js +93 -0
- package/scripts/development/update-imports.js +212 -0
- package/scripts/fetch-linear-status.js +125 -0
- package/scripts/git-hooks/README.md +310 -0
- package/scripts/git-hooks/branch-context-manager.sh +342 -0
- package/scripts/git-hooks/post-checkout-stackmemory.sh +63 -0
- package/scripts/git-hooks/post-commit-stackmemory.sh +305 -0
- package/scripts/git-hooks/pre-commit-stackmemory.sh +275 -0
- package/scripts/hooks/cleanup-shell.sh +130 -0
- package/scripts/hooks/task-complete.sh +114 -0
- package/scripts/initialize.ts +129 -0
- package/scripts/install-claude-hooks-auto.js +104 -0
- package/scripts/install-claude-hooks.sh +133 -0
- package/scripts/install-global.sh +296 -0
- package/scripts/install.sh +235 -0
- package/scripts/linear-auto-sync.js +262 -0
- package/scripts/linear-auto-sync.sh +161 -0
- package/scripts/linear-sync-daemon.js +150 -0
- package/scripts/linear-task-review.js +237 -0
- package/scripts/list-linear-tasks.ts +178 -0
- package/scripts/mcp-proxy.js +66 -0
- package/scripts/opencode-wrapper.sh +85 -0
- package/scripts/publish-local.js +74 -0
- package/scripts/query-chromadb.ts +201 -0
- package/scripts/railway-env-setup.sh +39 -0
- package/scripts/reconcile-local-tasks.js +170 -0
- package/scripts/recreate-frames-db.js +89 -0
- package/scripts/setup/claude-integration.js +138 -0
- package/scripts/setup/configure-alias.js +125 -0
- package/scripts/setup/configure-codex-alias.js +161 -0
- package/scripts/setup/configure-opencode-alias.js +175 -0
- package/scripts/setup-claude-integration.js +204 -0
- package/scripts/setup-claude-integration.sh +183 -0
- package/scripts/setup.sh +31 -0
- package/scripts/show-linear-summary.ts +172 -0
- package/scripts/stackmemory-auto-handoff.sh +231 -0
- package/scripts/stackmemory-daemon.sh +40 -0
- package/scripts/start-linear-sync-daemon.sh +141 -0
- package/scripts/start-temporal-paradox.sh +214 -0
- package/scripts/status.ts +159 -0
- package/scripts/sync-and-clean-tasks.js +258 -0
- package/scripts/sync-frames-from-railway.js +228 -0
- package/scripts/sync-linear-graphql.js +303 -0
- package/scripts/sync-linear-tasks.js +186 -0
- package/scripts/test-auto-triggers.sh +57 -0
- package/scripts/test-browser-mcp.js +74 -0
- package/scripts/test-chromadb-full.js +115 -0
- package/scripts/test-chromadb-hooks.sh +28 -0
- package/scripts/test-chromadb-sync.ts +245 -0
- package/scripts/test-cli-security.js +293 -0
- package/scripts/test-hooks-persistence.sh +220 -0
- package/scripts/test-installation-scenarios.sh +359 -0
- package/scripts/test-installation.sh +224 -0
- package/scripts/test-mcp.js +163 -0
- package/scripts/test-pre-publish-quick.sh +75 -0
- package/scripts/test-quality-gates.sh +263 -0
- package/scripts/test-railway-db.js +222 -0
- package/scripts/test-redis-storage.ts +490 -0
- package/scripts/test-rlm-basic.sh +122 -0
- package/scripts/test-rlm-comprehensive.sh +260 -0
- package/scripts/test-rlm-e2e.sh +268 -0
- package/scripts/test-rlm-simple.js +90 -0
- package/scripts/test-rlm.js +110 -0
- package/scripts/test-session-handoff.sh +165 -0
- package/scripts/test-shell-integration.sh +275 -0
- package/scripts/testing/ab-test-runner.ts +508 -0
- package/scripts/testing/collect-metrics.ts +457 -0
- package/scripts/testing/quick-effectiveness-demo.js +187 -0
- package/scripts/testing/real-performance-test.js +422 -0
- package/scripts/testing/run-effectiveness-tests.sh +176 -0
- package/scripts/testing/scripts/testing/ab-test-runner.js +363 -0
- package/scripts/testing/scripts/testing/collect-metrics.js +292 -0
- package/scripts/testing/simple-effectiveness-test.js +310 -0
- package/scripts/testing/src/core/context/context-bridge.js +253 -0
- package/scripts/testing/src/core/context/frame-manager.js +746 -0
- package/scripts/testing/src/core/context/shared-context-layer.js +437 -0
- package/scripts/testing/src/core/database/database-adapter.js +54 -0
- package/scripts/testing/src/core/errors/index.js +291 -0
- package/scripts/testing/src/core/errors/recovery.js +268 -0
- package/scripts/testing/src/core/monitoring/logger.js +145 -0
- package/scripts/testing/src/core/retrieval/context-retriever.js +516 -0
- package/scripts/testing/src/core/session/index.js +1 -0
- package/scripts/testing/src/core/session/session-manager.js +323 -0
- package/scripts/testing/src/core/trace/cli-trace-wrapper.js +140 -0
- package/scripts/testing/src/core/trace/db-trace-wrapper.js +251 -0
- package/scripts/testing/src/core/trace/debug-trace.js +398 -0
- package/scripts/testing/src/core/trace/index.js +120 -0
- package/scripts/testing/src/core/trace/linear-api-wrapper.js +204 -0
- package/scripts/update-linear-status.js +268 -0
- package/scripts/update-linear-tasks-fixed.js +284 -0
- package/templates/claude-hooks/hooks.json +5 -0
- package/templates/claude-hooks/on-clear.js +56 -0
- package/templates/claude-hooks/on-startup.js +56 -0
- package/templates/claude-hooks/tool-use-trace.js +67 -0
- package/dist/features/tui/components/analytics-panel.js +0 -157
- package/dist/features/tui/components/analytics-panel.js.map +0 -7
- package/dist/features/tui/components/frame-visualizer.js +0 -377
- package/dist/features/tui/components/frame-visualizer.js.map +0 -7
- package/dist/features/tui/components/pr-tracker.js +0 -135
- package/dist/features/tui/components/pr-tracker.js.map +0 -7
- package/dist/features/tui/components/session-monitor.js +0 -299
- package/dist/features/tui/components/session-monitor.js.map +0 -7
- package/dist/features/tui/components/subagent-fleet.js +0 -395
- package/dist/features/tui/components/subagent-fleet.js.map +0 -7
- package/dist/features/tui/components/task-board.js +0 -1139
- package/dist/features/tui/components/task-board.js.map +0 -7
- package/dist/features/tui/index.js +0 -408
- package/dist/features/tui/index.js.map +0 -7
- package/dist/features/tui/services/data-service.js +0 -641
- package/dist/features/tui/services/data-service.js.map +0 -7
- package/dist/features/tui/services/linear-task-reader.js +0 -102
- package/dist/features/tui/services/linear-task-reader.js.map +0 -7
- package/dist/features/tui/services/websocket-client.js +0 -162
- package/dist/features/tui/services/websocket-client.js.map +0 -7
- package/dist/features/tui/terminal-compat.js +0 -220
- package/dist/features/tui/terminal-compat.js.map +0 -7
- package/dist/features/tui/types.js +0 -1
- 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
|