@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.
Files changed (212) hide show
  1. package/dist/cli/commands/skills.js +15 -2
  2. package/dist/cli/commands/skills.js.map +2 -2
  3. package/dist/cli/index.js +113 -834
  4. package/dist/cli/index.js.map +3 -3
  5. package/dist/core/context/dual-stack-manager.js +1 -1
  6. package/dist/core/context/dual-stack-manager.js.map +1 -1
  7. package/dist/core/context/frame-manager.js +3 -0
  8. package/dist/core/context/frame-manager.js.map +2 -2
  9. package/dist/integrations/claude-code/subagent-client.js +106 -3
  10. package/dist/integrations/claude-code/subagent-client.js.map +2 -2
  11. package/dist/servers/railway/config.js +51 -0
  12. package/dist/servers/railway/config.js.map +7 -0
  13. package/dist/servers/railway/index-enhanced.js +156 -0
  14. package/dist/servers/railway/index-enhanced.js.map +7 -0
  15. package/dist/servers/railway/minimal.js +48 -3
  16. package/dist/servers/railway/minimal.js.map +2 -2
  17. package/dist/servers/railway/storage-test.js +455 -0
  18. package/dist/servers/railway/storage-test.js.map +7 -0
  19. package/dist/skills/claude-skills.js +13 -12
  20. package/dist/skills/claude-skills.js.map +2 -2
  21. package/dist/skills/recursive-agent-orchestrator.js +27 -18
  22. package/dist/skills/recursive-agent-orchestrator.js.map +2 -2
  23. package/dist/skills/unified-rlm-orchestrator.js.map +2 -2
  24. package/package.json +6 -18
  25. package/scripts/README-TESTING.md +186 -0
  26. package/scripts/analyze-cli-security.js +288 -0
  27. package/scripts/archive/add-phase-tasks-to-linear.js +163 -0
  28. package/scripts/archive/analyze-linear-duplicates.js +214 -0
  29. package/scripts/archive/analyze-remaining-duplicates.js +230 -0
  30. package/scripts/archive/analyze-sta-duplicates.js +292 -0
  31. package/scripts/archive/analyze-sta-graphql.js +399 -0
  32. package/scripts/archive/cancel-duplicate-tasks.ts +246 -0
  33. package/scripts/archive/check-all-duplicates.ts +419 -0
  34. package/scripts/archive/clean-duplicate-tasks.js +114 -0
  35. package/scripts/archive/cleanup-duplicate-tasks.ts +286 -0
  36. package/scripts/archive/create-phase-tasks.js +387 -0
  37. package/scripts/archive/delete-linear-duplicates.js +182 -0
  38. package/scripts/archive/delete-remaining-duplicates.js +158 -0
  39. package/scripts/archive/delete-sta-duplicates.js +201 -0
  40. package/scripts/archive/delete-sta-oauth.js +201 -0
  41. package/scripts/archive/export-sta-tasks.js +62 -0
  42. package/scripts/archive/install-auto-sync.js +266 -0
  43. package/scripts/archive/install-chromadb-hooks.sh +133 -0
  44. package/scripts/archive/install-enhanced-clear-hooks.sh +431 -0
  45. package/scripts/archive/install-post-task-hooks.sh +289 -0
  46. package/scripts/archive/install-stackmemory-hooks.sh +420 -0
  47. package/scripts/archive/merge-linear-duplicates-safe.ts +362 -0
  48. package/scripts/archive/merge-linear-duplicates.ts +180 -0
  49. package/scripts/archive/remove-sta-tasks.js +70 -0
  50. package/scripts/archive/setup-background-sync.sh +168 -0
  51. package/scripts/archive/setup-claude-auto-triggers.sh +181 -0
  52. package/scripts/archive/setup-claude-autostart.sh +305 -0
  53. package/scripts/archive/setup-git-hooks.sh +25 -0
  54. package/scripts/archive/setup-linear-oauth.sh +46 -0
  55. package/scripts/archive/setup-mcp.sh +113 -0
  56. package/scripts/archive/setup-railway-deployment.sh +81 -0
  57. package/scripts/auto-handoff.sh +262 -0
  58. package/scripts/background-sync-manager.js +416 -0
  59. package/scripts/benchmark-performance.ts +57 -0
  60. package/scripts/check-redis.ts +48 -0
  61. package/scripts/chromadb-auto-loader.sh +128 -0
  62. package/scripts/chromadb-context-loader.js +479 -0
  63. package/scripts/claude-chromadb-hook.js +460 -0
  64. package/scripts/claude-code-wrapper.sh +66 -0
  65. package/scripts/claude-linear-skill.js +455 -0
  66. package/scripts/claude-pre-commit.sh +302 -0
  67. package/scripts/claude-sm-autostart.js +532 -0
  68. package/scripts/claude-sm-setup.sh +367 -0
  69. package/scripts/claude-with-chromadb.sh +69 -0
  70. package/scripts/claude-worktree-manager.sh +323 -0
  71. package/scripts/claude-worktree-monitor.sh +371 -0
  72. package/scripts/claude-worktree-setup.sh +327 -0
  73. package/scripts/clean-linear-backlog.js +273 -0
  74. package/scripts/cleanup-old-sessions.sh +57 -0
  75. package/scripts/codex-wrapper.sh +88 -0
  76. package/scripts/create-sandbox.sh +269 -0
  77. package/scripts/debug-linear-update.js +174 -0
  78. package/scripts/delete-linear-tasks.js +167 -0
  79. package/scripts/deploy.sh +89 -0
  80. package/scripts/deployment/railway.sh +352 -0
  81. package/scripts/deployment/test-deployment.js +194 -0
  82. package/scripts/detect-and-rehydrate.js +162 -0
  83. package/scripts/detect-and-rehydrate.mjs +165 -0
  84. package/scripts/development/create-demo-tasks.js +143 -0
  85. package/scripts/development/debug-frame-test.js +16 -0
  86. package/scripts/development/demo-auto-sync.js +128 -0
  87. package/scripts/development/fix-all-imports.js +213 -0
  88. package/scripts/development/fix-imports.js +229 -0
  89. package/scripts/development/fix-lint-loop.cjs +103 -0
  90. package/scripts/development/fix-project-id.ts +161 -0
  91. package/scripts/development/fix-strict-mode-issues.ts +291 -0
  92. package/scripts/development/reorganize-structure.sh +228 -0
  93. package/scripts/development/test-persistence-direct.js +148 -0
  94. package/scripts/development/test-persistence.js +114 -0
  95. package/scripts/development/test-tasks.js +93 -0
  96. package/scripts/development/update-imports.js +212 -0
  97. package/scripts/fetch-linear-status.js +125 -0
  98. package/scripts/git-hooks/README.md +310 -0
  99. package/scripts/git-hooks/branch-context-manager.sh +342 -0
  100. package/scripts/git-hooks/post-checkout-stackmemory.sh +63 -0
  101. package/scripts/git-hooks/post-commit-stackmemory.sh +305 -0
  102. package/scripts/git-hooks/pre-commit-stackmemory.sh +275 -0
  103. package/scripts/hooks/cleanup-shell.sh +130 -0
  104. package/scripts/hooks/task-complete.sh +114 -0
  105. package/scripts/initialize.ts +129 -0
  106. package/scripts/install-claude-hooks-auto.js +104 -0
  107. package/scripts/install-claude-hooks.sh +133 -0
  108. package/scripts/install-global.sh +296 -0
  109. package/scripts/install.sh +235 -0
  110. package/scripts/linear-auto-sync.js +262 -0
  111. package/scripts/linear-auto-sync.sh +161 -0
  112. package/scripts/linear-sync-daemon.js +150 -0
  113. package/scripts/linear-task-review.js +237 -0
  114. package/scripts/list-linear-tasks.ts +178 -0
  115. package/scripts/mcp-proxy.js +66 -0
  116. package/scripts/opencode-wrapper.sh +85 -0
  117. package/scripts/publish-local.js +74 -0
  118. package/scripts/query-chromadb.ts +201 -0
  119. package/scripts/railway-env-setup.sh +39 -0
  120. package/scripts/reconcile-local-tasks.js +170 -0
  121. package/scripts/recreate-frames-db.js +89 -0
  122. package/scripts/setup/claude-integration.js +138 -0
  123. package/scripts/setup/configure-alias.js +125 -0
  124. package/scripts/setup/configure-codex-alias.js +161 -0
  125. package/scripts/setup/configure-opencode-alias.js +175 -0
  126. package/scripts/setup-claude-integration.js +204 -0
  127. package/scripts/setup-claude-integration.sh +183 -0
  128. package/scripts/setup.sh +31 -0
  129. package/scripts/show-linear-summary.ts +172 -0
  130. package/scripts/stackmemory-auto-handoff.sh +231 -0
  131. package/scripts/stackmemory-daemon.sh +40 -0
  132. package/scripts/start-linear-sync-daemon.sh +141 -0
  133. package/scripts/start-temporal-paradox.sh +214 -0
  134. package/scripts/status.ts +159 -0
  135. package/scripts/sync-and-clean-tasks.js +258 -0
  136. package/scripts/sync-frames-from-railway.js +228 -0
  137. package/scripts/sync-linear-graphql.js +303 -0
  138. package/scripts/sync-linear-tasks.js +186 -0
  139. package/scripts/test-auto-triggers.sh +57 -0
  140. package/scripts/test-browser-mcp.js +74 -0
  141. package/scripts/test-chromadb-full.js +115 -0
  142. package/scripts/test-chromadb-hooks.sh +28 -0
  143. package/scripts/test-chromadb-sync.ts +245 -0
  144. package/scripts/test-cli-security.js +293 -0
  145. package/scripts/test-hooks-persistence.sh +220 -0
  146. package/scripts/test-installation-scenarios.sh +359 -0
  147. package/scripts/test-installation.sh +224 -0
  148. package/scripts/test-mcp.js +163 -0
  149. package/scripts/test-pre-publish-quick.sh +75 -0
  150. package/scripts/test-quality-gates.sh +263 -0
  151. package/scripts/test-railway-db.js +222 -0
  152. package/scripts/test-redis-storage.ts +490 -0
  153. package/scripts/test-rlm-basic.sh +122 -0
  154. package/scripts/test-rlm-comprehensive.sh +260 -0
  155. package/scripts/test-rlm-e2e.sh +268 -0
  156. package/scripts/test-rlm-simple.js +90 -0
  157. package/scripts/test-rlm.js +110 -0
  158. package/scripts/test-session-handoff.sh +165 -0
  159. package/scripts/test-shell-integration.sh +275 -0
  160. package/scripts/testing/ab-test-runner.ts +508 -0
  161. package/scripts/testing/collect-metrics.ts +457 -0
  162. package/scripts/testing/quick-effectiveness-demo.js +187 -0
  163. package/scripts/testing/real-performance-test.js +422 -0
  164. package/scripts/testing/run-effectiveness-tests.sh +176 -0
  165. package/scripts/testing/scripts/testing/ab-test-runner.js +363 -0
  166. package/scripts/testing/scripts/testing/collect-metrics.js +292 -0
  167. package/scripts/testing/simple-effectiveness-test.js +310 -0
  168. package/scripts/testing/src/core/context/context-bridge.js +253 -0
  169. package/scripts/testing/src/core/context/frame-manager.js +746 -0
  170. package/scripts/testing/src/core/context/shared-context-layer.js +437 -0
  171. package/scripts/testing/src/core/database/database-adapter.js +54 -0
  172. package/scripts/testing/src/core/errors/index.js +291 -0
  173. package/scripts/testing/src/core/errors/recovery.js +268 -0
  174. package/scripts/testing/src/core/monitoring/logger.js +145 -0
  175. package/scripts/testing/src/core/retrieval/context-retriever.js +516 -0
  176. package/scripts/testing/src/core/session/index.js +1 -0
  177. package/scripts/testing/src/core/session/session-manager.js +323 -0
  178. package/scripts/testing/src/core/trace/cli-trace-wrapper.js +140 -0
  179. package/scripts/testing/src/core/trace/db-trace-wrapper.js +251 -0
  180. package/scripts/testing/src/core/trace/debug-trace.js +398 -0
  181. package/scripts/testing/src/core/trace/index.js +120 -0
  182. package/scripts/testing/src/core/trace/linear-api-wrapper.js +204 -0
  183. package/scripts/update-linear-status.js +268 -0
  184. package/scripts/update-linear-tasks-fixed.js +284 -0
  185. package/templates/claude-hooks/hooks.json +5 -0
  186. package/templates/claude-hooks/on-clear.js +56 -0
  187. package/templates/claude-hooks/on-startup.js +56 -0
  188. package/templates/claude-hooks/tool-use-trace.js +67 -0
  189. package/dist/features/tui/components/analytics-panel.js +0 -157
  190. package/dist/features/tui/components/analytics-panel.js.map +0 -7
  191. package/dist/features/tui/components/frame-visualizer.js +0 -377
  192. package/dist/features/tui/components/frame-visualizer.js.map +0 -7
  193. package/dist/features/tui/components/pr-tracker.js +0 -135
  194. package/dist/features/tui/components/pr-tracker.js.map +0 -7
  195. package/dist/features/tui/components/session-monitor.js +0 -299
  196. package/dist/features/tui/components/session-monitor.js.map +0 -7
  197. package/dist/features/tui/components/subagent-fleet.js +0 -395
  198. package/dist/features/tui/components/subagent-fleet.js.map +0 -7
  199. package/dist/features/tui/components/task-board.js +0 -1139
  200. package/dist/features/tui/components/task-board.js.map +0 -7
  201. package/dist/features/tui/index.js +0 -408
  202. package/dist/features/tui/index.js.map +0 -7
  203. package/dist/features/tui/services/data-service.js +0 -641
  204. package/dist/features/tui/services/data-service.js.map +0 -7
  205. package/dist/features/tui/services/linear-task-reader.js +0 -102
  206. package/dist/features/tui/services/linear-task-reader.js.map +0 -7
  207. package/dist/features/tui/services/websocket-client.js +0 -162
  208. package/dist/features/tui/services/websocket-client.js.map +0 -7
  209. package/dist/features/tui/terminal-compat.js +0 -220
  210. package/dist/features/tui/terminal-compat.js.map +0 -7
  211. package/dist/features/tui/types.js +0 -1
  212. package/dist/features/tui/types.js.map +0 -7
@@ -0,0 +1,262 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Linear Auto-Sync Script
5
+ * Automatically syncs tasks from Linear to local store
6
+ * Can be run via cron job or scheduled task
7
+ */
8
+
9
+ import { PebblesTaskStore } from '../dist/features/tasks/pebbles-task-store.js';
10
+ import { LinearSyncEngine } from '../dist/integrations/linear/sync.js';
11
+ import { LinearAuthManager } from '../dist/integrations/linear/auth.js';
12
+ import { logger } from '../dist/core/monitoring/logger.js';
13
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
14
+ import { join } from 'path';
15
+ import Database from 'better-sqlite3';
16
+
17
+ const SYNC_STATE_FILE = '.stackmemory/linear-sync-state.json';
18
+ const DEFAULT_SYNC_CONFIG = {
19
+ enabled: true,
20
+ direction: 'from_linear', // Only pull from Linear, don't push local changes
21
+ autoSync: false,
22
+ conflictResolution: 'linear_wins', // Linear is source of truth
23
+ maxBatchSize: 50,
24
+ rateLimitDelay: 500,
25
+ };
26
+
27
+ class LinearAutoSync {
28
+ constructor() {
29
+ this.projectRoot = process.cwd();
30
+ this.lastSyncTime = this.loadLastSyncTime();
31
+ }
32
+
33
+ /**
34
+ * Load last sync time from state file
35
+ */
36
+ loadLastSyncTime() {
37
+ const stateFile = join(this.projectRoot, SYNC_STATE_FILE);
38
+ if (existsSync(stateFile)) {
39
+ try {
40
+ const state = JSON.parse(readFileSync(stateFile, 'utf8'));
41
+ return state.lastSyncTime || null;
42
+ } catch (error) {
43
+ console.error('Failed to load sync state:', error.message);
44
+ }
45
+ }
46
+ return null;
47
+ }
48
+
49
+ /**
50
+ * Save last sync time to state file
51
+ */
52
+ saveLastSyncTime(timestamp) {
53
+ const stateFile = join(this.projectRoot, SYNC_STATE_FILE);
54
+ const state = {
55
+ lastSyncTime: timestamp,
56
+ lastSyncDate: new Date(timestamp).toISOString(),
57
+ };
58
+
59
+ try {
60
+ writeFileSync(stateFile, JSON.stringify(state, null, 2));
61
+ } catch (error) {
62
+ console.error('Failed to save sync state:', error.message);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Run the sync process
68
+ */
69
+ async sync() {
70
+ console.log('🔄 Starting Linear auto-sync...');
71
+ console.log(
72
+ `Last sync: ${this.lastSyncTime ? new Date(this.lastSyncTime).toLocaleString() : 'Never'}`
73
+ );
74
+
75
+ // Check for API key
76
+ if (!process.env.LINEAR_API_KEY) {
77
+ console.error('❌ LINEAR_API_KEY environment variable not set');
78
+ console.log('Set it with: export LINEAR_API_KEY="your-api-key"');
79
+ process.exit(1);
80
+ }
81
+
82
+ try {
83
+ // Check if StackMemory is initialized
84
+ const dbPath = join(this.projectRoot, '.stackmemory', 'context.db');
85
+ if (!existsSync(dbPath)) {
86
+ console.error(
87
+ '❌ StackMemory not initialized. Run "stackmemory init" first.'
88
+ );
89
+ process.exit(1);
90
+ }
91
+
92
+ // Initialize components
93
+ const db = new Database(dbPath);
94
+ const taskStore = new PebblesTaskStore(this.projectRoot, db);
95
+ const authManager = new LinearAuthManager(this.projectRoot);
96
+ const syncEngine = new LinearSyncEngine(
97
+ taskStore,
98
+ authManager,
99
+ DEFAULT_SYNC_CONFIG,
100
+ this.projectRoot
101
+ );
102
+
103
+ // Run sync
104
+ const startTime = Date.now();
105
+ const result = await syncEngine.sync();
106
+ const duration = ((Date.now() - startTime) / 1000).toFixed(2);
107
+
108
+ // Display results
109
+ if (result.success) {
110
+ console.log(`✅ Sync completed in ${duration}s`);
111
+ console.log(` From Linear: ${result.synced.fromLinear} created`);
112
+ console.log(` Updated: ${result.synced.updated} tasks`);
113
+
114
+ if (result.conflicts.length > 0) {
115
+ console.log(`\n⚠️ Conflicts (${result.conflicts.length}):`);
116
+ result.conflicts.forEach((c) => {
117
+ console.log(` - Task ${c.taskId}: ${c.reason}`);
118
+ });
119
+ }
120
+
121
+ // Save sync time
122
+ this.saveLastSyncTime(Date.now());
123
+ } else {
124
+ console.error('❌ Sync failed');
125
+ result.errors.forEach((error) => {
126
+ console.error(` - ${error}`);
127
+ });
128
+ process.exit(1);
129
+ }
130
+
131
+ // Show task summary
132
+ await this.showTaskSummary(taskStore);
133
+ } catch (error) {
134
+ console.error('❌ Sync error:', error.message);
135
+ if (error.stack && process.env.DEBUG) {
136
+ console.error(error.stack);
137
+ }
138
+ process.exit(1);
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Show summary of local tasks
144
+ */
145
+ async showTaskSummary(taskStore) {
146
+ const tasks = taskStore.getActiveTasks();
147
+
148
+ // Count by status
149
+ const byStatus = {};
150
+ const byPriority = {};
151
+ const linearTasks = [];
152
+
153
+ tasks.forEach((task) => {
154
+ byStatus[task.status] = (byStatus[task.status] || 0) + 1;
155
+ byPriority[task.priority] = (byPriority[task.priority] || 0) + 1;
156
+
157
+ if (task.title.match(/\[ENG-\d+\]/) || task.title.match(/\[STA-\d+\]/)) {
158
+ linearTasks.push(task);
159
+ }
160
+ });
161
+
162
+ console.log('\n📊 Local Task Summary:');
163
+ console.log(` Total tasks: ${tasks.length}`);
164
+ console.log(` Linear tasks: ${linearTasks.length}`);
165
+ console.log(` Local-only tasks: ${tasks.length - linearTasks.length}`);
166
+
167
+ console.log('\n By Status:');
168
+ Object.entries(byStatus).forEach(([status, count]) => {
169
+ console.log(` - ${status}: ${count}`);
170
+ });
171
+
172
+ console.log('\n By Priority:');
173
+ Object.entries(byPriority).forEach(([priority, count]) => {
174
+ console.log(` - ${priority}: ${count}`);
175
+ });
176
+
177
+ // Show recent Linear tasks
178
+ if (linearTasks.length > 0) {
179
+ console.log('\n Recent Linear tasks:');
180
+ linearTasks.slice(-5).forEach((task) => {
181
+ const identifier = task.title.match(/\[(\w+-\d+)\]/)?.[1] || '';
182
+ const title = task.title.replace(/\[\w+-\d+\]\s*/, '').substring(0, 50);
183
+ console.log(` - ${identifier}: ${title}...`);
184
+ });
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Run in watch mode - sync every N minutes
190
+ */
191
+ async watch(intervalMinutes = 5) {
192
+ console.log(
193
+ `👀 Running in watch mode (syncing every ${intervalMinutes} minutes)`
194
+ );
195
+
196
+ // Initial sync
197
+ await this.sync();
198
+
199
+ // Schedule periodic syncs
200
+ setInterval(
201
+ async () => {
202
+ console.log(
203
+ `\n⏰ Running scheduled sync at ${new Date().toLocaleString()}`
204
+ );
205
+ await this.sync();
206
+ },
207
+ intervalMinutes * 60 * 1000
208
+ );
209
+
210
+ console.log('\nPress Ctrl+C to stop watching');
211
+ }
212
+ }
213
+
214
+ // CLI execution
215
+ async function main() {
216
+ const autoSync = new LinearAutoSync();
217
+ const args = process.argv.slice(2);
218
+
219
+ if (args.includes('--watch') || args.includes('-w')) {
220
+ // Watch mode
221
+ const intervalIndex = args.findIndex(
222
+ (a) => a === '--interval' || a === '-i'
223
+ );
224
+ const interval = intervalIndex >= 0 ? parseInt(args[intervalIndex + 1]) : 5;
225
+ await autoSync.watch(interval);
226
+ } else if (args.includes('--help') || args.includes('-h')) {
227
+ // Help
228
+ console.log(`
229
+ Linear Auto-Sync Script
230
+ -----------------------
231
+
232
+ Usage:
233
+ npm run linear:sync Run one-time sync
234
+ npm run linear:sync -- --watch Watch mode (sync every 5 minutes)
235
+ npm run linear:sync -- --watch -i 10 Watch mode with custom interval
236
+
237
+ Options:
238
+ --watch, -w Run in watch mode
239
+ --interval, -i <min> Sync interval in minutes (default: 5)
240
+ --help, -h Show this help
241
+
242
+ Environment:
243
+ LINEAR_API_KEY Required - your Linear API key
244
+
245
+ Example cron job (every 15 minutes):
246
+ */15 * * * * cd /path/to/project && LINEAR_API_KEY=xxx npm run linear:sync
247
+ `);
248
+ } else {
249
+ // One-time sync
250
+ await autoSync.sync();
251
+ }
252
+ }
253
+
254
+ // Run if executed directly
255
+ if (import.meta.url === `file://${process.argv[1]}`) {
256
+ main().catch((error) => {
257
+ console.error('Fatal error:', error);
258
+ process.exit(1);
259
+ });
260
+ }
261
+
262
+ export { LinearAutoSync };
@@ -0,0 +1,161 @@
1
+ #!/bin/bash
2
+
3
+ # Linear Auto-Sync Daemon for StackMemory
4
+ # Automatically syncs tasks with Linear at regular intervals
5
+
6
+ # Configuration
7
+ INTERVAL=${1:-5} # Default 5 minutes
8
+ PID_FILE="/tmp/stackmemory-linear-sync.pid"
9
+ LOG_FILE="${HOME}/.stackmemory/logs/linear-sync.log"
10
+
11
+ # Ensure log directory exists
12
+ mkdir -p "$(dirname "$LOG_FILE")"
13
+
14
+ # Colors for output
15
+ GREEN='\033[0;32m'
16
+ YELLOW='\033[1;33m'
17
+ RED='\033[0;31m'
18
+ NC='\033[0m' # No Color
19
+
20
+ log() {
21
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
22
+ }
23
+
24
+ cleanup() {
25
+ log "🛑 Stopping Linear auto-sync daemon..."
26
+
27
+ # Final sync before exit
28
+ if [ -n "$LINEAR_API_KEY" ] && [ -d ".stackmemory" ]; then
29
+ log "📤 Performing final sync..."
30
+ stackmemory linear sync 2>&1 | tee -a "$LOG_FILE"
31
+ fi
32
+
33
+ rm -f "$PID_FILE"
34
+ log "✅ Linear auto-sync stopped"
35
+ exit 0
36
+ }
37
+
38
+ # Check prerequisites
39
+ check_requirements() {
40
+ if [ -z "$LINEAR_API_KEY" ]; then
41
+ echo -e "${RED}❌ LINEAR_API_KEY not set${NC}"
42
+ echo "Please set: export LINEAR_API_KEY='your_api_key'"
43
+ exit 1
44
+ fi
45
+
46
+ if ! command -v stackmemory &> /dev/null; then
47
+ echo -e "${RED}❌ StackMemory not installed${NC}"
48
+ echo "Install with: npm install -g @stackmemoryai/stackmemory"
49
+ exit 1
50
+ fi
51
+
52
+ if [ ! -d ".stackmemory" ]; then
53
+ echo -e "${YELLOW}⚠️ No .stackmemory directory found${NC}"
54
+ echo "Initialize with: stackmemory init"
55
+ exit 1
56
+ fi
57
+ }
58
+
59
+ start_daemon() {
60
+ # Check if already running
61
+ if [ -f "$PID_FILE" ]; then
62
+ OLD_PID=$(cat "$PID_FILE")
63
+ if kill -0 "$OLD_PID" 2>/dev/null; then
64
+ echo -e "${YELLOW}⚠️ Linear auto-sync already running (PID: $OLD_PID)${NC}"
65
+ exit 1
66
+ else
67
+ rm -f "$PID_FILE"
68
+ fi
69
+ fi
70
+
71
+ # Save PID
72
+ echo $$ > "$PID_FILE"
73
+
74
+ log "🚀 Linear auto-sync daemon started (PID: $$)"
75
+ log " Interval: ${INTERVAL} minutes"
76
+ log " API Key: ${LINEAR_API_KEY:0:10}..."
77
+ log " Log file: $LOG_FILE"
78
+ echo -e "${GREEN}✅ Auto-sync running. Use 'tail -f $LOG_FILE' to monitor${NC}"
79
+
80
+ # Initial sync
81
+ log "📤 Performing initial sync..."
82
+ stackmemory linear sync 2>&1 | tee -a "$LOG_FILE"
83
+
84
+ # Main loop
85
+ while true; do
86
+ sleep $((INTERVAL * 60))
87
+
88
+ log "🔄 Auto-syncing with Linear..."
89
+
90
+ # Run sync and capture output
91
+ SYNC_OUTPUT=$(stackmemory linear sync 2>&1)
92
+ SYNC_EXIT=$?
93
+
94
+ if [ $SYNC_EXIT -eq 0 ]; then
95
+ # Check if there were changes
96
+ if echo "$SYNC_OUTPUT" | grep -q "created\|updated"; then
97
+ log "✅ Sync completed with changes"
98
+ echo "$SYNC_OUTPUT" >> "$LOG_FILE"
99
+ else
100
+ log "✅ Sync completed (no changes)"
101
+ fi
102
+ else
103
+ log "⚠️ Sync failed: $SYNC_OUTPUT"
104
+ fi
105
+ done
106
+ }
107
+
108
+ # Parse commands
109
+ case "${1:-start}" in
110
+ start)
111
+ check_requirements
112
+ trap cleanup EXIT INT TERM
113
+ start_daemon
114
+ ;;
115
+ stop)
116
+ if [ -f "$PID_FILE" ]; then
117
+ PID=$(cat "$PID_FILE")
118
+ if kill -0 "$PID" 2>/dev/null; then
119
+ kill "$PID"
120
+ echo -e "${GREEN}✅ Stopped Linear auto-sync (PID: $PID)${NC}"
121
+ else
122
+ echo -e "${YELLOW}⚠️ Process not running${NC}"
123
+ rm -f "$PID_FILE"
124
+ fi
125
+ else
126
+ echo -e "${YELLOW}⚠️ No daemon running${NC}"
127
+ fi
128
+ ;;
129
+ status)
130
+ if [ -f "$PID_FILE" ]; then
131
+ PID=$(cat "$PID_FILE")
132
+ if kill -0 "$PID" 2>/dev/null; then
133
+ echo -e "${GREEN}✅ Linear auto-sync running (PID: $PID)${NC}"
134
+ echo " Log: tail -f $LOG_FILE"
135
+ else
136
+ echo -e "${RED}❌ Linear auto-sync not running (stale PID file)${NC}"
137
+ rm -f "$PID_FILE"
138
+ fi
139
+ else
140
+ echo -e "${YELLOW}⚠️ Linear auto-sync not running${NC}"
141
+ fi
142
+ ;;
143
+ logs)
144
+ if [ -f "$LOG_FILE" ]; then
145
+ tail -f "$LOG_FILE"
146
+ else
147
+ echo -e "${YELLOW}⚠️ No log file found${NC}"
148
+ fi
149
+ ;;
150
+ *)
151
+ echo "Usage: $0 {start|stop|status|logs} [interval_minutes]"
152
+ echo ""
153
+ echo "Examples:"
154
+ echo " $0 start # Start with 5-minute interval (default)"
155
+ echo " $0 start 10 # Start with 10-minute interval"
156
+ echo " $0 stop # Stop the daemon"
157
+ echo " $0 status # Check if running"
158
+ echo " $0 logs # Tail the log file"
159
+ exit 1
160
+ ;;
161
+ esac
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Linear Sync Daemon - Runs hourly sync between Linear and local tasks
5
+ * Checks .env first for API keys, then falls back to environment variables
6
+ */
7
+
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import { fileURLToPath } from 'url';
11
+ import dotenv from 'dotenv';
12
+ import { spawn } from 'child_process';
13
+
14
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+
16
+ // Load environment variables from .env file first (as per user preference)
17
+ dotenv.config({
18
+ path: path.join(__dirname, '..', '.env'),
19
+ override: false // Don't override existing env vars
20
+ });
21
+
22
+ const SYNC_INTERVAL = 60 * 60 * 1000; // 1 hour in milliseconds
23
+ const LOG_FILE = path.join(__dirname, '..', '.stackmemory', 'linear-sync.log');
24
+
25
+ class LinearSyncDaemon {
26
+ constructor() {
27
+ this.isRunning = false;
28
+ this.syncCount = 0;
29
+ this.lastSyncTime = null;
30
+ this.intervalId = null;
31
+ }
32
+
33
+ log(message) {
34
+ const timestamp = new Date().toISOString();
35
+ const logMessage = `[${timestamp}] ${message}\n`;
36
+
37
+ console.log(logMessage.trim());
38
+
39
+ // Append to log file
40
+ try {
41
+ fs.appendFileSync(LOG_FILE, logMessage);
42
+ } catch (error) {
43
+ console.error('Failed to write to log file:', error.message);
44
+ }
45
+ }
46
+
47
+ async runSync() {
48
+ if (this.isRunning) {
49
+ this.log('⏭️ Sync already in progress, skipping...');
50
+ return;
51
+ }
52
+
53
+ this.isRunning = true;
54
+ this.syncCount++;
55
+
56
+ this.log(`🔄 Starting Linear sync #${this.syncCount}...`);
57
+
58
+ return new Promise((resolve) => {
59
+ const syncScript = path.join(__dirname, 'sync-linear-graphql.js');
60
+
61
+ // Run the sync script as a child process
62
+ const syncProcess = spawn('node', [syncScript], {
63
+ env: process.env,
64
+ stdio: ['ignore', 'pipe', 'pipe']
65
+ });
66
+
67
+ let output = '';
68
+ let errorOutput = '';
69
+
70
+ syncProcess.stdout.on('data', (data) => {
71
+ output += data.toString();
72
+ });
73
+
74
+ syncProcess.stderr.on('data', (data) => {
75
+ errorOutput += data.toString();
76
+ });
77
+
78
+ syncProcess.on('close', (code) => {
79
+ this.isRunning = false;
80
+ this.lastSyncTime = new Date();
81
+
82
+ if (code === 0) {
83
+ // Parse output for summary
84
+ const lines = output.split('\n');
85
+ const summaryLine = lines.find(l => l.includes('Added to local:'));
86
+ const addedCount = summaryLine ? summaryLine.match(/\d+/)?.[0] : '0';
87
+
88
+ this.log(`✅ Sync #${this.syncCount} completed successfully. Added ${addedCount} tasks.`);
89
+ } else {
90
+ this.log(`❌ Sync #${this.syncCount} failed with code ${code}`);
91
+ if (errorOutput) {
92
+ this.log(`Error output: ${errorOutput.substring(0, 500)}`);
93
+ }
94
+ }
95
+
96
+ resolve();
97
+ });
98
+
99
+ syncProcess.on('error', (error) => {
100
+ this.isRunning = false;
101
+ this.log(`❌ Failed to start sync process: ${error.message}`);
102
+ resolve();
103
+ });
104
+ });
105
+ }
106
+
107
+ async start() {
108
+ // Check for API key
109
+ if (!process.env.LINEAR_API_KEY) {
110
+ this.log('❌ LINEAR_API_KEY not found in .env or environment variables');
111
+ this.log('Please add LINEAR_API_KEY to your .env file or export it');
112
+ process.exit(1);
113
+ }
114
+
115
+ this.log('🚀 Linear Sync Daemon starting...');
116
+ this.log(`📅 Sync interval: Every hour`);
117
+ this.log(`🔑 API Key: Found (${process.env.LINEAR_API_KEY.substring(0, 10)}...)`);
118
+
119
+ // Run initial sync
120
+ await this.runSync();
121
+
122
+ // Schedule hourly syncs
123
+ this.intervalId = setInterval(() => {
124
+ this.runSync();
125
+ }, SYNC_INTERVAL);
126
+
127
+ this.log('⏰ Hourly sync scheduled. Daemon running in background...');
128
+
129
+ // Handle graceful shutdown
130
+ process.on('SIGINT', () => this.stop());
131
+ process.on('SIGTERM', () => this.stop());
132
+ }
133
+
134
+ stop() {
135
+ this.log('🛑 Stopping Linear Sync Daemon...');
136
+
137
+ if (this.intervalId) {
138
+ clearInterval(this.intervalId);
139
+ this.intervalId = null;
140
+ }
141
+
142
+ this.log(`📊 Final stats: ${this.syncCount} syncs completed`);
143
+ this.log('👋 Daemon stopped');
144
+ process.exit(0);
145
+ }
146
+ }
147
+
148
+ // Start the daemon
149
+ const daemon = new LinearSyncDaemon();
150
+ daemon.start();