@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,230 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Analyze remaining tasks after first deletion round
5
+ */
6
+
7
+ import 'dotenv/config';
8
+ import fs from 'fs';
9
+
10
+ const API_KEY = process.env.LINEAR_OAUTH_TOKEN || process.env.LINEAR_API_KEY;
11
+ if (!API_KEY) {
12
+ console.error('āŒ LINEAR_OAUTH_TOKEN or LINEAR_API_KEY environment variable not set');
13
+ console.log('Please set LINEAR_OAUTH_TOKEN or LINEAR_API_KEY in your .env file or export it in your shell');
14
+ process.exit(1);
15
+ }
16
+
17
+ async function fetchAllIssues() {
18
+ const query = `
19
+ query GetAllIssues($after: String) {
20
+ issues(first: 100, after: $after, includeArchived: false) {
21
+ nodes {
22
+ id
23
+ identifier
24
+ title
25
+ description
26
+ state {
27
+ id
28
+ name
29
+ type
30
+ }
31
+ createdAt
32
+ updatedAt
33
+ priority
34
+ estimate
35
+ project {
36
+ id
37
+ name
38
+ }
39
+ team {
40
+ id
41
+ key
42
+ name
43
+ }
44
+ }
45
+ pageInfo {
46
+ hasNextPage
47
+ endCursor
48
+ }
49
+ }
50
+ }
51
+ `;
52
+
53
+ let allIssues = [];
54
+ let hasNextPage = true;
55
+ let cursor = null;
56
+
57
+ while (hasNextPage) {
58
+ const response = await fetch('https://api.linear.app/graphql', {
59
+ method: 'POST',
60
+ headers: {
61
+ 'Authorization': `Bearer ${API_KEY}`,
62
+ 'Content-Type': 'application/json'
63
+ },
64
+ body: JSON.stringify({
65
+ query,
66
+ variables: { after: cursor }
67
+ })
68
+ });
69
+
70
+ const result = await response.json();
71
+
72
+ if (result.errors) {
73
+ throw new Error(result.errors[0].message);
74
+ }
75
+
76
+ allIssues = allIssues.concat(result.data.issues.nodes);
77
+ hasNextPage = result.data.issues.pageInfo.hasNextPage;
78
+ cursor = result.data.issues.pageInfo.endCursor;
79
+
80
+ console.log(` Fetched ${allIssues.length} issues...`);
81
+ }
82
+
83
+ return allIssues;
84
+ }
85
+
86
+ async function analyzeRemainingDuplicates() {
87
+ try {
88
+ console.log('šŸ” Analyzing remaining tasks for duplicates...\n');
89
+ console.log('šŸ“„ Fetching all tasks from Linear...');
90
+
91
+ const allTasks = await fetchAllIssues();
92
+ console.log(`šŸ“Š Total tasks remaining: ${allTasks.length}\n`);
93
+
94
+ // Group by normalized title to find duplicates
95
+ const titleGroups = new Map();
96
+
97
+ allTasks.forEach(task => {
98
+ // Normalize title for comparison
99
+ const normalizedTitle = task.title
100
+ .toLowerCase()
101
+ .replace(/\[.*?\]/g, '') // Remove brackets
102
+ .replace(/sta-\d+/gi, '') // Remove STA references
103
+ .replace(/eng-\d+/gi, '') // Remove ENG references
104
+ .replace(/\s+/g, ' ') // Normalize whitespace
105
+ .trim();
106
+
107
+ if (!titleGroups.has(normalizedTitle)) {
108
+ titleGroups.set(normalizedTitle, []);
109
+ }
110
+ titleGroups.get(normalizedTitle).push(task);
111
+ });
112
+
113
+ // Find groups with duplicates
114
+ const duplicateGroups = Array.from(titleGroups.entries())
115
+ .filter(([_, tasks]) => tasks.length > 1)
116
+ .sort((a, b) => b[1].length - a[1].length);
117
+
118
+ console.log('šŸ“‹ REMAINING DUPLICATES ANALYSIS');
119
+ console.log('=' .repeat(60));
120
+
121
+ console.log(`\nFound ${duplicateGroups.length} groups of duplicates:\n`);
122
+
123
+ let totalDuplicates = 0;
124
+ const tasksToDelete = [];
125
+
126
+ duplicateGroups.forEach(([title, tasks]) => {
127
+ console.log(`\n"${title.substring(0, 60)}..." - ${tasks.length} copies:`);
128
+
129
+ // Sort by status and date to determine which to keep
130
+ const sorted = tasks.sort((a, b) => {
131
+ // Keep in-progress tasks
132
+ if (a.state.type === 'started') return -1;
133
+ if (b.state.type === 'started') return 1;
134
+ // Keep newer tasks
135
+ return new Date(b.updatedAt) - new Date(a.updatedAt);
136
+ });
137
+
138
+ sorted.forEach((task, index) => {
139
+ const keep = index === 0 ? 'āœ… KEEP' : 'āŒ DELETE';
140
+ console.log(` ${keep} ${task.identifier}: ${task.state.name}`);
141
+
142
+ if (index > 0) {
143
+ tasksToDelete.push(task);
144
+ totalDuplicates++;
145
+ }
146
+ });
147
+ });
148
+
149
+ // Look for specific patterns that might have been missed
150
+ console.log('\n\nšŸ” CHECKING SPECIFIC PATTERNS:');
151
+
152
+ const patterns = [
153
+ 'Enable TypeScript Strict Mode',
154
+ 'TypeScript Strict Mode',
155
+ 'strict mode',
156
+ 'Enhanced CLI Commands',
157
+ 'CLI Commands',
158
+ 'Performance Optimization',
159
+ 'Error Handling',
160
+ 'Security Audit',
161
+ 'Testing Suite',
162
+ 'Refactor Large Files'
163
+ ];
164
+
165
+ patterns.forEach(pattern => {
166
+ const matches = allTasks.filter(task =>
167
+ task.title.toLowerCase().includes(pattern.toLowerCase())
168
+ );
169
+
170
+ if (matches.length > 1) {
171
+ console.log(`\n"${pattern}": ${matches.length} tasks found`);
172
+ matches.forEach(task => {
173
+ console.log(` • ${task.identifier}: ${task.title.substring(0, 60)}...`);
174
+ });
175
+ }
176
+ });
177
+
178
+ console.log('\n\nšŸŽÆ DELETION SUMMARY');
179
+ console.log('=' .repeat(60));
180
+ console.log(`Current tasks: ${allTasks.length}`);
181
+ console.log(`Additional duplicates to delete: ${totalDuplicates}`);
182
+ console.log(`Tasks after cleanup: ${allTasks.length - totalDuplicates}`);
183
+
184
+ // Save deletion list
185
+ const deleteList = {
186
+ timestamp: new Date().toISOString(),
187
+ summary: {
188
+ currentTotal: allTasks.length,
189
+ toDelete: totalDuplicates,
190
+ afterDeletion: allTasks.length - totalDuplicates
191
+ },
192
+ duplicateGroups: duplicateGroups.map(([title, tasks]) => ({
193
+ title,
194
+ count: tasks.length,
195
+ tasks: tasks.map(t => ({
196
+ id: t.id,
197
+ identifier: t.identifier,
198
+ title: t.title,
199
+ state: t.state.name
200
+ }))
201
+ })),
202
+ tasks: tasksToDelete.map(t => ({
203
+ id: t.id,
204
+ identifier: t.identifier,
205
+ title: t.title,
206
+ state: t.state.name
207
+ }))
208
+ };
209
+
210
+ const filename = `remaining-duplicates-${new Date().toISOString().split('T')[0]}.json`;
211
+ fs.writeFileSync(filename, JSON.stringify(deleteList, null, 2));
212
+
213
+ console.log(`\nšŸ’¾ Deletion list saved to: ${filename}`);
214
+ console.log('\nTo delete these remaining duplicates, run:');
215
+ console.log(' node scripts/delete-remaining-duplicates.js\n');
216
+
217
+ return deleteList;
218
+
219
+ } catch (error) {
220
+ console.error('āŒ Analysis failed:', error.message);
221
+ process.exit(1);
222
+ }
223
+ }
224
+
225
+ // Run if called directly
226
+ if (import.meta.url === `file://${process.argv[1]}`) {
227
+ analyzeRemainingDuplicates().catch(console.error);
228
+ }
229
+
230
+ export { analyzeRemainingDuplicates };
@@ -0,0 +1,292 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Analyze Linear workspace specifically for STA duplicate tasks
5
+ */
6
+
7
+ import 'dotenv/config';
8
+ import { LinearRestClient } from '../dist/integrations/linear/rest-client.js';
9
+ import fs from 'fs';
10
+
11
+ // Load API key from environment
12
+ const API_KEY = process.env.LINEAR_API_KEY;
13
+ if (!API_KEY) {
14
+ console.error('āŒ LINEAR_API_KEY environment variable not set');
15
+ console.log('Please set LINEAR_API_KEY in your .env file or export it in your shell');
16
+ process.exit(1);
17
+ }
18
+
19
+ async function analyzeSTADuplicates() {
20
+ try {
21
+ console.log('šŸ” Analyzing Linear workspace for STA duplicates and unneeded tasks...\n');
22
+
23
+ const client = new LinearRestClient(API_KEY);
24
+
25
+ // Fetch all tasks
26
+ console.log('šŸ“„ Fetching all tasks from Linear...');
27
+ const allTasks = await client.getAllTasks(true);
28
+ console.log(`šŸ“Š Total tasks in workspace: ${allTasks.length}\n`);
29
+
30
+ // Filter STA tasks
31
+ const staTasks = allTasks.filter(task =>
32
+ task.identifier.startsWith('STA-') ||
33
+ task.title.includes('STA-') ||
34
+ task.title.includes('[STA-')
35
+ );
36
+
37
+ console.log(`šŸ“Œ Found ${staTasks.length} STA-related tasks\n`);
38
+
39
+ // Analyze patterns
40
+ const staByNumber = new Map(); // STA number -> tasks
41
+ const completedSTA = [];
42
+ const canceledSTA = [];
43
+ const duplicateTitles = new Map();
44
+ const lowValuePatterns = [];
45
+ const backlogSTA = [];
46
+ const todoSTA = [];
47
+
48
+ // Group tasks by patterns
49
+ staTasks.forEach(task => {
50
+ const state = task.state.type;
51
+ const status = task.state.name;
52
+
53
+ // Extract STA number
54
+ const staMatch = task.identifier.match(/STA-(\d+)/);
55
+ if (staMatch) {
56
+ const staNum = parseInt(staMatch[1]);
57
+ if (!staByNumber.has(staNum)) {
58
+ staByNumber.set(staNum, []);
59
+ }
60
+ staByNumber.get(staNum).push(task);
61
+ }
62
+
63
+ // Check for completed/canceled
64
+ if (state === 'completed') {
65
+ completedSTA.push(task);
66
+ } else if (state === 'canceled') {
67
+ canceledSTA.push(task);
68
+ } else if (state === 'backlog' || status === 'Backlog') {
69
+ backlogSTA.push(task);
70
+ } else if (state === 'triage' || status === 'Triage' || status === 'Todo') {
71
+ todoSTA.push(task);
72
+ }
73
+
74
+ // Check for low-value patterns
75
+ if (task.title.includes('Documentation TODO') ||
76
+ task.title.includes('Meeting') ||
77
+ task.title.includes('Task Analytics Dashboard') ||
78
+ task.title.includes('Weekly Sync') ||
79
+ task.title.includes('Standup') ||
80
+ task.title.includes('[Duplicate]') ||
81
+ task.title.includes('Test Task') ||
82
+ task.description?.includes('auto-generated')) {
83
+ lowValuePatterns.push(task);
84
+ }
85
+
86
+ // Find duplicate titles
87
+ const baseTitle = task.title.replace(/\[.*?\]/g, '').trim().toLowerCase();
88
+ if (!duplicateTitles.has(baseTitle)) {
89
+ duplicateTitles.set(baseTitle, []);
90
+ }
91
+ duplicateTitles.get(baseTitle).push(task);
92
+ });
93
+
94
+ // Find true duplicates (same STA number)
95
+ const duplicateSTANumbers = Array.from(staByNumber.entries())
96
+ .filter(([_, tasks]) => tasks.length > 1)
97
+ .sort((a, b) => b[1].length - a[1].length);
98
+
99
+ // Find duplicate titles
100
+ const trueDuplicateTitles = Array.from(duplicateTitles.entries())
101
+ .filter(([_, tasks]) => tasks.length > 1)
102
+ .sort((a, b) => b[1].length - a[1].length);
103
+
104
+ // Generate report
105
+ console.log('šŸ“‹ STA DUPLICATE ANALYSIS REPORT');
106
+ console.log('=' .repeat(60));
107
+
108
+ console.log('\nšŸ“Š SUMMARY:');
109
+ console.log(`Total tasks: ${allTasks.length}`);
110
+ console.log(`STA tasks: ${staTasks.length}`);
111
+ console.log(`Completed STA: ${completedSTA.length}`);
112
+ console.log(`Canceled STA: ${canceledSTA.length}`);
113
+ console.log(`Backlog STA: ${backlogSTA.length}`);
114
+ console.log(`Todo/Triage STA: ${todoSTA.length}`);
115
+ console.log(`Low-value patterns: ${lowValuePatterns.length}`);
116
+
117
+ console.log('\nšŸ”„ DUPLICATE STA NUMBERS:');
118
+ if (duplicateSTANumbers.length > 0) {
119
+ console.log(`Found ${duplicateSTANumbers.length} STA numbers with duplicates:\n`);
120
+ duplicateSTANumbers.slice(0, 10).forEach(([staNum, tasks]) => {
121
+ console.log(` STA-${staNum} - ${tasks.length} copies:`);
122
+ tasks.forEach(task => {
123
+ console.log(` • ${task.identifier}: "${task.title.substring(0, 50)}..." (${task.state.name})`);
124
+ });
125
+ });
126
+ } else {
127
+ console.log('No duplicate STA numbers found');
128
+ }
129
+
130
+ console.log('\nšŸ“ DUPLICATE TITLES:');
131
+ if (trueDuplicateTitles.length > 0) {
132
+ console.log(`Found ${trueDuplicateTitles.length} duplicate title groups:\n`);
133
+ trueDuplicateTitles.slice(0, 10).forEach(([title, tasks]) => {
134
+ console.log(` "${title.substring(0, 50)}..." - ${tasks.length} copies:`);
135
+ tasks.slice(0, 3).forEach(task => {
136
+ console.log(` • ${task.identifier}: ${task.state.name}`);
137
+ });
138
+ });
139
+ }
140
+
141
+ console.log('\nšŸ—‘ļø LOW-VALUE PATTERNS:');
142
+ if (lowValuePatterns.length > 0) {
143
+ console.log(`Found ${lowValuePatterns.length} low-value tasks:\n`);
144
+ const patterns = {
145
+ 'Documentation TODOs': lowValuePatterns.filter(t => t.title.includes('Documentation TODO')),
146
+ 'Meeting tasks': lowValuePatterns.filter(t => t.title.includes('Meeting')),
147
+ 'Analytics Dashboard': lowValuePatterns.filter(t => t.title.includes('Task Analytics Dashboard')),
148
+ 'Test/Auto-generated': lowValuePatterns.filter(t => t.title.includes('Test Task') || t.description?.includes('auto-generated'))
149
+ };
150
+
151
+ Object.entries(patterns).forEach(([category, tasks]) => {
152
+ if (tasks.length > 0) {
153
+ console.log(` ${category}: ${tasks.length} tasks`);
154
+ tasks.slice(0, 3).forEach(task => {
155
+ console.log(` • ${task.identifier}: ${task.title.substring(0, 50)}...`);
156
+ });
157
+ }
158
+ });
159
+ }
160
+
161
+ // Build deletion list
162
+ const toDelete = [];
163
+
164
+ // Add duplicates (keep the first one of each group)
165
+ duplicateSTANumbers.forEach(([_, tasks]) => {
166
+ // Keep the one that's in progress or most recently updated
167
+ const sorted = tasks.sort((a, b) => {
168
+ if (a.state.type === 'started') return -1;
169
+ if (b.state.type === 'started') return 1;
170
+ return new Date(b.updatedAt) - new Date(a.updatedAt);
171
+ });
172
+ toDelete.push(...sorted.slice(1)); // Delete all but the first
173
+ });
174
+
175
+ // Add completed and canceled
176
+ toDelete.push(...completedSTA);
177
+ toDelete.push(...canceledSTA);
178
+
179
+ // Add low-value patterns (but not if they're already in the list)
180
+ lowValuePatterns.forEach(task => {
181
+ if (!toDelete.find(t => t.id === task.id)) {
182
+ toDelete.push(task);
183
+ }
184
+ });
185
+
186
+ // Remove duplicates from deletion list
187
+ const uniqueToDelete = Array.from(new Set(toDelete.map(t => t.id)))
188
+ .map(id => toDelete.find(t => t.id === id))
189
+ .sort((a, b) => {
190
+ const aNum = parseInt(a.identifier.replace('STA-', ''));
191
+ const bNum = parseInt(b.identifier.replace('STA-', ''));
192
+ return aNum - bNum;
193
+ });
194
+
195
+ console.log('\nšŸŽÆ DELETION RECOMMENDATIONS');
196
+ console.log('=' .repeat(60));
197
+ console.log(`Total STA tasks: ${staTasks.length}`);
198
+ console.log(`Tasks to delete: ${uniqueToDelete.length}`);
199
+ console.log(`STA tasks remaining: ${staTasks.length - uniqueToDelete.length}`);
200
+ console.log(`Total workspace after deletion: ${allTasks.length - uniqueToDelete.length}\n`);
201
+
202
+ // Group deletions by reason
203
+ const deleteCategories = {
204
+ 'Duplicate STA Numbers': [],
205
+ 'Completed Tasks': [],
206
+ 'Canceled Tasks': [],
207
+ 'Low-Value Patterns': []
208
+ };
209
+
210
+ uniqueToDelete.forEach(task => {
211
+ if (completedSTA.find(t => t.id === task.id)) {
212
+ deleteCategories['Completed Tasks'].push(task.identifier);
213
+ } else if (canceledSTA.find(t => t.id === task.id)) {
214
+ deleteCategories['Canceled Tasks'].push(task.identifier);
215
+ } else if (lowValuePatterns.find(t => t.id === task.id)) {
216
+ deleteCategories['Low-Value Patterns'].push(task.identifier);
217
+ } else {
218
+ deleteCategories['Duplicate STA Numbers'].push(task.identifier);
219
+ }
220
+ });
221
+
222
+ console.log('šŸ“ Tasks to delete by category:\n');
223
+ Object.entries(deleteCategories).forEach(([category, tasks]) => {
224
+ if (tasks.length > 0) {
225
+ console.log(`${category} (${tasks.length} tasks):`);
226
+ const preview = tasks.slice(0, 20).join(', ');
227
+ console.log(` ${preview}${tasks.length > 20 ? ` ... and ${tasks.length - 20} more` : ''}\n`);
228
+ }
229
+ });
230
+
231
+ // Show what will remain
232
+ const remaining = staTasks.filter(t => !uniqueToDelete.find(d => d.id === t.id));
233
+ const inProgress = remaining.filter(t => t.state.type === 'started');
234
+ const todo = remaining.filter(t => t.state.type === 'unstarted' && t.state.name !== 'Backlog');
235
+
236
+ console.log('āœ… WHAT WILL REMAIN:');
237
+ console.log(`${remaining.length} STA tasks total:`);
238
+ console.log(` • In Progress: ${inProgress.length} tasks`);
239
+ console.log(` • Todo/Ready: ${todo.length} tasks`);
240
+ console.log(` • Backlog: ${remaining.filter(t => t.state.name === 'Backlog').length} tasks\n`);
241
+
242
+ // Save deletion list to file
243
+ const deleteList = {
244
+ timestamp: new Date().toISOString(),
245
+ summary: {
246
+ totalWorkspace: allTasks.length,
247
+ totalSTA: staTasks.length,
248
+ toDelete: uniqueToDelete.length,
249
+ remainingSTA: staTasks.length - uniqueToDelete.length,
250
+ remainingTotal: allTasks.length - uniqueToDelete.length
251
+ },
252
+ categories: deleteCategories,
253
+ tasks: uniqueToDelete.map(t => ({
254
+ id: t.id,
255
+ identifier: t.identifier,
256
+ title: t.title,
257
+ state: t.state.name,
258
+ reason: completedSTA.find(c => c.id === t.id) ? 'completed' :
259
+ canceledSTA.find(c => c.id === t.id) ? 'canceled' :
260
+ lowValuePatterns.find(l => l.id === t.id) ? 'low-value' : 'duplicate'
261
+ }))
262
+ };
263
+
264
+ const filename = `sta-deletion-list-${new Date().toISOString().split('T')[0]}.json`;
265
+ fs.writeFileSync(filename, JSON.stringify(deleteList, null, 2));
266
+ console.log(`šŸ’¾ Deletion list saved to: ${filename}\n`);
267
+
268
+ console.log('šŸŽÆ NEXT STEPS:');
269
+ console.log('1. Review the deletion list above');
270
+ console.log('2. Run the delete script to remove these tasks');
271
+ console.log(`3. This will free up ${uniqueToDelete.length} tasks worth of capacity\n`);
272
+
273
+ console.log('To delete these tasks, run:');
274
+ console.log(' node scripts/delete-sta-tasks.js\n');
275
+
276
+ return deleteList;
277
+
278
+ } catch (error) {
279
+ console.error('āŒ Analysis failed:', error.message);
280
+ if (error.message.includes('401')) {
281
+ console.error('\nāš ļø Authentication failed. Check your LINEAR_API_KEY');
282
+ }
283
+ process.exit(1);
284
+ }
285
+ }
286
+
287
+ // Run if called directly
288
+ if (import.meta.url === `file://${process.argv[1]}`) {
289
+ analyzeSTADuplicates().catch(console.error);
290
+ }
291
+
292
+ export { analyzeSTADuplicates };