UNPKG

conductor-tasks

Version:

Task Manager for AI Development

343 lines 14.3 kB
import { TaskPriority, TaskStatus } from './types.js'; const DEFAULT_CONFIG = { useEmoji: true, useProgressBars: true, includeMeta: true, colorize: true, showTimestamps: true, compactMode: false, includeSubtasks: true, taskDetailsLevel: 'standard' }; export class MarkdownRenderer { constructor(config = {}) { this.config = { ...DEFAULT_CONFIG, ...config }; } renderTasksToMarkdown(tasks, projectName = 'Project Tasks', lastUpdated) { let markdown = `# ${projectName}\n\n`; if (this.config.includeMeta) { markdown += this.renderMetadata(tasks, lastUpdated); } markdown += this.renderSummary(tasks); const tasksByStatus = this.groupTasksByStatus(tasks); for (const status of Object.values(TaskStatus)) { const tasksForStatus = tasksByStatus.get(status) || []; if (tasksForStatus.length === 0) continue; markdown += this.renderStatusSection(status, tasksForStatus, tasks); } return markdown; } renderMetadata(tasks, lastUpdated) { const now = lastUpdated || new Date(); const dateStr = now.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); const timeStr = now.toLocaleTimeString('en-US'); let markdown = '## Metadata\n\n'; markdown += `- **Last Updated**: ${dateStr} at ${timeStr}\n`; markdown += `- **Total Tasks**: ${tasks.size}\n`; const statusCounts = new Map(); const priorityCounts = new Map(); for (const task of tasks.values()) { const statusCount = statusCounts.get(task.status) || 0; statusCounts.set(task.status, statusCount + 1); const priorityCount = priorityCounts.get(task.priority) || 0; priorityCounts.set(task.priority, priorityCount + 1); } markdown += '- **Status Breakdown**:\n'; for (const [status, count] of statusCounts.entries()) { const emoji = this.getStatusEmoji(status); markdown += ` - ${emoji} ${status}: ${count}\n`; } markdown += '- **Priority Breakdown**:\n'; for (const priority of [ TaskPriority.CRITICAL, TaskPriority.HIGH, TaskPriority.MEDIUM, TaskPriority.LOW, TaskPriority.BACKLOG ]) { const count = priorityCounts.get(priority) || 0; if (count > 0) { const emoji = this.getPriorityEmoji(priority); markdown += ` - ${emoji} ${priority}: ${count}\n`; } } markdown += '\n'; return markdown; } renderSummary(tasks) { let markdown = '## Overview\n\n'; const statusCounts = { [TaskStatus.BACKLOG]: 0, [TaskStatus.TODO]: 0, [TaskStatus.IN_PROGRESS]: 0, [TaskStatus.REVIEW]: 0, [TaskStatus.DONE]: 0, [TaskStatus.BLOCKED]: 0 }; const topLevelTasks = Array.from(tasks.values()).filter(task => !task.parent); const totalTopLevelTasks = topLevelTasks.length; for (const task of topLevelTasks) { statusCounts[task.status]++; } if (this.config.useProgressBars) { const doneCount = statusCounts[TaskStatus.DONE]; const totalCount = totalTopLevelTasks; const progressPercent = totalCount > 0 ? Math.round((doneCount / totalCount) * 100) : 0; markdown += `### Overall Progress: ${progressPercent}%\n\n`; const progressBarLength = 30; const filledLength = Math.round((progressPercent / 100) * progressBarLength); const emptyLength = progressBarLength - filledLength; const filledChar = '█'; const emptyChar = '░'; const progressBar = filledChar.repeat(filledLength) + emptyChar.repeat(emptyLength); markdown += `\`${progressBar}\` ${doneCount}/${totalCount} tasks complete\n\n`; markdown += '### Status Breakdown\n\n'; for (const status of Object.values(TaskStatus)) { const count = statusCounts[status]; const statusPercent = totalTopLevelTasks > 0 ? Math.round((count / totalTopLevelTasks) * 100) : 0; const statusEmoji = this.getStatusEmoji(status); const miniBarLength = 15; const miniFilled = Math.round((statusPercent / 100) * miniBarLength); const miniEmpty = miniBarLength - miniFilled; const miniBar = filledChar.repeat(miniFilled) + emptyChar.repeat(miniEmpty); markdown += `${statusEmoji} **${status}**: \`${miniBar}\` ${count} tasks (${statusPercent}%)\n`; } markdown += '\n'; } return markdown; } renderStatusSection(status, tasks, allTasks) { const emoji = this.getStatusEmoji(status); let markdown = `## ${emoji} ${this.formatStatus(status)}\n\n`; const sortedTasks = this.sortTasksByPriority(tasks); for (const task of sortedTasks) { if (task.parent) continue; markdown += this.renderTask(task, allTasks); } return markdown; } renderTask(task, allTasks) { const priorityEmoji = this.getPriorityEmoji(task.priority); let markdown = ''; markdown += `### ${priorityEmoji} ${task.title} (${task.id})\n\n`; markdown += this.renderTaskMetadata(task); if (task.description && task.description.trim()) { const indentedDescription = task.description .split('\n') .map(line => line.trim() ? `> ${line}` : '') .join('\n'); markdown += `${indentedDescription}\n\n`; } if (this.config.useProgressBars && task.subtasks && task.subtasks.length > 0) { markdown += this.renderTaskProgressBar(task, allTasks); } if (task.dependencies && task.dependencies.length > 0) { markdown += '**Dependencies:**\n'; for (const depId of task.dependencies) { const dep = allTasks.get(depId); if (dep) { const depPriorityEmoji = this.getPriorityEmoji(dep.priority); const depStatusEmoji = this.getStatusEmoji(dep.status); markdown += `- ${depPriorityEmoji} ${depStatusEmoji} [${dep.title}] (${depId})\n`; } } markdown += '\n'; } if (this.config.includeSubtasks && task.subtasks && task.subtasks.length > 0) { markdown += '**Subtasks:**\n'; const useCollapsible = task.subtasks.length > 3 && !this.config.compactMode; if (useCollapsible) { markdown += '<details>\n<summary>Show Subtasks</summary>\n\n'; } for (const subId of task.subtasks) { const sub = allTasks.get(subId); if (sub) { const subPriorityEmoji = this.getPriorityEmoji(sub.priority); const subStatusEmoji = this.getStatusEmoji(sub.status); markdown += `- ${subPriorityEmoji} ${subStatusEmoji} **${sub.title}** (${subId})\n`; if (this.config.taskDetailsLevel === 'verbose' && sub.description && sub.description.trim()) { const truncatedDesc = sub.description.length > 100 ? sub.description.substring(0, 97) + '...' : sub.description; markdown += ` *${truncatedDesc.replace(/\n/g, ' ')}*\n`; } } } if (useCollapsible) { markdown += '</details>\n\n'; } else { markdown += '\n'; } } if (task.notes && task.notes.length > 0) { const useCollapsible = task.notes.length > 2 && !this.config.compactMode; markdown += '**Notes:**\n'; if (useCollapsible) { markdown += '<details>\n<summary>Show Notes</summary>\n\n'; } const sortedNotes = [...task.notes].sort((a, b) => b.timestamp - a.timestamp); for (const note of sortedNotes) { markdown += this.renderNote(note); } if (useCollapsible) { markdown += '</details>\n\n'; } else { markdown += '\n'; } } markdown += '---\n\n'; return markdown; } renderTaskMetadata(task) { let markdown = ''; markdown += `**Priority:** ${task.priority}`; const complexityStars = '★'.repeat(Math.min(task.complexity, 5)) + '☆'.repeat(Math.max(0, 5 - Math.min(task.complexity, 5))); markdown += ` | **Complexity:** ${complexityStars} (${task.complexity}/10)`; if (task.estimatedEffort) { markdown += ` | **Est:** ${task.estimatedEffort}`; } if (task.actualEffort) { markdown += ` | **Actual:** ${task.actualEffort}`; } if (task.assignee) { markdown += ` | **Assignee:** ${task.assignee}`; } if (this.config.showTimestamps) { const createdDate = new Date(task.createdAt).toLocaleDateString(); markdown += ` | **Created:** ${createdDate}`; if (task.updatedAt > task.createdAt) { const updatedDate = new Date(task.updatedAt).toLocaleDateString(); markdown += ` | **Updated:** ${updatedDate}`; } } markdown += '\n\n'; if (task.tags && task.tags.length > 0) { markdown += `Tags: ${task.tags.map(tag => `\`${tag}\``).join(' ')}\n\n`; } return markdown; } renderTaskProgressBar(task, allTasks) { let markdown = ''; let completedCount = 0; let totalCount = 0; if (task.subtasks) { totalCount = task.subtasks.length; for (const subId of task.subtasks) { const sub = allTasks.get(subId); if (sub && sub.status === TaskStatus.DONE) { completedCount++; } } } const progressPercent = totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0; const progressBarLength = 20; const filledLength = Math.round((progressPercent / 100) * progressBarLength); const emptyLength = progressBarLength - filledLength; const filledChar = '█'; const emptyChar = '░'; const progressBar = filledChar.repeat(filledLength) + emptyChar.repeat(emptyLength); markdown += `**Progress:** \`${progressBar}\` ${progressPercent}% (${completedCount}/${totalCount} subtasks)\n\n`; return markdown; } renderNote(note) { const date = new Date(note.timestamp).toLocaleString(); const typeEmoji = this.getNoteTypeEmoji(note.type); return `- ${typeEmoji} **${note.type}** (${date}, ${note.author}): ${note.content}\n`; } getStatusEmoji(status) { if (!this.config.useEmoji) return ''; switch (status) { case TaskStatus.BACKLOG: return '📝'; case TaskStatus.TODO: return '📋'; case TaskStatus.IN_PROGRESS: return '⚙️'; case TaskStatus.REVIEW: return '👁️'; case TaskStatus.BLOCKED: return '🚫'; case TaskStatus.DONE: return '✅'; default: return ''; } } getPriorityEmoji(priority) { if (!this.config.useEmoji) return ''; switch (priority) { case TaskPriority.CRITICAL: return '🔴'; case TaskPriority.HIGH: return '🟠'; case TaskPriority.MEDIUM: return '🟡'; case TaskPriority.LOW: return '🟢'; case TaskPriority.BACKLOG: return '⚪'; default: return ''; } } getNoteTypeEmoji(type) { if (!this.config.useEmoji) return ''; switch (type) { case 'progress': return '📈'; case 'comment': return '💬'; case 'blocker': return '🚧'; case 'solution': return '💡'; default: return '📌'; } } formatStatus(status) { switch (status) { case TaskStatus.BACKLOG: return 'Backlog'; case TaskStatus.TODO: return 'To Do'; case TaskStatus.IN_PROGRESS: return 'In Progress'; case TaskStatus.REVIEW: return 'In Review'; case TaskStatus.BLOCKED: return 'Blocked'; case TaskStatus.DONE: return 'Done'; default: return status; } } groupTasksByStatus(tasks) { const tasksByStatus = new Map(); for (const status of Object.values(TaskStatus)) { tasksByStatus.set(status, []); } for (const task of tasks.values()) { const tasksForStatus = tasksByStatus.get(task.status) || []; tasksForStatus.push(task); tasksByStatus.set(task.status, tasksForStatus); } return tasksByStatus; } sortTasksByPriority(tasks) { const priorityOrder = { [TaskPriority.CRITICAL]: 0, [TaskPriority.HIGH]: 1, [TaskPriority.MEDIUM]: 2, [TaskPriority.LOW]: 3, [TaskPriority.BACKLOG]: 4 }; return [...tasks].sort((a, b) => { const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority]; if (priorityDiff === 0) { return b.complexity - a.complexity; } return priorityDiff; }); } renderTasksToHtml(tasks, projectName = 'Project Tasks') { return ''; } updateConfig(config) { this.config = { ...this.config, ...config }; } getConfig() { return { ...this.config }; } } export default new MarkdownRenderer(); //# sourceMappingURL=markdownRenderer.js.map