@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.
- package/README.md +48 -2
- 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,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();
|