mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-15 02:42:45 +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:
@@ -5,7 +5,8 @@
|
||||
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { A2UIComponentRegistry, a2uiRegistry } from '../core/A2UIComponentRegistry';
|
||||
import type { A2UIComponent, A2UIState, ActionHandler, BindingResolver } from '../core/A2UIComponentRegistry';
|
||||
import type { A2UIState, ActionHandler, BindingResolver } from '../core/A2UIComponentRegistry';
|
||||
import type { A2UIComponent } from '../core/A2UITypes';
|
||||
|
||||
// Import component renderers to trigger auto-registration
|
||||
import '../renderer/components';
|
||||
@@ -24,15 +25,15 @@ describe('A2UIComponentRegistry', () => {
|
||||
|
||||
describe('register()', () => {
|
||||
it('should register a component renderer', () => {
|
||||
registry.register('TestComponent', mockRenderer);
|
||||
expect(registry.has('TestComponent')).toBe(true);
|
||||
registry.register('TestComponent' as any, mockRenderer);
|
||||
expect(registry.has('TestComponent' as any)).toBe(true);
|
||||
});
|
||||
|
||||
it('should allow overriding existing renderer', () => {
|
||||
registry.register('TestComponent', mockRenderer);
|
||||
registry.register('TestComponent', anotherMockRenderer);
|
||||
|
||||
const retrieved = registry.get('TestComponent');
|
||||
registry.register('TestComponent' as any, mockRenderer);
|
||||
registry.register('TestComponent' as any, anotherMockRenderer);
|
||||
|
||||
const retrieved = registry.get('TestComponent' as any);
|
||||
expect(retrieved).toBe(anotherMockRenderer);
|
||||
});
|
||||
|
||||
@@ -49,57 +50,57 @@ describe('A2UIComponentRegistry', () => {
|
||||
|
||||
describe('unregister()', () => {
|
||||
it('should remove a registered renderer', () => {
|
||||
registry.register('TestComponent', mockRenderer);
|
||||
expect(registry.has('TestComponent')).toBe(true);
|
||||
registry.register('TestComponent' as any, mockRenderer);
|
||||
expect(registry.has('TestComponent' as any)).toBe(true);
|
||||
|
||||
registry.unregister('TestComponent');
|
||||
expect(registry.has('TestComponent')).toBe(false);
|
||||
registry.unregister('TestComponent' as any);
|
||||
expect(registry.has('TestComponent' as any)).toBe(false);
|
||||
});
|
||||
|
||||
it('should be idempotent for non-existent components', () => {
|
||||
expect(() => registry.unregister('NonExistent')).not.toThrow();
|
||||
expect(registry.has('NonExistent')).toBe(false);
|
||||
expect(() => registry.unregister('NonExistent' as any)).not.toThrow();
|
||||
expect(registry.has('NonExistent' as any)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('get()', () => {
|
||||
it('should return registered renderer', () => {
|
||||
registry.register('TestComponent', mockRenderer);
|
||||
const retrieved = registry.get('TestComponent');
|
||||
registry.register('TestComponent' as any, mockRenderer);
|
||||
const retrieved = registry.get('TestComponent' as any);
|
||||
|
||||
expect(retrieved).toBe(mockRenderer);
|
||||
});
|
||||
|
||||
it('should return undefined for unregistered component', () => {
|
||||
const retrieved = registry.get('NonExistent');
|
||||
const retrieved = registry.get('NonExistent' as any);
|
||||
expect(retrieved).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return correct renderer after multiple registrations', () => {
|
||||
registry.register('First', mockRenderer);
|
||||
registry.register('Second', anotherMockRenderer);
|
||||
registry.register('First' as any, mockRenderer);
|
||||
registry.register('Second' as any, anotherMockRenderer);
|
||||
|
||||
expect(registry.get('First')).toBe(mockRenderer);
|
||||
expect(registry.get('Second')).toBe(anotherMockRenderer);
|
||||
expect(registry.get('First' as any)).toBe(mockRenderer);
|
||||
expect(registry.get('Second' as any)).toBe(anotherMockRenderer);
|
||||
});
|
||||
});
|
||||
|
||||
describe('has()', () => {
|
||||
it('should return true for registered components', () => {
|
||||
registry.register('TestComponent', mockRenderer);
|
||||
expect(registry.has('TestComponent')).toBe(true);
|
||||
registry.register('TestComponent' as any, mockRenderer);
|
||||
expect(registry.has('TestComponent' as any)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for unregistered components', () => {
|
||||
expect(registry.has('NonExistent')).toBe(false);
|
||||
expect(registry.has('NonExistent' as any)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false after unregistering', () => {
|
||||
registry.register('TestComponent', mockRenderer);
|
||||
expect(registry.has('TestComponent')).toBe(true);
|
||||
registry.register('TestComponent' as any, mockRenderer);
|
||||
expect(registry.has('TestComponent' as any)).toBe(true);
|
||||
|
||||
registry.unregister('TestComponent');
|
||||
expect(registry.has('TestComponent')).toBe(false);
|
||||
registry.unregister('TestComponent' as any);
|
||||
expect(registry.has('TestComponent' as any)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -149,7 +150,7 @@ describe('A2UIComponentRegistry', () => {
|
||||
});
|
||||
|
||||
it('should be idempotent', () => {
|
||||
registry.register('Test', mockRenderer);
|
||||
registry.register('Test' as any, mockRenderer);
|
||||
registry.clear();
|
||||
expect(registry.size).toBe(0);
|
||||
|
||||
|
||||
@@ -376,15 +376,13 @@ describe('A2UIParser', () => {
|
||||
code: z.ZodIssueCode.invalid_type,
|
||||
path: ['components', 0, 'id'],
|
||||
expected: 'string',
|
||||
received: 'undefined',
|
||||
message: 'Required',
|
||||
},
|
||||
} as any,
|
||||
{
|
||||
code: z.ZodIssueCode.invalid_string,
|
||||
code: 'invalid_format' as any,
|
||||
path: ['surfaceId'],
|
||||
validation: 'uuid',
|
||||
message: 'Invalid format',
|
||||
},
|
||||
} as any,
|
||||
]);
|
||||
|
||||
const parseError = new A2UIParseError('Validation failed', zodError);
|
||||
|
||||
@@ -105,14 +105,14 @@ export class A2UIParser {
|
||||
* @param json - JSON string to parse
|
||||
* @returns Result object with success flag and data or error
|
||||
*/
|
||||
safeParse(json: string): z.SafeParseReturnType<SurfaceUpdate, SurfaceUpdate> {
|
||||
safeParse(json: string): ReturnType<typeof SurfaceUpdateSchema.safeParse> {
|
||||
try {
|
||||
const data = JSON.parse(json);
|
||||
return SurfaceUpdateSchema.safeParse(data);
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false as const,
|
||||
error: error as z.ZodError,
|
||||
error: error as any,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -122,7 +122,7 @@ export class A2UIParser {
|
||||
* @param data - Object to validate
|
||||
* @returns Result object with success flag and data or error
|
||||
*/
|
||||
safeParseObject(data: unknown): z.SafeParseReturnType<SurfaceUpdate, SurfaceUpdate> {
|
||||
safeParseObject(data: unknown): ReturnType<typeof SurfaceUpdateSchema.safeParse> {
|
||||
return SurfaceUpdateSchema.safeParse(data);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ export const BooleanContentSchema = z.union([
|
||||
/** Action trigger */
|
||||
export const ActionSchema = z.object({
|
||||
actionId: z.string(),
|
||||
parameters: z.record(z.unknown()).optional(),
|
||||
parameters: z.record(z.string(), z.unknown()).optional(),
|
||||
});
|
||||
|
||||
/** Text component */
|
||||
@@ -196,7 +196,7 @@ export const DisplayModeSchema = z.enum(['popup', 'panel']);
|
||||
export const SurfaceUpdateSchema = z.object({
|
||||
surfaceId: z.string(),
|
||||
components: z.array(SurfaceComponentSchema),
|
||||
initialState: z.record(z.unknown()).optional(),
|
||||
initialState: z.record(z.string(), z.unknown()).optional(),
|
||||
/** Display mode: 'popup' for centered dialog, 'panel' for notification panel */
|
||||
displayMode: DisplayModeSchema.optional(),
|
||||
});
|
||||
|
||||
@@ -162,7 +162,7 @@ export function resolveLiteralOrBinding(
|
||||
const value = resolveBinding(content as Binding);
|
||||
|
||||
// Return resolved value or empty string as fallback
|
||||
return value ?? '';
|
||||
return (value ?? '') as string | number | boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,7 @@ function highlightSyntax(output: string, language?: string): React.ReactNode {
|
||||
const lines = output.split('\n');
|
||||
|
||||
// Define syntax patterns by language
|
||||
const patterns: Record<string, RegExp[]> = {
|
||||
const patterns: Record<string, { regex: RegExp; className: string }[]> = {
|
||||
bash: [
|
||||
{ regex: /^(\$|>|\s)(\s*)/gm, className: 'text-muted-foreground' }, // Prompt
|
||||
{ regex: /\b(error|fail|failed|failure)\b/gi, className: 'text-destructive font-semibold' },
|
||||
@@ -55,8 +55,8 @@ function highlightSyntax(output: string, language?: string): React.ReactNode {
|
||||
|
||||
for (const pattern of langPatterns) {
|
||||
if (typeof result === 'string') {
|
||||
const parts = result.split(pattern.regex);
|
||||
result = parts.map((part, i) => {
|
||||
const parts: string[] = result.split(pattern.regex);
|
||||
result = parts.map((part: string, i: number) => {
|
||||
if (pattern.regex.test(part)) {
|
||||
return (
|
||||
<span key={`${key}-${i}`} className={pattern.className}>
|
||||
|
||||
@@ -23,7 +23,7 @@ export const A2UIText: ComponentRenderer = ({ component, state, onAction, resolv
|
||||
const { Text } = component as { Text: { text: unknown; usageHint?: string } };
|
||||
|
||||
// Resolve text content
|
||||
const text = resolveTextContent(Text.text, resolveBinding);
|
||||
const text = resolveTextContent(Text.text as { literalString: string } | { path: string }, resolveBinding);
|
||||
const usageHint = Text.usageHint || 'span';
|
||||
|
||||
// Map usageHint to HTML elements
|
||||
|
||||
Reference in New Issue
Block a user