import { WorkflowStatusManager } from '../src/backend/services/WorkflowStatusManager.ts'; import { getPendingPhaseUpdates } from '../src/backend/services/PhaseUpdateQueue.ts'; import { exec as execCb } from 'child_process'; import { promisify } from 'util'; const exec = promisify(execCb); type CommitInfo = { hash: string; msg: string; date: string }; type MorningReportOptions = { commitWindowHours?: number; stalledThresholdMinutes?: number; now?: Date; getRecentCommitsFn?: (sinceIso: string) => Promise; }; async function getRecentCommits(sinceIso: string): Promise { const cmd = `git log --since="${sinceIso}" --pretty=format:"%h|%s|%cI"`; const { stdout } = await exec(cmd); if (!stdout.trim()) return []; return stdout .split(/\r?\n/) .map((line) => line.trim()) .filter(Boolean) .map((line) => { const [hash, msg, date] = line.split('|'); return { hash, msg, date }; }); } function minutesSince(iso: string, now: Date): number { return Math.floor((now.getTime() - new Date(iso).getTime()) / 60000); } export async function generateMorningReport(options: MorningReportOptions = {}): Promise { const commitWindowHours = options.commitWindowHours ?? 24; const stalledThresholdMinutes = options.stalledThresholdMinutes ?? 60; const now = options.now ?? new Date(); const getRecentCommitsFn = options.getRecentCommitsFn ?? getRecentCommits; const since = new Date(now.getTime() - commitWindowHours * 60 * 60 * 1000).toISOString(); const commits = await getRecentCommitsFn(since); const statusManager = new WorkflowStatusManager(); const status = await statusManager.read(); const pending = await getPendingPhaseUpdates(); let out = '# 🌅 Morning Workflow Report\n\n'; out += `## Recent Commits (last ${commitWindowHours}h)\n`; if (!commits.length) { out += '- (No commits in window)\n\n'; } else { for (const c of commits) { out += `- \`${c.hash}\` ${c.msg} _(at ${c.date})_\n`; } out += '\n'; } out += '## Workflow Status\n'; if (!status) { out += '- No workflow status available\n\n'; } else { out += `- Current phase: **${status.currentPhase ?? '-'}**\n`; out += `- State: **${status.overallStatus ?? '-'}**\n`; out += `- Last updated: ${status.lastUpdated ?? '-'}\n`; out += `- Completed phases: ${(status.completedPhases ?? []).join(', ') || '-'}\n\n`; if (status.overallStatus === 'running' && status.lastUpdated) { const elapsed = minutesSince(status.lastUpdated, now); if (elapsed >= stalledThresholdMinutes) { out += '## ⚠️ Workflow Stalled\n'; out += `- Reason: No progress in ${elapsed} minutes (threshold: ${stalledThresholdMinutes}m). Last update: ${status.lastUpdated}\n`; out += '- Recommendation: Restart or debug orchestrator.\n\n'; } } if (status.lastFailureReason) { out += '## Blockers\n'; out += `- ❗ ${status.lastFailureReason}\n\n`; } } out += '## Pending Phase Updates\n'; if (!pending.length) { out += '- All phase updates relayed\n'; } else { for (const p of pending) { out += `- [${p.eventType}] ${p.summary} (phase: ${p.phase ?? '-'}) at ${p.timestamp} [id: ${p.id}]\n`; } } console.log(out); return out; } if (import.meta.url === `file://${process.argv[1]}`) { generateMorningReport().catch((err) => { console.error('Failed to generate morning report', err); process.exit(1); }); } export default generateMorningReport;