mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
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:
@@ -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] || [];
|
||||
};
|
||||
|
||||
@@ -46,6 +46,42 @@ const generateId = (prefix: string): string => {
|
||||
// API base URL
|
||||
const API_BASE = '/api/orchestrator';
|
||||
|
||||
// Non-overlapping position calculation constants
|
||||
const OVERLAP_THRESHOLD = 50; // px distance to consider as overlap
|
||||
const OFFSET_X = 100; // diagonal offset per attempt
|
||||
const OFFSET_Y = 80;
|
||||
const MAX_ATTEMPTS = 20;
|
||||
|
||||
/**
|
||||
* Calculate a position that does not overlap with existing nodes.
|
||||
* Shifts diagonally (x+100, y+80) until a free spot is found.
|
||||
*/
|
||||
function calculateNonOverlappingPosition(
|
||||
baseX: number,
|
||||
baseY: number,
|
||||
existingNodes: { position: { x: number; y: number } }[],
|
||||
): { x: number; y: number } {
|
||||
let x = baseX;
|
||||
let y = baseY;
|
||||
|
||||
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
||||
const hasOverlap = existingNodes.some((node) => {
|
||||
const dx = Math.abs(node.position.x - x);
|
||||
const dy = Math.abs(node.position.y - y);
|
||||
return dx < OVERLAP_THRESHOLD && dy < OVERLAP_THRESHOLD;
|
||||
});
|
||||
|
||||
if (!hasOverlap) {
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
x += OFFSET_X;
|
||||
y += OFFSET_Y;
|
||||
}
|
||||
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
// Initial state
|
||||
const initialState = {
|
||||
// Current flow
|
||||
@@ -71,6 +107,9 @@ const initialState = {
|
||||
|
||||
// Custom templates (loaded from localStorage)
|
||||
customTemplates: loadCustomTemplatesFromStorage(),
|
||||
|
||||
// Interaction mode
|
||||
interactionMode: 'pan' as const,
|
||||
};
|
||||
|
||||
export const useFlowStore = create<FlowStore>()(
|
||||
@@ -263,11 +302,14 @@ export const useFlowStore = create<FlowStore>()(
|
||||
addNode: (position: { x: number; y: number }): string => {
|
||||
const config = nodeConfigs['prompt-template'];
|
||||
const id = generateId('node');
|
||||
const safePosition = calculateNonOverlappingPosition(
|
||||
position.x, position.y, get().nodes,
|
||||
);
|
||||
|
||||
const newNode: FlowNode = {
|
||||
id,
|
||||
type: 'prompt-template',
|
||||
position,
|
||||
position: safePosition,
|
||||
data: { ...config.defaultData },
|
||||
};
|
||||
|
||||
@@ -295,6 +337,9 @@ export const useFlowStore = create<FlowStore>()(
|
||||
|
||||
const id = generateId('node');
|
||||
const config = nodeConfigs['prompt-template'];
|
||||
const safePosition = calculateNonOverlappingPosition(
|
||||
position.x, position.y, get().nodes,
|
||||
);
|
||||
|
||||
// Merge template data with default data
|
||||
const nodeData: NodeData = {
|
||||
@@ -307,7 +352,7 @@ export const useFlowStore = create<FlowStore>()(
|
||||
const newNode: FlowNode = {
|
||||
id,
|
||||
type: 'prompt-template',
|
||||
position,
|
||||
position: safePosition,
|
||||
data: nodeData,
|
||||
};
|
||||
|
||||
@@ -462,6 +507,22 @@ export const useFlowStore = create<FlowStore>()(
|
||||
set({ leftPanelTab: tab }, false, 'setLeftPanelTab');
|
||||
},
|
||||
|
||||
// ========== Interaction Mode ==========
|
||||
|
||||
toggleInteractionMode: () => {
|
||||
set(
|
||||
(state) => ({
|
||||
interactionMode: state.interactionMode === 'pan' ? 'selection' : 'pan',
|
||||
}),
|
||||
false,
|
||||
'toggleInteractionMode'
|
||||
);
|
||||
},
|
||||
|
||||
setInteractionMode: (mode: 'pan' | 'selection') => {
|
||||
set({ interactionMode: mode }, false, 'setInteractionMode');
|
||||
},
|
||||
|
||||
// ========== Custom Templates ==========
|
||||
|
||||
addCustomTemplate: (template: QuickTemplate) => {
|
||||
|
||||
Reference in New Issue
Block a user