@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,288 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Security Analysis Script for StackMemory CLI/API
|
|
5
|
+
* Analyzes code for input validation and security vulnerabilities
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, readdirSync, statSync } from 'fs';
|
|
9
|
+
import { join, extname } from 'path';
|
|
10
|
+
|
|
11
|
+
const SECURITY_ISSUES = {
|
|
12
|
+
CRITICAL: [],
|
|
13
|
+
HIGH: [],
|
|
14
|
+
MEDIUM: [],
|
|
15
|
+
LOW: []
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Color helpers
|
|
19
|
+
const red = (str) => `\x1b[31m${str}\x1b[0m`;
|
|
20
|
+
const yellow = (str) => `\x1b[33m${str}\x1b[0m`;
|
|
21
|
+
const green = (str) => `\x1b[32m${str}\x1b[0m`;
|
|
22
|
+
const blue = (str) => `\x1b[34m${str}\x1b[0m`;
|
|
23
|
+
|
|
24
|
+
// Security patterns to check
|
|
25
|
+
const SECURITY_PATTERNS = {
|
|
26
|
+
SQL_INJECTION: {
|
|
27
|
+
patterns: [
|
|
28
|
+
/\.prepare\s*\(\s*[`"'].*\$\{.*\}.*[`"']\s*\)/g,
|
|
29
|
+
/\.prepare\s*\(\s*[`"'].*\+.*[`"']\s*\)/g,
|
|
30
|
+
/\.exec\s*\(\s*[^`"'].*\)/g,
|
|
31
|
+
/WHERE.*LIKE\s*\?/gi
|
|
32
|
+
],
|
|
33
|
+
severity: 'HIGH',
|
|
34
|
+
description: 'Potential SQL injection vulnerability'
|
|
35
|
+
},
|
|
36
|
+
COMMAND_INJECTION: {
|
|
37
|
+
patterns: [
|
|
38
|
+
/exec\s*\(.*\$\{.*\}/g,
|
|
39
|
+
/execSync\s*\(.*\$\{.*\}/g,
|
|
40
|
+
/spawn\s*\(.*,\s*\[.*\$\{.*\}/g,
|
|
41
|
+
/child_process.*exec/g
|
|
42
|
+
],
|
|
43
|
+
severity: 'CRITICAL',
|
|
44
|
+
description: 'Potential command injection vulnerability'
|
|
45
|
+
},
|
|
46
|
+
PATH_TRAVERSAL: {
|
|
47
|
+
patterns: [
|
|
48
|
+
/\.\.\//g,
|
|
49
|
+
/join\s*\(.*process\.cwd\(\).*,.*[^'"]\)/g,
|
|
50
|
+
/readFileSync\s*\([^'"].*\)/g
|
|
51
|
+
],
|
|
52
|
+
severity: 'HIGH',
|
|
53
|
+
description: 'Potential path traversal vulnerability'
|
|
54
|
+
},
|
|
55
|
+
NO_INPUT_VALIDATION: {
|
|
56
|
+
patterns: [
|
|
57
|
+
/parseInt\s*\(.*\)\s*(?!.*isNaN)/g,
|
|
58
|
+
/JSON\.parse\s*\(.*\)\s*(?!.*try)/g,
|
|
59
|
+
/\.action\s*\(\s*async.*\{[\s\S]*?(?!validate|check|verify)[\s\S]*?\}\)/g
|
|
60
|
+
],
|
|
61
|
+
severity: 'MEDIUM',
|
|
62
|
+
description: 'Missing input validation'
|
|
63
|
+
},
|
|
64
|
+
HARDCODED_SECRETS: {
|
|
65
|
+
patterns: [
|
|
66
|
+
/api[_-]?key\s*[:=]\s*["'][^"']+["']/gi,
|
|
67
|
+
/secret\s*[:=]\s*["'][^"']+["']/gi,
|
|
68
|
+
/token\s*[:=]\s*["'][^"']+["']/gi,
|
|
69
|
+
/password\s*[:=]\s*["'][^"']+["']/gi
|
|
70
|
+
],
|
|
71
|
+
severity: 'CRITICAL',
|
|
72
|
+
description: 'Hardcoded secrets detected'
|
|
73
|
+
},
|
|
74
|
+
UNSAFE_REGEX: {
|
|
75
|
+
patterns: [
|
|
76
|
+
/new RegExp\s*\(/g,
|
|
77
|
+
/\/\.\*.*\.\*\//g
|
|
78
|
+
],
|
|
79
|
+
severity: 'LOW',
|
|
80
|
+
description: 'Potentially unsafe regular expression'
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Validation patterns found
|
|
85
|
+
const VALIDATION_PATTERNS = {
|
|
86
|
+
INPUT_SANITIZATION: [
|
|
87
|
+
/\.replace\s*\(\/\[.*\]\/g/,
|
|
88
|
+
/\.trim\(\)/,
|
|
89
|
+
/\.slice\(0,\s*\d+\)/,
|
|
90
|
+
/validator\./
|
|
91
|
+
],
|
|
92
|
+
ERROR_HANDLING: [
|
|
93
|
+
/try\s*\{[\s\S]*?\}\s*catch/,
|
|
94
|
+
/\.catch\s*\(/,
|
|
95
|
+
/if\s*\(!.*\)\s*\{[\s\S]*?throw/,
|
|
96
|
+
/if\s*\(!.*\)\s*\{[\s\S]*?process\.exit/
|
|
97
|
+
],
|
|
98
|
+
TYPE_CHECKING: [
|
|
99
|
+
/typeof.*===\s*['"]string['"]/,
|
|
100
|
+
/instanceof/,
|
|
101
|
+
/isNaN\s*\(/,
|
|
102
|
+
/Number\.isInteger/
|
|
103
|
+
],
|
|
104
|
+
BOUNDS_CHECKING: [
|
|
105
|
+
/if\s*\(.*[<>]=?\s*\d+/,
|
|
106
|
+
/Math\.(min|max)\s*\(/,
|
|
107
|
+
/\.slice\(0,\s*\d+\)/
|
|
108
|
+
],
|
|
109
|
+
SQL_PARAMETERIZATION: [
|
|
110
|
+
/\.prepare\s*\([\s\S]*?\?\s*[\s\S]*?\)/,
|
|
111
|
+
/\.all\s*\(.*\)/,
|
|
112
|
+
/\.run\s*\(.*\)/
|
|
113
|
+
]
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Analyze a file
|
|
117
|
+
function analyzeFile(filePath) {
|
|
118
|
+
const content = readFileSync(filePath, 'utf8');
|
|
119
|
+
const lines = content.split('\n');
|
|
120
|
+
const fileName = filePath.replace(process.cwd() + '/', '');
|
|
121
|
+
const issues = [];
|
|
122
|
+
const validations = [];
|
|
123
|
+
|
|
124
|
+
// Check for security patterns
|
|
125
|
+
for (const [name, config] of Object.entries(SECURITY_PATTERNS)) {
|
|
126
|
+
for (const pattern of config.patterns) {
|
|
127
|
+
const matches = content.matchAll(pattern);
|
|
128
|
+
for (const match of matches) {
|
|
129
|
+
const lineNum = content.substring(0, match.index).split('\n').length;
|
|
130
|
+
const line = lines[lineNum - 1]?.trim() || '';
|
|
131
|
+
|
|
132
|
+
// Skip if it's in a comment
|
|
133
|
+
if (line.startsWith('//') || line.startsWith('*')) continue;
|
|
134
|
+
|
|
135
|
+
issues.push({
|
|
136
|
+
file: fileName,
|
|
137
|
+
line: lineNum,
|
|
138
|
+
type: name,
|
|
139
|
+
severity: config.severity,
|
|
140
|
+
description: config.description,
|
|
141
|
+
code: line.substring(0, 80)
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Check for validation patterns
|
|
148
|
+
for (const [type, patterns] of Object.entries(VALIDATION_PATTERNS)) {
|
|
149
|
+
for (const pattern of patterns) {
|
|
150
|
+
if (pattern.test(content)) {
|
|
151
|
+
validations.push({
|
|
152
|
+
file: fileName,
|
|
153
|
+
type: type,
|
|
154
|
+
found: true
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return { issues, validations };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Recursively find TypeScript files
|
|
164
|
+
function findFiles(dir, files = []) {
|
|
165
|
+
const items = readdirSync(dir);
|
|
166
|
+
|
|
167
|
+
for (const item of items) {
|
|
168
|
+
const fullPath = join(dir, item);
|
|
169
|
+
const stat = statSync(fullPath);
|
|
170
|
+
|
|
171
|
+
if (stat.isDirectory()) {
|
|
172
|
+
if (!item.includes('node_modules') && !item.startsWith('.')) {
|
|
173
|
+
findFiles(fullPath, files);
|
|
174
|
+
}
|
|
175
|
+
} else if (extname(item) === '.ts' || extname(item) === '.js') {
|
|
176
|
+
files.push(fullPath);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return files;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Main analysis
|
|
184
|
+
function main() {
|
|
185
|
+
console.log(blue('\nš StackMemory CLI/API Security Analysis\n'));
|
|
186
|
+
|
|
187
|
+
const srcDir = join(process.cwd(), 'src');
|
|
188
|
+
const cliFiles = findFiles(join(srcDir, 'cli'));
|
|
189
|
+
const apiFiles = findFiles(join(srcDir, 'features', 'analytics', 'api'));
|
|
190
|
+
const allFiles = [...cliFiles, ...apiFiles];
|
|
191
|
+
|
|
192
|
+
console.log(`Analyzing ${allFiles.length} files...\n`);
|
|
193
|
+
|
|
194
|
+
const allIssues = [];
|
|
195
|
+
const allValidations = new Map();
|
|
196
|
+
|
|
197
|
+
for (const file of allFiles) {
|
|
198
|
+
const { issues, validations } = analyzeFile(file);
|
|
199
|
+
allIssues.push(...issues);
|
|
200
|
+
|
|
201
|
+
for (const validation of validations) {
|
|
202
|
+
const key = `${validation.file}:${validation.type}`;
|
|
203
|
+
allValidations.set(key, validation);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Group issues by severity
|
|
208
|
+
for (const issue of allIssues) {
|
|
209
|
+
SECURITY_ISSUES[issue.severity].push(issue);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Report findings
|
|
213
|
+
console.log(red('šØ CRITICAL Issues:'), SECURITY_ISSUES.CRITICAL.length);
|
|
214
|
+
for (const issue of SECURITY_ISSUES.CRITICAL) {
|
|
215
|
+
console.log(` ${issue.file}:${issue.line}`);
|
|
216
|
+
console.log(` ${issue.description}`);
|
|
217
|
+
console.log(` ${yellow(issue.code)}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
console.log(yellow('\nā ļø HIGH Priority Issues:'), SECURITY_ISSUES.HIGH.length);
|
|
221
|
+
for (const issue of SECURITY_ISSUES.HIGH.slice(0, 10)) {
|
|
222
|
+
console.log(` ${issue.file}:${issue.line}`);
|
|
223
|
+
console.log(` ${issue.description}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
console.log(blue('\nšµ MEDIUM Priority Issues:'), SECURITY_ISSUES.MEDIUM.length);
|
|
227
|
+
console.log(green('š¢ LOW Priority Issues:'), SECURITY_ISSUES.LOW.length);
|
|
228
|
+
|
|
229
|
+
// Report validation mechanisms found
|
|
230
|
+
console.log(green('\nā
Validation Mechanisms Found:'));
|
|
231
|
+
const validationTypes = {};
|
|
232
|
+
for (const validation of allValidations.values()) {
|
|
233
|
+
validationTypes[validation.type] = (validationTypes[validation.type] || 0) + 1;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
for (const [type, count] of Object.entries(validationTypes)) {
|
|
237
|
+
console.log(` ${type}: ${count} occurrences`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Specific CLI command analysis
|
|
241
|
+
console.log(blue('\nš CLI Command Analysis:'));
|
|
242
|
+
const commands = [
|
|
243
|
+
'init', 'status', 'linear', 'search', 'projects', 'config',
|
|
244
|
+
'analytics', 'tasks', 'context', 'session'
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
for (const cmd of commands) {
|
|
248
|
+
const cmdFile = allFiles.find(f => f.includes(`commands/${cmd}.ts`));
|
|
249
|
+
if (cmdFile) {
|
|
250
|
+
const content = readFileSync(cmdFile, 'utf8');
|
|
251
|
+
const hasValidation = /validate|check|verify|isValid|sanitize/i.test(content);
|
|
252
|
+
const hasErrorHandling = /try\s*\{|\.catch\(|error\s*:/i.test(content);
|
|
253
|
+
const usesParams = /\.prepare\s*\([\s\S]*?\?/.test(content);
|
|
254
|
+
|
|
255
|
+
console.log(` ${cmd}:`);
|
|
256
|
+
console.log(` Input validation: ${hasValidation ? green('ā') : red('ā')}`);
|
|
257
|
+
console.log(` Error handling: ${hasErrorHandling ? green('ā') : red('ā')}`);
|
|
258
|
+
console.log(` SQL parameterized: ${usesParams ? green('ā') : yellow('ā ')}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Summary and recommendations
|
|
263
|
+
console.log(blue('\nš Summary:'));
|
|
264
|
+
const total = allIssues.length;
|
|
265
|
+
const critical = SECURITY_ISSUES.CRITICAL.length;
|
|
266
|
+
const high = SECURITY_ISSUES.HIGH.length;
|
|
267
|
+
|
|
268
|
+
if (critical > 0) {
|
|
269
|
+
console.log(red(` ā ${critical} CRITICAL issues require immediate attention`));
|
|
270
|
+
}
|
|
271
|
+
if (high > 0) {
|
|
272
|
+
console.log(yellow(` ā ļø ${high} HIGH priority issues should be fixed soon`));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
console.log(blue('\nš§ Recommendations:'));
|
|
276
|
+
console.log(' 1. Add input validation for all CLI arguments');
|
|
277
|
+
console.log(' 2. Use parameterized queries for all SQL operations');
|
|
278
|
+
console.log(' 3. Sanitize file paths to prevent traversal attacks');
|
|
279
|
+
console.log(' 4. Implement rate limiting for API endpoints');
|
|
280
|
+
console.log(' 5. Add comprehensive error handling');
|
|
281
|
+
console.log(' 6. Use environment variables for all sensitive data');
|
|
282
|
+
console.log(' 7. Implement proper authentication checks');
|
|
283
|
+
console.log(' 8. Add input length limits to prevent DoS');
|
|
284
|
+
|
|
285
|
+
process.exit(critical > 0 ? 1 : 0);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
main();
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Add all StackMemory phase tasks to Linear via API
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import 'dotenv/config';
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import { LinearRestClient } from '../dist/integrations/linear/rest-client.js';
|
|
10
|
+
|
|
11
|
+
const API_KEY = process.env.LINEAR_API_KEY;
|
|
12
|
+
if (!API_KEY) {
|
|
13
|
+
console.error('ā LINEAR_API_KEY environment variable not set');
|
|
14
|
+
console.log('Please set LINEAR_API_KEY in your .env file or export it in your shell');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const DELAY_BETWEEN_TASKS = 5000; // 5 seconds to avoid rate limits
|
|
18
|
+
|
|
19
|
+
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
20
|
+
|
|
21
|
+
async function addTasksToLinear() {
|
|
22
|
+
try {
|
|
23
|
+
console.log('š Starting to add StackMemory phase tasks to Linear...');
|
|
24
|
+
|
|
25
|
+
// Read the generated tasks
|
|
26
|
+
const tasksFile = `stackmemory-phase-tasks-${new Date().toISOString().split('T')[0]}.json`;
|
|
27
|
+
if (!fs.existsSync(tasksFile)) {
|
|
28
|
+
throw new Error(`Tasks file not found: ${tasksFile}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const taskData = JSON.parse(fs.readFileSync(tasksFile, 'utf8'));
|
|
32
|
+
console.log(`š Found ${taskData.totalTasks} tasks to create`);
|
|
33
|
+
|
|
34
|
+
const client = new LinearRestClient(API_KEY);
|
|
35
|
+
|
|
36
|
+
// Get team info
|
|
37
|
+
console.log('š Getting team information...');
|
|
38
|
+
const team = await client.getTeam();
|
|
39
|
+
console.log(`šÆ Target team: ${team.name} (${team.key})`);
|
|
40
|
+
|
|
41
|
+
let created = 0;
|
|
42
|
+
let failed = 0;
|
|
43
|
+
const results = [];
|
|
44
|
+
|
|
45
|
+
// Process each phase
|
|
46
|
+
for (const [phaseName, tasks] of Object.entries(taskData.phases)) {
|
|
47
|
+
console.log(`\nš¦ Processing ${phaseName} (${tasks.length} tasks)`);
|
|
48
|
+
|
|
49
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
50
|
+
const task = tasks[i];
|
|
51
|
+
console.log(`\nā³ Creating task ${i + 1}/${tasks.length}: ${task.title}`);
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
// Create task via GraphQL
|
|
55
|
+
const createQuery = `
|
|
56
|
+
mutation CreateIssue($input: IssueCreateInput!) {
|
|
57
|
+
issueCreate(input: $input) {
|
|
58
|
+
success
|
|
59
|
+
issue {
|
|
60
|
+
id
|
|
61
|
+
identifier
|
|
62
|
+
title
|
|
63
|
+
state { name }
|
|
64
|
+
priority
|
|
65
|
+
url
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
`;
|
|
70
|
+
|
|
71
|
+
const taskInput = {
|
|
72
|
+
title: task.title.replace('STA-XXX:', '').trim(), // Remove placeholder ID
|
|
73
|
+
description: task.description,
|
|
74
|
+
teamId: team.id,
|
|
75
|
+
priority: task.priority
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const response = await client.makeRequest(createQuery, { input: taskInput });
|
|
79
|
+
|
|
80
|
+
if (response.data?.issueCreate?.success) {
|
|
81
|
+
const createdTask = response.data.issueCreate.issue;
|
|
82
|
+
console.log(`ā
Created: ${createdTask.identifier} - ${createdTask.title}`);
|
|
83
|
+
console.log(` URL: ${createdTask.url}`);
|
|
84
|
+
|
|
85
|
+
results.push({
|
|
86
|
+
original: task.title,
|
|
87
|
+
created: createdTask.identifier,
|
|
88
|
+
url: createdTask.url,
|
|
89
|
+
success: true
|
|
90
|
+
});
|
|
91
|
+
created++;
|
|
92
|
+
} else {
|
|
93
|
+
const errors = response.data?.issueCreate?.errors || [{ message: 'Unknown error' }];
|
|
94
|
+
throw new Error(errors[0].message);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Delay to avoid rate limits (except for last task)
|
|
98
|
+
if (i < tasks.length - 1) {
|
|
99
|
+
console.log(`ā³ Waiting ${DELAY_BETWEEN_TASKS / 1000}s to avoid rate limits...`);
|
|
100
|
+
await delay(DELAY_BETWEEN_TASKS);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.log(`ā Failed: ${error.message}`);
|
|
105
|
+
results.push({
|
|
106
|
+
original: task.title,
|
|
107
|
+
error: error.message,
|
|
108
|
+
success: false
|
|
109
|
+
});
|
|
110
|
+
failed++;
|
|
111
|
+
|
|
112
|
+
// If rate limited, wait longer and continue
|
|
113
|
+
if (error.message.includes('usage limit') || error.message.includes('rate limit')) {
|
|
114
|
+
console.log('š« Hit rate limit. Waiting 60 seconds...');
|
|
115
|
+
await delay(60000);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Summary
|
|
122
|
+
console.log('\nš Creation Summary:');
|
|
123
|
+
console.log(`ā
Successfully created: ${created} tasks`);
|
|
124
|
+
console.log(`ā Failed: ${failed} tasks`);
|
|
125
|
+
console.log(`š Success rate: ${Math.round((created / (created + failed)) * 100)}%`);
|
|
126
|
+
|
|
127
|
+
// Save results
|
|
128
|
+
const resultsFile = `linear-task-creation-results-${new Date().toISOString().split('T')[0]}.json`;
|
|
129
|
+
fs.writeFileSync(resultsFile, JSON.stringify({
|
|
130
|
+
summary: { created, failed, total: created + failed },
|
|
131
|
+
results: results,
|
|
132
|
+
timestamp: new Date().toISOString()
|
|
133
|
+
}, null, 2));
|
|
134
|
+
|
|
135
|
+
console.log(`š¾ Results saved to: ${resultsFile}`);
|
|
136
|
+
|
|
137
|
+
if (created > 0) {
|
|
138
|
+
console.log('\nš Success! StackMemory phase tasks have been added to Linear.');
|
|
139
|
+
console.log('š” Next steps:');
|
|
140
|
+
console.log(' 1. Review and organize tasks in Linear workspace');
|
|
141
|
+
console.log(' 2. Start with high-priority Phase 2 tasks');
|
|
142
|
+
console.log(' 3. Assign tasks to team members');
|
|
143
|
+
console.log(' 4. Begin implementation of LLM-driven context retrieval');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (failed > 0) {
|
|
147
|
+
console.log('\nā ļø Some tasks failed to create. Check the results file for details.');
|
|
148
|
+
console.log(' You can retry failed tasks manually using:');
|
|
149
|
+
console.log(' node dist/cli/index.js linear:create --api-key <key> --title "..." --description "..."');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error('š„ Script failed:', error.message);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Run if called directly
|
|
159
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
160
|
+
addTasksToLinear().catch(console.error);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export { addTasksToLinear };
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Analyze Linear workspace for duplicate tasks to identify what to delete
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import 'dotenv/config';
|
|
8
|
+
import { LinearRestClient } from '../dist/integrations/linear/rest-client.js';
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
|
|
11
|
+
const API_KEY = process.env.LINEAR_API_KEY;
|
|
12
|
+
if (!API_KEY) {
|
|
13
|
+
console.error('ā LINEAR_API_KEY environment variable not set');
|
|
14
|
+
console.log('Please set LINEAR_API_KEY in your .env file or export it in your shell');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function analyzeLinearDuplicates() {
|
|
19
|
+
try {
|
|
20
|
+
console.log('š Analyzing Linear workspace for duplicates and low-value tasks...\n');
|
|
21
|
+
|
|
22
|
+
const client = new LinearRestClient(API_KEY);
|
|
23
|
+
|
|
24
|
+
// Fetch all tasks
|
|
25
|
+
console.log('š„ Fetching all tasks from Linear...');
|
|
26
|
+
const allTasks = await client.getAllTasks(true);
|
|
27
|
+
console.log(`š Total tasks: ${allTasks.length}\n`);
|
|
28
|
+
|
|
29
|
+
// Analyze patterns
|
|
30
|
+
const duplicates = new Map(); // title pattern -> tasks
|
|
31
|
+
const meetingTasks = [];
|
|
32
|
+
const todoTasks = [];
|
|
33
|
+
const analyticsDashboardTasks = [];
|
|
34
|
+
const phaseImportTasks = [];
|
|
35
|
+
const completedTasks = [];
|
|
36
|
+
const canceledTasks = [];
|
|
37
|
+
|
|
38
|
+
// Group tasks by patterns
|
|
39
|
+
allTasks.forEach(task => {
|
|
40
|
+
const title = task.title;
|
|
41
|
+
const state = task.state.type;
|
|
42
|
+
|
|
43
|
+
// Check for completed/canceled
|
|
44
|
+
if (state === 'completed') {
|
|
45
|
+
completedTasks.push(task);
|
|
46
|
+
} else if (state === 'canceled') {
|
|
47
|
+
canceledTasks.push(task);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Engineering meetings pattern
|
|
51
|
+
if (title.includes('Engineering x') && title.includes('Meeting')) {
|
|
52
|
+
meetingTasks.push(task);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Documentation TODOs pattern
|
|
56
|
+
if (title.includes('Complete Documentation TODOs')) {
|
|
57
|
+
todoTasks.push(task);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Task Analytics Dashboard pattern
|
|
61
|
+
if (title.includes('Task Analytics Dashboard')) {
|
|
62
|
+
analyticsDashboardTasks.push(task);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Phase tasks with duplicate numbers
|
|
66
|
+
if (title.match(/\[STA-\d+\]/)) {
|
|
67
|
+
const match = title.match(/\[(STA-\d+)\]/);
|
|
68
|
+
if (match) {
|
|
69
|
+
const innerRef = match[1];
|
|
70
|
+
if (!phaseImportTasks[innerRef]) {
|
|
71
|
+
phaseImportTasks[innerRef] = [];
|
|
72
|
+
}
|
|
73
|
+
phaseImportTasks[innerRef].push(task);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Find exact duplicates by title
|
|
78
|
+
const baseTitle = title.replace(/\[.*?\]/g, '').trim();
|
|
79
|
+
if (!duplicates.has(baseTitle)) {
|
|
80
|
+
duplicates.set(baseTitle, []);
|
|
81
|
+
}
|
|
82
|
+
duplicates.get(baseTitle).push(task);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Filter to only show actual duplicates
|
|
86
|
+
const trueDuplicates = Array.from(duplicates.entries())
|
|
87
|
+
.filter(([_, tasks]) => tasks.length > 1)
|
|
88
|
+
.sort((a, b) => b[1].length - a[1].length);
|
|
89
|
+
|
|
90
|
+
// Generate report
|
|
91
|
+
console.log('š DUPLICATE ANALYSIS REPORT');
|
|
92
|
+
console.log('=' .repeat(60));
|
|
93
|
+
|
|
94
|
+
console.log('\nš EXACT DUPLICATES (same title):');
|
|
95
|
+
console.log(`Found ${trueDuplicates.length} duplicate groups\n`);
|
|
96
|
+
trueDuplicates.slice(0, 10).forEach(([title, tasks]) => {
|
|
97
|
+
console.log(` "${title}" - ${tasks.length} copies`);
|
|
98
|
+
tasks.slice(0, 3).forEach(task => {
|
|
99
|
+
console.log(` ⢠${task.identifier}: ${task.state.name}`);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
console.log('\nš¢ ENGINEERING MEETINGS:');
|
|
104
|
+
console.log(`Found ${meetingTasks.length} meeting tasks`);
|
|
105
|
+
if (meetingTasks.length > 0) {
|
|
106
|
+
console.log('Examples:');
|
|
107
|
+
meetingTasks.slice(0, 5).forEach(task => {
|
|
108
|
+
console.log(` ⢠${task.identifier}: ${task.title}`);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log('\nš DOCUMENTATION TODO DUPLICATES:');
|
|
113
|
+
console.log(`Found ${todoTasks.length} TODO tasks`);
|
|
114
|
+
if (todoTasks.length > 0) {
|
|
115
|
+
console.log('Examples:');
|
|
116
|
+
todoTasks.slice(0, 5).forEach(task => {
|
|
117
|
+
console.log(` ⢠${task.identifier}: ${task.title}`);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.log('\nš TASK ANALYTICS DASHBOARD DUPLICATES:');
|
|
122
|
+
console.log(`Found ${analyticsDashboardTasks.length} analytics dashboard tasks`);
|
|
123
|
+
if (analyticsDashboardTasks.length > 0) {
|
|
124
|
+
console.log('Examples:');
|
|
125
|
+
analyticsDashboardTasks.slice(0, 5).forEach(task => {
|
|
126
|
+
console.log(` ⢠${task.identifier}: ${task.title}`);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
console.log('\nā
COMPLETED TASKS (can be archived):');
|
|
131
|
+
console.log(`Found ${completedTasks.length} completed tasks`);
|
|
132
|
+
|
|
133
|
+
console.log('\nā CANCELED TASKS (can be deleted):');
|
|
134
|
+
console.log(`Found ${canceledTasks.length} canceled tasks`);
|
|
135
|
+
|
|
136
|
+
// Calculate deletion recommendations
|
|
137
|
+
const toDelete = [
|
|
138
|
+
...meetingTasks,
|
|
139
|
+
...todoTasks.slice(1), // Keep one TODO task
|
|
140
|
+
...analyticsDashboardTasks.slice(1), // Keep one dashboard task
|
|
141
|
+
...canceledTasks,
|
|
142
|
+
...completedTasks
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
// Remove duplicates from deletion list
|
|
146
|
+
const uniqueToDelete = Array.from(new Set(toDelete.map(t => t.id)))
|
|
147
|
+
.map(id => toDelete.find(t => t.id === id));
|
|
148
|
+
|
|
149
|
+
console.log('\nšļø DELETION RECOMMENDATIONS');
|
|
150
|
+
console.log('=' .repeat(60));
|
|
151
|
+
console.log(`Total tasks to delete: ${uniqueToDelete.length}`);
|
|
152
|
+
console.log(`Space to be freed: ${uniqueToDelete.length} issues`);
|
|
153
|
+
console.log(`Remaining after deletion: ${allTasks.length - uniqueToDelete.length} issues\n`);
|
|
154
|
+
|
|
155
|
+
// Group by category for deletion
|
|
156
|
+
const deleteCategories = {
|
|
157
|
+
'Engineering Meetings': meetingTasks.map(t => t.identifier),
|
|
158
|
+
'Duplicate TODOs': todoTasks.slice(1).map(t => t.identifier),
|
|
159
|
+
'Duplicate Dashboards': analyticsDashboardTasks.slice(1).map(t => t.identifier),
|
|
160
|
+
'Completed Tasks': completedTasks.map(t => t.identifier),
|
|
161
|
+
'Canceled Tasks': canceledTasks.map(t => t.identifier)
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
console.log('š Tasks to delete by category:\n');
|
|
165
|
+
Object.entries(deleteCategories).forEach(([category, tasks]) => {
|
|
166
|
+
if (tasks.length > 0) {
|
|
167
|
+
console.log(`${category} (${tasks.length} tasks):`);
|
|
168
|
+
console.log(` ${tasks.slice(0, 10).join(', ')}${tasks.length > 10 ? '...' : ''}\n`);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Save deletion list to file
|
|
173
|
+
const deleteList = {
|
|
174
|
+
timestamp: new Date().toISOString(),
|
|
175
|
+
summary: {
|
|
176
|
+
total: allTasks.length,
|
|
177
|
+
toDelete: uniqueToDelete.length,
|
|
178
|
+
remaining: allTasks.length - uniqueToDelete.length
|
|
179
|
+
},
|
|
180
|
+
categories: deleteCategories,
|
|
181
|
+
tasks: uniqueToDelete.map(t => ({
|
|
182
|
+
id: t.id,
|
|
183
|
+
identifier: t.identifier,
|
|
184
|
+
title: t.title,
|
|
185
|
+
state: t.state.name
|
|
186
|
+
}))
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const filename = `linear-deletion-list-${new Date().toISOString().split('T')[0]}.json`;
|
|
190
|
+
fs.writeFileSync(filename, JSON.stringify(deleteList, null, 2));
|
|
191
|
+
console.log(`š¾ Deletion list saved to: ${filename}\n`);
|
|
192
|
+
|
|
193
|
+
console.log('šÆ NEXT STEPS:');
|
|
194
|
+
console.log('1. Review the deletion list above');
|
|
195
|
+
console.log('2. Run the delete script to remove these tasks');
|
|
196
|
+
console.log('3. Then add the new phase tasks\n');
|
|
197
|
+
|
|
198
|
+
console.log('To delete these tasks, run:');
|
|
199
|
+
console.log(' node scripts/delete-linear-tasks.js\n');
|
|
200
|
+
|
|
201
|
+
return deleteList;
|
|
202
|
+
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error('ā Analysis failed:', error.message);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Run if called directly
|
|
210
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
211
|
+
analyzeLinearDuplicates().catch(console.error);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export { analyzeLinearDuplicates };
|