feat: Wire full request-changes flow for phase review
- Add PhaseChangesRequestedEvent to event bus - Add requestChangesOnPhase() to ExecutionOrchestrator: reads unresolved comments, creates revision task (category='review'), resets phase to in_progress, queues task for dispatch - Expand merge-skip and branch routing to include 'review' category so revision tasks work directly on the phase branch - Add requestPhaseChanges tRPC procedure (reads comments from DB) - Wire frontend: mutation replaces stub handler, window.prompt for optional summary, loading state on button
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
* - Review per-phase: pause after each phase for diff review
|
||||
*/
|
||||
|
||||
import type { EventBus, TaskCompletedEvent, PhasePendingReviewEvent, PhaseMergedEvent, TaskMergedEvent, PhaseQueuedEvent, AgentStoppedEvent } from '../events/index.js';
|
||||
import type { EventBus, TaskCompletedEvent, PhasePendingReviewEvent, PhaseChangesRequestedEvent, PhaseMergedEvent, TaskMergedEvent, PhaseQueuedEvent, AgentStoppedEvent } from '../events/index.js';
|
||||
import type { BranchManager } from '../git/branch-manager.js';
|
||||
import type { PhaseRepository } from '../db/repositories/phase-repository.js';
|
||||
import type { TaskRepository } from '../db/repositories/task-repository.js';
|
||||
@@ -145,8 +145,8 @@ export class ExecutionOrchestrator {
|
||||
const phase = await this.phaseRepository.findById(task.phaseId);
|
||||
if (!phase) return;
|
||||
|
||||
// Skip merge tasks — they already work on the phase branch directly
|
||||
if (task.category === 'merge') return;
|
||||
// Skip merge/review tasks — they already work on the phase branch directly
|
||||
if (task.category === 'merge' || task.category === 'review') return;
|
||||
|
||||
const initBranch = initiative.branch;
|
||||
const phBranch = phaseBranchName(initBranch, phase.name);
|
||||
@@ -304,4 +304,84 @@ export class ExecutionOrchestrator {
|
||||
|
||||
log.info({ phaseId }, 'phase review approved and merged');
|
||||
}
|
||||
|
||||
/**
|
||||
* Request changes on a phase that's pending review.
|
||||
* Creates a revision task, resets the phase to in_progress, and dispatches.
|
||||
*/
|
||||
async requestChangesOnPhase(
|
||||
phaseId: string,
|
||||
unresolvedComments: Array<{ filePath: string; lineNumber: number; body: string }>,
|
||||
summary?: string,
|
||||
): Promise<{ taskId: string }> {
|
||||
const phase = await this.phaseRepository.findById(phaseId);
|
||||
if (!phase) throw new Error(`Phase not found: ${phaseId}`);
|
||||
if (phase.status !== 'pending_review') {
|
||||
throw new Error(`Phase ${phaseId} is not pending review (status: ${phase.status})`);
|
||||
}
|
||||
|
||||
const initiative = await this.initiativeRepository.findById(phase.initiativeId);
|
||||
if (!initiative) throw new Error(`Initiative not found: ${phase.initiativeId}`);
|
||||
|
||||
// Build revision task description from comments + summary
|
||||
const lines: string[] = [];
|
||||
if (summary) {
|
||||
lines.push(`## Summary\n\n${summary}\n`);
|
||||
}
|
||||
if (unresolvedComments.length > 0) {
|
||||
lines.push('## Review Comments\n');
|
||||
// Group comments by file
|
||||
const byFile = new Map<string, typeof unresolvedComments>();
|
||||
for (const c of unresolvedComments) {
|
||||
const arr = byFile.get(c.filePath) ?? [];
|
||||
arr.push(c);
|
||||
byFile.set(c.filePath, arr);
|
||||
}
|
||||
for (const [filePath, fileComments] of byFile) {
|
||||
lines.push(`### ${filePath}\n`);
|
||||
for (const c of fileComments) {
|
||||
lines.push(`- **Line ${c.lineNumber}**: ${c.body}`);
|
||||
}
|
||||
lines.push('');
|
||||
}
|
||||
}
|
||||
|
||||
const description = lines.join('\n') || 'Address review feedback.';
|
||||
|
||||
// Create revision task
|
||||
const task = await this.taskRepository.create({
|
||||
phaseId,
|
||||
initiativeId: phase.initiativeId,
|
||||
name: `Address review feedback: ${phase.name}`,
|
||||
description,
|
||||
category: 'review',
|
||||
priority: 'high',
|
||||
});
|
||||
|
||||
// Reset phase status back to in_progress
|
||||
await this.phaseRepository.update(phaseId, { status: 'in_progress' as any });
|
||||
|
||||
// Queue task for dispatch
|
||||
await this.dispatchManager.queue(task.id);
|
||||
|
||||
// Emit event
|
||||
const event: PhaseChangesRequestedEvent = {
|
||||
type: 'phase:changes_requested',
|
||||
timestamp: new Date(),
|
||||
payload: {
|
||||
phaseId,
|
||||
initiativeId: phase.initiativeId,
|
||||
taskId: task.id,
|
||||
commentCount: unresolvedComments.length,
|
||||
},
|
||||
};
|
||||
this.eventBus.emit(event);
|
||||
|
||||
log.info({ phaseId, taskId: task.id, commentCount: unresolvedComments.length }, 'changes requested on phase');
|
||||
|
||||
// Kick off dispatch
|
||||
this.scheduleDispatch();
|
||||
|
||||
return { taskId: task.id };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user