@stackmemoryai/stackmemory 0.3.17 ā 0.3.19
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/claude-sm.js +51 -5
- package/dist/cli/claude-sm.js.map +2 -2
- package/dist/cli/codex-sm.js +52 -19
- package/dist/cli/codex-sm.js.map +2 -2
- package/dist/cli/commands/db.js +143 -0
- package/dist/cli/commands/db.js.map +7 -0
- package/dist/cli/commands/login.js +50 -0
- package/dist/cli/commands/login.js.map +7 -0
- package/dist/cli/commands/migrate.js +178 -0
- package/dist/cli/commands/migrate.js.map +7 -0
- package/dist/cli/commands/onboard.js +158 -2
- package/dist/cli/commands/onboard.js.map +2 -2
- package/dist/cli/commands/skills.js +15 -2
- package/dist/cli/commands/skills.js.map +2 -2
- package/dist/cli/index.js +118 -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-database.js +1 -0
- package/dist/core/context/frame-database.js.map +2 -2
- package/dist/core/context/frame-manager.js +59 -2
- package/dist/core/context/frame-manager.js.map +2 -2
- package/dist/core/database/database-adapter.js +6 -1
- package/dist/core/database/database-adapter.js.map +2 -2
- package/dist/core/database/sqlite-adapter.js +60 -2
- package/dist/core/database/sqlite-adapter.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/index.js +843 -82
- package/dist/servers/railway/index.js.map +3 -3
- 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 +13 -21
- 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-railway-deployment.sh +37 -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/scripts/verify-railway-schema.ts +35 -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,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);
|