@stackmemoryai/stackmemory
Version:
Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.
114 lines (95 loc) ⢠3.66 kB
JavaScript
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
function cleanDuplicateTasks() {
const tasksFile = path.join(__dirname, '..', '.stackmemory', 'tasks.jsonl');
if (!fs.existsSync(tasksFile)) {
console.error('Tasks file not found:', tasksFile);
return;
}
const lines = fs.readFileSync(tasksFile, 'utf8').split('\n').filter(l => l.trim());
const taskMap = new Map();
const seenTitles = new Map();
const duplicates = [];
// Process each line
for (const line of lines) {
try {
const task = JSON.parse(line);
// Create a unique key based on title and external refs
let key = task.title;
if (task.external_refs) {
const linearId = Object.keys(task.external_refs).find(k => k.startsWith('STA-') || k.startsWith('ENG-'));
if (linearId) key = linearId;
}
// Normalize title for comparison (remove task IDs from title)
const normalizedTitle = task.title
.replace(/^\[[^\]]+\]\s*/, '') // Remove [STA-XXX] or [ENG-XXX] prefix
.replace(/^\[.*?\]\s*/, '') // Remove priority markers
.trim();
// Track duplicates by normalized title
if (!seenTitles.has(normalizedTitle)) {
seenTitles.set(normalizedTitle, task.id);
taskMap.set(task.id, task);
} else {
duplicates.push({
id: task.id,
title: task.title,
keepId: seenTitles.get(normalizedTitle)
});
}
} catch (e) {
console.warn('Failed to parse line:', e.message);
}
}
console.log(`\nš Task Analysis:`);
console.log(` Total lines: ${lines.length}`);
console.log(` Unique tasks: ${taskMap.size}`);
console.log(` Duplicates found: ${duplicates.length}`);
if (duplicates.length > 0) {
console.log('\nš Duplicates to remove:');
duplicates.forEach(d => {
console.log(` - ${d.id}: ${d.title}`);
console.log(` (keeping ${d.keepId})`);
});
// Create backup
const backupFile = tasksFile + '.backup-' + Date.now();
fs.copyFileSync(tasksFile, backupFile);
console.log(`\nš¾ Backup created: ${backupFile}`);
// Write cleaned tasks
const cleanedLines = [];
for (const line of lines) {
try {
const task = JSON.parse(line);
const isDuplicate = duplicates.find(d => d.id === task.id);
if (!isDuplicate) {
cleanedLines.push(line);
}
} catch (e) {
// Keep unparseable lines as-is
cleanedLines.push(line);
}
}
fs.writeFileSync(tasksFile, cleanedLines.join('\n') + '\n');
console.log(`\nā
Cleaned tasks file written`);
console.log(` Removed ${duplicates.length} duplicate tasks`);
console.log(` Final task count: ${cleanedLines.length}`);
} else {
console.log('\nā
No duplicates found!');
}
// Show remaining unique tasks
console.log('\nš Unique tasks remaining:');
const uniqueTasks = Array.from(taskMap.values())
.filter(t => t.type === 'task_create' || t.type === 'task_update')
.sort((a, b) => (a.priority === 'urgent' ? -1 : a.priority === 'high' ? 0 : 1));
uniqueTasks.slice(0, 10).forEach(task => {
const status = task.status === 'completed' ? 'ā
' :
task.status === 'in_progress' ? 'š' : 'ā³';
console.log(` ${status} [${task.priority}] ${task.title}`);
});
if (uniqueTasks.length > 10) {
console.log(` ... and ${uniqueTasks.length - 10} more tasks`);
}
}
cleanDuplicateTasks();