@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,258 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Sync Linear tasks with local storage and clean up duplicates
5
+ */
6
+
7
+ import 'dotenv/config';
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import { fileURLToPath } from 'url';
11
+
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
+ const API_KEY = process.env.LINEAR_OAUTH_TOKEN || process.env.LINEAR_API_KEY;
14
+ if (!API_KEY) {
15
+ console.error('āŒ LINEAR_OAUTH_TOKEN or LINEAR_API_KEY environment variable not set');
16
+ console.log('Please set LINEAR_OAUTH_TOKEN or LINEAR_API_KEY in your .env file or export it in your shell');
17
+ process.exit(1);
18
+ }
19
+
20
+ async function fetchAllLinearTasks() {
21
+ console.log('šŸ“„ Fetching all tasks from Linear...');
22
+
23
+ const query = `
24
+ query GetAllIssues($after: String) {
25
+ issues(first: 100, after: $after, includeArchived: false) {
26
+ nodes {
27
+ id
28
+ identifier
29
+ title
30
+ description
31
+ state {
32
+ id
33
+ name
34
+ type
35
+ }
36
+ priority
37
+ estimate
38
+ createdAt
39
+ updatedAt
40
+ }
41
+ pageInfo {
42
+ hasNextPage
43
+ endCursor
44
+ }
45
+ }
46
+ }
47
+ `;
48
+
49
+ let allIssues = [];
50
+ let hasNextPage = true;
51
+ let cursor = null;
52
+
53
+ while (hasNextPage) {
54
+ const response = await fetch('https://api.linear.app/graphql', {
55
+ method: 'POST',
56
+ headers: {
57
+ 'Authorization': `Bearer ${API_KEY}`,
58
+ 'Content-Type': 'application/json'
59
+ },
60
+ body: JSON.stringify({
61
+ query,
62
+ variables: { after: cursor }
63
+ })
64
+ });
65
+
66
+ const result = await response.json();
67
+
68
+ if (result.errors) {
69
+ throw new Error(result.errors[0].message);
70
+ }
71
+
72
+ allIssues = allIssues.concat(result.data.issues.nodes);
73
+ hasNextPage = result.data.issues.pageInfo.hasNextPage;
74
+ cursor = result.data.issues.pageInfo.endCursor;
75
+ }
76
+
77
+ console.log(` Found ${allIssues.length} Linear tasks\n`);
78
+ return allIssues;
79
+ }
80
+
81
+ function readLocalTasks() {
82
+ const tasksFile = path.join(process.cwd(), '.stackmemory', 'tasks.jsonl');
83
+
84
+ if (!fs.existsSync(tasksFile)) {
85
+ return [];
86
+ }
87
+
88
+ const lines = fs.readFileSync(tasksFile, 'utf8').split('\n').filter(Boolean);
89
+ return lines.map(line => {
90
+ try {
91
+ return JSON.parse(line);
92
+ } catch {
93
+ return null;
94
+ }
95
+ }).filter(Boolean);
96
+ }
97
+
98
+ function writeLocalTasks(tasks) {
99
+ const tasksFile = path.join(process.cwd(), '.stackmemory', 'tasks.jsonl');
100
+ const backupFile = `${tasksFile}.backup-${Date.now()}`;
101
+
102
+ // Backup current file
103
+ if (fs.existsSync(tasksFile)) {
104
+ fs.copyFileSync(tasksFile, backupFile);
105
+ console.log(` Backed up to: ${path.basename(backupFile)}`);
106
+ }
107
+
108
+ // Write new tasks
109
+ const content = tasks.map(t => JSON.stringify(t)).join('\n');
110
+ fs.writeFileSync(tasksFile, content + '\n');
111
+ }
112
+
113
+ async function syncAndCleanTasks() {
114
+ console.log('šŸ”„ Syncing and cleaning local tasks with Linear...\n');
115
+
116
+ try {
117
+ // Fetch Linear tasks
118
+ const linearTasks = await fetchAllLinearTasks();
119
+ const linearMap = new Map(linearTasks.map(t => [t.identifier, t]));
120
+
121
+ // Read local tasks
122
+ console.log('šŸ“‚ Reading local tasks...');
123
+ const localTasks = readLocalTasks();
124
+ console.log(` Found ${localTasks.length} local tasks\n`);
125
+
126
+ // Build new clean task list from Linear
127
+ const cleanTasks = [];
128
+ const taskIdMap = new Map(); // To track duplicates
129
+
130
+ for (const linearTask of linearTasks) {
131
+ // Skip if we already have this task ID
132
+ if (taskIdMap.has(linearTask.identifier)) {
133
+ console.log(` Skipping duplicate: ${linearTask.identifier}`);
134
+ continue;
135
+ }
136
+
137
+ // Find matching local task for metadata
138
+ const localTask = localTasks.find(t =>
139
+ t.linearId === linearTask.identifier ||
140
+ t.taskId === linearTask.identifier ||
141
+ t.id === linearTask.identifier
142
+ );
143
+
144
+ // Create clean task entry
145
+ const cleanTask = {
146
+ id: linearTask.identifier,
147
+ taskId: linearTask.identifier,
148
+ linearId: linearTask.identifier,
149
+ title: linearTask.title,
150
+ description: linearTask.description || '',
151
+ status: mapLinearStatus(linearTask.state),
152
+ priority: linearTask.priority || 4,
153
+ createdAt: linearTask.createdAt,
154
+ updatedAt: linearTask.updatedAt,
155
+ linearState: linearTask.state.name,
156
+ linearStateType: linearTask.state.type,
157
+ // Preserve local metadata if it exists
158
+ ...(localTask ? {
159
+ localContext: localTask.localContext,
160
+ gitBranch: localTask.gitBranch,
161
+ files: localTask.files,
162
+ completedAt: localTask.completedAt
163
+ } : {})
164
+ };
165
+
166
+ cleanTasks.push(cleanTask);
167
+ taskIdMap.set(linearTask.identifier, true);
168
+ }
169
+
170
+ console.log('šŸ“Š Sync Results:');
171
+ console.log(` Linear tasks: ${linearTasks.length}`);
172
+ console.log(` Local tasks (before): ${localTasks.length}`);
173
+ console.log(` Clean tasks (after): ${cleanTasks.length}`);
174
+ console.log(` Removed duplicates: ${localTasks.length - cleanTasks.length}\n`);
175
+
176
+ // Show what's being removed
177
+ const removedTasks = localTasks.filter(t =>
178
+ !linearMap.has(t.linearId) &&
179
+ !linearMap.has(t.taskId) &&
180
+ !linearMap.has(t.id)
181
+ );
182
+
183
+ if (removedTasks.length > 0) {
184
+ console.log('šŸ—‘ļø Removing local-only tasks (not in Linear):');
185
+ removedTasks.slice(0, 10).forEach(t => {
186
+ console.log(` - ${t.taskId || t.id}: ${(t.title || '').substring(0, 50)}...`);
187
+ });
188
+ if (removedTasks.length > 10) {
189
+ console.log(` ... and ${removedTasks.length - 10} more\n`);
190
+ }
191
+ }
192
+
193
+ // Write clean tasks
194
+ console.log('šŸ’¾ Writing clean task list...');
195
+ writeLocalTasks(cleanTasks);
196
+
197
+ // Update Linear mappings
198
+ const mappingsFile = path.join(process.cwd(), '.stackmemory', 'linear-mappings.json');
199
+ const mappings = {};
200
+
201
+ for (const task of cleanTasks) {
202
+ mappings[task.linearId] = {
203
+ linearId: task.linearId,
204
+ localId: task.id,
205
+ title: task.title,
206
+ state: task.linearState,
207
+ lastSync: new Date().toISOString()
208
+ };
209
+ }
210
+
211
+ fs.writeFileSync(mappingsFile, JSON.stringify(mappings, null, 2));
212
+ console.log(' Updated linear-mappings.json\n');
213
+
214
+ // Clean up old backup files
215
+ console.log('🧹 Cleaning up old backup files...');
216
+ const backupFiles = fs.readdirSync(path.join(process.cwd(), '.stackmemory'))
217
+ .filter(f => f.startsWith('tasks.jsonl.backup-'))
218
+ .sort()
219
+ .reverse();
220
+
221
+ // Keep only the 3 most recent backups
222
+ const toDelete = backupFiles.slice(3);
223
+ toDelete.forEach(file => {
224
+ fs.unlinkSync(path.join(process.cwd(), '.stackmemory', file));
225
+ console.log(` Deleted: ${file}`);
226
+ });
227
+
228
+ if (toDelete.length === 0) {
229
+ console.log(' No old backups to delete');
230
+ }
231
+
232
+ console.log('\nāœ… Sync and cleanup complete!');
233
+ console.log(` Active tasks: ${cleanTasks.filter(t => t.linearStateType !== 'completed' && t.linearStateType !== 'canceled').length}`);
234
+ console.log(` Completed tasks: ${cleanTasks.filter(t => t.linearStateType === 'completed').length}`);
235
+ console.log(` Total synced tasks: ${cleanTasks.length}`);
236
+
237
+ } catch (error) {
238
+ console.error('āŒ Sync failed:', error.message);
239
+ process.exit(1);
240
+ }
241
+ }
242
+
243
+ function mapLinearStatus(state) {
244
+ switch (state.type) {
245
+ case 'completed': return 'completed';
246
+ case 'started': return 'in_progress';
247
+ case 'canceled': return 'cancelled';
248
+ case 'backlog': return 'backlog';
249
+ default: return 'todo';
250
+ }
251
+ }
252
+
253
+ // Run if called directly
254
+ if (import.meta.url === `file://${process.argv[1]}`) {
255
+ syncAndCleanTasks().catch(console.error);
256
+ }
257
+
258
+ export { syncAndCleanTasks };
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';
3
+ import pg from 'pg';
4
+ import Database from 'better-sqlite3';
5
+ import { mkdirSync } from 'fs';
6
+ import { dirname, join } from 'path';
7
+ import { homedir } from 'os';
8
+
9
+ const { Client } = pg;
10
+
11
+ // Railway PostgreSQL connection
12
+ // Use environment variable or fallback to internal URL
13
+ const RAILWAY_DATABASE_URL = process.env.RAILWAY_DATABASE_URL ||
14
+ process.env.DATABASE_URL ||
15
+ 'postgresql://postgres:YTSFXqPzFhghOcefgwPvJyWOBTYHbYxd@postgres.railway.internal:5432/railway';
16
+
17
+ // Local SQLite database path
18
+ const dbPath = join(homedir(), '.stackmemory', 'context.db');
19
+ mkdirSync(dirname(dbPath), { recursive: true });
20
+
21
+ async function syncFramesFromRailway() {
22
+ console.log('šŸ”„ Starting sync from Railway database...\n');
23
+
24
+ // Connect to Railway PostgreSQL
25
+ const pgClient = new Client({
26
+ connectionString: RAILWAY_DATABASE_URL,
27
+ });
28
+
29
+ try {
30
+ await pgClient.connect();
31
+ console.log('āœ… Connected to Railway PostgreSQL database');
32
+
33
+ // Connect to local SQLite
34
+ const sqliteDb = new Database(dbPath);
35
+ console.log('āœ… Connected to local SQLite database\n');
36
+
37
+ // Check if frames table exists in PostgreSQL
38
+ const tableCheckQuery = `
39
+ SELECT EXISTS (
40
+ SELECT FROM information_schema.tables
41
+ WHERE table_name = 'frames'
42
+ );
43
+ `;
44
+
45
+ const tableExists = await pgClient.query(tableCheckQuery);
46
+
47
+ if (!tableExists.rows[0].exists) {
48
+ console.log('āš ļø No frames table found in Railway database');
49
+ console.log(' The Railway deployment may not have created frames yet.\n');
50
+
51
+ // Check for other relevant tables
52
+ const tablesQuery = `
53
+ SELECT table_name
54
+ FROM information_schema.tables
55
+ WHERE table_schema = 'public'
56
+ ORDER BY table_name;
57
+ `;
58
+
59
+ const tables = await pgClient.query(tablesQuery);
60
+ console.log('šŸ“Š Available tables in Railway database:');
61
+ tables.rows.forEach(row => {
62
+ console.log(` - ${row.table_name}`);
63
+ });
64
+
65
+ return;
66
+ }
67
+
68
+ // Fetch frames from Railway
69
+ const framesQuery = `
70
+ SELECT
71
+ frame_id,
72
+ run_id,
73
+ project_id,
74
+ parent_frame_id,
75
+ depth,
76
+ type,
77
+ name,
78
+ state,
79
+ inputs,
80
+ outputs,
81
+ digest_text,
82
+ digest_json,
83
+ created_at,
84
+ closed_at
85
+ FROM frames
86
+ ORDER BY created_at DESC
87
+ LIMIT 1000;
88
+ `;
89
+
90
+ const framesResult = await pgClient.query(framesQuery);
91
+ console.log(`šŸ“„ Found ${framesResult.rows.length} frames in Railway database\n`);
92
+
93
+ if (framesResult.rows.length === 0) {
94
+ console.log('ā„¹ļø No frames to sync. The Railway database is empty.');
95
+ return;
96
+ }
97
+
98
+ // Prepare SQLite insert statement
99
+ const insertStmt = sqliteDb.prepare(`
100
+ INSERT OR REPLACE INTO frames (
101
+ frame_id, run_id, project_id, parent_frame_id, depth,
102
+ type, name, state, inputs, outputs, digest_text, digest_json,
103
+ created_at, closed_at
104
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
105
+ `);
106
+
107
+ // Begin transaction for bulk insert
108
+ const insertMany = sqliteDb.transaction((frames) => {
109
+ for (const frame of frames) {
110
+ insertStmt.run(
111
+ frame.frame_id,
112
+ frame.run_id,
113
+ frame.project_id,
114
+ frame.parent_frame_id,
115
+ frame.depth,
116
+ frame.type,
117
+ frame.name,
118
+ frame.state,
119
+ typeof frame.inputs === 'object' ? JSON.stringify(frame.inputs) : frame.inputs,
120
+ typeof frame.outputs === 'object' ? JSON.stringify(frame.outputs) : frame.outputs,
121
+ frame.digest_text,
122
+ typeof frame.digest_json === 'object' ? JSON.stringify(frame.digest_json) : frame.digest_json,
123
+ frame.created_at ? new Date(frame.created_at).getTime() : Date.now(),
124
+ frame.closed_at ? new Date(frame.closed_at).getTime() : null
125
+ );
126
+ }
127
+ });
128
+
129
+ // Execute bulk insert
130
+ insertMany(framesResult.rows);
131
+ console.log(`āœ… Synced ${framesResult.rows.length} frames to local database\n`);
132
+
133
+ // Also sync events if they exist
134
+ const eventsCheckQuery = `
135
+ SELECT EXISTS (
136
+ SELECT FROM information_schema.tables
137
+ WHERE table_name = 'events'
138
+ );
139
+ `;
140
+
141
+ const eventsExist = await pgClient.query(eventsCheckQuery);
142
+
143
+ if (eventsExist.rows[0].exists) {
144
+ const eventsQuery = `
145
+ SELECT
146
+ event_id,
147
+ run_id,
148
+ frame_id,
149
+ seq,
150
+ event_type,
151
+ payload,
152
+ ts
153
+ FROM events
154
+ ORDER BY ts DESC
155
+ LIMIT 5000;
156
+ `;
157
+
158
+ const eventsResult = await pgClient.query(eventsQuery);
159
+
160
+ if (eventsResult.rows.length > 0) {
161
+ console.log(`šŸ“„ Found ${eventsResult.rows.length} events in Railway database`);
162
+
163
+ const eventInsertStmt = sqliteDb.prepare(`
164
+ INSERT OR REPLACE INTO events (
165
+ event_id, run_id, frame_id, seq, event_type, payload, ts
166
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
167
+ `);
168
+
169
+ const insertEvents = sqliteDb.transaction((events) => {
170
+ for (const event of events) {
171
+ eventInsertStmt.run(
172
+ event.event_id,
173
+ event.run_id,
174
+ event.frame_id,
175
+ event.seq,
176
+ event.event_type,
177
+ typeof event.payload === 'object' ? JSON.stringify(event.payload) : event.payload,
178
+ event.ts ? new Date(event.ts).getTime() : Date.now()
179
+ );
180
+ }
181
+ });
182
+
183
+ insertEvents(eventsResult.rows);
184
+ console.log(`āœ… Synced ${eventsResult.rows.length} events to local database\n`);
185
+ }
186
+ }
187
+
188
+ // Verify the sync
189
+ const frameCount = sqliteDb.prepare('SELECT COUNT(*) as count FROM frames').get();
190
+ const eventCount = sqliteDb.prepare('SELECT COUNT(*) as count FROM events').get();
191
+
192
+ console.log('šŸ“Š Local database statistics:');
193
+ console.log(` - Frames: ${frameCount.count}`);
194
+ console.log(` - Events: ${eventCount.count}`);
195
+
196
+ // Show recent frames
197
+ const recentFrames = sqliteDb.prepare(`
198
+ SELECT frame_id, name, type, state, datetime(created_at/1000, 'unixepoch') as created
199
+ FROM frames
200
+ ORDER BY created_at DESC
201
+ LIMIT 5
202
+ `).all();
203
+
204
+ if (recentFrames.length > 0) {
205
+ console.log('\nšŸ• Recent frames:');
206
+ recentFrames.forEach(frame => {
207
+ console.log(` - ${frame.name} (${frame.type}) - ${frame.state} - ${frame.created}`);
208
+ });
209
+ }
210
+
211
+ sqliteDb.close();
212
+
213
+ } catch (error) {
214
+ console.error('āŒ Error syncing frames:', error.message);
215
+
216
+ // If connection failed due to internal network, try external URL
217
+ if (error.message.includes('ENOTFOUND') || error.message.includes('postgres.railway.internal')) {
218
+ console.log('\nšŸ”„ Retrying with external Railway database URL...');
219
+ console.log(' Note: You may need to get the external DATABASE_URL from Railway dashboard.');
220
+ console.log(' Run: railway variables --json | jq -r .DATABASE_URL');
221
+ }
222
+ } finally {
223
+ await pgClient.end();
224
+ }
225
+ }
226
+
227
+ // Run the sync
228
+ syncFramesFromRailway().catch(console.error);