@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.
- package/dist/cli/commands/skills.js +15 -2
- package/dist/cli/commands/skills.js.map +2 -2
- package/dist/cli/index.js +113 -834
- package/dist/cli/index.js.map +3 -3
- package/dist/core/context/dual-stack-manager.js +1 -1
- package/dist/core/context/dual-stack-manager.js.map +1 -1
- package/dist/core/context/frame-manager.js +3 -0
- package/dist/core/context/frame-manager.js.map +2 -2
- package/dist/integrations/claude-code/subagent-client.js +106 -3
- package/dist/integrations/claude-code/subagent-client.js.map +2 -2
- package/dist/servers/railway/config.js +51 -0
- package/dist/servers/railway/config.js.map +7 -0
- package/dist/servers/railway/index-enhanced.js +156 -0
- package/dist/servers/railway/index-enhanced.js.map +7 -0
- package/dist/servers/railway/minimal.js +48 -3
- package/dist/servers/railway/minimal.js.map +2 -2
- package/dist/servers/railway/storage-test.js +455 -0
- package/dist/servers/railway/storage-test.js.map +7 -0
- package/dist/skills/claude-skills.js +13 -12
- package/dist/skills/claude-skills.js.map +2 -2
- package/dist/skills/recursive-agent-orchestrator.js +27 -18
- package/dist/skills/recursive-agent-orchestrator.js.map +2 -2
- package/dist/skills/unified-rlm-orchestrator.js.map +2 -2
- package/package.json +6 -18
- package/scripts/README-TESTING.md +186 -0
- package/scripts/analyze-cli-security.js +288 -0
- package/scripts/archive/add-phase-tasks-to-linear.js +163 -0
- package/scripts/archive/analyze-linear-duplicates.js +214 -0
- package/scripts/archive/analyze-remaining-duplicates.js +230 -0
- package/scripts/archive/analyze-sta-duplicates.js +292 -0
- package/scripts/archive/analyze-sta-graphql.js +399 -0
- package/scripts/archive/cancel-duplicate-tasks.ts +246 -0
- package/scripts/archive/check-all-duplicates.ts +419 -0
- package/scripts/archive/clean-duplicate-tasks.js +114 -0
- package/scripts/archive/cleanup-duplicate-tasks.ts +286 -0
- package/scripts/archive/create-phase-tasks.js +387 -0
- package/scripts/archive/delete-linear-duplicates.js +182 -0
- package/scripts/archive/delete-remaining-duplicates.js +158 -0
- package/scripts/archive/delete-sta-duplicates.js +201 -0
- package/scripts/archive/delete-sta-oauth.js +201 -0
- package/scripts/archive/export-sta-tasks.js +62 -0
- package/scripts/archive/install-auto-sync.js +266 -0
- package/scripts/archive/install-chromadb-hooks.sh +133 -0
- package/scripts/archive/install-enhanced-clear-hooks.sh +431 -0
- package/scripts/archive/install-post-task-hooks.sh +289 -0
- package/scripts/archive/install-stackmemory-hooks.sh +420 -0
- package/scripts/archive/merge-linear-duplicates-safe.ts +362 -0
- package/scripts/archive/merge-linear-duplicates.ts +180 -0
- package/scripts/archive/remove-sta-tasks.js +70 -0
- package/scripts/archive/setup-background-sync.sh +168 -0
- package/scripts/archive/setup-claude-auto-triggers.sh +181 -0
- package/scripts/archive/setup-claude-autostart.sh +305 -0
- package/scripts/archive/setup-git-hooks.sh +25 -0
- package/scripts/archive/setup-linear-oauth.sh +46 -0
- package/scripts/archive/setup-mcp.sh +113 -0
- package/scripts/archive/setup-railway-deployment.sh +81 -0
- package/scripts/auto-handoff.sh +262 -0
- package/scripts/background-sync-manager.js +416 -0
- package/scripts/benchmark-performance.ts +57 -0
- package/scripts/check-redis.ts +48 -0
- package/scripts/chromadb-auto-loader.sh +128 -0
- package/scripts/chromadb-context-loader.js +479 -0
- package/scripts/claude-chromadb-hook.js +460 -0
- package/scripts/claude-code-wrapper.sh +66 -0
- package/scripts/claude-linear-skill.js +455 -0
- package/scripts/claude-pre-commit.sh +302 -0
- package/scripts/claude-sm-autostart.js +532 -0
- package/scripts/claude-sm-setup.sh +367 -0
- package/scripts/claude-with-chromadb.sh +69 -0
- package/scripts/claude-worktree-manager.sh +323 -0
- package/scripts/claude-worktree-monitor.sh +371 -0
- package/scripts/claude-worktree-setup.sh +327 -0
- package/scripts/clean-linear-backlog.js +273 -0
- package/scripts/cleanup-old-sessions.sh +57 -0
- package/scripts/codex-wrapper.sh +88 -0
- package/scripts/create-sandbox.sh +269 -0
- package/scripts/debug-linear-update.js +174 -0
- package/scripts/delete-linear-tasks.js +167 -0
- package/scripts/deploy.sh +89 -0
- package/scripts/deployment/railway.sh +352 -0
- package/scripts/deployment/test-deployment.js +194 -0
- package/scripts/detect-and-rehydrate.js +162 -0
- package/scripts/detect-and-rehydrate.mjs +165 -0
- package/scripts/development/create-demo-tasks.js +143 -0
- package/scripts/development/debug-frame-test.js +16 -0
- package/scripts/development/demo-auto-sync.js +128 -0
- package/scripts/development/fix-all-imports.js +213 -0
- package/scripts/development/fix-imports.js +229 -0
- package/scripts/development/fix-lint-loop.cjs +103 -0
- package/scripts/development/fix-project-id.ts +161 -0
- package/scripts/development/fix-strict-mode-issues.ts +291 -0
- package/scripts/development/reorganize-structure.sh +228 -0
- package/scripts/development/test-persistence-direct.js +148 -0
- package/scripts/development/test-persistence.js +114 -0
- package/scripts/development/test-tasks.js +93 -0
- package/scripts/development/update-imports.js +212 -0
- package/scripts/fetch-linear-status.js +125 -0
- package/scripts/git-hooks/README.md +310 -0
- package/scripts/git-hooks/branch-context-manager.sh +342 -0
- package/scripts/git-hooks/post-checkout-stackmemory.sh +63 -0
- package/scripts/git-hooks/post-commit-stackmemory.sh +305 -0
- package/scripts/git-hooks/pre-commit-stackmemory.sh +275 -0
- package/scripts/hooks/cleanup-shell.sh +130 -0
- package/scripts/hooks/task-complete.sh +114 -0
- package/scripts/initialize.ts +129 -0
- package/scripts/install-claude-hooks-auto.js +104 -0
- package/scripts/install-claude-hooks.sh +133 -0
- package/scripts/install-global.sh +296 -0
- package/scripts/install.sh +235 -0
- package/scripts/linear-auto-sync.js +262 -0
- package/scripts/linear-auto-sync.sh +161 -0
- package/scripts/linear-sync-daemon.js +150 -0
- package/scripts/linear-task-review.js +237 -0
- package/scripts/list-linear-tasks.ts +178 -0
- package/scripts/mcp-proxy.js +66 -0
- package/scripts/opencode-wrapper.sh +85 -0
- package/scripts/publish-local.js +74 -0
- package/scripts/query-chromadb.ts +201 -0
- package/scripts/railway-env-setup.sh +39 -0
- package/scripts/reconcile-local-tasks.js +170 -0
- package/scripts/recreate-frames-db.js +89 -0
- package/scripts/setup/claude-integration.js +138 -0
- package/scripts/setup/configure-alias.js +125 -0
- package/scripts/setup/configure-codex-alias.js +161 -0
- package/scripts/setup/configure-opencode-alias.js +175 -0
- package/scripts/setup-claude-integration.js +204 -0
- package/scripts/setup-claude-integration.sh +183 -0
- package/scripts/setup.sh +31 -0
- package/scripts/show-linear-summary.ts +172 -0
- package/scripts/stackmemory-auto-handoff.sh +231 -0
- package/scripts/stackmemory-daemon.sh +40 -0
- package/scripts/start-linear-sync-daemon.sh +141 -0
- package/scripts/start-temporal-paradox.sh +214 -0
- package/scripts/status.ts +159 -0
- package/scripts/sync-and-clean-tasks.js +258 -0
- package/scripts/sync-frames-from-railway.js +228 -0
- package/scripts/sync-linear-graphql.js +303 -0
- package/scripts/sync-linear-tasks.js +186 -0
- package/scripts/test-auto-triggers.sh +57 -0
- package/scripts/test-browser-mcp.js +74 -0
- package/scripts/test-chromadb-full.js +115 -0
- package/scripts/test-chromadb-hooks.sh +28 -0
- package/scripts/test-chromadb-sync.ts +245 -0
- package/scripts/test-cli-security.js +293 -0
- package/scripts/test-hooks-persistence.sh +220 -0
- package/scripts/test-installation-scenarios.sh +359 -0
- package/scripts/test-installation.sh +224 -0
- package/scripts/test-mcp.js +163 -0
- package/scripts/test-pre-publish-quick.sh +75 -0
- package/scripts/test-quality-gates.sh +263 -0
- package/scripts/test-railway-db.js +222 -0
- package/scripts/test-redis-storage.ts +490 -0
- package/scripts/test-rlm-basic.sh +122 -0
- package/scripts/test-rlm-comprehensive.sh +260 -0
- package/scripts/test-rlm-e2e.sh +268 -0
- package/scripts/test-rlm-simple.js +90 -0
- package/scripts/test-rlm.js +110 -0
- package/scripts/test-session-handoff.sh +165 -0
- package/scripts/test-shell-integration.sh +275 -0
- package/scripts/testing/ab-test-runner.ts +508 -0
- package/scripts/testing/collect-metrics.ts +457 -0
- package/scripts/testing/quick-effectiveness-demo.js +187 -0
- package/scripts/testing/real-performance-test.js +422 -0
- package/scripts/testing/run-effectiveness-tests.sh +176 -0
- package/scripts/testing/scripts/testing/ab-test-runner.js +363 -0
- package/scripts/testing/scripts/testing/collect-metrics.js +292 -0
- package/scripts/testing/simple-effectiveness-test.js +310 -0
- package/scripts/testing/src/core/context/context-bridge.js +253 -0
- package/scripts/testing/src/core/context/frame-manager.js +746 -0
- package/scripts/testing/src/core/context/shared-context-layer.js +437 -0
- package/scripts/testing/src/core/database/database-adapter.js +54 -0
- package/scripts/testing/src/core/errors/index.js +291 -0
- package/scripts/testing/src/core/errors/recovery.js +268 -0
- package/scripts/testing/src/core/monitoring/logger.js +145 -0
- package/scripts/testing/src/core/retrieval/context-retriever.js +516 -0
- package/scripts/testing/src/core/session/index.js +1 -0
- package/scripts/testing/src/core/session/session-manager.js +323 -0
- package/scripts/testing/src/core/trace/cli-trace-wrapper.js +140 -0
- package/scripts/testing/src/core/trace/db-trace-wrapper.js +251 -0
- package/scripts/testing/src/core/trace/debug-trace.js +398 -0
- package/scripts/testing/src/core/trace/index.js +120 -0
- package/scripts/testing/src/core/trace/linear-api-wrapper.js +204 -0
- package/scripts/update-linear-status.js +268 -0
- package/scripts/update-linear-tasks-fixed.js +284 -0
- package/templates/claude-hooks/hooks.json +5 -0
- package/templates/claude-hooks/on-clear.js +56 -0
- package/templates/claude-hooks/on-startup.js +56 -0
- package/templates/claude-hooks/tool-use-trace.js +67 -0
- package/dist/features/tui/components/analytics-panel.js +0 -157
- package/dist/features/tui/components/analytics-panel.js.map +0 -7
- package/dist/features/tui/components/frame-visualizer.js +0 -377
- package/dist/features/tui/components/frame-visualizer.js.map +0 -7
- package/dist/features/tui/components/pr-tracker.js +0 -135
- package/dist/features/tui/components/pr-tracker.js.map +0 -7
- package/dist/features/tui/components/session-monitor.js +0 -299
- package/dist/features/tui/components/session-monitor.js.map +0 -7
- package/dist/features/tui/components/subagent-fleet.js +0 -395
- package/dist/features/tui/components/subagent-fleet.js.map +0 -7
- package/dist/features/tui/components/task-board.js +0 -1139
- package/dist/features/tui/components/task-board.js.map +0 -7
- package/dist/features/tui/index.js +0 -408
- package/dist/features/tui/index.js.map +0 -7
- package/dist/features/tui/services/data-service.js +0 -641
- package/dist/features/tui/services/data-service.js.map +0 -7
- package/dist/features/tui/services/linear-task-reader.js +0 -102
- package/dist/features/tui/services/linear-task-reader.js.map +0 -7
- package/dist/features/tui/services/websocket-client.js +0 -162
- package/dist/features/tui/services/websocket-client.js.map +0 -7
- package/dist/features/tui/terminal-compat.js +0 -220
- package/dist/features/tui/terminal-compat.js.map +0 -7
- package/dist/features/tui/types.js +0 -1
- package/dist/features/tui/types.js.map +0 -7
|
@@ -0,0 +1,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 };
|