UNPKG

@stackmemoryai/stackmemory

Version:

Lossless, project-scoped memory for AI coding tools. Durable context across sessions with 56 MCP tools, FTS5 search, conductor orchestrator, loop/watch monitoring, snapshot capture, pre-flight overlap checks, Claude/Codex/OpenCode wrappers, Linear sync, a

96 lines (95 loc) 3.77 kB
#!/usr/bin/env node import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); import chalk from "chalk"; import { AnalyticsService } from "../../features/analytics/core/analytics-service.js"; async function displayAnalyticsDashboard(projectPath) { const service = new AnalyticsService(projectPath || process.cwd()); try { await service.syncFromTaskStore(); const state = await service.getDashboardState(); const { metrics, recentTasks, teamMetrics } = state; console.clear(); console.log(chalk.bold.cyan("\nStackMemory Analytics Dashboard\n")); console.log(chalk.gray("\u2500".repeat(50))); console.log(chalk.bold.white("\nKey Metrics\n")); const metricsDisplay = [ ["Total Tasks", metrics.totalTasks], ["Completed", chalk.green(metrics.completedTasks)], ["In Progress", chalk.yellow(metrics.inProgressTasks)], ["Blocked", chalk.red(metrics.blockedTasks)], ["Completion Rate", `${metrics.completionRate.toFixed(1)}%`], ["Avg Time to Complete", formatDuration(metrics.averageTimeToComplete)], ["Effort Accuracy", `${metrics.effortAccuracy.toFixed(0)}%`], ["Blocking Issues", metrics.blockingIssuesCount] ]; metricsDisplay.forEach(([label, value]) => { console.log(` ${chalk.gray(String(label).padEnd(20))} ${value}`); }); if (metrics.velocityTrend.length > 0) { console.log(chalk.bold.white("\n\u{1F4C9} Velocity Trend (last 7 days)\n")); const maxVelocity = Math.max(...metrics.velocityTrend); const scale = maxVelocity > 0 ? 10 / maxVelocity : 1; metrics.velocityTrend.slice(-7).forEach((velocity, i) => { const bar = "\u2588".repeat(Math.round(velocity * scale)); const day = /* @__PURE__ */ new Date(); day.setDate(day.getDate() - (6 - i)); console.log( ` ${day.toLocaleDateString("en", { weekday: "short" }).padEnd(4)} ${bar} ${velocity}` ); }); } if (recentTasks.length > 0) { console.log(chalk.bold.white("\nRecent Tasks\n")); recentTasks.slice(0, 5).forEach((task) => { const statePrefix = { completed: "[DONE]", in_progress: "[PROG]", blocked: "[BLCK]", todo: "[TODO]" }[task.state]; const priorityColor = { urgent: chalk.red, high: chalk.yellow, medium: chalk.blue, low: chalk.gray }[task.priority]; console.log( ` ${statePrefix} ${priorityColor(`[${task.priority.toUpperCase()}]`)} ${task.title.slice(0, 50)}` ); }); } if (teamMetrics.length > 0) { console.log(chalk.bold.white("\n\u{1F465} Team Performance\n")); teamMetrics.slice(0, 3).forEach((member) => { const bar = "\u2593".repeat(Math.round(member.contributionPercentage / 10)); console.log( ` ${member.userName.padEnd(15)} ${bar} ${member.contributionPercentage.toFixed(0)}% (${member.individualMetrics.completedTasks} tasks)` ); }); } console.log(chalk.gray("\n" + "\u2500".repeat(50))); console.log( chalk.gray(`Last updated: ${state.lastUpdated.toLocaleString()}`) ); console.log(); } finally { service.close(); } } function formatDuration(ms) { if (ms === 0) return "N/A"; const hours = Math.floor(ms / 36e5); const days = Math.floor(hours / 24); if (days > 0) return `${days}d ${hours % 24}h`; if (hours > 0) return `${hours}h`; return "<1h"; } if (import.meta.url === `file://${process.argv[1]}`) { displayAnalyticsDashboard().catch(console.error); } export { displayAnalyticsDashboard };