Files
Lukas May 8804455c77 Remove task-level approval system
Task-level approval (requiresApproval, mergeRequiresApproval,
pending_approval status) was redundant with executionMode
(yolo vs review_per_phase) and blocked the orchestrator's
phase completion flow. Tasks now complete directly;
phase-level review via executionMode is the right granularity.

Removed: schema columns (left in DB, removed from Drizzle),
TaskPendingApprovalEvent, approveTask/listPendingApprovals
procedures, findPendingApproval repository method, and all
frontend approval UI.
2026-03-05 17:09:48 +01:00

157 lines
4.8 KiB
TypeScript

/**
* 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);
}