feat: Implement phases 6 to 9 of the review cycle fix process, including discovery, batching, parallel planning, execution, and completion

- Added Phase 6: Fix Discovery & Batching with intelligent grouping and batching of findings.
- Added Phase 7: Fix Parallel Planning to launch planning agents for concurrent analysis and aggregation of partial plans.
- Added Phase 8: Fix Execution for stage-based execution of fixes with conservative test verification.
- Added Phase 9: Fix Completion to aggregate results, generate summary reports, and handle session completion.
- Introduced new frontend components: ResizeHandle for draggable resizing of sidebar panels and useResizablePanel hook for managing panel sizes with localStorage persistence.
- Added PowerShell script for checking TypeScript errors in source code, excluding test files.
This commit is contained in:
catlog22
2026-02-07 19:28:33 +08:00
parent ba5f4eba84
commit d43696d756
90 changed files with 8462 additions and 616 deletions

View File

@@ -11,7 +11,10 @@ import type {
ExecutionStatus,
NodeExecutionState,
ExecutionLog,
NodeExecutionOutput,
} from '../types/execution';
import type { ToolCallExecution } from '../types/toolCall';
import type { CliOutputLine } from './cliStreamStore';
// Constants
const MAX_LOGS = 500;
@@ -28,6 +31,15 @@ const initialState = {
logs: [] as ExecutionLog[],
maxLogs: MAX_LOGS,
// Node output tracking
nodeOutputs: {} as Record<string, NodeExecutionOutput>,
// Tool call tracking
nodeToolCalls: {} as Record<string, ToolCallExecution[]>,
// Selected node for detail view
selectedNodeId: null as string | null,
// UI state
isMonitorPanelOpen: false,
autoScrollLogs: true,
@@ -35,7 +47,7 @@ const initialState = {
export const useExecutionStore = create<ExecutionStore>()(
devtools(
(set) => ({
(set, get) => ({
...initialState,
// ========== Execution Lifecycle ==========
@@ -204,6 +216,248 @@ export const useExecutionStore = create<ExecutionStore>()(
setAutoScrollLogs: (autoScroll: boolean) => {
set({ autoScrollLogs: autoScroll }, false, 'setAutoScrollLogs');
},
// ========== Node Output Management ==========
addNodeOutput: (nodeId: string, output: CliOutputLine) => {
set(
(state) => {
const current = state.nodeOutputs[nodeId];
if (!current) {
// Create new node output
return {
nodeOutputs: {
...state.nodeOutputs,
[nodeId]: {
nodeId,
outputs: [output],
toolCalls: [],
logs: [],
variables: {},
startTime: Date.now(),
},
},
};
}
// Append to existing output
return {
nodeOutputs: {
...state.nodeOutputs,
[nodeId]: {
...current,
outputs: [...current.outputs, output],
},
},
};
},
false,
'addNodeOutput'
);
},
clearNodeOutputs: (nodeId: string) => {
set(
(state) => {
const newOutputs = { ...state.nodeOutputs };
delete newOutputs[nodeId];
return { nodeOutputs: newOutputs };
},
false,
'clearNodeOutputs'
);
},
// ========== Tool Call Management ==========
startToolCall: (
nodeId: string,
callId: string,
data: { kind: ToolCallExecution['kind']; subtype?: string; description: string }
) => {
const newToolCall: ToolCallExecution = {
callId,
nodeId,
status: 'executing',
kind: data.kind,
subtype: data.subtype,
description: data.description,
startTime: Date.now(),
outputLines: [],
outputBuffer: {
stdout: '',
stderr: '',
combined: '',
},
};
set(
(state) => {
const currentCalls = state.nodeToolCalls[nodeId] || [];
return {
nodeToolCalls: {
...state.nodeToolCalls,
[nodeId]: [...currentCalls, newToolCall],
},
};
},
false,
'startToolCall'
);
},
updateToolCall: (
nodeId: string,
callId: string,
update: {
status?: ToolCallExecution['status'];
outputChunk?: string;
stream?: 'stdout' | 'stderr';
}
) => {
set(
(state) => {
const calls = state.nodeToolCalls[nodeId];
if (!calls) return state;
const index = calls.findIndex((c) => c.callId === callId);
if (index === -1) return state;
const updatedCalls = [...calls];
const current = updatedCalls[index];
// Update status if provided
if (update.status) {
current.status = update.status;
if (update.status !== 'executing' && !current.endTime) {
current.endTime = Date.now();
current.duration = current.endTime - current.startTime;
}
}
// Append output chunk if provided
if (update.outputChunk !== undefined) {
const outputLine: CliOutputLine = {
type: update.stream === 'stderr' ? 'stderr' : 'stdout',
content: update.outputChunk,
timestamp: Date.now(),
};
current.outputLines.push(outputLine);
// Update buffer
if (update.stream === 'stderr') {
current.outputBuffer.stderr += update.outputChunk;
current.outputBuffer.combined += update.outputChunk;
} else {
current.outputBuffer.stdout += update.outputChunk;
current.outputBuffer.combined += update.outputChunk;
}
}
updatedCalls[index] = current;
return {
nodeToolCalls: {
...state.nodeToolCalls,
[nodeId]: updatedCalls,
},
};
},
false,
'updateToolCall'
);
},
completeToolCall: (
nodeId: string,
callId: string,
result: {
status: ToolCallExecution['status'];
exitCode?: number;
error?: string;
result?: unknown;
}
) => {
set(
(state) => {
const calls = state.nodeToolCalls[nodeId];
if (!calls) return state;
const index = calls.findIndex((c) => c.callId === callId);
if (index === -1) return state;
const updatedCalls = [...calls];
const current = { ...updatedCalls[index] };
current.status = result.status;
current.endTime = Date.now();
current.duration = current.endTime - current.startTime;
if (result.exitCode !== undefined) {
current.exitCode = result.exitCode;
}
if (result.error !== undefined) {
current.error = result.error;
}
if (result.result !== undefined) {
current.result = result.result;
}
updatedCalls[index] = current;
return {
nodeToolCalls: {
...state.nodeToolCalls,
[nodeId]: updatedCalls,
},
};
},
false,
'completeToolCall'
);
},
toggleToolCallExpanded: (nodeId: string, callId: string) => {
set(
(state) => {
const calls = state.nodeToolCalls[nodeId];
if (!calls) return state;
const index = calls.findIndex((c) => c.callId === callId);
if (index === -1) return state;
const updatedCalls = [...calls];
updatedCalls[index] = {
...updatedCalls[index],
isExpanded: !updatedCalls[index].isExpanded,
};
return {
nodeToolCalls: {
...state.nodeToolCalls,
[nodeId]: updatedCalls,
},
};
},
false,
'toggleToolCallExpanded'
);
},
// ========== Node Selection ==========
selectNode: (nodeId: string | null) => {
set({ selectedNodeId: nodeId }, false, 'selectNode');
},
// ========== Getters ==========
getNodeOutputs: (nodeId: string) => {
return get().nodeOutputs[nodeId];
},
getToolCallsForNode: (nodeId: string) => {
return get().nodeToolCalls[nodeId] || [];
},
}),
{ name: 'ExecutionStore' }
)
@@ -216,6 +470,13 @@ export const selectLogs = (state: ExecutionStore) => state.logs;
export const selectIsMonitorPanelOpen = (state: ExecutionStore) => state.isMonitorPanelOpen;
export const selectAutoScrollLogs = (state: ExecutionStore) => state.autoScrollLogs;
// Node output selectors (new)
export const selectNodeOutputs = (state: ExecutionStore, nodeId: string) =>
state.nodeOutputs[nodeId];
export const selectNodeToolCalls = (state: ExecutionStore, nodeId: string) =>
state.nodeToolCalls[nodeId] || [];
export const selectSelectedNodeId = (state: ExecutionStore) => state.selectedNodeId;
// Helper to check if execution is active
export const selectIsExecuting = (state: ExecutionStore) => {
return state.currentExecution?.status === 'running';
@@ -225,3 +486,9 @@ export const selectIsExecuting = (state: ExecutionStore) => {
export const selectNodeStatus = (nodeId: string) => (state: ExecutionStore) => {
return state.nodeStates[nodeId]?.status ?? 'pending';
};
// Helper to get selected node's tool calls
export const selectSelectedNodeToolCalls = (state: ExecutionStore) => {
if (!state.selectedNodeId) return [];
return state.nodeToolCalls[state.selectedNodeId] || [];
};