export function scoreDecay(task, settings, allTasks) { const now = Date.now(); const ageMs = now - task.createdAt.getTime(); const ageDays = ageMs / (1000 * 60 * 60 * 24); // Priority weight: 1 = lowest, 4 = highest; invert so low-priority tasks decay faster const priorityWeight = (5 - task.priority) * 15; // 15-60 range // Project activity: if many tasks in same project are completed recently, project is active const projectTasks = allTasks.filter(t => t.projectId === task.projectId && t.projectId !== null); const recentProjectActivity = projectTasks.filter(t => t.completedAt && (now - t.completedAt.getTime()) < 30 * 24 * 60 * 60 * 1000).length; const projectActivity = Math.min(30, recentProjectActivity * 5); // 0-30 // Label density: more labels = more mental overhead = higher decay const labelDensity = task.labels.length * 8; // Due urgency: if overdue, add urgency; if far future, reduce decay let dueUrgency = 0; if (task.dueDate) { const dueMs = task.dueDate.getTime() - now; if (dueMs < 0) { dueUrgency = 20; // overdue } else if (dueMs < 7 * 24 * 60 * 60 * 1000) { dueUrgency = -10; // due soon, reduce decay } } // Auto-archive: if older than autoArchiveDays, cap at 100 let score = Math.round((Math.min(ageDays, settings.autoArchiveDays) / settings.autoArchiveDays) * 50 + priorityWeight + projectActivity + labelDensity + dueUrgency); score = Math.max(0, Math.min(100, score)); let explanation = `Age: ${Math.round(ageDays)} days`; if (priorityWeight > 30) explanation += `; Low priority (${task.priority})`; if (projectActivity > 10) explanation += `; Active project`; if (labelDensity > 0) explanation += `; ${task.labels.length} labels`; if (dueUrgency > 0) explanation += `; Overdue`; if (dueUrgency < 0) explanation += `; Due soon`; return { score, ageDays: Math.round(ageDays * 10) / 10, priorityWeight, projectActivity, labelDensity, dueUrgency, explanation, }; } export function getReviewTasks(tasks, settings) { const scored = tasks .filter(t => !t.completedAt) .filter(t => { const ageDays = (Date.now() - t.createdAt.getTime()) / (1000 * 60 * 60 * 24); return ageDays >= settings.decayThresholdDays; }) .filter(t => !settings.dismissedTasks.includes(t.id)) .filter(t => !settings.archivedTasks.includes(t.id)) .map(t => ({ task: t, score: scoreDecay(t, settings, tasks) })) .sort((a, b) => b.score.score - a.score.score); return scored.slice(0, settings.maxTasksPerReview); } export function getAutoArchiveCandidates(tasks, settings) { return tasks.filter(t => { if (t.completedAt) return false; const ageDays = (Date.now() - t.createdAt.getTime()) / (1000 * 60 * 60 * 24); return ageDays >= settings.autoArchiveDays && !settings.archivedTasks.includes(t.id); }); }