@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,362 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Script to merge duplicate Linear tasks (with dry-run option)
5
+ * Keeps the lowest-numbered task as primary and marks others as duplicates
6
+ */
7
+
8
+ import { readFileSync } from 'fs';
9
+ import { join } from 'path';
10
+
11
+ interface DuplicateGroup {
12
+ name: string;
13
+ taskIds: string[];
14
+ primaryId: string;
15
+ }
16
+
17
+ interface LinearTeam {
18
+ id: string;
19
+ key: string;
20
+ name: string;
21
+ states: {
22
+ nodes: WorkflowState[];
23
+ };
24
+ }
25
+
26
+ interface WorkflowState {
27
+ id: string;
28
+ name: string;
29
+ type: string;
30
+ }
31
+
32
+ interface LinearIssue {
33
+ id: string;
34
+ identifier: string;
35
+ title: string;
36
+ description?: string;
37
+ state: {
38
+ name: string;
39
+ type: string;
40
+ };
41
+ }
42
+
43
+ const duplicateGroups: DuplicateGroup[] = [
44
+ {
45
+ name: 'Linear API Integration',
46
+ taskIds: ['STA-88', 'STA-74', 'STA-61', 'STA-46', 'STA-32', 'STA-9'],
47
+ primaryId: 'STA-9',
48
+ },
49
+ {
50
+ name: 'Performance Optimization',
51
+ taskIds: [
52
+ 'STA-87',
53
+ 'STA-73',
54
+ 'STA-60',
55
+ 'STA-45',
56
+ 'STA-31',
57
+ 'STA-13',
58
+ 'STA-21',
59
+ 'STA-35',
60
+ 'STA-50',
61
+ 'STA-63',
62
+ 'STA-77',
63
+ ],
64
+ primaryId: 'STA-13',
65
+ },
66
+ {
67
+ name: 'Security Audit and Input Validation',
68
+ taskIds: ['STA-85', 'STA-71', 'STA-58', 'STA-43', 'STA-29'],
69
+ primaryId: 'STA-29',
70
+ },
71
+ {
72
+ name: 'Implement Proper Error Handling',
73
+ taskIds: ['STA-84', 'STA-70', 'STA-57', 'STA-42', 'STA-28'],
74
+ primaryId: 'STA-28',
75
+ },
76
+ {
77
+ name: 'Implement Comprehensive Testing Suite',
78
+ taskIds: ['STA-83', 'STA-69', 'STA-56', 'STA-41', 'STA-27'],
79
+ primaryId: 'STA-27',
80
+ },
81
+ ];
82
+
83
+ async function mergeDuplicateTasks(dryRun = true) {
84
+ const mode = dryRun ? '🔍 DRY RUN MODE' : '⚡ LIVE MODE';
85
+ console.log(`\n${mode} - Linear duplicate task merge\n`);
86
+ console.log('='.repeat(60));
87
+
88
+ // Load Linear tokens
89
+ const tokensPath = join(process.cwd(), '.stackmemory', 'linear-tokens.json');
90
+ let accessToken: string;
91
+
92
+ try {
93
+ const tokensData = readFileSync(tokensPath, 'utf8');
94
+ const tokens = JSON.parse(tokensData);
95
+ accessToken = tokens.accessToken;
96
+ console.log('✅ Loaded Linear authentication tokens\n');
97
+ } catch {
98
+ console.error(
99
+ '❌ Failed to load Linear tokens. Please run: stackmemory linear setup'
100
+ );
101
+ process.exit(1);
102
+ }
103
+
104
+ // Initialize Linear client using GraphQL directly
105
+ const linearApiUrl = 'https://api.linear.app/graphql';
106
+
107
+ async function graphqlRequest(
108
+ query: string,
109
+ variables: Record<string, unknown> = {}
110
+ ) {
111
+ const response = await fetch(linearApiUrl, {
112
+ method: 'POST',
113
+ headers: {
114
+ Authorization: `Bearer ${accessToken}`,
115
+ 'Content-Type': 'application/json',
116
+ },
117
+ body: JSON.stringify({ query, variables }),
118
+ });
119
+
120
+ if (!response.ok) {
121
+ throw new Error(
122
+ `Linear API error: ${response.status} ${response.statusText}`
123
+ );
124
+ }
125
+
126
+ const result = (await response.json()) as {
127
+ errors?: unknown[];
128
+ data: unknown;
129
+ };
130
+ if (result.errors) {
131
+ throw new Error(`GraphQL errors: ${JSON.stringify(result.errors)}`);
132
+ }
133
+
134
+ return result.data;
135
+ }
136
+
137
+ // Get team and workflow states
138
+ console.log('Fetching team information...');
139
+ const teamQuery = `
140
+ query {
141
+ teams {
142
+ nodes {
143
+ id
144
+ key
145
+ name
146
+ states {
147
+ nodes {
148
+ id
149
+ name
150
+ type
151
+ }
152
+ }
153
+ }
154
+ }
155
+ }
156
+ `;
157
+
158
+ const teamData = (await graphqlRequest(teamQuery)) as {
159
+ teams: { nodes: LinearTeam[] };
160
+ };
161
+ const team = teamData.teams.nodes[0];
162
+
163
+ if (!team) {
164
+ console.error('❌ No team found');
165
+ process.exit(1);
166
+ }
167
+
168
+ const canceledState = team.states.nodes.find(
169
+ (s: WorkflowState) => s.type === 'canceled'
170
+ );
171
+ if (!canceledState) {
172
+ console.error('❌ No canceled state found in team workflow');
173
+ process.exit(1);
174
+ }
175
+
176
+ console.log(`✅ Found team: ${team.name} (${team.key})`);
177
+ console.log(`✅ Canceled state ID: ${canceledState.id}\n`);
178
+
179
+ // Process each duplicate group
180
+ let totalUpdates = 0;
181
+ let totalCanceled = 0;
182
+
183
+ for (const group of duplicateGroups) {
184
+ console.log(`\n📋 Processing: ${group.name}`);
185
+ console.log(` Primary: ${group.primaryId}`);
186
+ console.log(
187
+ ` Will cancel: ${group.taskIds.filter((id) => id !== group.primaryId).join(', ')}`
188
+ );
189
+
190
+ if (dryRun) {
191
+ console.log(' 🔍 [DRY RUN] Would:');
192
+ console.log(` - Keep ${group.primaryId} as primary`);
193
+ console.log(` - Cancel ${group.taskIds.length - 1} duplicates`);
194
+ console.log(` - Merge any unique descriptions into primary`);
195
+ totalCanceled += group.taskIds.length - 1;
196
+ } else {
197
+ try {
198
+ // Get all issues in the group
199
+ const issueQuery = `
200
+ query GetIssue($identifier: String!) {
201
+ issue(id: $identifier) {
202
+ id
203
+ identifier
204
+ title
205
+ description
206
+ state {
207
+ id
208
+ name
209
+ type
210
+ }
211
+ }
212
+ }
213
+ `;
214
+
215
+ // Get primary issue
216
+ const primaryData = (await graphqlRequest(issueQuery, {
217
+ identifier: group.primaryId,
218
+ })) as { issue: LinearIssue | null };
219
+ const primaryIssue = primaryData.issue;
220
+
221
+ if (!primaryIssue) {
222
+ console.log(
223
+ ` ⚠️ Primary issue ${group.primaryId} not found, skipping group`
224
+ );
225
+ continue;
226
+ }
227
+
228
+ // Process duplicates
229
+ const duplicateIds = group.taskIds.filter(
230
+ (id) => id !== group.primaryId
231
+ );
232
+ const mergedDescriptions: string[] = [];
233
+
234
+ for (const duplicateId of duplicateIds) {
235
+ try {
236
+ const dupData = (await graphqlRequest(issueQuery, {
237
+ identifier: duplicateId,
238
+ })) as { issue: LinearIssue | null };
239
+ const duplicateIssue = dupData.issue;
240
+
241
+ if (!duplicateIssue) {
242
+ console.log(` ⚠️ Issue ${duplicateId} not found, skipping`);
243
+ continue;
244
+ }
245
+
246
+ // Collect unique descriptions
247
+ if (
248
+ duplicateIssue.description &&
249
+ duplicateIssue.description !== primaryIssue.description
250
+ ) {
251
+ mergedDescriptions.push(
252
+ `[Merged from ${duplicateId}]\n${duplicateIssue.description}`
253
+ );
254
+ }
255
+
256
+ // Cancel the duplicate
257
+ const updateMutation = `
258
+ mutation UpdateIssue($id: String!, $stateId: String!, $description: String!) {
259
+ issueUpdate(
260
+ id: $id,
261
+ input: {
262
+ stateId: $stateId,
263
+ description: $description
264
+ }
265
+ ) {
266
+ success
267
+ issue {
268
+ identifier
269
+ state {
270
+ name
271
+ type
272
+ }
273
+ }
274
+ }
275
+ }
276
+ `;
277
+
278
+ await graphqlRequest(updateMutation, {
279
+ id: duplicateIssue.id,
280
+ stateId: canceledState.id,
281
+ description: `Duplicate of ${group.primaryId}\n\n${duplicateIssue.description || ''}`,
282
+ });
283
+
284
+ console.log(` ✅ Canceled ${duplicateId} as duplicate`);
285
+ totalCanceled++;
286
+ } catch (error: unknown) {
287
+ const errorMessage =
288
+ error instanceof Error ? error.message : String(error);
289
+ console.log(
290
+ ` ❌ Failed to process ${duplicateId}: ${errorMessage}`
291
+ );
292
+ }
293
+ }
294
+
295
+ // Update primary with merged descriptions if any
296
+ if (mergedDescriptions.length > 0) {
297
+ const newDescription = [
298
+ primaryIssue.description || '',
299
+ '',
300
+ '--- Merged Content ---',
301
+ ...mergedDescriptions,
302
+ ]
303
+ .filter(Boolean)
304
+ .join('\n\n');
305
+
306
+ const updatePrimaryMutation = `
307
+ mutation UpdatePrimaryIssue($id: String!, $description: String!) {
308
+ issueUpdate(
309
+ id: $id,
310
+ input: {
311
+ description: $description
312
+ }
313
+ ) {
314
+ success
315
+ }
316
+ }
317
+ `;
318
+
319
+ await graphqlRequest(updatePrimaryMutation, {
320
+ id: primaryIssue.id,
321
+ description: newDescription,
322
+ });
323
+
324
+ console.log(
325
+ ` ✅ Updated ${group.primaryId} with merged descriptions`
326
+ );
327
+ totalUpdates++;
328
+ }
329
+ } catch (error: unknown) {
330
+ const errorMessage =
331
+ error instanceof Error ? error.message : String(error);
332
+ console.error(` ❌ Error processing group: ${errorMessage}`);
333
+ }
334
+ }
335
+ }
336
+
337
+ // Summary
338
+ console.log('\n' + '='.repeat(60));
339
+ console.log(`\n✨ ${dryRun ? 'DRY RUN' : 'MERGE'} COMPLETE!\n`);
340
+ console.log('📊 Summary:');
341
+ console.log(` Groups processed: ${duplicateGroups.length}`);
342
+ console.log(` Primary tasks kept: ${duplicateGroups.length}`);
343
+ console.log(
344
+ ` Tasks ${dryRun ? 'to cancel' : 'canceled'}: ${dryRun ? duplicateGroups.reduce((acc, g) => acc + g.taskIds.length - 1, 0) : totalCanceled}`
345
+ );
346
+ if (!dryRun) {
347
+ console.log(` Primary tasks updated: ${totalUpdates}`);
348
+ }
349
+
350
+ if (dryRun) {
351
+ console.log('\n💡 To execute these changes, run with --execute flag');
352
+ }
353
+ }
354
+
355
+ // Parse command line arguments
356
+ const isDryRun = !process.argv.includes('--execute');
357
+
358
+ // Run the merge
359
+ mergeDuplicateTasks(isDryRun).catch((error) => {
360
+ console.error('❌ Fatal error:', error);
361
+ process.exit(1);
362
+ });
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Script to merge duplicate Linear tasks
5
+ * Keeps the lowest-numbered task as primary and marks others as duplicates
6
+ */
7
+
8
+ import { LinearClient } from '@linear/sdk';
9
+ import { readFileSync } from 'fs';
10
+ import { join } from 'path';
11
+
12
+ interface DuplicateGroup {
13
+ name: string;
14
+ taskIds: string[];
15
+ primaryId: string;
16
+ }
17
+
18
+ const duplicateGroups: DuplicateGroup[] = [
19
+ {
20
+ name: 'Linear API Integration',
21
+ taskIds: ['STA-88', 'STA-74', 'STA-61', 'STA-46', 'STA-32', 'STA-9'],
22
+ primaryId: 'STA-9',
23
+ },
24
+ {
25
+ name: 'Performance Optimization',
26
+ taskIds: [
27
+ 'STA-87',
28
+ 'STA-73',
29
+ 'STA-60',
30
+ 'STA-45',
31
+ 'STA-31',
32
+ 'STA-13',
33
+ 'STA-21',
34
+ 'STA-35',
35
+ 'STA-50',
36
+ 'STA-63',
37
+ 'STA-77',
38
+ ],
39
+ primaryId: 'STA-13',
40
+ },
41
+ {
42
+ name: 'Security Audit and Input Validation',
43
+ taskIds: ['STA-85', 'STA-71', 'STA-58', 'STA-43', 'STA-29'],
44
+ primaryId: 'STA-29',
45
+ },
46
+ {
47
+ name: 'Implement Proper Error Handling',
48
+ taskIds: ['STA-84', 'STA-70', 'STA-57', 'STA-42', 'STA-28'],
49
+ primaryId: 'STA-28',
50
+ },
51
+ {
52
+ name: 'Implement Comprehensive Testing Suite',
53
+ taskIds: ['STA-83', 'STA-69', 'STA-56', 'STA-41', 'STA-27'],
54
+ primaryId: 'STA-27',
55
+ },
56
+ ];
57
+
58
+ async function mergeDuplicateTasks() {
59
+ console.log('🔄 Starting Linear duplicate task merge...\n');
60
+
61
+ // Load Linear tokens
62
+ const tokensPath = join(process.cwd(), '.stackmemory', 'linear-tokens.json');
63
+ let accessToken: string;
64
+
65
+ try {
66
+ const tokensData = readFileSync(tokensPath, 'utf8');
67
+ const tokens = JSON.parse(tokensData);
68
+ accessToken = tokens.accessToken;
69
+ console.log('Loaded Linear authentication tokens\n');
70
+ } catch {
71
+ console.error(
72
+ 'Failed to load Linear tokens. Please run: stackmemory linear setup'
73
+ );
74
+ process.exit(1);
75
+ }
76
+
77
+ // Initialize Linear client
78
+ const client = new LinearClient({
79
+ apiKey: accessToken,
80
+ });
81
+
82
+ // Process each duplicate group
83
+ for (const group of duplicateGroups) {
84
+ console.log(`\n📋 Processing: ${group.name}`);
85
+ console.log(` Primary: ${group.primaryId}`);
86
+ console.log(
87
+ ` Duplicates: ${group.taskIds.filter((id) => id !== group.primaryId).join(', ')}`
88
+ );
89
+
90
+ try {
91
+ // Get the primary issue
92
+ const primaryIssue = await client.issue(group.primaryId);
93
+ if (!primaryIssue) {
94
+ console.log(
95
+ ` ⚠️ Primary issue ${group.primaryId} not found, skipping group`
96
+ );
97
+ continue;
98
+ }
99
+
100
+ // Collect descriptions from all duplicates
101
+ let combinedDescription = primaryIssue.description || '';
102
+ const duplicateIds = group.taskIds.filter((id) => id !== group.primaryId);
103
+
104
+ for (const duplicateId of duplicateIds) {
105
+ try {
106
+ const duplicateIssue = await client.issue(duplicateId);
107
+ if (!duplicateIssue) {
108
+ console.log(` ⚠️ Issue ${duplicateId} not found, skipping`);
109
+ continue;
110
+ }
111
+
112
+ // Add duplicate's description if it exists and differs
113
+ if (
114
+ duplicateIssue.description &&
115
+ duplicateIssue.description !== primaryIssue.description
116
+ ) {
117
+ combinedDescription += `\n\n---\n[Merged from ${duplicateId}]\n${duplicateIssue.description}`;
118
+ }
119
+
120
+ // Get the canceled state for the team
121
+ const team = await duplicateIssue.team;
122
+ const states = await team.states();
123
+ const canceledState = states.nodes.find((s) => s.type === 'canceled');
124
+
125
+ if (!canceledState) {
126
+ console.log(
127
+ ` ⚠️ No 'canceled' state found for team, skipping ${duplicateId}`
128
+ );
129
+ continue;
130
+ }
131
+
132
+ // Update duplicate to canceled state with reference to primary
133
+ await duplicateIssue.update({
134
+ stateId: canceledState.id,
135
+ description: `Duplicate of ${group.primaryId}\n\n${duplicateIssue.description || ''}`,
136
+ });
137
+
138
+ console.log(
139
+ ` ✅ Marked ${duplicateId} as duplicate of ${group.primaryId}`
140
+ );
141
+ } catch (error: any) {
142
+ console.log(
143
+ ` ❌ Failed to process ${duplicateId}: ${error.message}`
144
+ );
145
+ }
146
+ }
147
+
148
+ // Update primary issue with combined description if changed
149
+ if (combinedDescription !== primaryIssue.description) {
150
+ await primaryIssue.update({
151
+ description: combinedDescription,
152
+ });
153
+ console.log(
154
+ ` ✅ Updated ${group.primaryId} with merged descriptions`
155
+ );
156
+ }
157
+
158
+ console.log(` ✅ Group "${group.name}" processed successfully`);
159
+ } catch (error: any) {
160
+ console.error(` ❌ Error processing group: ${error.message}`);
161
+ }
162
+ }
163
+
164
+ console.log('\n✨ Duplicate merge complete!');
165
+ console.log('\n📊 Summary:');
166
+ console.log(` Groups processed: ${duplicateGroups.length}`);
167
+ console.log(
168
+ ` Total tasks affected: ${duplicateGroups.reduce((acc, g) => acc + g.taskIds.length, 0)}`
169
+ );
170
+ console.log(` Primary tasks kept: ${duplicateGroups.length}`);
171
+ console.log(
172
+ ` Tasks marked as duplicate: ${duplicateGroups.reduce((acc, g) => acc + g.taskIds.length - 1, 0)}`
173
+ );
174
+ }
175
+
176
+ // Run the merge
177
+ mergeDuplicateTasks().catch((error) => {
178
+ console.error('❌ Fatal error:', error);
179
+ process.exit(1);
180
+ });
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+
9
+ function removeSTATasks() {
10
+ const tasksFile = path.join(__dirname, '..', '.stackmemory', 'tasks.jsonl');
11
+
12
+ if (!fs.existsSync(tasksFile)) {
13
+ console.error('❌ Tasks file not found:', tasksFile);
14
+ return;
15
+ }
16
+
17
+ // Create backup first
18
+ const backupFile = tasksFile + '.backup-' + Date.now();
19
+ fs.copyFileSync(tasksFile, backupFile);
20
+ console.log(`💾 Backup created: ${backupFile}`);
21
+
22
+ // Read all tasks
23
+ const lines = fs.readFileSync(tasksFile, 'utf8').split('\n').filter(l => l.trim());
24
+
25
+ const keptTasks = [];
26
+ const removedTasks = [];
27
+
28
+ for (const line of lines) {
29
+ try {
30
+ const task = JSON.parse(line);
31
+
32
+ // Check if task has STA- in title
33
+ const hasSTAId = task.title?.includes('[STA-');
34
+
35
+ if (hasSTAId) {
36
+ removedTasks.push(task);
37
+ } else {
38
+ keptTasks.push(line);
39
+ }
40
+ } catch (e) {
41
+ // Keep unparseable lines as-is
42
+ keptTasks.push(line);
43
+ }
44
+ }
45
+
46
+ console.log(`\n📊 Task Analysis:`);
47
+ console.log(` Total tasks: ${lines.length}`);
48
+ console.log(` STA tasks to remove: ${removedTasks.length}`);
49
+ console.log(` Tasks to keep: ${keptTasks.length}`);
50
+
51
+ if (removedTasks.length > 0) {
52
+ console.log('\n🗑️ Removing STA tasks:');
53
+ for (const task of removedTasks.slice(0, 10)) {
54
+ const match = task.title.match(/\[(STA-\d+)\]/);
55
+ console.log(` - ${match?.[1] || 'STA-???'}: ${task.title.substring(0, 60)}...`);
56
+ }
57
+ if (removedTasks.length > 10) {
58
+ console.log(` ... and ${removedTasks.length - 10} more`);
59
+ }
60
+
61
+ // Write cleaned file
62
+ fs.writeFileSync(tasksFile, keptTasks.join('\n') + '\n');
63
+ console.log(`\n✅ Removed ${removedTasks.length} STA tasks`);
64
+ console.log(`📂 ${keptTasks.length} tasks remaining`);
65
+ } else {
66
+ console.log('\n✅ No STA tasks found to remove');
67
+ }
68
+ }
69
+
70
+ removeSTATasks();