/** * Session todo list: merge-by-id, grok-shaped statuses. No Bare imports. */ const STATUSES = ['pending', 'in_progress', 'completed', 'cancelled']; function normalizeStatus(s) { const v = String(s || 'pending') .toLowerCase() .replace(/[\s-]+/g, '_'); if (v === 'done' || v === 'complete') return 'completed'; if (v === 'progress' || v === 'doing' || v === 'inprogress') return 'in_progress'; if (v === 'cancel' || v === 'canceled') return 'cancelled'; if (STATUSES.indexOf(v) >= 0) return v; return 'pending'; } function normalizeItem(t, i) { t = t || {}; return { id: String(t.id || 'todo_' + (i + 1)), content: String(t.content || t.text || t.title || ''), status: normalizeStatus(t.status), }; } function merge(existing, incoming, mode) { const next = Array.isArray(incoming) ? incoming.map(normalizeItem) : []; if (mode === 'replace' || !existing || !existing.length) return next; const byId = new Map(); for (const t of existing) { const n = normalizeItem(t, 0); byId.set(n.id, n); } for (const t of next) { const prev = byId.get(t.id) || {}; const merged = Object.assign({}, prev, t); if (!t.content && prev.content) merged.content = prev.content; byId.set(t.id, merged); } return Array.from(byId.values()); } function hasOpen(list) { return (list || []).some((t) => t.status === 'pending' || t.status === 'in_progress'); } function formatBlock(list) { const todos = list || []; if (!todos.length) return '[todos]\n(none)'; return ( '[todos]\n' + todos.map((t) => '- [' + t.status + '] ' + t.id + ': ' + t.content).join('\n') ); } module.exports = { STATUSES, normalizeStatus, normalizeItem, merge, hasOpen, formatBlock };