@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.
Files changed (234) hide show
  1. package/dist/cli/claude-sm.js +51 -5
  2. package/dist/cli/claude-sm.js.map +2 -2
  3. package/dist/cli/codex-sm.js +52 -19
  4. package/dist/cli/codex-sm.js.map +2 -2
  5. package/dist/cli/commands/db.js +143 -0
  6. package/dist/cli/commands/db.js.map +7 -0
  7. package/dist/cli/commands/login.js +50 -0
  8. package/dist/cli/commands/login.js.map +7 -0
  9. package/dist/cli/commands/migrate.js +178 -0
  10. package/dist/cli/commands/migrate.js.map +7 -0
  11. package/dist/cli/commands/onboard.js +158 -2
  12. package/dist/cli/commands/onboard.js.map +2 -2
  13. package/dist/cli/commands/skills.js +15 -2
  14. package/dist/cli/commands/skills.js.map +2 -2
  15. package/dist/cli/index.js +118 -834
  16. package/dist/cli/index.js.map +3 -3
  17. package/dist/core/context/dual-stack-manager.js +1 -1
  18. package/dist/core/context/dual-stack-manager.js.map +1 -1
  19. package/dist/core/context/frame-database.js +1 -0
  20. package/dist/core/context/frame-database.js.map +2 -2
  21. package/dist/core/context/frame-manager.js +59 -2
  22. package/dist/core/context/frame-manager.js.map +2 -2
  23. package/dist/core/database/database-adapter.js +6 -1
  24. package/dist/core/database/database-adapter.js.map +2 -2
  25. package/dist/core/database/sqlite-adapter.js +60 -2
  26. package/dist/core/database/sqlite-adapter.js.map +2 -2
  27. package/dist/integrations/claude-code/subagent-client.js +106 -3
  28. package/dist/integrations/claude-code/subagent-client.js.map +2 -2
  29. package/dist/servers/railway/config.js +51 -0
  30. package/dist/servers/railway/config.js.map +7 -0
  31. package/dist/servers/railway/index-enhanced.js +156 -0
  32. package/dist/servers/railway/index-enhanced.js.map +7 -0
  33. package/dist/servers/railway/index.js +843 -82
  34. package/dist/servers/railway/index.js.map +3 -3
  35. package/dist/servers/railway/minimal.js +48 -3
  36. package/dist/servers/railway/minimal.js.map +2 -2
  37. package/dist/servers/railway/storage-test.js +455 -0
  38. package/dist/servers/railway/storage-test.js.map +7 -0
  39. package/dist/skills/claude-skills.js +13 -12
  40. package/dist/skills/claude-skills.js.map +2 -2
  41. package/dist/skills/recursive-agent-orchestrator.js +27 -18
  42. package/dist/skills/recursive-agent-orchestrator.js.map +2 -2
  43. package/dist/skills/unified-rlm-orchestrator.js.map +2 -2
  44. package/package.json +13 -21
  45. package/scripts/README-TESTING.md +186 -0
  46. package/scripts/analyze-cli-security.js +288 -0
  47. package/scripts/archive/add-phase-tasks-to-linear.js +163 -0
  48. package/scripts/archive/analyze-linear-duplicates.js +214 -0
  49. package/scripts/archive/analyze-remaining-duplicates.js +230 -0
  50. package/scripts/archive/analyze-sta-duplicates.js +292 -0
  51. package/scripts/archive/analyze-sta-graphql.js +399 -0
  52. package/scripts/archive/cancel-duplicate-tasks.ts +246 -0
  53. package/scripts/archive/check-all-duplicates.ts +419 -0
  54. package/scripts/archive/clean-duplicate-tasks.js +114 -0
  55. package/scripts/archive/cleanup-duplicate-tasks.ts +286 -0
  56. package/scripts/archive/create-phase-tasks.js +387 -0
  57. package/scripts/archive/delete-linear-duplicates.js +182 -0
  58. package/scripts/archive/delete-remaining-duplicates.js +158 -0
  59. package/scripts/archive/delete-sta-duplicates.js +201 -0
  60. package/scripts/archive/delete-sta-oauth.js +201 -0
  61. package/scripts/archive/export-sta-tasks.js +62 -0
  62. package/scripts/archive/install-auto-sync.js +266 -0
  63. package/scripts/archive/install-chromadb-hooks.sh +133 -0
  64. package/scripts/archive/install-enhanced-clear-hooks.sh +431 -0
  65. package/scripts/archive/install-post-task-hooks.sh +289 -0
  66. package/scripts/archive/install-stackmemory-hooks.sh +420 -0
  67. package/scripts/archive/merge-linear-duplicates-safe.ts +362 -0
  68. package/scripts/archive/merge-linear-duplicates.ts +180 -0
  69. package/scripts/archive/remove-sta-tasks.js +70 -0
  70. package/scripts/archive/setup-background-sync.sh +168 -0
  71. package/scripts/archive/setup-claude-auto-triggers.sh +181 -0
  72. package/scripts/archive/setup-claude-autostart.sh +305 -0
  73. package/scripts/archive/setup-git-hooks.sh +25 -0
  74. package/scripts/archive/setup-linear-oauth.sh +46 -0
  75. package/scripts/archive/setup-mcp.sh +113 -0
  76. package/scripts/archive/setup-railway-deployment.sh +81 -0
  77. package/scripts/auto-handoff.sh +262 -0
  78. package/scripts/background-sync-manager.js +416 -0
  79. package/scripts/benchmark-performance.ts +57 -0
  80. package/scripts/check-redis.ts +48 -0
  81. package/scripts/chromadb-auto-loader.sh +128 -0
  82. package/scripts/chromadb-context-loader.js +479 -0
  83. package/scripts/claude-chromadb-hook.js +460 -0
  84. package/scripts/claude-code-wrapper.sh +66 -0
  85. package/scripts/claude-linear-skill.js +455 -0
  86. package/scripts/claude-pre-commit.sh +302 -0
  87. package/scripts/claude-sm-autostart.js +532 -0
  88. package/scripts/claude-sm-setup.sh +367 -0
  89. package/scripts/claude-with-chromadb.sh +69 -0
  90. package/scripts/claude-worktree-manager.sh +323 -0
  91. package/scripts/claude-worktree-monitor.sh +371 -0
  92. package/scripts/claude-worktree-setup.sh +327 -0
  93. package/scripts/clean-linear-backlog.js +273 -0
  94. package/scripts/cleanup-old-sessions.sh +57 -0
  95. package/scripts/codex-wrapper.sh +88 -0
  96. package/scripts/create-sandbox.sh +269 -0
  97. package/scripts/debug-linear-update.js +174 -0
  98. package/scripts/delete-linear-tasks.js +167 -0
  99. package/scripts/deploy.sh +89 -0
  100. package/scripts/deployment/railway.sh +352 -0
  101. package/scripts/deployment/test-deployment.js +194 -0
  102. package/scripts/detect-and-rehydrate.js +162 -0
  103. package/scripts/detect-and-rehydrate.mjs +165 -0
  104. package/scripts/development/create-demo-tasks.js +143 -0
  105. package/scripts/development/debug-frame-test.js +16 -0
  106. package/scripts/development/demo-auto-sync.js +128 -0
  107. package/scripts/development/fix-all-imports.js +213 -0
  108. package/scripts/development/fix-imports.js +229 -0
  109. package/scripts/development/fix-lint-loop.cjs +103 -0
  110. package/scripts/development/fix-project-id.ts +161 -0
  111. package/scripts/development/fix-strict-mode-issues.ts +291 -0
  112. package/scripts/development/reorganize-structure.sh +228 -0
  113. package/scripts/development/test-persistence-direct.js +148 -0
  114. package/scripts/development/test-persistence.js +114 -0
  115. package/scripts/development/test-tasks.js +93 -0
  116. package/scripts/development/update-imports.js +212 -0
  117. package/scripts/fetch-linear-status.js +125 -0
  118. package/scripts/git-hooks/README.md +310 -0
  119. package/scripts/git-hooks/branch-context-manager.sh +342 -0
  120. package/scripts/git-hooks/post-checkout-stackmemory.sh +63 -0
  121. package/scripts/git-hooks/post-commit-stackmemory.sh +305 -0
  122. package/scripts/git-hooks/pre-commit-stackmemory.sh +275 -0
  123. package/scripts/hooks/cleanup-shell.sh +130 -0
  124. package/scripts/hooks/task-complete.sh +114 -0
  125. package/scripts/initialize.ts +129 -0
  126. package/scripts/install-claude-hooks-auto.js +104 -0
  127. package/scripts/install-claude-hooks.sh +133 -0
  128. package/scripts/install-global.sh +296 -0
  129. package/scripts/install.sh +235 -0
  130. package/scripts/linear-auto-sync.js +262 -0
  131. package/scripts/linear-auto-sync.sh +161 -0
  132. package/scripts/linear-sync-daemon.js +150 -0
  133. package/scripts/linear-task-review.js +237 -0
  134. package/scripts/list-linear-tasks.ts +178 -0
  135. package/scripts/mcp-proxy.js +66 -0
  136. package/scripts/opencode-wrapper.sh +85 -0
  137. package/scripts/publish-local.js +74 -0
  138. package/scripts/query-chromadb.ts +201 -0
  139. package/scripts/railway-env-setup.sh +39 -0
  140. package/scripts/reconcile-local-tasks.js +170 -0
  141. package/scripts/recreate-frames-db.js +89 -0
  142. package/scripts/setup/claude-integration.js +138 -0
  143. package/scripts/setup/configure-alias.js +125 -0
  144. package/scripts/setup/configure-codex-alias.js +161 -0
  145. package/scripts/setup/configure-opencode-alias.js +175 -0
  146. package/scripts/setup-claude-integration.js +204 -0
  147. package/scripts/setup-claude-integration.sh +183 -0
  148. package/scripts/setup-railway-deployment.sh +37 -0
  149. package/scripts/setup.sh +31 -0
  150. package/scripts/show-linear-summary.ts +172 -0
  151. package/scripts/stackmemory-auto-handoff.sh +231 -0
  152. package/scripts/stackmemory-daemon.sh +40 -0
  153. package/scripts/start-linear-sync-daemon.sh +141 -0
  154. package/scripts/start-temporal-paradox.sh +214 -0
  155. package/scripts/status.ts +159 -0
  156. package/scripts/sync-and-clean-tasks.js +258 -0
  157. package/scripts/sync-frames-from-railway.js +228 -0
  158. package/scripts/sync-linear-graphql.js +303 -0
  159. package/scripts/sync-linear-tasks.js +186 -0
  160. package/scripts/test-auto-triggers.sh +57 -0
  161. package/scripts/test-browser-mcp.js +74 -0
  162. package/scripts/test-chromadb-full.js +115 -0
  163. package/scripts/test-chromadb-hooks.sh +28 -0
  164. package/scripts/test-chromadb-sync.ts +245 -0
  165. package/scripts/test-cli-security.js +293 -0
  166. package/scripts/test-hooks-persistence.sh +220 -0
  167. package/scripts/test-installation-scenarios.sh +359 -0
  168. package/scripts/test-installation.sh +224 -0
  169. package/scripts/test-mcp.js +163 -0
  170. package/scripts/test-pre-publish-quick.sh +75 -0
  171. package/scripts/test-quality-gates.sh +263 -0
  172. package/scripts/test-railway-db.js +222 -0
  173. package/scripts/test-redis-storage.ts +490 -0
  174. package/scripts/test-rlm-basic.sh +122 -0
  175. package/scripts/test-rlm-comprehensive.sh +260 -0
  176. package/scripts/test-rlm-e2e.sh +268 -0
  177. package/scripts/test-rlm-simple.js +90 -0
  178. package/scripts/test-rlm.js +110 -0
  179. package/scripts/test-session-handoff.sh +165 -0
  180. package/scripts/test-shell-integration.sh +275 -0
  181. package/scripts/testing/ab-test-runner.ts +508 -0
  182. package/scripts/testing/collect-metrics.ts +457 -0
  183. package/scripts/testing/quick-effectiveness-demo.js +187 -0
  184. package/scripts/testing/real-performance-test.js +422 -0
  185. package/scripts/testing/run-effectiveness-tests.sh +176 -0
  186. package/scripts/testing/scripts/testing/ab-test-runner.js +363 -0
  187. package/scripts/testing/scripts/testing/collect-metrics.js +292 -0
  188. package/scripts/testing/simple-effectiveness-test.js +310 -0
  189. package/scripts/testing/src/core/context/context-bridge.js +253 -0
  190. package/scripts/testing/src/core/context/frame-manager.js +746 -0
  191. package/scripts/testing/src/core/context/shared-context-layer.js +437 -0
  192. package/scripts/testing/src/core/database/database-adapter.js +54 -0
  193. package/scripts/testing/src/core/errors/index.js +291 -0
  194. package/scripts/testing/src/core/errors/recovery.js +268 -0
  195. package/scripts/testing/src/core/monitoring/logger.js +145 -0
  196. package/scripts/testing/src/core/retrieval/context-retriever.js +516 -0
  197. package/scripts/testing/src/core/session/index.js +1 -0
  198. package/scripts/testing/src/core/session/session-manager.js +323 -0
  199. package/scripts/testing/src/core/trace/cli-trace-wrapper.js +140 -0
  200. package/scripts/testing/src/core/trace/db-trace-wrapper.js +251 -0
  201. package/scripts/testing/src/core/trace/debug-trace.js +398 -0
  202. package/scripts/testing/src/core/trace/index.js +120 -0
  203. package/scripts/testing/src/core/trace/linear-api-wrapper.js +204 -0
  204. package/scripts/update-linear-status.js +268 -0
  205. package/scripts/update-linear-tasks-fixed.js +284 -0
  206. package/scripts/verify-railway-schema.ts +35 -0
  207. package/templates/claude-hooks/hooks.json +5 -0
  208. package/templates/claude-hooks/on-clear.js +56 -0
  209. package/templates/claude-hooks/on-startup.js +56 -0
  210. package/templates/claude-hooks/tool-use-trace.js +67 -0
  211. package/dist/features/tui/components/analytics-panel.js +0 -157
  212. package/dist/features/tui/components/analytics-panel.js.map +0 -7
  213. package/dist/features/tui/components/frame-visualizer.js +0 -377
  214. package/dist/features/tui/components/frame-visualizer.js.map +0 -7
  215. package/dist/features/tui/components/pr-tracker.js +0 -135
  216. package/dist/features/tui/components/pr-tracker.js.map +0 -7
  217. package/dist/features/tui/components/session-monitor.js +0 -299
  218. package/dist/features/tui/components/session-monitor.js.map +0 -7
  219. package/dist/features/tui/components/subagent-fleet.js +0 -395
  220. package/dist/features/tui/components/subagent-fleet.js.map +0 -7
  221. package/dist/features/tui/components/task-board.js +0 -1139
  222. package/dist/features/tui/components/task-board.js.map +0 -7
  223. package/dist/features/tui/index.js +0 -408
  224. package/dist/features/tui/index.js.map +0 -7
  225. package/dist/features/tui/services/data-service.js +0 -641
  226. package/dist/features/tui/services/data-service.js.map +0 -7
  227. package/dist/features/tui/services/linear-task-reader.js +0 -102
  228. package/dist/features/tui/services/linear-task-reader.js.map +0 -7
  229. package/dist/features/tui/services/websocket-client.js +0 -162
  230. package/dist/features/tui/services/websocket-client.js.map +0 -7
  231. package/dist/features/tui/terminal-compat.js +0 -220
  232. package/dist/features/tui/terminal-compat.js.map +0 -7
  233. package/dist/features/tui/types.js +0 -1
  234. package/dist/features/tui/types.js.map +0 -7
@@ -0,0 +1,419 @@
1
+ #!/usr/bin/env tsx
2
+
3
+ /**
4
+ * Comprehensive Duplicate Check Script
5
+ * Scans all tasks in memory and checks for duplicates in Linear
6
+ */
7
+
8
+ import { LinearClient } from '../dist/integrations/linear/client.js';
9
+ import { LinearDuplicateDetector } from '../dist/integrations/linear/sync-enhanced.js';
10
+ import { LinearAuthManager } from '../dist/integrations/linear/auth.js';
11
+ import { join } from 'path';
12
+ import { existsSync, writeFileSync, readFileSync } from 'fs';
13
+ import chalk from 'chalk';
14
+ import ora from 'ora';
15
+ import Table from 'cli-table3';
16
+
17
+ interface DuplicateReport {
18
+ taskId: string;
19
+ taskTitle: string;
20
+ duplicates: Array<{
21
+ linearId: string;
22
+ identifier: string;
23
+ title: string;
24
+ similarity: number;
25
+ url: string;
26
+ }>;
27
+ recommendation: 'merge' | 'skip' | 'review';
28
+ }
29
+
30
+ class DuplicateChecker {
31
+ private linearClient: LinearClient;
32
+ private duplicateDetector: LinearDuplicateDetector;
33
+ private authManager: LinearAuthManager;
34
+ private projectRoot: string;
35
+ private report: DuplicateReport[] = [];
36
+
37
+ constructor() {
38
+ this.projectRoot = process.cwd();
39
+ }
40
+
41
+ async initialize(): Promise<void> {
42
+ // Check database
43
+ const dbPath = join(this.projectRoot, '.stackmemory', 'context.db');
44
+ if (!existsSync(dbPath)) {
45
+ throw new Error(
46
+ 'StackMemory not initialized. Run "stackmemory init" first.'
47
+ );
48
+ }
49
+
50
+ // Initialize Linear auth - check env var first
51
+ const envApiKey = process.env.LINEAR_API_KEY;
52
+
53
+ if (envApiKey) {
54
+ // Use environment variable API key
55
+ this.linearClient = new LinearClient({
56
+ apiKey: envApiKey,
57
+ useBearer: false,
58
+ });
59
+ } else {
60
+ // Try OAuth auth
61
+ this.authManager = new LinearAuthManager(this.projectRoot);
62
+ const token = await this.authManager.getValidToken();
63
+
64
+ if (!token) {
65
+ throw new Error(
66
+ 'Linear not authenticated. Set LINEAR_API_KEY env var or run "stackmemory linear auth".'
67
+ );
68
+ }
69
+
70
+ // Check if using OAuth by looking for refresh token
71
+ const tokens = this.authManager.loadTokens();
72
+ const isOAuth = !!(tokens && tokens.refreshToken);
73
+
74
+ this.linearClient = new LinearClient({
75
+ apiKey: token,
76
+ useBearer: isOAuth,
77
+ onUnauthorized: isOAuth
78
+ ? async () => {
79
+ const refreshed = await this.authManager.refreshAccessToken();
80
+ return refreshed.accessToken;
81
+ }
82
+ : undefined,
83
+ });
84
+ }
85
+
86
+ // Initialize duplicate detector
87
+ this.duplicateDetector = new LinearDuplicateDetector(this.linearClient);
88
+ }
89
+
90
+ async runFullScan(): Promise<void> {
91
+ console.log(chalk.cyan('\nšŸ” Starting Comprehensive Duplicate Check\n'));
92
+
93
+ // Get all tasks from memory
94
+ const spinner = ora('Loading tasks from memory...').start();
95
+
96
+ // Read tasks from JSONL file
97
+ const tasksFile = join(this.projectRoot, '.stackmemory', 'tasks.jsonl');
98
+ const tasksData = existsSync(tasksFile)
99
+ ? readFileSync(tasksFile, 'utf8')
100
+ .split('\n')
101
+ .filter((line) => line.trim())
102
+ : [];
103
+ const tasks = tasksData.map((line) => JSON.parse(line));
104
+
105
+ spinner.succeed(`Loaded ${tasks.length} tasks from memory`);
106
+
107
+ // Get default team ID
108
+ spinner.start('Connecting to Linear...');
109
+ const teams = await this.linearClient.getTeams();
110
+ const defaultTeamId = teams[0]?.id;
111
+ spinner.succeed(`Connected to Linear (Team: ${teams[0]?.name})`);
112
+
113
+ // Progress tracking
114
+ let checked = 0;
115
+ let duplicatesFound = 0;
116
+ const startTime = Date.now();
117
+
118
+ console.log(
119
+ chalk.yellow(`\nšŸ“Š Checking ${tasks.length} tasks for duplicates...\n`)
120
+ );
121
+
122
+ // Check each task for duplicates
123
+ for (const task of tasks) {
124
+ checked++;
125
+
126
+ // Update progress
127
+ const progress = Math.round((checked / tasks.length) * 100);
128
+ spinner.start(
129
+ `[${progress}%] Checking: ${task.title.substring(0, 50)}...`
130
+ );
131
+
132
+ try {
133
+ // Skip if task already has Linear ID mapped
134
+ if (task.external_refs?.linear_id) {
135
+ spinner.info(
136
+ `[${progress}%] Skipped (already mapped): ${task.title.substring(0, 40)}...`
137
+ );
138
+ continue;
139
+ }
140
+
141
+ // Check for duplicates
142
+ const duplicateCheck = await this.duplicateDetector.checkForDuplicate(
143
+ task.title,
144
+ defaultTeamId
145
+ );
146
+
147
+ if (duplicateCheck.isDuplicate && duplicateCheck.existingIssue) {
148
+ duplicatesFound++;
149
+
150
+ // Add to report
151
+ this.report.push({
152
+ taskId: task.id,
153
+ taskTitle: task.title,
154
+ duplicates: [
155
+ {
156
+ linearId: duplicateCheck.existingIssue.id,
157
+ identifier: duplicateCheck.existingIssue.identifier,
158
+ title: duplicateCheck.existingIssue.title,
159
+ similarity: duplicateCheck.similarity || 0,
160
+ url: duplicateCheck.existingIssue.url,
161
+ },
162
+ ],
163
+ recommendation:
164
+ duplicateCheck.similarity! > 0.95
165
+ ? 'merge'
166
+ : duplicateCheck.similarity! > 0.85
167
+ ? 'review'
168
+ : 'skip',
169
+ });
170
+
171
+ spinner.warn(
172
+ `[${progress}%] DUPLICATE FOUND: "${task.title.substring(0, 30)}..." → ${duplicateCheck.existingIssue.identifier} (${Math.round((duplicateCheck.similarity || 0) * 100)}% match)`
173
+ );
174
+ } else {
175
+ spinner.succeed(
176
+ `[${progress}%] No duplicates: ${task.title.substring(0, 40)}...`
177
+ );
178
+ }
179
+
180
+ // Rate limiting delay
181
+ await this.delay(100);
182
+ } catch (error: unknown) {
183
+ spinner.fail(
184
+ `[${progress}%] Error checking task: ${(error as Error).message}`
185
+ );
186
+ }
187
+ }
188
+
189
+ const duration = Math.round((Date.now() - startTime) / 1000);
190
+
191
+ console.log(chalk.green(`\nāœ… Duplicate check completed in ${duration}s`));
192
+ console.log(chalk.cyan(`\nšŸ“ˆ Summary:`));
193
+ console.log(` • Total tasks checked: ${checked}`);
194
+ console.log(` • Duplicates found: ${duplicatesFound}`);
195
+ console.log(
196
+ ` • Check rate: ${Math.round(checked / duration)} tasks/second`
197
+ );
198
+ }
199
+
200
+ displayReport(): void {
201
+ if (this.report.length === 0) {
202
+ console.log(
203
+ chalk.green('\n✨ No duplicates found! Your tasks are unique.\n')
204
+ );
205
+ return;
206
+ }
207
+
208
+ console.log(
209
+ chalk.yellow(`\nāš ļø Found ${this.report.length} potential duplicates:\n`)
210
+ );
211
+
212
+ // Group by recommendation
213
+ const mergeItems = this.report.filter((r) => r.recommendation === 'merge');
214
+ const reviewItems = this.report.filter(
215
+ (r) => r.recommendation === 'review'
216
+ );
217
+ const skipItems = this.report.filter((r) => r.recommendation === 'skip');
218
+
219
+ // Display high confidence duplicates (merge)
220
+ if (mergeItems.length > 0) {
221
+ console.log(
222
+ chalk.red(
223
+ 'šŸ”“ High Confidence Duplicates (>95% match) - Recommend Merge:\n'
224
+ )
225
+ );
226
+ const table = new Table({
227
+ head: ['Local Task', 'Linear Issue', 'Match %', 'URL'],
228
+ style: { head: ['red'] },
229
+ colWidths: [40, 15, 10, 50],
230
+ });
231
+
232
+ mergeItems.forEach((item) => {
233
+ item.duplicates.forEach((dup) => {
234
+ table.push([
235
+ item.taskTitle.substring(0, 38),
236
+ dup.identifier,
237
+ `${Math.round(dup.similarity * 100)}%`,
238
+ dup.url.substring(0, 48),
239
+ ]);
240
+ });
241
+ });
242
+
243
+ console.log(table.toString());
244
+ }
245
+
246
+ // Display medium confidence duplicates (review)
247
+ if (reviewItems.length > 0) {
248
+ console.log(
249
+ chalk.yellow(
250
+ '\n🟔 Medium Confidence Duplicates (85-95% match) - Recommend Review:\n'
251
+ )
252
+ );
253
+ const table = new Table({
254
+ head: ['Local Task', 'Linear Issue', 'Match %', 'Linear Title'],
255
+ style: { head: ['yellow'] },
256
+ colWidths: [35, 15, 10, 40],
257
+ });
258
+
259
+ reviewItems.forEach((item) => {
260
+ item.duplicates.forEach((dup) => {
261
+ table.push([
262
+ item.taskTitle.substring(0, 33),
263
+ dup.identifier,
264
+ `${Math.round(dup.similarity * 100)}%`,
265
+ dup.title.substring(0, 38),
266
+ ]);
267
+ });
268
+ });
269
+
270
+ console.log(table.toString());
271
+ }
272
+
273
+ // Display low confidence (skip)
274
+ if (skipItems.length > 0) {
275
+ console.log(
276
+ chalk.gray(
277
+ `\nšŸ”µ Low Confidence Matches (<85%) - ${skipItems.length} items (not shown)\n`
278
+ )
279
+ );
280
+ }
281
+ }
282
+
283
+ async saveReport(): Promise<void> {
284
+ if (this.report.length === 0) return;
285
+
286
+ const reportPath = join(
287
+ this.projectRoot,
288
+ '.stackmemory',
289
+ 'duplicate-report.json'
290
+ );
291
+ const markdownPath = join(
292
+ this.projectRoot,
293
+ '.stackmemory',
294
+ 'duplicate-report.md'
295
+ );
296
+
297
+ // Save JSON report
298
+ writeFileSync(reportPath, JSON.stringify(this.report, null, 2));
299
+
300
+ // Generate markdown report
301
+ let markdown = '# Linear Duplicate Check Report\n\n';
302
+ markdown += `**Generated:** ${new Date().toLocaleString()}\n`;
303
+ markdown += `**Total Duplicates Found:** ${this.report.length}\n\n`;
304
+
305
+ // High confidence section
306
+ const mergeItems = this.report.filter((r) => r.recommendation === 'merge');
307
+ if (mergeItems.length > 0) {
308
+ markdown += '## šŸ”“ High Confidence Duplicates (>95% match)\n\n';
309
+ markdown += 'These should be merged:\n\n';
310
+ mergeItems.forEach((item) => {
311
+ item.duplicates.forEach((dup) => {
312
+ markdown += `- **${item.taskTitle}**\n`;
313
+ markdown += ` - Linear: [${dup.identifier}](${dup.url}) - ${Math.round(dup.similarity * 100)}% match\n`;
314
+ markdown += ` - Action: MERGE\n\n`;
315
+ });
316
+ });
317
+ }
318
+
319
+ // Medium confidence section
320
+ const reviewItems = this.report.filter(
321
+ (r) => r.recommendation === 'review'
322
+ );
323
+ if (reviewItems.length > 0) {
324
+ markdown += '## 🟔 Medium Confidence Duplicates (85-95% match)\n\n';
325
+ markdown += 'These need manual review:\n\n';
326
+ reviewItems.forEach((item) => {
327
+ item.duplicates.forEach((dup) => {
328
+ markdown += `- **${item.taskTitle}**\n`;
329
+ markdown += ` - Linear: [${dup.identifier}](${dup.url}) - "${dup.title}"\n`;
330
+ markdown += ` - Match: ${Math.round(dup.similarity * 100)}%\n`;
331
+ markdown += ` - Action: REVIEW\n\n`;
332
+ });
333
+ });
334
+ }
335
+
336
+ // Save markdown
337
+ writeFileSync(markdownPath, markdown);
338
+
339
+ console.log(chalk.green(`\nšŸ“ Reports saved:`));
340
+ console.log(chalk.gray(` • JSON: ${reportPath}`));
341
+ console.log(chalk.gray(` • Markdown: ${markdownPath}`));
342
+ }
343
+
344
+ async suggestActions(): Promise<void> {
345
+ const mergeCount = this.report.filter(
346
+ (r) => r.recommendation === 'merge'
347
+ ).length;
348
+ const reviewCount = this.report.filter(
349
+ (r) => r.recommendation === 'review'
350
+ ).length;
351
+
352
+ if (mergeCount > 0 || reviewCount > 0) {
353
+ console.log(chalk.cyan('\nšŸ’” Recommended Actions:\n'));
354
+
355
+ if (mergeCount > 0) {
356
+ console.log(chalk.green('1. Auto-merge high confidence duplicates:'));
357
+ console.log(
358
+ chalk.gray(
359
+ ' stackmemory linear sync --merge-strategy merge_content\n'
360
+ )
361
+ );
362
+ }
363
+
364
+ if (reviewCount > 0) {
365
+ console.log(
366
+ chalk.yellow('2. Review medium confidence duplicates manually:')
367
+ );
368
+ console.log(
369
+ chalk.gray(
370
+ ' Review the duplicate-report.md file and decide per case\n'
371
+ )
372
+ );
373
+ }
374
+
375
+ console.log(
376
+ chalk.blue('3. Enable duplicate prevention for future syncs:')
377
+ );
378
+ console.log(
379
+ chalk.gray(
380
+ ' stackmemory linear sync --daemon --merge-strategy merge_content\n'
381
+ )
382
+ );
383
+ }
384
+ }
385
+
386
+ private delay(ms: number): Promise<void> {
387
+ return new Promise((resolve) => setTimeout(resolve, ms));
388
+ }
389
+ }
390
+
391
+ // Main execution
392
+ async function main() {
393
+ const checker = new DuplicateChecker();
394
+
395
+ try {
396
+ // Initialize
397
+ await checker.initialize();
398
+
399
+ // Run full scan
400
+ await checker.runFullScan();
401
+
402
+ // Display report
403
+ checker.displayReport();
404
+
405
+ // Save reports
406
+ await checker.saveReport();
407
+
408
+ // Suggest actions
409
+ await checker.suggestActions();
410
+
411
+ process.exit(0);
412
+ } catch (error: unknown) {
413
+ console.error(chalk.red('\nāŒ Error:'), (error as Error).message);
414
+ process.exit(1);
415
+ }
416
+ }
417
+
418
+ // Run if executed directly
419
+ main();
@@ -0,0 +1,114 @@
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 cleanDuplicateTasks() {
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
+ const lines = fs.readFileSync(tasksFile, 'utf8').split('\n').filter(l => l.trim());
18
+
19
+ const taskMap = new Map();
20
+ const seenTitles = new Map();
21
+ const duplicates = [];
22
+
23
+ // Process each line
24
+ for (const line of lines) {
25
+ try {
26
+ const task = JSON.parse(line);
27
+
28
+ // Create a unique key based on title and external refs
29
+ let key = task.title;
30
+ if (task.external_refs) {
31
+ const linearId = Object.keys(task.external_refs).find(k => k.startsWith('STA-') || k.startsWith('ENG-'));
32
+ if (linearId) key = linearId;
33
+ }
34
+
35
+ // Normalize title for comparison (remove task IDs from title)
36
+ const normalizedTitle = task.title
37
+ .replace(/^\[[^\]]+\]\s*/, '') // Remove [STA-XXX] or [ENG-XXX] prefix
38
+ .replace(/^\[.*?\]\s*/, '') // Remove priority markers
39
+ .trim();
40
+
41
+ // Track duplicates by normalized title
42
+ if (!seenTitles.has(normalizedTitle)) {
43
+ seenTitles.set(normalizedTitle, task.id);
44
+ taskMap.set(task.id, task);
45
+ } else {
46
+ duplicates.push({
47
+ id: task.id,
48
+ title: task.title,
49
+ keepId: seenTitles.get(normalizedTitle)
50
+ });
51
+ }
52
+ } catch (e) {
53
+ console.warn('Failed to parse line:', e.message);
54
+ }
55
+ }
56
+
57
+ console.log(`\nšŸ“Š Task Analysis:`);
58
+ console.log(` Total lines: ${lines.length}`);
59
+ console.log(` Unique tasks: ${taskMap.size}`);
60
+ console.log(` Duplicates found: ${duplicates.length}`);
61
+
62
+ if (duplicates.length > 0) {
63
+ console.log('\nšŸ”„ Duplicates to remove:');
64
+ duplicates.forEach(d => {
65
+ console.log(` - ${d.id}: ${d.title}`);
66
+ console.log(` (keeping ${d.keepId})`);
67
+ });
68
+
69
+ // Create backup
70
+ const backupFile = tasksFile + '.backup-' + Date.now();
71
+ fs.copyFileSync(tasksFile, backupFile);
72
+ console.log(`\nšŸ’¾ Backup created: ${backupFile}`);
73
+
74
+ // Write cleaned tasks
75
+ const cleanedLines = [];
76
+ for (const line of lines) {
77
+ try {
78
+ const task = JSON.parse(line);
79
+ const isDuplicate = duplicates.find(d => d.id === task.id);
80
+ if (!isDuplicate) {
81
+ cleanedLines.push(line);
82
+ }
83
+ } catch (e) {
84
+ // Keep unparseable lines as-is
85
+ cleanedLines.push(line);
86
+ }
87
+ }
88
+
89
+ fs.writeFileSync(tasksFile, cleanedLines.join('\n') + '\n');
90
+ console.log(`\nāœ… Cleaned tasks file written`);
91
+ console.log(` Removed ${duplicates.length} duplicate tasks`);
92
+ console.log(` Final task count: ${cleanedLines.length}`);
93
+ } else {
94
+ console.log('\nāœ… No duplicates found!');
95
+ }
96
+
97
+ // Show remaining unique tasks
98
+ console.log('\nšŸ“‹ Unique tasks remaining:');
99
+ const uniqueTasks = Array.from(taskMap.values())
100
+ .filter(t => t.type === 'task_create' || t.type === 'task_update')
101
+ .sort((a, b) => (a.priority === 'urgent' ? -1 : a.priority === 'high' ? 0 : 1));
102
+
103
+ uniqueTasks.slice(0, 10).forEach(task => {
104
+ const status = task.status === 'completed' ? 'āœ…' :
105
+ task.status === 'in_progress' ? 'šŸ”„' : 'ā³';
106
+ console.log(` ${status} [${task.priority}] ${task.title}`);
107
+ });
108
+
109
+ if (uniqueTasks.length > 10) {
110
+ console.log(` ... and ${uniqueTasks.length - 10} more tasks`);
111
+ }
112
+ }
113
+
114
+ cleanDuplicateTasks();