/** * Full-Flow Test Report Utility * * Plain console.log formatters for human-readable output at each stage of the * full-flow integration test. No external dependencies. */ import { execSync } from 'node:child_process'; import { join } from 'node:path'; import type { Phase, Task } from '../../../db/schema.js'; import type { AgentResult } from '../../../agent/types.js'; // ============================================================================= // Types // ============================================================================= export interface ExecutedTask { task: Task; result: AgentResult | null; } // ============================================================================= // Helpers // ============================================================================= const DIVIDER = '═'.repeat(60); const THIN = '─'.repeat(60); function section(title: string): void { console.log(`\n${DIVIDER}`); console.log(` ${title}`); console.log(DIVIDER); } function line(msg: string): void { console.log(` ${msg}`); } // ============================================================================= // Stage reporters // ============================================================================= export function printHeader(initiativeName: string): void { section(`FULL-FLOW TEST: ${initiativeName}`); console.log(` Started at: ${new Date().toISOString()}`); } export function printDiscussResult(agentId: string, result: AgentResult | null): void { console.log(`\n[DISCUSS]`); console.log(THIN); line(`Agent: ${agentId}`); if (result) { line(`Success: ${result.success}`); if (result.message) line(`Message: ${result.message.slice(0, 200)}`); } else { line('Result: null (agent may have crashed)'); } } export function printPlanResult(phases: Phase[]): void { console.log(`\n[PLAN] ${phases.length} phase(s) created`); console.log(THIN); phases.forEach((ph, i) => { line(`${i + 1}. ${ph.name}`); }); } export function printDetailResult(phase: Phase, tasks: Task[]): void { console.log(`\n[DETAIL] Phase "${phase.name}" → ${tasks.length} task(s)`); console.log(THIN); tasks.forEach((t, i) => { const flags = [t.category, t.type].join(', '); line(`${i + 1}. ${t.name} [${flags}]`); if (t.description) { line(` ${t.description.slice(0, 120)}`); } }); } export function printExecuteResult(executed: ExecutedTask[]): void { const succeeded = executed.filter((e) => e.result?.success).length; console.log(`\n[EXECUTE] ${succeeded}/${executed.length} task(s) succeeded`); console.log(THIN); for (const { task, result } of executed) { const icon = result?.success ? '✓' : '✗'; line(`${icon} ${task.name}`); if (result && !result.success) { line(` Error: ${result.message?.slice(0, 120)}`); } } } export function printGitDiff(workspaceRoot: string, projectName: string): void { console.log('\n[GIT DIFF — agent worktrees]'); console.log(THIN); // Find all agent worktrees for this project const worktreesBase = join(workspaceRoot, 'agent-workdirs'); try { const dirs = execSync(`ls "${worktreesBase}" 2>/dev/null || echo ""`, { encoding: 'utf8' }) .trim() .split('\n') .filter(Boolean); for (const dir of dirs) { const projectDir = join(worktreesBase, dir, projectName); try { const stat = execSync(`git -C "${projectDir}" diff HEAD~1 --stat 2>/dev/null || echo ""`, { encoding: 'utf8', }).trim(); if (stat) { line(`Worktree: ${dir}/${projectName}`); stat.split('\n').forEach((l) => line(` ${l}`)); } } catch { // Worktree might not have commits — skip silently } } } catch { line('(no agent worktrees found)'); } } export function printNpmTestResult(projectDir: string): void { console.log('\n[NPM TEST]'); console.log(THIN); try { const output = execSync('node --test src/todo.test.js', { cwd: projectDir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], }); line('Tests passed:'); output.split('\n').forEach((l) => line(` ${l}`)); } catch (err: unknown) { const e = err as { stdout?: string; stderr?: string; status?: number }; line(`Tests FAILED (exit ${e.status ?? '?'})`); if (e.stdout) e.stdout.split('\n').forEach((l) => line(` ${l}`)); if (e.stderr) e.stderr.split('\n').forEach((l) => line(` ${l}`)); } } export function printFinalSummary( initiativeName: string, phases: Phase[], tasks: Task[], executed: ExecutedTask[], durationMs: number, ): void { section(`SUMMARY: ${initiativeName}`); line(`Duration : ${Math.round(durationMs / 1000)}s`); line(`Phases : ${phases.length}`); line(`Tasks : ${tasks.length}`); line(`Executed : ${executed.filter((e) => e.result?.success).length}/${executed.length} succeeded`); console.log(DIVIDER); }